Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f759577ce | ||
|
|
04376ca047 | ||
|
|
7ac108f495 | ||
|
|
a2eece0f5c | ||
|
|
a03279df3b | ||
|
|
bef6c2b665 | ||
|
|
8002c65693 | ||
|
|
5c7247b8b5 | ||
|
|
1a18e68441 | ||
|
|
72afa55897 | ||
|
|
eed9fca10c | ||
|
|
25d97e187a | ||
|
|
7ca2ca8045 | ||
|
|
54885c3e5f | ||
|
|
b8ffb6e53d | ||
|
|
7bc43c226d | ||
|
|
da4697794d | ||
|
|
879c524658 | ||
|
|
e3a8f010c3 | ||
|
|
f2f8f92172 | ||
|
|
265ec96c6f | ||
|
|
6e06174db6 | ||
|
|
9677569293 | ||
|
|
c8e00195ed | ||
|
|
64bc189010 | ||
|
|
12496e4432 | ||
|
|
9e2fd67487 | ||
|
|
af17d56cbd | ||
|
|
6e0a657b76 | ||
|
|
b0a9fc7085 | ||
|
|
5e47b8a835 | ||
|
|
0b94ad1fef | ||
|
|
9dcd856099 | ||
|
|
cfbcc4093c | ||
|
|
b8169ae08b | ||
|
|
103625fbdd | ||
|
|
4712080a1b | ||
|
|
93c683be3a | ||
|
|
c9a7c15f98 | ||
|
|
64a08f0533 | ||
|
|
52c6455ca5 | ||
|
|
238681d72f | ||
|
|
dbcc6feb3f | ||
|
|
300b53f28c | ||
|
|
3dabb1c891 | ||
|
|
4381f7a973 | ||
|
|
77b767ae5b | ||
|
|
2c4f78133f | ||
|
|
ec2c181f4e | ||
|
|
fae98eb13a | ||
|
|
f1d117d5d7 | ||
|
|
529ac8041a | ||
|
|
391e84e0f4 | ||
|
|
6a2b717ff1 | ||
|
|
475a39cb73 | ||
|
|
4dba74e2f9 | ||
|
|
587d783955 | ||
|
|
3bfe87d516 | ||
|
|
d3e43012fb | ||
|
|
5da156b433 | ||
|
|
b2eeb63b6f | ||
|
|
a7517dace3 | ||
|
|
bd9f2d2009 | ||
|
|
78730f7235 | ||
|
|
8a2491d955 | ||
|
|
0d68faf40c | ||
|
|
957a2b2169 | ||
|
|
856318f947 | ||
|
|
4df9d6b20a | ||
|
|
8519d09141 | ||
|
|
1b4fe83739 | ||
|
|
8fc825b2eb | ||
|
|
6d0fc22ab5 | ||
|
|
9a71b4c237 | ||
|
|
9aa487418f | ||
|
|
203b2fcaca | ||
|
|
dae21f2638 | ||
|
|
af6e99cbac | ||
|
|
6d6acf4308 | ||
|
|
7dd4815edf | ||
|
|
2c068a99a3 | ||
|
|
fa93364ddd | ||
|
|
9b1eef3f63 | ||
|
|
ed6c07fd4b | ||
|
|
5ad96f5287 | ||
|
|
fa8845c7e2 | ||
|
|
b96ace7f04 | ||
|
|
234a8dc3d5 | ||
|
|
0467c2f893 | ||
|
|
762fe92933 | ||
|
|
16d105d462 | ||
|
|
8a8b624f47 | ||
|
|
9470615c63 | ||
|
|
8f0ea700a5 | ||
|
|
b7cbec0b1c | ||
|
|
9be9934b8c | ||
|
|
55f0e1e1d5 | ||
|
|
bbd784fc85 | ||
|
|
408f93732b |
@@ -20,6 +20,7 @@ matrix:
|
||||
- qt59base
|
||||
- qt59quickcontrols2
|
||||
- qt59svg
|
||||
- qt59multimedia
|
||||
- libesd0-dev
|
||||
- libgl-dev
|
||||
- libegl1-mesa-dev
|
||||
|
||||
@@ -13,8 +13,8 @@ You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
|
||||
|
||||
## Features
|
||||
- Hardware accelerated video decoding on Windows, Mac, and Linux
|
||||
- Supports streaming at up to 120 FPS on high refresh rate monitors
|
||||
- Supports streaming at 720p, 1080p, 1440p, or 4K
|
||||
- Supports streaming at up to 120 FPS (high refresh rate monitor recommended)
|
||||
- Supports streaming at 720p, 1080p, 1440p, 4K, and the client PC's native screen resolution
|
||||
- 5.1 surround sound audio
|
||||
- HEVC support for better image quality at reduced bandwidth
|
||||
- Keyboard and mouse support
|
||||
|
||||
+2
-2
@@ -23,9 +23,9 @@
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.3.0</string>
|
||||
<string>0.5.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.0</string>
|
||||
<string>0.5.0</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Moonlight</string>
|
||||
</dict>
|
||||
|
||||
+31
-13
@@ -1,4 +1,4 @@
|
||||
QT += core quick network quickcontrols2 svg
|
||||
QT += core quick network quickcontrols2 svg multimedia
|
||||
CONFIG += c++11
|
||||
|
||||
unix:!macx {
|
||||
@@ -11,6 +11,9 @@ unix:!macx {
|
||||
# Support debug and release builds from command line for CI
|
||||
CONFIG += debug_and_release
|
||||
|
||||
# Ensure symbols are always generated
|
||||
CONFIG += force_debug_info
|
||||
|
||||
TEMPLATE = app
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
@@ -34,7 +37,7 @@ win32 {
|
||||
LIBS += -L$$PWD/../libs/windows/lib/x64
|
||||
}
|
||||
|
||||
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib
|
||||
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib dbghelp.lib
|
||||
}
|
||||
macx {
|
||||
INCLUDEPATH += $$PWD/../libs/mac/include $$PWD/../libs/mac/Frameworks/SDL2.framework/Versions/A/Headers
|
||||
@@ -84,7 +87,9 @@ SOURCES += \
|
||||
settings/streamingpreferences.cpp \
|
||||
streaming/input.cpp \
|
||||
streaming/session.cpp \
|
||||
streaming/audio.cpp \
|
||||
streaming/audio/audio.cpp \
|
||||
streaming/audio/renderers/sdlaud.cpp \
|
||||
streaming/audio/renderers/qtaud.cpp \
|
||||
gui/computermodel.cpp \
|
||||
gui/appmodel.cpp \
|
||||
streaming/streamutils.cpp \
|
||||
@@ -101,6 +106,9 @@ HEADERS += \
|
||||
settings/streamingpreferences.h \
|
||||
streaming/input.hpp \
|
||||
streaming/session.hpp \
|
||||
streaming/audio/renderers/renderer.h \
|
||||
streaming/audio/renderers/sdl.h \
|
||||
streaming/audio/renderers/qtaud.h \
|
||||
gui/computermodel.h \
|
||||
gui/appmodel.h \
|
||||
streaming/video/decoder.h \
|
||||
@@ -115,7 +123,7 @@ ffmpeg {
|
||||
DEFINES += HAVE_FFMPEG
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg.cpp \
|
||||
streaming/video/ffmpeg-renderers/sdl.cpp \
|
||||
streaming/video/ffmpeg-renderers/sdlvid.cpp \
|
||||
streaming/video/ffmpeg-renderers/pacer/pacer.cpp \
|
||||
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.cpp
|
||||
|
||||
@@ -177,7 +185,7 @@ macx {
|
||||
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg-renderers/vt.mm \
|
||||
streaming/video/ffmpeg-renderers/pacer/displaylinkvsyncsource.cpp
|
||||
streaming/video/ffmpeg-renderers/pacer/displaylinkvsyncsource.mm
|
||||
|
||||
HEADERS += \
|
||||
streaming/video/ffmpeg-renderers/vt.h \
|
||||
@@ -215,32 +223,42 @@ unix:!macx: {
|
||||
isEmpty(BINDIR) {
|
||||
BINDIR = bin
|
||||
}
|
||||
isEmpty(DATADIR) {
|
||||
DATADIR = share
|
||||
}
|
||||
|
||||
target.path = $$PREFIX/$$BINDIR/
|
||||
|
||||
desktop.files = deploy/linux/com.moonlight_stream.Moonlight.desktop
|
||||
desktop.path = $$PREFIX/share/applications/
|
||||
desktop.path = $$PREFIX/$$DATADIR/applications/
|
||||
|
||||
icons.files = res/moonlight.svg
|
||||
icons.path = $$PREFIX/share/icons/hicolor/scalable/apps/
|
||||
icons.path = $$PREFIX/$$DATADIR/icons/hicolor/scalable/apps/
|
||||
|
||||
appdata.files = deploy/linux/com.moonlight_stream.Moonlight.appdata.xml
|
||||
appdata.path = $$PREFIX/share/metainfo/
|
||||
appstream.files = deploy/linux/com.moonlight_stream.Moonlight.appdata.xml
|
||||
appstream.path = $$PREFIX/$$DATADIR/metainfo/
|
||||
|
||||
INSTALLS += target desktop icons appdata
|
||||
appdata.files = gamecontrollerdb.txt
|
||||
appdata.path = "$$PREFIX/$$DATADIR/Moonlight Game Streaming Project/Moonlight/"
|
||||
|
||||
INSTALLS += target appdata desktop icons appstream
|
||||
}
|
||||
win32 {
|
||||
RC_ICONS = moonlight.ico
|
||||
QMAKE_TARGET_COMPANY = Moonlight Game Streaming Project
|
||||
QMAKE_TARGET_DESCRIPTION = Moonlight Game Streaming Client
|
||||
QMAKE_TARGET_PRODUCT = Moonlight
|
||||
|
||||
CONFIG -= embed_manifest_exe
|
||||
QMAKE_LFLAGS += /MANIFEST:embed /MANIFESTINPUT:$${PWD}/Moonlight.exe.manifest
|
||||
}
|
||||
macx {
|
||||
QMAKE_INFO_PLIST = $$PWD/Info.plist
|
||||
|
||||
APP_BUNDLE_RESOURCES.files = moonlight.icns
|
||||
APP_BUNDLE_RESOURCES.files = moonlight.icns gamecontrollerdb.txt
|
||||
APP_BUNDLE_RESOURCES.path = Contents/Resources
|
||||
QMAKE_BUNDLE_DATA += APP_BUNDLE_RESOURCES
|
||||
}
|
||||
|
||||
VERSION = 0.3.0
|
||||
DEFINES += VERSION_STR=\\\"0.3.0\\\"
|
||||
VERSION = 0.5.0
|
||||
DEFINES += VERSION_STR=\\\"0.5.0\\\"
|
||||
|
||||
@@ -6,8 +6,14 @@
|
||||
|
||||
BoxArtManager::BoxArtManager(QObject *parent) :
|
||||
QObject(parent),
|
||||
m_BoxArtDir(Path::getBoxArtCacheDir())
|
||||
m_BoxArtDir(Path::getBoxArtCacheDir()),
|
||||
m_ThreadPool(this)
|
||||
{
|
||||
// 4 is a good balance between fast loading for large
|
||||
// app grids and not crushing GFE with tons of requests
|
||||
// and causing UI jank from constantly stalling to decode
|
||||
// new images.
|
||||
m_ThreadPool.setMaxThreadCount(4);
|
||||
if (!m_BoxArtDir.exists()) {
|
||||
m_BoxArtDir.mkpath(".");
|
||||
}
|
||||
@@ -41,7 +47,7 @@ QUrl BoxArtManager::loadBoxArt(NvComputer* computer, NvApp& app)
|
||||
// If we get here, we need to fetch asynchronously.
|
||||
// Kick off a worker on our thread pool to do just that.
|
||||
NetworkBoxArtLoadTask* netLoadTask = new NetworkBoxArtLoadTask(this, computer, app);
|
||||
QThreadPool::globalInstance()->start(netLoadTask);
|
||||
m_ThreadPool.start(netLoadTask);
|
||||
|
||||
// Return the placeholder then we can notify the caller
|
||||
// later when the real image is ready.
|
||||
|
||||
@@ -36,6 +36,7 @@ private:
|
||||
getFilePathForBoxArt(NvComputer* computer, int appId);
|
||||
|
||||
QDir m_BoxArtDir;
|
||||
QThreadPool m_ThreadPool;
|
||||
};
|
||||
|
||||
class NetworkBoxArtLoadTask : public QObject, public QRunnable
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "computermanager.h"
|
||||
#include "nvhttp.h"
|
||||
#include "settings/streamingpreferences.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QUdpSocket>
|
||||
@@ -92,7 +93,7 @@ void NvComputer::sortAppList()
|
||||
NvComputer::NvComputer(QString address, QString serverInfo)
|
||||
{
|
||||
this->name = NvHTTP::getXmlString(serverInfo, "hostname");
|
||||
if (this->name.isNull()) {
|
||||
if (this->name.isEmpty()) {
|
||||
this->name = "UNKNOWN";
|
||||
}
|
||||
|
||||
@@ -106,7 +107,7 @@ NvComputer::NvComputer(QString address, QString serverInfo)
|
||||
}
|
||||
|
||||
QString codecSupport = NvHTTP::getXmlString(serverInfo, "ServerCodecModeSupport");
|
||||
if (!codecSupport.isNull()) {
|
||||
if (!codecSupport.isEmpty()) {
|
||||
this->serverCodecModeSupport = codecSupport.toInt();
|
||||
}
|
||||
else {
|
||||
@@ -114,7 +115,7 @@ NvComputer::NvComputer(QString address, QString serverInfo)
|
||||
}
|
||||
|
||||
QString maxLumaPixelsHEVC = NvHTTP::getXmlString(serverInfo, "MaxLumaPixelsHEVC");
|
||||
if (!maxLumaPixelsHEVC.isNull()) {
|
||||
if (!maxLumaPixelsHEVC.isEmpty()) {
|
||||
this->maxLumaPixelsHEVC = maxLumaPixelsHEVC.toInt();
|
||||
}
|
||||
else {
|
||||
@@ -148,7 +149,7 @@ bool NvComputer::wake()
|
||||
return true;
|
||||
}
|
||||
|
||||
if (macAddress.isNull()) {
|
||||
if (macAddress.isEmpty()) {
|
||||
qWarning() << name << "has no MAC address stored";
|
||||
return false;
|
||||
}
|
||||
@@ -177,6 +178,11 @@ bool NvComputer::wake()
|
||||
for (QString& addressString : addressList) {
|
||||
QHostInfo hostInfo = QHostInfo::fromName(addressString);
|
||||
|
||||
if (hostInfo.error() != QHostInfo::NoError) {
|
||||
qWarning() << "Error resolving" << addressString << ":" << hostInfo.errorString();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try all IP addresses that this string resolves to
|
||||
for (QHostAddress& address : hostInfo.addresses()) {
|
||||
QUdpSocket sock;
|
||||
@@ -211,7 +217,7 @@ QVector<QString> NvComputer::uniqueAddresses()
|
||||
|
||||
// Prune duplicates (always giving precedence to the first)
|
||||
for (int i = 0; i < uniqueAddressList.count(); i++) {
|
||||
if (uniqueAddressList[i].isEmpty() || uniqueAddressList[i].isNull()) {
|
||||
if (uniqueAddressList[i].isEmpty()) {
|
||||
uniqueAddressList.remove(i);
|
||||
i--;
|
||||
continue;
|
||||
@@ -363,17 +369,24 @@ void ComputerManager::startPolling()
|
||||
return;
|
||||
}
|
||||
|
||||
// Start an MDNS query for GameStream hosts
|
||||
m_MdnsBrowser = new QMdnsEngine::Browser(&m_MdnsServer, "_nvstream._tcp.local.", &m_MdnsCache);
|
||||
connect(m_MdnsBrowser, &QMdnsEngine::Browser::serviceAdded,
|
||||
this, [this](const QMdnsEngine::Service& service) {
|
||||
qInfo() << "Discovered mDNS host:" << service.hostname();
|
||||
StreamingPreferences prefs;
|
||||
|
||||
MdnsPendingComputer* pendingComputer = new MdnsPendingComputer(&m_MdnsServer, &m_MdnsCache, service);
|
||||
connect(pendingComputer, SIGNAL(resolvedv4(MdnsPendingComputer*,QHostAddress)),
|
||||
this, SLOT(handleMdnsServiceResolved(MdnsPendingComputer*,QHostAddress)));
|
||||
m_PendingResolution.append(pendingComputer);
|
||||
});
|
||||
if (prefs.enableMdns) {
|
||||
// Start an MDNS query for GameStream hosts
|
||||
m_MdnsBrowser = new QMdnsEngine::Browser(&m_MdnsServer, "_nvstream._tcp.local.", &m_MdnsCache);
|
||||
connect(m_MdnsBrowser, &QMdnsEngine::Browser::serviceAdded,
|
||||
this, [this](const QMdnsEngine::Service& service) {
|
||||
qInfo() << "Discovered mDNS host:" << service.hostname();
|
||||
|
||||
MdnsPendingComputer* pendingComputer = new MdnsPendingComputer(&m_MdnsServer, &m_MdnsCache, service);
|
||||
connect(pendingComputer, SIGNAL(resolvedv4(MdnsPendingComputer*,QHostAddress)),
|
||||
this, SLOT(handleMdnsServiceResolved(MdnsPendingComputer*,QHostAddress)));
|
||||
m_PendingResolution.append(pendingComputer);
|
||||
});
|
||||
}
|
||||
else {
|
||||
qWarning() << "mDNS is disabled by user preference";
|
||||
}
|
||||
|
||||
// Start polling threads for each known host
|
||||
QMapIterator<QString, NvComputer*> i(m_KnownHosts);
|
||||
|
||||
@@ -103,7 +103,7 @@ private:
|
||||
|
||||
QString serverInfo;
|
||||
try {
|
||||
serverInfo = http.getServerInfo();
|
||||
serverInfo = http.getServerInfo(NvHTTP::NvLogLevel::NONE);
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
@@ -154,6 +154,7 @@ private:
|
||||
while (!isInterruptionRequested()) {
|
||||
bool stateChanged = false;
|
||||
bool online = false;
|
||||
bool wasOnline = m_Computer->state == NvComputer::CS_ONLINE;
|
||||
for (int i = 0; i < TRIES_BEFORE_OFFLINING && !online; i++) {
|
||||
for (auto& address : m_Computer->uniqueAddresses()) {
|
||||
if (isInterruptionRequested()) {
|
||||
@@ -161,6 +162,9 @@ private:
|
||||
}
|
||||
|
||||
if (tryPollComputer(address, stateChanged)) {
|
||||
if (!wasOnline) {
|
||||
qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress;
|
||||
}
|
||||
online = true;
|
||||
break;
|
||||
}
|
||||
@@ -171,6 +175,7 @@ private:
|
||||
// Note: we don't need to acquire the read lock here,
|
||||
// because we're on the writing thread.
|
||||
if (!online && m_Computer->state != NvComputer::CS_OFFLINE) {
|
||||
qInfo() << m_Computer->name << "is now offline";
|
||||
m_Computer->state = NvComputer::CS_OFFLINE;
|
||||
stateChanged = true;
|
||||
}
|
||||
@@ -180,6 +185,13 @@ private:
|
||||
if (m_Computer->state == NvComputer::CS_ONLINE &&
|
||||
m_Computer->pairState == NvComputer::PS_PAIRED &&
|
||||
(m_Computer->appList.isEmpty() || pollsSinceLastAppListFetch >= POLLS_PER_APPLIST_FETCH)) {
|
||||
// Notify prior to the app list poll since it may take a while, and we don't
|
||||
// want to delay onlining of a machine, especially if we already have a cached list.
|
||||
if (stateChanged) {
|
||||
emit computerStateChanged(m_Computer);
|
||||
stateChanged = false;
|
||||
}
|
||||
|
||||
if (updateAppList(stateChanged)) {
|
||||
pollsSinceLastAppListFetch = 0;
|
||||
}
|
||||
@@ -411,9 +423,11 @@ private:
|
||||
{
|
||||
NvHTTP http(m_Address);
|
||||
|
||||
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user");
|
||||
|
||||
QString serverInfo;
|
||||
try {
|
||||
serverInfo = http.getServerInfo();
|
||||
serverInfo = http.getServerInfo(NvHTTP::NvLogLevel::VERBOSE);
|
||||
} catch (...) {
|
||||
if (!m_Mdns) {
|
||||
emit computerAddCompleted(false);
|
||||
@@ -449,6 +463,7 @@ private:
|
||||
|
||||
// Tell our client if something changed
|
||||
if (changed) {
|
||||
qInfo() << existingComputer->name << "is now at" << existingComputer->activeAddress;
|
||||
emit computerStateChanged(existingComputer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,12 +185,12 @@ IdentityManager::getSslConfig()
|
||||
QString
|
||||
IdentityManager::getUniqueId()
|
||||
{
|
||||
if (m_CachedUniqueId.isNull()) {
|
||||
if (m_CachedUniqueId.isEmpty()) {
|
||||
QSettings settings;
|
||||
|
||||
// Load the unique ID from settings
|
||||
m_CachedUniqueId = settings.value(SER_UNIQUEID).toString();
|
||||
if (!m_CachedUniqueId.isEmpty() && !m_CachedUniqueId.isNull()) {
|
||||
if (!m_CachedUniqueId.isEmpty()) {
|
||||
qInfo() << "Loaded unique ID from settings:" << m_CachedUniqueId;
|
||||
}
|
||||
else {
|
||||
|
||||
+20
-13
@@ -29,9 +29,15 @@ NvHTTP::NvHTTP(QString address) :
|
||||
QVector<int>
|
||||
NvHTTP::parseQuad(QString quad)
|
||||
{
|
||||
QStringList parts = quad.split(".");
|
||||
QVector<int> ret;
|
||||
|
||||
// Return an empty vector for old GFE versions
|
||||
// that were missing GfeVersion.
|
||||
if (quad.isEmpty()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
QStringList parts = quad.split(".");
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
ret.append(parts.at(i).toInt());
|
||||
@@ -58,7 +64,7 @@ NvHTTP::getCurrentGame(QString serverInfo)
|
||||
}
|
||||
|
||||
QString
|
||||
NvHTTP::getServerInfo()
|
||||
NvHTTP::getServerInfo(NvLogLevel logLevel)
|
||||
{
|
||||
QString serverInfo;
|
||||
|
||||
@@ -70,7 +76,7 @@ NvHTTP::getServerInfo()
|
||||
"serverinfo",
|
||||
nullptr,
|
||||
true,
|
||||
true);
|
||||
logLevel);
|
||||
// Throws if the request failed
|
||||
verifyResponseStatus(serverInfo);
|
||||
}
|
||||
@@ -83,7 +89,7 @@ NvHTTP::getServerInfo()
|
||||
"serverinfo",
|
||||
nullptr,
|
||||
true,
|
||||
true);
|
||||
logLevel);
|
||||
verifyResponseStatus(serverInfo);
|
||||
}
|
||||
else
|
||||
@@ -189,7 +195,7 @@ NvHTTP::quitApp()
|
||||
|
||||
// Newer GFE versions will just return success even if quitting fails
|
||||
// if we're not the original requestor.
|
||||
if (getCurrentGame(getServerInfo()) != 0) {
|
||||
if (getCurrentGame(getServerInfo(NvHTTP::ERROR)) != 0) {
|
||||
// Generate a synthetic GfeResponseException letting the caller know
|
||||
// that they can't kill someone else's stream.
|
||||
throw GfeHttpResponseException(599, "");
|
||||
@@ -230,7 +236,7 @@ NvHTTP::getAppList()
|
||||
"applist",
|
||||
nullptr,
|
||||
true,
|
||||
true);
|
||||
NvLogLevel::ERROR);
|
||||
verifyResponseStatus(appxml);
|
||||
|
||||
QXmlStreamReader xmlReader(appxml);
|
||||
@@ -297,7 +303,8 @@ NvHTTP::getBoxArt(int appId)
|
||||
"appasset",
|
||||
"appid="+QString::number(appId)+
|
||||
"&AssetType=2&AssetIdx=0",
|
||||
true);
|
||||
true,
|
||||
NvLogLevel::VERBOSE);
|
||||
QImage image = QImageReader(reply).read();
|
||||
delete reply;
|
||||
|
||||
@@ -344,9 +351,9 @@ NvHTTP::openConnectionToString(QUrl baseUrl,
|
||||
QString command,
|
||||
QString arguments,
|
||||
bool enableTimeout,
|
||||
bool suppressLogging)
|
||||
NvLogLevel logLevel)
|
||||
{
|
||||
QNetworkReply* reply = openConnection(baseUrl, command, arguments, enableTimeout, suppressLogging);
|
||||
QNetworkReply* reply = openConnection(baseUrl, command, arguments, enableTimeout, logLevel);
|
||||
QString ret;
|
||||
|
||||
QTextStream stream(reply);
|
||||
@@ -362,7 +369,7 @@ NvHTTP::openConnection(QUrl baseUrl,
|
||||
QString command,
|
||||
QString arguments,
|
||||
bool enableTimeout,
|
||||
bool suppressLogging)
|
||||
NvLogLevel logLevel)
|
||||
{
|
||||
// Build a URL for the request
|
||||
QUrl url(baseUrl);
|
||||
@@ -388,7 +395,7 @@ NvHTTP::openConnection(QUrl baseUrl,
|
||||
{
|
||||
QTimer::singleShot(REQUEST_TIMEOUT_MS, &loop, SLOT(quit()));
|
||||
}
|
||||
if (!suppressLogging) {
|
||||
if (logLevel >= NvLogLevel::VERBOSE) {
|
||||
qInfo() << "Executing request:" << url.toString();
|
||||
}
|
||||
loop.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
@@ -396,7 +403,7 @@ NvHTTP::openConnection(QUrl baseUrl,
|
||||
// Abort the request if it timed out
|
||||
if (!reply->isFinished())
|
||||
{
|
||||
if (!suppressLogging) {
|
||||
if (logLevel >= NvLogLevel::ERROR) {
|
||||
qWarning() << "Aborting timed out request for" << url.toString();
|
||||
}
|
||||
reply->abort();
|
||||
@@ -409,7 +416,7 @@ NvHTTP::openConnection(QUrl baseUrl,
|
||||
// Handle error
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
if (!suppressLogging) {
|
||||
if (logLevel >= NvLogLevel::ERROR) {
|
||||
qWarning() << command << " request failed with error " << reply->error();
|
||||
}
|
||||
GfeHttpResponseException exception(reply->error(), reply->errorString());
|
||||
|
||||
+10
-4
@@ -17,7 +17,7 @@ public:
|
||||
|
||||
bool isInitialized()
|
||||
{
|
||||
return id != 0 && !name.isNull();
|
||||
return id != 0 && !name.isEmpty();
|
||||
}
|
||||
|
||||
int id;
|
||||
@@ -80,6 +80,12 @@ private:
|
||||
class NvHTTP
|
||||
{
|
||||
public:
|
||||
enum NvLogLevel {
|
||||
NONE,
|
||||
ERROR,
|
||||
VERBOSE
|
||||
};
|
||||
|
||||
explicit NvHTTP(QString address);
|
||||
|
||||
static
|
||||
@@ -87,7 +93,7 @@ public:
|
||||
getCurrentGame(QString serverInfo);
|
||||
|
||||
QString
|
||||
getServerInfo();
|
||||
getServerInfo(NvLogLevel logLevel);
|
||||
|
||||
static
|
||||
void
|
||||
@@ -108,7 +114,7 @@ public:
|
||||
QString command,
|
||||
QString arguments,
|
||||
bool enableTimeout,
|
||||
bool suppressLogging = false);
|
||||
NvLogLevel logLevel = NvLogLevel::VERBOSE);
|
||||
|
||||
static
|
||||
QVector<int>
|
||||
@@ -145,7 +151,7 @@ private:
|
||||
QString command,
|
||||
QString arguments,
|
||||
bool enableTimeout,
|
||||
bool suppressLogging = false);
|
||||
NvLogLevel logLevel);
|
||||
|
||||
QString m_Address;
|
||||
QNetworkAccessManager m_Nam;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<p>Features include:</p>
|
||||
<ul>
|
||||
<li>Streaming at up to 4K resolution</li>
|
||||
<li>Support for up to 120 FPS streaming on high refresh rate monitors</li>
|
||||
<li>Support for up to 120 FPS streaming (high refresh rate monitor recommended)</li>
|
||||
<li>Hardware accelerated video decoding with VAAPI and VDPAU support</li>
|
||||
<li>5.1 surround sound audio support</li>
|
||||
<li>HEVC support for better video compression efficiency</li>
|
||||
@@ -33,6 +33,46 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="0.5.0" date="2018-09-15">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>Added option to enable mouse acceleration for remote desktop usage</li>
|
||||
<li>Added option to disable automatic PC discovery</li>
|
||||
<li>Added gamepad mappings from gabomdq's SDL_GameControllerDB</li>
|
||||
<li>Added help links to error dialogs</li>
|
||||
<li>Added quit shortcut tip to stream loading page</li>
|
||||
<li>Restored the autodetect audio configuration option</li>
|
||||
</ul>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Fixed quit app dialog starting the wrong app</li>
|
||||
<li>Fixed very high input lag on GFE 3.14.0 and earlier</li>
|
||||
<li>Fixed sending errant mouse click when capturing the mouse</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.4.0" date="2018-09-08">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>Added option to enable (unsupported) 90/120 FPS streaming on 60 Hz monitors</li>
|
||||
<li>Added refresh rate matching for better frame pacing on high refresh rate monitors</li>
|
||||
<li>The streaming window now appears on the same monitor the UI was displayed on</li>
|
||||
<li>The streaming window is now centered on the target monitor</li>
|
||||
<li>Display a dialog when the connection is terminated by the remote PC</li>
|
||||
<li>Added option to select between full-screen exclusive and borderless windowed mode</li>
|
||||
</ul>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Fixed streaming with no audio device</li>
|
||||
<li>Fixed mouse lag with some high polling rate mice</li>
|
||||
<li>Improved UI performance on the app grid page</li>
|
||||
<li>1440p and 4K 120 FPS performance is improved to avoid encoder bottlenecks</li>
|
||||
<li>Autodetect audio configuration option removed because it was broken</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.3.0" date="2018-08-21">
|
||||
<description>
|
||||
<p>Bugfixes:</p>
|
||||
|
||||
@@ -0,0 +1,438 @@
|
||||
# Game Controller DB for SDL in 2.0.6 format
|
||||
# Source: https://github.com/gabomdq/SDL_GameControllerDB
|
||||
|
||||
# Windows
|
||||
03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,platform:Windows,
|
||||
03000000022000000090000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
|
||||
03000000203800000900000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
|
||||
03000000c82d00000060000000000000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
|
||||
03000000c82d00000061000000000000,8Bitdo SF30 Pro Wireless,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
|
||||
03000000102800000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,
|
||||
03000000a00500003232000000000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,
|
||||
030000008f0e00001200000000000000,Acme,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000341a00003608000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,platform:Windows,
|
||||
030000006b1400000055000000000000,bigben ps3padstreetnew,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
0300000066f700000500000000000000,BrutalLegendTest,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000d81d00000b00000000000000,BUFFALO BSGP1601 Series ,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Windows,
|
||||
03000000e82000006058000000000000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
030000005e0400008e02000000000000,Controller (XBOX 360 For Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
|
||||
03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Windows,
|
||||
03000000a306000022f6000000000000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000791d00000103000000000000,Dual Box WII,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
030000004f04000023b3000000000000,Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000341a00000108000000000000,EXEQ RF USB Gamepad 8206,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
030000000d0f00008500000000000000,Fighting Commander 2016 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00008400000000000000,Fighting Commander 5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00008700000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00008800000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00002700000000000000,FIGHTING STICK V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
78696e70757403000000000000000000,Fightstick TES,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Windows,
|
||||
03000000790000000600000000000000,G-Shark GS-GP702,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000260900002625000000000000,Gamecube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Windows,
|
||||
030000008f0e00000d31000000000000,GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000280400000140000000000000,GamePad Pro USB,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000ffff00000000000000000000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000451300000010000000000000,Generic USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000341a00000302000000000000,Hama Scorpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00004900000000000000,Hatsune Miku Sho Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000d81400000862000000000000,HitBox Edition Cthulhu+,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00005f00000000000000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00005e00000000000000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00004000000000000000,Hori Fighting Stick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00006e00000000000000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00006600000000000000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f0000ee00000000000000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00004d00000000000000,HORIPAD3 A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000250900000017000000000000,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,platform:Windows,
|
||||
030000008f0e00001330000000000000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000830500006020000000000000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Windows,
|
||||
03000000b50700001403000000000000,IMPACT BLACK,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,
|
||||
030000006f0e00002401000000000000,INJUSTICE FightStick for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000491900000204000000000000,Ipega PG-9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
|
||||
030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows,
|
||||
030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000006d04000018c2000000000000,Logitech F510 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000006d04000019c2000000000000,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700005032000000000000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700005082000000000000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008433000000000000,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008483000000000000,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b6,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008134000000000000,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008184000000000000,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700008532000000000000,Madcatz Arcade Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000380700001888000000000000,MadCatz SFIV FightStick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000008305000031b0000000000000,MaxfireBlaze3,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,platform:Windows,
|
||||
03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000790000004318000000000000,Mayflash GameCube Controller Adapter,a:b1,b:b2,back:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b0,leftshoulder:b4,leftstick:b0,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b0,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,platform:Windows,
|
||||
0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,
|
||||
03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000250900006688000000000000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
|
||||
030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,
|
||||
030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
030000004b120000014d000000000000,NYKO AIRFLO,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a3,leftstick:a0,lefttrigger:b6,rightshoulder:b5,rightstick:a2,righttrigger:b7,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows,
|
||||
03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000008f0e00007530000000000000,PS (R) Gamepad,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000e30500009605000000000000,PS to USB convert cable,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
|
||||
03000000100800000100000000000000,PS1 USB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000100800000300000000000000,PS2 USB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
|
||||
030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Windows,
|
||||
03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,
|
||||
03000000250900000500000000000000,PS3 DualShock,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows,
|
||||
03000000100000008200000000000000,PS360+ v1.66,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:h0.4,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000004c050000a00b000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000004c050000cc09000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000300f00000011000000000000,QanBa Arcade JoyStick 1008,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b10,x:b0,y:b3,platform:Windows,
|
||||
03000000300f00001611000000000000,QanBa Arcade JoyStick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,
|
||||
03000000222c00000020000000000000,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000300f00001210000000000000,QanBa Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000341a00000104000000000000,QanBa Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,platform:Windows,
|
||||
03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick PS3 Mode,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick PS4 Mode,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
|
||||
030000000d0f00001100000000000000,REAL ARCADE PRO.3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00006a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00006b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00008a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00008b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00007000000000000000,REAL ARCADE PRO.4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00002200000000000000,REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00005b00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,
|
||||
0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,
|
||||
0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,
|
||||
030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000006f0e00001e01000000000000,Rock Candy Gamepad for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000a30600001af5000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000a306000023f6000000000000,Saitek Cyborg V.1 Game pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000300f00001201000000000000,Saitek Dual Analog Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,
|
||||
03000000a30600000cff000000000000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,x:b0,y:b1,platform:Windows,
|
||||
03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000300f00001001000000000000,Saitek P480 Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,
|
||||
03000000a30600000b04000000000000,Saitek P990,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Windows,
|
||||
03000000300f00001101000000000000,saitek rumble pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,
|
||||
0300000000050000289b000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000009b2800000500000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000341a00000208000000000000,SL-6555-SBK,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,platform:Windows,
|
||||
030000008f0e00000800000000000000,SpeedLink Strike FX Wireless,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000ff1100003133000000000000,SVEN X-PAD,a:b2,b:b3,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a4,start:b5,x:b0,y:b1,platform:Windows,
|
||||
03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,
|
||||
030000004f04000015b3000000000000,Thrustmaster Dual Analog 2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
|
||||
030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Windows,
|
||||
030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
|
||||
03000000666600000488000000000000,TigerGame PS/PS2 Game Controller Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
|
||||
03000000d90400000200000000000000,TwinShock PS2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000380700006652000000000000,UnKnown,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000632500002305000000000000,USB Vibration Joystick (BM),a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000790000001b18000000000000,Venom Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000450c00002043000000000000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
|
||||
03000000786901006e70000000000000,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
|
||||
xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
|
||||
|
||||
# Mac OS X
|
||||
03000000022000000090000001000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
|
||||
03000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
|
||||
03000000102800000900000000000000,8Bitdo SFC30 GamePad Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
|
||||
03000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
03000000a00500003232000009010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000a306000022f6000001030000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000790000000600000000000000,G-Shark GP-702,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
03000000ad1b000001f9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
|
||||
030000000d0f00005f00000000010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f00005e00000000010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f00005f00000000000000,HORI Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f00005e00000000000000,HORI Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f00004d00000000000000,HORI Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f00006e00000000010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f00006600000000010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f00006600000000000000,HORIPAD FPS PLUS 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000008f0e00001330000011010000,HuiJia SNES Controller,a:b4,b:b2,back:b16,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b12,rightshoulder:b14,start:b18,x:b6,y:b0,platform:Mac OS X,
|
||||
03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,
|
||||
03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,
|
||||
030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000006d04000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000006d04000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000006d0400001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000006d04000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000380700005032000000010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Mac OS X,
|
||||
03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X,
|
||||
03000000d8140000cecf000000000000,MC Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
|
||||
030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
03000000d62000006dca000000010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,
|
||||
030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,
|
||||
030000004c050000a00b000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000321500000010000000010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000c6240000fefa000000000000,Rock Candy Gamepad for PS3,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,platform:Mac OS X,
|
||||
03000000b40400000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Mac OS X,
|
||||
030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
|
||||
030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000004c050000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000005e0400008e02000001000000,Steam Virtual GamePad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,
|
||||
030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Mac OS X,
|
||||
030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X,
|
||||
03000000bd12000015d0000000000000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
03000000bd12000015d0000000010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X,
|
||||
050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,platform:Mac OS X,
|
||||
050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,platform:Mac OS X,
|
||||
030000005e0400008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000c6240000045d000000000000,Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000d102000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000dd02000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000e302000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000e002000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000e002000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000ea02000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
|
||||
# Linux
|
||||
03000000022000000090000011010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
|
||||
05000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
|
||||
05000000c82d00002038000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
|
||||
03000000c82d00000190000011010000,8Bitdo NES30 Pro 8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
|
||||
05000000102800000900000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,
|
||||
05000000c82d00003028000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,
|
||||
05000000a00500003232000001000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,
|
||||
05000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,
|
||||
030000006f0e00003901000000430000,Afterglow Prismatic Wired Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000006f0e00003901000020060000,Afterglow Wired Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
|
||||
05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux,
|
||||
03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Linux,
|
||||
03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000a306000022f6000011010000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux,
|
||||
03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,
|
||||
030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,
|
||||
0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
|
||||
030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:a0,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:a3,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000006f0e00001f01000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000280400000140000000010000,Gravis GamePad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys GamePad ,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Linux,
|
||||
030000008f0e00000300000010010000,GreenAsia Inc. USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
|
||||
030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
|
||||
0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
|
||||
06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,platform:Linux,
|
||||
03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
|
||||
030000000d0f00000d00000000010000,hori,a:b0,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftx:b4,lefty:b5,rightshoulder:b7,start:b9,x:b1,y:b2,platform:Linux,
|
||||
030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000000d0f00006b00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,
|
||||
050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,
|
||||
03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,platform:Linux,
|
||||
0500000049190000020400001b010000,Ipega PG-9069 - Bluetooth Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b161,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux,
|
||||
03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,
|
||||
03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
|
||||
030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000006d04000015c2000010010000,Logitech Logitech Extreme 3D,a:b0,b:b4,back:b6,guide:b8,leftshoulder:b9,leftstick:h0.8,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:h0.2,start:b7,x:b2,y:b5,platform:Linux,
|
||||
030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux,
|
||||
05000000380700006652000025010000,Mad Catz C.T.R.L.R ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000380700005032000011010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000380700008433000011010000,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000380700008483000011010000,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
|
||||
03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
0300000079000000d218000011010000,MAGIC-NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,
|
||||
030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux,
|
||||
030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000005e040000e302000003020000,Microsoft X-Box One Elite pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,
|
||||
030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,
|
||||
05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,
|
||||
03000000250900006688000000010000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,
|
||||
030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Linux,
|
||||
050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
|
||||
050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,
|
||||
05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
|
||||
030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,
|
||||
03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,
|
||||
05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,
|
||||
05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,
|
||||
03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
|
||||
030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000d62000006dca000011010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
|
||||
030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
|
||||
030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
|
||||
030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
|
||||
050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
|
||||
050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
|
||||
050000004c0500006802000000800000,PS3 Controller (Bluetooth),a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
|
||||
05000000504c415953544154494f4e00,PS3 Controller (Bluetooth),a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
|
||||
060000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
|
||||
030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
|
||||
030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
|
||||
030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
|
||||
050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
|
||||
050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
|
||||
03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,platform:Linux,
|
||||
030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux,
|
||||
030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000008916000000fd000024010000,Razer Onza Tournament,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
|
||||
050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
|
||||
0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,
|
||||
0300000000f000000300000000010000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,
|
||||
0300000000f00000f100000000010000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,
|
||||
030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000006f0e00001e01000011010000,Rock Candy Gamepad for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000006f0e00004601000001010000,Rock Candy Wired Controller for Xbox One,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,
|
||||
03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,platform:Linux,
|
||||
03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,
|
||||
03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux,
|
||||
03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,a:b12,b:b10,back:b4,dpdown:b2,dpleft:b3,dpright:b1,dpup:b0,leftshoulder:b9,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b8,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b5,x:b13,y:b11,platform:Linux,
|
||||
03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000632500007505000010010000,SHANWAN PS3/PC Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,
|
||||
030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
|
||||
05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000ad1b000038f0000090040000,Street Fighter IV FightStick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,
|
||||
030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,
|
||||
030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,
|
||||
030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Linux,
|
||||
030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000bd12000015d0000010010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux,
|
||||
03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux,
|
||||
05000000ac0500003232000001000000,VR-BOX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
|
||||
030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux,
|
||||
030000005e040000a102000014010000,Xbox 360 Wireless Receiver (XBOX),a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
|
||||
050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
|
||||
05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux,
|
||||
03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux,
|
||||
xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
|
||||
# Android
|
||||
64633436313965656664373634323364,Microsoft X-Box 360 pad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android,
|
||||
37336435666338653565313731303834,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
61363931656135336130663561616264,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
35643031303033326130316330353564,PS4 Controller,a:b1,b:b17,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,
|
||||
05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,
|
||||
5477696e20555342204a6f7973746963,Twin USB Joystick,a:b22,b:b21,back:b28,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b26,leftstick:b30,lefttrigger:b24,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b31,righttrigger:b25,rightx:a3,righty:a2,start:b29,x:b23,y:b20,platform:Android,
|
||||
34356136633366613530316338376136,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,x:b17,y:b2,platform:Android,
|
||||
|
||||
# iOS
|
||||
4d466947616d65706164010000000000,MFi Extended Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:iOS,
|
||||
4d466947616d65706164020000000000,MFi Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b6,x:b2,y:b3,platform:iOS,
|
||||
05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,
|
||||
+43
-44
@@ -1,5 +1,5 @@
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Dialogs 1.3
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Controls 2.2
|
||||
|
||||
import AppModel 1.0
|
||||
@@ -16,12 +16,7 @@ GridView {
|
||||
anchors.topMargin: 20
|
||||
anchors.rightMargin: 5
|
||||
anchors.bottomMargin: 5
|
||||
cellWidth: 225; cellHeight: 350;
|
||||
focus: true
|
||||
|
||||
// Cache delegates for 1000px in both directions to improve
|
||||
// scrolling and resizing performance
|
||||
cacheBuffer: 1000
|
||||
cellWidth: 225; cellHeight: 385;
|
||||
|
||||
// The StackView will trigger a visibility change when
|
||||
// we're pushed onto it, causing our onVisibleChanged
|
||||
@@ -52,12 +47,13 @@ GridView {
|
||||
|
||||
model: appModel
|
||||
|
||||
delegate: Item {
|
||||
width: 200; height: 300;
|
||||
delegate: ItemDelegate {
|
||||
width: 200; height: 335;
|
||||
|
||||
Image {
|
||||
id: appIcon
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
y: 20
|
||||
source: model.boxart
|
||||
sourceSize {
|
||||
width: 150
|
||||
@@ -95,6 +91,8 @@ GridView {
|
||||
if (runningIndex >= 0 && runningIndex !== index) {
|
||||
quitAppDialog.appName = appModel.getRunningAppName()
|
||||
quitAppDialog.segueToStream = true
|
||||
quitAppDialog.nextAppName = model.name
|
||||
quitAppDialog.nextAppIndex = index
|
||||
quitAppDialog.open()
|
||||
return
|
||||
}
|
||||
@@ -104,46 +102,17 @@ GridView {
|
||||
stackView.push(segue)
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: quitAppDialog
|
||||
modality:Qt.WindowModal
|
||||
property string appName : "";
|
||||
property bool segueToStream : false
|
||||
text:"Are you sure you want to quit " + appName +"? Any unsaved progress will be lost."
|
||||
standardButtons: StandardButton.Yes | StandardButton.No
|
||||
onYes: {
|
||||
var component = Qt.createComponent("QuitSegue.qml")
|
||||
var params = {"appName": appName}
|
||||
if (segueToStream) {
|
||||
// Store the session and app name if we're going to stream after
|
||||
// successfully quitting the old app.
|
||||
params.nextAppName = model.name
|
||||
params.nextSession = appModel.createSessionForApp(index)
|
||||
}
|
||||
else {
|
||||
params.nextAppName = null
|
||||
params.nextSession = null
|
||||
}
|
||||
|
||||
stackView.push(component.createObject(stackView, params))
|
||||
|
||||
// Trigger the quit after pushing the quit segue on screen
|
||||
appModel.quitRunningApp()
|
||||
}
|
||||
onClicked: {
|
||||
// Nothing is running or this app is running
|
||||
launchOrResumeSelectedApp()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
// Nothing is running or this app is running
|
||||
launchOrResumeSelectedApp()
|
||||
}
|
||||
else {
|
||||
// Right click
|
||||
appContextMenu.open()
|
||||
}
|
||||
// Right click
|
||||
appContextMenu.open()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +139,36 @@ GridView {
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: quitAppDialog
|
||||
modality:Qt.WindowModal
|
||||
property string appName : ""
|
||||
property bool segueToStream : false
|
||||
property string nextAppName: ""
|
||||
property int nextAppIndex: 0
|
||||
text:"Are you sure you want to quit " + appName +"? Any unsaved progress will be lost."
|
||||
standardButtons: StandardButton.Yes | StandardButton.No
|
||||
onYes: {
|
||||
var component = Qt.createComponent("QuitSegue.qml")
|
||||
var params = {"appName": appName}
|
||||
if (segueToStream) {
|
||||
// Store the session and app name if we're going to stream after
|
||||
// successfully quitting the old app.
|
||||
params.nextAppName = nextAppName
|
||||
params.nextSession = appModel.createSessionForApp(nextAppIndex)
|
||||
}
|
||||
else {
|
||||
params.nextAppName = null
|
||||
params.nextSession = null
|
||||
}
|
||||
|
||||
stackView.push(component.createObject(stackView, params))
|
||||
|
||||
// Trigger the quit after pushing the quit segue on screen
|
||||
appModel.quitRunningApp()
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
parent: appGrid.parent
|
||||
anchors {
|
||||
|
||||
+47
-44
@@ -1,6 +1,6 @@
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Dialogs 1.3
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
@@ -19,7 +19,6 @@ GridView {
|
||||
anchors.rightMargin: 5
|
||||
anchors.bottomMargin: 5
|
||||
cellWidth: 350; cellHeight: 350;
|
||||
focus: true
|
||||
objectName: "Computers"
|
||||
|
||||
// The StackView will trigger a visibility change when
|
||||
@@ -49,7 +48,7 @@ GridView {
|
||||
pairDialog.close()
|
||||
|
||||
// Display a failed dialog if we got an error
|
||||
if (error !== null) {
|
||||
if (error !== undefined) {
|
||||
errorDialog.text = error
|
||||
errorDialog.open()
|
||||
}
|
||||
@@ -58,7 +57,7 @@ GridView {
|
||||
function addComplete(success)
|
||||
{
|
||||
if (!success) {
|
||||
errorDialog.text = "Unable to connect to the specified PC. Ensure GameStream is enabled in GeForce Experience."
|
||||
errorDialog.text = "Unable to connect to the specified PC. Click the Help button for possible solutions."
|
||||
errorDialog.open()
|
||||
}
|
||||
}
|
||||
@@ -73,7 +72,7 @@ GridView {
|
||||
|
||||
model: computerModel
|
||||
|
||||
delegate: Item {
|
||||
delegate: ItemDelegate {
|
||||
width: 300; height: 300;
|
||||
|
||||
Image {
|
||||
@@ -142,47 +141,46 @@ GridView {
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (model.addPc) {
|
||||
addPcDialog.open()
|
||||
}
|
||||
else if (model.online) {
|
||||
if (model.paired) {
|
||||
// go to game view
|
||||
var component = Qt.createComponent("AppView.qml")
|
||||
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name})
|
||||
stackView.push(appView)
|
||||
}
|
||||
else {
|
||||
if (!model.busy) {
|
||||
var pin = ("0000" + Math.floor(Math.random() * 10000)).slice(-4)
|
||||
|
||||
// Kick off pairing in the background
|
||||
computerModel.pairComputer(index, pin)
|
||||
|
||||
// Display the pairing dialog
|
||||
pairDialog.pin = pin
|
||||
pairDialog.open()
|
||||
}
|
||||
else {
|
||||
// cannot pair while something is streaming or attempting to pair
|
||||
errorDialog.text = "This PC is currently busy. Make sure to quit any running games and try again."
|
||||
errorDialog.open()
|
||||
}
|
||||
}
|
||||
} else if (!model.online) {
|
||||
pcContextMenu.open()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton;
|
||||
acceptedButtons: Qt.RightButton;
|
||||
onClicked: {
|
||||
if(mouse.button === Qt.LeftButton) {
|
||||
if (model.addPc) {
|
||||
addPcDialog.open()
|
||||
}
|
||||
else if (model.online) {
|
||||
if (model.paired) {
|
||||
// go to game view
|
||||
var component = Qt.createComponent("AppView.qml")
|
||||
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name})
|
||||
stackView.push(appView)
|
||||
}
|
||||
else {
|
||||
if (!model.busy) {
|
||||
var pin = ("0000" + Math.floor(Math.random() * 10000)).slice(-4)
|
||||
|
||||
// Kick off pairing in the background
|
||||
computerModel.pairComputer(index, pin)
|
||||
|
||||
// Display the pairing dialog
|
||||
pairDialog.pin = pin
|
||||
pairDialog.open()
|
||||
}
|
||||
else {
|
||||
// cannot pair while something is streaming or attempting to pair
|
||||
errorDialog.text = "This PC is currently busy. Make sure to quit any running games and try again."
|
||||
errorDialog.open()
|
||||
}
|
||||
}
|
||||
} else if(!model.online) {
|
||||
pcContextMenu.open()
|
||||
}
|
||||
|
||||
}
|
||||
else { // right click
|
||||
if(!model.addPc) { // but only for actual PCs, not the add-pc option
|
||||
pcContextMenu.open()
|
||||
}
|
||||
// right click
|
||||
if (!model.addPc) { // but only for actual PCs, not the add-pc option
|
||||
pcContextMenu.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,7 +191,12 @@ GridView {
|
||||
// don't allow edits to the rest of the window while open
|
||||
modality:Qt.WindowModal
|
||||
icon: StandardIcon.Critical
|
||||
standardButtons: StandardButton.Ok
|
||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
||||
onHelp: {
|
||||
// Using Setup-Guide here instead of Troubleshooting because it's likely that users
|
||||
// will arrive here by forgetting to enable GameStream or not forwarding ports.
|
||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Dialogs 1.3
|
||||
import QtQuick.Dialogs 1.2
|
||||
|
||||
import ComputerManager 1.0
|
||||
import Session 1.0
|
||||
|
||||
+223
-82
@@ -2,6 +2,7 @@ import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
|
||||
import StreamingPreferences 1.0
|
||||
import ComputerManager 1.0
|
||||
|
||||
ScrollView {
|
||||
id: settingsPage
|
||||
@@ -56,37 +57,51 @@ ScrollView {
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
Component.onCompleted: {
|
||||
// Add native resolutions for all attached displays
|
||||
for (var displayIndex = 0;; displayIndex++) {
|
||||
var screenRect = prefs.getDisplayResolution(displayIndex)
|
||||
if (screenRect.width === 0) {
|
||||
// Exceeded max count of displays
|
||||
break
|
||||
}
|
||||
var done = false
|
||||
for (var displayIndex = 0; !done; displayIndex++) {
|
||||
for (var displayResIndex = 0; displayResIndex < 2; displayResIndex++) {
|
||||
var screenRect;
|
||||
|
||||
var indexToAdd = 0
|
||||
for (var j = 0; j < resolutionComboBox.count; j++) {
|
||||
var existing_width = parseInt(resolutionListModel.get(j).video_width);
|
||||
var existing_height = parseInt(resolutionListModel.get(j).video_height);
|
||||
// Some platforms have different desktop resolutions
|
||||
// and native resolutions (like macOS with Retina displays)
|
||||
if (displayResIndex == 0) {
|
||||
screenRect = prefs.getDesktopResolution(displayIndex)
|
||||
}
|
||||
else {
|
||||
screenRect = prefs.getNativeResolution(displayIndex)
|
||||
}
|
||||
|
||||
if (screenRect.width === existing_width && screenRect.height === existing_height) {
|
||||
// Duplicate entry, skip
|
||||
indexToAdd = -1
|
||||
if (screenRect.width === 0) {
|
||||
// Exceeded max count of displays
|
||||
done = true
|
||||
break
|
||||
}
|
||||
else if (screenRect.width * screenRect.height > existing_width * existing_height) {
|
||||
// Candidate entrypoint after this entry
|
||||
indexToAdd = j + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Insert this display's resolution if it's not a duplicate
|
||||
if (indexToAdd >= 0) {
|
||||
resolutionListModel.insert(indexToAdd,
|
||||
{
|
||||
"text": "Native ("+screenRect.width+"x"+screenRect.height+")",
|
||||
"video_width": ""+screenRect.width,
|
||||
"video_height": ""+screenRect.height
|
||||
})
|
||||
var indexToAdd = 0
|
||||
for (var j = 0; j < resolutionComboBox.count; j++) {
|
||||
var existing_width = parseInt(resolutionListModel.get(j).video_width);
|
||||
var existing_height = parseInt(resolutionListModel.get(j).video_height);
|
||||
|
||||
if (screenRect.width === existing_width && screenRect.height === existing_height) {
|
||||
// Duplicate entry, skip
|
||||
indexToAdd = -1
|
||||
break
|
||||
}
|
||||
else if (screenRect.width * screenRect.height > existing_width * existing_height) {
|
||||
// Candidate entrypoint after this entry
|
||||
indexToAdd = j + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Insert this display's resolution if it's not a duplicate
|
||||
if (indexToAdd >= 0) {
|
||||
resolutionListModel.insert(indexToAdd,
|
||||
{
|
||||
"text": "Native ("+screenRect.width+"x"+screenRect.height+")",
|
||||
"video_width": ""+screenRect.width,
|
||||
"video_height": ""+screenRect.height
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +110,7 @@ ScrollView {
|
||||
var saved_width = prefs.width
|
||||
var saved_height = prefs.height
|
||||
currentIndex = 0
|
||||
for (var i = 0; i < resolutionComboBox.count; i++) {
|
||||
for (var i = 0; i < resolutionListModel.count; i++) {
|
||||
var el_width = parseInt(resolutionListModel.get(i).video_width);
|
||||
var el_height = parseInt(resolutionListModel.get(i).video_height);
|
||||
|
||||
@@ -104,6 +119,9 @@ ScrollView {
|
||||
currentIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
// Persist the selected value
|
||||
activated(currentIndex)
|
||||
}
|
||||
|
||||
id: resolutionComboBox
|
||||
@@ -136,59 +154,97 @@ ScrollView {
|
||||
}
|
||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||
onActivated : {
|
||||
prefs.width = parseInt(resolutionListModel.get(currentIndex).video_width)
|
||||
prefs.height = parseInt(resolutionListModel.get(currentIndex).video_height)
|
||||
var selectedWidth = parseInt(resolutionListModel.get(currentIndex).video_width)
|
||||
var selectedHeight = parseInt(resolutionListModel.get(currentIndex).video_height)
|
||||
|
||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
||||
slider.value = prefs.bitrateKbps
|
||||
// Only modify the bitrate if the values actually changed
|
||||
if (prefs.width !== selectedWidth || prefs.height !== selectedHeight) {
|
||||
prefs.width = selectedWidth
|
||||
prefs.height = selectedHeight
|
||||
|
||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
||||
slider.value = prefs.bitrateKbps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
Component.onCompleted: {
|
||||
function createModel() {
|
||||
var fpsListModel = Qt.createQmlObject('import QtQuick 2.0; ListModel {}', parent, '')
|
||||
|
||||
// Get the max supported FPS on this system
|
||||
var max_fps = prefs.getMaximumStreamingFrameRate();
|
||||
|
||||
// Default entries
|
||||
fpsListModel.append({"text": "30 FPS", "video_fps": "30"})
|
||||
fpsListModel.append({"text": "60 FPS", "video_fps": "60"})
|
||||
|
||||
// Add unsupported FPS values that come before the display max FPS
|
||||
if (prefs.unsupportedFps) {
|
||||
if (max_fps > 90) {
|
||||
fpsListModel.append({"text": "90 FPS (Unsupported)", "video_fps": "90"})
|
||||
}
|
||||
if (max_fps > 120) {
|
||||
fpsListModel.append({"text": "120 FPS (Unsupported)", "video_fps": "120"})
|
||||
}
|
||||
}
|
||||
|
||||
// Use 64 as the cutoff for adding a separate option to
|
||||
// handle wonky displays that report just over 60 Hz.
|
||||
if (max_fps > 64) {
|
||||
fpsListModel.append({"text": max_fps+" FPS", "video_fps": ""+max_fps})
|
||||
}
|
||||
|
||||
// Add unsupported FPS values that come after the display max FPS
|
||||
if (prefs.unsupportedFps) {
|
||||
if (max_fps < 90) {
|
||||
fpsListModel.append({"text": "90 FPS (Unsupported)", "video_fps": "90"})
|
||||
}
|
||||
if (max_fps < 120) {
|
||||
fpsListModel.append({"text": "120 FPS (Unsupported)", "video_fps": "120"})
|
||||
}
|
||||
}
|
||||
|
||||
return fpsListModel
|
||||
}
|
||||
|
||||
function reinitialize() {
|
||||
model = createModel()
|
||||
|
||||
var saved_fps = prefs.fps
|
||||
currentIndex = 0
|
||||
for (var i = 0; i < fpsComboBox.count; i++) {
|
||||
var el_fps = parseInt(fpsListModel.get(i).video_fps);
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
var el_fps = parseInt(model.get(i).video_fps);
|
||||
|
||||
// Pick the highest value lesser or equal to the saved FPS
|
||||
if (saved_fps >= el_fps) {
|
||||
currentIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
// Persist the selected value
|
||||
activated(currentIndex)
|
||||
}
|
||||
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
Component.onCompleted: {
|
||||
reinitialize()
|
||||
}
|
||||
|
||||
id: fpsComboBox
|
||||
textRole: "text"
|
||||
model: ListModel {
|
||||
id: fpsListModel
|
||||
ListElement {
|
||||
text: "30 FPS"
|
||||
video_fps: "30"
|
||||
}
|
||||
ListElement {
|
||||
text: "60 FPS"
|
||||
video_fps: "60"
|
||||
}
|
||||
// A higher value may be added at runtime
|
||||
// based on the attached display refresh rate
|
||||
}
|
||||
width: 250
|
||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||
onActivated : {
|
||||
prefs.fps = parseInt(fpsListModel.get(currentIndex).video_fps)
|
||||
var selectedFps = parseInt(model.get(currentIndex).video_fps)
|
||||
|
||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
||||
slider.value = prefs.bitrateKbps
|
||||
// Only modify the bitrate if the values actually changed
|
||||
if (prefs.fps !== selectedFps) {
|
||||
prefs.fps = selectedFps
|
||||
|
||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
||||
slider.value = prefs.bitrateKbps
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,26 +286,60 @@ ScrollView {
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
CheckBox {
|
||||
id: fullScreenCheck
|
||||
text: "<font color=\"white\">Full-screen</font>"
|
||||
font.pointSize: 12
|
||||
checked: prefs.fullScreen
|
||||
onCheckedChanged: {
|
||||
prefs.fullScreen = checked
|
||||
Label {
|
||||
width: parent.width
|
||||
id: windowModeTitle
|
||||
text: qsTr("Display mode")
|
||||
font.pointSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
color: "white"
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
Component.onCompleted: {
|
||||
var savedWm = prefs.windowMode
|
||||
currentIndex = 0
|
||||
for (var i = 0; i < windowModeListModel.count; i++) {
|
||||
var thisWm = windowModeListModel.get(i).val;
|
||||
if (savedWm === thisWm) {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
activated(currentIndex)
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: vsyncCheck
|
||||
text: "<font color=\"white\">Enable V-Sync</font>"
|
||||
font.pointSize: 12
|
||||
visible: fullScreenCheck.checked
|
||||
checked: prefs.enableVsync
|
||||
onCheckedChanged: {
|
||||
prefs.enableVsync = checked
|
||||
id: windowModeComboBox
|
||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
||||
textRole: "text"
|
||||
model: ListModel {
|
||||
id: windowModeListModel
|
||||
ListElement {
|
||||
text: "Full-screen (Recommended)"
|
||||
val: StreamingPreferences.WM_FULLSCREEN
|
||||
}
|
||||
ListElement {
|
||||
text: "Borderless windowed"
|
||||
val: StreamingPreferences.WM_FULLSCREEN_DESKTOP
|
||||
}
|
||||
ListElement {
|
||||
text: "Windowed"
|
||||
val: StreamingPreferences.WM_WINDOWED
|
||||
}
|
||||
}
|
||||
onActivated: {
|
||||
prefs.windowMode = windowModeListModel.get(currentIndex).val
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: vsyncCheck
|
||||
text: "<font color=\"white\">Enable V-Sync</font>"
|
||||
font.pointSize: 12
|
||||
checked: prefs.enableVsync
|
||||
onCheckedChanged: {
|
||||
prefs.enableVsync = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,12 +371,14 @@ ScrollView {
|
||||
Component.onCompleted: {
|
||||
var saved_audio = prefs.audioConfig
|
||||
currentIndex = 0
|
||||
for(var i = 0; i < audioListModel.count; i++) {
|
||||
var el_audio= audioListModel.get(i).val;
|
||||
if(saved_audio === el_audio) {
|
||||
currentIndex = i
|
||||
}
|
||||
for (var i = 0; i < audioListModel.count; i++) {
|
||||
var el_audio = audioListModel.get(i).val;
|
||||
if (saved_audio === el_audio) {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
activated(currentIndex)
|
||||
}
|
||||
|
||||
id: audioComboBox
|
||||
@@ -327,10 +419,10 @@ ScrollView {
|
||||
id: gamepadSettingsGroupBox
|
||||
width: (parent.width - parent.padding)
|
||||
padding: 12
|
||||
title: "<font color=\"skyblue\">Gamepad Settings</font>"
|
||||
title: "<font color=\"skyblue\">Input Settings</font>"
|
||||
font.pointSize: 12
|
||||
|
||||
Column {
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
@@ -343,6 +435,16 @@ ScrollView {
|
||||
prefs.multiController = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: mouseAccelerationCheck
|
||||
text: "<font color=\"white\">Enable mouse acceleration</font>"
|
||||
font.pointSize: 12
|
||||
checked: prefs.mouseAcceleration
|
||||
onCheckedChanged: {
|
||||
prefs.mouseAcceleration = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,13 +455,15 @@ ScrollView {
|
||||
title: "<font color=\"skyblue\">Host Settings</font>"
|
||||
font.pointSize: 12
|
||||
|
||||
Column {
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
CheckBox {
|
||||
id: optimizeGameSettingsCheck
|
||||
text: "<font color=\"white\">Optimize game settings</font>"
|
||||
// HACK: Match width of the other checkbox to make the UI not look bad
|
||||
width: multiControllerCheck.width
|
||||
font.pointSize: 12
|
||||
checked: prefs.gameOptimizations
|
||||
onCheckedChanged: {
|
||||
@@ -404,12 +508,14 @@ ScrollView {
|
||||
Component.onCompleted: {
|
||||
var saved_vds = prefs.videoDecoderSelection
|
||||
currentIndex = 0
|
||||
for(var i = 0; i < decoderListModel.count; i++) {
|
||||
for (var i = 0; i < decoderListModel.count; i++) {
|
||||
var el_vds = decoderListModel.get(i).val;
|
||||
if(saved_vds === el_vds) {
|
||||
currentIndex = i
|
||||
}
|
||||
if (saved_vds === el_vds) {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
activated(currentIndex)
|
||||
}
|
||||
|
||||
id: decoderComboBox
|
||||
@@ -452,10 +558,12 @@ ScrollView {
|
||||
currentIndex = 0
|
||||
for(var i = 0; i < codecListModel.count; i++) {
|
||||
var el_vcc = codecListModel.get(i).val;
|
||||
if(saved_vcc === el_vcc) {
|
||||
currentIndex = i
|
||||
}
|
||||
if (saved_vcc === el_vcc) {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
activated(currentIndex)
|
||||
}
|
||||
|
||||
id: codecComboBox
|
||||
@@ -489,6 +597,39 @@ ScrollView {
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: unlockUnsupportedFps
|
||||
text: "<font color=\"white\">Unlock unsupported FPS options</font>"
|
||||
font.pointSize: 12
|
||||
checked: prefs.unsupportedFps
|
||||
onCheckedChanged: {
|
||||
prefs.unsupportedFps = checked
|
||||
|
||||
// The selectable FPS values depend on whether
|
||||
// this option is enabled or not
|
||||
fpsComboBox.reinitialize()
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: enableMdns
|
||||
text: "<font color=\"white\">Automatically find PCs on the local network (Recommended)</font>"
|
||||
font.pointSize: 12
|
||||
checked: prefs.enableMdns
|
||||
onCheckedChanged: {
|
||||
prefs.enableMdns = checked
|
||||
|
||||
// We must save the updated preference to ensure
|
||||
// ComputerManager can observe the change internally.
|
||||
prefs.save()
|
||||
|
||||
// Restart polling so the mDNS change takes effect
|
||||
if (window.pollingActive) {
|
||||
ComputerManager.stopPollingAsync()
|
||||
ComputerManager.startPolling()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-3
@@ -1,6 +1,7 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Dialogs 1.3
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import Session 1.0
|
||||
|
||||
@@ -69,7 +70,7 @@ Item {
|
||||
session.displayLaunchWarning.connect(displayLaunchWarning)
|
||||
|
||||
// Run the streaming session to completion
|
||||
session.exec()
|
||||
session.exec(Screen.virtualX, Screen.virtualY)
|
||||
|
||||
// Show the Qt window again after streaming
|
||||
window.visible = true
|
||||
@@ -103,10 +104,25 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Tip: Press Ctrl+Alt+Shift+Q to disconnect your session"
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 50
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
font.pointSize: 18
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
color: "white"
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: errorDialog
|
||||
modality:Qt.WindowModal
|
||||
icon: StandardIcon.Critical
|
||||
standardButtons: StandardButton.Ok
|
||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
||||
onHelp: {
|
||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "computermodel.h"
|
||||
|
||||
#include <QThreadPool>
|
||||
|
||||
ComputerModel::ComputerModel(QObject* object)
|
||||
: QAbstractListModel(object) {}
|
||||
|
||||
@@ -119,11 +121,27 @@ void ComputerModel::deleteComputer(int computerIndex)
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
bool ComputerModel::wakeComputer(int computerIndex)
|
||||
class DeferredWakeHostTask : public QRunnable
|
||||
{
|
||||
public:
|
||||
DeferredWakeHostTask(NvComputer* computer)
|
||||
: m_Computer(computer) {}
|
||||
|
||||
void run()
|
||||
{
|
||||
m_Computer->wake();
|
||||
}
|
||||
|
||||
private:
|
||||
NvComputer* m_Computer;
|
||||
};
|
||||
|
||||
void ComputerModel::wakeComputer(int computerIndex)
|
||||
{
|
||||
Q_ASSERT(computerIndex < m_Computers.count());
|
||||
|
||||
return m_Computers[computerIndex]->wake();
|
||||
DeferredWakeHostTask* wakeTask = new DeferredWakeHostTask(m_Computers[computerIndex]);
|
||||
QThreadPool::globalInstance()->start(wakeTask);
|
||||
}
|
||||
|
||||
void ComputerModel::pairComputer(int computerIndex, QString pin)
|
||||
@@ -135,7 +153,7 @@ void ComputerModel::pairComputer(int computerIndex, QString pin)
|
||||
|
||||
void ComputerModel::handlePairingCompleted(NvComputer*, QString error)
|
||||
{
|
||||
emit pairingCompleted(error.isNull() ? QVariant() : error);
|
||||
emit pairingCompleted(error.isEmpty() ? QVariant() : error);
|
||||
}
|
||||
|
||||
void ComputerModel::handleComputerStateChanged(NvComputer* computer)
|
||||
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
|
||||
Q_INVOKABLE void pairComputer(int computerIndex, QString pin);
|
||||
|
||||
Q_INVOKABLE bool wakeComputer(int computerIndex);
|
||||
Q_INVOKABLE void wakeComputer(int computerIndex);
|
||||
|
||||
Q_INVOKABLE Session* createSessionForCurrentGame(int computerIndex);
|
||||
|
||||
|
||||
+49
-32
@@ -61,6 +61,8 @@ ApplicationWindow {
|
||||
|
||||
header: ToolBar {
|
||||
id: toolBar
|
||||
anchors.topMargin: 5
|
||||
anchors.bottomMargin: 5
|
||||
|
||||
RowLayout {
|
||||
spacing: 20
|
||||
@@ -70,14 +72,19 @@ ApplicationWindow {
|
||||
// Only make the button visible if the user has navigated somewhere.
|
||||
visible: stackView.depth > 1
|
||||
|
||||
icon.source: "qrc:/res/arrow_left.svg"
|
||||
onClicked: stackView.pop()
|
||||
|
||||
Menu {
|
||||
id: backButton
|
||||
x: parent.width - width
|
||||
transformOrigin: Menu.TopRight
|
||||
// FIXME: We're using an Image here rather than icon.source because
|
||||
// icons don't work on Qt 5.9 LTS.
|
||||
Image {
|
||||
source: "qrc:/res/arrow_left.svg"
|
||||
anchors.centerIn: parent
|
||||
sourceSize {
|
||||
// The icon should be square so use the height as the width too
|
||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: stackView.pop()
|
||||
}
|
||||
|
||||
Label {
|
||||
@@ -94,8 +101,15 @@ ApplicationWindow {
|
||||
property string browserUrl: ""
|
||||
|
||||
id: updateButton
|
||||
icon.source: "qrc:/res/update.svg"
|
||||
icon.color: "#0BF200"
|
||||
|
||||
Image {
|
||||
source: "qrc:/res/update.svg"
|
||||
anchors.centerIn: parent
|
||||
sourceSize {
|
||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 3000
|
||||
@@ -108,11 +122,6 @@ ApplicationWindow {
|
||||
|
||||
onClicked: Qt.openUrlExternally(browserUrl);
|
||||
|
||||
Menu {
|
||||
x: parent.width
|
||||
transformOrigin: Menu.TopRight
|
||||
}
|
||||
|
||||
|
||||
function updateAvailable(url)
|
||||
{
|
||||
@@ -127,7 +136,14 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
icon.source: "qrc:/res/question_mark.svg"
|
||||
Image {
|
||||
source: "qrc:/res/question_mark.svg"
|
||||
anchors.centerIn: parent
|
||||
sourceSize {
|
||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 3000
|
||||
@@ -136,11 +152,6 @@ ApplicationWindow {
|
||||
|
||||
// TODO need to make sure browser is brought to foreground.
|
||||
onClicked: Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
|
||||
|
||||
Menu {
|
||||
x: parent.width
|
||||
transformOrigin: Menu.TopRight
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
@@ -152,28 +163,34 @@ ApplicationWindow {
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Gamepad Mapper"
|
||||
|
||||
icon.source: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
||||
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping")
|
||||
|
||||
Menu {
|
||||
x: parent.width
|
||||
transformOrigin: Menu.TopRight
|
||||
Image {
|
||||
source: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
||||
anchors.centerIn: parent
|
||||
sourceSize {
|
||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping")
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
icon.source: "qrc:/res/settings.svg"
|
||||
Image {
|
||||
source: "qrc:/res/settings.svg"
|
||||
anchors.centerIn: parent
|
||||
sourceSize {
|
||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: navigateTo("qrc:/gui/SettingsView.qml", "Settings")
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 3000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Settings"
|
||||
|
||||
Menu {
|
||||
x: parent.width
|
||||
transformOrigin: Menu.TopRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+107
-7
@@ -11,6 +11,10 @@
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
#include "streaming/video/ffmpeg.h"
|
||||
#endif
|
||||
|
||||
#include "path.h"
|
||||
#include "gui/computermodel.h"
|
||||
#include "gui/appmodel.h"
|
||||
@@ -62,7 +66,8 @@ void logToLoggerStream(QString& message)
|
||||
}
|
||||
#endif
|
||||
|
||||
s_LoggerStream << message << endl;
|
||||
s_LoggerStream << message;
|
||||
s_LoggerStream.flush();
|
||||
}
|
||||
|
||||
void sdlLogToDiskHandler(void*, int category, SDL_LogPriority priority, const char* message)
|
||||
@@ -94,7 +99,7 @@ void sdlLogToDiskHandler(void*, int category, SDL_LogPriority priority, const ch
|
||||
}
|
||||
|
||||
QTime logTime = QTime::fromMSecsSinceStartOfDay(s_LoggerTime.elapsed());
|
||||
QString txt = QString("%1 - SDL %2 (%3): %4").arg(logTime.toString()).arg(priorityTxt).arg(category).arg(message);
|
||||
QString txt = QString("%1 - SDL %2 (%3): %4\n").arg(logTime.toString()).arg(priorityTxt).arg(category).arg(message);
|
||||
|
||||
logToLoggerStream(txt);
|
||||
}
|
||||
@@ -122,15 +127,99 @@ void qtLogToDiskHandler(QtMsgType type, const QMessageLogContext&, const QString
|
||||
}
|
||||
|
||||
QTime logTime = QTime::fromMSecsSinceStartOfDay(s_LoggerTime.elapsed());
|
||||
QString txt = QString("%1 - Qt %2: %3").arg(logTime.toString()).arg(typeTxt).arg(msg);
|
||||
QString txt = QString("%1 - Qt %2: %3\n").arg(logTime.toString()).arg(typeTxt).arg(msg);
|
||||
|
||||
logToLoggerStream(txt);
|
||||
}
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
|
||||
void ffmpegLogToDiskHandler(void* ptr, int level, const char* fmt, va_list vl)
|
||||
{
|
||||
char lineBuffer[1024];
|
||||
static int printPrefix = 1;
|
||||
|
||||
if ((level & 0xFF) > av_log_get_level()) {
|
||||
return;
|
||||
}
|
||||
|
||||
av_log_format_line(ptr, level, fmt, vl, lineBuffer, sizeof(lineBuffer), &printPrefix);
|
||||
|
||||
QTime logTime = QTime::fromMSecsSinceStartOfDay(s_LoggerTime.elapsed());
|
||||
QString txt = QString("%1 - FFmpeg: %2").arg(logTime.toString()).arg(lineBuffer);
|
||||
|
||||
logToLoggerStream(txt);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
|
||||
static UINT s_HitUnhandledException = 0;
|
||||
|
||||
LONG WINAPI UnhandledExceptionHandler(struct _EXCEPTION_POINTERS *ExceptionInfo)
|
||||
{
|
||||
// Only write a dump for the first unhandled exception
|
||||
if (InterlockedCompareExchange(&s_HitUnhandledException, 1, 0) != 0) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
WCHAR dmpFileName[MAX_PATH];
|
||||
swprintf_s(dmpFileName, L"%ls\\Moonlight-%I64u.dmp",
|
||||
(PWCHAR)QDir::toNativeSeparators(Path::getLogDir()).utf16(), QDateTime::currentSecsSinceEpoch());
|
||||
QString qDmpFileName = QString::fromUtf16((unsigned short*)dmpFileName);
|
||||
HANDLE dumpHandle = CreateFileW(dmpFileName, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (dumpHandle != INVALID_HANDLE_VALUE) {
|
||||
MINIDUMP_EXCEPTION_INFORMATION info;
|
||||
|
||||
info.ThreadId = GetCurrentThreadId();
|
||||
info.ExceptionPointers = ExceptionInfo;
|
||||
info.ClientPointers = FALSE;
|
||||
|
||||
DWORD typeFlags = MiniDumpWithIndirectlyReferencedMemory |
|
||||
MiniDumpIgnoreInaccessibleMemory |
|
||||
MiniDumpWithUnloadedModules |
|
||||
MiniDumpWithThreadInfo;
|
||||
|
||||
if (MiniDumpWriteDump(GetCurrentProcess(),
|
||||
GetCurrentProcessId(),
|
||||
dumpHandle,
|
||||
(MINIDUMP_TYPE)typeFlags,
|
||||
&info,
|
||||
nullptr,
|
||||
nullptr)) {
|
||||
qCritical() << "Unhandled exception! Minidump written to:" << qDmpFileName;
|
||||
}
|
||||
else {
|
||||
qCritical() << "Unhandled exception! Failed to write dump:" << GetLastError();
|
||||
}
|
||||
|
||||
CloseHandle(dumpHandle);
|
||||
}
|
||||
else {
|
||||
qCritical() << "Unhandled exception! Failed to open dump file:" << qDmpFileName << "with error" << GetLastError();
|
||||
}
|
||||
|
||||
// Let the program crash and WER collect a dump
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// Set these here to allow us to use the default QSettings constructor.
|
||||
// These also ensure that our cache directory is named correctly. As such,
|
||||
// it is critical that these be called before Path::initialize().
|
||||
QCoreApplication::setOrganizationName("Moonlight Game Streaming Project");
|
||||
QCoreApplication::setOrganizationDomain("moonlight-stream.com");
|
||||
QCoreApplication::setApplicationName("Moonlight");
|
||||
|
||||
if (QFile(QDir::currentPath() + "/portable.dat").exists()) {
|
||||
qInfo() << "Running in portable mode from:" << QDir::currentPath();
|
||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||
@@ -158,6 +247,16 @@ int main(int argc, char *argv[])
|
||||
s_LoggerTime.start();
|
||||
qInstallMessageHandler(qtLogToDiskHandler);
|
||||
SDL_LogSetOutputFunction(sdlLogToDiskHandler, nullptr);
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
av_log_set_callback(ffmpegLogToDiskHandler);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Create a crash dump when we crash on Windows
|
||||
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
|
||||
#endif
|
||||
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
@@ -166,10 +265,11 @@ int main(int argc, char *argv[])
|
||||
// password prompts on macOS.
|
||||
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1"));
|
||||
|
||||
// Set these here to allow us to use the default QSettings constructor
|
||||
QCoreApplication::setOrganizationName("Moonlight Game Streaming Project");
|
||||
QCoreApplication::setOrganizationDomain("moonlight-stream.com");
|
||||
QCoreApplication::setApplicationName("Moonlight");
|
||||
#ifdef Q_OS_WIN32
|
||||
// On Windows, use ANGLE so we don't have to load both DX and OGL
|
||||
// user-mode drivers into our app.
|
||||
qputenv("QT_OPENGL", "angle");
|
||||
#endif
|
||||
|
||||
// Register custom metatypes for use in signals
|
||||
qRegisterMetaType<NvApp>("NvApp");
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
#include <QSettings>
|
||||
#include <QCoreApplication>
|
||||
|
||||
QString Path::s_LogDir;
|
||||
QString Path::s_BoxArtCacheDir;
|
||||
const QString Path::k_GameControllerMappingFile("gamecontrollerdb.txt");
|
||||
|
||||
QString Path::getLogDir()
|
||||
{
|
||||
@@ -20,6 +22,42 @@ QString Path::getBoxArtCacheDir()
|
||||
return s_BoxArtCacheDir;
|
||||
}
|
||||
|
||||
QString Path::getGamepadMappingFile()
|
||||
{
|
||||
QString candidatePath;
|
||||
|
||||
// Check the current directory first
|
||||
candidatePath = QDir(QDir::currentPath()).absoluteFilePath(k_GameControllerMappingFile);
|
||||
if (QFile::exists(candidatePath)) {
|
||||
return candidatePath;
|
||||
}
|
||||
|
||||
// Now check the data directories (for Linux, in particular)
|
||||
candidatePath = QStandardPaths::locate(QStandardPaths::DataLocation, k_GameControllerMappingFile);
|
||||
if (!candidatePath.isEmpty() && QFile::exists(candidatePath)) {
|
||||
return candidatePath;
|
||||
}
|
||||
|
||||
// Now try the directory of our app installation (for Windows, if current dir doesn't find it)
|
||||
candidatePath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(k_GameControllerMappingFile);
|
||||
if (QFile::exists(candidatePath)) {
|
||||
return candidatePath;
|
||||
}
|
||||
|
||||
// Now try the Resources folder in our app bundle for macOS
|
||||
QDir dir = QDir(QCoreApplication::applicationDirPath());
|
||||
dir.cdUp();
|
||||
dir.cd("Resources");
|
||||
dir.filePath(k_GameControllerMappingFile);
|
||||
candidatePath = dir.absoluteFilePath(k_GameControllerMappingFile);
|
||||
if (QFile::exists(candidatePath)) {
|
||||
return candidatePath;
|
||||
}
|
||||
|
||||
// Couldn't find it
|
||||
return QString();
|
||||
}
|
||||
|
||||
void Path::initialize(bool portable)
|
||||
{
|
||||
if (portable) {
|
||||
|
||||
@@ -9,9 +9,12 @@ public:
|
||||
|
||||
static QString getBoxArtCacheDir();
|
||||
|
||||
static QString getGamepadMappingFile();
|
||||
|
||||
static void initialize(bool portable);
|
||||
|
||||
private:
|
||||
static QString s_LogDir;
|
||||
static QString s_BoxArtCacheDir;
|
||||
static const QString k_GameControllerMappingFile;
|
||||
};
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="#0BF200">
|
||||
<path d="M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 484 B After Width: | Height: | Size: 499 B |
@@ -1,5 +1,6 @@
|
||||
#include "streamingpreferences.h"
|
||||
#include "streaming/session.hpp"
|
||||
#include "streaming/streamutils.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
@@ -16,6 +17,10 @@
|
||||
#define SER_AUDIOCFG "audiocfg"
|
||||
#define SER_VIDEOCFG "videocfg"
|
||||
#define SER_VIDEODEC "videodec"
|
||||
#define SER_WINDOWMODE "windowmode"
|
||||
#define SER_UNSUPPORTEDFPS "unsupportedfps"
|
||||
#define SER_MDNS "mdns"
|
||||
#define SER_MOUSEACCELERATION "mouseacceleration"
|
||||
|
||||
StreamingPreferences::StreamingPreferences()
|
||||
{
|
||||
@@ -30,17 +35,23 @@ void StreamingPreferences::reload()
|
||||
height = settings.value(SER_HEIGHT, 720).toInt();
|
||||
fps = settings.value(SER_FPS, 60).toInt();
|
||||
bitrateKbps = settings.value(SER_BITRATE, getDefaultBitrate(width, height, fps)).toInt();
|
||||
fullScreen = settings.value(SER_FULLSCREEN, true).toBool();
|
||||
enableVsync = settings.value(SER_VSYNC, true).toBool();
|
||||
gameOptimizations = settings.value(SER_GAMEOPTS, true).toBool();
|
||||
playAudioOnHost = settings.value(SER_HOSTAUDIO, false).toBool();
|
||||
multiController = settings.value(SER_MULTICONT, true).toBool();
|
||||
unsupportedFps = settings.value(SER_UNSUPPORTEDFPS, false).toBool();
|
||||
enableMdns = settings.value(SER_MDNS, true).toBool();
|
||||
mouseAcceleration = settings.value(SER_MOUSEACCELERATION, false).toBool();
|
||||
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
|
||||
static_cast<int>(AudioConfig::AC_AUTO)).toInt());
|
||||
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
|
||||
static_cast<int>(VideoCodecConfig::VCC_AUTO)).toInt());
|
||||
videoDecoderSelection = static_cast<VideoDecoderSelection>(settings.value(SER_VIDEODEC,
|
||||
static_cast<int>(VideoDecoderSelection::VDS_AUTO)).toInt());
|
||||
windowMode = static_cast<WindowMode>(settings.value(SER_WINDOWMODE,
|
||||
// Try to load from the old preference value too
|
||||
static_cast<int>(settings.value(SER_FULLSCREEN, true).toBool() ?
|
||||
WindowMode::WM_FULLSCREEN : WindowMode::WM_WINDOWED)).toInt());
|
||||
}
|
||||
|
||||
void StreamingPreferences::save()
|
||||
@@ -51,14 +62,17 @@ void StreamingPreferences::save()
|
||||
settings.setValue(SER_HEIGHT, height);
|
||||
settings.setValue(SER_FPS, fps);
|
||||
settings.setValue(SER_BITRATE, bitrateKbps);
|
||||
settings.setValue(SER_FULLSCREEN, fullScreen);
|
||||
settings.setValue(SER_VSYNC, enableVsync);
|
||||
settings.setValue(SER_GAMEOPTS, gameOptimizations);
|
||||
settings.setValue(SER_HOSTAUDIO, playAudioOnHost);
|
||||
settings.setValue(SER_MULTICONT, multiController);
|
||||
settings.setValue(SER_UNSUPPORTEDFPS, unsupportedFps);
|
||||
settings.setValue(SER_MDNS, enableMdns);
|
||||
settings.setValue(SER_MOUSEACCELERATION, mouseAcceleration);
|
||||
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));
|
||||
}
|
||||
|
||||
bool StreamingPreferences::hasAnyHardwareAcceleration()
|
||||
@@ -87,12 +101,26 @@ int StreamingPreferences::getMaximumStreamingFrameRate()
|
||||
return maxFrameRate;
|
||||
}
|
||||
|
||||
for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
|
||||
SDL_DisplayMode mode;
|
||||
if (SDL_GetCurrentDisplayMode(i, &mode) == 0) {
|
||||
SDL_DisplayMode mode, bestMode, desktopMode;
|
||||
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
|
||||
// Get the native desktop resolution
|
||||
if (StreamUtils::getRealDesktopMode(displayIndex, &desktopMode)) {
|
||||
|
||||
// Start at desktop mode and work our way up
|
||||
bestMode = desktopMode;
|
||||
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
||||
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
|
||||
if (mode.w == desktopMode.w && mode.h == desktopMode.h) {
|
||||
if (mode.refresh_rate > bestMode.refresh_rate) {
|
||||
bestMode = mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cap the frame rate at 120 FPS. Past this, the encoders start
|
||||
// to max out and drop frames.
|
||||
maxFrameRate = qMax(maxFrameRate, qMin(120, mode.refresh_rate));
|
||||
maxFrameRate = qMax(maxFrameRate, qMin(120, bestMode.refresh_rate));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +129,7 @@ int StreamingPreferences::getMaximumStreamingFrameRate()
|
||||
return maxFrameRate;
|
||||
}
|
||||
|
||||
QRect StreamingPreferences::getDisplayResolution(int displayIndex)
|
||||
QRect StreamingPreferences::getDesktopResolution(int displayIndex)
|
||||
{
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -116,7 +144,7 @@ QRect StreamingPreferences::getDisplayResolution(int displayIndex)
|
||||
}
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
int err = SDL_GetCurrentDisplayMode(displayIndex, &mode);
|
||||
int err = SDL_GetDesktopDisplayMode(displayIndex, &mode);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
if (err == 0) {
|
||||
@@ -124,12 +152,38 @@ QRect StreamingPreferences::getDisplayResolution(int displayIndex)
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetCurrentDisplayMode() failed: %s",
|
||||
"SDL_GetDesktopDisplayMode() failed: %s",
|
||||
SDL_GetError());
|
||||
return QRect();
|
||||
}
|
||||
}
|
||||
|
||||
QRect StreamingPreferences::getNativeResolution(int displayIndex)
|
||||
{
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||
SDL_GetError());
|
||||
return QRect();
|
||||
}
|
||||
|
||||
if (displayIndex >= SDL_GetNumVideoDisplays()) {
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return QRect();
|
||||
}
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
bool success = StreamUtils::getRealDesktopMode(displayIndex, &mode);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
if (success) {
|
||||
return QRect(0, 0, mode.w, mode.h);
|
||||
}
|
||||
else {
|
||||
return QRect();
|
||||
}
|
||||
}
|
||||
|
||||
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
||||
{
|
||||
// This table prefers 16:10 resolutions because they are
|
||||
|
||||
@@ -21,7 +21,9 @@ public:
|
||||
|
||||
Q_INVOKABLE static int getMaximumStreamingFrameRate();
|
||||
|
||||
Q_INVOKABLE QRect getDisplayResolution(int displayIndex);
|
||||
Q_INVOKABLE QRect getDesktopResolution(int displayIndex);
|
||||
|
||||
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
|
||||
|
||||
void reload();
|
||||
|
||||
@@ -50,43 +52,60 @@ public:
|
||||
};
|
||||
Q_ENUM(VideoDecoderSelection)
|
||||
|
||||
enum WindowMode
|
||||
{
|
||||
WM_FULLSCREEN,
|
||||
WM_FULLSCREEN_DESKTOP,
|
||||
WM_WINDOWED
|
||||
};
|
||||
Q_ENUM(WindowMode)
|
||||
|
||||
Q_PROPERTY(int width MEMBER width NOTIFY displayModeChanged)
|
||||
Q_PROPERTY(int height MEMBER height NOTIFY displayModeChanged)
|
||||
Q_PROPERTY(int fps MEMBER fps NOTIFY displayModeChanged)
|
||||
Q_PROPERTY(int bitrateKbps MEMBER bitrateKbps NOTIFY bitrateChanged)
|
||||
Q_PROPERTY(bool fullScreen MEMBER fullScreen NOTIFY fullScreenChanged)
|
||||
Q_PROPERTY(bool enableVsync MEMBER enableVsync NOTIFY enableVsyncChanged)
|
||||
Q_PROPERTY(bool gameOptimizations MEMBER gameOptimizations NOTIFY gameOptimizationsChanged)
|
||||
Q_PROPERTY(bool playAudioOnHost MEMBER playAudioOnHost NOTIFY playAudioOnHostChanged)
|
||||
Q_PROPERTY(bool multiController MEMBER multiController NOTIFY multiControllerChanged)
|
||||
Q_PROPERTY(bool unsupportedFps MEMBER unsupportedFps NOTIFY unsupportedFpsChanged)
|
||||
Q_PROPERTY(bool enableMdns MEMBER enableMdns NOTIFY enableMdnsChanged)
|
||||
Q_PROPERTY(bool mouseAcceleration MEMBER mouseAcceleration NOTIFY mouseAccelerationChanged)
|
||||
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)
|
||||
|
||||
// Directly accessible members for preferences
|
||||
int width;
|
||||
int height;
|
||||
int fps;
|
||||
int bitrateKbps;
|
||||
bool fullScreen;
|
||||
bool enableVsync;
|
||||
bool gameOptimizations;
|
||||
bool playAudioOnHost;
|
||||
bool multiController;
|
||||
bool unsupportedFps;
|
||||
bool enableMdns;
|
||||
bool mouseAcceleration;
|
||||
AudioConfig audioConfig;
|
||||
VideoCodecConfig videoCodecConfig;
|
||||
VideoDecoderSelection videoDecoderSelection;
|
||||
WindowMode windowMode;
|
||||
|
||||
signals:
|
||||
void displayModeChanged();
|
||||
void bitrateChanged();
|
||||
void fullScreenChanged();
|
||||
void enableVsyncChanged();
|
||||
void gameOptimizationsChanged();
|
||||
void playAudioOnHostChanged();
|
||||
void multiControllerChanged();
|
||||
void unsupportedFpsChanged();
|
||||
void enableMdnsChanged();
|
||||
void mouseAccelerationChanged();
|
||||
void audioConfigChanged();
|
||||
void videoCodecConfigChanged();
|
||||
void videoDecoderSelectionChanged();
|
||||
void windowModeChanged();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
#include "session.hpp"
|
||||
|
||||
#include <Limelight.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#define MAX_CHANNELS 6
|
||||
#define SAMPLES_PER_FRAME 240
|
||||
#define MIN_QUEUED_FRAMES 2
|
||||
#define MAX_QUEUED_FRAMES 4
|
||||
#define STOP_THE_WORLD_LIMIT 20
|
||||
#define DROP_RATIO_DENOM 32
|
||||
|
||||
SDL_AudioDeviceID Session::s_AudioDevice;
|
||||
OpusMSDecoder* Session::s_OpusDecoder;
|
||||
short Session::s_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME];
|
||||
int Session::s_ChannelCount;
|
||||
int Session::s_PendingDrops;
|
||||
int Session::s_PendingHardDrops;
|
||||
unsigned int Session::s_SampleIndex;
|
||||
Uint32 Session::s_BaselinePendingData;
|
||||
|
||||
int Session::sdlDetermineAudioConfiguration()
|
||||
{
|
||||
SDL_AudioSpec want, have;
|
||||
SDL_AudioDeviceID dev;
|
||||
int ret;
|
||||
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s",
|
||||
SDL_GetError());
|
||||
return AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
|
||||
SDL_zero(want);
|
||||
want.freq = 48000;
|
||||
want.format = AUDIO_S16;
|
||||
want.channels = 6;
|
||||
want.samples = 1024;
|
||||
|
||||
// Try to open for 5.1 surround sound, but allow SDL to tell us that's
|
||||
// not available.
|
||||
dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, SDL_AUDIO_ALLOW_CHANNELS_CHANGE);
|
||||
if (dev == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to open audio device");
|
||||
// We'll probably have issues during audio stream init, but we'll
|
||||
// try anyway
|
||||
ret = AUDIO_CONFIGURATION_STEREO;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
SDL_CloseAudioDevice(dev);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio device has %d channels", have.channels);
|
||||
|
||||
if (have.channels > 2) {
|
||||
// We don't support quadraphonic or 7.1 surround, but SDL
|
||||
// should be able to downmix or upmix better from 5.1 than
|
||||
// from stereo, so use 5.1 in non-stereo cases.
|
||||
ret = AUDIO_CONFIGURATION_51_SURROUND;
|
||||
}
|
||||
else {
|
||||
ret = AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
|
||||
Exit:
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Session::sdlAudioInit(int /* audioConfiguration */,
|
||||
POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||
void* /* arContext */, int /* arFlags */)
|
||||
{
|
||||
SDL_AudioSpec want, have;
|
||||
int error;
|
||||
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s",
|
||||
SDL_GetError());
|
||||
return AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
|
||||
SDL_zero(want);
|
||||
want.freq = opusConfig->sampleRate;
|
||||
want.format = AUDIO_S16;
|
||||
want.channels = opusConfig->channelCount;
|
||||
|
||||
// This is supposed to be a power of 2, but our
|
||||
// frames contain a non-power of 2 number of samples,
|
||||
// so the slop would require buffering another full frame.
|
||||
// Specifying non-Po2 seems to work for our supported platforms.
|
||||
want.samples = SAMPLES_PER_FRAME;
|
||||
|
||||
s_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||
if (s_AudioDevice == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to open audio device: %s",
|
||||
SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return -1;
|
||||
}
|
||||
|
||||
s_OpusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate,
|
||||
opusConfig->channelCount,
|
||||
opusConfig->streams,
|
||||
opusConfig->coupledStreams,
|
||||
opusConfig->mapping,
|
||||
&error);
|
||||
if (s_OpusDecoder == NULL) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create decoder: %d",
|
||||
error);
|
||||
SDL_CloseAudioDevice(s_AudioDevice);
|
||||
s_AudioDevice = 0;
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return -2;
|
||||
}
|
||||
|
||||
// SDL counts pending samples in the queued
|
||||
// audio size using the WASAPI backend. This
|
||||
// includes silence, which can throw off our
|
||||
// pending data count. Get a baseline so we
|
||||
// can exclude that data.
|
||||
s_BaselinePendingData = 0;
|
||||
#ifdef Q_OS_WIN32
|
||||
for (int i = 0; i < 100; i++) {
|
||||
s_BaselinePendingData = qMax(s_BaselinePendingData, SDL_GetQueuedAudioSize(s_AudioDevice));
|
||||
SDL_Delay(10);
|
||||
}
|
||||
#endif
|
||||
s_BaselinePendingData *= 2;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Baseline pending audio data: %d bytes",
|
||||
s_BaselinePendingData);
|
||||
|
||||
s_ChannelCount = opusConfig->channelCount;
|
||||
s_SampleIndex = 0;
|
||||
s_PendingDrops = s_PendingHardDrops = 0;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio stream has %d channels",
|
||||
opusConfig->channelCount);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Session::sdlAudioStart()
|
||||
{
|
||||
// Unpause the audio device
|
||||
SDL_PauseAudioDevice(s_AudioDevice, 0);
|
||||
}
|
||||
|
||||
void Session::sdlAudioStop()
|
||||
{
|
||||
// Pause the audio device
|
||||
SDL_PauseAudioDevice(s_AudioDevice, 1);
|
||||
}
|
||||
|
||||
void Session::sdlAudioCleanup()
|
||||
{
|
||||
SDL_CloseAudioDevice(s_AudioDevice);
|
||||
s_AudioDevice = 0;
|
||||
|
||||
opus_multistream_decoder_destroy(s_OpusDecoder);
|
||||
s_OpusDecoder = nullptr;
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||
}
|
||||
|
||||
void Session::sdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
{
|
||||
int samplesDecoded;
|
||||
|
||||
s_SampleIndex++;
|
||||
|
||||
Uint32 queuedAudio = qMax((int)SDL_GetQueuedAudioSize(s_AudioDevice) - (int)s_BaselinePendingData, 0);
|
||||
Uint32 framesQueued = queuedAudio / (SAMPLES_PER_FRAME * s_ChannelCount * sizeof(short));
|
||||
|
||||
// We must check this prior to the below checks to ensure we don't
|
||||
// underflow if framesQueued - s_PendingHardDrops < 0.
|
||||
if (framesQueued <= MIN_QUEUED_FRAMES) {
|
||||
s_PendingDrops = s_PendingHardDrops = 0;
|
||||
}
|
||||
// Pend enough drops to get us back to MIN_QUEUED_FRAMES
|
||||
else if (framesQueued - s_PendingHardDrops > STOP_THE_WORLD_LIMIT) {
|
||||
s_PendingHardDrops = framesQueued - MIN_QUEUED_FRAMES;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Pending hard drop of %u audio frames",
|
||||
s_PendingHardDrops);
|
||||
}
|
||||
else if (framesQueued - s_PendingHardDrops - s_PendingDrops > MAX_QUEUED_FRAMES) {
|
||||
s_PendingDrops = framesQueued - MIN_QUEUED_FRAMES;
|
||||
}
|
||||
|
||||
// Determine if this frame should be dropped
|
||||
if (s_PendingHardDrops != 0) {
|
||||
// Hard drops happen all at once to forcefully
|
||||
// resync with the source.
|
||||
s_PendingHardDrops--;
|
||||
return;
|
||||
}
|
||||
else if (s_PendingDrops != 0 && s_SampleIndex % DROP_RATIO_DENOM == 0) {
|
||||
// Normal drops are interspersed with the audio data
|
||||
// to hide the glitches.
|
||||
s_PendingDrops--;
|
||||
return;
|
||||
}
|
||||
|
||||
samplesDecoded = opus_multistream_decode(s_OpusDecoder,
|
||||
(unsigned char*)sampleData,
|
||||
sampleLength,
|
||||
s_OpusDecodeBuffer,
|
||||
SAMPLES_PER_FRAME,
|
||||
0);
|
||||
if (samplesDecoded > 0) {
|
||||
if (SDL_QueueAudio(s_AudioDevice,
|
||||
s_OpusDecodeBuffer,
|
||||
sizeof(short) * samplesDecoded * s_ChannelCount) < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to queue audio sample: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
#include "../session.hpp"
|
||||
#include "renderers/renderer.h"
|
||||
#include "renderers/sdl.h"
|
||||
#include "renderers/qtaud.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
IAudioRenderer* Session::createAudioRenderer()
|
||||
{
|
||||
return new SdlAudioRenderer();
|
||||
}
|
||||
|
||||
bool Session::testAudio(int audioConfiguration)
|
||||
{
|
||||
IAudioRenderer* audioRenderer;
|
||||
|
||||
audioRenderer = createAudioRenderer();
|
||||
if (audioRenderer == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = audioRenderer->testAudio(audioConfiguration);
|
||||
|
||||
delete audioRenderer;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Session::detectAudioConfiguration()
|
||||
{
|
||||
IAudioRenderer* audioRenderer;
|
||||
|
||||
audioRenderer = createAudioRenderer();
|
||||
if (audioRenderer == nullptr) {
|
||||
// Hope for the best
|
||||
return AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
|
||||
int audioConfig = audioRenderer->detectAudioConfiguration();
|
||||
|
||||
delete audioRenderer;
|
||||
|
||||
return audioConfig;
|
||||
}
|
||||
|
||||
int Session::arInit(int /* audioConfiguration */,
|
||||
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||
void* /* arContext */, int /* arFlags */)
|
||||
{
|
||||
int error;
|
||||
|
||||
SDL_memcpy(&s_ActiveSession->m_AudioConfig, opusConfig, sizeof(*opusConfig));
|
||||
|
||||
s_ActiveSession->m_OpusDecoder =
|
||||
opus_multistream_decoder_create(opusConfig->sampleRate,
|
||||
opusConfig->channelCount,
|
||||
opusConfig->streams,
|
||||
opusConfig->coupledStreams,
|
||||
opusConfig->mapping,
|
||||
&error);
|
||||
if (s_ActiveSession->m_OpusDecoder == NULL) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create decoder: %d",
|
||||
error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer();
|
||||
if (s_ActiveSession->m_AudioRenderer == nullptr) {
|
||||
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(opusConfig)) {
|
||||
delete s_ActiveSession->m_AudioRenderer;
|
||||
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
||||
return -3;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio stream has %d channels",
|
||||
opusConfig->channelCount);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Session::arCleanup()
|
||||
{
|
||||
// m_AudioRenderer is deleted in cleanupAudioRenderer()
|
||||
|
||||
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
||||
s_ActiveSession->m_OpusDecoder = nullptr;
|
||||
}
|
||||
|
||||
// This is called on the main thread
|
||||
void Session::cleanupAudioRendererOnMainThread()
|
||||
{
|
||||
SDL_AtomicLock(&m_AudioRendererLock);
|
||||
delete m_AudioRenderer;
|
||||
m_AudioRenderer = nullptr;
|
||||
SDL_AtomicUnlock(&m_AudioRendererLock);
|
||||
}
|
||||
|
||||
void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
{
|
||||
int samplesDecoded;
|
||||
|
||||
samplesDecoded = opus_multistream_decode(s_ActiveSession->m_OpusDecoder,
|
||||
(unsigned char*)sampleData,
|
||||
sampleLength,
|
||||
s_ActiveSession->m_OpusDecodeBuffer,
|
||||
SAMPLES_PER_FRAME,
|
||||
0);
|
||||
if (samplesDecoded > 0) {
|
||||
SDL_AtomicLock(&s_ActiveSession->m_AudioRendererLock);
|
||||
if (s_ActiveSession->m_AudioRenderer != nullptr) {
|
||||
s_ActiveSession->m_AudioRenderer->submitAudio(s_ActiveSession->m_OpusDecodeBuffer,
|
||||
static_cast<int>(sizeof(short) *
|
||||
samplesDecoded *
|
||||
s_ActiveSession->m_AudioConfig.channelCount));
|
||||
}
|
||||
SDL_AtomicUnlock(&s_ActiveSession->m_AudioRendererLock);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#include "qtaud.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
QtAudioRenderer::QtAudioRenderer()
|
||||
: m_AudioOutput(nullptr),
|
||||
m_OutputDevice(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QtAudioRenderer::~QtAudioRenderer()
|
||||
{
|
||||
delete m_AudioOutput;
|
||||
}
|
||||
|
||||
bool QtAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig)
|
||||
{
|
||||
QAudioFormat format;
|
||||
|
||||
// testAudio() assumes this sample rate
|
||||
Q_ASSERT(opusConfig->sampleRate == 48000);
|
||||
|
||||
format.setSampleRate(opusConfig->sampleRate);
|
||||
format.setChannelCount(opusConfig->channelCount);
|
||||
format.setSampleSize(16);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
format.setCodec("audio/pcm");
|
||||
|
||||
m_AudioOutput = new QAudioOutput(format);
|
||||
|
||||
// Buffer 40 ms of audio data
|
||||
m_AudioOutput->setBufferSize(format.bytesForDuration(40000));
|
||||
|
||||
m_OutputDevice = m_AudioOutput->start();
|
||||
if (m_OutputDevice == nullptr) {
|
||||
qWarning() << "Audio start failed:" << m_AudioOutput->error();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check what buffer size was really used after starting
|
||||
qInfo() << "Audio stream buffer:" << m_AudioOutput->bufferSize() << "bytes";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QtAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
||||
{
|
||||
m_OutputDevice->write((const char *)audioBuffer, audioSize);
|
||||
}
|
||||
|
||||
bool QtAudioRenderer::testAudio(int audioConfiguration)
|
||||
{
|
||||
QAudioFormat format;
|
||||
|
||||
format.setSampleRate(48000);
|
||||
format.setSampleSize(16);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
format.setCodec("audio/pcm");
|
||||
|
||||
switch (audioConfiguration) {
|
||||
case AUDIO_CONFIGURATION_STEREO:
|
||||
format.setChannelCount(2);
|
||||
break;
|
||||
case AUDIO_CONFIGURATION_51_SURROUND:
|
||||
format.setChannelCount(6);
|
||||
break;
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (QAudioDeviceInfo::defaultOutputDevice().isFormatSupported(format)) {
|
||||
qInfo() << "Audio test successful with" << format.channelCount() << "channels";
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
qWarning() << "Audio test failed with" << format.channelCount() << "channels";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int QtAudioRenderer::detectAudioConfiguration()
|
||||
{
|
||||
int preferredChannelCount = QAudioDeviceInfo::defaultOutputDevice().preferredFormat().channelCount();
|
||||
|
||||
qInfo() << "Audio output device prefers" << preferredChannelCount << "channel configuration";
|
||||
|
||||
// We can better downmix 5.1 to quad than we can upmix stereo
|
||||
if (preferredChannelCount > 2) {
|
||||
return AUDIO_CONFIGURATION_51_SURROUND;
|
||||
}
|
||||
else {
|
||||
return AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "renderer.h"
|
||||
#include <QAudioOutput>
|
||||
|
||||
class QtAudioRenderer : public IAudioRenderer
|
||||
{
|
||||
public:
|
||||
QtAudioRenderer();
|
||||
|
||||
virtual ~QtAudioRenderer();
|
||||
|
||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
||||
|
||||
virtual void submitAudio(short* audioBuffer, int audioSize);
|
||||
|
||||
virtual bool testAudio(int audioConfiguration);
|
||||
|
||||
virtual int detectAudioConfiguration();
|
||||
|
||||
private:
|
||||
QAudioOutput* m_AudioOutput;
|
||||
QIODevice* m_OutputDevice;
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
#define MAX_CHANNELS 6
|
||||
#define SAMPLES_PER_FRAME 240
|
||||
|
||||
class IAudioRenderer
|
||||
{
|
||||
public:
|
||||
virtual ~IAudioRenderer() {}
|
||||
|
||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig) = 0;
|
||||
|
||||
virtual void submitAudio(short* audioBuffer, int audioSize) = 0;
|
||||
|
||||
virtual bool testAudio(int audioConfiguration) = 0;
|
||||
|
||||
virtual int detectAudioConfiguration() = 0;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "renderer.h"
|
||||
#include <SDL.h>
|
||||
|
||||
class SdlAudioRenderer : public IAudioRenderer
|
||||
{
|
||||
public:
|
||||
SdlAudioRenderer();
|
||||
|
||||
virtual ~SdlAudioRenderer();
|
||||
|
||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
||||
|
||||
virtual void submitAudio(short* audioBuffer, int audioSize);
|
||||
|
||||
virtual bool testAudio(int audioConfiguration);
|
||||
|
||||
virtual int detectAudioConfiguration();
|
||||
|
||||
private:
|
||||
SDL_AudioDeviceID m_AudioDevice;
|
||||
Uint32 m_ChannelCount;
|
||||
Uint32 m_PendingDrops;
|
||||
Uint32 m_PendingHardDrops;
|
||||
Uint32 m_SampleIndex;
|
||||
Uint32 m_BaselinePendingData;
|
||||
};
|
||||
@@ -0,0 +1,218 @@
|
||||
#include "sdl.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#include <QAudioDeviceInfo>
|
||||
#include <QtGlobal>
|
||||
|
||||
#define MIN_QUEUED_FRAMES 2
|
||||
#define MAX_QUEUED_FRAMES 4
|
||||
#define STOP_THE_WORLD_LIMIT 20
|
||||
#define DROP_RATIO_DENOM 32
|
||||
|
||||
// Detecting this with SDL is quite problematic, so we'll use Qt's
|
||||
// multimedia framework to do so. It appears to be actually
|
||||
// accurate on Linux and macOS, unlike using SDL and relying
|
||||
// on a channel change in the format received.
|
||||
int SdlAudioRenderer::detectAudioConfiguration()
|
||||
{
|
||||
int preferredChannelCount = QAudioDeviceInfo::defaultOutputDevice().preferredFormat().channelCount();
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio output device prefers %d channel configuration",
|
||||
preferredChannelCount);
|
||||
|
||||
// We can better downmix 5.1 to quad than we can upmix stereo
|
||||
if (preferredChannelCount > 2) {
|
||||
return AUDIO_CONFIGURATION_51_SURROUND;
|
||||
}
|
||||
else {
|
||||
return AUDIO_CONFIGURATION_STEREO;
|
||||
}
|
||||
}
|
||||
|
||||
bool SdlAudioRenderer::testAudio(int audioConfiguration)
|
||||
{
|
||||
SDL_AudioSpec want, have;
|
||||
SDL_AudioDeviceID dev;
|
||||
|
||||
SDL_zero(want);
|
||||
want.freq = 48000;
|
||||
want.format = AUDIO_S16;
|
||||
want.samples = SAMPLES_PER_FRAME;
|
||||
|
||||
switch (audioConfiguration) {
|
||||
case AUDIO_CONFIGURATION_STEREO:
|
||||
want.channels = 2;
|
||||
break;
|
||||
case AUDIO_CONFIGURATION_51_SURROUND:
|
||||
want.channels = 6;
|
||||
break;
|
||||
default:
|
||||
SDL_assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test audio device for functionality
|
||||
dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||
if (dev == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio test - Failed to open audio device: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_CloseAudioDevice(dev);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio test - Successful with %d channels",
|
||||
want.channels);
|
||||
return true;
|
||||
}
|
||||
|
||||
SdlAudioRenderer::SdlAudioRenderer()
|
||||
: m_AudioDevice(0),
|
||||
m_ChannelCount(0),
|
||||
m_PendingDrops(0),
|
||||
m_PendingHardDrops(0),
|
||||
m_SampleIndex(0),
|
||||
m_BaselinePendingData(0)
|
||||
{
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s",
|
||||
SDL_GetError());
|
||||
SDL_assert(SDL_WasInit(SDL_INIT_AUDIO));
|
||||
}
|
||||
}
|
||||
|
||||
bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig)
|
||||
{
|
||||
SDL_AudioSpec want, have;
|
||||
|
||||
SDL_zero(want);
|
||||
want.freq = opusConfig->sampleRate;
|
||||
want.format = AUDIO_S16;
|
||||
want.channels = opusConfig->channelCount;
|
||||
|
||||
// This is supposed to be a power of 2, but our
|
||||
// frames contain a non-power of 2 number of samples,
|
||||
// so the slop would require buffering another full frame.
|
||||
// Specifying non-Po2 seems to work for our supported platforms.
|
||||
want.samples = SAMPLES_PER_FRAME;
|
||||
|
||||
m_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||
if (m_AudioDevice == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to open audio device: %s",
|
||||
SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Desired audio buffer: %u samples (%lu bytes)",
|
||||
want.samples,
|
||||
want.samples * sizeof(short) * want.channels);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Obtained audio buffer: %u samples (%u bytes)",
|
||||
have.samples,
|
||||
have.size);
|
||||
|
||||
// SDL counts pending samples in the queued
|
||||
// audio size using the WASAPI backend. This
|
||||
// includes silence, which can throw off our
|
||||
// pending data count. Get a baseline so we
|
||||
// can exclude that data.
|
||||
m_BaselinePendingData = 0;
|
||||
#ifdef Q_OS_WIN32
|
||||
for (int i = 0; i < 100; i++) {
|
||||
m_BaselinePendingData = qMax(m_BaselinePendingData, SDL_GetQueuedAudioSize(m_AudioDevice));
|
||||
SDL_Delay(10);
|
||||
}
|
||||
#endif
|
||||
m_BaselinePendingData *= 2;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Baseline pending audio data: %d bytes",
|
||||
m_BaselinePendingData);
|
||||
|
||||
m_ChannelCount = opusConfig->channelCount;
|
||||
m_SampleIndex = 0;
|
||||
m_PendingDrops = m_PendingHardDrops = 0;
|
||||
|
||||
// Start playback
|
||||
SDL_PauseAudioDevice(m_AudioDevice, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SdlAudioRenderer::~SdlAudioRenderer()
|
||||
{
|
||||
if (m_AudioDevice != 0) {
|
||||
// Stop playback
|
||||
SDL_PauseAudioDevice(m_AudioDevice, 1);
|
||||
SDL_CloseAudioDevice(m_AudioDevice);
|
||||
}
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||
}
|
||||
|
||||
void SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
||||
{
|
||||
m_SampleIndex++;
|
||||
|
||||
Uint32 queuedAudio = SDL_GetQueuedAudioSize(m_AudioDevice);
|
||||
if (queuedAudio > m_BaselinePendingData) {
|
||||
queuedAudio -= m_BaselinePendingData;
|
||||
}
|
||||
else {
|
||||
queuedAudio = 0;
|
||||
}
|
||||
|
||||
Uint32 framesQueued = queuedAudio / (SAMPLES_PER_FRAME * m_ChannelCount * sizeof(short));
|
||||
|
||||
// We must check this prior to the below checks to ensure we don't
|
||||
// underflow if framesQueued - m_PendingHardDrops < 0.
|
||||
if (framesQueued <= MIN_QUEUED_FRAMES) {
|
||||
m_PendingDrops = m_PendingHardDrops = 0;
|
||||
}
|
||||
// Pend enough drops to get us back to MIN_QUEUED_FRAMES, checking first
|
||||
// to ensure we don't underflow.
|
||||
else if (framesQueued > m_PendingHardDrops &&
|
||||
framesQueued - m_PendingHardDrops > STOP_THE_WORLD_LIMIT) {
|
||||
m_PendingHardDrops = framesQueued - MIN_QUEUED_FRAMES;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Pending hard drop of %u audio frames",
|
||||
m_PendingHardDrops);
|
||||
}
|
||||
// If we're under the stop the world limit, we can drop samples
|
||||
// gracefully over the next little while.
|
||||
else if (framesQueued > m_PendingHardDrops + m_PendingDrops &&
|
||||
framesQueued - m_PendingHardDrops - m_PendingDrops > MAX_QUEUED_FRAMES) {
|
||||
m_PendingDrops = framesQueued - MIN_QUEUED_FRAMES;
|
||||
}
|
||||
|
||||
// Determine if this frame should be dropped
|
||||
if (m_PendingHardDrops != 0) {
|
||||
// Hard drops happen all at once to forcefully
|
||||
// resync with the source.
|
||||
m_PendingHardDrops--;
|
||||
return;
|
||||
}
|
||||
else if (m_PendingDrops != 0 && m_SampleIndex % DROP_RATIO_DENOM == 0) {
|
||||
// Normal drops are interspersed with the audio data
|
||||
// to hide the glitches.
|
||||
m_PendingDrops--;
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDL_QueueAudio(m_AudioDevice, audioBuffer, audioSize) < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to queue audio sample: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
}
|
||||
+92
-7
@@ -1,8 +1,10 @@
|
||||
#include <Limelight.h>
|
||||
#include <SDL.h>
|
||||
#include "streaming/session.hpp"
|
||||
#include "path.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QDir>
|
||||
|
||||
#define VK_0 0x30
|
||||
#define VK_A 0x41
|
||||
@@ -22,12 +24,21 @@ const int SdlInputHandler::k_ButtonMap[] = {
|
||||
UP_FLAG, DOWN_FLAG, LEFT_FLAG, RIGHT_FLAG
|
||||
};
|
||||
|
||||
SdlInputHandler::SdlInputHandler(bool multiController)
|
||||
: m_MultiController(multiController)
|
||||
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer* computer)
|
||||
: m_LastMouseMotionTime(0),
|
||||
m_MultiController(prefs.multiController),
|
||||
m_NeedsInputDelay(false)
|
||||
{
|
||||
// Allow gamepad input when the app doesn't have focus
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
|
||||
// If mouse acceleration is enabled, use relative mode warp (which
|
||||
// is via normal motion events that are influenced by mouse acceleration).
|
||||
// Otherwise, we'll use raw input capture which is straight from the device
|
||||
// without modification by the OS.
|
||||
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP,
|
||||
prefs.mouseAcceleration ? "1" : "0");
|
||||
|
||||
// We need to reinit this each time, since you only get
|
||||
// an initial set of gamepad arrival events once per init.
|
||||
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
|
||||
@@ -37,6 +48,31 @@ SdlInputHandler::SdlInputHandler(bool multiController)
|
||||
SDL_GetError());
|
||||
}
|
||||
|
||||
QString mappingFile = Path::getGamepadMappingFile();
|
||||
if (!mappingFile.isEmpty()) {
|
||||
std::string mappingFileNative = QDir::toNativeSeparators(mappingFile).toStdString();
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Loading gamepad mappings from: %s",
|
||||
mappingFileNative.c_str());
|
||||
|
||||
int newMappings = SDL_GameControllerAddMappingsFromFile(mappingFileNative.c_str());
|
||||
if (newMappings < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error loading gamepad mappings: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
else {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Loaded %d new gamepad mappings",
|
||||
newMappings);
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No gamepad mapping file found");
|
||||
}
|
||||
|
||||
if (!m_MultiController) {
|
||||
// Player 1 is always present in non-MC mode
|
||||
m_GamepadMask = 0x1;
|
||||
@@ -46,6 +82,19 @@ SdlInputHandler::SdlInputHandler(bool multiController)
|
||||
m_GamepadMask = 0;
|
||||
}
|
||||
|
||||
// Prior to GFE 3.14.1, sending too many mouse motion events can cause
|
||||
// GFE to choke and input latency to increase significantly. We will
|
||||
// artificially throttle them to avoid this situation.
|
||||
QVector<int> gfeVersion = NvHTTP::parseQuad(computer->gfeVersion);
|
||||
if (gfeVersion.isEmpty() || // Very old versions don't have GfeVersion at all
|
||||
gfeVersion[0] < 3 ||
|
||||
(gfeVersion[0] == 3 && gfeVersion[1] < 14) ||
|
||||
(gfeVersion[0] == 3 && gfeVersion[1] == 14 && gfeVersion[2] < 1)) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"This older version of GFE requires input delay hack");
|
||||
m_NeedsInputDelay = true;
|
||||
}
|
||||
|
||||
SDL_zero(m_GamepadState);
|
||||
}
|
||||
|
||||
@@ -324,6 +373,9 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||
case SDL_SCANCODE_APOSTROPHE:
|
||||
keyCode = 0xDE;
|
||||
break;
|
||||
case SDL_SCANCODE_NONUSBACKSLASH:
|
||||
keyCode = 0xE2;
|
||||
break;
|
||||
default:
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unhandled button event: %d",
|
||||
@@ -342,9 +394,13 @@ void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
||||
{
|
||||
int button;
|
||||
|
||||
// Capture the mouse again if clicked when unbound
|
||||
// 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
|
||||
// event to the host when clicking into our window (since
|
||||
// the pressed event was consumed by this code).
|
||||
if (event->button == SDL_BUTTON_LEFT &&
|
||||
event->state == SDL_PRESSED &&
|
||||
event->state == SDL_RELEASED &&
|
||||
!SDL_GetRelativeMouseMode()) {
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
return;
|
||||
@@ -388,10 +444,39 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||
// Not capturing
|
||||
return;
|
||||
}
|
||||
short xdelta = (short)event->xrel;
|
||||
short ydelta = (short)event->yrel;
|
||||
|
||||
if (event->xrel != 0 || event->yrel != 0) {
|
||||
LiSendMouseMoveEvent((short)event->xrel,
|
||||
(short)event->yrel);
|
||||
// If we're sending more than one motion event per millisecond,
|
||||
// delay for 1 ms to allow batching of mouse move events. On older
|
||||
// versions of GFE, we will unconditionally wait this 1 ms to
|
||||
// work around an input processing issue that causes massive mouse latency.
|
||||
Uint32 currentTime = SDL_GetTicks();
|
||||
if (m_NeedsInputDelay || !SDL_TICKS_PASSED(currentTime, m_LastMouseMotionTime + 1)) {
|
||||
SDL_Delay(1);
|
||||
currentTime = SDL_GetTicks();
|
||||
}
|
||||
m_LastMouseMotionTime = currentTime;
|
||||
|
||||
// Pump even if we didn't delay since we might get some extra events
|
||||
SDL_PumpEvents();
|
||||
|
||||
// Batch all of the pending mouse motion events
|
||||
SDL_Event nextEvent;
|
||||
while (SDL_PeepEvents(&nextEvent,
|
||||
1,
|
||||
SDL_GETEVENT,
|
||||
SDL_MOUSEMOTION,
|
||||
SDL_MOUSEMOTION) == 1) {
|
||||
// In theory, these can overflow but in practice
|
||||
// it should be highly unlikely since it would require
|
||||
// moving 64K pixels in 1 ms.
|
||||
xdelta += nextEvent.motion.xrel;
|
||||
ydelta += nextEvent.motion.yrel;
|
||||
}
|
||||
|
||||
if (xdelta != 0 || ydelta != 0) {
|
||||
LiSendMouseMoveEvent(xdelta, ydelta);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "settings/streamingpreferences.h"
|
||||
#include "backend/computermanager.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
struct GamepadState {
|
||||
@@ -18,7 +21,7 @@ struct GamepadState {
|
||||
class SdlInputHandler
|
||||
{
|
||||
public:
|
||||
explicit SdlInputHandler(bool multiController);
|
||||
explicit SdlInputHandler(StreamingPreferences& prefs, NvComputer* computer);
|
||||
|
||||
~SdlInputHandler();
|
||||
|
||||
@@ -44,7 +47,9 @@ private:
|
||||
|
||||
void sendGamepadState(GamepadState* state);
|
||||
|
||||
Uint32 m_LastMouseMotionTime;
|
||||
bool m_MultiController;
|
||||
bool m_NeedsInputDelay;
|
||||
int m_GamepadMask;
|
||||
GamepadState m_GamepadState[MAX_GAMEPADS];
|
||||
|
||||
|
||||
+321
-99
@@ -1,5 +1,6 @@
|
||||
#include "session.hpp"
|
||||
#include "settings/streamingpreferences.h"
|
||||
#include "streaming/streamutils.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
#include <SDL.h>
|
||||
@@ -19,13 +20,6 @@
|
||||
#include "video/sl.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_DARWIN)
|
||||
// Using full-screen desktop allows allows Spaces to work on macOS.
|
||||
#define SDL_OS_FULLSCREEN_FLAG SDL_WINDOW_FULLSCREEN_DESKTOP
|
||||
#else
|
||||
#define SDL_OS_FULLSCREEN_FLAG SDL_WINDOW_FULLSCREEN
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Scaling the icon down on Win32 looks dreadful, so render at lower res
|
||||
#define ICON_SIZE 32
|
||||
@@ -54,11 +48,11 @@ CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
|
||||
};
|
||||
|
||||
AUDIO_RENDERER_CALLBACKS Session::k_AudioCallbacks = {
|
||||
Session::sdlAudioInit,
|
||||
Session::sdlAudioStart,
|
||||
Session::sdlAudioStop,
|
||||
Session::sdlAudioCleanup,
|
||||
Session::sdlAudioDecodeAndPlaySample,
|
||||
Session::arInit,
|
||||
nullptr,
|
||||
nullptr,
|
||||
Session::arCleanup,
|
||||
Session::arDecodeAndPlaySample,
|
||||
CAPABILITY_DIRECT_SUBMIT
|
||||
};
|
||||
|
||||
@@ -85,6 +79,8 @@ void Session::clStageFailed(int stage, long errorCode)
|
||||
|
||||
void Session::clConnectionTerminated(long errorCode)
|
||||
{
|
||||
emit s_ActiveSession->displayLaunchError("Connection terminated");
|
||||
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Connection terminated: %ld",
|
||||
errorCode);
|
||||
@@ -108,13 +104,15 @@ void Session::clLogMessage(const char* format, ...)
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#define CALL_INITIALIZE(dec) (dec)->initialize(vds, window, videoFormat, width, height, frameRate, enableVsync)
|
||||
|
||||
bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window, int videoFormat, int width, int height,
|
||||
int frameRate, bool enableVsync, IVideoDecoder*& chosenDecoder)
|
||||
{
|
||||
#ifdef HAVE_SLVIDEO
|
||||
chosenDecoder = new SLVideoDecoder();
|
||||
if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate)) {
|
||||
if (CALL_INITIALIZE(chosenDecoder)) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SLVideo video decoder chosen");
|
||||
return true;
|
||||
@@ -129,7 +127,7 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
chosenDecoder = new FFmpegVideoDecoder();
|
||||
if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate, enableVsync)) {
|
||||
if (CALL_INITIALIZE(chosenDecoder)) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"FFmpeg-based video decoder chosen");
|
||||
return true;
|
||||
@@ -232,15 +230,65 @@ bool Session::isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelect
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Session::getDecoderCapabilities(StreamingPreferences::VideoDecoderSelection vds,
|
||||
int videoFormat, int width, int height, int frameRate)
|
||||
{
|
||||
IVideoDecoder* decoder;
|
||||
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Window* window = SDL_CreateWindow("", 0, 0, width, height, SDL_WINDOW_HIDDEN);
|
||||
if (!window) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create window for hardware decode test: %s",
|
||||
SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, decoder)) {
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_DestroyWindow(window);
|
||||
|
||||
int caps = decoder->getDecoderCapabilities();
|
||||
|
||||
delete decoder;
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
Session::Session(NvComputer* computer, NvApp& app)
|
||||
: m_Computer(computer),
|
||||
m_App(app),
|
||||
m_Window(nullptr),
|
||||
m_VideoDecoder(nullptr),
|
||||
m_DecoderLock(0),
|
||||
m_NeedsIdr(false)
|
||||
m_NeedsIdr(false),
|
||||
m_AudioDisabled(false),
|
||||
m_DisplayOriginX(0),
|
||||
m_DisplayOriginY(0),
|
||||
m_PendingWindowedTransition(false),
|
||||
m_OpusDecoder(nullptr),
|
||||
m_AudioRenderer(nullptr),
|
||||
m_AudioRendererLock(0)
|
||||
{
|
||||
qDebug() << "Server GPU:" << m_Computer->gpuModel;
|
||||
|
||||
}
|
||||
|
||||
void Session::initialize()
|
||||
{
|
||||
qInfo() << "Server GPU:" << m_Computer->gpuModel;
|
||||
qInfo() << "Server GFE version:" << m_Computer->gfeVersion;
|
||||
|
||||
LiInitializeVideoCallbacks(&m_VideoCallbacks);
|
||||
m_VideoCallbacks.setup = drSetup;
|
||||
@@ -277,7 +325,7 @@ Session::Session(NvComputer* computer, NvApp& app)
|
||||
{
|
||||
case StreamingPreferences::AC_AUTO:
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Autodetecting audio configuration");
|
||||
m_StreamConfig.audioConfiguration = sdlDetermineAudioConfiguration();
|
||||
m_StreamConfig.audioConfiguration = detectAudioConfiguration();
|
||||
break;
|
||||
case StreamingPreferences::AC_FORCE_STEREO:
|
||||
m_StreamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO;
|
||||
@@ -301,6 +349,16 @@ Session::Session(NvComputer* computer, NvApp& app)
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps);
|
||||
|
||||
// Oddly enough, the H.264 encoder on my GTX 1080 is actually more
|
||||
// performant than the HEVC encoder (especially because split frame encode
|
||||
// is disabled for HEVC by moonlight-common-c). Use H.264 for 1440p 100 FPS and above.
|
||||
if (m_StreamConfig.width * m_StreamConfig.height * m_StreamConfig.fps >= 2560 * 1440 * 100) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Preferring H.264 for higher performance");
|
||||
m_StreamConfig.supportsHevc = false;
|
||||
}
|
||||
|
||||
m_StreamConfig.enableHdr = false;
|
||||
break;
|
||||
case StreamingPreferences::VCC_FORCE_H264:
|
||||
@@ -317,19 +375,30 @@ Session::Session(NvComputer* computer, NvApp& app)
|
||||
break;
|
||||
}
|
||||
|
||||
if (computer->activeAddress == computer->remoteAddress) {
|
||||
if (m_Computer->activeAddress == m_Computer->remoteAddress) {
|
||||
m_StreamConfig.streamingRemotely = 1;
|
||||
}
|
||||
else {
|
||||
m_StreamConfig.streamingRemotely = 0;
|
||||
}
|
||||
|
||||
if (computer->activeAddress == computer->localAddress) {
|
||||
if (m_Computer->activeAddress == m_Computer->localAddress) {
|
||||
m_StreamConfig.packetSize = 1392;
|
||||
}
|
||||
else {
|
||||
m_StreamConfig.packetSize = 1024;
|
||||
}
|
||||
|
||||
switch (m_Preferences.windowMode)
|
||||
{
|
||||
case StreamingPreferences::WM_FULLSCREEN_DESKTOP:
|
||||
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
break;
|
||||
case StreamingPreferences::WM_FULLSCREEN:
|
||||
default:
|
||||
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Session::emitLaunchWarning(QString text)
|
||||
@@ -356,6 +425,14 @@ bool Session::validateLaunch()
|
||||
emitLaunchWarning("Your settings selection to force software decoding may cause poor streaming performance.");
|
||||
}
|
||||
|
||||
if (m_Preferences.unsupportedFps && m_StreamConfig.fps > 60) {
|
||||
emitLaunchWarning("Using unsupported FPS options may cause stuttering or lag.");
|
||||
|
||||
if (m_Preferences.enableVsync) {
|
||||
emitLaunchWarning("V-sync will be disabled when streaming at a higher frame rate than the display.");
|
||||
}
|
||||
}
|
||||
|
||||
if (m_StreamConfig.supportsHevc) {
|
||||
bool hevcForced = m_Preferences.videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
|
||||
m_Preferences.videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR;
|
||||
@@ -418,7 +495,7 @@ bool Session::validateLaunch()
|
||||
|
||||
if (m_StreamConfig.width >= 3840) {
|
||||
// Only allow 4K on GFE 3.x+
|
||||
if (m_Computer->gfeVersion.isNull() || m_Computer->gfeVersion.startsWith("2.")) {
|
||||
if (m_Computer->gfeVersion.isEmpty() || m_Computer->gfeVersion.startsWith("2.")) {
|
||||
emitLaunchWarning("GeForce Experience 3.0 or higher is required for 4K streaming.");
|
||||
|
||||
m_StreamConfig.width = 1920;
|
||||
@@ -435,6 +512,12 @@ bool Session::validateLaunch()
|
||||
}
|
||||
}
|
||||
|
||||
// Test that audio hardware is functional
|
||||
m_AudioDisabled = !testAudio(m_StreamConfig.audioConfiguration);
|
||||
if (m_AudioDisabled) {
|
||||
emitLaunchWarning("Failed to open audio device. Audio will be unavailable during this session.");
|
||||
}
|
||||
|
||||
if (m_Preferences.videoDecoderSelection == StreamingPreferences::VDS_FORCE_HARDWARE &&
|
||||
!isHardwareDecodeAvailable(m_Preferences.videoDecoderSelection,
|
||||
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
||||
@@ -452,6 +535,13 @@ bool Session::validateLaunch()
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the capability flags from the chosen decoder/renderer
|
||||
m_VideoCallbacks.capabilities |= getDecoderCapabilities(m_Preferences.videoDecoderSelection,
|
||||
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
||||
m_StreamConfig.width,
|
||||
m_StreamConfig.height,
|
||||
m_StreamConfig.fps);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -469,8 +559,7 @@ class DeferredSessionCleanupTask : public QRunnable
|
||||
}
|
||||
};
|
||||
|
||||
void Session::getWindowDimensions(bool fullScreen,
|
||||
int& x, int& y,
|
||||
void Session::getWindowDimensions(int& x, int& y,
|
||||
int& width, int& height)
|
||||
{
|
||||
int displayIndex = 0;
|
||||
@@ -479,20 +568,29 @@ void Session::getWindowDimensions(bool fullScreen,
|
||||
displayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||
SDL_assert(displayIndex >= 0);
|
||||
}
|
||||
// If there's a display matching this exact resolution, pick that
|
||||
// one (for native full-screen streaming). Otherwise, assume
|
||||
// display 0 for now. TODO: Default to the screen that the Qt window is on
|
||||
else if (fullScreen) {
|
||||
// Create our window on the same display that Qt's UI
|
||||
// was being displayed on.
|
||||
else {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Qt UI screen is at (%d,%d)",
|
||||
m_DisplayOriginX, m_DisplayOriginY);
|
||||
for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
|
||||
SDL_DisplayMode mode;
|
||||
if (SDL_GetCurrentDisplayMode(i, &mode) == 0 &&
|
||||
m_ActiveVideoWidth == mode.w &&
|
||||
m_ActiveVideoHeight == mode.h) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Found exact resolution match on display: %d",
|
||||
i);
|
||||
displayIndex = i;
|
||||
break;
|
||||
SDL_Rect displayBounds;
|
||||
|
||||
if (SDL_GetDisplayBounds(i, &displayBounds) == 0) {
|
||||
if (displayBounds.x == m_DisplayOriginX &&
|
||||
displayBounds.y == m_DisplayOriginY) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL found matching display %d",
|
||||
i);
|
||||
displayIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetDisplayBounds(%d) failed: %s",
|
||||
i, SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -501,23 +599,19 @@ void Session::getWindowDimensions(bool fullScreen,
|
||||
if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) == 0) {
|
||||
width = usableBounds.w;
|
||||
height = usableBounds.h;
|
||||
x = usableBounds.x;
|
||||
y = usableBounds.y;
|
||||
|
||||
if (m_Window != nullptr) {
|
||||
int top, left, bottom, right;
|
||||
|
||||
if (SDL_GetWindowBordersSize(m_Window, &top, &left, &bottom, &right) < 0) {
|
||||
if (SDL_GetWindowBordersSize(m_Window, &top, &left, &bottom, &right) == 0) {
|
||||
width -= left + right;
|
||||
height -= top + bottom;
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to get window border size");
|
||||
return;
|
||||
"Unable to get window border size: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
|
||||
x += left;
|
||||
y += top;
|
||||
|
||||
width -= left + right;
|
||||
height -= top + bottom;
|
||||
|
||||
// If the stream window can fit within the usable drawing area with 1:1
|
||||
// scaling, do that rather than filling the screen.
|
||||
@@ -534,44 +628,107 @@ void Session::getWindowDimensions(bool fullScreen,
|
||||
|
||||
width = m_StreamConfig.width;
|
||||
height = m_StreamConfig.height;
|
||||
x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex);
|
||||
}
|
||||
|
||||
x = y = SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex);
|
||||
}
|
||||
|
||||
void Session::updateOptimalWindowDisplayMode()
|
||||
{
|
||||
SDL_DisplayMode desktopMode, bestMode, mode;
|
||||
int displayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||
|
||||
// Try the current display mode first. On macOS, this will be the normal
|
||||
// scaled desktop resolution setting.
|
||||
if (SDL_GetDesktopDisplayMode(displayIndex, &desktopMode) == 0) {
|
||||
// If this doesn't fit the selected resolution, use the native
|
||||
// resolution of the panel (unscaled).
|
||||
if (desktopMode.w < m_ActiveVideoWidth || desktopMode.h < m_ActiveVideoHeight) {
|
||||
if (!StreamUtils::getRealDesktopMode(displayIndex, &desktopMode)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetDesktopDisplayMode() failed: %s",
|
||||
SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
// Start with the native desktop resolution and try to find
|
||||
// the highest refresh rate that our stream FPS evenly divides.
|
||||
bestMode = desktopMode;
|
||||
bestMode.refresh_rate = 0;
|
||||
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
||||
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
|
||||
if (mode.w == desktopMode.w && mode.h == desktopMode.h &&
|
||||
mode.refresh_rate % m_StreamConfig.fps == 0) {
|
||||
if (mode.refresh_rate > bestMode.refresh_rate) {
|
||||
bestMode = mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMode.refresh_rate == 0) {
|
||||
// We may find no match if the user has moved a 120 FPS
|
||||
// stream onto a 60 Hz monitor (since no refresh rate can
|
||||
// divide our FPS setting). We'll stick to the default in
|
||||
// this case.
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No matching refresh rate found; using desktop mode");
|
||||
bestMode = desktopMode;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Chosen best display mode: %dx%dx%d",
|
||||
bestMode.w, bestMode.h, bestMode.refresh_rate);
|
||||
|
||||
SDL_SetWindowDisplayMode(m_Window, &bestMode);
|
||||
}
|
||||
|
||||
void Session::toggleFullscreen()
|
||||
{
|
||||
bool fullScreen = !(SDL_GetWindowFlags(m_Window) & SDL_OS_FULLSCREEN_FLAG);
|
||||
|
||||
int x, y, width, height;
|
||||
|
||||
// If we're leaving full screen, toggle out before setting our size and position
|
||||
// that way we have accurate display size metrics (not the size our stream changed it to).
|
||||
if (!fullScreen) {
|
||||
SDL_SetWindowFullscreen(m_Window, 0);
|
||||
SDL_SetWindowResizable(m_Window, SDL_TRUE);
|
||||
}
|
||||
|
||||
getWindowDimensions(fullScreen, x, y, width, height);
|
||||
|
||||
SDL_SetWindowPosition(m_Window, x, y);
|
||||
SDL_SetWindowSize(m_Window, width, height);
|
||||
bool fullScreen = !(SDL_GetWindowFlags(m_Window) & m_FullScreenFlag);
|
||||
|
||||
if (fullScreen) {
|
||||
SDL_SetWindowResizable(m_Window, SDL_FALSE);
|
||||
SDL_SetWindowFullscreen(m_Window, SDL_OS_FULLSCREEN_FLAG);
|
||||
SDL_SetWindowFullscreen(m_Window, m_FullScreenFlag);
|
||||
}
|
||||
else {
|
||||
SDL_SetWindowFullscreen(m_Window, 0);
|
||||
SDL_SetWindowResizable(m_Window, SDL_TRUE);
|
||||
|
||||
// Reposition the window when the resize is complete
|
||||
m_PendingWindowedTransition = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Session::exec()
|
||||
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).
|
||||
initialize();
|
||||
|
||||
// Check for validation errors/warnings and emit
|
||||
// signals for them, if appropriate
|
||||
if (!validateLaunch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Manually pump the UI thread for the view
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
// Wait 1.5 seconds before connecting to let the user
|
||||
// have time to read any messages present on the segue
|
||||
uint32_t start = SDL_GetTicks();
|
||||
while (!SDL_TICKS_PASSED(SDL_GetTicks(), start + 1500)) {
|
||||
// Pump the UI loop while we wait
|
||||
SDL_Delay(5);
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
|
||||
// Wait for any old session to finish cleanup
|
||||
s_ActiveSessionSemaphore.acquire();
|
||||
@@ -581,7 +738,7 @@ void Session::exec()
|
||||
|
||||
// Initialize the gamepad code with our preferences
|
||||
StreamingPreferences prefs;
|
||||
SdlInputHandler inputHandler(prefs.multiController);
|
||||
SdlInputHandler inputHandler(prefs, m_Computer);
|
||||
|
||||
// The UI should have ensured the old game was already quit
|
||||
// if we decide to stream a different game.
|
||||
@@ -636,15 +793,16 @@ void Session::exec()
|
||||
|
||||
// Older GFE versions didn't have this field
|
||||
QByteArray siGfeVersion;
|
||||
if (!m_Computer->gfeVersion.isNull()) {
|
||||
if (!m_Computer->gfeVersion.isEmpty()) {
|
||||
siGfeVersion = m_Computer->gfeVersion.toLatin1();
|
||||
}
|
||||
if (!siGfeVersion.isNull()) {
|
||||
if (!siGfeVersion.isEmpty()) {
|
||||
hostInfo.serverInfoGfeVersion = siGfeVersion.data();
|
||||
}
|
||||
|
||||
int err = LiStartConnection(&hostInfo, &m_StreamConfig, &k_ConnCallbacks,
|
||||
&m_VideoCallbacks, &k_AudioCallbacks,
|
||||
&m_VideoCallbacks,
|
||||
m_AudioDisabled ? nullptr : &k_AudioCallbacks,
|
||||
NULL, 0, NULL, 0);
|
||||
if (err != 0) {
|
||||
// We already displayed an error dialog in the stage failure
|
||||
@@ -659,15 +817,14 @@ void Session::exec()
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
int x, y, width, height;
|
||||
getWindowDimensions(m_Preferences.fullScreen,
|
||||
x, y, width, height);
|
||||
getWindowDimensions(x, y, width, height);
|
||||
|
||||
m_Window = SDL_CreateWindow("Moonlight",
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
0);
|
||||
SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
if (!m_Window) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_CreateWindow() failed: %s",
|
||||
@@ -680,11 +837,13 @@ void Session::exec()
|
||||
// For non-full screen windows, call getWindowDimensions()
|
||||
// again after creating a window to allow it to account
|
||||
// for window chrome size.
|
||||
if (!m_Preferences.fullScreen) {
|
||||
getWindowDimensions(false, x, y, width, height);
|
||||
if (m_Preferences.windowMode == StreamingPreferences::WM_WINDOWED) {
|
||||
getWindowDimensions(x, y, width, height);
|
||||
|
||||
SDL_SetWindowPosition(m_Window, x, y);
|
||||
// We must set the size before the position because centering
|
||||
// won't work unless it knows the final size of the window.
|
||||
SDL_SetWindowSize(m_Window, width, height);
|
||||
SDL_SetWindowPosition(m_Window, x, y);
|
||||
|
||||
// Passing SDL_WINDOW_RESIZABLE to set this during window
|
||||
// creation causes our window to be full screen for some reason
|
||||
@@ -692,13 +851,10 @@ void Session::exec()
|
||||
}
|
||||
else {
|
||||
// Update the window display mode based on our current monitor
|
||||
SDL_DisplayMode mode;
|
||||
if (SDL_GetDesktopDisplayMode(SDL_GetWindowDisplayIndex(m_Window), &mode) == 0) {
|
||||
SDL_SetWindowDisplayMode(m_Window, &mode);
|
||||
}
|
||||
updateOptimalWindowDisplayMode();
|
||||
|
||||
// Enter full screen
|
||||
SDL_SetWindowFullscreen(m_Window, SDL_OS_FULLSCREEN_FLAG);
|
||||
SDL_SetWindowFullscreen(m_Window, m_FullScreenFlag);
|
||||
}
|
||||
|
||||
QSvgRenderer svgIconRenderer(QString(":/res/moonlight.svg"));
|
||||
@@ -724,7 +880,7 @@ void Session::exec()
|
||||
// Capture the mouse by default on release builds only.
|
||||
// This prevents the mouse from becoming trapped inside
|
||||
// Moonlight when it's halted at a debug break.
|
||||
if (m_Preferences.fullScreen) {
|
||||
if (m_Preferences.windowMode != StreamingPreferences::WM_WINDOWED) {
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
}
|
||||
#endif
|
||||
@@ -757,6 +913,8 @@ void Session::exec()
|
||||
SDL_GetError());
|
||||
}
|
||||
|
||||
int currentDisplayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||
|
||||
// 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.
|
||||
SDL_Event event;
|
||||
@@ -795,11 +953,61 @@ void Session::exec()
|
||||
}
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
// Capture mouse cursor when user actives the window by clicking on
|
||||
// window's client area (borders and title bar excluded).
|
||||
// Without this you would have to click the window twice (once to
|
||||
// activate it, second time to enable capture). With this you need to
|
||||
// click it only once.
|
||||
// On Linux, the button press event is delivered after the focus gain
|
||||
// so this is not neccessary (and leads to a click sent to the host
|
||||
// when focusing the window by clicking).
|
||||
// By excluding window's borders and title bar out, lets user still
|
||||
// interact with them without mouse capture kicking in.
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
|
||||
if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
|
||||
int mouseX, mouseY;
|
||||
Uint32 mouseState = SDL_GetGlobalMouseState(&mouseX, &mouseY);
|
||||
if (mouseState & SDL_BUTTON(SDL_BUTTON_LEFT)) {
|
||||
int x, y, width, height;
|
||||
SDL_GetWindowPosition(m_Window, &x, &y);
|
||||
SDL_GetWindowSize(m_Window, &width, &height);
|
||||
if (mouseX > x && mouseX < x+width && mouseY > y && mouseY < y+height) {
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Release mouse cursor when another window is activated (e.g. by using ALT+TAB).
|
||||
// This lets user to interact with our window's title bar and with the buttons in it.
|
||||
if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
}
|
||||
|
||||
// We want to recreate the decoder for resizes (full-screen toggles) and the initial shown event.
|
||||
// We use SDL_WINDOWEVENT_SIZE_CHANGED rather than SDL_WINDOWEVENT_RESIZED because the latter doesn't
|
||||
// seem to fire when switching from windowed to full-screen on X11.
|
||||
if (event.window.event != SDL_WINDOWEVENT_SIZE_CHANGED && event.window.event != SDL_WINDOWEVENT_SHOWN) {
|
||||
break;
|
||||
// Check that the window display hasn't changed. If it has, we want
|
||||
// to recreate the decoder to allow it to adapt to the new display.
|
||||
// This will allow Pacer to pull the new display refresh rate.
|
||||
if (SDL_GetWindowDisplayIndex(m_Window) == currentDisplayIndex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Complete any repositioning that was deferred until
|
||||
// the resize from full-screen to windowed had completed.
|
||||
// If we try to do this immediately, the resize won't take effect
|
||||
// properly on Windows.
|
||||
if (m_PendingWindowedTransition) {
|
||||
m_PendingWindowedTransition = false;
|
||||
|
||||
int x, y, width, height;
|
||||
getWindowDimensions(x, y, width, height);
|
||||
|
||||
SDL_SetWindowSize(m_Window, width, height);
|
||||
SDL_SetWindowPosition(m_Window, x, y);
|
||||
}
|
||||
|
||||
// Fall through
|
||||
@@ -817,10 +1025,8 @@ void Session::exec()
|
||||
SDL_FlushEvent(SDL_WINDOWEVENT);
|
||||
|
||||
// Update the window display mode based on our current monitor
|
||||
SDL_DisplayMode mode;
|
||||
if (SDL_GetDesktopDisplayMode(SDL_GetWindowDisplayIndex(m_Window), &mode) == 0) {
|
||||
SDL_SetWindowDisplayMode(m_Window, &mode);
|
||||
}
|
||||
currentDisplayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||
updateOptimalWindowDisplayMode();
|
||||
|
||||
// Now that the old decoder is dead, flush any events it may
|
||||
// have queued to reset itself (if this reset was the result
|
||||
@@ -829,18 +1035,31 @@ void Session::exec()
|
||||
SDL_FlushEvent(SDL_RENDER_DEVICE_RESET);
|
||||
SDL_FlushEvent(SDL_RENDER_TARGETS_RESET);
|
||||
|
||||
// Choose a new decoder (hopefully the same one, but possibly
|
||||
// not if a GPU was removed or something).
|
||||
if (!chooseDecoder(m_Preferences.videoDecoderSelection,
|
||||
m_Window, m_ActiveVideoFormat, m_ActiveVideoWidth,
|
||||
m_ActiveVideoHeight, m_ActiveVideoFrameRate,
|
||||
m_Preferences.enableVsync,
|
||||
s_ActiveSession->m_VideoDecoder)) {
|
||||
SDL_AtomicUnlock(&m_DecoderLock);
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to recreate decoder after reset");
|
||||
emit displayLaunchError("Unable to initialize video decoder. Please check your streaming settings and try again.");
|
||||
goto DispatchDeferredCleanup;
|
||||
{
|
||||
// If the stream exceeds the display refresh rate (plus some slack),
|
||||
// forcefully disable V-sync to allow the stream to render faster
|
||||
// than the display.
|
||||
int displayHz = StreamUtils::getDisplayRefreshRate(m_Window);
|
||||
bool enableVsync = m_Preferences.enableVsync;
|
||||
if (displayHz + 5 < m_StreamConfig.fps) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Disabling V-sync because refresh rate limit exceeded");
|
||||
enableVsync = false;
|
||||
}
|
||||
|
||||
// Choose a new decoder (hopefully the same one, but possibly
|
||||
// not if a GPU was removed or something).
|
||||
if (!chooseDecoder(m_Preferences.videoDecoderSelection,
|
||||
m_Window, m_ActiveVideoFormat, m_ActiveVideoWidth,
|
||||
m_ActiveVideoHeight, m_ActiveVideoFrameRate,
|
||||
enableVsync,
|
||||
s_ActiveSession->m_VideoDecoder)) {
|
||||
SDL_AtomicUnlock(&m_DecoderLock);
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to recreate decoder after reset");
|
||||
emit displayLaunchError("Unable to initialize video decoder. Please check your streaming settings and try again.");
|
||||
goto DispatchDeferredCleanup;
|
||||
}
|
||||
}
|
||||
|
||||
// Request an IDR frame to complete the reset
|
||||
@@ -899,6 +1118,9 @@ DispatchDeferredCleanup:
|
||||
m_VideoDecoder = nullptr;
|
||||
SDL_AtomicUnlock(&m_DecoderLock);
|
||||
|
||||
// Destroy the audio renderer which must also be done on the main thread
|
||||
cleanupAudioRendererOnMainThread();
|
||||
|
||||
SDL_DestroyWindow(m_Window);
|
||||
if (iconSurface != nullptr) {
|
||||
SDL_FreeSurface(iconSurface);
|
||||
|
||||
+39
-29
@@ -8,6 +8,7 @@
|
||||
#include "settings/streamingpreferences.h"
|
||||
#include "input.hpp"
|
||||
#include "video/decoder.h"
|
||||
#include "audio/renderers/renderer.h"
|
||||
|
||||
class Session : public QObject
|
||||
{
|
||||
@@ -19,12 +20,16 @@ class Session : public QObject
|
||||
public:
|
||||
explicit Session(NvComputer* computer, NvApp& app);
|
||||
|
||||
Q_INVOKABLE void exec();
|
||||
Q_INVOKABLE void exec(int displayOriginX, int displayOriginY);
|
||||
|
||||
static
|
||||
bool isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelection vds,
|
||||
int videoFormat, int width, int height, int frameRate);
|
||||
|
||||
static
|
||||
int getDecoderCapabilities(StreamingPreferences::VideoDecoderSelection vds,
|
||||
int videoFormat, int width, int height, int frameRate);
|
||||
|
||||
signals:
|
||||
void stageStarting(QString stage);
|
||||
|
||||
@@ -37,20 +42,29 @@ signals:
|
||||
void displayLaunchWarning(QString text);
|
||||
|
||||
private:
|
||||
void initialize();
|
||||
|
||||
bool validateLaunch();
|
||||
|
||||
void emitLaunchWarning(QString text);
|
||||
|
||||
int getDecoderCapabilities();
|
||||
|
||||
int sdlDetermineAudioConfiguration();
|
||||
IAudioRenderer* createAudioRenderer();
|
||||
|
||||
void getWindowDimensions(bool fullScreen,
|
||||
int& x, int& y,
|
||||
int detectAudioConfiguration();
|
||||
|
||||
void cleanupAudioRendererOnMainThread();
|
||||
|
||||
bool testAudio(int audioConfiguration);
|
||||
|
||||
void getWindowDimensions(int& x, int& y,
|
||||
int& width, int& height);
|
||||
|
||||
void toggleFullscreen();
|
||||
|
||||
void updateOptimalWindowDisplayMode();
|
||||
|
||||
static
|
||||
bool chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window, int videoFormat, int width, int height,
|
||||
@@ -68,6 +82,17 @@ private:
|
||||
static
|
||||
void clLogMessage(const char* format, ...);
|
||||
|
||||
static
|
||||
int arInit(int audioConfiguration,
|
||||
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||
void* arContext, int arFlags);
|
||||
|
||||
static
|
||||
void arCleanup();
|
||||
|
||||
static
|
||||
void arDecodeAndPlaySample(char* sampleData, int sampleLength);
|
||||
|
||||
static
|
||||
int drSetup(int videoFormat, int width, int height, int frameRate, void*, int);
|
||||
|
||||
@@ -77,23 +102,6 @@ private:
|
||||
static
|
||||
int drSubmitDecodeUnit(PDECODE_UNIT du);
|
||||
|
||||
static
|
||||
int sdlAudioInit(int audioConfiguration,
|
||||
POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||
void* arContext, int arFlags);
|
||||
|
||||
static
|
||||
void sdlAudioStart();
|
||||
|
||||
static
|
||||
void sdlAudioStop();
|
||||
|
||||
static
|
||||
void sdlAudioCleanup();
|
||||
|
||||
static
|
||||
void sdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength);
|
||||
|
||||
StreamingPreferences m_Preferences;
|
||||
STREAM_CONFIGURATION m_StreamConfig;
|
||||
DECODER_RENDERER_CALLBACKS m_VideoCallbacks;
|
||||
@@ -103,20 +111,22 @@ private:
|
||||
IVideoDecoder* m_VideoDecoder;
|
||||
SDL_SpinLock m_DecoderLock;
|
||||
bool m_NeedsIdr;
|
||||
bool m_AudioDisabled;
|
||||
Uint32 m_FullScreenFlag;
|
||||
int m_DisplayOriginX;
|
||||
int m_DisplayOriginY;
|
||||
bool m_PendingWindowedTransition;
|
||||
|
||||
int m_ActiveVideoFormat;
|
||||
int m_ActiveVideoWidth;
|
||||
int m_ActiveVideoHeight;
|
||||
int m_ActiveVideoFrameRate;
|
||||
|
||||
static SDL_AudioDeviceID s_AudioDevice;
|
||||
static OpusMSDecoder* s_OpusDecoder;
|
||||
static short s_OpusDecodeBuffer[];
|
||||
static int s_ChannelCount;
|
||||
static int s_PendingDrops;
|
||||
static int s_PendingHardDrops;
|
||||
static unsigned int s_SampleIndex;
|
||||
static Uint32 s_BaselinePendingData;
|
||||
OpusMSDecoder* m_OpusDecoder;
|
||||
short m_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME];
|
||||
IAudioRenderer* m_AudioRenderer;
|
||||
OPUS_MULTISTREAM_CONFIGURATION m_AudioConfig;
|
||||
SDL_SpinLock m_AudioRendererLock;
|
||||
|
||||
static AUDIO_RENDERER_CALLBACKS k_AudioCallbacks;
|
||||
static CONNECTION_LISTENER_CALLBACKS k_ConnCallbacks;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
#include "streamutils.h"
|
||||
|
||||
#include <Qt>
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#endif
|
||||
|
||||
void StreamUtils::scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst)
|
||||
{
|
||||
int dstH = dst->w * src->h / src->w;
|
||||
@@ -18,3 +24,104 @@ void StreamUtils::scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst)
|
||||
SDL_assert(dst->h * src->w / src->h <= dst->w);
|
||||
}
|
||||
}
|
||||
|
||||
int StreamUtils::getDisplayRefreshRate(SDL_Window* window)
|
||||
{
|
||||
int displayIndex = SDL_GetWindowDisplayIndex(window);
|
||||
if (displayIndex < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to get current display: %s",
|
||||
SDL_GetError());
|
||||
|
||||
// Assume display 0 if it fails
|
||||
displayIndex = 0;
|
||||
}
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
||||
// Use the window display mode for full-screen exclusive mode
|
||||
if (SDL_GetWindowDisplayMode(window, &mode) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetWindowDisplayMode() failed: %s",
|
||||
SDL_GetError());
|
||||
|
||||
// Assume 60 Hz
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Use the current display mode for windowed and borderless
|
||||
if (SDL_GetCurrentDisplayMode(displayIndex, &mode) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetCurrentDisplayMode() failed: %s",
|
||||
SDL_GetError());
|
||||
|
||||
// Assume 60 Hz
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
|
||||
// May be zero if undefined
|
||||
if (mode.refresh_rate == 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Refresh rate unknown; assuming 60 Hz");
|
||||
mode.refresh_rate = 60;
|
||||
}
|
||||
|
||||
return mode.refresh_rate;
|
||||
}
|
||||
|
||||
bool StreamUtils::getRealDesktopMode(int displayIndex, SDL_DisplayMode* mode)
|
||||
{
|
||||
#ifdef Q_OS_DARWIN
|
||||
#define MAX_DISPLAYS 16
|
||||
CGDirectDisplayID displayIds[MAX_DISPLAYS];
|
||||
uint32_t displayCount = 0;
|
||||
CGGetActiveDisplayList(MAX_DISPLAYS, displayIds, &displayCount);
|
||||
if (displayIndex >= displayCount) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Too many displays: %d vs %d",
|
||||
displayIndex, displayCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_zerop(mode);
|
||||
|
||||
// Retina displays have non-native resolutions both below and above (!) their
|
||||
// native resolution, so it's impossible for us to figure out what's actually
|
||||
// native on macOS using the SDL API alone. We'll talk to CoreGraphics to
|
||||
// find the correct resolution and match it in our SDL list.
|
||||
CFArrayRef modeList = CGDisplayCopyAllDisplayModes(displayIds[displayIndex], nullptr);
|
||||
CFIndex count = CFArrayGetCount(modeList);
|
||||
for (CFIndex i = 0; i < count; i++) {
|
||||
auto cgMode = (CGDisplayModeRef)(CFArrayGetValueAtIndex(modeList, i));
|
||||
if ((CGDisplayModeGetIOFlags(cgMode) & kDisplayModeNativeFlag) != 0) {
|
||||
mode->w = static_cast<int>(CGDisplayModeGetWidth(cgMode));
|
||||
mode->h = static_cast<int>(CGDisplayModeGetHeight(cgMode));
|
||||
break;
|
||||
}
|
||||
}
|
||||
CFRelease(modeList);
|
||||
|
||||
// Now find the SDL mode that matches the CG native mode
|
||||
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
||||
SDL_DisplayMode thisMode;
|
||||
if (SDL_GetDisplayMode(displayIndex, i, &thisMode) == 0) {
|
||||
if (thisMode.w == mode->w && thisMode.h == mode->h &&
|
||||
thisMode.refresh_rate >= mode->refresh_rate) {
|
||||
*mode = thisMode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (SDL_GetDesktopDisplayMode(displayIndex, mode) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetDesktopDisplayMode() failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,4 +7,10 @@ class StreamUtils
|
||||
public:
|
||||
static
|
||||
void scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst);
|
||||
|
||||
static
|
||||
bool getRealDesktopMode(int displayIndex, SDL_DisplayMode* mode);
|
||||
|
||||
static
|
||||
int getDisplayRefreshRate(SDL_Window* window);
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ public:
|
||||
int frameRate,
|
||||
bool enableVsync) = 0;
|
||||
virtual bool isHardwareAccelerated() = 0;
|
||||
virtual int getDecoderCapabilities() = 0;
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
||||
virtual void renderFrame(SDL_UserEvent* event) = 0;
|
||||
virtual void dropFrame(SDL_UserEvent* event) = 0;
|
||||
|
||||
@@ -364,6 +364,13 @@ bool DXVA2Renderer::isDecoderBlacklisted()
|
||||
id.Description,
|
||||
id.VendorId,
|
||||
id.DeviceId);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GPU driver: %s %d.%d.%d.%d",
|
||||
id.Driver,
|
||||
HIWORD(id.DriverVersion.HighPart),
|
||||
LOWORD(id.DriverVersion.HighPart),
|
||||
HIWORD(id.DriverVersion.LowPart),
|
||||
LOWORD(id.DriverVersion.LowPart));
|
||||
|
||||
if (id.VendorId == 0x8086) {
|
||||
// Intel seems to encode the series in the high byte of
|
||||
@@ -379,26 +386,50 @@ bool DXVA2Renderer::isDecoderBlacklisted()
|
||||
case 0x1600: // Broadwell
|
||||
case 0x2200: // Cherry Trail and Braswell
|
||||
// Blacklist these for HEVC to avoid hybrid decode
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GPU blacklisted for HEVC due to hybrid decode");
|
||||
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
||||
break;
|
||||
default:
|
||||
// Everything else is fine with whatever it says it supports
|
||||
result = false;
|
||||
// Intel drivers from before late-2017 had a bug that caused some strange artifacts
|
||||
// when decoding HEVC. Avoid HEVC on drivers prior to build 4836 which I confirmed
|
||||
// is not affected on my Intel HD 515.
|
||||
// https://github.com/moonlight-stream/moonlight-qt/issues/32
|
||||
// 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");
|
||||
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
||||
}
|
||||
else {
|
||||
// Everything else is fine with whatever it says it supports
|
||||
result = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (id.VendorId == 0x10DE) {
|
||||
// For NVIDIA, we wait to avoid those GPUs with Feature Set E
|
||||
// for HEVC decoding, since that's hybrid.
|
||||
// for HEVC decoding, since that's hybrid. It appears that Kepler GPUs
|
||||
// also had some hybrid decode support (per DXVA2 Checker) so we'll
|
||||
// blacklist those too.
|
||||
// https://en.wikipedia.org/wiki/Nvidia_PureVideo
|
||||
// https://bluesky23.yukishigure.com/en/dxvac/deviceInfo/decoder.html
|
||||
// http://envytools.readthedocs.io/en/latest/hw/pciid.html (missing GM200)
|
||||
if ((id.DeviceId >= 0x1340 && id.DeviceId <= 0x137F) || // GM108
|
||||
if ((id.DeviceId >= 0x1180 && id.DeviceId <= 0x11BF) || // GK104
|
||||
(id.DeviceId >= 0x11C0 && id.DeviceId <= 0x11FF) || // GK106
|
||||
(id.DeviceId >= 0x0FC0 && id.DeviceId <= 0x0FFF) || // GK107
|
||||
(id.DeviceId >= 0x1000 && id.DeviceId <= 0x103F) || // GK110/GK110B
|
||||
(id.DeviceId >= 0x1280 && id.DeviceId <= 0x12BF) || // GK208
|
||||
(id.DeviceId >= 0x1340 && id.DeviceId <= 0x137F) || // GM108
|
||||
(id.DeviceId >= 0x1380 && id.DeviceId <= 0x13BF) || // GM107
|
||||
(id.DeviceId >= 0x13C0 && id.DeviceId <= 0x13FF) || // GM204
|
||||
(id.DeviceId >= 0x1617 && id.DeviceId <= 0x161A) || // GM204
|
||||
(id.DeviceId == 0x1667) || // GM204
|
||||
(id.DeviceId >= 0x17C0 && id.DeviceId <= 0x17FF)) { // GM200
|
||||
// Avoid HEVC on Feature Set E GPUs
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GPU blacklisted for HEVC due to hybrid decode");
|
||||
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
||||
}
|
||||
else {
|
||||
@@ -466,7 +497,6 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
||||
D3DPRESENT_PARAMETERS d3dpp = {};
|
||||
d3dpp.hDeviceWindow = info.info.win.window;
|
||||
d3dpp.Flags = D3DPRESENTFLAG_VIDEO;
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
|
||||
if ((windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
||||
d3dpp.Windowed = false;
|
||||
@@ -489,10 +519,20 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
||||
// to reduce latency by avoiding double v-syncing.
|
||||
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
|
||||
|
||||
// D3DSWAPEFFECT_FLIPEX requires at least 2 back buffers to allow us to
|
||||
// continue while DWM is waiting to render the surface to the display.
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_FLIPEX;
|
||||
d3dpp.BackBufferCount = 2;
|
||||
// If V-sync is enabled (not rendering faster than display),
|
||||
// we can use FlipEx for more efficient swapping.
|
||||
if (enableVsync) {
|
||||
// D3DSWAPEFFECT_FLIPEX requires at least 2 back buffers to allow us to
|
||||
// continue while DWM is waiting to render the surface to the display.
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_FLIPEX;
|
||||
d3dpp.BackBufferCount = 2;
|
||||
}
|
||||
else {
|
||||
// With V-sync off, we won't use FlipEx because that will block while
|
||||
// DWM is waiting to render our surface (effectively behaving like V-Sync).
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
d3dpp.BackBufferCount = 1;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Windowed mode with DWM running");
|
||||
@@ -501,6 +541,7 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
||||
// Uncomposited desktop or full-screen exclusive mode with V-sync enabled
|
||||
// We will enable V-sync in this scenario to avoid tearing.
|
||||
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
d3dpp.BackBufferCount = 1;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -510,6 +551,7 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
||||
// Uncomposited desktop or full-screen exclusive mode with V-sync disabled
|
||||
// We will allowing tearing for lowest latency.
|
||||
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
d3dpp.BackBufferCount = 1;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -619,6 +661,16 @@ bool DXVA2Renderer::needsTestFrame()
|
||||
return false;
|
||||
}
|
||||
|
||||
int DXVA2Renderer::getDecoderCapabilities()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
IFFmpegRenderer::VSyncConstraint DXVA2Renderer::getVsyncConstraint()
|
||||
{
|
||||
return VSYNC_ANY;
|
||||
}
|
||||
|
||||
void DXVA2Renderer::renderFrameAtVsync(AVFrame *frame)
|
||||
{
|
||||
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
|
||||
|
||||
@@ -24,6 +24,8 @@ public:
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual VSyncConstraint getVsyncConstraint();
|
||||
|
||||
private:
|
||||
bool initializeDecoder();
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
#include "displaylinkvsyncsource.h"
|
||||
|
||||
DisplayLinkVsyncSource::DisplayLinkVsyncSource(Pacer* pacer)
|
||||
: m_Pacer(pacer),
|
||||
m_DisplayLink(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DisplayLinkVsyncSource::~DisplayLinkVsyncSource()
|
||||
{
|
||||
if (m_DisplayLink != nullptr) {
|
||||
CVDisplayLinkStop(m_DisplayLink);
|
||||
CVDisplayLinkRelease(m_DisplayLink);
|
||||
}
|
||||
}
|
||||
|
||||
CVReturn
|
||||
DisplayLinkVsyncSource::displayLinkOutputCallback(
|
||||
CVDisplayLinkRef,
|
||||
const CVTimeStamp* /* now */,
|
||||
const CVTimeStamp* /* vsyncTime */,
|
||||
CVOptionFlags,
|
||||
CVOptionFlags*,
|
||||
void *displayLinkContext)
|
||||
{
|
||||
auto me = reinterpret_cast<DisplayLinkVsyncSource*>(displayLinkContext);
|
||||
|
||||
// In my testing on macOS 10.13, this callback is invoked about 24 ms
|
||||
// prior to the specified v-sync time (now - vsyncTime). Since this is
|
||||
// greater than the standard v-sync interval (16 ms = 60 FPS), we will
|
||||
// draw using the current host time, rather than the actual v-sync target
|
||||
// time. Because the CVDisplayLink is in sync with the actual v-sync
|
||||
// interval, even if many ms prior, we can safely use the current host time
|
||||
// and get a consistent callback for each v-sync. This reduces video latency
|
||||
// by at least 1 frame vs. rendering with the actual vsyncTime.
|
||||
me->m_Pacer->vsyncCallback(500 / me->m_DisplayFps);
|
||||
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
bool DisplayLinkVsyncSource::initialize(SDL_Window*, int displayFps)
|
||||
{
|
||||
m_DisplayFps = displayFps;
|
||||
|
||||
CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
|
||||
CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
|
||||
CVDisplayLinkStart(m_DisplayLink);
|
||||
return true;
|
||||
}
|
||||
@@ -1,31 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <CoreVideo/CoreVideo.h>
|
||||
|
||||
#include "pacer.h"
|
||||
|
||||
class DisplayLinkVsyncSource : public IVsyncSource
|
||||
class DisplayLinkVsyncSourceFactory
|
||||
{
|
||||
public:
|
||||
DisplayLinkVsyncSource(Pacer* pacer);
|
||||
|
||||
virtual ~DisplayLinkVsyncSource();
|
||||
|
||||
virtual bool initialize(SDL_Window* window, int displayFps);
|
||||
|
||||
private:
|
||||
static
|
||||
CVReturn
|
||||
displayLinkOutputCallback(
|
||||
CVDisplayLinkRef,
|
||||
const CVTimeStamp* now,
|
||||
const CVTimeStamp* /* vsyncTime */,
|
||||
CVOptionFlags,
|
||||
CVOptionFlags*,
|
||||
void *displayLinkContext);
|
||||
|
||||
Pacer* m_Pacer;
|
||||
CVDisplayLinkRef m_DisplayLink;
|
||||
int m_DisplayFps;
|
||||
IVsyncSource* createVsyncSource(Pacer* pacer);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#include "displaylinkvsyncsource.h"
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
#include <CoreVideo/CoreVideo.h>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
class DisplayLinkVsyncSource : public IVsyncSource
|
||||
{
|
||||
public:
|
||||
DisplayLinkVsyncSource(Pacer* pacer)
|
||||
: m_Pacer(pacer),
|
||||
m_DisplayLink(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
virtual ~DisplayLinkVsyncSource() override
|
||||
{
|
||||
if (m_DisplayLink != nullptr) {
|
||||
CVDisplayLinkStop(m_DisplayLink);
|
||||
CVDisplayLinkRelease(m_DisplayLink);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
CGDirectDisplayID
|
||||
getDisplayID(NSScreen* screen)
|
||||
{
|
||||
NSNumber* screenNumber = [screen deviceDescription][@"NSScreenNumber"];
|
||||
return [screenNumber unsignedIntValue];
|
||||
}
|
||||
|
||||
static
|
||||
CVReturn
|
||||
displayLinkOutputCallback(
|
||||
CVDisplayLinkRef displayLink,
|
||||
const CVTimeStamp* /* now */,
|
||||
const CVTimeStamp* /* vsyncTime */,
|
||||
CVOptionFlags,
|
||||
CVOptionFlags*,
|
||||
void *displayLinkContext)
|
||||
{
|
||||
auto me = reinterpret_cast<DisplayLinkVsyncSource*>(displayLinkContext);
|
||||
|
||||
SDL_assert(displayLink == me->m_DisplayLink);
|
||||
|
||||
// In my testing on macOS 10.13, this callback is invoked about 24 ms
|
||||
// prior to the specified v-sync time (now - vsyncTime). Since this is
|
||||
// greater than the standard v-sync interval (16 ms = 60 FPS), we will
|
||||
// draw using the current host time, rather than the actual v-sync target
|
||||
// time. Because the CVDisplayLink is in sync with the actual v-sync
|
||||
// interval, even if many ms prior, we can safely use the current host time
|
||||
// and get a consistent callback for each v-sync. This reduces video latency
|
||||
// by at least 1 frame vs. rendering with the actual vsyncTime.
|
||||
me->m_Pacer->vsyncCallback(500 / me->m_DisplayFps);
|
||||
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
virtual bool initialize(SDL_Window* window, int displayFps) override
|
||||
{
|
||||
SDL_SysWMinfo info;
|
||||
|
||||
SDL_VERSION(&info.version);
|
||||
|
||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetWindowWMInfo() failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_assert(info.subsystem == SDL_SYSWM_COCOA);
|
||||
|
||||
m_DisplayFps = displayFps;
|
||||
|
||||
NSScreen* screen = [info.info.cocoa.window screen];
|
||||
CVReturn status;
|
||||
if (screen == nullptr) {
|
||||
// Window not visible on any display, so use a
|
||||
// CVDisplayLink that can work with all active displays.
|
||||
// When we become visible, we'll recreate ourselves
|
||||
// and associate with the new screen.
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"NSWindow is not visible on any display");
|
||||
status = CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
|
||||
}
|
||||
else {
|
||||
CGDirectDisplayID displayId;
|
||||
displayId = getDisplayID(screen);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"NSWindow on display: %x",
|
||||
displayId);
|
||||
status = CVDisplayLinkCreateWithCGDisplay(displayId, &m_DisplayLink);
|
||||
}
|
||||
if (status != kCVReturnSuccess) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create CVDisplayLink: %d",
|
||||
status);
|
||||
return false;
|
||||
}
|
||||
|
||||
status = CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
|
||||
if (status != kCVReturnSuccess) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CVDisplayLinkSetOutputCallback() failed: %d",
|
||||
status);
|
||||
return false;
|
||||
}
|
||||
|
||||
status = CVDisplayLinkStart(m_DisplayLink);
|
||||
if (status != kCVReturnSuccess) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CVDisplayLinkStart() failed: %d",
|
||||
status);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Pacer* m_Pacer;
|
||||
CVDisplayLinkRef m_DisplayLink;
|
||||
int m_DisplayFps;
|
||||
};
|
||||
|
||||
IVsyncSource* DisplayLinkVsyncSourceFactory::createVsyncSource(Pacer* pacer) {
|
||||
return new DisplayLinkVsyncSource(pacer);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "pacer.h"
|
||||
#include "streaming/streamutils.h"
|
||||
|
||||
#include "nullthreadedvsyncsource.h"
|
||||
|
||||
@@ -120,22 +121,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enableVsync)
|
||||
{
|
||||
m_MaxVideoFps = maxVideoFps;
|
||||
m_EnableVsync = enableVsync;
|
||||
|
||||
int displayIndex = SDL_GetWindowDisplayIndex(window);
|
||||
if (displayIndex < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to get current display: %s",
|
||||
SDL_GetError());
|
||||
|
||||
// Assume display 0 if it fails
|
||||
displayIndex = 0;
|
||||
}
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
if (SDL_GetCurrentDisplayMode(displayIndex, &mode) == 0) {
|
||||
// May be zero if undefined
|
||||
m_DisplayFps = mode.refresh_rate;
|
||||
}
|
||||
m_DisplayFps = StreamUtils::getDisplayRefreshRate(window);
|
||||
|
||||
if (m_EnableVsync) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -143,7 +129,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enableVsync)
|
||||
m_DisplayFps, m_MaxVideoFps);
|
||||
|
||||
#if defined(Q_OS_DARWIN)
|
||||
m_VsyncSource = new DisplayLinkVsyncSource(this);
|
||||
m_VsyncSource = DisplayLinkVsyncSourceFactory::createVsyncSource(this);
|
||||
#elif defined(Q_OS_WIN32)
|
||||
m_VsyncSource = new DxVsyncSource(this);
|
||||
#else
|
||||
|
||||
@@ -8,6 +8,12 @@ extern "C" {
|
||||
|
||||
class IFFmpegRenderer {
|
||||
public:
|
||||
enum VSyncConstraint {
|
||||
VSYNC_FORCE_OFF,
|
||||
VSYNC_FORCE_ON,
|
||||
VSYNC_ANY
|
||||
};
|
||||
|
||||
virtual ~IFFmpegRenderer() {}
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
@@ -18,6 +24,8 @@ public:
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context) = 0;
|
||||
virtual void renderFrameAtVsync(AVFrame* frame) = 0;
|
||||
virtual bool needsTestFrame() = 0;
|
||||
virtual int getDecoderCapabilities() = 0;
|
||||
virtual VSyncConstraint getVsyncConstraint() = 0;
|
||||
};
|
||||
|
||||
class SdlRenderer : public IFFmpegRenderer {
|
||||
@@ -33,6 +41,8 @@ public:
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual VSyncConstraint getVsyncConstraint();
|
||||
|
||||
private:
|
||||
SDL_Renderer* m_Renderer;
|
||||
|
||||
+12
@@ -36,6 +36,18 @@ bool SdlRenderer::needsTestFrame()
|
||||
return false;
|
||||
}
|
||||
|
||||
int SdlRenderer::getDecoderCapabilities()
|
||||
{
|
||||
// The FFmpeg CPU decoder can handle reference frame invalidation,
|
||||
// but only for H.264.
|
||||
return CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC;
|
||||
}
|
||||
|
||||
IFFmpegRenderer::VSyncConstraint SdlRenderer::getVsyncConstraint()
|
||||
{
|
||||
return VSYNC_ANY;
|
||||
}
|
||||
|
||||
bool SdlRenderer::initialize(SDL_Window* window,
|
||||
int,
|
||||
int width,
|
||||
@@ -149,6 +149,17 @@ VAAPIRenderer::needsTestFrame()
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
VAAPIRenderer::getDecoderCapabilities()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
IFFmpegRenderer::VSyncConstraint VAAPIRenderer::getVsyncConstraint()
|
||||
{
|
||||
return VSYNC_ANY;
|
||||
}
|
||||
|
||||
void
|
||||
VAAPIRenderer::renderFrameAtVsync(AVFrame* frame)
|
||||
{
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual VSyncConstraint getVsyncConstraint();
|
||||
|
||||
private:
|
||||
int m_WindowSystem;
|
||||
|
||||
@@ -217,6 +217,9 @@ bool VDPAURenderer::prepareDecoderContext(AVCodecContext* context)
|
||||
// https://github.com/FFmpeg/FFmpeg/commit/64ecb78b7179cab2dbdf835463104679dbb7c895
|
||||
context->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH;
|
||||
|
||||
// This flag is recommended due to hardware underreporting supported levels
|
||||
context->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using VDPAU accelerated renderer");
|
||||
|
||||
@@ -230,6 +233,16 @@ bool VDPAURenderer::needsTestFrame()
|
||||
return true;
|
||||
}
|
||||
|
||||
int VDPAURenderer::getDecoderCapabilities()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
IFFmpegRenderer::VSyncConstraint VDPAURenderer::getVsyncConstraint()
|
||||
{
|
||||
return VSYNC_ANY;
|
||||
}
|
||||
|
||||
void VDPAURenderer::renderFrameAtVsync(AVFrame* frame)
|
||||
{
|
||||
VdpStatus status;
|
||||
|
||||
@@ -22,6 +22,8 @@ public:
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual VSyncConstraint getVsyncConstraint();
|
||||
|
||||
private:
|
||||
uint32_t m_VideoWidth, m_VideoHeight;
|
||||
|
||||
@@ -196,6 +196,19 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual int getDecoderCapabilities() override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual IFFmpegRenderer::VSyncConstraint getVsyncConstraint() override
|
||||
{
|
||||
// This renderer is inherently tied to V-sync due how we're
|
||||
// rendering with AVSampleBufferDisplay layer. Running without
|
||||
// the V-Sync source leads to massive stuttering.
|
||||
return VSYNC_FORCE_ON;
|
||||
}
|
||||
|
||||
private:
|
||||
void setupDisplayLayer()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Limelight.h>
|
||||
#include "ffmpeg.h"
|
||||
#include "streaming/streamutils.h"
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#include "ffmpeg-renderers/dxva2.h"
|
||||
@@ -27,6 +28,11 @@ bool FFmpegVideoDecoder::isHardwareAccelerated()
|
||||
return m_HwDecodeCfg != nullptr;
|
||||
}
|
||||
|
||||
int FFmpegVideoDecoder::getDecoderCapabilities()
|
||||
{
|
||||
return m_Renderer->getDecoderCapabilities();
|
||||
}
|
||||
|
||||
enum AVPixelFormat FFmpegVideoDecoder::ffGetFormat(AVCodecContext* context,
|
||||
const enum AVPixelFormat* pixFmts)
|
||||
{
|
||||
@@ -100,16 +106,37 @@ void FFmpegVideoDecoder::reset()
|
||||
delete m_Pacer;
|
||||
m_Pacer = nullptr;
|
||||
|
||||
// This must be called after deleting Pacer because it
|
||||
// may be holding AVFrames to free in its destructor.
|
||||
// However, it must be called before deleting the IFFmpegRenderer
|
||||
// since the codec context may be referencing objects that we
|
||||
// need to delete in the renderer destructor.
|
||||
avcodec_free_context(&m_VideoDecoderCtx);
|
||||
|
||||
delete m_Renderer;
|
||||
m_Renderer = nullptr;
|
||||
|
||||
avcodec_free_context(&m_VideoDecoderCtx);
|
||||
}
|
||||
|
||||
bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* window,
|
||||
int videoFormat, int width, int height,
|
||||
int maxFps, bool enableVsync, bool testOnly)
|
||||
{
|
||||
auto vsyncConstraint = m_Renderer->getVsyncConstraint();
|
||||
if (vsyncConstraint == IFFmpegRenderer::VSYNC_FORCE_OFF && enableVsync) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-sync is forcefully disabled by the active renderer");
|
||||
enableVsync = false;
|
||||
}
|
||||
else if (vsyncConstraint == IFFmpegRenderer::VSYNC_FORCE_ON && !enableVsync) {
|
||||
// FIXME: This duplicates logic in Session.cpp
|
||||
int displayHz = StreamUtils::getDisplayRefreshRate(window);
|
||||
if (displayHz + 5 >= maxFps) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-sync is forcefully enabled by the active renderer");
|
||||
enableVsync = true;
|
||||
}
|
||||
}
|
||||
|
||||
m_Pacer = new Pacer(m_Renderer);
|
||||
if (!m_Pacer->initialize(window, maxFps, enableVsync)) {
|
||||
return false;
|
||||
@@ -125,6 +152,10 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* wi
|
||||
// Always request low delay decoding
|
||||
m_VideoDecoderCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||
|
||||
// Allow display of corrupt frames and frames missing references
|
||||
m_VideoDecoderCtx->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT;
|
||||
m_VideoDecoderCtx->flags2 |= AV_CODEC_FLAG2_SHOW_ALL;
|
||||
|
||||
// Enable slice multi-threading for software decoding
|
||||
if (!m_HwDecodeCfg) {
|
||||
m_VideoDecoderCtx->thread_type = FF_THREAD_SLICE;
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
int maxFps,
|
||||
bool enableVsync) override;
|
||||
virtual bool isHardwareAccelerated() override;
|
||||
virtual int getDecoderCapabilities() override;
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du) override;
|
||||
virtual void renderFrame(SDL_UserEvent* event) override;
|
||||
virtual void dropFrame(SDL_UserEvent* event) override;
|
||||
|
||||
@@ -25,6 +25,12 @@ SLVideoDecoder::isHardwareAccelerated()
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
SLVideoDecoder::getDecoderCapabilities()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
SLVideoDecoder::initialize(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window*,
|
||||
|
||||
@@ -17,6 +17,7 @@ public:
|
||||
int frameRate,
|
||||
bool enableVsync);
|
||||
virtual bool isHardwareAccelerated();
|
||||
virtual int getDecoderCapabilities();
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du);
|
||||
|
||||
// Unused since rendering is done directly from the decode thread
|
||||
|
||||
Submodule moonlight-common-c/moonlight-common-c updated: feb46b978e...718d6a4b28
@@ -12,6 +12,9 @@ TEMPLATE = lib
|
||||
# Support debug and release builds from command line for CI
|
||||
CONFIG += debug_and_release
|
||||
|
||||
# Ensure symbols are always generated
|
||||
CONFIG += force_debug_info
|
||||
|
||||
win32 {
|
||||
INCLUDEPATH += $$PWD/../libs/windows/include
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ TEMPLATE = lib
|
||||
# Support debug and release builds from command line for CI
|
||||
CONFIG += debug_and_release
|
||||
|
||||
# Ensure symbols are always generated
|
||||
CONFIG += force_debug_info
|
||||
|
||||
QMDNSE_DIR = $$PWD/qmdnsengine/src
|
||||
DEFINES += \
|
||||
QT_NO_SIGNALS_SLOTS_KEYWORDS
|
||||
|
||||
@@ -33,6 +33,12 @@ pushd $BUILD_FOLDER
|
||||
make $(echo "$BUILD_CONFIG" | tr '[:upper:]' '[:lower:]') || fail "Make failed!"
|
||||
popd
|
||||
|
||||
echo Saving dSYM file
|
||||
pushd $BUILD_FOLDER
|
||||
dsymutil app/Moonlight.app/Contents/MacOS/Moonlight -o Moonlight.dsym || fail "dSYM creation failed!"
|
||||
cp -R Moonlight.dsym $INSTALLER_FOLDER || fail "dSYM copy failed!"
|
||||
popd
|
||||
|
||||
echo Copying dylib dependencies
|
||||
mkdir $BUILD_FOLDER/app/Moonlight.app/Contents/lib
|
||||
cp $SOURCE_ROOT/libs/mac/lib/*.dylib $BUILD_FOLDER/app/Moonlight.app/Contents/lib/ || fail "Dylib copy failed!"
|
||||
|
||||
@@ -16,6 +16,7 @@ if /I "%BUILD_CONFIG%"=="debug" (
|
||||
if /I "%BUILD_CONFIG%"=="signed-release" (
|
||||
set BUILD_CONFIG=release
|
||||
set SIGN=1
|
||||
set MUST_DEPLOY_SYMBOLS=1
|
||||
) else (
|
||||
echo Invalid build configuration
|
||||
exit /b 1
|
||||
@@ -31,7 +32,6 @@ if /I "%ARCH%" NEQ "x86" (
|
||||
)
|
||||
|
||||
set VS_PATH=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community
|
||||
set VCREDIST_PATH=%VS_PATH%\VC\Redist\MSVC\14.14.26405
|
||||
set SIGNTOOL_PARAMS=sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sha1 1B3C676E831A94EC0327C3347EB32C68C26B3A67 /v
|
||||
|
||||
call "%VS_PATH%\VC\Auxiliary\Build\vcvarsall.bat" %ARCH%
|
||||
@@ -42,15 +42,18 @@ set SOURCE_ROOT=%cd%
|
||||
set BUILD_FOLDER=%BUILD_ROOT%\build-%ARCH%-%BUILD_CONFIG%
|
||||
set DEPLOY_FOLDER=%BUILD_ROOT%\deploy-%ARCH%-%BUILD_CONFIG%
|
||||
set INSTALLER_FOLDER=%BUILD_ROOT%\installer-%ARCH%-%BUILD_CONFIG%
|
||||
set SYMBOLS_FOLDER=%BUILD_ROOT%\symbols-%ARCH%-%BUILD_CONFIG%
|
||||
|
||||
echo Cleaning output directories
|
||||
rmdir /s /q %DEPLOY_FOLDER%
|
||||
rmdir /s /q %BUILD_FOLDER%
|
||||
rmdir /s /q %INSTALLER_FOLDER%
|
||||
rmdir /s /q %SYMBOLS_FOLDER%
|
||||
mkdir %BUILD_ROOT%
|
||||
mkdir %DEPLOY_FOLDER%
|
||||
mkdir %BUILD_FOLDER%
|
||||
mkdir %INSTALLER_FOLDER%
|
||||
mkdir %SYMBOLS_FOLDER%
|
||||
|
||||
echo Configuring the project
|
||||
pushd %BUILD_FOLDER%
|
||||
@@ -64,10 +67,35 @@ nmake %BUILD_CONFIG%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
popd
|
||||
|
||||
echo Saving PDBs
|
||||
for /r "%BUILD_FOLDER%" %%f in (*.pdb) do (
|
||||
copy "%%f" %SYMBOLS_FOLDER%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
)
|
||||
copy %SOURCE_ROOT%\libs\windows\lib\%ARCH%\*.pdb %SYMBOLS_FOLDER%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
7z a %SYMBOLS_FOLDER%\MoonlightDebuggingSymbols.zip %SYMBOLS_FOLDER%\*.pdb
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
if "%ML_SYMBOL_STORE%" NEQ "" (
|
||||
echo Publishing PDBs to symbol store: %ML_SYMBOL_STORE%
|
||||
symstore add /f %SYMBOLS_FOLDER%\*.pdb /s %ML_SYMBOL_STORE% /t Moonlight
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
) else (
|
||||
if "%MUST_DEPLOY_SYMBOLS%"=="1" (
|
||||
echo "A symbol server must be specified in ML_SYMBOL_STORE for signed release builds"
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
echo Copying DLL dependencies
|
||||
copy %SOURCE_ROOT%\libs\windows\lib\%ARCH%\*.dll %DEPLOY_FOLDER%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
echo Copying GC mapping list
|
||||
copy %SOURCE_ROOT%\app\gamecontrollerdb.txt %DEPLOY_FOLDER%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
echo Deploying Qt dependencies
|
||||
windeployqt.exe --dir %DEPLOY_FOLDER% --%BUILD_CONFIG% --qmldir %SOURCE_ROOT%\app\gui --no-opengl-sw --no-compiler-runtime %BUILD_FOLDER%\app\%BUILD_CONFIG%\Moonlight.exe
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
@@ -99,7 +127,7 @@ if "%SIGN%"=="1" (
|
||||
)
|
||||
|
||||
echo Building bundle
|
||||
set VCREDIST_INSTALLER=%VCREDIST_PATH%\vcredist_%ARCH%.exe
|
||||
set VCREDIST_INSTALLER=%VCToolsRedistDir%vcredist_%ARCH%.exe
|
||||
rem Bundles are always x86 binaries
|
||||
msbuild %SOURCE_ROOT%\wix\MoonlightSetup\MoonlightSetup.wixproj /p:Configuration=%BUILD_CONFIG% /p:Platform=x86
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
@@ -119,7 +147,7 @@ if "%SIGN%"=="1" (
|
||||
echo Building portable package
|
||||
rem This must be done after WiX harvesting and signing, since the VCRT dlls are MS signed
|
||||
rem and should not be harvested for inclusion in the full installer
|
||||
copy "%VCREDIST_PATH%\%ARCH%\Microsoft.VC141.CRT\*.dll" %DEPLOY_FOLDER%
|
||||
copy "%VCToolsRedistDir%%ARCH%\Microsoft.VC141.CRT\*.dll" %DEPLOY_FOLDER%
|
||||
rem This file tells Moonlight that it's a portable installation
|
||||
echo. > %DEPLOY_FOLDER%\portable.dat
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
@@ -132,6 +160,7 @@ if /i "%APPVEYOR%"=="true" (
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
appveyor PushArtifact %INSTALLER_FOLDER%\MoonlightPortable.zip -FileName MoonlightPortable-%ARCH%-%BUILD_CONFIG%.zip
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
appveyor PushArtifact %SYMBOLS_FOLDER%\MoonlightDebuggingSymbols.zip -FileName MoonlightDebuggingSymbols-%ARCH%-%BUILD_CONFIG%.zip
|
||||
)
|
||||
|
||||
echo Build successful!
|
||||
@@ -139,4 +168,4 @@ exit /b 0
|
||||
|
||||
:Error
|
||||
echo Build failed!
|
||||
exit /b !ERRORLEVEL!
|
||||
exit /b !ERRORLEVEL!
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<?define FullName = "Moonlight Game Streaming Client" ?>
|
||||
|
||||
<?define ShortcutName = "$(var.ShortName)" ?>
|
||||
<?define ShortcutDesc = "Stream games from a NVIDIA GameStream-compatible PC" ?>
|
||||
<?define ShortcutDesc = "Stream games from your NVIDIA GameStream-enabled PC" ?>
|
||||
<?define InstallFolder = "Moonlight Game Streaming" ?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
|
||||
@@ -39,7 +39,7 @@
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Property Id="APPDATAFOLDER">%LOCALAPPDATA%\Moonlight Game Streaming</Property>
|
||||
<Property Id="APPDATAFOLDER">%LOCALAPPDATA%\Moonlight Game Streaming Project</Property>
|
||||
|
||||
<!-- There's no way to delete a registry key on uninstall but not major upgrade, so
|
||||
we have to roll our own deletion via custom action -->
|
||||
@@ -60,16 +60,38 @@
|
||||
Target="[#MoonlightExe]"
|
||||
Directory="ApplicationProgramsFolder"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<RemoveFolder Id="CleanupStartMenuShortcut" Directory="ApplicationProgramsFolder" On="uninstall" />
|
||||
<util:RemoveFolderEx Id="CleanupAppDataFolder" On="uninstall" Property="APPDATAFOLDER" />
|
||||
<RegistryValue Root="HKCU" Key="Software\Moonlight Game Streaming Project" Name="Installed" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<Component Id="MoonlightDesktopShortcut" Guid="*" Directory="INSTALLFOLDER">
|
||||
<Shortcut Id="DesktopShortcut"
|
||||
Name="$(var.ShortcutName)"
|
||||
Description="$(var.ShortcutDesc)"
|
||||
Target="[#MoonlightExe]"
|
||||
Directory="DesktopFolder"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<RemoveFolder Id="CleanupStartMenuShortcut" Directory="ApplicationProgramsFolder" On="uninstall" />
|
||||
<RemoveFolder Id="CleanupDesktopShortcut" Directory="DesktopFolder" On="uninstall" />
|
||||
<util:RemoveFolderEx Id="CleanupAppDataFolder" On="uninstall" Property="APPDATAFOLDER" />
|
||||
<RegistryValue Root="HKCU" Key="Software\Moonlight Game Streaming Project" Name="Installed" Type="integer" Value="1" KeyPath="yes" />
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\Moonlight Game Streaming Project"
|
||||
Name="DesktopShortcutInstalled"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes" />
|
||||
<Condition>ADDDESKTOPSHORTCUT=1</Condition>
|
||||
</Component>
|
||||
|
||||
<!-- Persist desktop shortcut's installed state to let Bundle.wxs know if
|
||||
the desktop shortcut should installed by default when upgrading the
|
||||
product -->
|
||||
<Component Id="MoonlightDesktopShortcutState" Guid="*" Directory="INSTALLFOLDER">
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\Moonlight Game Streaming Project"
|
||||
Name="DesktopShortcutInstallState"
|
||||
Type="integer"
|
||||
Value="[ADDDESKTOPSHORTCUT]"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
@@ -85,6 +107,8 @@
|
||||
<Feature Id="ProductFeature" Title="Moonlight" Level="1" ConfigurableDirectory="INSTALLFOLDER">
|
||||
<ComponentRef Id="Moonlight" />
|
||||
<ComponentRef Id="MoonlightShortcuts" />
|
||||
<ComponentRef Id="MoonlightDesktopShortcutState" />
|
||||
<ComponentRef Id="MoonlightDesktopShortcut" />
|
||||
<ComponentGroupRef Id="MoonlightDependencies" />
|
||||
</Feature>
|
||||
</Product>
|
||||
|
||||
@@ -51,13 +51,32 @@
|
||||
<Variable Name="InstallFolder" Type="string" Value="[ProgramFilesFolder]Moonlight Game Streaming" />
|
||||
<?endif ?>
|
||||
|
||||
<!-- Define "Add desktop shortcut" -checkbox's state by defining a variable
|
||||
which has same name as the checkbox has. Value 1 means that checkbox
|
||||
is checked, 0 means that is unchecked-->
|
||||
<!-- Set checkbox's state as checked by default -->
|
||||
<Variable Name="AddDesktopShortcutCheckbox" Type="numeric" Value="1" />
|
||||
<!-- Get checkbox's state from registry if present. The registry value
|
||||
"DesktopShortcutInstallState" is set in Product.wxs. -->
|
||||
<util:RegistrySearch Variable="HasDesktopShortcutInstallStateRegKey"
|
||||
Root="HKCU"
|
||||
Key="Software\Moonlight Game Streaming Project"
|
||||
Value="DesktopShortcutInstallState"
|
||||
Result="exists" />
|
||||
<util:RegistrySearch Variable="AddDesktopShortcutCheckbox"
|
||||
Root="HKCU"
|
||||
Key="Software\Moonlight Game Streaming Project"
|
||||
Value="DesktopShortcutInstallState"
|
||||
Condition="HasDesktopShortcutInstallStateRegKey" />
|
||||
|
||||
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
|
||||
<bal:WixStandardBootstrapperApplication
|
||||
ShowVersion="yes"
|
||||
LicenseFile="license.rtf"
|
||||
LogoFile="..\..\app\moonlight_wix.png"
|
||||
ShowFilesInUse="yes"
|
||||
LaunchTarget="[InstallFolder]\Moonlight.exe" />
|
||||
LaunchTarget="[InstallFolder]\Moonlight.exe"
|
||||
ThemeFile="RtfTheme.xml" />
|
||||
</BootstrapperApplicationRef>
|
||||
|
||||
<Chain>
|
||||
@@ -75,6 +94,7 @@
|
||||
<MsiPackage Id="Moonlight" SourceFile="$(var.Moonlight.TargetPath)" Vital="yes">
|
||||
<MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" />
|
||||
<MsiProperty Name="REINSTALLMODE" Value="[REINSTALLMODE]" />
|
||||
<MsiProperty Name="ADDDESKTOPSHORTCUT" Value="[AddDesktopShortcutCheckbox]" />
|
||||
</MsiPackage>
|
||||
</Chain>
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
|
||||
<!-- This file has been copied from C:\Program Files (x86)\WiX Toolset v3.11\SDK\themes\RtfTheme.xml and then altered. -->
|
||||
|
||||
<Theme xmlns="http://wixtoolset.org/schemas/thmutil/2010">
|
||||
<Window Width="485" Height="300" HexStyle="100a0000" FontId="0">#(loc.Caption)</Window>
|
||||
<Font Id="0" Height="-12" Weight="500" Foreground="000000" Background="FFFFFF">Segoe UI</Font>
|
||||
<Font Id="1" Height="-24" Weight="500" Foreground="000000">Segoe UI</Font>
|
||||
<Font Id="2" Height="-22" Weight="500" Foreground="666666">Segoe UI</Font>
|
||||
<Font Id="3" Height="-12" Weight="500" Foreground="000000" Background="FFFFFF">Segoe UI</Font>
|
||||
<Font Id="4" Height="-12" Weight="500" Foreground="ff0000" Background="FFFFFF" Underline="yes">Segoe UI</Font>
|
||||
|
||||
<Image X="11" Y="11" Width="64" Height="64" ImageFile="logo.png" Visible="yes"/>
|
||||
<Text X="80" Y="11" Width="-11" Height="64" FontId="1" Visible="yes" DisablePrefix="yes">#(loc.Title)</Text>
|
||||
|
||||
<Page Name="Help">
|
||||
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.HelpHeader)</Text>
|
||||
<Text X="11" Y="112" Width="-11" Height="-35" FontId="3" DisablePrefix="yes">#(loc.HelpText)</Text>
|
||||
<Button Name="HelpCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.HelpCloseButton)</Button>
|
||||
</Page>
|
||||
<Page Name="Install">
|
||||
<Richedit Name="EulaRichedit" X="11" Y="80" Width="-11" Height="-70" TabStop="yes" FontId="0" HexStyle="0x800000" />
|
||||
<Checkbox Name="EulaAcceptCheckbox" X="-11" Y="-41" Width="260" Height="17" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.InstallAcceptCheckbox)</Checkbox>
|
||||
<Button Name="OptionsButton" X="-171" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.InstallOptionsButton)</Button>
|
||||
<Button Name="InstallButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.InstallInstallButton)</Button>
|
||||
<Button Name="WelcomeCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.InstallCloseButton)</Button>
|
||||
</Page>
|
||||
<Page Name="Options">
|
||||
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.OptionsHeader)</Text>
|
||||
<Text X="11" Y="121" Width="-11" Height="17" FontId="3" DisablePrefix="yes">#(loc.OptionsLocationLabel)</Text>
|
||||
<Editbox Name="FolderEditbox" X="11" Y="143" Width="-91" Height="21" TabStop="yes" FontId="3" FileSystemAutoComplete="yes" />
|
||||
<Button Name="BrowseButton" X="-11" Y="142" Width="75" Height="23" TabStop="yes" FontId="3">#(loc.OptionsBrowseButton)</Button>
|
||||
|
||||
<!-- Added a checkbox for user to choose if shortcut is placed on
|
||||
desktop or not. Checkbox's current state (checked/unchecked) is
|
||||
read from a variable which has same name as this checkbox. The
|
||||
variable is defined in Bundle.wxs. -->
|
||||
<Checkbox Name="AddDesktopShortcutCheckbox" X="11" Y="178" Width="250" Height="17" TabStop="yes" FontId="3" HideWhenDisabled="yes">Add desktop shortcut</Checkbox>
|
||||
|
||||
<Button Name="OptionsOkButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.OptionsOkButton)</Button>
|
||||
<Button Name="OptionsCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.OptionsCancelButton)</Button>
|
||||
</Page>
|
||||
<Page Name="FilesInUse">
|
||||
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.FilesInUseHeader)</Text>
|
||||
<Text X="11" Y="121" Width="-11" Height="34" FontId="3" DisablePrefix="yes">#(loc.FilesInUseLabel)</Text>
|
||||
<Text Name="FilesInUseText" X="11" Y="150" Width="-11" Height="-86" FontId="3" DisablePrefix="yes" HexStyle="0x0000C000"></Text>
|
||||
|
||||
<Button Name="FilesInUseCloseRadioButton" X="11" Y="-60" Width="-11" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes" HexStyle="0x000009">#(loc.FilesInUseCloseRadioButton)</Button>
|
||||
<Button Name="FilesInUseDontCloseRadioButton" X="11" Y="-40" Width="-11" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes" HexStyle="0x000009">#(loc.FilesInUseDontCloseRadioButton)</Button>
|
||||
|
||||
<Button Name="FilesInUseOkButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.FilesInUseOkButton)</Button>
|
||||
<Button Name="FilesInUseCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.FilesInUseCancelButton)</Button>
|
||||
</Page>
|
||||
<Page Name="Progress">
|
||||
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.ProgressHeader)</Text>
|
||||
<Text X="11" Y="121" Width="70" Height="17" FontId="3" DisablePrefix="yes">#(loc.ProgressLabel)</Text>
|
||||
<Text Name="OverallProgressPackageText" X="85" Y="121" Width="-11" Height="17" FontId="3" DisablePrefix="yes">#(loc.OverallProgressPackageText)</Text>
|
||||
<Progressbar Name="OverallCalculatedProgressbar" X="11" Y="143" Width="-11" Height="15" />
|
||||
<Button Name="ProgressCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.ProgressCancelButton)</Button>
|
||||
</Page>
|
||||
<Page Name="Modify">
|
||||
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.ModifyHeader)</Text>
|
||||
<Button Name="RepairButton" X="-171" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.ModifyRepairButton)</Button>
|
||||
<Button Name="UninstallButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.ModifyUninstallButton)</Button>
|
||||
<Button Name="ModifyCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.ModifyCloseButton)</Button>
|
||||
</Page>
|
||||
<Page Name="Success">
|
||||
<Text Name="SuccessHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.SuccessHeader)</Text>
|
||||
<Text Name="SuccessInstallHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.SuccessInstallHeader)</Text>
|
||||
<Text Name="SuccessRepairHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.SuccessRepairHeader)</Text>
|
||||
<Text Name="SuccessUninstallHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.SuccessUninstallHeader)</Text>
|
||||
<Button Name="LaunchButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.SuccessLaunchButton)</Button>
|
||||
<Text Name="SuccessRestartText" X="-11" Y="-51" Width="400" Height="34" FontId="3" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.SuccessRestartText)</Text>
|
||||
<Button Name="SuccessRestartButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.SuccessRestartButton)</Button>
|
||||
<Button Name="SuccessCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.SuccessCloseButton)</Button>
|
||||
</Page>
|
||||
<Page Name="Failure">
|
||||
<Text Name="FailureHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureHeader)</Text>
|
||||
<Text Name="FailureInstallHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureInstallHeader)</Text>
|
||||
<Text Name="FailureUninstallHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureUninstallHeader)</Text>
|
||||
<Text Name="FailureRepairHeader" X="11" Y="80" Width="-11" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureRepairHeader)</Text>
|
||||
<Hypertext Name="FailureLogFileLink" X="11" Y="121" Width="-11" Height="42" FontId="3" TabStop="yes" HideWhenDisabled="yes">#(loc.FailureHyperlinkLogText)</Hypertext>
|
||||
<Hypertext Name="FailureMessageText" X="22" Y="163" Width="-11" Height="51" FontId="3" TabStop="yes" HideWhenDisabled="yes" />
|
||||
<Text Name="FailureRestartText" X="-11" Y="-51" Width="400" Height="34" FontId="3" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureRestartText)</Text>
|
||||
<Button Name="FailureRestartButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.FailureRestartButton)</Button>
|
||||
<Button Name="FailureCloseButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.FailureCloseButton)</Button>
|
||||
</Page>
|
||||
</Theme>
|
||||
Reference in New Issue
Block a user