Compare commits

...
96 Commits
Author SHA1 Message Date
Cameron Gutman e21fd19a70 Prepare for v3.1.0 release 2021-03-14 11:30:21 -05:00
Cameron Gutman 98672c8e44 Allow remote desktop mouse mode in embedded mode
It can be easier to control the mouse on the desktop that way
2021-03-13 16:58:58 -06:00
Cameron Gutman 2e92f0640b Update SDL_GameControllerDB 2021-03-13 15:50:01 -06:00
Cameron Gutman d6b314f573 Add option to build for embedded environments 2021-03-13 15:20:58 -06:00
Cameron Gutman d7a150ffa8 Only enable capture mode list if capture toggle is enabled 2021-03-13 14:59:32 -06:00
Cameron Gutman 955da6be30 Enable Gallium VAAPI driver on VAAPI 1.x 2021-03-13 14:37:13 -06:00
Cameron Gutman b5f8e6738a Move LANG_DE to the end of the Language enum 2021-03-09 20:13:55 -06:00
reloxx13 d7fa8c9795 Add german translation (#536)
* add german translation

* add german to language list

* donttranslate language names. always use the native name.

* add compiled german translation file

* german translation handling

* add german to getSuffixFromLanguage

* update german string and translation

* update all language files with new strings

* typo

* compile

* more typos and fixes for german
2021-03-09 20:10:36 -06:00
Cameron Gutman acb7a5b3c8 Fix XCB out-of-sequence abort after switching to non-threaded Qt renderer 2021-03-07 16:48:10 -06:00
Cameron Gutman 783a57ef89 Use separate thread for streaming on X11 and Wayland 2021-03-07 12:15:42 -06:00
Cameron Gutman d33ccce683 Hold a reference to the underlying DMA-BUFs until the next frame 2021-03-07 12:01:25 -06:00
Cameron Gutman d722bf10b9 Don't use glFinish() with KMSDRM backend 2021-03-07 11:23:31 -06:00
Cameron Gutman 41337475a2 Update SDL_GameControllerDB 2021-03-06 14:29:55 -06:00
Cameron Gutman f504a069d7 Update VCRedist version for VC 16.9 2021-03-05 17:51:26 -06:00
Cameron Gutman 96f79688dd Use av_packet_alloc() instead of av_init_packet()
sizeof(AVPacket) may be deprecated from ABI in the near future
2021-03-05 17:47:04 -06:00
Cameron Gutman c330898aeb Add workaround for AppView breaking after changing language at runtime 2021-03-05 17:44:31 -06:00
Cameron Gutman 94952cf0a4 Update issue template 2021-03-03 01:39:55 -06:00
Cameron Gutman a3c249abe9 Don't load the app label asynchronously 2021-03-02 19:03:09 -06:00
Cameron Gutman c26562fab8 Fix Steam Link build 2021-03-02 18:57:16 -06:00
Cameron Gutman b7cbd99212 Fix tooltip display in app grid 2021-03-02 18:35:50 -06:00
Cameron Gutman b53ef35a85 Improve loading speed of PC and app grids 2021-03-02 18:32:56 -06:00
Cameron Gutman 1ebb5fefb1 Address Clazy warnings 2021-03-02 18:14:15 -06:00
Cameron Gutman 164b3edd41 Fix viewport size on High DPI Wayland systems
Fixes #533
2021-03-01 22:47:14 -06:00
Cameron Gutman 819ea36cca Hack to fix build on AppVeyor 2021-02-28 16:47:39 -06:00
Cameron Gutman 088c9b9e4e Stop single-arch binary build before extracting dSYM 2021-02-28 16:02:50 -06:00
Cameron Gutman ab25a9b8ee Add Universal binary support to build script 2021-02-28 15:49:59 -06:00
Cameron Gutman b2aea9f8f0 Revert change to only fire special combos on key up 2021-02-28 15:06:46 -06:00
Cameron Gutman 5399b95936 Fix update checker on macOS with Qt 6.0 builds 2021-02-28 14:33:20 -06:00
Cameron Gutman 1d5c120748 Add support for specifying minimum kernel versions in the update manifest 2021-02-28 14:27:29 -06:00
jmt-gh 6be7301e69 Stats overlay controller combo (#495)
* Update gamepad.cpp

add gamepad combo to toggle stats overlay

* typo fixes
2021-02-27 17:03:52 -06:00
Maximiliano Bertacchini fff9f2b17d Add numpad Enter handlers (Keys.onEnterPressed) (#516) 2021-02-27 17:01:22 -06:00
Cameron Gutman 87a7d2e45c Add setting to only capture system keys in full-screen 2021-02-27 16:47:38 -06:00
Cameron Gutman 58803ef40d Update SDL to e561ce3 and OpenSSL to 1.1.1j 2021-02-27 13:08:21 -06:00
Cameron Gutman b27ca993aa Add support for system key capture in windowed mode on macOS 2021-02-26 21:43:47 -06:00
Cameron Gutman 28ecc6bcbf Remove old Qt Multimedia audio renderer 2021-02-25 23:23:08 -06:00
Cameron Gutman 30c5e3f237 Fix window icons on Wayland 2021-02-25 23:20:21 -06:00
Cameron Gutman 6a25a3a3d0 Don't require DRM master for SDL KMSDRM backend 2021-02-25 22:27:20 -06:00
Cameron Gutman 9a84c0ae30 Update AppImage to official SDL Git repo 2021-02-25 22:00:35 -06:00
Cameron Gutman 133cc5457d Use SDL_CaptureMouse() on macOS and Linux too 2021-02-08 18:30:22 -06:00
Cameron Gutman 05e4c5b507 Move clipboard processing to a separate thread to avoid blocking the main thread during pastes 2021-02-07 17:18:35 -06:00
Cameron Gutman 8372717a09 Improve Unicode support for some common characters 2021-02-07 16:00:00 -06:00
Cameron Gutman 5f3f2a96fd Improve detection of having no clipboard text 2021-02-07 13:53:03 -06:00
Cameron Gutman b83c6f0c28 Add support for pasting clipboard text 2021-02-07 13:46:38 -06:00
Cameron Gutman 36dc0f3e3c Use mouse capture for Win32 instead of our global mouse state hack 2021-02-07 12:38:57 -06:00
Cameron Gutman b4edde6f90 Disable Qt 6 threaded renderer on Windows
They switched from non-threaded to threaded by default in Qt 6
2021-02-07 11:21:47 -06:00
Cameron Gutman 395eb5581c Cache results of WMUtils::isRunningX11() and WMUtils::isRunningWayland() 2021-02-07 11:03:53 -06:00
Cameron Gutman 6dab251055 Don't overwrite window mode settings when switching between WM and non-WM environments 2021-02-07 10:49:29 -06:00
Cameron Gutman 3bd6a6a614 Fullscreen is no longer required for passing system keyboard shortcuts 2021-02-06 20:15:58 -06:00
Cameron Gutman 93d21a0d1d Fix EGLImage leak when not using a hwaccel backend 2021-02-06 17:59:05 -06:00
Cameron Gutman 2c2a73e320 Fix build and require SDL 2.0.15 to hook 2021-02-06 17:07:21 -06:00
Cameron Gutman a88a3c9ea3 Allow SDL to render with KMSDRM backend while Qt is also using DRM 2021-02-06 16:32:54 -06:00
Cameron Gutman e4df70cd56 Use the async connect thread on all platforms
It turned out that the cause of the random crashes was the threaded renderer
that we now turn off.
2021-02-06 15:14:15 -06:00
Cameron Gutman 90f7d35150 Always use a non-threaded renderer
This ensures we can block Qt's rendering as needed to synchronize with SDL
2021-02-06 14:58:30 -06:00
Cameron Gutman 75b19a60c1 Fix build on macOS and keyboard grab bugs 2021-02-06 11:29:18 -06:00
Cameron Gutman 9fc921f49f Use SDL's keyboard grab support now that it's been upstreamed 2021-02-05 00:13:20 -06:00
Cameron Gutman 4b9e90ac72 Update SDL for Windows and macOS to Hg 60b9ce4ee73e
https://github.com/cgutman/moonlight-deps/commit/ad955430988be24fd379a6152642c7ce4053bc7f
2021-02-04 20:51:33 -06:00
Cameron Gutman 36dd6c83e0 Fix assert failure when using v4l2m2m with EGL 2021-02-04 20:38:18 -06:00
Cameron Gutman d790313221 Use the existing install of Qt on macOS in CI 2021-02-04 19:39:58 -06:00
Cameron Gutman c3895f06c0 Fix handling of preferred/compatible pixel formats with EGLRenderer and VAAPI/DRM backends 2021-02-04 19:39:18 -06:00
Cameron Gutman 76e81fa651 Add windowed support for MMAL 2021-02-03 20:57:39 -06:00
Cameron Gutman d63a1b0eb0 Handle custom hwaccel decoders 2021-02-02 19:45:22 -06:00
Cameron Gutman 4dffac3c78 Ask for NV12 buffers for DRMRenderer 2021-02-02 19:40:06 -06:00
Cameron Gutman 88391b0274 Use the decoder's supported pix_fmts to select a suitable renderer 2021-02-02 19:05:27 -06:00
Cameron Gutman c3cea0238e Fix build with old EGL 1.5 headers without EGL_EGL_PROTOTYPES 2021-01-31 21:42:56 -06:00
Cameron Gutman 5bf4266973 Fix build with old Mesa EGL headers 2021-01-31 18:41:53 -06:00
Cameron Gutman ed57c54d06 Move EGL definitions into renderer.h to try to fix build on Armbian 2021-01-31 17:01:11 -06:00
Cameron Gutman 01936e000e Fix Steam Link bundle build 2021-01-31 16:27:38 -06:00
Cameron Gutman 80137848dc Add DMA-BUF export compatibility with EGL 1.4 2021-01-31 16:24:31 -06:00
Cameron Gutman 149575e0c4 Don't retranslate if the language didn't change 2021-01-31 15:28:00 -06:00
Cameron Gutman 6a66b462ce Allow DRMRenderer to be used for indirect rendering 2021-01-31 15:19:19 -06:00
Cameron Gutman 1e31f6fe77 Fix initialization of DRM backend with X running 2021-01-31 15:03:40 -06:00
Cameron Gutman f173b3ff09 Attempt to fix AppImage and Steam Link build errors 2021-01-31 14:48:42 -06:00
Cameron Gutman baffab62ce Fix uninitialized variable warning 2021-01-31 12:58:19 -06:00
Cameron Gutman d141cec2a8 Fix mangled macOS libraries after transition to submodule 2021-01-31 12:23:52 -06:00
Cameron Gutman df87ac3810 Update SDL in AppImage to Hg d75deb75464a 2021-01-31 12:13:20 -06:00
Cameron Gutman 46960ce0a4 Remove side-by-side support for Qt 5.9 on Steam Link 2021-01-31 12:08:15 -06:00
Cameron Gutman 522206b0bc Use shallow-cloned submodule for prebuilts 2021-01-31 11:50:41 -06:00
Cameron Gutman 24d60dd3ab Remove prebuilt binaries 2021-01-31 11:40:41 -06:00
Cameron Gutman dbfdc2fd14 Share DRM FD and GBM device with SDL
Depends on https://hg.libsdl.org/SDL/rev/d75deb75464a
2021-01-30 21:11:52 -06:00
Cameron Gutman a97912e297 Print the error when querying EGL_EXTENSIONS fails 2021-01-30 20:13:20 -06:00
Cameron Gutman cedba501ee Query the frontend renderer to determine fullscreen-only limitations 2021-01-30 17:57:34 -06:00
Cameron Gutman ac947d3bda Add EGLImage export support to DRMRenderer 2021-01-30 17:52:23 -06:00
Cameron Gutman 243ba371b3 Initialize a DRM hardware context for hwaccel usage 2021-01-30 16:33:58 -06:00
Cameron Gutman 3af24eafbb Build fixes 2021-01-30 12:53:57 -06:00
Cameron Gutman 69b6022afe Add language selection option in settings
Fixes #501
2021-01-30 12:14:31 -06:00
Cameron Gutman 1cd66419d4 Remember desired cursor hide state
Fixes #511
2021-01-30 11:08:01 -06:00
WLongandWLong 5309a02f6d Add simplified Chinese language (#506)
* 添加qml_zh_cn.ts

* 更新部分翻译

* 优化部分文字描述

* 优化部分文字描述

* 恢复README.md

Co-authored-by: WLong <a101084@126.com>
2021-01-30 10:10:36 -06:00
Cameron Gutman 541784382a Add support for AV_HWDEVICE_TYPE_DRM hwaccels 2021-01-30 10:00:01 -06:00
Cameron Gutman 2a7fae2c82 Revert "Add logic to try LibreELEC's out-of-tree stateless V4L2 decoders"
v4l2request is a hwaccel not a decoder

This reverts commit ed3a5448b1.
2021-01-29 20:30:01 -06:00
Cameron Gutman ed3a5448b1 Add logic to try LibreELEC's out-of-tree stateless V4L2 decoders 2021-01-29 20:19:27 -06:00
Cameron Gutman 2eb4769bbc Only trigger special key combos when all keys are up 2021-01-24 13:02:42 -06:00
Cameron Gutman 02019000df Ctrl+Alt+Shift+Down Arrow is a reserved key combo on GNOME, so switch to Ctrl+Alt+Shift+D 2021-01-21 23:05:48 -06:00
Cameron Gutman 6262f51c96 Key capture is supported on Wayland with latest SDL Hg 2021-01-21 21:07:54 -06:00
Cameron Gutman d38ff5759a Add Ctrl+Alt+Shift+Down Arrow shortcut to minimize 2021-01-21 19:39:17 -06:00
Cameron Gutman f6de2e4dd3 Revert "Disable the system keyboard capture option on Wayland"
SDL now supports keyboard grab with https://hg.libsdl.org/SDL/rev/b0e13627c656

This reverts commit bc10fd0aba.
2021-01-20 21:43:16 -06:00
Cameron Gutman 3959f447d4 Fix detection of x64 architecture on some machines 2021-01-17 12:58:08 -06:00
780 changed files with 4792 additions and 197616 deletions
+11 -2
View File
@@ -1,10 +1,12 @@
---
name: Bug report
about: Ask for help on Discord before reporting a bug
about: Follow the troubleshooting guide before reporting a bug
---
**READ ME FIRST!**
This issue tracker is not the right place to ask for troubleshooting help if you're not able to connect or stream at all. For those requests, please use the #pc-help channel on our Discord server where there are many more people who can help (or direct you back here if it looks like a Moonlight bug after all). https://moonlight-stream.org/discord
If you're here because something basic is not working (like gamepad input, video, or similar), it's probably something specific to your setup, so make sure you've gone through the Troubleshooting Guide first: https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting
If you still have trouble with basic functionality after following the guide, join our Discord server where there are many other volunteers who can help (or direct you back here if it looks like a Moonlight bug after all). https://moonlight-stream.org/discord
**Describe the bug**
A clear and concise description of what the bug is.
@@ -26,14 +28,21 @@ List the games you've tried that exhibit the issue. To see if the issue is game-
- If so, which settings have been changed?
- Does the problem still occur after reverting settings back to default?
**Gamepad-related issues (please complete if problem is gamepad-related)**
- Do you have any gamepads connected to your host PC directly?
- Does the problem still remain if you stream the desktop and use https://html5gamepad.com to test your gamepad?
- Instructions for streaming the desktop can be found here: https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide
**Client PC details (please complete the following information)**
- OS: [e.g. Windows 10 1809]
- Moonlight Version: [e.g. v0.9.0]
- GPU: [e.g. Intel HD Graphics 520]
**Server PC details (please complete the following information)**
- OS: [e.g. Windows 10 1809]
- GeForce Experience version: [e.g. 3.16.0.140]
- Nvidia GPU driver: [e.g. 417.35]
- Antivirus and firewall software: [e.g. Windows Defender and Windows Firewall]
**Moonlight Logs (please attach)**
- On Windows, `Moonlight-###.log` files can be found in `%TEMP%`. Simply type that into the File Explorer path field to navigate there.
+4
View File
@@ -13,3 +13,7 @@
[submodule "h264bitstream/h264bitstream"]
path = h264bitstream/h264bitstream
url = https://github.com/aizvorski/h264bitstream.git
[submodule "libs"]
path = libs
url = https://github.com/cgutman/moonlight-qt-prebuilts.git
shallow = true
+3 -1
View File
@@ -15,7 +15,7 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
- Supports streaming at 720p, 1080p, 1440p, 4K, and the client PC's native screen resolution
- 7.1 surround sound audio support
- Support for both pointer capture (for games) and direct mouse control (for remote desktop)
- Support for passing system-wide keyboard shortcuts like Alt+Tab to the host in fullscreen mode
- Support for passing system-wide keyboard shortcuts like Alt+Tab to the host
- Direct touchscreen mouse control
- HEVC support for better image quality at reduced bandwidth
- Gamepad support with force feedback for up to 4 players
@@ -68,6 +68,8 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
* For macOS builds, use `scripts/generate-dmg.sh`. Execute this script from the root of the repository and ensure Qt's `bin` folder is in your `$PATH`.
* For Steam Link builds, run `scripts/build-steamlink-app.sh` from the root of the repository.
* To build from the command line for development use, run `qmake moonlight-qt.pro` then `make debug` or `make release`
* To create an embedded build for a single-purpose device, use `qmake "CONFIG+=embedded" moonlight-qt.pro` and build normally.
* This build will lack windowed mode, Discord/Help links, and other features that don't make sense on an embedded device.
## Contribute
1. Fork us
+15 -1
View File
@@ -134,6 +134,7 @@ SOURCES += \
settings/mappingfetcher.cpp \
settings/streamingpreferences.cpp \
streaming/input/abstouch.cpp \
streaming/input/clipboard.cpp \
streaming/input/gamepad.cpp \
streaming/input/input.cpp \
streaming/input/keyboard.cpp \
@@ -250,6 +251,12 @@ libdrm {
DEFINES += HAVE_DRM
SOURCES += streaming/video/ffmpeg-renderers/drm.cpp
HEADERS += streaming/video/ffmpeg-renderers/drm.h
linux {
message(Master hooks enabled)
SOURCES += masterhook.c
LIBS += -ldl
}
}
config_EGL {
message(EGL renderer selected)
@@ -264,7 +271,7 @@ config_EGL {
config_SL {
message(Steam Link build configuration selected)
DEFINES += STEAM_LINK HAVE_SLVIDEO HAVE_SLAUDIO
DEFINES += EMBEDDED_BUILD STEAM_LINK HAVE_SLVIDEO HAVE_SLAUDIO
LIBS += -lSLVideo -lSLAudio
SOURCES += \
@@ -307,12 +314,19 @@ discord-rpc {
LIBS += -ldiscord-rpc
DEFINES += HAVE_DISCORD
}
embedded {
message(Embedded build)
DEFINES += EMBEDDED_BUILD
}
RESOURCES += \
resources.qrc \
qml.qrc
TRANSLATIONS += \
languages/qml_zh_cn.ts \
languages/qml_de.ts \
languages/qml_fr.ts
# Additional import path used to resolve QML modules in Qt Creator's code model
+66 -35
View File
@@ -15,8 +15,8 @@ AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
// Allow HTTP redirects
m_Nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
connect(&m_Nam, SIGNAL(finished(QNetworkReply*)),
this, SLOT(handleUpdateCheckRequestFinished(QNetworkReply*)));
connect(&m_Nam, &QNetworkAccessManager::finished,
this, &AutoUpdateChecker::handleUpdateCheckRequestFinished);
QString currentVersion(VERSION_STR);
qDebug() << "Current Moonlight version:" << currentVersion;
@@ -63,11 +63,41 @@ QString AutoUpdateChecker::getPlatform()
return QStringLiteral("steamlink");
#elif defined(APP_IMAGE)
return QStringLiteral("appimage");
#elif defined(Q_OS_DARWIN) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
// Qt 6 changed this from 'osx' to 'macos'. Use the old one
// to be consistent (and not require another entry in the manifest).
return QStringLiteral("osx");
#else
return QSysInfo::productType();
#endif
}
int AutoUpdateChecker::compareVersion(QVector<int>& version1, QVector<int>& version2) {
for (int i = 0;; i++) {
int v1Val = 0;
int v2Val = 0;
// Treat missing decimal places as 0
if (i < version1.count()) {
v1Val = version1[i];
}
if (i < version2.count()) {
v2Val = version2[i];
}
if (i >= version1.count() && i >= version2.count()) {
// Equal versions
return 0;
}
if (v1Val < v2Val) {
return -1;
}
else if (v1Val > v2Val) {
return 1;
}
}
}
void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
{
Q_ASSERT(reply->isFinished());
@@ -119,46 +149,47 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
if (updateObj["arch"] == QSysInfo::buildCpuArchitecture() &&
updateObj["platform"] == getPlatform()) {
// Check the kernel version minimum if one exists
if (updateObj.contains("kernel_version_at_least") && updateObj["kernel_version_at_least"].isString()) {
QVector<int> requiredVersionQuad;
QVector<int> actualVersionQuad;
QString requiredVersion = updateObj["kernel_version_at_least"].toString();
QString actualVersion = QSysInfo::kernelVersion();
parseStringToVersionQuad(requiredVersion, requiredVersionQuad);
parseStringToVersionQuad(actualVersion, actualVersionQuad);
if (compareVersion(actualVersionQuad, requiredVersionQuad) < 0) {
qDebug() << "Skipping manifest entry due to kernel version (" << actualVersion << "<" << requiredVersion << ")";
continue;
}
}
qDebug() << "Found update manifest match for current platform";
QString latestVersion = updateObj["version"].toString();
qDebug() << "Latest version of Moonlight for this platform is:" << latestVersion;
QVector<int> latestVersionQuad;
parseStringToVersionQuad(latestVersion, latestVersionQuad);
for (int i = 0;; i++) {
int latestVer = 0;
int currentVer = 0;
// Treat missing decimal places as 0
if (i < latestVersionQuad.count()) {
latestVer = latestVersionQuad[i];
}
if (i < m_CurrentVersionQuad.count()) {
currentVer = m_CurrentVersionQuad[i];
}
if (i >= latestVersionQuad.count() && i >= m_CurrentVersionQuad.count()) {
break;
}
if (currentVer < latestVer) {
qDebug() << "Update available";
emit onUpdateAvailable(updateObj["version"].toString(),
updateObj["browser_url"].toString());
return;
}
else if (currentVer > latestVer) {
qDebug() << "Update manifest version lower than current version";
return;
}
int res = compareVersion(m_CurrentVersionQuad, latestVersionQuad);
if (res < 0) {
// m_CurrentVersionQuad < latestVersionQuad
qDebug() << "Update available";
emit onUpdateAvailable(updateObj["version"].toString(),
updateObj["browser_url"].toString());
return;
}
else if (res > 0) {
qDebug() << "Update manifest version lower than current version";
return;
}
else {
qDebug() << "Update manifest version equal to current version";
return;
}
qDebug() << "Update manifest version equal to current version";
return;
}
}
else {
@@ -166,8 +197,8 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
}
}
qWarning() << "No entry in update manifest found for current platform: "
<< QSysInfo::buildCpuArchitecture() << getPlatform();
qWarning() << "No entry in update manifest found for current platform:"
<< QSysInfo::buildCpuArchitecture() << getPlatform() << QSysInfo::kernelVersion();
}
else {
qWarning() << "Update checking failed with error: " << reply->error();
+2
View File
@@ -20,6 +20,8 @@ private slots:
private:
void parseStringToVersionQuad(QString& string, QVector<int>& version);
int compareVersion(QVector<int>& version1, QVector<int>& version2);
QString getPlatform();
QVector<int> m_CurrentVersionQuad;
+2 -2
View File
@@ -46,8 +46,8 @@ public:
m_Computer(computer),
m_App(app)
{
connect(this, SIGNAL(boxArtFetchCompleted(NvComputer*,NvApp,QUrl)),
boxArtManager, SLOT(handleBoxArtLoadComplete(NvComputer*,NvApp,QUrl)));
connect(this, &NetworkBoxArtLoadTask::boxArtFetchCompleted,
boxArtManager, &BoxArtManager::handleBoxArtLoadComplete);
}
signals:
+4 -5
View File
@@ -164,7 +164,7 @@ ComputerManager::ComputerManager(QObject *parent)
// because NvHTTP uses aboutToQuit() to abort requests in progres
// while quitting, however this is a one time signal - additional
// requests would not be aborted and block termination.
connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(handleAboutToQuit()));
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &ComputerManager::handleAboutToQuit);
}
ComputerManager::~ComputerManager()
@@ -302,8 +302,8 @@ void ComputerManager::startPollingComputer(NvComputer* computer)
if (!pollingEntry->isActive()) {
PcMonitorThread* thread = new PcMonitorThread(computer);
connect(thread, SIGNAL(computerStateChanged(NvComputer*)),
this, SLOT(handleComputerStateChanged(NvComputer*)));
connect(thread, &PcMonitorThread::computerStateChanged,
this, &ComputerManager::handleComputerStateChanged);
pollingEntry->setActiveThread(thread);
thread->start();
}
@@ -337,7 +337,6 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
address.isInSubnet(QHostAddress("fec0::"), 10) ||
address.isInSubnet(QHostAddress("fc00::"), 7)) {
addNewHost(address.toString(), true, v6Global);
added = true;
break;
}
}
@@ -418,7 +417,7 @@ void ComputerManager::deleteHost(NvComputer* computer)
void ComputerManager::renameHost(NvComputer* computer, QString name)
{
{
QWriteLocker(&computer->lock);
QWriteLocker lock(&computer->lock);
computer->name = name;
computer->hasCustomName = true;
+1 -1
View File
@@ -70,7 +70,7 @@ private:
m_Resolver = new QMdnsEngine::Resolver(m_Server, m_Hostname);
connect(m_Resolver, &QMdnsEngine::Resolver::resolved,
this, &MdnsPendingComputer::handleResolvedAddress);
QTimer::singleShot(2000, this, SLOT(handleResolvedTimeout()));
QTimer::singleShot(2000, this, &MdnsPendingComputer::handleResolvedTimeout);
}
QByteArray m_Hostname;
+4 -1
View File
@@ -143,7 +143,10 @@ QSslKey
IdentityManager::getSslKey()
{
if (m_CachedSslKey.isNull()) {
BIO* bio = BIO_new_mem_buf(m_CachedPrivateKey.data(), -1);
// This seemingly useless const_cast is required for old OpenSSL headers
// where BIO_new_mem_buf's parameter is not declared const like those on
// the Steam Link hardware.
BIO* bio = BIO_new_mem_buf(const_cast<char*>(m_CachedPrivateKey.constData()), -1);
THROW_BAD_ALLOC_IF_NULL(bio);
EVP_PKEY* pk = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr);
+2 -1
View File
@@ -30,6 +30,7 @@ NvComputer::NvComputer(QSettings& settings)
this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray());
int appCount = settings.beginReadArray(SER_APPLIST);
this->appList.reserve(appCount);
for (int i = 0; i < appCount; i++) {
settings.setArrayIndex(i);
@@ -98,7 +99,7 @@ NvComputer::NvComputer(QString address, QString serverInfo, QSslCertificate serv
QString newMacString = NvHTTP::getXmlString(serverInfo, "mac");
if (newMacString != "00:00:00:00:00:00") {
QStringList macOctets = newMacString.split(':');
for (QString macOctet : macOctets) {
for (const QString& macOctet : macOctets) {
this->macAddress.append((char) macOctet.toInt(nullptr, 16));
}
}
+6 -5
View File
@@ -67,7 +67,8 @@ NvHTTP::parseQuad(QString quad)
}
QStringList parts = quad.split(".");
for (int i = 0; i < 4; i++)
ret.reserve(parts.length());
for (int i = 0; i < parts.length(); i++)
{
ret.append(parts.at(i).toInt());
}
@@ -391,7 +392,7 @@ void NvHTTP::handleSslErrors(QNetworkReply* reply, const QList<QSslError>& error
return;
}
for (auto error : errors) {
for (const QSslError& error : errors) {
if (m_ServerCert != error.certificate()) {
ignoreErrors = false;
break;
@@ -462,10 +463,10 @@ NvHTTP::openConnection(QUrl baseUrl,
// Run the request with a timeout if requested
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), &loop, SLOT(quit()));
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, &loop, &QEventLoop::quit);
if (timeoutMs) {
QTimer::singleShot(timeoutMs, &loop, SLOT(quit()));
QTimer::singleShot(timeoutMs, &loop, &QEventLoop::quit);
}
if (logLevel >= NvLogLevel::NVLL_VERBOSE) {
qInfo() << "Executing request:" << url.toString();
+1
View File
@@ -23,6 +23,7 @@ public:
int height;
int refreshRate;
};
Q_DECLARE_TYPEINFO(NvDisplayMode, Q_PRIMITIVE_TYPE);
class GfeHttpResponseException : public std::exception
{
+1 -1
View File
@@ -215,7 +215,7 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
QByteArray salt = generateRandomBytes(16);
QByteArray saltedPin = saltPin(salt, pin);
QByteArray aesKey = QCryptographicHash::hash(saltedPin, hashAlgo).data();
QByteArray aesKey = QCryptographicHash::hash(saltedPin, hashAlgo).constData();
aesKey.truncate(16);
QString getCert = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
+62 -7
View File
@@ -14,7 +14,7 @@
SystemProperties::SystemProperties()
{
versionString = QString(VERSION_STR);
hasWindowManager = WMUtils::isRunningWindowManager();
hasDesktopEnvironment = WMUtils::isRunningDesktopEnvironment();
isRunningWayland = WMUtils::isRunningWayland();
isRunningXWayland = isRunningWayland && QGuiApplication::platformName() == "xcb";
QString nativeArch = QSysInfo::currentCpuArchitecture();
@@ -55,12 +55,8 @@ SystemProperties::SystemProperties()
friendlyNativeArchName = nativeArch.toUpper();
}
#ifndef STEAM_LINK
// Assume we can probably launch a browser if we're in a GUI environment
hasBrowser = hasWindowManager;
#else
hasBrowser = false;
#endif
hasBrowser = hasDesktopEnvironment;
#ifdef HAVE_DISCORD
hasDiscordIntegration = true;
@@ -91,7 +87,36 @@ QRect SystemProperties::getNativeResolution(int displayIndex)
return monitorNativeResolutions.value(displayIndex);
}
class QuerySdlVideoThread : public QThread
{
public:
QuerySdlVideoThread(SystemProperties* me) :
QThread(nullptr),
m_Me(me) {}
void run() override
{
m_Me->querySdlVideoInfoInternal();
}
SystemProperties* m_Me;
};
void SystemProperties::querySdlVideoInfo()
{
if (WMUtils::isRunningX11() || WMUtils::isRunningWayland()) {
// Use a separate thread to temporarily initialize SDL
// video to avoid stomping on Qt's X11 and OGL state.
QuerySdlVideoThread thread(this);
thread.start();
thread.wait();
}
else {
querySdlVideoInfoInternal();
}
}
void SystemProperties::querySdlVideoInfoInternal()
{
hasHardwareAcceleration = false;
@@ -103,7 +128,8 @@ void SystemProperties::querySdlVideoInfo()
}
// Update display related attributes (max FPS, native resolution, etc).
refreshDisplays();
// We call the internal variant because we're already in a safe thread context.
refreshDisplaysInternal();
SDL_Window* testWindow = SDL_CreateWindow("", 0, 0, 1280, 720,
SDL_WINDOW_HIDDEN | StreamUtils::getPlatformWindowFlags());
@@ -129,7 +155,36 @@ void SystemProperties::querySdlVideoInfo()
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
class RefreshDisplaysThread : public QThread
{
public:
RefreshDisplaysThread(SystemProperties* me) :
QThread(nullptr),
m_Me(me) {}
void run() override
{
m_Me->refreshDisplaysInternal();
}
SystemProperties* m_Me;
};
void SystemProperties::refreshDisplays()
{
if (WMUtils::isRunningX11() || WMUtils::isRunningWayland()) {
// Use a separate thread to temporarily initialize SDL
// video to avoid stomping on Qt's X11 and OGL state.
RefreshDisplaysThread thread(this);
thread.start();
thread.wait();
}
else {
refreshDisplaysInternal();
}
}
void SystemProperties::refreshDisplaysInternal()
{
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+7 -2
View File
@@ -7,6 +7,9 @@ class SystemProperties : public QObject
{
Q_OBJECT
friend class QuerySdlVideoThread;
friend class RefreshDisplaysThread;
public:
SystemProperties();
@@ -16,7 +19,7 @@ public:
Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT)
Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT)
Q_PROPERTY(QString friendlyNativeArchName MEMBER friendlyNativeArchName CONSTANT)
Q_PROPERTY(bool hasWindowManager MEMBER hasWindowManager CONSTANT)
Q_PROPERTY(bool hasDesktopEnvironment MEMBER hasDesktopEnvironment CONSTANT)
Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT)
Q_PROPERTY(bool hasDiscordIntegration MEMBER hasDiscordIntegration CONSTANT)
Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged)
@@ -33,6 +36,8 @@ signals:
private:
void querySdlVideoInfo();
void querySdlVideoInfoInternal();
void refreshDisplaysInternal();
bool hasHardwareAcceleration;
bool rendererAlwaysFullScreen;
@@ -40,7 +45,7 @@ private:
bool isRunningXWayland;
bool isWow64;
QString friendlyNativeArchName;
bool hasWindowManager;
bool hasDesktopEnvironment;
bool hasBrowser;
bool hasDiscordIntegration;
QString unmappedGamepads;
+12 -9
View File
@@ -52,7 +52,7 @@ public:
void handleUnknownOptions()
{
if (unknownOptionNames().length()) {
if (!unknownOptionNames().isEmpty()) {
showError(QString("Unknown options: %1").arg(unknownOptionNames().join(", ")));
}
}
@@ -264,6 +264,11 @@ StreamCommandLineParser::StreamCommandLineParser()
{"software", StreamingPreferences::VDS_FORCE_SOFTWARE},
{"hardware", StreamingPreferences::VDS_FORCE_HARDWARE},
};
m_CaptureSysKeysModeMap = {
{"never", StreamingPreferences::CSK_OFF},
{"fullscreen", StreamingPreferences::CSK_FULLSCREEN},
{"always", StreamingPreferences::CSK_ALWAYS},
};
}
StreamCommandLineParser::~StreamCommandLineParser()
@@ -307,7 +312,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
parser.addToggleOption("background-gamepad", "background gamepad input");
parser.addToggleOption("reverse-scroll-direction", "inverted scroll direction");
parser.addToggleOption("swap-gamepad-buttons", "swap A/B and X/Y gamepad buttons (Nintendo-style)");
parser.addToggleOption("capture-system-keys", "capture system key combos in fullscreen mode");
parser.addChoiceOption("capture-system-keys", "capture system key combos", m_CaptureSysKeysModeMap.keys());
parser.addChoiceOption("video-codec", "video codec", m_VideoCodecMap.keys());
parser.addChoiceOption("video-decoder", "video decoder", m_VideoDecoderMap.keys());
@@ -320,25 +325,21 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
// Resolve display's width and height
QRegularExpression resolutionRexExp("^(720|1080|1440|4K|resolution)$");
QStringList resoOptions = parser.optionNames().filter(resolutionRexExp);
bool displaySet = resoOptions.length();
bool displaySet = !resoOptions.isEmpty();
if (displaySet) {
QString name = resoOptions.last();
if (name == "720") {
preferences->width = 1280;
preferences->height = 720;
displaySet = true;
} else if (name == "1080") {
preferences->width = 1920;
preferences->height = 1080;
displaySet = true;
} else if (name == "1440") {
preferences->width = 2560;
preferences->height = 1440;
displaySet = true;
} else if (name == "4K") {
preferences->width = 3840;
preferences->height = 2160;
displaySet = true;
} else if (name == "resolution") {
auto resolution = parser.getResolutionOptionValue(name);
preferences->width = resolution.first;
@@ -422,8 +423,10 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
// Resolve --swap-gamepad-buttons and --no-swap-gamepad-buttons options
preferences->swapFaceButtons = parser.getToggleOptionValue("swap-gamepad-buttons", preferences->swapFaceButtons);
// Resolve --capture-system-keys and --no-capture-system-keys options
preferences->captureSysKeys = parser.getToggleOptionValue("capture-system-keys", preferences->captureSysKeys);
// Resolve --capture-system-keys option
if (parser.isSet("capture-system-keys")) {
preferences->captureSysKeysMode = mapValue(m_CaptureSysKeysModeMap, parser.getChoiceOptionValue("capture-system-keys"));
}
// Resolve --video-codec option
if (parser.isSet("video-codec")) {
+1
View File
@@ -53,4 +53,5 @@ private:
QMap<QString, StreamingPreferences::AudioConfig> m_AudioConfigMap;
QMap<QString, StreamingPreferences::VideoCodecConfig> m_VideoCodecMap;
QMap<QString, StreamingPreferences::VideoDecoderSelection> m_VideoDecoderMap;
QMap<QString, StreamingPreferences::CaptureSysKeysMode> m_CaptureSysKeysModeMap;
};
@@ -15,7 +15,7 @@
<li>Hardware accelerated video decoding with VAAPI, VDPAU, and NVDEC support</li>
<li>7.1 surround sound audio support</li>
<li>Support for both pointer capture (for games) and direct mouse control (for remote desktop)</li>
<li>Support for passing system-wide keyboard shortcuts like Alt+Tab to the host in fullscreen mode on X11</li>
<li>Support for passing system-wide keyboard shortcuts like Alt+Tab to the host</li>
<li>Direct touchscreen mouse control</li>
<li>HEVC support for better video compression efficiency</li>
<li>Gamepad support with force feedback for up to 4 players</li>
@@ -36,6 +36,28 @@
</screenshots>
<releases>
<release version="3.1.0" date="2021-03-14">
<description>
<p>New features:</p>
<ul>
<li>System keyboard shortcuts (Alt+Tab, Super key, Ctrl+Shift+Esc, etc.) can now be (optionally) captured in windowed mode</li>
<li>System keyboard shortcuts can now be captured under Wayland</li>
<li>Added support for typing the client's clipboard text on the host by pressing Ctrl+Alt+Shift+V</li>
<li>Added Ctrl+Alt+Shift+D key combo to minimize the streaming window</li>
<li>Added Ctrl+Alt+Shift+C key combo to show the local cursor while streaming in remote desktop mouse mode</li>
<li>Added Select+L1+R1+X gamepad button combo to toggle performance overlay</li>
<li>Added translations for Simplified Chinese and German</li>
<li>Added a language selector on the settings page</li>
</ul>
<p>Bugfixes:</p>
<ul>
<li>Improved performance of loading the app grid</li>
<li>Fixed video scaling in High DPI mode on Wayland</li>
<li>Fixed window icons on Wayland</li>
<li>Fixed number pad enter key functionality in the UI</li>
</ul>
</description>
</release>
<release version="3.0.0" date="2021-01-17">
<description>
<p>New features:</p>
+1 -11
View File
@@ -5,19 +5,9 @@
# pairing data will be lost each reboot.
HOME=/usr/local/moonlight
# For a seamless transition across the Qt 5.9 -> Qt 5.14 boundary,
# we bundle binaries built with both Qt 5.9 and Qt 5.14 and pick
# the correct one at runtime.
if [ -d "/usr/local/Qt-5.14.1" ]; then
BIN="moonlight"
else
BIN="moonlight59"
fi
# Renice PE_Single_CPU which seems to host A/V stuff
renice -10 -p $(pidof PE_Single_CPU)
# Renice Moonlight itself to avoid preemption by background tasks
# Write output to a logfile in /tmp
echo Launching $BIN
exec nice -n -10 ./bin/$BIN > /tmp/moonlight.log
exec nice -n -10 ./bin/moonlight > /tmp/moonlight.log
+128 -97
View File
@@ -73,6 +73,9 @@ CenteredGridView {
width: 220; height: 287;
grid: appGrid
property alias appContextMenu: appContextMenuLoader.item
property alias appNameText: appNameTextLoader.item
// Dim the app if it's hidden
opacity: model.hidden ? 0.4 : 1.0
@@ -104,81 +107,94 @@ CenteredGridView {
ToolTip.text: model.name
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: (parent.hovered || parent.highlighted) && (!appNameText.visible || appNameText.truncated)
ToolTip.visible: (parent.hovered || parent.highlighted) && (!appNameText || appNameText.truncated)
}
ToolButton {
id: resumeButton
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? -47 : 0
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : -60
anchors.centerIn: appIcon
visible: model.running
implicitWidth: 125
implicitHeight: 125
Loader {
active: model.running
asynchronous: true
anchors.fill: appIcon
Image {
source: "qrc:/res/baseline-play_circle_filled_white-48px.svg"
anchors.centerIn: parent
sourceSize {
width: 75
height: 75
sourceComponent: Item {
ToolButton {
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? -47 : 0
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : -60
anchors.centerIn: parent
implicitWidth: 125
implicitHeight: 125
Image {
source: "qrc:/res/baseline-play_circle_filled_white-48px.svg"
anchors.centerIn: parent
sourceSize {
width: 75
height: 75
}
}
onClicked: {
launchOrResumeSelectedApp(true)
}
ToolTip.text: qsTr("Resume Game")
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
ToolButton {
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? 47 : 0
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : 60
anchors.centerIn: parent
implicitWidth: 125
implicitHeight: 125
Image {
source: "qrc:/res/baseline-cancel-24px.svg"
anchors.centerIn: parent
sourceSize {
width: 75
height: 75
}
}
onClicked: {
doQuitGame()
}
ToolTip.text: qsTr("Quit Game")
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
}
onClicked: {
launchOrResumeSelectedApp(true)
}
ToolTip.text: qsTr("Resume Game")
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
ToolButton {
id: quitButton
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? 47 : 0
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : 60
anchors.centerIn: appIcon
visible: model.running
implicitWidth: 125
implicitHeight: 125
Loader {
id: appNameTextLoader
active: appIcon.isPlaceholder
Image {
source: "qrc:/res/baseline-cancel-24px.svg"
anchors.centerIn: parent
sourceSize {
width: 75
height: 75
}
}
// This loader is not asynchronous to avoid noticeable differences
// in the time in which the text loads for each game.
onClicked: {
doQuitGame()
}
ToolTip.text: qsTr("Quit Game")
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
Label {
id: appNameText
visible: appIcon.isPlaceholder
text: model.name
width: appIcon.width
height: model.running ? 175 : appIcon.height
leftPadding: 20
rightPadding: 20
anchors.left: appIcon.left
anchors.right: appIcon.right
anchors.bottom: appIcon.bottom
font.pointSize: 22
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
elide: Text.ElideRight
sourceComponent: Label {
id: appNameText
text: model.name
font.pointSize: 22
leftPadding: 20
rightPadding: 20
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
elide: Text.ElideRight
}
}
function launchOrResumeSelectedApp(quitExistingApp)
@@ -245,6 +261,17 @@ CenteredGridView {
}
}
Keys.onEnterPressed: {
// Open the app context menu if activated via the gamepad or keyboard
// for running games. If the game isn't running, the above onClicked
// method will handle the launch.
if (model.running) {
// This will be keyboard/gamepad driven so use
// open() instead of popup()
appContextMenu.open()
}
}
Keys.onMenuPressed: {
// This will be keyboard/gamepad driven so use open() instead of popup()
appContextMenu.open()
@@ -256,44 +283,48 @@ CenteredGridView {
quitAppDialog.open()
}
NavigableMenu {
id: appContextMenu
NavigableMenuItem {
parentMenu: appContextMenu
text: model.running ? qsTr("Resume Game") : qsTr("Launch Game")
onTriggered: launchOrResumeSelectedApp(true)
}
NavigableMenuItem {
parentMenu: appContextMenu
text: qsTr("Quit Game")
onTriggered: doQuitGame()
visible: model.running
}
NavigableMenuItem {
parentMenu: appContextMenu
checkable: true
checked: model.directLaunch
text: qsTr("Direct Launch")
onTriggered: appModel.setAppDirectLaunch(model.index, !model.directLaunch)
enabled: !model.hidden
Loader {
id: appContextMenuLoader
asynchronous: true
sourceComponent: NavigableMenu {
id: appContextMenu
NavigableMenuItem {
parentMenu: appContextMenu
text: model.running ? qsTr("Resume Game") : qsTr("Launch Game")
onTriggered: launchOrResumeSelectedApp(true)
}
NavigableMenuItem {
parentMenu: appContextMenu
text: qsTr("Quit Game")
onTriggered: doQuitGame()
visible: model.running
}
NavigableMenuItem {
parentMenu: appContextMenu
checkable: true
checked: model.directLaunch
text: qsTr("Direct Launch")
onTriggered: appModel.setAppDirectLaunch(model.index, !model.directLaunch)
enabled: !model.hidden
ToolTip.text: qsTr("Launch this app immediately when the host is selected, bypassing the app selection grid.")
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
NavigableMenuItem {
parentMenu: appContextMenu
checkable: true
checked: model.hidden
text: qsTr("Hide Game")
onTriggered: appModel.setAppHidden(model.index, !model.hidden)
enabled: model.hidden || (!model.running && !model.directLaunch)
ToolTip.text: qsTr("Launch this app immediately when the host is selected, bypassing the app selection grid.")
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
NavigableMenuItem {
parentMenu: appContextMenu
checkable: true
checked: model.hidden
text: qsTr("Hide Game")
onTriggered: appModel.setAppHidden(model.index, !model.hidden)
enabled: model.hidden || (!model.running && !model.directLaunch)
ToolTip.text: qsTr("Hide this game from the app grid. To access hidden games, right-click on the host and choose %1.").arg(qsTr("View All Apps"))
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: qsTr("Hide this game from the app grid. To access hidden games, right-click on the host and choose %1.").arg(qsTr("View All Apps"))
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
}
}
}
}
+3
View File
@@ -21,4 +21,7 @@ ItemDelegate {
Keys.onReturnPressed: {
clicked()
}
Keys.onEnterPressed: {
clicked()
}
}
+4
View File
@@ -22,6 +22,10 @@ MenuItem {
triggered()
}
Keys.onEnterPressed: {
triggered()
}
Keys.onEscapePressed: {
parentMenu.close()
}
+4
View File
@@ -56,6 +56,10 @@ NavigableDialog {
accept()
}
Keys.onEnterPressed: {
accept()
}
Keys.onEscapePressed: {
reject()
}
+4
View File
@@ -27,6 +27,10 @@ ToolButton {
clicked()
}
Keys.onEnterPressed: {
clicked()
}
Keys.onRightPressed: {
nextItemInFocusChain(true).forceActiveFocus(Qt.TabFocus)
}
+54 -44
View File
@@ -113,6 +113,8 @@ CenteredGridView {
width: 300; height: 320;
grid: pcGrid
property alias pcContextMenu : pcContextMenuLoader.item
Image {
id: pcIcon
anchors.horizontalCenter: parent.horizontalCenter
@@ -160,54 +162,58 @@ CenteredGridView {
elide: Text.ElideRight
}
NavigableMenu {
id: pcContextMenu
MenuItem {
text: qsTr("PC Status: %1").arg(model.online ? qsTr("Online") : qsTr("Offline"))
font.bold: true
enabled: false
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("View All Apps")
onTriggered: {
var component = Qt.createComponent("AppView.qml")
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name, "showHiddenGames": true})
stackView.push(appView)
Loader {
id: pcContextMenuLoader
asynchronous: true
sourceComponent: NavigableMenu {
id: pcContextMenu
MenuItem {
text: qsTr("PC Status: %1").arg(model.online ? qsTr("Online") : qsTr("Offline"))
font.bold: true
enabled: false
}
visible: model.online && model.paired
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Wake PC")
onTriggered: computerModel.wakeComputer(index)
visible: !model.online && model.wakeable
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Test Network")
onTriggered: {
computerModel.testConnectionForComputer(index)
testConnectionDialog.open()
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("View All Apps")
onTriggered: {
var component = Qt.createComponent("AppView.qml")
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name, "showHiddenGames": true})
stackView.push(appView)
}
visible: model.online && model.paired
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Wake PC")
onTriggered: computerModel.wakeComputer(index)
visible: !model.online && model.wakeable
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Test Network")
onTriggered: {
computerModel.testConnectionForComputer(index)
testConnectionDialog.open()
}
}
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Rename PC")
onTriggered: {
renamePcDialog.pcIndex = index
renamePcDialog.originalName = model.name
renamePcDialog.open()
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Rename PC")
onTriggered: {
renamePcDialog.pcIndex = index
renamePcDialog.originalName = model.name
renamePcDialog.open()
}
}
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Delete PC")
onTriggered: {
deletePcDialog.pcIndex = index
// get confirmation first, actual closing is called from the dialog
deletePcDialog.open()
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Delete PC")
onTriggered: {
deletePcDialog.pcIndex = index
// get confirmation first, actual closing is called from the dialog
deletePcDialog.open()
}
}
}
}
@@ -379,6 +385,10 @@ CenteredGridView {
Keys.onReturnPressed: {
renamePcDialog.accept()
}
Keys.onEnterPressed: {
renamePcDialog.accept()
}
}
}
}
+176 -46
View File
@@ -358,6 +358,10 @@ Flickable {
Keys.onReturnPressed: {
customResolutionDialog.accept()
}
Keys.onEnterPressed: {
customResolutionDialog.accept()
}
}
Label {
@@ -382,6 +386,10 @@ Flickable {
Keys.onReturnPressed: {
customResolutionDialog.accept()
}
Keys.onEnterPressed: {
customResolutionDialog.accept()
}
}
}
}
@@ -519,12 +527,17 @@ Flickable {
text: qsTr("Display mode")
font.pointSize: 12
wrapMode: Text.Wrap
visible: SystemProperties.hasWindowManager
visible: SystemProperties.hasDesktopEnvironment
}
AutoResizingComboBox {
// ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
if (!visible) {
// Do nothing if the control won't even be visible
return
}
// Set the recommended option based on the OS
for (var i = 0; i < windowModeListModel.count; i++) {
var thisWm = windowModeListModel.get(i).val;
@@ -537,22 +550,20 @@ Flickable {
currentIndex = 0
if (SystemProperties.hasWindowManager && !SystemProperties.rendererAlwaysFullScreen) {
var savedWm = StreamingPreferences.windowMode
for (var i = 0; i < windowModeListModel.count; i++) {
var thisWm = windowModeListModel.get(i).val;
if (savedWm === thisWm) {
currentIndex = i
break
}
}
var savedWm = StreamingPreferences.windowMode
for (var i = 0; i < windowModeListModel.count; i++) {
var thisWm = windowModeListModel.get(i).val;
if (savedWm === thisWm) {
currentIndex = i
break
}
}
activated(currentIndex)
}
id: windowModeComboBox
visible: SystemProperties.hasWindowManager
visible: SystemProperties.hasDesktopEnvironment
enabled: !SystemProperties.rendererAlwaysFullScreen
hoverEnabled: true
textRole: "text"
@@ -697,7 +708,7 @@ Flickable {
width: parent.width
text: qsTr("Mute audio stream when Moonlight is not the active window")
font.pointSize: 12
visible: SystemProperties.hasWindowManager
visible: SystemProperties.hasDesktopEnvironment
checked: StreamingPreferences.muteOnFocusLoss
onCheckedChanged: {
StreamingPreferences.muteOnFocusLoss = checked
@@ -724,37 +735,103 @@ Flickable {
Label {
width: parent.width
id: uiDisplayModeTitle
text: qsTr("GUI display mode")
id: languageTitle
text: qsTr("Language")
font.pointSize: 12
wrapMode: Text.Wrap
visible: SystemProperties.hasWindowManager
}
AutoResizingComboBox {
// ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
if (SystemProperties.hasWindowManager) {
var saved_uidisplaymode = StreamingPreferences.uiDisplayMode
currentIndex = 0
for (var i = 0; i < uiDisplayModeListModel.count; i++) {
var el_uidisplaymode = uiDisplayModeListModel.get(i).val;
if (saved_uidisplaymode === el_uidisplaymode) {
currentIndex = i
break
}
var saved_language = StreamingPreferences.language
currentIndex = 0
for (var i = 0; i < languageListModel.count; i++) {
var el_language = languageListModel.get(i).val;
if (saved_language === el_language) {
currentIndex = i
break
}
}
else {
// Full-screen is always selected when there is no window manager
currentIndex = 2
activated(currentIndex)
}
id: languageComboBox
textRole: "text"
model: ListModel {
id: languageListModel
ListElement {
text: qsTr("Automatic")
val: StreamingPreferences.LANG_AUTO
}
ListElement {
text: "Deutsch" //German
val: StreamingPreferences.LANG_DE
}
ListElement {
text: "English"
val: StreamingPreferences.LANG_EN
}
ListElement {
text: "Français" //French
val: StreamingPreferences.LANG_FR
}
ListElement {
text: "简体中文" //Simplified Chinese
val: StreamingPreferences.LANG_ZH_CN
}
}
// ::onActivated must be used, as it only listens for when the index is changed by a human
onActivated : {
// Retranslating is expensive, so only do it if the language actually changed
var new_language = languageListModel.get(currentIndex).val
if (StreamingPreferences.language !== new_language) {
StreamingPreferences.language = languageListModel.get(currentIndex).val
if (!StreamingPreferences.retranslate()) {
ToolTip.show(qsTr("You must restart Moonlight for this change to take effect"), 5000)
}
else {
// Force the back operation to pop any AppView pages that exist.
// The AppView stops working after retranslate() for some reason.
window.clearOnBack = true
}
}
}
}
Label {
width: parent.width
id: uiDisplayModeTitle
text: qsTr("GUI display mode")
font.pointSize: 12
wrapMode: Text.Wrap
visible: SystemProperties.hasDesktopEnvironment
}
AutoResizingComboBox {
// ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
if (!visible) {
// Do nothing if the control won't even be visible
return
}
var saved_uidisplaymode = StreamingPreferences.uiDisplayMode
currentIndex = 0
for (var i = 0; i < uiDisplayModeListModel.count; i++) {
var el_uidisplaymode = uiDisplayModeListModel.get(i).val;
if (saved_uidisplaymode === el_uidisplaymode) {
currentIndex = i
break
}
}
activated(currentIndex)
}
id: uiDisplayModeComboBox
visible: SystemProperties.hasWindowManager
visible: SystemProperties.hasDesktopEnvironment
textRole: "text"
model: ListModel {
id: uiDisplayModeListModel
@@ -833,7 +910,6 @@ Flickable {
width: parent.width
text: qsTr("Optimize mouse for remote desktop instead of games")
font.pointSize: 12
visible: SystemProperties.hasWindowManager
checked: StreamingPreferences.absoluteMouseMode
onCheckedChanged: {
StreamingPreferences.absoluteMouseMode = checked
@@ -847,25 +923,79 @@ Flickable {
qsTr("NOTE: Due to a bug in GeForce Experience, this option may not work properly if your host PC has multiple monitors.")
}
CheckBox {
id: captureSysKeysCheck
hoverEnabled: true
Row {
spacing: 5
width: parent.width
text: qsTr("Capture system keyboard shortcuts ") +
(!SystemProperties.isRunningWayland ? qsTr("while streaming in fullscreen") :
qsTr("(unsupported on Wayland)"))
font.pointSize: 12
enabled: SystemProperties.hasWindowManager && !SystemProperties.isRunningWayland
checked: (StreamingPreferences.captureSysKeys || !SystemProperties.hasWindowManager) && !SystemProperties.isRunningWayland
onCheckedChanged: {
StreamingPreferences.captureSysKeys = checked
CheckBox {
id: captureSysKeysCheck
hoverEnabled: true
text: qsTr("Capture system keyboard shortcuts")
font.pointSize: 12
enabled: SystemProperties.hasDesktopEnvironment
checked: StreamingPreferences.captureSysKeysMode !== StreamingPreferences.CSK_OFF || !SystemProperties.hasDesktopEnvironment
ToolTip.delay: 1000
ToolTip.timeout: 10000
ToolTip.visible: hovered
ToolTip.text: qsTr("This enables the capture of system-wide keyboard shortcuts like Alt+Tab that would normally be handled by the client OS while streaming.") + "\n\n" +
qsTr("NOTE: Certain keyboard shortcuts like Ctrl+Alt+Del on Windows cannot be intercepted by any application, including Moonlight.")
}
ToolTip.delay: 1000
ToolTip.timeout: 10000
ToolTip.visible: hovered
ToolTip.text: qsTr("This enables the capture of system-wide keyboard shortcuts like Alt+Tab that would normally be handled by the client OS while streaming in fullscreen.") + "\n\n" +
qsTr("NOTE: Certain keyboard shortcuts like Ctrl+Alt+Del on Windows cannot be intercepted by any application, including Moonlight.")
AutoResizingComboBox {
// ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
if (!visible) {
// Do nothing if the control won't even be visible
return
}
var saved_syskeysmode = StreamingPreferences.captureSysKeysMode
currentIndex = 0
for (var i = 0; i < captureSysKeysModeListModel.count; i++) {
var el_syskeysmode = captureSysKeysModeListModel.get(i).val;
if (saved_syskeysmode === el_syskeysmode) {
currentIndex = i
break
}
}
activated(currentIndex)
}
enabled: captureSysKeysCheck.checked && captureSysKeysCheck.enabled
textRole: "text"
model: ListModel {
id: captureSysKeysModeListModel
ListElement {
text: qsTr("in fullscreen")
val: StreamingPreferences.CSK_FULLSCREEN
}
ListElement {
text: qsTr("always")
val: StreamingPreferences.CSK_ALWAYS
}
}
function updatePref() {
if (!enabled) {
StreamingPreferences.captureSysKeysMode = StreamingPreferences.CSK_OFF
}
else {
StreamingPreferences.captureSysKeysMode = captureSysKeysModeListModel.get(currentIndex).val
}
}
// ::onActivated must be used, as it only listens for when the index is changed by a human
onActivated: {
updatePref()
}
// This handles transition of the checkbox state
onEnabledChanged: {
updatePref()
}
}
}
CheckBox {
@@ -980,7 +1110,7 @@ Flickable {
width: parent.width
text: qsTr("Process gamepad input when Moonlight is in the background")
font.pointSize: 12
visible: SystemProperties.hasWindowManager
visible: SystemProperties.hasDesktopEnvironment
checked: StreamingPreferences.backgroundGamepad
onCheckedChanged: {
StreamingPreferences.backgroundGamepad = checked
+24 -4
View File
@@ -12,13 +12,18 @@ import SdlGamepadKeyNavigation 1.0
ApplicationWindow {
property bool pollingActive: false
// Set by SettingsView to force the back operation to pop all
// pages except the initial view. This is required when doing
// a retranslate() because AppView breaks for some reason.
property bool clearOnBack: false
id: window
visible: true
width: 1280
height: 600
visibility: {
if (SystemProperties.hasWindowManager) {
if (SystemProperties.hasDesktopEnvironment) {
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_WINDOWED) return "Windowed"
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) return "Maximized"
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_FULLSCREEN) return "FullScreen"
@@ -31,6 +36,17 @@ ApplicationWindow {
// it will never insert a line break and just extend on forever.
ToolTip.toolTip.contentWidth: ToolTip.toolTip.implicitContentWidth < 400 ? ToolTip.toolTip.implicitContentWidth : 400
function goBack() {
if (clearOnBack) {
// Pop all items except the first one
stackView.pop(null)
clearOnBack = false
}
else {
stackView.pop()
}
}
StackView {
id: stackView
initialItem: initialView
@@ -46,7 +62,7 @@ ApplicationWindow {
Keys.onEscapePressed: {
if (depth > 1) {
stackView.pop()
goBack()
}
else {
quitConfirmationDialog.open()
@@ -55,7 +71,7 @@ ApplicationWindow {
Keys.onBackPressed: {
if (depth > 1) {
stackView.pop()
goBack()
}
else {
quitConfirmationDialog.open()
@@ -210,7 +226,7 @@ ApplicationWindow {
iconSource: "qrc:/res/arrow_left.svg"
onClicked: stackView.pop()
onClicked: goBack()
Keys.onDownPressed: {
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
@@ -495,6 +511,10 @@ ApplicationWindow {
Keys.onReturnPressed: {
addPcDialog.accept()
}
Keys.onEnterPressed: {
addPcDialog.accept()
}
}
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ SdlGamepadKeyNavigation::SdlGamepadKeyNavigation()
m_LastAxisNavigationEventTime(0)
{
m_PollingTimer = new QTimer(this);
connect(m_PollingTimer, SIGNAL(timeout()), this, SLOT(onPollingTimerFired()));
connect(m_PollingTimer, &QTimer::timeout, this, &SdlGamepadKeyNavigation::onPollingTimerFired);
}
SdlGamepadKeyNavigation::~SdlGamepadKeyNavigation()
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
+382 -160
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+990
View File
@@ -0,0 +1,990 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>AppView</name>
<message>
<location filename="../gui/AppView.qml" line="139"/>
<location filename="../gui/AppView.qml" line="293"/>
<source>Resume Game</source>
<translation>继续游戏</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="165"/>
<location filename="../gui/AppView.qml" line="298"/>
<source>Quit Game</source>
<translation>退出游戏</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="293"/>
<source>Launch Game</source>
<translation>启动游戏</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="306"/>
<source>Direct Launch</source>
<translation>直接启动</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="310"/>
<source>Launch this app immediately when the host is selected, bypassing the app selection grid.</source>
<translation>选择目标计算机后立即启动此应用程序,绕过应用程序选择网格。</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="319"/>
<source>Hide Game</source>
<translation>隐藏游戏</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="323"/>
<source>Hide this game from the app grid. To access hidden games, right-click on the host and choose %1.</source>
<translation>从应用程序网格中隐藏此游戏。要访问隐藏的游戏,请右键单击目标计算机并选择 %1。</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="323"/>
<source>View All Apps</source>
<translation>查看所有应用程序</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="338"/>
<source>Are you sure you want to quit %1? Any unsaved progress will be lost.</source>
<translation>你确定要退出 %1 吗?所有未保存的游戏进度都将丢失。</translation>
</message>
</context>
<context>
<name>CliQuitStreamSegue</name>
<message>
<location filename="../gui/CliQuitStreamSegue.qml" line="9"/>
<source>Establishing connection to PC...</source>
<translation>正在建立与目标计算机的连接...</translation>
</message>
<message>
<location filename="../gui/CliQuitStreamSegue.qml" line="13"/>
<source>Quitting app...</source>
<translation>应用程序退出中...</translation>
</message>
</context>
<context>
<name>CliStartStreamSegue</name>
<message>
<location filename="../gui/CliStartStreamSegue.qml" line="9"/>
<source>Establishing connection to PC...</source>
<translation>正在建立与目标计算机的连接...</translation>
</message>
<message>
<location filename="../gui/CliStartStreamSegue.qml" line="13"/>
<source>Loading app list...</source>
<translation>应用程序列表加载中...</translation>
</message>
<message>
<location filename="../gui/CliStartStreamSegue.qml" line="81"/>
<source>Are you sure you want to quit %1? Any unsaved progress will be lost.</source>
<translation>你确定要退出 %1 吗?任何未保存的进度都将丢失。</translation>
</message>
</context>
<context>
<name>GamepadMapper</name>
<message>
<location filename="../gui/GamepadMapper.qml" line="4"/>
<source>Gamepad Mapping</source>
<translation>手柄按键映射</translation>
</message>
</context>
<context>
<name>NvHTTP</name>
<message>
<location filename="../backend/nvhttp.cpp" line="327"/>
<source>Missing audio capture device. Reinstalling GeForce Experience should resolve this error.</source>
<translation>缺少音频捕获设备。重新安装 GeForce Experience 可以解决此错误。</translation>
</message>
</context>
<context>
<name>PcView</name>
<message>
<location filename="../gui/PcView.qml" line="20"/>
<source>Computers</source>
<translation>计算机列表</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="67"/>
<source>Unable to connect to the specified PC.</source>
<translation>无法连接到目标计算机。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="70"/>
<source>This PC&apos;s Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network.</source>
<translation>此计算机的互联网连接被阻塞。当连接到此网络时,互联网上的流式传输可能无法工作。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="73"/>
<source>Click the Help button for possible solutions.</source>
<translation>单击帮助按钮以获取可能的解决方案。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="102"/>
<source>Searching for PCs on your local network with NVIDIA GameStream enabled...</source>
<translation>正在本地网络上搜索启用 NVIDIA GameStrea 的计算机...</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="103"/>
<source>Automatic PC discovery is disabled. Add your PC manually.</source>
<translation>已禁用计算机自动发现。需要手动添加计算机。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="171"/>
<source>PC Status: %1</source>
<translation>计算机状态: %1</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="171"/>
<source>Online</source>
<translation>在线</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="171"/>
<source>Offline</source>
<translation>离线</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="177"/>
<source>View All Apps</source>
<translation>查看所有应用程序</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="187"/>
<source>Wake PC</source>
<translation>唤醒目标计算机</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="193"/>
<source>Test Network</source>
<translation>网络测试</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="202"/>
<source>Rename PC</source>
<translation>重命名计算机</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="211"/>
<source>Delete PC</source>
<translation>删除计算机</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="242"/>
<source>You cannot pair while a previous session is still running on the host PC. Quit any running games or reboot the host PC, then try pairing again.</source>
<translation>当上一个会话仍在运行时,无法进行配对。请退出所有正在运行的游戏或重新启动目标计算机,然后重试配对。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="297"/>
<source>Please enter %1 on your GameStream PC. This dialog will close when pairing is completed.</source>
<translation>请在启用 GameStream 的计算机上输入 %1。配对完成后,此对话框将关闭。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="308"/>
<source>Are you sure you want to remove this PC?</source>
<translation>确定要移除这个计算机吗?</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="325"/>
<source>Moonlight is testing your network connection to determine if NVIDIA GameStream is blocked.</source>
<translation>Moonlight 正在测试你的网络连接,以检测 NVIDIA GameStream 是否被阻止。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="325"/>
<source>This may take a few seconds…</source>
<translation>这可能需要几秒钟...</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="332"/>
<source>The network test could not be performed because none of Moonlight&apos;s connection testing servers were reachable from this PC. Check your Internet connection or try again later.</source>
<translation>无法执行网络测试,因为此计算机无法访问 Moonlight 的连接测试服务器。请检查你的互联网连接或稍后再试。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="336"/>
<source>This network does not appear to be blocking Moonlight. If you still have trouble connecting, check your PC&apos;s firewall settings.</source>
<translation>此网络似乎没有阻止 Moonlight。如果仍然无法连接,请检查计算机的防火墙设置。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="336"/>
<source>If you are trying to stream over the Internet, install the Moonlight Internet Hosting Tool on your gaming PC and run the included Internet Streaming Tester to check your gaming PC&apos;s Internet connection.</source>
<translation>如果你想通过互联网进行流式传输,请在你的目标计算机上安装 Moonlight Internet Hosting 工具,并运行包含的互联网流式传输测试程序来检查你目标计算机的互联网连接。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="340"/>
<source>Your PC&apos;s current network connection seems to be blocking Moonlight. Streaming over the Internet may not work while connected to this network.</source>
<translation>此计算机当前的网络连接似乎被阻塞了。当连接到此网络时,互联网上的流式传输可能无法工作。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="340"/>
<source>The following network ports were blocked:</source>
<translation>下列网络端口被阻止:</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="352"/>
<source>Enter the new name for this PC:</source>
<translation>输入这台计算机的新名称:</translation>
</message>
</context>
<context>
<name>PendingQuitTask</name>
<message>
<location filename="../backend/computermanager.cpp" line="538"/>
<source>The running game wasn&apos;t started by this PC. You must quit the game on the host PC manually or use the device that originally started the game.</source>
<translation>正在运行的游戏不是由这台计算机启动的。你必须在目标计算机上手动退出游戏或使用最初启动游戏的设备。</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<location filename="../cli/quitstream.cpp" line="76"/>
<location filename="../cli/startstream.cpp" line="133"/>
<source>Failed to connect to %1</source>
<translation>无法连接到 %1</translation>
</message>
<message>
<location filename="../cli/quitstream.cpp" line="88"/>
<location filename="../cli/startstream.cpp" line="89"/>
<source>Computer %1 has not been paired. Please open Moonlight to pair before streaming.</source>
<translation>计算机 %1 尚未配对。</translation>
</message>
<message>
<location filename="../cli/quitstream.cpp" line="102"/>
<location filename="../cli/startstream.cpp" line="126"/>
<source>Quitting app failed, reason: %1</source>
<translation>退出应用程序失败,原因: %1</translation>
</message>
<message>
<location filename="../cli/startstream.cpp" line="137"/>
<source>Failed to find application %1</source>
<translation>找不到应用程序 %1</translation>
</message>
</context>
<context>
<name>QuitSegue</name>
<message>
<location filename="../gui/QuitSegue.qml" line="12"/>
<source>Quitting %1...</source>
<translation>正在退出 %1...</translation>
</message>
</context>
<context>
<name>Session</name>
<message>
<location filename="../streaming/session.cpp" line="89"/>
<source>No video received from host.</source>
<translation>没有从目标计算机接收到视频画面。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="90"/>
<source>Check your firewall and port forwarding rules for port(s): %1</source>
<translation>检查防火墙和端口转发规则: %1</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="95"/>
<source>Your network connection isn&apos;t performing well. Reduce your video bitrate setting or try a faster connection.</source>
<translation>网络连接性能不佳。请降低视频比特率或尝试更快的网络连接。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="100"/>
<source>Something went wrong on your host PC when starting the stream.</source>
<translation>目标计算机启动流式传输时出现问题。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="101"/>
<source>Make sure you don&apos;t have any DRM-protected content open on your host PC. You can also try restarting your host PC.</source>
<translation>确保目标计算机上没有打开任何受 DRM 保护的内容。你也可以尝试重新启动目标计算机。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="102"/>
<source>If the issue persists, try reinstalling your GPU drivers and GeForce Experience.</source>
<translation>如果问题仍然存在,请尝试重新安装显卡驱动程序和 GeForce Experience。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="107"/>
<source>Connection terminated</source>
<translation>会话已终止</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="578"/>
<source>Your selection to enable remote desktop mouse mode may cause problems in games.</source>
<translation>启用远程桌面鼠标模式可能会导致游戏出现问题。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="583"/>
<source>HDR is not supported with software decoding.</source>
<translation>软件解码不支持 HDR。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="587"/>
<source>Your settings selection to force software decoding may cause poor streaming performance.</source>
<translation>你选择的用于强制软件解码的设置可能会导致流式传输性能不佳。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="592"/>
<source>Using unsupported FPS options may cause stuttering or lag.</source>
<translation>使用不受支持的 FPS 选项可能会导致卡顿或延迟。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="595"/>
<source>V-sync will be disabled when streaming at a higher frame rate than the display.</source>
<translation>当流式传输的帧率高于显示时,垂直同步将被禁用。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="612"/>
<source>Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.</source>
<translation>根据你的选择使用软件解码,以在没有显卡支持的情况下强制 HEVC。这可能会导致流式传输性能不佳。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="615"/>
<source>This PC&apos;s GPU doesn&apos;t support HEVC decoding.</source>
<translation>此计算机的显卡不支持 HEVC 解码。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="622"/>
<source>Your host PC GPU doesn&apos;t support HEVC. A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.</source>
<translation>目标计算机的显卡不支持 HEVC。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="639"/>
<source>%1 doesn&apos;t support HDR10.</source>
<translation>%1 不支持 HDR10。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="643"/>
<source>Your host PC GPU doesn&apos;t support HDR streaming. A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.</source>
<translation>目标计算机的显卡不支持 HDR 流。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="652"/>
<source>This PC&apos;s GPU doesn&apos;t support HEVC Main10 decoding for HDR streaming.</source>
<translation>此计算机的显卡不支持 HEVC Main10 解码 HDR 流。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="665"/>
<source>GeForce Experience 3.0 or higher is required for 4K streaming.</source>
<translation>4K 流式传输需要 GeForce Experience 3.0 或更高版本。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="680"/>
<source>Your selected surround sound setting is not supported by the current audio device.</source>
<translation>当前音频设备不支持你选择的环绕声设置。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="687"/>
<source>Failed to open audio device. Audio will be unavailable during this session.</source>
<translation>无法打开音频设备。音频在此会话期间将不可用。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="692"/>
<source>An attached gamepad has no mapping and won&apos;t be usable. Visit the Moonlight help to resolve this.</source>
<translation>附加的手柄没有映射,因此无法使用。请访问 Moonlight 帮助解决此问题。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="700"/>
<source>Your host PC&apos;s GPU doesn&apos;t support streaming video resolutions over 4K.</source>
<translation>目标计算机的显卡不支持 4K 以上的流式传输视频分辨率。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="704"/>
<source>Video resolutions over 4K are only supported by the HEVC codec.</source>
<translation>只有 HEVC 编解码器才支持 4K 以上的视频分辨率。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="718"/>
<source>Your selection to force hardware decoding cannot be satisfied due to missing hardware decoding support on this PC&apos;s GPU.</source>
<translation>由于此计算机的显卡上缺少硬件解码支持,无法满足强制硬件解码的选择。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="721"/>
<source>Your codec selection and force hardware decoding setting are not compatible. This PC&apos;s GPU lacks support for decoding your chosen codec.</source>
<translation>你选择的编解码器和强制硬件解码设置不兼容。此计算机的显卡不支持解码你选择的编解码器。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="1022"/>
<source>GeForce Experience returned error: %1</source>
<translation>GeForce Experience 返回错误: %1</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="1445"/>
<source>Unable to initialize video decoder. Please check your streaming settings and try again.</source>
<translation>无法初始化视频解码器。请检查你的流式传输设置,然后重试。</translation>
</message>
</context>
<context>
<name>SettingsView</name>
<message>
<location filename="../gui/SettingsView.qml" line="12"/>
<source>Settings</source>
<translation>设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="60"/>
<source>Basic Settings</source>
<translation>基本设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="70"/>
<source>Resolution and FPS</source>
<translation>分辨率和 FPS</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="78"/>
<source>Setting values too high for your PC or network connection may cause lag, stuttering, or errors.</source>
<translation>如果你的计算机或网络连接设置的值过高,可能会导致延迟,卡顿或错误。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="209"/>
<source>720p</source>
<translation></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="215"/>
<source>1080p</source>
<translation></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="221"/>
<source>1440p</source>
<translation></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="227"/>
<source>4K</source>
<translation></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="331"/>
<source>Custom resolutions are not officially supported by GeForce Experience, so it will not set your host display resolution. You will need to set it manually while in game.</source>
<translation>自定义分辨率不受 GeForce Experience 的正式支持,因此不会设置目标计算机的分辨率。你需要在游戏中手动设置。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="332"/>
<source>Resolutions that are not supported by your client or host PC may cause streaming errors.</source>
<translation>使用当前计算机或目标计算机不支持的分辨率可能会导致流式传输错误。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="338"/>
<source>Enter a custom resolution:</source>
<translation>输入自定义分辨率:</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="406"/>
<location filename="../gui/SettingsView.qml" line="407"/>
<location filename="../gui/SettingsView.qml" line="427"/>
<location filename="../gui/SettingsView.qml" line="430"/>
<source>%1 FPS</source>
<translation></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="412"/>
<location filename="../gui/SettingsView.qml" line="415"/>
<location filename="../gui/SettingsView.qml" line="424"/>
<location filename="../gui/SettingsView.qml" line="437"/>
<location filename="../gui/SettingsView.qml" line="440"/>
<source>%1 FPS (Unsupported)</source>
<translation>%1 FPS (不支持)</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="493"/>
<source>Video bitrate:</source>
<translation>视频比特率:</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="501"/>
<source>Lower the bitrate on slower connections. Raise the bitrate to increase image quality.</source>
<translation>在较慢的连接上降低比特率。提高比特率以提高图像质量。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="519"/>
<source>Video bitrate: %1 Mbps</source>
<translation>视频比特率: %1 Mbps</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="527"/>
<source>Display mode</source>
<translation>显示模式</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="545"/>
<source> (Recommended)</source>
<translation> (推荐)</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="573"/>
<location filename="../gui/SettingsView.qml" line="847"/>
<source>Fullscreen</source>
<translation>全屏</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="577"/>
<source>Borderless windowed</source>
<translation>无边框窗口</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="581"/>
<location filename="../gui/SettingsView.qml" line="839"/>
<source>Windowed</source>
<translation>窗口化</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="592"/>
<source>Fullscreen generally provides the best performance, but borderless windowed may work better with features like macOS Spaces, Alt+Tab, screenshot tools, on-screen overlays, etc.</source>
<translation>全屏通常提供最好的性能,但是无边框窗口可以更好地与 macOS Spaces、Alt+Tab、屏幕截图工具、屏幕覆盖等功能配合使用。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="599"/>
<source>V-Sync</source>
<translation>垂直同步</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="609"/>
<source>Disabling V-Sync allows sub-frame rendering latency, but it can display visible tearing</source>
<translation>禁用垂直同步可允许子帧渲染延迟,但会显示可见的撕裂</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="616"/>
<source>Frame pacing</source>
<translation>帧速调节</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="626"/>
<source>Frame pacing reduces micro-stutter by delaying frames that come in too early</source>
<translation>通过延迟过早进入的帧来减少卡顿</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="636"/>
<source>Audio Settings</source>
<translation>音频设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="646"/>
<source>Audio configuration</source>
<translation>音频配置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="671"/>
<source>Stereo</source>
<translation>立体声</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="675"/>
<source>5.1 surround sound</source>
<translation>5.1 环绕声</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="679"/>
<source>7.1 surround sound</source>
<translation>7.1 环绕声</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="693"/>
<source>Mute host PC speakers while streaming</source>
<translation>流式传输启动时将目标计算机的扬声器静音</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="703"/>
<source>You must restart any game currently in progress for this setting to take effect</source>
<translation>要使此设置生效,必须重新启动当前正在进行的所有游戏</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="709"/>
<source>Mute audio stream when Moonlight is not the active window</source>
<translation>当 Moonlight 失去焦点时静音</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="720"/>
<source>Mutes Moonlight&apos;s audio when you Alt+Tab out of the stream or click on a different window.</source>
<translation>当你用 Alt+Tab 键离开流式传输或者点击其他窗口时,Moonlight 将静音。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="729"/>
<source>UI Settings</source>
<translation>界面设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="739"/>
<source>Language</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="765"/>
<source>Automatic</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="792"/>
<source>You must restart Moonlight for this change to take effect</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="806"/>
<source>GUI display mode</source>
<translation>窗口显示模式</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="843"/>
<source>Maximized</source>
<translation>最大化</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="860"/>
<source>Show connection quality warnings</source>
<translation>显示连接质量警告</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="872"/>
<source>Discord Rich Presence integration</source>
<translation>Discord Rich Presence 集成</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="882"/>
<source>Updates your Discord status to display the name of the game you&apos;re streaming.</source>
<translation>更新你的 Discord 状态,以显示正在运行的游戏名称。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="900"/>
<source>Input Settings</source>
<translation>输入设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="911"/>
<source>Optimize mouse for remote desktop instead of games</source>
<translation>为远程桌面而不是游戏优化鼠标</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="922"/>
<source>This enables seamless mouse control without capturing the client&apos;s mouse cursor. It is ideal for remote desktop usage but will not work in most games.</source>
<translation>这可以实现无缝鼠标控制,而无需捕获客户端的鼠标光标。这是理想的远程桌面使用方案,但不会在大多数游戏中工作。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="923"/>
<source>You can toggle this while streaming using Ctrl+Alt+Shift+M.</source>
<translation>你可以使用 Ctrl+Alt+Shift+M 切换鼠标控制模式。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="924"/>
<source>NOTE: Due to a bug in GeForce Experience, this option may not work properly if your host PC has multiple monitors.</source>
<translation>注意:由于 GeForce Experience 的 Bug,如果你的目标计算机有多个显示器,则此选项可能无法正常工作。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="934"/>
<source>Capture system keyboard shortcuts</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="942"/>
<source>This enables the capture of system-wide keyboard shortcuts like Alt+Tab that would normally be handled by the client OS while streaming.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Capture system keyboard shortcuts while streaming in fullscreen</source>
<translation type="vanished">在全屏流式传输时捕获系统键盘快捷键</translation>
</message>
<message>
<source>This enables the capture of system-wide keyboard shortcuts like Alt+Tab that would normally be handled by the client OS while streaming in fullscreen.</source>
<translation type="vanished">这允许捕获系统范围内的键盘快捷键(如Alt+Tab),这些快捷键通常在全屏流式传输时由客户端操作系统处理。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="943"/>
<source>NOTE: Certain keyboard shortcuts like Ctrl+Alt+Del on Windows cannot be intercepted by any application, including Moonlight.</source>
<translation>注意:Windows 上的某些键盘快捷键(如 Ctrl+Alt+Del)不能被任何应用程序截获,包括 Moonlight。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="972"/>
<source>in fullscreen</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="976"/>
<source>always</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1006"/>
<source>Use touchscreen as a virtual trackpad</source>
<translation>将触摸屏用作虚拟触控板</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1016"/>
<source>When checked, the touchscreen acts like a trackpad. When unchecked, the touchscreen will directly control the mouse pointer.</source>
<translation>选中时,触摸屏就像一个触控板。取消选中时,触摸屏将直接控制鼠标指针。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1023"/>
<source>Swap left and right mouse buttons</source>
<translation>交换鼠标左键和右键</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1035"/>
<source>Reverse mouse scrolling direction</source>
<translation>反转鼠标滚动方向</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1049"/>
<source>Gamepad Settings</source>
<translation>手柄设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1059"/>
<source>Swap A/B and X/Y gamepad buttons</source>
<translation>交换手柄的 A/B 和 X/Y 按钮</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1077"/>
<source>This switches gamepads into a Nintendo-style button layout</source>
<translation>这会将手柄切换为任天堂风格的按钮布局</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1083"/>
<source>Force gamepad #1 always connected</source>
<translation>强制手柄 #1 始终连接</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1093"/>
<source>Forces a single gamepad to always stay connected to the host, even if no gamepads are actually connected to this PC.</source>
<translation>强制单个手柄始终与目标计算机保持连接,即使没有任何手柄实际连接到此计算机。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1094"/>
<source>Only enable this option when streaming a game that doesn&apos;t support gamepads being connected after startup.</source>
<translation>仅在游戏启动后不支持手柄连接时才启用此选项。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1101"/>
<source>Enable mouse control with gamepads by holding the &apos;Start&apos; button</source>
<translation>按住手柄的“Start”按钮启用鼠标控制</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1112"/>
<source>Process gamepad input when Moonlight is in the background</source>
<translation>当 Moonlight 在后台时处理手柄输入</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1123"/>
<source>Allows Moonlight to capture gamepad inputs even if it&apos;s not the current window in focus</source>
<translation>即使不是焦点窗口,也允许 Moonlight 捕获手柄的输入</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1132"/>
<source>Host Settings</source>
<translation>目标计算机设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1142"/>
<source>Optimize game settings for streaming</source>
<translation>优化游戏设置以进行流式传输</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1153"/>
<source>Quit app on host PC after ending stream</source>
<translation>流式传输结束后退出目标计算机上的应用程序</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1163"/>
<source>This will close the app or game you are streaming when you end your stream. You will lose any unsaved progress!</source>
<translation>当你结束流式传输时,这将关闭正在运行的应用程序或游戏。这会导致你失去所有未保存的游戏进度!</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1172"/>
<source>Advanced Settings</source>
<translation>高级设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1182"/>
<source>Video decoder</source>
<translation>视频解码器</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1207"/>
<location filename="../gui/SettingsView.qml" line="1253"/>
<source>Automatic (Recommended)</source>
<translation>自动 (推荐)</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1211"/>
<source>Force software decoding</source>
<translation>强制软件解码</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1215"/>
<source>Force hardware decoding</source>
<translation>强制硬件解码</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1228"/>
<source>Video codec</source>
<translation>视频编解码器</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1257"/>
<source>H.264</source>
<translation></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1261"/>
<source>HEVC (H.265)</source>
<translation></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1278"/>
<source>Unlock unsupported FPS options</source>
<translation>解锁不受支持的 FPS 选项</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1297"/>
<source>Automatically find PCs on the local network (Recommended)</source>
<translation>自动在本地网络上查找计算机 (推荐)</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1322"/>
<source>Automatically detect blocked connections (Recommended)</source>
<translation>自动检测被阻止的连接 (推荐)</translation>
</message>
</context>
<context>
<name>StreamSegue</name>
<message>
<location filename="../gui/StreamSegue.qml" line="11"/>
<source>Resuming %1...</source>
<translation>正在恢复 %1...</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="12"/>
<location filename="../gui/StreamSegue.qml" line="19"/>
<source>Starting %1...</source>
<translation>正在启动 %1...</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="25"/>
<source>Starting %1 failed: Error %2</source>
<translation>启动 %1 错误: 错误 %2</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="28"/>
<source>Check your firewall and port forwarding rules for port(s): %1</source>
<translation>检查防火墙和端口转发规则: %1</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="74"/>
<source>This PC&apos;s Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network.</source>
<translation>此计算机的互联网连接被阻塞。当连接到此网络时,互联网上的流式传输可能无法工作。</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="161"/>
<source>Tip:</source>
<translation>提示:</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="161"/>
<source>Press %1 to disconnect your session</source>
<translation>按 %1 来断开会话</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="162"/>
<source>Start+Select+L1+R1</source>
<translation></translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="162"/>
<source>Ctrl+Alt+Shift+Q</source>
<translation></translation>
</message>
</context>
<context>
<name>main</name>
<message>
<location filename="../gui/main.qml" line="254"/>
<location filename="../gui/main.qml" line="264"/>
<location filename="../gui/main.qml" line="393"/>
<location filename="../gui/main.qml" line="408"/>
<source>Settings</source>
<translation>设置</translation>
</message>
<message>
<location filename="../gui/main.qml" line="255"/>
<source>Version %1</source>
<translation>版本 %1</translation>
</message>
<message>
<location filename="../gui/main.qml" line="271"/>
<source>Join our community on Discord</source>
<translation>加入我们的社区</translation>
</message>
<message>
<location filename="../gui/main.qml" line="283"/>
<source>Computers</source>
<translation>计算机列表</translation>
</message>
<message>
<location filename="../gui/main.qml" line="290"/>
<source>Add PC manually</source>
<translation>手动添加计算机</translation>
</message>
<message>
<location filename="../gui/main.qml" line="330"/>
<source>Update available for Moonlight: Version %1</source>
<translation>Moonlight 可用更新:版本 %1</translation>
</message>
<message>
<location filename="../gui/main.qml" line="354"/>
<source>Help</source>
<translation>帮助</translation>
</message>
<message>
<location filename="../gui/main.qml" line="377"/>
<source>Gamepad Mapper</source>
<translation>手柄按键映射</translation>
</message>
<message>
<location filename="../gui/main.qml" line="381"/>
<source>Gamepad Mapping</source>
<translation>手柄按键映射</translation>
</message>
<message>
<location filename="../gui/main.qml" line="415"/>
<source>No functioning hardware accelerated H.264 video decoder was detected by Moonlight. Your streaming performance may be severely degraded in this configuration.</source>
<translation>Moonlight 未检测到功能正常的硬件加速 H.264 视频解码器。</translation>
</message>
<message>
<location filename="../gui/main.qml" line="417"/>
<source>Click the Help button for more information on solving this problem.</source>
<translation>单击帮助按钮以获取有关解决此问题的更多信息。</translation>
</message>
<message>
<location filename="../gui/main.qml" line="423"/>
<source>Hardware acceleration doesn&apos;t work on XWayland. Continuing on XWayland may result in poor streaming performance. Try running with QT_QPA_PLATFORM=wayland or switch to X11.</source>
<translation>硬件加速在 XWayland 上不起作用。在 XWayland 上继续操作可能会导致流式传输性能下降。</translation>
</message>
<message>
<location filename="../gui/main.qml" line="425"/>
<source>Click the Help button for more information.</source>
<translation>单击帮助按钮以获取更多信息。</translation>
</message>
<message>
<location filename="../gui/main.qml" line="432"/>
<source>This version of Moonlight isn&apos;t optimized for your PC. Please download the &apos;%1&apos; version of Moonlight for the best streaming performance.</source>
<translation>此版本的 Moonlight 尚未针对你的计算机优化。请下载“%1”版本的 Moonlight,以获得最佳的流式传输性能。</translation>
</message>
<message>
<location filename="../gui/main.qml" line="441"/>
<source>Moonlight detected gamepads without a mapping:</source>
<translation>Moonlight 检测到没有映射的手柄:</translation>
</message>
<message>
<location filename="../gui/main.qml" line="443"/>
<source>Click the Help button for information on how to map your gamepads.</source>
<translation>单击帮助按钮以获取有关如何映射手柄的信息。</translation>
</message>
<message>
<location filename="../gui/main.qml" line="451"/>
<source>Are you sure you want to quit?</source>
<translation>确定要退出吗?</translation>
</message>
<message>
<location filename="../gui/main.qml" line="481"/>
<source>Enter the IP address of your GameStream PC:</source>
<translation>输入目标计算机的 IP 地址:</translation>
</message>
</context>
</TS>
+23 -14
View File
@@ -11,7 +11,6 @@
#include <QCursor>
#include <QElapsedTimer>
#include <QFile>
#include<QTranslator>
// Don't let SDL hook our main function, since Qt is already
// doing the same thing. This needs to be before any headers
@@ -351,14 +350,16 @@ int main(int argc, char *argv[])
}
#endif
#if defined(Q_OS_DARWIN) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (!qEnvironmentVariableIsSet("QSG_RHI_BACKEND")) {
// The Metal backend in Qt 6.0.0 causes really strange issues transitioning to
// full-screen in our SDL window (hangs in SDL's Cocoa_SetWindowFullscreenSpace())
// and breaks drawing our status updates in the StreamSegue, so use OpenGL like
// Qt 5 does until we figure out the cause of these issues.
qputenv("QSG_RHI_BACKEND", "opengl");
}
#if !defined(Q_OS_WIN32) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
// Moonlight requires the non-threaded renderer because we depend
// on being able to control the render thread by blocking in the
// main thread (and pumping events from the main thread when needed).
// That doesn't work with the threaded renderer which causes all
// sorts of odd behavior depending on the platform.
//
// NB: Windows defaults to the "windows" non-threaded render loop on
// Qt 5 and the threaded render loop on Qt 6.
qputenv("QSG_RENDER_LOOP", "basic");
#endif
// We don't want system proxies to apply to us
@@ -394,6 +395,9 @@ int main(int argc, char *argv[])
// future KMSDRM platforms that use SDL for rendering.
SDL_SetHint(SDL_HINT_VIDEO_DOUBLE_BUFFER, "1");
// We use MMAL to render on Raspberry Pi, so we do not require DRM master.
SDL_SetHint("SDL_KMSDRM_REQUIRE_DRM_MASTER", "0");
// Use Direct3D 9Ex to avoid a deadlock caused by the D3D device being reset when
// the user triggers a UAC prompt. This option controls the software/SDL renderer.
// The DXVA2 renderer uses Direct3D 9Ex itself directly.
@@ -451,9 +455,9 @@ int main(int argc, char *argv[])
QGuiApplication app(argc, argv);
QTranslator translator;
qDebug() << "Translation loaded:" << translator.load(QString(":/languages/qml_") + QLocale::system().name());
app.installTranslator(&translator);
// Apply the initial translation based on user preference
StreamingPreferences prefs;
prefs.retranslate();
// After the QGuiApplication is created, the platform stuff will be initialized
// and we can set the SDL video driver to match Qt.
@@ -499,6 +503,11 @@ int main(int argc, char *argv[])
app.setWindowIcon(QIcon(":/res/moonlight.svg"));
#endif
// This is necessary to show our icon correctly on Wayland
app.setDesktopFileName("com.moonlight_stream.Moonlight.desktop");
qputenv("SDL_VIDEO_WAYLAND_WMCLASS", "com.moonlight_stream.Moonlight");
qputenv("SDL_VIDEO_X11_WMCLASS", "com.moonlight_stream.Moonlight");
// Register our C++ types for QML
qmlRegisterType<ComputerModel>("ComputerModel", 1, 0, "ComputerModel");
qmlRegisterType<AppModel>("AppModel", 1, 0, "AppModel");
@@ -525,8 +534,8 @@ int main(int argc, char *argv[])
});
qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0,
"StreamingPreferences",
[](QQmlEngine*, QJSEngine*) -> QObject* {
return new StreamingPreferences();
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
return new StreamingPreferences(qmlEngine);
});
// Create the identity manager on the main thread
+146
View File
@@ -0,0 +1,146 @@
// This file contains hooks for several functions that allow
// Qt and SDL to more or less share DRM master ownership.
//
// This technique requires Linux v5.8 or later, or for Moonlight
// to run as root (with CAP_SYS_ADMIN). Prior to Linux v5.8,
// DRM_IOCTL_DROP_MASTER required CAP_SYS_ADMIN, which prevents
// our trick from working (without root, that is).
//
// The specific kernel change required to run without root is:
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=45bc3d26c95a8fc63a7d8668ca9e57ef0883351c
#define _GNU_SOURCE
#include <SDL.h>
#include <dlfcn.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
// We require SDL 2.0.15+ to hook because it supports sharing
// the DRM FD with our code. This avoids having multiple DRM FDs
// in flight at the same time which would significantly complicate
// the logic here because we'd need to figure out exactly which FD
// should be the master at any given time. With the position of our
// hooks, that is definitely not trivial.
#if SDL_VERSION_ATLEAST(2, 0, 15)
// Qt's DRM master FD grabbed by our hook
int g_QtDrmMasterFd = -1;
struct stat64 g_DrmMasterStat;
// The DRM master FD created for SDL
int g_SdlDrmMasterFd = -1;
// This hook will handle legacy DRM rendering
int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
uint32_t x, uint32_t y, uint32_t *connectors, int count,
drmModeModeInfoPtr mode)
{
// Grab the first DRM FD that makes it in here. This will be the Qt
// EGLFS backend's DRM FD, which we will need later.
if (g_QtDrmMasterFd == -1) {
g_QtDrmMasterFd = fd;
fstat64(g_QtDrmMasterFd, &g_DrmMasterStat);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Captured Qt EGLFS DRM master fd (legacy): %d",
g_QtDrmMasterFd);
}
// Call into the real thing
return ((typeof(drmModeSetCrtc)*)dlsym(RTLD_NEXT, "drmModeSetCrtc"))(fd, crtcId, bufferId, x, y, connectors, count, mode);
}
// This hook will handle atomic DRM rendering
int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req,
uint32_t flags, void *user_data)
{
// Grab the first DRM FD that makes it in here. This will be the Qt
// EGLFS backend's DRM FD, which we will need later.
if (g_QtDrmMasterFd == -1) {
g_QtDrmMasterFd = fd;
fstat64(g_QtDrmMasterFd, &g_DrmMasterStat);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Captured Qt EGLFS DRM master fd (atomic): %d",
g_QtDrmMasterFd);
}
// Call into the real thing
return ((typeof(drmModeAtomicCommit)*)dlsym(RTLD_NEXT, "drmModeAtomicCommit"))(fd, req, flags, user_data);
}
// This hook will handle SDL's open() on the DRM device. We just need to
// hook this variant of open(), since that's what SDL uses. When we see
// the open a FD for the same card as the Qt DRM master FD, we'll drop
// master on the Qt FD to allow the new FD to have master.
int open(const char *pathname, int flags)
{
// Call the real thing to do the open operation
int fd = ((typeof(open)*)dlsym(RTLD_NEXT, "open"))(pathname, flags);
// If the file was successfully opened and we have a DRM master FD,
// check if the FD we just opened is a DRM device.
if (fd >= 0 && g_QtDrmMasterFd != -1) {
if (strncmp(pathname, "/dev/dri/card", 13) == 0) {
// It's a DRM device, but is it _our_ DRM device?
struct stat64 fdstat;
fstat64(fd, &fdstat);
if (g_DrmMasterStat.st_dev == fdstat.st_dev &&
g_DrmMasterStat.st_ino == fdstat.st_ino) {
// It is our device. Time to do the magic!
// This code assumes SDL only ever opens a single FD
// for a given DRM device.
SDL_assert(g_SdlDrmMasterFd == -1);
// Drop master on Qt's FD so we can pick it up for SDL.
if (drmDropMaster(g_QtDrmMasterFd) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to drop master on Qt DRM FD: %d",
errno);
// Hope for the best
return fd;
}
// We are not allowed to call drmSetMaster() without CAP_SYS_ADMIN,
// but since we just dropped the master, we can become master by
// simply creating a new FD. Let's do it.
close(fd);
fd = ((typeof(open)*)dlsym(RTLD_NEXT, "open"))(pathname, flags);
g_SdlDrmMasterFd = fd;
}
}
}
return fd;
}
// Our close() hook handles restoring DRM master to the Qt FD
// after SDL closes its DRM FD.
int close(int fd)
{
// Call the real thing
int ret = ((typeof(close)*)dlsym(RTLD_NEXT, "close"))(fd);
if (ret == 0) {
// If we just closed the SDL DRM master FD, restore master
// to the Qt DRM FD. This works because the Qt DRM master FD
// was master once before, so we can set it as master again
// using drmSetMaster() without CAP_SYS_ADMIN.
if (g_SdlDrmMasterFd != -1 && fd == g_SdlDrmMasterFd) {
if (drmSetMaster(g_QtDrmMasterFd) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to restore master to Qt DRM FD: %d",
errno);
}
g_SdlDrmMasterFd = -1;
}
}
return ret;
}
#endif
+4
View File
@@ -17,8 +17,12 @@
<file>res/baseline-error_outline-24px.svg</file>
<file>res/baseline-check_circle_outline-24px.svg</file>
<file>res/Discord-Logo-White.svg</file>
<file>languages/qml_de.ts</file>
<file>languages/qml_de.qm</file>
<file>languages/qml_fr.ts</file>
<file>languages/qml_fr.qm</file>
<file>languages/qml_zh_cn.ts</file>
<file>languages/qml_zh_cn.qm</file>
</qresource>
<qresource prefix="/data">
<file alias="gamecontrollerdb.txt">SDL_GameControllerDB/gamecontrollerdb.txt</file>
+2 -2
View File
@@ -13,8 +13,8 @@ MappingFetcher::MappingFetcher(QObject *parent) :
// Allow HTTP redirects
m_Nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
connect(&m_Nam, SIGNAL(finished(QNetworkReply*)),
this, SLOT(handleMappingListFetched(QNetworkReply*)));
connect(&m_Nam, &QNetworkAccessManager::finished,
this, &MappingFetcher::handleMappingListFetched);
}
void MappingFetcher::start()
+1 -1
View File
@@ -41,7 +41,7 @@ MappingManager::MappingManager()
#else
.split('\n', QString::SkipEmptyParts);
#endif
for (QString sdlMapping : sdlMappings) {
for (const QString& sdlMapping : sdlMappings) {
SdlGamepadMapping mapping(sdlMapping);
addMapping(mapping);
}
+91 -3
View File
@@ -1,6 +1,11 @@
#include "streamingpreferences.h"
#include <QSettings>
#include <QTranslator>
#include <QCoreApplication>
#include <QLocale>
#include <QtDebug>
#define SER_STREAMSETTINGS "streamsettings"
#define SER_WIDTH "width"
@@ -36,11 +41,20 @@
#define SER_REVERSESCROLL "reversescroll"
#define SER_SWAPFACEBUTTONS "swapfacebuttons"
#define SER_CAPTURESYSKEYS "capturesyskeys"
#define SER_LANGUAGE "language"
#define CURRENT_DEFAULT_VER 1
StreamingPreferences::StreamingPreferences(QObject *parent)
: QObject(parent)
: QObject(parent),
m_QmlEngine(nullptr)
{
reload();
}
StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent)
: QObject(parent),
m_QmlEngine(qmlEngine)
{
reload();
}
@@ -81,7 +95,8 @@ void StreamingPreferences::reload()
backgroundGamepad = settings.value(SER_BACKGROUNDGAMEPAD, false).toBool();
reverseScrollDirection = settings.value(SER_REVERSESCROLL, false).toBool();
swapFaceButtons = settings.value(SER_SWAPFACEBUTTONS, false).toBool();
captureSysKeys = settings.value(SER_CAPTURESYSKEYS, false).toBool();
captureSysKeysMode = static_cast<CaptureSysKeysMode>(settings.value(SER_CAPTURESYSKEYS,
static_cast<int>(CaptureSysKeysMode::CSK_OFF)).toInt());
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
static_cast<int>(AudioConfig::AC_STEREO)).toInt());
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
@@ -95,6 +110,8 @@ void StreamingPreferences::reload()
uiDisplayMode = static_cast<UIDisplayMode>(settings.value(SER_UIDISPLAYMODE,
static_cast<int>(settings.value(SER_STARTWINDOWED, true).toBool() ? UIDisplayMode::UI_WINDOWED
: UIDisplayMode::UI_MAXIMIZED)).toInt());
language = static_cast<Language>(settings.value(SER_LANGUAGE,
static_cast<int>(Language::LANG_AUTO)).toInt());
// Perform default settings updates as required based on last default version
@@ -108,6 +125,76 @@ void StreamingPreferences::reload()
}
}
bool StreamingPreferences::retranslate()
{
static QTranslator* translator = nullptr;
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
if (m_QmlEngine != nullptr) {
// Dynamic retranslation is not supported until Qt 5.10
return false;
}
#endif
QTranslator* newTranslator = new QTranslator();
QString languageSuffix = getSuffixFromLanguage(language);
// Remove the old translator, even if we can't load a new one.
// Otherwise we'll be stuck with the old translated values instead
// of defaulting to English.
if (translator != nullptr) {
QCoreApplication::removeTranslator(translator);
delete translator;
translator = nullptr;
}
if (newTranslator->load(QString(":/languages/qml_") + languageSuffix)) {
qInfo() << "Successfully loaded translation for " << languageSuffix;
translator = newTranslator;
QCoreApplication::installTranslator(translator);
}
else {
qInfo() << "No translation available for " << languageSuffix;
delete newTranslator;
}
if (m_QmlEngine != nullptr) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
// This is a dynamic retranslation from the settings page.
// We have to kick the QML engine into reloading our text.
m_QmlEngine->retranslate();
#else
// Unreachable below Qt 5.10 due to the check above
Q_ASSERT(false);
#endif
}
else {
// This is a translation from a non-QML context, which means
// it is probably app startup. There's nothing to refresh.
}
return true;
}
QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Language lang)
{
switch (lang)
{
case LANG_DE:
return "de";
case LANG_EN:
return "en";
case LANG_FR:
return "fr";
case LANG_ZH_CN:
return "zh_cn";
case LANG_AUTO:
default:
return QLocale::system().name();
}
}
void StreamingPreferences::save()
{
QSettings settings;
@@ -136,13 +223,14 @@ void StreamingPreferences::save()
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
settings.setValue(SER_UIDISPLAYMODE, static_cast<int>(uiDisplayMode));
settings.setValue(SER_LANGUAGE, static_cast<int>(language));
settings.setValue(SER_DEFAULTVER, CURRENT_DEFAULT_VER);
settings.setValue(SER_SWAPMOUSEBUTTONS, swapMouseButtons);
settings.setValue(SER_MUTEONFOCUSLOSS, muteOnFocusLoss);
settings.setValue(SER_BACKGROUNDGAMEPAD, backgroundGamepad);
settings.setValue(SER_REVERSESCROLL, reverseScrollDirection);
settings.setValue(SER_SWAPFACEBUTTONS, swapFaceButtons);
settings.setValue(SER_CAPTURESYSKEYS, captureSysKeys);
settings.setValue(SER_CAPTURESYSKEYS, captureSysKeysMode);
}
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
+36 -3
View File
@@ -2,6 +2,7 @@
#include <QObject>
#include <QRect>
#include <QQmlEngine>
class StreamingPreferences : public QObject
{
@@ -9,6 +10,7 @@ class StreamingPreferences : public QObject
public:
StreamingPreferences(QObject *parent = nullptr);
StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent = nullptr);
Q_INVOKABLE static int
getDefaultBitrate(int width, int height, int fps);
@@ -58,6 +60,27 @@ public:
};
Q_ENUM(UIDisplayMode)
// New entries must go at the end of the enum
// to avoid renumbering existing entries (which
// would affect existing user preferences).
enum Language
{
LANG_AUTO,
LANG_EN,
LANG_FR,
LANG_ZH_CN,
LANG_DE
};
Q_ENUM(Language);
enum CaptureSysKeysMode
{
CSK_OFF,
CSK_FULLSCREEN,
CSK_ALWAYS,
};
Q_ENUM(CaptureSysKeysMode);
Q_PROPERTY(int width MEMBER width NOTIFY displayModeChanged)
Q_PROPERTY(int height MEMBER height NOTIFY displayModeChanged)
Q_PROPERTY(int fps MEMBER fps NOTIFY displayModeChanged)
@@ -87,7 +110,10 @@ public:
Q_PROPERTY(bool backgroundGamepad MEMBER backgroundGamepad NOTIFY backgroundGamepadChanged)
Q_PROPERTY(bool reverseScrollDirection MEMBER reverseScrollDirection NOTIFY reverseScrollDirectionChanged)
Q_PROPERTY(bool swapFaceButtons MEMBER swapFaceButtons NOTIFY swapFaceButtonsChanged)
Q_PROPERTY(bool captureSysKeys MEMBER captureSysKeys NOTIFY captureSysKeysChanged)
Q_PROPERTY(CaptureSysKeysMode captureSysKeysMode MEMBER captureSysKeysMode NOTIFY captureSysKeysModeChanged)
Q_PROPERTY(Language language MEMBER language NOTIFY languageChanged);
Q_INVOKABLE bool retranslate();
// Directly accessible members for preferences
int width;
@@ -113,7 +139,6 @@ public:
bool backgroundGamepad;
bool reverseScrollDirection;
bool swapFaceButtons;
bool captureSysKeys;
int packetSize;
AudioConfig audioConfig;
VideoCodecConfig videoCodecConfig;
@@ -121,6 +146,8 @@ public:
WindowMode windowMode;
WindowMode recommendedFullScreenMode;
UIDisplayMode uiDisplayMode;
Language language;
CaptureSysKeysMode captureSysKeysMode;
signals:
void displayModeChanged();
@@ -149,6 +176,12 @@ signals:
void backgroundGamepadChanged();
void reverseScrollDirectionChanged();
void swapFaceButtonsChanged();
void captureSysKeysChanged();
void captureSysKeysModeChanged();
void languageChanged();
private:
QString getSuffixFromLanguage(Language lang);
QQmlEngine* m_QmlEngine;
};
-96
View File
@@ -1,96 +0,0 @@
#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;
}
}
-24
View File
@@ -1,24 +0,0 @@
#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;
};
+190
View File
@@ -0,0 +1,190 @@
#include "input.h"
#include <QString>
int SdlInputHandler::clipboardThreadProc(void *ptr)
{
auto me = (SdlInputHandler*)ptr;
while (!SDL_AtomicGet(&me->m_ShutdownClipboardThread)) {
QString clipboardData;
SDL_LockMutex(me->m_ClipboardLock);
for (;;) {
clipboardData = me->m_ClipboardData;
me->m_ClipboardData.clear();
if (!clipboardData.isEmpty() || SDL_AtomicGet(&me->m_ShutdownClipboardThread)) {
break;
}
SDL_CondWait(me->m_ClipboardHasData, me->m_ClipboardLock);
}
SDL_UnlockMutex(me->m_ClipboardLock);
// We might get here on shutdown, so don't send text if there's
// nothing to send.
if (!clipboardData.isEmpty()) {
me->sendText(clipboardData);
}
}
return 0;
}
#define MAP_KEY(c, sc) \
case c: \
event.key.keysym.scancode = sc; \
break
#define MAP_KEY_SHIFT(c, sc) \
case c: \
event.key.keysym.scancode = sc; \
event.key.keysym.mod = KMOD_SHIFT; \
break
void SdlInputHandler::sendText(QString& string)
{
for (int i = 0; i < string.size(); i++) {
char16_t c = string[i].unicode();
SDL_Event event = {};
// If we're sending a very long string, we might get a termination request
// while we're in the middle of sending a string. In that case, just bail.
if (SDL_AtomicGet(&m_ShutdownClipboardThread)) {
return;
}
if (c >= 'A' && c <= 'Z') {
event.key.keysym.scancode = (SDL_Scancode)((c - 'A') + SDL_SCANCODE_A);
event.key.keysym.mod = KMOD_SHIFT;
}
else if (c >= 'a' && c <= 'z') {
event.key.keysym.scancode = (SDL_Scancode)((c - 'a') + SDL_SCANCODE_A);
}
else if (c >= '1' && c <= '9') {
event.key.keysym.scancode = (SDL_Scancode)((c - '1') + SDL_SCANCODE_1);
}
else {
// Fixup some Unicode characters to be expressible in ASCII
switch (c) {
case u'\U0000201C': // Left "
case u'\U0000201D': // Right "
c = '"';
break;
case u'\U00002018': // Left '
case u'\U00002019': // Right '
c = '\'';
break;
case u'\U00002013': // Smart -
c = '-';
break;
case u'\U00002026': // Ellipsis (...)
// Convert this character into 3 periods
string.insert(i+1, '.');
string.insert(i+1, '.');
c = '.';
break;
case u'\U000000A0': // Non-breaking space
c = ' ';
break;
default:
// Nothing
break;
}
switch (c) {
// Handle CRLF separately to avoid duplicate newlines
case '\r':
if (string[i + 1] == '\n') {
i++;
}
event.key.keysym.scancode = SDL_SCANCODE_RETURN;
break;
MAP_KEY('\b', SDL_SCANCODE_BACKSPACE);
MAP_KEY('\n', SDL_SCANCODE_RETURN);
MAP_KEY('\t', SDL_SCANCODE_TAB);
MAP_KEY(' ', SDL_SCANCODE_SPACE);
MAP_KEY_SHIFT('!', SDL_SCANCODE_1);
MAP_KEY_SHIFT('"', SDL_SCANCODE_APOSTROPHE);
MAP_KEY_SHIFT('#', SDL_SCANCODE_3);
MAP_KEY_SHIFT('$', SDL_SCANCODE_4);
MAP_KEY_SHIFT('%', SDL_SCANCODE_5);
MAP_KEY_SHIFT('&', SDL_SCANCODE_7);
MAP_KEY('\'', SDL_SCANCODE_APOSTROPHE);
MAP_KEY_SHIFT('(', SDL_SCANCODE_9);
MAP_KEY_SHIFT(')', SDL_SCANCODE_0);
MAP_KEY_SHIFT('*', SDL_SCANCODE_8);
MAP_KEY_SHIFT('+', SDL_SCANCODE_EQUALS);
MAP_KEY(',', SDL_SCANCODE_COMMA);
MAP_KEY('-', SDL_SCANCODE_MINUS);
MAP_KEY('.', SDL_SCANCODE_PERIOD);
MAP_KEY('/', SDL_SCANCODE_SLASH);
MAP_KEY('0', SDL_SCANCODE_0);
MAP_KEY_SHIFT(':', SDL_SCANCODE_SEMICOLON);
MAP_KEY(';', SDL_SCANCODE_SEMICOLON);
MAP_KEY_SHIFT('<', SDL_SCANCODE_COMMA);
MAP_KEY('=', SDL_SCANCODE_EQUALS);
MAP_KEY_SHIFT('>', SDL_SCANCODE_PERIOD);
MAP_KEY_SHIFT('?', SDL_SCANCODE_SLASH);
MAP_KEY_SHIFT('@', SDL_SCANCODE_2);
MAP_KEY('[', SDL_SCANCODE_LEFTBRACKET);
MAP_KEY('\\', SDL_SCANCODE_BACKSLASH);
MAP_KEY(']', SDL_SCANCODE_RIGHTBRACKET);
MAP_KEY_SHIFT('^', SDL_SCANCODE_6);
MAP_KEY_SHIFT('_', SDL_SCANCODE_MINUS);
MAP_KEY('`', SDL_SCANCODE_GRAVE);
MAP_KEY_SHIFT('{', SDL_SCANCODE_LEFTBRACKET);
MAP_KEY_SHIFT('|', SDL_SCANCODE_BACKSLASH);
MAP_KEY_SHIFT('}', SDL_SCANCODE_RIGHTBRACKET);
MAP_KEY_SHIFT('~', SDL_SCANCODE_GRAVE);
default:
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Pasting text - non-ASCII character value 'U+%x' ignored",
c);
continue;
}
}
if (event.key.keysym.mod & KMOD_SHIFT) {
SDL_Event modifierEvent = {};
modifierEvent.type = SDL_KEYDOWN;
modifierEvent.key.state = SDL_PRESSED;
modifierEvent.key.keysym.scancode = SDL_SCANCODE_LSHIFT;
handleKeyEvent(&modifierEvent.key);
SDL_Delay(10);
}
event.type = SDL_KEYDOWN;
event.key.state = SDL_PRESSED;
handleKeyEvent(&event.key);
SDL_Delay(10);
event.type = SDL_KEYUP;
event.key.state = SDL_RELEASED;
handleKeyEvent(&event.key);
if (event.key.keysym.mod & KMOD_SHIFT) {
SDL_Event modifierEvent = {};
modifierEvent.type = SDL_KEYUP;
modifierEvent.key.state = SDL_RELEASED;
modifierEvent.key.keysym.scancode = SDL_SCANCODE_LSHIFT;
handleKeyEvent(&modifierEvent.key);
SDL_Delay(10);
}
}
}
+16 -1
View File
@@ -269,7 +269,22 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
event.quit.timestamp = SDL_GetTicks();
SDL_PushEvent(&event);
// Clear buttons down on this gameapd
// Clear buttons down on this gamepad
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
return;
}
// Handle Select+L1+R1+X as a gamepad overlay combo
if (state->buttons == (BACK_FLAG | LB_FLAG | RB_FLAG | X_FLAG)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected stats toggle gamepad combo");
// Toggle the stats overlay
Session::get()->getOverlayManager().setOverlayState(Overlay::OverlayDebug,
!Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebug));
// Clear buttons down on this gamepad
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
return;
+190 -199
View File
@@ -7,13 +7,10 @@
#include <QtGlobal>
#include <QDir>
#include <QGuiApplication>
#define MOUSE_POLLING_INTERVAL 5
#ifdef Q_OS_WIN32
HHOOK g_KeyboardHook;
#endif
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int streamWidth, int streamHeight)
: m_MultiController(prefs.multiController),
m_GamepadMouse(prefs.gamepadMouse),
@@ -25,19 +22,25 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
m_MouseWasInVideoRegion(false),
m_PendingMouseButtonsAllUpOnVideoRegionLeave(false),
m_FakeCaptureActive(false),
m_CaptureSystemKeysEnabled(prefs.captureSysKeys || !WMUtils::isRunningWindowManager()),
m_CaptureSystemKeysMode(prefs.captureSysKeysMode),
m_MouseCursorCapturedVisibilityState(SDL_DISABLE),
m_LongPressTimer(0),
m_StreamWidth(streamWidth),
m_StreamHeight(streamHeight),
m_AbsoluteMouseMode(prefs.absoluteMouseMode),
m_AbsoluteTouchMode(prefs.absoluteTouchMode),
m_PendingMouseLeaveButtonUp(0),
m_LeftButtonReleaseTimer(0),
m_RightButtonReleaseTimer(0),
m_DragTimer(0),
m_DragButton(0),
m_NumFingersDown(0)
m_NumFingersDown(0),
m_ClipboardData()
{
// System keys are always captured when running without a DE
if (!WMUtils::isRunningDesktopEnvironment()) {
m_CaptureSystemKeysMode = StreamingPreferences::CSK_ALWAYS;
}
// Allow gamepad input when the app doesn't have focus if requested
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, prefs.backgroundGamepad ? "1" : "0");
@@ -49,10 +52,15 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
prefs.absoluteMouseMode ? "1" : "0",
SDL_HINT_OVERRIDE);
// If we're grabbing system keys, enable the X11 keyboard grab in SDL
#if !SDL_VERSION_ATLEAST(2, 0, 15)
// For older versions of SDL (2.0.14 and earlier), use SDL_HINT_GRAB_KEYBOARD
SDL_SetHintWithPriority(SDL_HINT_GRAB_KEYBOARD,
m_CaptureSystemKeysEnabled ? "1" : "0",
m_CaptureSystemKeysMode != StreamingPreferences::CSK_OFF ? "1" : "0",
SDL_HINT_OVERRIDE);
#endif
// Opt-out of SDL's built-in Alt+Tab handling while keyboard grab is enabled
SDL_SetHint("SDL_ALLOW_ALT_TAB_WHILE_GRABBED", "0");
// Allow clicks to pass through to us when focusing the window. If we're in
// absolute mouse mode, this will avoid the user having to click twice to
@@ -67,6 +75,47 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
SDL_SetHint("SDL_JOYSTICK_HIDAPI_PS4_RUMBLE", "1");
SDL_SetHint("SDL_JOYSTICK_HIDAPI_PS5_RUMBLE", "1");
// Populate special key combo configuration
m_SpecialKeyCombos[KeyComboQuit].keyCombo = KeyComboQuit;
m_SpecialKeyCombos[KeyComboQuit].keyCode = SDLK_q;
m_SpecialKeyCombos[KeyComboQuit].scanCode = SDL_SCANCODE_Q;
m_SpecialKeyCombos[KeyComboQuit].enabled = true;
m_SpecialKeyCombos[KeyComboUngrabInput].keyCombo = KeyComboUngrabInput;
m_SpecialKeyCombos[KeyComboUngrabInput].keyCode = SDLK_z;
m_SpecialKeyCombos[KeyComboUngrabInput].scanCode = SDL_SCANCODE_Z;
m_SpecialKeyCombos[KeyComboUngrabInput].enabled = QGuiApplication::platformName() != "eglfs";
m_SpecialKeyCombos[KeyComboToggleFullScreen].keyCombo = KeyComboToggleFullScreen;
m_SpecialKeyCombos[KeyComboToggleFullScreen].keyCode = SDLK_x;
m_SpecialKeyCombos[KeyComboToggleFullScreen].scanCode = SDL_SCANCODE_X;
m_SpecialKeyCombos[KeyComboToggleFullScreen].enabled = QGuiApplication::platformName() != "eglfs";
m_SpecialKeyCombos[KeyComboToggleStatsOverlay].keyCombo = KeyComboToggleStatsOverlay;
m_SpecialKeyCombos[KeyComboToggleStatsOverlay].keyCode = SDLK_s;
m_SpecialKeyCombos[KeyComboToggleStatsOverlay].scanCode = SDL_SCANCODE_S;
m_SpecialKeyCombos[KeyComboToggleStatsOverlay].enabled = true;
m_SpecialKeyCombos[KeyComboToggleMouseMode].keyCombo = KeyComboToggleMouseMode;
m_SpecialKeyCombos[KeyComboToggleMouseMode].keyCode = SDLK_m;
m_SpecialKeyCombos[KeyComboToggleMouseMode].scanCode = SDL_SCANCODE_M;
m_SpecialKeyCombos[KeyComboToggleMouseMode].enabled = true;
m_SpecialKeyCombos[KeyComboToggleCursorHide].keyCombo = KeyComboToggleCursorHide;
m_SpecialKeyCombos[KeyComboToggleCursorHide].keyCode = SDLK_c;
m_SpecialKeyCombos[KeyComboToggleCursorHide].scanCode = SDL_SCANCODE_C;
m_SpecialKeyCombos[KeyComboToggleCursorHide].enabled = true;
m_SpecialKeyCombos[KeyComboToggleMinimize].keyCombo = KeyComboToggleMinimize;
m_SpecialKeyCombos[KeyComboToggleMinimize].keyCode = SDLK_d;
m_SpecialKeyCombos[KeyComboToggleMinimize].scanCode = SDL_SCANCODE_D;
m_SpecialKeyCombos[KeyComboToggleMinimize].enabled = QGuiApplication::platformName() != "eglfs";
m_SpecialKeyCombos[KeyComboPasteText].keyCombo = KeyComboPasteText;
m_SpecialKeyCombos[KeyComboPasteText].keyCode = SDLK_v;
m_SpecialKeyCombos[KeyComboPasteText].scanCode = SDL_SCANCODE_V;
m_SpecialKeyCombos[KeyComboPasteText].enabled = true;
m_OldIgnoreDevices = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES);
m_OldIgnoreDevicesExcept = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT);
@@ -123,6 +172,10 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
}
#endif
#ifdef Q_OS_DARWIN
CGSGetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), &m_OldHotKeyMode);
#endif
// Initialize the gamepad mask with currently attached gamepads to avoid
// causing gamepads to unexpectedly disappear and reappear on the host
// during stream startup as we detect currently attached gamepads one at a time.
@@ -150,37 +203,16 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
m_MouseMoveTimer = SDL_AddTimer(pollingInterval, SdlInputHandler::mouseMoveTimerCallback, this);
#ifdef Q_OS_WIN32
if (m_CaptureSystemKeysEnabled) {
// If system key capture is enabled, install the window hook required to intercept
// these key presses and block them from the OS itself.
g_KeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, SdlInputHandler::keyboardHookProc, GetModuleHandle(NULL), 0);
}
#endif
// Initialize state used by the clipboard thread before we start it
SDL_AtomicSet(&m_ShutdownClipboardThread, 0);
m_ClipboardHasData = SDL_CreateCond();
m_ClipboardLock = SDL_CreateMutex();
m_ClipboardThread = SDL_CreateThread(SdlInputHandler::clipboardThreadProc, "Clipboard Sender", this);
}
SdlInputHandler::~SdlInputHandler()
{
#ifdef Q_OS_WIN32
if (g_KeyboardHook != nullptr) {
UnhookWindowsHookEx(g_KeyboardHook);
g_KeyboardHook = nullptr;
// If we're terminating because of the user pressing the quit combo,
// we won't have a chance to inform SDL as the modifier keys raise.
// This will leave stale modifier flags in the SDL_Keyboard global
// inside SDL which will show up on our next key event if the user
// streams again. Avoid this by explicitly zeroing mod state when
// ending the stream.
//
// This is only needed for the case where we're hooking the keyboard
// because we're generating synthetic SDL_KEYDOWN/SDL_KEYUP events
// which don't properly maintain SDL's internal keyboard state,
// so we're forced to invoke SDL_SetModState() ourselves to set mods.
SDL_SetModState(KMOD_NONE);
}
#endif
for (int i = 0; i < MAX_GAMEPADS; i++) {
if (m_GamepadState[i].mouseEmulationTimer != 0) {
Session::get()->notifyMouseEmulationMode(false);
@@ -202,6 +234,21 @@ SdlInputHandler::~SdlInputHandler()
SDL_RemoveTimer(m_RightButtonReleaseTimer);
SDL_RemoveTimer(m_DragTimer);
// Wake up the clipboard thread to terminate it
SDL_AtomicSet(&m_ShutdownClipboardThread, 1);
SDL_CondBroadcast(m_ClipboardHasData);
// Wait for it to terminate
SDL_WaitThread(m_ClipboardThread, nullptr);
// Now we can safely clean up its resources
SDL_DestroyCond(m_ClipboardHasData);
SDL_DestroyMutex(m_ClipboardLock);
#ifdef Q_OS_DARWIN
CGSSetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), m_OldHotKeyMode);
#endif
#if !SDL_VERSION_ATLEAST(2, 0, 9)
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
@@ -234,157 +281,6 @@ void SdlInputHandler::setWindow(SDL_Window *window)
m_Window = window;
}
#ifdef Q_OS_WIN32
LRESULT CALLBACK SdlInputHandler::keyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode < 0 || nCode != HC_ACTION) {
return CallNextHookEx(g_KeyboardHook, nCode, wParam, lParam);
}
bool keyCaptureActive;
SDL_Event event = {};
KBDLLHOOKSTRUCT* hookData = (KBDLLHOOKSTRUCT*)lParam;
switch (hookData->vkCode) {
case VK_LWIN:
event.key.keysym.sym = SDLK_LGUI;
event.key.keysym.scancode = SDL_SCANCODE_LGUI;
break;
case VK_RWIN:
event.key.keysym.sym = SDLK_RGUI;
event.key.keysym.scancode = SDL_SCANCODE_RGUI;
break;
case VK_LMENU:
event.key.keysym.sym = SDLK_LALT;
event.key.keysym.scancode = SDL_SCANCODE_LALT;
break;
case VK_RMENU:
event.key.keysym.sym = SDLK_RALT;
event.key.keysym.scancode = SDL_SCANCODE_RALT;
break;
case VK_LCONTROL:
event.key.keysym.sym = SDLK_LCTRL;
event.key.keysym.scancode = SDL_SCANCODE_LCTRL;
break;
case VK_RCONTROL:
event.key.keysym.sym = SDLK_RCTRL;
event.key.keysym.scancode = SDL_SCANCODE_RCTRL;
break;
default:
// Bail quickly if it's not a key we care about
goto NextHook;
}
// Make sure we're in a state where we actually want to steal this event (and it is safe to do so)
Session* session = Session::get();
if (session == nullptr || session->m_InputHandler == nullptr) {
goto NextHook;
}
keyCaptureActive = session->m_InputHandler->isSystemKeyCaptureActive();
// If this is a key we're going to intercept, create the synthetic SDL event.
// This is necessary because we are also hiding this key event from ourselves too.
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
event.type = SDL_KEYDOWN;
event.key.state = SDL_PRESSED;
}
else {
event.type = SDL_KEYUP;
event.key.state = SDL_RELEASED;
}
// Drop the event if it's a key down and capture is not active.
// For key up, we'll need to do a little more work to determine if we need to send this
// event to SDL in order to keep its internal keyboard modifier state consistent.
if (event.type == SDL_KEYDOWN && !keyCaptureActive) {
goto NextHook;
}
event.key.keysym.mod = SDL_GetModState();
switch (hookData->vkCode) {
case VK_LWIN:
if (event.key.state == SDL_PRESSED) {
event.key.keysym.mod |= KMOD_LGUI;
}
else {
event.key.keysym.mod &= ~KMOD_LGUI;
}
break;
case VK_RWIN:
if (event.key.state == SDL_PRESSED) {
event.key.keysym.mod |= KMOD_RGUI;
}
else {
event.key.keysym.mod &= ~KMOD_RGUI;
}
break;
case VK_LMENU:
if (event.key.state == SDL_PRESSED) {
event.key.keysym.mod |= KMOD_LALT;
}
else {
event.key.keysym.mod &= ~KMOD_LALT;
}
break;
case VK_RMENU:
if (event.key.state == SDL_PRESSED) {
event.key.keysym.mod |= KMOD_RALT;
}
else {
event.key.keysym.mod &= ~KMOD_RALT;
}
break;
case VK_LCONTROL:
if (event.key.state == SDL_PRESSED) {
event.key.keysym.mod |= KMOD_LCTRL;
}
else {
event.key.keysym.mod &= ~KMOD_LCTRL;
}
break;
case VK_RCONTROL:
if (event.key.state == SDL_PRESSED) {
event.key.keysym.mod |= KMOD_RCTRL;
}
else {
event.key.keysym.mod &= ~KMOD_RCTRL;
}
break;
}
// If the modifier state is unchanged and we're not capturing system keys,
// drop the event. If the modifier state is changed, we need to send the
// event to update SDL's state even if system key capture is now inactive
// (due to focus loss, mouse capture toggled off, etc.) otherwise SDL won't
// know that the modifier key has been lifted.
if (event.key.keysym.mod == SDL_GetModState() && !keyCaptureActive) {
goto NextHook;
}
event.key.timestamp = SDL_GetTicks();
event.key.windowID = SDL_GetWindowID(session->m_Window);
// NOTE: event.key.repeat is not populated in this path!
SDL_PushEvent(&event);
// Synchronize SDL's modifier state with the current state.
// SDL won't do this on its own because it will never see the
// WM_KEYUP/WM_KEYDOWN events.
SDL_SetModState((SDL_Keymod)event.key.keysym.mod);
// Eat the event only if key capture is active.
// If capture is not active and we're just resyncing SDL's modifier state,
// we need to ensure the key event is still delivered normally to the
// window in focus.
if (keyCaptureActive) {
return 1;
}
NextHook:
return CallNextHookEx(g_KeyboardHook, nCode, wParam, lParam);
}
#endif
void SdlInputHandler::raiseAllKeys()
{
if (m_KeysDown.isEmpty()) {
@@ -404,20 +300,22 @@ void SdlInputHandler::raiseAllKeys()
void SdlInputHandler::notifyMouseLeave()
{
#ifdef Q_OS_WIN32
// SDL on Windows doesn't send the mouse button up until the mouse re-enters the window
// after leaving it. This breaks some of the Aero snap gestures, so we'll fake it here.
// after leaving it. This breaks some of the Aero snap gestures, so we'll capture it to
// allow us to receive the mouse button up events later.
//
// On macOS and X11, capturing the mouse allows us to receive mouse motion outside the
// window (button up already worked without capture).
if (m_AbsoluteMouseMode && isCaptureActive()) {
// NB: Not using SDL_GetGlobalMouseState() because we want our state not the system's
Uint32 mouseState = SDL_GetMouseState(nullptr, nullptr);
for (Uint32 button = SDL_BUTTON_LEFT; button <= SDL_BUTTON_X2; button++) {
if (mouseState & SDL_BUTTON(button)) {
m_PendingMouseLeaveButtonUp = button;
SDL_CaptureMouse(SDL_TRUE);
break;
}
}
}
#endif
}
void SdlInputHandler::notifyFocusLost()
@@ -430,11 +328,27 @@ void SdlInputHandler::notifyFocusLost()
setCaptureActive(false);
}
#ifdef Q_OS_DARWIN
// Ungrab the keyboard
updateKeyboardGrabState();
#endif
// Raise all keys that are currently pressed. If we don't do this, certain keys
// used in shortcuts that cause focus loss (such as Alt+Tab) may get stuck down.
raiseAllKeys();
}
void SdlInputHandler::notifyFocusGained()
{
#ifdef Q_OS_DARWIN
// Re-grab the keyboard if it was grabbed before focus loss
// FIXME: We only do this on macOS because we get a spurious
// focus gain when in SDL_WINDOW_FULLSCREEN_DESKTOP on Windows
// immediately after losing focus by clicking on another window.
updateKeyboardGrabState();
#endif
}
bool SdlInputHandler::isCaptureActive()
{
if (SDL_GetRelativeMouseMode()) {
@@ -445,9 +359,62 @@ bool SdlInputHandler::isCaptureActive()
return m_FakeCaptureActive;
}
void SdlInputHandler::updateKeyboardGrabState()
{
if (m_CaptureSystemKeysMode == StreamingPreferences::CSK_OFF) {
return;
}
bool shouldGrab = isCaptureActive();
Uint32 windowFlags = SDL_GetWindowFlags(m_Window);
if (m_CaptureSystemKeysMode == StreamingPreferences::CSK_FULLSCREEN &&
!(windowFlags & SDL_WINDOW_FULLSCREEN)) {
// Ungrab if it's fullscreen only and we left fullscreen
shouldGrab = false;
}
else if (!(windowFlags & SDL_WINDOW_INPUT_FOCUS)) {
// Ungrab if we lose input focus (SDL will do this internally, but
// not for macOS where SDL is not handling the grab logic).
shouldGrab = false;
}
// Don't close the window on Alt+F4 when keyboard grab is enabled
SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, shouldGrab ? "1" : "0");
if (shouldGrab) {
#if SDL_VERSION_ATLEAST(2, 0, 15)
// On SDL 2.0.15, we can get keyboard-only grab on Win32, X11, and Wayland.
// This does nothing on macOS but it sets the SDL_WINDOW_KEYBOARD_GRABBED flag
// that we look for to see if keyboard capture is enabled. We'll handle macOS
// ourselves below using the private CGSSetGlobalHotKeyOperatingMode() API.
SDL_SetWindowKeyboardGrab(m_Window, SDL_TRUE);
#else
// If we're in full-screen desktop mode and SDL doesn't have keyboard grab yet,
// grab the cursor (will grab the keyboard too on X11).
if (SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN) {
SDL_SetWindowGrab(m_Window, SDL_TRUE);
}
#endif
#ifdef Q_OS_DARWIN
// SDL doesn't support this private macOS API
CGSSetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), CGSGlobalHotKeyDisable);
#endif
}
else {
#if SDL_VERSION_ATLEAST(2, 0, 15)
// Allow the keyboard to leave the window
SDL_SetWindowKeyboardGrab(m_Window, SDL_FALSE);
#endif
#ifdef Q_OS_DARWIN
// SDL doesn't support this private macOS API
CGSSetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), m_OldHotKeyMode);
#endif
}
}
bool SdlInputHandler::isSystemKeyCaptureActive()
{
if (!m_CaptureSystemKeysEnabled) {
if (m_CaptureSystemKeysMode == StreamingPreferences::CSK_OFF) {
return false;
}
@@ -456,19 +423,35 @@ bool SdlInputHandler::isSystemKeyCaptureActive()
}
Uint32 windowFlags = SDL_GetWindowFlags(m_Window);
return (windowFlags & SDL_WINDOW_INPUT_FOCUS) &&
(windowFlags & SDL_WINDOW_INPUT_GRABBED) &&
(windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP);
if (!(windowFlags & SDL_WINDOW_INPUT_FOCUS)
#if SDL_VERSION_ATLEAST(2, 0, 15)
|| !(windowFlags & SDL_WINDOW_KEYBOARD_GRABBED)
#else
|| !(windowFlags & SDL_WINDOW_INPUT_GRABBED)
#endif
)
{
return false;
}
if (m_CaptureSystemKeysMode == StreamingPreferences::CSK_FULLSCREEN &&
!(windowFlags & SDL_WINDOW_FULLSCREEN)) {
return false;
}
return true;
}
void SdlInputHandler::setCaptureActive(bool active)
{
if (active) {
// If we're in full-screen exclusive mode, grab the cursor so it can't accidentally leave our window.
// If we're in full-screen desktop mode but system key capture is enabled, also grab the cursor (will grab the keyboard too on X11).
if (((SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0 && m_CaptureSystemKeysEnabled) ||
(SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
if ((SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
#if SDL_VERSION_ATLEAST(2, 0, 15)
SDL_SetWindowMouseGrab(m_Window, SDL_TRUE);
#else
SDL_SetWindowGrab(m_Window, SDL_TRUE);
#endif
}
if (!m_AbsoluteMouseMode) {
@@ -482,7 +465,7 @@ void SdlInputHandler::setCaptureActive(bool active)
// If we're in relative mode, try to activate SDL's relative mouse mode
if (m_AbsoluteMouseMode || SDL_SetRelativeMouseMode(SDL_TRUE) < 0) {
// Relative mouse mode didn't work or was disabled, so we'll just hide the cursor
SDL_ShowCursor(SDL_DISABLE);
SDL_ShowCursor(m_MouseCursorCapturedVisibilityState);
m_FakeCaptureActive = true;
}
@@ -515,9 +498,17 @@ void SdlInputHandler::setCaptureActive(bool active)
SDL_SetRelativeMouseMode(SDL_FALSE);
}
#if SDL_VERSION_ATLEAST(2, 0, 15)
// Allow the cursor to leave the bounds of our window again.
SDL_SetWindowMouseGrab(m_Window, SDL_FALSE);
#else
// Allow the cursor to leave the bounds of our window again.
SDL_SetWindowGrab(m_Window, SDL_FALSE);
#endif
}
// Now update the keyboard grab
updateKeyboardGrabState();
}
void SdlInputHandler::handleTouchFingerEvent(SDL_TouchFingerEvent* event)
+60 -10
View File
@@ -5,13 +5,9 @@
#include <SDL.h>
#ifdef Q_OS_WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif
#define SDL_CODE_HIDE_CURSOR 1
#define SDL_CODE_SHOW_CURSOR 2
#define SDL_CODE_UNCAPTURE_MOUSE 3
struct GamepadState {
SDL_GameController* controller;
@@ -33,6 +29,25 @@ struct GamepadState {
unsigned char lt, rt;
};
#ifdef Q_OS_DARWIN
#include <CoreGraphics/CGError.h>
extern "C" {
typedef int CGSConnection;
typedef enum {
CGSGlobalHotKeyEnable = 0,
CGSGlobalHotKeyDisable = 1,
} CGSGlobalHotKeyOperatingMode;
extern CGSConnection _CGSDefaultConnection(void);
extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection connection,
CGSGlobalHotKeyOperatingMode* mode);
extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection,
CGSGlobalHotKeyOperatingMode mode);
}
#endif
#define MAX_GAMEPADS 4
#define MAX_FINGERS 2
@@ -69,6 +84,8 @@ public:
void handleJoystickArrivalEvent(SDL_JoyDeviceEvent* event);
void sendText(QString& string);
void rumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor);
void handleTouchFingerEvent(SDL_TouchFingerEvent* event);
@@ -81,6 +98,8 @@ public:
void notifyFocusLost();
void notifyFocusGained();
bool isCaptureActive();
bool isSystemKeyCaptureActive();
@@ -93,10 +112,24 @@ public:
void flushMousePositionUpdate();
void updateKeyboardGrabState();
static
QString getUnmappedGamepads();
private:
enum KeyCombo {
KeyComboQuit,
KeyComboUngrabInput,
KeyComboToggleFullScreen,
KeyComboToggleStatsOverlay,
KeyComboToggleMouseMode,
KeyComboToggleCursorHide,
KeyComboToggleMinimize,
KeyComboPasteText,
KeyComboMax
};
GamepadState*
findStateForGamepad(SDL_JoystickID id);
@@ -106,6 +139,8 @@ private:
void handleRelativeFingerEvent(SDL_TouchFingerEvent* event);
void performSpecialKeyCombo(KeyCombo combo);
static
Uint32 longPressTimerCallback(Uint32 interval, void* param);
@@ -124,10 +159,8 @@ private:
static
Uint32 dragTimerCallback(Uint32 interval, void* param);
#ifdef Q_OS_WIN32
static
LRESULT CALLBACK keyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam);
#endif
int clipboardThreadProc(void *ptr);
SDL_Window* m_Window;
bool m_MultiController;
@@ -154,7 +187,19 @@ private:
bool m_FakeCaptureActive;
QString m_OldIgnoreDevices;
QString m_OldIgnoreDevicesExcept;
bool m_CaptureSystemKeysEnabled;
StreamingPreferences::CaptureSysKeysMode m_CaptureSystemKeysMode;
int m_MouseCursorCapturedVisibilityState;
#ifdef Q_OS_DARWIN
CGSGlobalHotKeyOperatingMode m_OldHotKeyMode;
#endif
struct {
KeyCombo keyCombo;
SDL_Keycode keyCode;
SDL_Scancode scanCode;
bool enabled;
} m_SpecialKeyCombos[KeyComboMax];
SDL_TouchFingerEvent m_LastTouchDownEvent;
SDL_TouchFingerEvent m_LastTouchUpEvent;
@@ -163,7 +208,6 @@ private:
int m_StreamHeight;
bool m_AbsoluteMouseMode;
bool m_AbsoluteTouchMode;
Uint32 m_PendingMouseLeaveButtonUp;
SDL_TouchFingerEvent m_TouchDownEvent[MAX_FINGERS];
SDL_TimerID m_LeftButtonReleaseTimer;
@@ -172,5 +216,11 @@ private:
char m_DragButton;
int m_NumFingersDown;
SDL_Thread* m_ClipboardThread;
SDL_atomic_t m_ShutdownClipboardThread;
QString m_ClipboardData;
SDL_cond* m_ClipboardHasData;
SDL_mutex* m_ClipboardLock;
static const int k_ButtonMap[];
};
+142 -168
View File
@@ -3,8 +3,6 @@
#include <Limelight.h>
#include <SDL.h>
#include <QGuiApplication>
#define VK_0 0x30
#define VK_A 0x41
@@ -15,13 +13,134 @@
#define VK_NUMPAD0 0x60
#endif
void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
{
switch (combo) {
case KeyComboQuit:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected quit key combo");
// Push a quit event to the main loop
SDL_Event event;
event.type = SDL_QUIT;
event.quit.timestamp = SDL_GetTicks();
SDL_PushEvent(&event);
break;
case KeyComboUngrabInput:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse capture toggle combo");
// Stop handling future input
setCaptureActive(!isCaptureActive());
// Force raise all keys to ensure they aren't stuck,
// since we won't get their key up events.
raiseAllKeys();
break;
case KeyComboToggleFullScreen:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected full-screen toggle combo");
Session::s_ActiveSession->toggleFullscreen();
// Force raise all keys just be safe across this full-screen/windowed
// transition just in case key events get lost.
raiseAllKeys();
break;
case KeyComboToggleStatsOverlay:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected stats toggle combo");
// Toggle the stats overlay
Session::get()->getOverlayManager().setOverlayState(Overlay::OverlayDebug,
!Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebug));
break;
case KeyComboToggleMouseMode:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse mode toggle combo");
// Uncapture input
setCaptureActive(false);
// Toggle mouse mode
m_AbsoluteMouseMode = !m_AbsoluteMouseMode;
// Recapture input
setCaptureActive(true);
break;
case KeyComboToggleCursorHide:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected show mouse combo");
if (!SDL_GetRelativeMouseMode()) {
m_MouseCursorCapturedVisibilityState = !m_MouseCursorCapturedVisibilityState;
SDL_ShowCursor(m_MouseCursorCapturedVisibilityState);
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Cursor can only be shown in remote desktop mouse mode");
}
break;
case KeyComboToggleMinimize:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected minimize combo");
SDL_MinimizeWindow(m_Window);
break;
case KeyComboPasteText:
{
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected type clipboard text combo");
// Force raise all keys to ensure that none of them interfere
// with the text we're going to type.
raiseAllKeys();
const char* text;
if (SDL_HasClipboardText() && (text = SDL_GetClipboardText()) != nullptr) {
// Append this data to the clipboard data string for the thread to process
SDL_LockMutex(m_ClipboardLock);
m_ClipboardData.append(text);
SDL_CondSignal(m_ClipboardHasData);
SDL_UnlockMutex(m_ClipboardLock);
// SDL_GetClipboardText() allocates, so we must free
SDL_free((void*)text);
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"No text in clipboard to paste!");
}
break;
}
default:
Q_UNREACHABLE();
}
}
#define IS_SYNTHETIC_KEY_EVENT(x) ((x)->timestamp == 0)
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
{
short keyCode;
char modifiers;
if (event->repeat) {
// Ignore repeat key down events
SDL_assert(event->state == SDL_PRESSED);
return;
}
// Check for our special key combos
if ((event->state == SDL_PRESSED) &&
// Ignore timestamp == 0 which are sent from our keyboard code.
if (!IS_SYNTHETIC_KEY_EVENT(event) &&
(event->state == SDL_PRESSED) &&
(event->keysym.mod & KMOD_CTRL) &&
(event->keysym.mod & KMOD_ALT) &&
(event->keysym.mod & KMOD_SHIFT)) {
@@ -36,168 +155,19 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
// where the SDLK for one shortcut collides with
// the scancode of another.
// Check for quit combo (Ctrl+Alt+Shift+Q)
if (event->keysym.sym == SDLK_q) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected quit key combo (SDLK)");
// Push a quit event to the main loop
SDL_Event event;
event.type = SDL_QUIT;
event.quit.timestamp = SDL_GetTicks();
SDL_PushEvent(&event);
return;
}
// Check for the unbind combo (Ctrl+Alt+Shift+Z) unless on EGLFS which has no window manager
else if (event->keysym.sym == SDLK_z && QGuiApplication::platformName() != "eglfs") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse capture toggle combo (SDLK)");
// Stop handling future input
setCaptureActive(!isCaptureActive());
// Force raise all keys to ensure they aren't stuck,
// since we won't get their key up events.
raiseAllKeys();
return;
}
// Check for the mouse mode combo (Ctrl+Alt+Shift+M) unless on EGLFS which has no window manager
else if (event->keysym.sym == SDLK_m && QGuiApplication::platformName() != "eglfs") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse mode toggle combo (SDLK)");
// Uncapture input
setCaptureActive(false);
// Toggle mouse mode
m_AbsoluteMouseMode = !m_AbsoluteMouseMode;
// Recapture input
setCaptureActive(true);
return;
}
else if (event->keysym.sym == SDLK_x && QGuiApplication::platformName() != "eglfs") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected full-screen toggle combo (SDLK)");
Session::s_ActiveSession->toggleFullscreen();
// Force raise all keys just be safe across this full-screen/windowed
// transition just in case key events get lost.
raiseAllKeys();
return;
}
// Check for overlay combo (Ctrl+Alt+Shift+S)
else if (event->keysym.sym == SDLK_s) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected stats toggle combo (SDLK)");
// Toggle the stats overlay
Session::get()->getOverlayManager().setOverlayState(Overlay::OverlayDebug,
!Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebug));
// Force raise all keys just be safe across this full-screen/windowed
// transition just in case key events get lost.
raiseAllKeys();
return;
}
// Check for mouse show combo (Ctrl+Alt+Shift+C)
else if (event->keysym.sym == SDLK_c) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected show mouse combo (SDLK)");
if (!SDL_GetRelativeMouseMode()) {
SDL_ShowCursor(!SDL_ShowCursor(SDL_QUERY));
for (int i = 0; i < KeyComboMax; i++) {
if (m_SpecialKeyCombos[i].enabled && event->keysym.sym == m_SpecialKeyCombos[i].keyCode) {
performSpecialKeyCombo(m_SpecialKeyCombos[i].keyCombo);
return;
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Cursor can only be shown in remote desktop mouse mode");
}
for (int i = 0; i < KeyComboMax; i++) {
if (m_SpecialKeyCombos[i].enabled && event->keysym.scancode == m_SpecialKeyCombos[i].scanCode) {
performSpecialKeyCombo(m_SpecialKeyCombos[i].keyCombo);
return;
}
return;
}
// Check for quit combo (Ctrl+Alt+Shift+Q)
else if (event->keysym.scancode == SDL_SCANCODE_Q) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected quit key combo (scancode)");
// Push a quit event to the main loop
SDL_Event event;
event.type = SDL_QUIT;
event.quit.timestamp = SDL_GetTicks();
SDL_PushEvent(&event);
return;
}
// Check for the unbind combo (Ctrl+Alt+Shift+Z)
else if (event->keysym.scancode == SDL_SCANCODE_Z && QGuiApplication::platformName() != "eglfs") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse capture toggle combo (scancode)");
// Stop handling future input
setCaptureActive(!isCaptureActive());
// Force raise all keys to ensure they aren't stuck,
// since we won't get their key up events.
raiseAllKeys();
return;
}
// Check for the full-screen combo (Ctrl+Alt+Shift+X) unless on EGLFS which has no window manager
else if (event->keysym.scancode == SDL_SCANCODE_X && QGuiApplication::platformName() != "eglfs") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected full-screen toggle combo (scancode)");
Session::s_ActiveSession->toggleFullscreen();
// Force raise all keys just be safe across this full-screen/windowed
// transition just in case key events get lost.
raiseAllKeys();
return;
}
// Check for the mouse mode toggle combo (Ctrl+Alt+Shift+M) unless on EGLFS which has no window manager
else if (event->keysym.scancode == SDL_SCANCODE_M && QGuiApplication::platformName() != "eglfs") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse mode toggle combo (scancode)");
// Uncapture input
setCaptureActive(false);
// Toggle mouse mode
m_AbsoluteMouseMode = !m_AbsoluteMouseMode;
// Recapture input
setCaptureActive(true);
return;
}
else if (event->keysym.scancode == SDL_SCANCODE_S) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected stats toggle combo (scancode)");
// Toggle the stats overlay
Session::get()->getOverlayManager().setOverlayState(Overlay::OverlayDebug,
!Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebug));
// Force raise all keys just be safe across this full-screen/windowed
// transition just in case key events get lost.
raiseAllKeys();
return;
}
// Check for mouse show combo (Ctrl+Alt+Shift+C)
else if (event->keysym.sym == SDL_SCANCODE_C) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected show mouse combo (scancode)");
if (!SDL_GetRelativeMouseMode()) {
SDL_ShowCursor(!SDL_ShowCursor(SDL_QUERY));
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Cursor can only be shown in remote desktop mouse mode");
}
return;
}
}
if (event->repeat) {
// Ignore repeat key down events
SDL_assert(event->state == SDL_PRESSED);
return;
}
// Set modifier flags
@@ -433,12 +403,16 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
}
}
// Track the key state so we always know which keys are down
if (event->state == SDL_PRESSED) {
m_KeysDown.insert(keyCode);
}
else {
m_KeysDown.remove(keyCode);
// If this is a synthetic keypress from the clipboard code,
// this will be on a non-main thread, so don't touch m_KeysDown.
if (!IS_SYNTHETIC_KEY_EVENT(event)) {
// Track the key state so we always know which keys are down
if (event->state == SDL_PRESSED) {
m_KeysDown.insert(keyCode);
}
else {
m_KeysDown.remove(keyCode);
}
}
LiSendKeyboardEvent(0x8000 | keyCode,
+11 -30
View File
@@ -133,7 +133,15 @@ void SdlInputHandler::flushMousePositionUpdate()
// c) a mouse button is still down from before the cursor left the video region (to allow smooth dragging)
Uint32 buttonState = SDL_GetMouseState(nullptr, nullptr);
if (buttonState == 0) {
m_PendingMouseButtonsAllUpOnVideoRegionLeave = false;
if (m_PendingMouseButtonsAllUpOnVideoRegionLeave) {
// Tell the main thread to stop capturing the mouse now
SDL_Event event;
event.type = SDL_USEREVENT;
event.user.code = SDL_CODE_UNCAPTURE_MOUSE;
SDL_PushEvent(&event);
m_PendingMouseButtonsAllUpOnVideoRegionLeave = false;
}
}
if (mouseInVideoRegion || m_MouseWasInVideoRegion || m_PendingMouseButtonsAllUpOnVideoRegionLeave) {
LiSendMousePositionEvent(x, y, dst.w, dst.h);
@@ -146,7 +154,8 @@ void SdlInputHandler::flushMousePositionUpdate()
// executes.
SDL_Event event;
event.type = SDL_USEREVENT;
event.user.code = mouseInVideoRegion ? SDL_CODE_HIDE_CURSOR : SDL_CODE_SHOW_CURSOR;
event.user.code = (mouseInVideoRegion && m_MouseCursorCapturedVisibilityState == SDL_DISABLE) ?
SDL_CODE_HIDE_CURSOR : SDL_CODE_SHOW_CURSOR;
SDL_PushEvent(&event);
if (!mouseInVideoRegion && buttonState != 0) {
@@ -250,33 +259,5 @@ Uint32 SdlInputHandler::mouseMoveTimerCallback(Uint32 interval, void *param)
// Send mouse position updates if applicable
me->flushMousePositionUpdate();
#ifdef Q_OS_WIN32
// See comment in SdlInputHandler::notifyMouseLeave()
if (me->m_AbsoluteMouseMode && me->m_PendingMouseLeaveButtonUp != 0 && me->isCaptureActive()) {
int mouseX, mouseY;
int windowX, windowY;
Uint32 mouseState = SDL_GetGlobalMouseState(&mouseX, &mouseY);
SDL_GetWindowPosition(me->m_Window, &windowX, &windowY);
// If the button is now up, send the synthetic mouse up event
if ((mouseState & SDL_BUTTON(me->m_PendingMouseLeaveButtonUp)) == 0) {
SDL_Event event;
event.button.type = SDL_MOUSEBUTTONUP;
event.button.timestamp = SDL_GetTicks();
event.button.windowID = SDL_GetWindowID(me->m_Window);
event.button.which = 0;
event.button.button = me->m_PendingMouseLeaveButtonUp;
event.button.state = SDL_RELEASED;
event.button.clicks = 1;
event.button.x = mouseX - windowX;
event.button.y = mouseY - windowY;
SDL_PushEvent(&event);
me->m_PendingMouseLeaveButtonUp = 0;
}
}
#endif
return interval;
}
+90 -59
View File
@@ -37,14 +37,6 @@
#define CONN_TEST_SERVER "qt.conntest.moonlight-stream.org"
// Running the connection process asynchronously seems to reliably
// cause a crash in QSGRenderThread on Wayland and strange crashes
// elsewhere. Until these are figured out, avoid the async connect
// thread on Linux and BSDs.
#if !defined(Q_OS_UNIX) || defined(Q_OS_DARWIN)
#define USE_ASYNC_CONNECT_THREAD 1
#endif
CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
Session::clStageStarting,
nullptr,
@@ -65,11 +57,6 @@ void Session::clStageStarting(int stage)
// which happens to be the main thread, so it's cool to interact
// with the GUI in these callbacks.
emit s_ActiveSession->stageStarting(QString::fromLocal8Bit(LiGetStageName(stage)));
#ifndef USE_ASYNC_CONNECT_THREAD
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
#endif
}
void Session::clStageFailed(int stage, int errorCode)
@@ -81,11 +68,6 @@ void Session::clStageFailed(int stage, int errorCode)
char failingPorts[128];
LiStringifyPortFlags(portFlags, ", ", failingPorts, sizeof(failingPorts));
emit s_ActiveSession->stageFailed(QString::fromLocal8Bit(LiGetStageName(stage)), errorCode, QString(failingPorts));
#ifndef USE_ASYNC_CONNECT_THREAD
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
#endif
}
void Session::clConnectionTerminated(int errorCode)
@@ -358,6 +340,10 @@ bool Session::populateDecoderProperties(SDL_Window* window)
m_StreamConfig.colorSpace = decoder->getDecoderColorspace();
if (decoder->isAlwaysFullScreen()) {
m_IsFullScreen = true;
}
delete decoder;
return true;
@@ -365,6 +351,7 @@ bool Session::populateDecoderProperties(SDL_Window* window)
Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences)
: m_Preferences(preferences ? preferences : new StreamingPreferences(this)),
m_IsFullScreen(m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED || !WMUtils::isRunningDesktopEnvironment()),
m_Computer(computer),
m_App(app),
m_Window(nullptr),
@@ -524,22 +511,17 @@ bool Session::initialize()
{
default:
case StreamingPreferences::WM_FULLSCREEN_DESKTOP:
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN_DESKTOP;
break;
// Only use full-screen desktop mode if we're running a desktop environment
if (WMUtils::isRunningDesktopEnvironment()) {
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN_DESKTOP;
break;
}
// Fall-through
case StreamingPreferences::WM_FULLSCREEN:
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
break;
}
#ifdef Q_OS_DARWIN
// macOS behaves as if we're capturing system keys when
// we enter "real" full-screen mode, so use standard
// full-screen as our full-screen flag when capturing keys.
if (m_Preferences->captureSysKeys) {
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
}
#endif
#if !SDL_VERSION_ATLEAST(2, 0, 11)
// HACK: Using a full-screen window breaks mouse capture on the Pi's LXDE
// GUI environment. Force the session to use windowed mode (which won't
@@ -581,10 +563,13 @@ void Session::emitLaunchWarning(QString text)
// to allow it to transition off the screen before continuing.
uint32_t start = SDL_GetTicks();
while (!SDL_TICKS_PASSED(SDL_GetTicks(), start + 3500)) {
// Pump the UI loop while we wait
SDL_Delay(5);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
if (!m_ThreadedExec) {
// Pump the UI loop while we wait if we're on the main thread
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
}
}
}
@@ -835,7 +820,7 @@ void Session::getWindowDimensions(int& x, int& y,
}
}
fullScreen = (m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED);
fullScreen = m_IsFullScreen;
}
SDL_Rect usableBounds;
@@ -944,10 +929,8 @@ void Session::toggleFullscreen()
bool fullScreen = !(SDL_GetWindowFlags(m_Window) & m_FullScreenFlag);
if (fullScreen) {
if ((m_FullScreenFlag == SDL_WINDOW_FULLSCREEN || m_Preferences->captureSysKeys) && m_InputHandler->isCaptureActive()) {
if (m_FullScreenFlag == SDL_WINDOW_FULLSCREEN && m_InputHandler->isCaptureActive()) {
// Confine the cursor to the window if we're capturing input while transitioning to full screen.
// We also need to grab if we're capturing system keys, because SDL requires window grab to
// capture the keyboard on X11.
SDL_SetWindowGrab(m_Window, SDL_TRUE);
}
@@ -964,6 +947,9 @@ void Session::toggleFullscreen()
// Reposition the window when the resize is complete
m_PendingWindowedTransition = true;
}
// Input handler might need to start/stop keyboard grab after changing modes
m_InputHandler->updateKeyboardGrabState();
}
void Session::notifyMouseEmulationMode(bool enabled)
@@ -1002,17 +988,7 @@ bool Session::startConnectionAsync()
{
// Wait 1.5 seconds before connecting to let the user
// have time to read any messages present on the segue
#ifdef USE_ASYNC_CONNECT_THREAD
SDL_Delay(1500);
#else
uint32_t start = SDL_GetTicks();
while (!SDL_TICKS_PASSED(SDL_GetTicks(), start + 1500)) {
// Pump the UI loop while we wait since we're not async
SDL_Delay(5);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
}
#endif
// The UI should have ensured the old game was already quit
// if we decide to stream a different game.
@@ -1124,11 +1100,58 @@ void Session::flushWindowEvents()
SDL_PushEvent(&flushEvent);
}
class ExecThread : public QThread
{
public:
ExecThread(Session* session) :
QThread(nullptr),
m_Session(session) {}
void run() override
{
m_Session->execInternal();
}
Session* m_Session;
};
void Session::exec(int displayOriginX, int displayOriginY)
{
m_DisplayOriginX = displayOriginX;
m_DisplayOriginY = displayOriginY;
// Use a separate thread for the streaming session on X11 or Wayland
// to ensure we don't stomp on Qt's GL context. This breaks when using
// the Qt EGLFS backend, so we will restrict this to X11
m_ThreadedExec = WMUtils::isRunningX11() || WMUtils::isRunningWayland();
if (m_ThreadedExec) {
// Run the streaming session on a separate thread for Linux/BSD
ExecThread execThread(this);
execThread.start();
// Until the SDL streaming window is created, we should continue
// to update the Qt UI to allow warning messages to display and
// make sure that the Qt window can hide itself.
while (!execThread.wait(10) && m_Window == nullptr) {
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
}
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
// SDL is in charge now. Wait until the streaming thread exits
// to further update the Qt window.
execThread.wait();
}
else {
// Run the streaming session on the main thread for Windows and macOS
execInternal();
}
}
void Session::execInternal()
{
// Complete initialization in this deferred context to avoid
// calling expensive functions in the constructor (during the
// process of loading the StreamSegue).
@@ -1152,22 +1175,26 @@ void Session::exec(int displayOriginX, int displayOriginY)
m_StreamConfig.width,
m_StreamConfig.height);
// Kick off the async connection thread while we sit here and pump the event loop
AsyncConnectionStartThread asyncConnThread(this);
#ifdef USE_ASYNC_CONNECT_THREAD
asyncConnThread.start();
while (!asyncConnThread.wait(10)) {
if (!m_ThreadedExec) {
// Kick off the async connection thread while we sit here and pump the event loop
asyncConnThread.start();
while (!asyncConnThread.wait(10)) {
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
}
// Pump the event loop one last time to ensure we pick up any events from
// the thread that happened while it was in the final successful QThread::wait().
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
}
#else
asyncConnThread.run();
#endif
// Pump the event loop one last time to ensure we pick up any events from
// the thread that happened while it was in the final successful QThread::wait().
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
else {
// We're already in a separate thread so run the connection operations
// synchronously and don't pump the event loop. The main thread is already
// pumping the event loop for us.
asyncConnThread.run();
}
// If the connection failed, clean up and abort the connection.
if (!m_AsyncConnectionSuccess) {
@@ -1244,7 +1271,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
// For non-full screen windows, call getWindowDimensions()
// again after creating a window to allow it to account
// for window chrome size.
if (m_Preferences->windowMode == StreamingPreferences::WM_WINDOWED) {
if (!m_IsFullScreen) {
getWindowDimensions(x, y, width, height);
// We must set the size before the position because centering
@@ -1344,6 +1371,9 @@ void Session::exec(int displayOriginX, int displayOriginY)
case SDL_CODE_SHOW_CURSOR:
SDL_ShowCursor(SDL_ENABLE);
break;
case SDL_CODE_UNCAPTURE_MOUSE:
SDL_CaptureMouse(SDL_FALSE);
break;
case SDL_CODE_FLUSH_WINDOW_EVENT_BARRIER:
m_FlushingWindowEventsRef--;
break;
@@ -1365,6 +1395,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
if (m_Preferences->muteOnFocusLoss) {
m_AudioMuted = false;
}
m_InputHandler->notifyFocusGained();
break;
case SDL_WINDOWEVENT_LEAVE:
m_InputHandler->notifyMouseLeave();
+5
View File
@@ -17,6 +17,7 @@ class Session : public QObject
friend class SdlInputHandler;
friend class DeferredSessionCleanupTask;
friend class AsyncConnectionStartThread;
friend class ExecThread;
public:
explicit Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences = nullptr);
@@ -57,6 +58,8 @@ signals:
void sessionFinished(int portTestResult);
private:
void execInternal();
bool initialize();
bool startConnectionAsync();
@@ -133,6 +136,7 @@ private:
int drSubmitDecodeUnit(PDECODE_UNIT du);
StreamingPreferences* m_Preferences;
bool m_IsFullScreen;
STREAM_CONFIGURATION m_StreamConfig;
DECODER_RENDERER_CALLBACKS m_VideoCallbacks;
AUDIO_RENDERER_CALLBACKS m_AudioCallbacks;
@@ -147,6 +151,7 @@ private:
Uint32 m_FullScreenFlag;
int m_DisplayOriginX;
int m_DisplayOriginY;
bool m_ThreadedExec;
bool m_PendingWindowedTransition;
bool m_UnexpectedTermination;
SdlInputHandler* m_InputHandler;
+235 -23
View File
@@ -14,12 +14,23 @@ extern "C" {
#include <Limelight.h>
#include <SDL_syswm.h>
DrmRenderer::DrmRenderer()
: m_DrmFd(-1),
: m_HwContext(nullptr),
m_DrmFd(-1),
m_SdlOwnsDrmFd(false),
m_SupportsDirectRendering(false),
m_CrtcId(0),
m_PlaneId(0),
m_CurrentFbId(0)
{
#ifdef HAVE_EGL
m_eglCreateImage = nullptr;
m_eglCreateImageKHR = nullptr;
m_eglDestroyImage = nullptr;
m_eglDestroyImageKHR = nullptr;
#endif
}
DrmRenderer::~DrmRenderer()
@@ -28,14 +39,22 @@ DrmRenderer::~DrmRenderer()
drmModeRmFB(m_DrmFd, m_CurrentFbId);
}
if (m_DrmFd != -1) {
if (m_HwContext != nullptr) {
av_buffer_unref(&m_HwContext);
}
if (!m_SdlOwnsDrmFd && m_DrmFd != -1) {
close(m_DrmFd);
}
}
bool DrmRenderer::prepareDecoderContext(AVCodecContext*, AVDictionary**)
bool DrmRenderer::prepareDecoderContext(AVCodecContext* context, AVDictionary** options)
{
/* Nothing to do */
// The out-of-tree LibreELEC patches use this option to control the type of the V4L2
// buffers that we get back. We only support NV12 buffers now.
av_dict_set_int(options, "pixel_format", AV_PIX_FMT_NV12, 0);
context->hw_device_ctx = av_buffer_ref(m_HwContext);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Using DRM renderer");
@@ -43,33 +62,89 @@ bool DrmRenderer::prepareDecoderContext(AVCodecContext*, AVDictionary**)
return true;
}
bool DrmRenderer::initialize(PDECODER_PARAMETERS)
bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
{
const char* device = SDL_getenv("DRM_DEV");
int i;
if (device == nullptr) {
device = "/dev/dri/card0";
}
#if SDL_VERSION_ATLEAST(2, 0, 15)
SDL_SysWMinfo info;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Opening DRM device: %s",
device);
SDL_VERSION(&info.version);
m_DrmFd = open(device, O_RDWR | O_CLOEXEC);
if (m_DrmFd < 0) {
if (!SDL_GetWindowWMInfo(params->window, &info)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to open DRM device: %d",
errno);
"SDL_GetWindowWMInfo() failed: %s",
SDL_GetError());
return false;
}
if (info.subsystem == SDL_SYSWM_KMSDRM) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Sharing DRM FD with SDL");
SDL_assert(info.info.kmsdrm.drm_fd >= 0);
m_DrmFd = info.info.kmsdrm.drm_fd;
m_SdlOwnsDrmFd = true;
}
else
#endif
{
const char* device = SDL_getenv("DRM_DEV");
if (device == nullptr) {
device = "/dev/dri/card0";
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Opening DRM device: %s",
device);
m_DrmFd = open(device, O_RDWR | O_CLOEXEC);
if (m_DrmFd < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to open DRM device: %d",
errno);
return false;
}
}
// Create the device context first because it is needed whether we can
// actually use direct rendering or not.
m_HwContext = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_DRM);
if (m_HwContext == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"av_hwdevice_ctx_alloc(DRM) failed");
return false;
}
AVHWDeviceContext* deviceContext = (AVHWDeviceContext*)m_HwContext->data;
AVDRMDeviceContext* drmDeviceContext = (AVDRMDeviceContext*)deviceContext->hwctx;
drmDeviceContext->fd = m_DrmFd;
int err = av_hwdevice_ctx_init(m_HwContext);
if (err < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"av_hwdevice_ctx_init(DRM) failed: %d",
err);
return false;
}
#ifdef HAVE_EGL
// Still return true if we fail to initialize DRM direct rendering
// stuff, since we have EGL that we can use for indirect rendering.
const bool DIRECT_RENDERING_INIT_FAILED = true;
#else
// Fail if we can't initialize direct rendering and we don't have EGL.
const bool DIRECT_RENDERING_INIT_FAILED = false;
#endif
drmModeRes* resources = drmModeGetResources(m_DrmFd);
if (resources == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"drmModeGetResources() failed: %d",
errno);
return false;
"drmModeGetResources() failed: %d",
errno);
return DIRECT_RENDERING_INIT_FAILED;
}
// Look for a connected connector and get the associated encoder
@@ -89,7 +164,7 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS)
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"No connected displays found!");
drmModeFreeResources(resources);
return false;
return DIRECT_RENDERING_INIT_FAILED;
}
// Now find the CRTC from the encoder
@@ -109,7 +184,7 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS)
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"DRM encoder not found!");
drmModeFreeResources(resources);
return false;
return DIRECT_RENDERING_INIT_FAILED;
}
int crtcIndex = -1;
@@ -130,7 +205,7 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS)
if (crtcIndex == -1) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to get CRTC!");
return false;
return DIRECT_RENDERING_INIT_FAILED;
}
drmSetClientCap(m_DrmFd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
@@ -140,7 +215,7 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS)
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"drmGetPlaneResources() failed: %d",
errno);
return false;
return DIRECT_RENDERING_INIT_FAILED;
}
// Find an NV12 overlay plane to render on
@@ -185,6 +260,15 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS)
drmModeFreePlaneResources(planeRes);
if (m_PlaneId == 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to find suitable NV12 overlay plane!");
return DIRECT_RENDERING_INIT_FAILED;
}
// If we got this far, we can do direct rendering via the DRM FD.
m_SupportsDirectRendering = true;
return true;
}
@@ -277,3 +361,131 @@ void DrmRenderer::renderFrame(AVFrame* frame)
// Free the previous FB object which has now been superseded
drmModeRmFB(m_DrmFd, lastFbId);
}
bool DrmRenderer::needsTestFrame()
{
return true;
}
bool DrmRenderer::isDirectRenderingSupported()
{
return m_SupportsDirectRendering;
}
#ifdef HAVE_EGL
bool DrmRenderer::canExportEGL() {
if (qgetenv("DRM_FORCE_DIRECT") == "1") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Using direct rendering due to environment variable");
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"DRM backend supports exporting EGLImage");
return true;
}
AVPixelFormat DrmRenderer::getEGLImagePixelFormat() {
return AV_PIX_FMT_NV12;
}
bool DrmRenderer::initializeEGL(EGLDisplay,
const EGLExtensions &ext) {
if (!ext.isSupported("EGL_EXT_image_dma_buf_import")) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"DRM-EGL: DMABUF unsupported");
return false;
}
// NB: eglCreateImage() and eglCreateImageKHR() have slightly different definitions
m_eglCreateImage = (typeof(m_eglCreateImage))eglGetProcAddress("eglCreateImage");
m_eglCreateImageKHR = (typeof(m_eglCreateImageKHR))eglGetProcAddress("eglCreateImageKHR");
m_eglDestroyImage = (typeof(m_eglDestroyImage))eglGetProcAddress("eglDestroyImage");
m_eglDestroyImageKHR = (typeof(m_eglDestroyImageKHR))eglGetProcAddress("eglDestroyImageKHR");
if (!(m_eglCreateImage && m_eglDestroyImage) &&
!(m_eglCreateImageKHR && m_eglDestroyImageKHR)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Missing eglCreateImage()/eglDestroyImage() in EGL driver");
return false;
}
return true;
}
ssize_t DrmRenderer::exportEGLImages(AVFrame *frame, EGLDisplay dpy,
EGLImage images[EGL_MAX_PLANES]) {
ssize_t count = 0;
AVDRMFrameDescriptor* drmFrame = (AVDRMFrameDescriptor*)frame->data[0];
memset(images, 0, sizeof(EGLImage) * EGL_MAX_PLANES);
SDL_assert(drmFrame->nb_objects == 1);
SDL_assert(drmFrame->nb_layers == 1);
SDL_assert(drmFrame->layers[0].nb_planes == 2);
for (int i = 0; i < drmFrame->layers[0].nb_planes; ++i) {
const auto &plane = drmFrame->layers[0].planes[i];
const auto &object = drmFrame->objects[plane.object_index];
const int EGL_ATTRIB_COUNT = 13;
EGLAttrib attribs[EGL_ATTRIB_COUNT] = {
EGL_LINUX_DRM_FOURCC_EXT, i == 0 ? DRM_FORMAT_R8 : DRM_FORMAT_GR88,
EGL_WIDTH, i == 0 ? frame->width : frame->width / 2,
EGL_HEIGHT, i == 0 ? frame->height : frame->height / 2,
EGL_DMA_BUF_PLANE0_FD_EXT, object.fd,
EGL_DMA_BUF_PLANE0_OFFSET_EXT, plane.offset,
EGL_DMA_BUF_PLANE0_PITCH_EXT, plane.pitch,
EGL_NONE
};
if (m_eglCreateImage) {
images[i] = m_eglCreateImage(dpy, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT,
nullptr, attribs);
if (!images[i]) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"eglCreateImage() Failed: %d", eglGetError());
goto fail;
}
}
else {
// Cast the EGLAttrib array elements to EGLint for the KHR extension
EGLint intAttribs[EGL_ATTRIB_COUNT];
for (int i = 0; i < EGL_ATTRIB_COUNT; i++) {
intAttribs[i] = (EGLint)attribs[i];
}
images[i] = m_eglCreateImageKHR(dpy, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT,
nullptr, intAttribs);
if (!images[i]) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"eglCreateImageKHR() Failed: %d", eglGetError());
goto fail;
}
}
++count;
}
return count;
fail:
freeEGLImages(dpy, images);
return -1;
}
void DrmRenderer::freeEGLImages(EGLDisplay dpy, EGLImage images[EGL_MAX_PLANES]) {
for (size_t i = 0; i < EGL_MAX_PLANES; ++i) {
if (m_eglDestroyImage) {
m_eglDestroyImage(dpy, images[i]);
}
else {
m_eglDestroyImageKHR(dpy, images[i]);
}
}
}
#endif
@@ -14,12 +14,31 @@ public:
virtual void renderFrame(AVFrame* frame) override;
virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) override;
virtual int getRendererAttributes() override;
virtual bool needsTestFrame() override;
virtual bool isDirectRenderingSupported() override;
#ifdef HAVE_EGL
virtual bool canExportEGL() override;
virtual AVPixelFormat getEGLImagePixelFormat() override;
virtual bool initializeEGL(EGLDisplay dpy, const EGLExtensions &ext) override;
virtual ssize_t exportEGLImages(AVFrame *frame, EGLDisplay dpy, EGLImage images[EGL_MAX_PLANES]) override;
virtual void freeEGLImages(EGLDisplay dpy, EGLImage[EGL_MAX_PLANES]) override;
#endif
private:
AVBufferRef* m_HwContext;
int m_DrmFd;
bool m_SdlOwnsDrmFd;
bool m_SupportsDirectRendering;
uint32_t m_CrtcId;
uint32_t m_PlaneId;
uint32_t m_CurrentFbId;
SDL_Rect m_OutputRect;
#ifdef HAVE_EGL
PFNEGLCREATEIMAGEPROC m_eglCreateImage;
PFNEGLDESTROYIMAGEPROC m_eglDestroyImage;
PFNEGLCREATEIMAGEKHRPROC m_eglCreateImageKHR;
PFNEGLDESTROYIMAGEKHRPROC m_eglDestroyImageKHR;
#endif
};
@@ -6,7 +6,7 @@
static QStringList egl_get_extensions(EGLDisplay dpy) {
const auto EGLExtensionsStr = eglQueryString(dpy, EGL_EXTENSIONS);
if (!EGLExtensionsStr) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Unable to get EGL extensions");
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Unable to get EGL extensions: %d", eglGetError());
return QStringList();
}
return QString(EGLExtensionsStr).split(" ");
+81 -56
View File
@@ -15,18 +15,6 @@
#include <SDL_render.h>
#include <SDL_syswm.h>
#ifndef EGL_VERSION_1_5
typedef intptr_t EGLAttrib;
#endif
#if !defined(EGL_VERSION_1_5) || !defined(EGL_EGL_PROTOTYPES)
typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYPROC) (EGLenum platform, void *native_display, const EGLAttrib *attrib_list);
#endif
#ifndef EGL_EXT_platform_base
typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list);
#endif
// These are EGL extensions, so some platform headers may not provide them
#ifndef EGL_PLATFORM_WAYLAND_KHR
#define EGL_PLATFORM_WAYLAND_KHR 0x31D8
@@ -34,6 +22,9 @@ typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platf
#ifndef EGL_PLATFORM_X11_KHR
#define EGL_PLATFORM_X11_KHR 0x31D5
#endif
#ifndef EGL_PLATFORM_GBM_KHR
#define EGL_PLATFORM_GBM_KHR 0x31D7
#endif
typedef struct _OVERLAY_VERTEX
{
@@ -68,7 +59,7 @@ SDL_Window* EGLRenderer::s_LastFailedWindow = nullptr;
EGLRenderer::EGLRenderer(IFFmpegRenderer *backendRenderer)
:
m_SwPixelFormat(AV_PIX_FMT_NONE),
m_EGLImagePixelFormat(AV_PIX_FMT_NONE),
m_EGLDisplay(EGL_NO_DISPLAY),
m_Textures{0},
m_OverlayTextures{0},
@@ -83,6 +74,7 @@ EGLRenderer::EGLRenderer(IFFmpegRenderer *backendRenderer)
m_ColorSpace(AVCOL_SPC_NB),
m_ColorFull(false),
m_BlockingSwapBuffers(false),
m_LastFrame(av_frame_alloc()),
m_glEGLImageTargetTexture2DOES(nullptr),
m_glGenVertexArraysOES(nullptr),
m_glBindVertexArrayOES(nullptr),
@@ -133,6 +125,8 @@ EGLRenderer::~EGLRenderer()
SDL_DestroyRenderer(m_DummyRenderer);
}
av_frame_free(&m_LastFrame);
// Reset the global properties back to what they were before
SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "0");
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, m_OldContextProfileMask);
@@ -162,16 +156,16 @@ void EGLRenderer::notifyOverlayUpdated(Overlay::OverlayType type)
}
}
bool EGLRenderer::isPixelFormatSupported(int, AVPixelFormat pixelFormat)
bool EGLRenderer::isPixelFormatSupported(int videoFormat, AVPixelFormat pixelFormat)
{
// Remember to keep this in sync with EGLRenderer::renderFrame()!
switch (pixelFormat)
{
case AV_PIX_FMT_NV12:
return true;
default:
return false;
}
// Pixel format support should be determined by the backend renderer
return m_Backend->isPixelFormatSupported(videoFormat, pixelFormat);
}
AVPixelFormat EGLRenderer::getPreferredPixelFormat(int videoFormat)
{
// Pixel format preference should be determined by the backend renderer
return m_Backend->getPreferredPixelFormat(videoFormat);
}
void EGLRenderer::renderOverlay(Overlay::OverlayType type)
@@ -195,7 +189,7 @@ void EGLRenderer::renderOverlay(Overlay::OverlayType type)
float y;
float w;
float h;
} overlayRect;
} overlayRect = {};
// These overlay positions differ from the other renderers because OpenGL
// places the origin in the lower-left corner instead of the upper-left.
@@ -208,6 +202,8 @@ void EGLRenderer::renderOverlay(Overlay::OverlayType type)
// Top left
overlayRect.x = 0;
overlayRect.y = m_ViewportHeight - newSurface->h;
} else {
SDL_assert(false);
}
overlayRect.w = newSurface->w;
@@ -374,10 +370,10 @@ bool EGLRenderer::compileShaders() {
SDL_assert(!m_ShaderProgram);
SDL_assert(!m_OverlayShaderProgram);
SDL_assert(m_SwPixelFormat != AV_PIX_FMT_NONE);
SDL_assert(m_EGLImagePixelFormat != AV_PIX_FMT_NONE);
// XXX: TODO: other formats
SDL_assert(m_SwPixelFormat == AV_PIX_FMT_NV12);
SDL_assert(m_EGLImagePixelFormat == AV_PIX_FMT_NV12);
m_ShaderProgram = compileShader("egl.vert", "egl.frag");
if (!m_ShaderProgram) {
@@ -479,6 +475,13 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
return false;
}
break;
#endif
#if SDL_VERSION_ATLEAST(2, 0, 15) && defined(SDL_VIDEO_DRIVER_KMSDRM)
case SDL_SYSWM_KMSDRM:
if (!openDisplay(EGL_PLATFORM_GBM_KHR, info.info.kmsdrm.gbm_dev)) {
return false;
}
break;
#endif
default:
EGL_LOG(Error, "not compatible with SYSWM");
@@ -544,7 +547,7 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
src.x = src.y = dst.x = dst.y = 0;
src.w = params->width;
src.h = params->height;
SDL_GetWindowSize(m_Window, &dst.w, &dst.h);
SDL_GL_GetDrawableSize(m_Window, &dst.w, &dst.h);
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
glViewport(dst.x, dst.y, dst.w, dst.h);
@@ -557,7 +560,17 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
if (params->enableVsync) {
SDL_GL_SetSwapInterval(1);
m_BlockingSwapBuffers = true;
#if SDL_VERSION_ATLEAST(2, 0, 15) && defined(SDL_VIDEO_DRIVER_KMSDRM)
// The SDL KMSDRM backend already enforces double buffering (due to
// SDL_HINT_VIDEO_DOUBLE_BUFFER=1), so calling glFinish() after
// SDL_GL_SwapWindow() will block an extra frame and lock rendering
// at 1/2 the display refresh rate.
if (info.subsystem != SDL_SYSWM_KMSDRM)
#endif
{
m_BlockingSwapBuffers = true;
}
} else {
SDL_GL_SetSwapInterval(0);
}
@@ -648,7 +661,18 @@ const float *EGLRenderer::getColorMatrix() {
case AVCOL_SPC_BT2020_CL:
return m_ColorFull ? bt2020Full : bt2020Lim;
default:
SDL_assert(false);
// Some backends don't populate this, so we'll assume
// the host gave us what we asked for by default.
switch (getDecoderColorspace()) {
case COLORSPACE_REC_601:
return m_ColorFull ? bt601Full : bt601Lim;
case COLORSPACE_REC_709:
return m_ColorFull ? bt709Full : bt709Lim;
case COLORSPACE_REC_2020:
return m_ColorFull ? bt2020Full : bt2020Lim;
default:
SDL_assert(false);
}
};
return bt601Lim;
@@ -721,39 +745,33 @@ void EGLRenderer::renderFrame(AVFrame* frame)
return;
}
if (frame->hw_frames_ctx != nullptr) {
// Find the native read-back format and load the shader
if (m_SwPixelFormat == AV_PIX_FMT_NONE) {
auto hwFrameCtx = (AVHWFramesContext*)frame->hw_frames_ctx->data;
// Find the native read-back format and load the shaders
if (m_EGLImagePixelFormat == AV_PIX_FMT_NONE) {
m_EGLImagePixelFormat = m_Backend->getEGLImagePixelFormat();
EGL_LOG(Info, "EGLImage pixel format: %d", m_EGLImagePixelFormat);
m_SwPixelFormat = hwFrameCtx->sw_format;
SDL_assert(m_SwPixelFormat != AV_PIX_FMT_NONE);
SDL_assert(m_EGLImagePixelFormat != AV_PIX_FMT_NONE);
EGL_LOG(Info, "Selected read-back format: %d", m_SwPixelFormat);
m_ColorSpace = frame->colorspace;
// XXX: TODO: Handle other pixel formats
SDL_assert(m_SwPixelFormat == AV_PIX_FMT_NV12);
m_ColorSpace = frame->colorspace;
m_ColorFull = frame->color_range == AVCOL_RANGE_JPEG;
// This handles the case where the color range is unknown,
// so that we use Limited color range which is the default
// behavior for Moonlight.
m_ColorFull = frame->color_range == AVCOL_RANGE_JPEG;
if (!specialize()) {
m_SwPixelFormat = AV_PIX_FMT_NONE;
return;
}
}
ssize_t plane_count = m_Backend->exportEGLImages(frame, m_EGLDisplay, imgs);
if (plane_count < 0)
if (!specialize()) {
m_EGLImagePixelFormat = AV_PIX_FMT_NONE;
return;
for (ssize_t i = 0; i < plane_count; ++i) {
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, m_Textures[i]);
m_glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, imgs[i]);
}
} else {
// TODO: load texture for SW decoding ?
EGL_LOG(Error, "EGL rendering only supports hw frames");
}
ssize_t plane_count = m_Backend->exportEGLImages(frame, m_EGLDisplay, imgs);
if (plane_count < 0)
return;
for (ssize_t i = 0; i < plane_count; ++i) {
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, m_Textures[i]);
m_glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, imgs[i]);
}
glClear(GL_COLOR_BUFFER_BIT);
@@ -787,6 +805,13 @@ void EGLRenderer::renderFrame(AVFrame* frame)
glFinish();
}
if (frame->hw_frames_ctx != nullptr)
m_Backend->freeEGLImages(m_EGLDisplay, imgs);
m_Backend->freeEGLImages(m_EGLDisplay, imgs);
// Free the DMA-BUF backing the last frame now that it is definitely
// no longer being used anymore. While the PRIME FD stays around until
// EGL is done with it, the memory backing it may be reused by FFmpeg
// before the GPU has read it. This is particularly noticeable on the
// RK3288-based TinkerBoard when V-Sync is disabled.
av_frame_unref(m_LastFrame);
av_frame_move_ref(m_LastFrame, frame);
}
@@ -14,6 +14,7 @@ public:
virtual void renderFrame(AVFrame* frame) override;
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
virtual bool isPixelFormatSupported(int videoFormat, enum AVPixelFormat pixelFormat) override;
virtual AVPixelFormat getPreferredPixelFormat(int videoFormat) override;
private:
@@ -29,7 +30,7 @@ private:
int m_ViewportWidth;
int m_ViewportHeight;
int m_SwPixelFormat;
AVPixelFormat m_EGLImagePixelFormat;
void *m_EGLDisplay;
unsigned m_Textures[EGL_MAX_PLANES];
unsigned m_OverlayTextures[Overlay::OverlayMax];
@@ -44,6 +45,7 @@ private:
int m_ColorSpace;
bool m_ColorFull;
bool m_BlockingSwapBuffers;
AVFrame* m_LastFrame;
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_glEGLImageTargetTexture2DOES;
PFNGLGENVERTEXARRAYSOESPROC m_glGenVertexArraysOES;
PFNGLBINDVERTEXARRAYOESPROC m_glBindVertexArrayOES;
+72 -22
View File
@@ -10,7 +10,12 @@
MmalRenderer::MmalRenderer()
: m_Renderer(nullptr),
m_InputPort(nullptr),
m_BackgroundRenderer(nullptr)
m_BackgroundRenderer(nullptr),
m_Window(nullptr),
m_VideoWidth(0),
m_VideoHeight(0),
m_LastWindowPosX(-1),
m_LastWindowPosY(-1)
{
}
@@ -46,10 +51,67 @@ bool MmalRenderer::prepareDecoderContext(AVCodecContext* context, AVDictionary**
return true;
}
void MmalRenderer::updateDisplayRegion()
{
MMAL_STATUS_T status;
int currentPosX, currentPosY;
MMAL_DISPLAYREGION_T dr;
dr.hdr.id = MMAL_PARAMETER_DISPLAYREGION;
dr.hdr.size = sizeof(MMAL_DISPLAYREGION_T);
dr.set = MMAL_DISPLAY_SET_DEST_RECT;
SDL_GetWindowPosition(m_Window, &currentPosX, &currentPosY);
if ((SDL_GetWindowFlags(m_Window) & SDL_WINDOW_INPUT_FOCUS) == 0) {
dr.dest_rect.x = 0;
dr.dest_rect.y = 0;
dr.dest_rect.width = 0;
dr.dest_rect.height = 0;
// Force a re-evaluation next time
m_LastWindowPosX = -1;
m_LastWindowPosY = -1;
}
else if (m_LastWindowPosX != currentPosX || m_LastWindowPosY != currentPosY) {
SDL_Rect src, dst;
src.x = src.y = 0;
src.w = m_VideoWidth;
src.h = m_VideoHeight;
dst.x = dst.y = 0;
SDL_GetWindowSize(m_Window, &dst.w, &dst.h);
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
dr.dest_rect.x = currentPosX + dst.x;
dr.dest_rect.y = currentPosY + dst.y;
dr.dest_rect.width = dst.w;
dr.dest_rect.height = dst.h;
m_LastWindowPosX = currentPosX;
m_LastWindowPosY = currentPosY;
}
else {
// Nothing to do
return;
}
status = mmal_port_parameter_set(m_InputPort, &dr.hdr);
if (status != MMAL_SUCCESS) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"mmal_port_parameter_set() failed: %x (%s)",
status, mmal_status_to_string(status));
}
}
bool MmalRenderer::initialize(PDECODER_PARAMETERS params)
{
MMAL_STATUS_T status;
m_Window = params->window;
m_VideoWidth = params->width;
m_VideoHeight = params->height;
// Clear the background if possible
setupBackground(params);
@@ -107,23 +169,6 @@ bool MmalRenderer::initialize(PDECODER_PARAMETERS params)
dr.src_rect.width = params->width;
dr.src_rect.height = params->height;
{
SDL_Rect src, dst;
src.x = src.y = 0;
src.w = params->width;
src.h = params->height;
dst.x = dst.y = 0;
SDL_GetWindowSize(params->window, &dst.w, &dst.h);
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
dr.set |= MMAL_DISPLAY_SET_DEST_RECT;
dr.dest_rect.x = dst.x;
dr.dest_rect.y = dst.y;
dr.dest_rect.width = dst.w;
dr.dest_rect.height = dst.h;
}
status = mmal_port_parameter_set(m_InputPort, &dr.hdr);
if (status != MMAL_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@@ -131,6 +176,9 @@ bool MmalRenderer::initialize(PDECODER_PARAMETERS params)
status, mmal_status_to_string(status));
return false;
}
// Set the destination display region
updateDisplayRegion();
}
status = mmal_port_enable(m_InputPort, InputPortCallback);
@@ -179,17 +227,16 @@ void MmalRenderer::InputPortCallback(MMAL_PORT_T*, MMAL_BUFFER_HEADER_T* buffer)
mmal_buffer_header_release(buffer);
}
enum AVPixelFormat MmalRenderer::getPreferredPixelFormat(int videoFormat)
enum AVPixelFormat MmalRenderer::getPreferredPixelFormat(int)
{
// Opaque MMAL buffers
SDL_assert(videoFormat == VIDEO_FORMAT_H264);
return AV_PIX_FMT_MMAL;
}
int MmalRenderer::getRendererAttributes()
{
// This renderer can only draw in full-screen and maxes out at 1080p
return RENDERER_ATTRIBUTE_FULLSCREEN_ONLY | RENDERER_ATTRIBUTE_1080P_MAX;
// This renderer maxes out at 1080p
return RENDERER_ATTRIBUTE_1080P_MAX;
}
bool MmalRenderer::needsTestFrame()
@@ -209,6 +256,9 @@ void MmalRenderer::renderFrame(AVFrame* frame)
MMAL_BUFFER_HEADER_T* buffer = (MMAL_BUFFER_HEADER_T*)frame->data[3];
MMAL_STATUS_T status;
// Update the destination display region in case the window moved
updateDisplayRegion();
status = mmal_port_send_buffer(m_InputPort, buffer);
if (status != MMAL_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@@ -22,10 +22,14 @@ private:
static void InputPortCallback(MMAL_PORT_T* port, MMAL_BUFFER_HEADER_T* buffer);
void setupBackground(PDECODER_PARAMETERS params);
void updateDisplayRegion();
MMAL_COMPONENT_T* m_Renderer;
MMAL_PORT_T* m_InputPort;
SDL_Renderer* m_BackgroundRenderer;
SDL_Window* m_Window;
int m_VideoWidth, m_VideoHeight;
int m_LastWindowPosX, m_LastWindowPosY;
};
@@ -10,9 +10,45 @@ extern "C" {
}
#ifdef HAVE_EGL
// SDL_egl.h have too many conflicts, we will do without
typedef void *EGLDisplay;
#define MESA_EGL_NO_X11_HEADERS
#define EGL_NO_X11
#include <SDL_egl.h>
#ifndef EGL_VERSION_1_5
typedef intptr_t EGLAttrib;
typedef void *EGLImage;
#endif
#if !defined(EGL_VERSION_1_5) || !defined(EGL_EGL_PROTOTYPES)
typedef EGLImage (EGLAPIENTRYP PFNEGLCREATEIMAGEPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLAttrib *attrib_list);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEPROC) (EGLDisplay dpy, EGLImage image);
typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYPROC) (EGLenum platform, void *native_display, const EGLAttrib *attrib_list);
#endif
#if !defined(EGL_KHR_image) || !defined(EGL_EGLEXT_PROTOTYPES)
// EGL_KHR_image technically uses EGLImageKHR instead of EGLImage, but they're compatible
// so we swap them here to avoid mixing them all over the place
typedef EGLImage (EGLAPIENTRYP PFNEGLCREATEIMAGEKHRPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay dpy, EGLImage image);
#endif
#if !defined(EGL_EXT_platform_base) || !defined(EGL_EGLEXT_PROTOTYPES)
typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list);
#endif
#ifndef EGL_EXT_image_dma_buf_import
#define EGL_LINUX_DMA_BUF_EXT 0x3270
#define EGL_LINUX_DRM_FOURCC_EXT 0x3271
#define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272
#define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273
#define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274
#endif
#ifndef EGL_EXT_image_dma_buf_import_modifiers
#define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443
#define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444
#endif
#define EGL_MAX_PLANES 4
class EGLExtensions {
@@ -65,7 +101,7 @@ public:
return true;
}
virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) {
virtual AVPixelFormat getPreferredPixelFormat(int videoFormat) {
if (videoFormat == VIDEO_FORMAT_H265_MAIN10) {
// 10-bit YUV 4:2:0
return AV_PIX_FMT_P010;
@@ -76,7 +112,7 @@ public:
}
}
virtual bool isPixelFormatSupported(int videoFormat, enum AVPixelFormat pixelFormat) {
virtual bool isPixelFormatSupported(int videoFormat, AVPixelFormat pixelFormat) {
// By default, we only support the preferred pixel format
return getPreferredPixelFormat(videoFormat) == pixelFormat;
}
@@ -92,6 +128,10 @@ public:
return false;
}
virtual AVPixelFormat getEGLImagePixelFormat() {
return AV_PIX_FMT_NONE;
}
virtual bool initializeEGL(EGLDisplay,
const EGLExtensions &) {
return false;
+81 -64
View File
@@ -5,22 +5,23 @@
#include <streaming/streamutils.h>
#include <SDL_syswm.h>
#ifdef HAVE_EGL
#include <SDL_egl.h>
#endif
#include <unistd.h>
#include <fcntl.h>
VAAPIRenderer::VAAPIRenderer()
: m_HwContext(nullptr),
m_DrmFd(-1),
m_BlacklistedForDirectRendering(false)
{
#ifdef HAVE_EGL
m_PrimeDescriptor.num_layers = 0;
m_PrimeDescriptor.num_objects = 0;
m_EGLExtDmaBuf = false;
m_eglCreateImage = nullptr;
m_eglCreateImageKHR = nullptr;
m_eglDestroyImage = nullptr;
m_eglDestroyImageKHR = nullptr;
#endif
}
@@ -39,10 +40,6 @@ VAAPIRenderer::~VAAPIRenderer()
vaTerminate(display);
}
}
if (m_DrmFd != -1) {
close(m_DrmFd);
}
}
VADisplay
@@ -90,41 +87,17 @@ VAAPIRenderer::openDisplay(SDL_Window* window)
return nullptr;
#endif
}
// TODO: Upstream a better solution for SDL_GetWindowWMInfo on KMSDRM
else if (strcmp(SDL_GetCurrentVideoDriver(), "KMSDRM") == 0) {
#ifdef HAVE_LIBVA_DRM
if (m_DrmFd < 0) {
const char* device = SDL_getenv("DRM_DEV");
if (device == nullptr) {
device = "/dev/dri/card0";
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Opening DRM device: %s",
device);
m_DrmFd = open(device, O_RDWR | O_CLOEXEC);
if (m_DrmFd < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to open DRM device: %d",
errno);
return nullptr;
}
}
display = vaGetDisplayDRM(m_DrmFd);
#if defined(SDL_VIDEO_DRIVER_KMSDRM) && defined(HAVE_LIBVA_DRM) && SDL_VERSION_ATLEAST(2, 0, 15)
else if (info.subsystem == SDL_SYSWM_KMSDRM) {
SDL_assert(info.info.kmsdrm.drm_fd >= 0);
display = vaGetDisplayDRM(info.info.kmsdrm.drm_fd);
if (display == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Unable to open DRM display for VAAPI");
return nullptr;
}
#else
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Moonlight not compiled with VAAPI DRM support!");
return nullptr;
#endif
}
#endif
else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Unsupported VAAPI rendering subsystem: %d",
@@ -259,12 +232,12 @@ VAAPIRenderer::initialize(PDECODER_PARAMETERS params)
"Driver: %s",
vendorString ? vendorString : "<unknown>");
// AMD's Gallium VAAPI driver has a nasty memory leak that causes memory
// to be leaked for each submitted frame. The Flatpak runtime has a VDPAU
// driver in place that works well, so use that instead on AMD systems. If
// we're using Wayland, we have no choice but to use VAAPI because VDPAU
// Older versions of the Gallium VAAPI driver have a nasty memory leak that
// causes memory to be leaked for each submitted frame. I believe this is
// resolved in the libva2 drivers (VAAPI 1.x).
// If we're using Wayland, we have no choice but to use VAAPI because VDPAU
// is only supported under X11 or XWayland.
if (qgetenv("FORCE_VAAPI") != "1" && !WMUtils::isRunningWayland()) {
if (major == 0 && qgetenv("FORCE_VAAPI") != "1" && !WMUtils::isRunningWayland()) {
if (vendorStr.contains("AMD", Qt::CaseInsensitive) ||
vendorStr.contains("Radeon", Qt::CaseInsensitive)) {
// Fail and let VDPAU pick this up
@@ -510,6 +483,10 @@ VAAPIRenderer::canExportEGL() {
return true;
}
AVPixelFormat VAAPIRenderer::getEGLImagePixelFormat() {
return AV_PIX_FMT_NV12;
}
bool
VAAPIRenderer::initializeEGL(EGLDisplay,
const EGLExtensions &ext) {
@@ -519,6 +496,20 @@ VAAPIRenderer::initializeEGL(EGLDisplay,
return false;
}
m_EGLExtDmaBuf = ext.isSupported("EGL_EXT_image_dma_buf_import_modifiers");
// NB: eglCreateImage() and eglCreateImageKHR() have slightly different definitions
m_eglCreateImage = (typeof(m_eglCreateImage))eglGetProcAddress("eglCreateImage");
m_eglCreateImageKHR = (typeof(m_eglCreateImageKHR))eglGetProcAddress("eglCreateImageKHR");
m_eglDestroyImage = (typeof(m_eglDestroyImage))eglGetProcAddress("eglDestroyImage");
m_eglDestroyImageKHR = (typeof(m_eglDestroyImageKHR))eglGetProcAddress("eglDestroyImageKHR");
if (!(m_eglCreateImage && m_eglDestroyImage) &&
!(m_eglCreateImageKHR && m_eglDestroyImageKHR)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Missing eglCreateImage()/eglDestroyImage() in EGL driver");
return false;
}
return true;
}
@@ -554,33 +545,54 @@ VAAPIRenderer::exportEGLImages(AVFrame *frame, EGLDisplay dpy,
const auto &layer = m_PrimeDescriptor.layers[i];
const auto &object = m_PrimeDescriptor.objects[layer.object_index[0]];
EGLAttrib attribs[17] = {
EGL_LINUX_DRM_FOURCC_EXT, (EGLint)layer.drm_format,
const int EGL_ATTRIB_COUNT = 17;
EGLAttrib attribs[EGL_ATTRIB_COUNT] = {
EGL_LINUX_DRM_FOURCC_EXT, layer.drm_format,
EGL_WIDTH, i == 0 ? frame->width : frame->width / 2,
EGL_HEIGHT, i == 0 ? frame->height : frame->height / 2,
EGL_DMA_BUF_PLANE0_FD_EXT, object.fd,
EGL_DMA_BUF_PLANE0_OFFSET_EXT, (EGLint)layer.offset[0],
EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)layer.pitch[0],
EGL_NONE,
EGL_DMA_BUF_PLANE0_OFFSET_EXT, layer.offset[0],
EGL_DMA_BUF_PLANE0_PITCH_EXT, layer.pitch[0],
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, (EGLint)object.drm_format_modifier,
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, (EGLint)(object.drm_format_modifier >> 32),
EGL_NONE
};
if (m_EGLExtDmaBuf) {
const EGLAttrib extra[] = {
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
(EGLint)object.drm_format_modifier,
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
(EGLint)(object.drm_format_modifier >> 32),
EGL_NONE,
};
memcpy((void *)(&attribs[12]), (void *)extra, sizeof (extra));
// Cut off the attribute array before the modifiers if they aren't supported
if (!m_EGLExtDmaBuf) {
SDL_assert(attribs[12] == EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT);
SDL_assert(attribs[14] == EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT);
SDL_assert(attribs[16] == EGL_NONE);
attribs[12] = EGL_NONE;
}
images[i] = eglCreateImage(dpy, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT,
nullptr, attribs);
if (!images[i]) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"eglCreateImage() Failed: %d", eglGetError());
goto create_image_fail;
if (m_eglCreateImage) {
images[i] = m_eglCreateImage(dpy, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT,
nullptr, attribs);
if (!images[i]) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"eglCreateImage() Failed: %d", eglGetError());
goto create_image_fail;
}
}
else {
// Cast the EGLAttrib array elements to EGLint for the KHR extension
EGLint intAttribs[EGL_ATTRIB_COUNT];
for (int i = 0; i < EGL_ATTRIB_COUNT; i++) {
intAttribs[i] = (EGLint)attribs[i];
}
images[i] = m_eglCreateImageKHR(dpy, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT,
nullptr, intAttribs);
if (!images[i]) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"eglCreateImageKHR() Failed: %d", eglGetError());
goto create_image_fail;
}
}
++count;
}
return count;
@@ -595,7 +607,12 @@ sync_fail:
void
VAAPIRenderer::freeEGLImages(EGLDisplay dpy, EGLImage images[EGL_MAX_PLANES]) {
for (size_t i = 0; i < m_PrimeDescriptor.num_layers; ++i) {
eglDestroyImage(dpy, images[i]);
if (m_eglDestroyImage) {
m_eglDestroyImage(dpy, images[i]);
}
else {
m_eglDestroyImageKHR(dpy, images[i]);
}
}
for (size_t i = 0; i < m_PrimeDescriptor.num_objects; ++i) {
close(m_PrimeDescriptor.objects[i].fd);
+5 -1
View File
@@ -44,6 +44,7 @@ public:
virtual int getDecoderColorspace() override;
#ifdef HAVE_EGL
virtual bool canExportEGL() override;
virtual AVPixelFormat getEGLImagePixelFormat() override;
virtual bool initializeEGL(EGLDisplay dpy, const EGLExtensions &ext) override;
virtual ssize_t exportEGLImages(AVFrame *frame, EGLDisplay dpy, EGLImage images[EGL_MAX_PLANES]) override;
virtual void freeEGLImages(EGLDisplay dpy, EGLImage[EGL_MAX_PLANES]) override;
@@ -54,7 +55,6 @@ private:
int m_WindowSystem;
AVBufferRef* m_HwContext;
int m_DrmFd;
bool m_BlacklistedForDirectRendering;
#ifdef HAVE_LIBVA_X11
@@ -69,5 +69,9 @@ private:
#ifdef HAVE_EGL
VADRMPRIMESurfaceDescriptor m_PrimeDescriptor;
bool m_EGLExtDmaBuf;
PFNEGLCREATEIMAGEPROC m_eglCreateImage;
PFNEGLDESTROYIMAGEPROC m_eglDestroyImage;
PFNEGLCREATEIMAGEKHRPROC m_eglCreateImageKHR;
PFNEGLDESTROYIMAGEKHRPROC m_eglDestroyImageKHR;
#endif
};
+5 -1
View File
@@ -281,8 +281,8 @@ public:
// Queue this sample for the next v-sync
CMSampleTimingInfo timingInfo = {
.duration = kCMTimeInvalid,
.presentationTimeStamp = CMClockMakeHostTimeFromSystemUnits(mach_absolute_time()),
.decodeTimeStamp = kCMTimeInvalid,
.presentationTimeStamp = CMClockMakeHostTimeFromSystemUnits(mach_absolute_time())
};
CMSampleBufferRef sampleBuffer;
@@ -365,6 +365,7 @@ public:
if ([device.name containsString:@" 5"]) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"No HEVC Main10 support on Skylake iGPU");
[device release];
return false;
}
}
@@ -375,10 +376,13 @@ public:
[device.name containsString:@" M3"]) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"No HEVC Main10 support on AMD GPUs until Polaris");
[device release];
return false;
}
}
}
[device release];
}
else
#endif
+121 -92
View File
@@ -51,7 +51,7 @@ bool FFmpegVideoDecoder::isHardwareAccelerated()
bool FFmpegVideoDecoder::isAlwaysFullScreen()
{
return m_BackendRenderer->getRendererAttributes() & RENDERER_ATTRIBUTE_FULLSCREEN_ONLY;
return m_FrontendRenderer->getRendererAttributes() & RENDERER_ATTRIBUTE_FULLSCREEN_ONLY;
}
int FFmpegVideoDecoder::getDecoderCapabilities()
@@ -117,7 +117,8 @@ enum AVPixelFormat FFmpegVideoDecoder::ffGetFormat(AVCodecContext* context,
}
FFmpegVideoDecoder::FFmpegVideoDecoder(bool testOnly)
: m_VideoDecoderCtx(nullptr),
: m_Pkt(av_packet_alloc()),
m_VideoDecoderCtx(nullptr),
m_DecodeBuffer(1024 * 1024, 0),
m_HwDecodeCfg(nullptr),
m_BackendRenderer(nullptr),
@@ -132,8 +133,6 @@ FFmpegVideoDecoder::FFmpegVideoDecoder(bool testOnly)
m_NeedsSpsFixup(false),
m_TestOnly(testOnly)
{
av_init_packet(&m_Pkt);
SDL_zero(m_ActiveWndVideoStats);
SDL_zero(m_LastWndVideoStats);
SDL_zero(m_GlobalVideoStats);
@@ -151,6 +150,8 @@ FFmpegVideoDecoder::~FFmpegVideoDecoder()
// to preserve the log level across reset() during
// test initialization.
av_log_set_level(AV_LOG_INFO);
av_packet_free(&m_Pkt);
}
IFFmpegRenderer* FFmpegVideoDecoder::getBackendRenderer()
@@ -311,16 +312,16 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAM
if (testFrame) {
switch (params->videoFormat) {
case VIDEO_FORMAT_H264:
m_Pkt.data = (uint8_t*)k_H264TestFrame;
m_Pkt.size = sizeof(k_H264TestFrame);
m_Pkt->data = (uint8_t*)k_H264TestFrame;
m_Pkt->size = sizeof(k_H264TestFrame);
break;
case VIDEO_FORMAT_H265:
m_Pkt.data = (uint8_t*)k_HEVCMainTestFrame;
m_Pkt.size = sizeof(k_HEVCMainTestFrame);
m_Pkt->data = (uint8_t*)k_HEVCMainTestFrame;
m_Pkt->size = sizeof(k_HEVCMainTestFrame);
break;
case VIDEO_FORMAT_H265_MAIN10:
m_Pkt.data = (uint8_t*)k_HEVCMain10TestFrame;
m_Pkt.size = sizeof(k_HEVCMain10TestFrame);
m_Pkt->data = (uint8_t*)k_HEVCMain10TestFrame;
m_Pkt->size = sizeof(k_HEVCMain10TestFrame);
break;
default:
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@@ -341,7 +342,7 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAM
for (int retries = 0; retries < 5; retries++) {
// Most FFmpeg decoders process input using a "push" model.
// We'll see those fail here if the format is not supported.
err = avcodec_send_packet(m_VideoDecoderCtx, &m_Pkt);
err = avcodec_send_packet(m_VideoDecoderCtx, m_Pkt);
if (err < 0) {
av_frame_free(&frame);
char errorstring[512];
@@ -522,6 +523,10 @@ IFFmpegRenderer* FFmpegVideoDecoder::createHwAccelRenderer(const AVCodecHWConfig
#ifdef HAVE_LIBVDPAU
case AV_HWDEVICE_TYPE_VDPAU:
return new VDPAURenderer();
#endif
#ifdef HAVE_DRM
case AV_HWDEVICE_TYPE_DRM:
return new DrmRenderer();
#endif
default:
return nullptr;
@@ -589,27 +594,107 @@ bool FFmpegVideoDecoder::tryInitializeRenderer(AVCodec* decoder,
return false;
}
#define TRY_PREFERRED_PIXEL_FORMAT(RENDERER_TYPE) \
{ \
RENDERER_TYPE renderer; \
if (renderer.getPreferredPixelFormat(params->videoFormat) == decoder->pix_fmts[i]) { \
if (tryInitializeRenderer(decoder, params, nullptr, \
[]() -> IFFmpegRenderer* { return new RENDERER_TYPE(); })) { \
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, \
"Chose " #RENDERER_TYPE " for codec %s due to preferred pixel format: 0x%x", \
decoder->name, decoder->pix_fmts[i]); \
return true; \
} \
} \
}
#define TRY_SUPPORTED_PIXEL_FORMAT(RENDERER_TYPE) \
{ \
RENDERER_TYPE renderer; \
if (renderer.isPixelFormatSupported(params->videoFormat, decoder->pix_fmts[i])) { \
if (tryInitializeRenderer(decoder, params, nullptr, \
[]() -> IFFmpegRenderer* { return new RENDERER_TYPE(); })) { \
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, \
"Chose " #RENDERER_TYPE " for codec %s due to compatible pixel format: 0x%x", \
decoder->name, decoder->pix_fmts[i]); \
return true; \
} \
} \
}
bool FFmpegVideoDecoder::tryInitializeRendererForDecoderByName(const char *decoderName,
PDECODER_PARAMETERS params)
{
AVCodec* decoder = avcodec_find_decoder_by_name(decoderName);
if (decoder == nullptr) {
return false;
}
// This might be a hwaccel decoder, so try any hw configs first
for (int i = 0;; i++) {
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
if (!config) {
// No remaing hwaccel options
break;
}
// Initialize the hardware codec and submit a test frame if the renderer needs it
if (tryInitializeRenderer(decoder, params, config,
[config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 0); })) {
return true;
}
}
if (decoder->pix_fmts == NULL) {
// Supported output pixel formats are unknown. We'll just try SDL and hope it can cope.
return tryInitializeRenderer(decoder, params, nullptr,
[]() -> IFFmpegRenderer* { return new SdlRenderer(); });
}
// Check if any of our decoders prefer any of the pixel formats first
for (int i = 0; decoder->pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
#ifdef HAVE_DRM
TRY_PREFERRED_PIXEL_FORMAT(DrmRenderer);
#endif
#ifdef HAVE_MMAL
TRY_PREFERRED_PIXEL_FORMAT(MmalRenderer);
#endif
TRY_PREFERRED_PIXEL_FORMAT(SdlRenderer);
}
// Nothing prefers any of them. Let's see if anyone will tolerate one.
for (int i = 0; decoder->pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
#ifdef HAVE_DRM
TRY_SUPPORTED_PIXEL_FORMAT(DrmRenderer);
#endif
#ifdef HAVE_MMAL
TRY_SUPPORTED_PIXEL_FORMAT(MmalRenderer);
#endif
TRY_SUPPORTED_PIXEL_FORMAT(SdlRenderer);
}
// If we made it here, we couldn't find anything
return false;
}
bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
{
// Increase log level until the first frame is decoded
av_log_set_level(AV_LOG_DEBUG);
// First try decoders that the user has manually specified via environment variables.
// These must output surfaces in one of the formats that the SDL renderer supports,
// These must output surfaces in one of the formats that one of our renderers supports,
// which is currently:
// - AV_PIX_FMT_YUV420P (preferred)
// - AV_PIX_FMT_DRM_PRIME
// - AV_PIX_FMT_MMAL
// - AV_PIX_FMT_YUV420P
// - AV_PIX_FMT_NV12
// - AV_PIX_FMT_NV21
// These formats should cover most/all decoders that output in a standard YUV format.
{
QString h264DecoderHint = qgetenv("H264_DECODER_HINT");
if (!h264DecoderHint.isEmpty() && (params->videoFormat & VIDEO_FORMAT_MASK_H264)) {
QByteArray decoderString = h264DecoderHint.toLocal8Bit();
AVCodec* customAvcDecoder = avcodec_find_decoder_by_name(decoderString.constData());
if (customAvcDecoder != nullptr &&
tryInitializeRenderer(customAvcDecoder, params, nullptr,
[]() -> IFFmpegRenderer* { return new SdlRenderer(); })) {
if (tryInitializeRendererForDecoderByName(decoderString.constData(), params)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Using custom H.264 decoder (H264_DECODER_HINT): %s",
decoderString.constData());
@@ -626,11 +711,7 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
QString hevcDecoderHint = qgetenv("HEVC_DECODER_HINT");
if (!hevcDecoderHint.isEmpty() && (params->videoFormat & VIDEO_FORMAT_MASK_H265)) {
QByteArray decoderString = hevcDecoderHint.toLocal8Bit();
AVCodec* customHevcDecoder = avcodec_find_decoder_by_name(decoderString.constData());
if (customHevcDecoder != nullptr &&
tryInitializeRenderer(customHevcDecoder, params, nullptr,
[]() -> IFFmpegRenderer* { return new SdlRenderer(); })) {
if (tryInitializeRendererForDecoderByName(decoderString.constData(), params)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Using custom HEVC decoder (HEVC_DECODER_HINT): %s",
decoderString.constData());
@@ -682,74 +763,22 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
}
// Continue with special non-hwaccel hardware decoders
#ifdef HAVE_MMAL
// MMAL is the decoder for the Raspberry Pi
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
AVCodec* mmalDecoder = avcodec_find_decoder_by_name("h264_mmal");
if (mmalDecoder != nullptr &&
tryInitializeRenderer(mmalDecoder, params, nullptr,
[]() -> IFFmpegRenderer* { return new MmalRenderer(); })) {
return true;
QList<const char *> knownAvcCodecs = { "h264_mmal", "h264_rkmpp", "h264_nvmpi", "h264_v4l2m2m" };
for (const char* codec : knownAvcCodecs) {
if (tryInitializeRendererForDecoderByName(codec, params)) {
return true;
}
}
}
#endif
#ifdef HAVE_DRM
{
// RKMPP is a hardware accelerated decoder that outputs DRI PRIME buffers
AVCodec* rkmppDecoder;
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
rkmppDecoder = avcodec_find_decoder_by_name("h264_rkmpp");
}
else {
rkmppDecoder = avcodec_find_decoder_by_name("hevc_rkmpp");
}
if (rkmppDecoder != nullptr &&
tryInitializeRenderer(rkmppDecoder, params, nullptr,
[]() -> IFFmpegRenderer* { return new DrmRenderer(); })) {
return true;
else {
QList<const char *> knownHevcCodecs = { "hevc_rkmpp", "hevc_nvmpi", "hevc_v4l2m2m" };
for (const char* codec : knownHevcCodecs) {
if (tryInitializeRendererForDecoderByName(codec, params)) {
return true;
}
}
}
#endif
#ifdef Q_OS_LINUX
{
AVCodec* nvmpiDecoder;
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
nvmpiDecoder = avcodec_find_decoder_by_name("h264_nvmpi");
}
else {
nvmpiDecoder = avcodec_find_decoder_by_name("hevc_nvmpi");
}
if (nvmpiDecoder != nullptr &&
tryInitializeRenderer(nvmpiDecoder, params, nullptr,
[]() -> IFFmpegRenderer* { return new SdlRenderer(); })) {
return true;
}
}
{
AVCodec* v4l2Decoder;
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
v4l2Decoder = avcodec_find_decoder_by_name("h264_v4l2m2m");
}
else {
v4l2Decoder = avcodec_find_decoder_by_name("hevc_v4l2m2m");
}
if (v4l2Decoder != nullptr &&
tryInitializeRenderer(v4l2Decoder, params, nullptr,
[]() -> IFFmpegRenderer* { return new SdlRenderer(); })) {
return true;
}
}
#endif
// Look for the first matching hwaccel hardware decoder (pass 1)
// This picks up "second-tier" hwaccels like CUDA.
@@ -881,19 +910,19 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
entry = entry->next;
}
m_Pkt.data = reinterpret_cast<uint8_t*>(m_DecodeBuffer.data());
m_Pkt.size = offset;
m_Pkt->data = reinterpret_cast<uint8_t*>(m_DecodeBuffer.data());
m_Pkt->size = offset;
if (du->frameType == FRAME_TYPE_IDR) {
m_Pkt.flags = AV_PKT_FLAG_KEY;
m_Pkt->flags = AV_PKT_FLAG_KEY;
}
else {
m_Pkt.flags = 0;
m_Pkt->flags = 0;
}
m_ActiveWndVideoStats.totalReassemblyTime += du->enqueueTimeMs - du->receiveTimeMs;
err = avcodec_send_packet(m_VideoDecoderCtx, &m_Pkt);
err = avcodec_send_packet(m_VideoDecoderCtx, m_Pkt);
if (err < 0) {
char errorstring[512];
av_strerror(err, errorstring, sizeof(errorstring));
+4 -1
View File
@@ -36,6 +36,9 @@ private:
bool createFrontendRenderer(PDECODER_PARAMETERS params);
bool tryInitializeRendererForDecoderByName(const char* decoderName,
PDECODER_PARAMETERS params);
bool tryInitializeRenderer(AVCodec* decoder,
PDECODER_PARAMETERS params,
const AVCodecHWConfig* hwConfig,
@@ -51,7 +54,7 @@ private:
enum AVPixelFormat ffGetFormat(AVCodecContext* context,
const enum AVPixelFormat* pixFmts);
AVPacket m_Pkt;
AVPacket* m_Pkt;
AVCodecContext* m_VideoDecoderCtx;
QByteArray m_DecodeBuffer;
const AVCodecHWConfig* m_HwDecodeCfg;
+1
View File
@@ -7,4 +7,5 @@ namespace WMUtils {
bool isRunningX11();
bool isRunningWayland();
bool isRunningWindowManager();
bool isRunningDesktopEnvironment();
}
+1 -1
View File
@@ -1 +1 @@
3.0.0
3.1.0
+58 -8
View File
@@ -2,6 +2,8 @@
#include "utils.h"
#include <SDL.h>
#ifdef HAS_X11
#include <X11/Xlib.h>
#endif
@@ -10,14 +12,30 @@
#include <wayland-client.h>
#endif
#define VALUE_SET 0x01
#define VALUE_TRUE 0x02
bool WMUtils::isRunningX11()
{
#ifdef HAS_X11
Display* display = XOpenDisplay(nullptr);
if (display != nullptr) {
XCloseDisplay(display);
return true;
static SDL_atomic_t isRunningOnX11;
// If the value is not set yet, populate it now.
int val = SDL_AtomicGet(&isRunningOnX11);
if (!(val & VALUE_SET)) {
Display* display = XOpenDisplay(nullptr);
if (display != nullptr) {
XCloseDisplay(display);
}
// Populate the value to return and have for next time.
// This can race with another thread populating the same data,
// but that's no big deal.
val = VALUE_SET | ((display != nullptr) ? VALUE_TRUE : 0);
SDL_AtomicSet(&isRunningOnX11, val);
}
return !!(val & VALUE_TRUE);
#endif
return false;
@@ -26,11 +44,24 @@ bool WMUtils::isRunningX11()
bool WMUtils::isRunningWayland()
{
#ifdef HAS_WAYLAND
struct wl_display* display = wl_display_connect(nullptr);
if (display != nullptr) {
wl_display_disconnect(display);
return true;
static SDL_atomic_t isRunningOnWayland;
// If the value is not set yet, populate it now.
int val = SDL_AtomicGet(&isRunningOnWayland);
if (!(val & VALUE_SET)) {
struct wl_display* display = wl_display_connect(nullptr);
if (display != nullptr) {
wl_display_disconnect(display);
}
// Populate the value to return and have for next time.
// This can race with another thread populating the same data,
// but that's no big deal.
val = VALUE_SET | ((display != nullptr) ? VALUE_TRUE : 0);
SDL_AtomicSet(&isRunningOnWayland, val);
}
return !!(val & VALUE_TRUE);
#endif
return false;
@@ -46,3 +77,22 @@ bool WMUtils::isRunningWindowManager()
return WMUtils::isRunningWayland() || WMUtils::isRunningX11();
#endif
}
bool WMUtils::isRunningDesktopEnvironment()
{
if (qEnvironmentVariableIsSet("HAS_DESKTOP_ENVIRONMENT")) {
return qEnvironmentVariableIntValue("HAS_DESKTOP_ENVIRONMENT");
}
#if defined(Q_OS_WIN) || defined(Q_OS_DARWIN)
// Windows and macOS are always running a desktop environment
return true;
#elif defined(EMBEDDED_BUILD)
// Embedded systems don't run desktop environments
return false;
#else
// On non-embedded systems, assume we have a desktop environment
// if we have a WM running.
return isRunningWindowManager();
#endif
}
+5 -5
View File
@@ -17,14 +17,12 @@ environment:
install:
- sh: '[ "$BUILD_TARGET" != macos ] || nvm use node'
- sh: '[ "$BUILD_TARGET" != macos ] || npm install --global create-dmg'
- sh: '[ "$BUILD_TARGET" != macos ] || brew install qt'
- sh: '[ "$BUILD_TARGET" != macos ] || brew link --force qt'
- sh: '[ "$BUILD_TARGET" != steamlink ] || sudo apt install -y libc6:i386 libstdc++6:i386'
- sh: '[ "$BUILD_TARGET" != steamlink ] || git clone --depth=1 https://github.com/ValveSoftware/steamlink-sdk.git $HOME/steamlink-sdk'
- sh: '[ "$BUILD_TARGET" != linux ] || sudo add-apt-repository ppa:beineri/opt-qt-5.14.2-xenial'
- sh: '[ "$BUILD_TARGET" != linux ] || sudo apt update'
- sh: '[ "$BUILD_TARGET" != linux ] || sudo apt install -y qt514base qt514quickcontrols2 qt514svg qt514wayland nasm libgbm-dev libdrm-dev libfreetype6-dev libasound2-dev libdbus-1-dev libegl1-mesa-dev libgl1-mesa-dev libgles2-mesa-dev libglu1-mesa-dev libibus-1.0-dev libpulse-dev libudev-dev libx11-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev libxkbcommon-dev libxrandr-dev libxss-dev libxt-dev libxv-dev libxxf86vm-dev wayland-protocols libopus-dev libvdpau-dev'
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export SDL2_VER=2.0.14 && wget https://www.libsdl.org/release/SDL2-$SDL2_VER.tar.gz && tar -xvf SDL2-$SDL2_VER.tar.gz && cd SDL2-$SDL2_VER && ./configure && make -j$(nproc) && sudo make install && cd ..; fi'
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export SDL2_REV=dfa64eadd38a6a8125290dcf121f1bc285e98dad && git clone https://github.com/libsdl-org/SDL.git SDL2 && cd SDL2 && git checkout $SDL2_REV && ./configure --enable-video-kmsdrm && make -j$(nproc) && sudo make install && cd ..; fi'
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export SDL2_TTF_VER=2.0.15 && wget https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-$SDL2_TTF_VER.tar.gz && tar -xvf SDL2_ttf-$SDL2_TTF_VER.tar.gz && cd SDL2_ttf-$SDL2_TTF_VER && ./configure && make -j$(nproc) && sudo make install && cd ..; fi'
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export LIBVA_VER=2.10.0 && wget https://github.com/intel/libva/releases/download/$LIBVA_VER/libva-$LIBVA_VER.tar.bz2 && tar -xvf libva-$LIBVA_VER.tar.bz2 && cd libva-$LIBVA_VER && ./configure && make -j$(nproc) && sudo make install && cd ..; fi'
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export NVHDR_VER=8.1.24.12 && wget https://github.com/FFmpeg/nv-codec-headers/releases/download/n$NVHDR_VER/nv-codec-headers-$NVHDR_VER.tar.gz && tar -xvf nv-codec-headers-$NVHDR_VER.tar.gz && cd nv-codec-headers-$NVHDR_VER && sudo make install && cd ..; fi'
@@ -32,7 +30,9 @@ install:
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then mkdir $HOME/bin && wget -O $HOME/bin/linuxdeployqt https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage && chmod a+x $HOME/bin/linuxdeployqt; fi'
before_build:
- 'git submodule update --init --recursive'
- 'git -c submodule.libs.update=none submodule update --init --recursive'
- cmd: 'git submodule update --init --recursive'
- sh: '[ "$BUILD_TARGET" != macos ] || git submodule update --init --recursive'
- cmd: 'set OLDPATH=%PATH%'
- cmd: 'set /p VERSION=<app\version.txt'
- sh: 'export VERSION=`cat app/version.txt`'
@@ -45,7 +45,7 @@ build_script:
- cmd: 'scripts\generate-bundle.bat Release'
- sh: '[ "$BUILD_TARGET" != linux ] || source /opt/qt514/bin/qt514-env.sh'
- sh: '[ "$BUILD_TARGET" != linux ] || PATH=$PATH:$HOME/bin scripts/build-appimage.sh'
- sh: '[ "$BUILD_TARGET" != macos ] || scripts/generate-dmg.sh Release'
- sh: '[ "$BUILD_TARGET" != macos ] || PATH=$PATH:$HOME/Qt/5.15/clang_64/bin scripts/generate-dmg.sh Release'
- sh: '[ "$BUILD_TARGET" != steamlink ] || STEAMLINK_SDK_PATH=$HOME/steamlink-sdk scripts/build-steamlink-app.sh'
after_build:
Submodule
+1
Submodule libs added at db54134a65
@@ -1 +0,0 @@
Versions/Current/Headers
@@ -1 +0,0 @@
Versions/Current/Resources
-1
View File
@@ -1 +0,0 @@
Versions/Current/SDL2
@@ -1 +0,0 @@
Versions/Current/Resources
@@ -1,16 +0,0 @@
HIDAPI Authors:
Alan Ott <alan@signal11.us>:
Original Author and Maintainer
Linux, Windows, and Mac implementations
Ludovic Rousseau <rousseau@debian.org>:
Formatting for Doxygen documentation
Bug fixes
Correctness fixes
For a comprehensive list of contributions, see the commit list at github:
https://github.com/libusb/hidapi/commits/master
@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>19H114</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>hidapi</string>
<key>CFBundleIdentifier</key>
<string>org.libsdl.hidapi</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>hidapi</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>12C33</string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>11.1</string>
<key>DTSDKBuild</key>
<string>20C63</string>
<key>DTSDKName</key>
<string>macosx11.1</string>
<key>DTXcode</key>
<string>1230</string>
<key>DTXcodeBuild</key>
<string>12C33</string>
<key>LSMinimumSystemVersion</key>
<string>10.6</string>
</dict>
</plist>
@@ -1,26 +0,0 @@
Copyright (c) 2010, Alan Ott, Signal 11 Software
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Signal 11 Software nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
@@ -1,9 +0,0 @@
HIDAPI - Multi-Platform library for
communication with HID devices.
Copyright 2009, Alan Ott, Signal 11 Software.
All Rights Reserved.
This software may be used by anyone for any reason so
long as the copyright notice in the source files
remains intact.
@@ -1,13 +0,0 @@
HIDAPI can be used under one of three licenses.
1. The GNU General Public License, version 3.0, in LICENSE-gpl3.txt
2. A BSD-Style License, in LICENSE-bsd.txt.
3. The more liberal original HIDAPI license. LICENSE-orig.txt
The license chosen is at the discretion of the user of HIDAPI. For example:
1. An author of GPL software would likely use HIDAPI under the terms of the
GPL.
2. An author of commercial closed-source software would likely use HIDAPI
under the terms of the BSD-style license or the original HIDAPI license.
@@ -1,207 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Resources/AUTHORS.txt</key>
<data>
MKG+c/Ng8ZXbj7p1G5WReTJm06Y=
</data>
<key>Resources/Info.plist</key>
<data>
8Yk54YzUKmLCGTNkmc8LiukTgFM=
</data>
<key>Resources/LICENSE-bsd.txt</key>
<data>
fd5CtMb9r65yLY0HVWttnbpNKWM=
</data>
<key>Resources/LICENSE-gpl3.txt</key>
<data>
hiS82uVbru8AzRHV38+mD2hxCgI=
</data>
<key>Resources/LICENSE-orig.txt</key>
<data>
ZgR9vPP9aJyZRyJm9a0UHFPW8sY=
</data>
<key>Resources/LICENSE.txt</key>
<data>
9VS7iq5RUyXd9/XSbbwcYpQMum4=
</data>
</dict>
<key>files2</key>
<dict>
<key>Resources/AUTHORS.txt</key>
<dict>
<key>hash</key>
<data>
MKG+c/Ng8ZXbj7p1G5WReTJm06Y=
</data>
<key>hash2</key>
<data>
syens6RZflkCVW7D6EB8vdHiiKvUYjt7S1bRTUnoTss=
</data>
</dict>
<key>Resources/Info.plist</key>
<dict>
<key>hash</key>
<data>
8Yk54YzUKmLCGTNkmc8LiukTgFM=
</data>
<key>hash2</key>
<data>
g8uVTrv9wFcDsYV6z37oNILYTKi5mgge59ixh4aBoo4=
</data>
</dict>
<key>Resources/LICENSE-bsd.txt</key>
<dict>
<key>hash</key>
<data>
fd5CtMb9r65yLY0HVWttnbpNKWM=
</data>
<key>hash2</key>
<data>
MOsb7ym0b4unq4tBYDXb2TywNKRUgd2XgVuUQoRYLNI=
</data>
</dict>
<key>Resources/LICENSE-gpl3.txt</key>
<dict>
<key>hash</key>
<data>
hiS82uVbru8AzRHV38+mD2hxCgI=
</data>
<key>hash2</key>
<data>
jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM=
</data>
</dict>
<key>Resources/LICENSE-orig.txt</key>
<dict>
<key>hash</key>
<data>
ZgR9vPP9aJyZRyJm9a0UHFPW8sY=
</data>
<key>hash2</key>
<data>
+1Q2qmPRtxqN+/dOyvGltOHsTff4AHTRH+yZKE9pyl8=
</data>
</dict>
<key>Resources/LICENSE.txt</key>
<dict>
<key>hash</key>
<data>
9VS7iq5RUyXd9/XSbbwcYpQMum4=
</data>
<key>hash2</key>
<data>
fTsIfDTzXU1TjjvN3R/49m6S+e8zaIGZlIKADd+ECRM=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^Resources/</key>
<true/>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^.*</key>
<true/>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^[^/]+$</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>
@@ -1 +0,0 @@
Versions/Current/hidapi
@@ -1,138 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL.h
*
* Main include header for the SDL library
*/
#ifndef SDL_h_
#define SDL_h_
#include "SDL_main.h"
#include "SDL_stdinc.h"
#include "SDL_assert.h"
#include "SDL_atomic.h"
#include "SDL_audio.h"
#include "SDL_clipboard.h"
#include "SDL_cpuinfo.h"
#include "SDL_endian.h"
#include "SDL_error.h"
#include "SDL_events.h"
#include "SDL_filesystem.h"
#include "SDL_gamecontroller.h"
#include "SDL_haptic.h"
#include "SDL_hints.h"
#include "SDL_joystick.h"
#include "SDL_loadso.h"
#include "SDL_log.h"
#include "SDL_messagebox.h"
#include "SDL_metal.h"
#include "SDL_mutex.h"
#include "SDL_power.h"
#include "SDL_render.h"
#include "SDL_rwops.h"
#include "SDL_sensor.h"
#include "SDL_shape.h"
#include "SDL_system.h"
#include "SDL_thread.h"
#include "SDL_timer.h"
#include "SDL_version.h"
#include "SDL_video.h"
#include "SDL_locale.h"
#include "SDL_misc.h"
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/* As of version 0.5, SDL is loaded dynamically into the application */
/**
* \name SDL_INIT_*
*
* These are the flags which may be passed to SDL_Init(). You should
* specify the subsystems which you will be using in your application.
*/
/* @{ */
#define SDL_INIT_TIMER 0x00000001u
#define SDL_INIT_AUDIO 0x00000010u
#define SDL_INIT_VIDEO 0x00000020u /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */
#define SDL_INIT_JOYSTICK 0x00000200u /**< SDL_INIT_JOYSTICK implies SDL_INIT_EVENTS */
#define SDL_INIT_HAPTIC 0x00001000u
#define SDL_INIT_GAMECONTROLLER 0x00002000u /**< SDL_INIT_GAMECONTROLLER implies SDL_INIT_JOYSTICK */
#define SDL_INIT_EVENTS 0x00004000u
#define SDL_INIT_SENSOR 0x00008000u
#define SDL_INIT_NOPARACHUTE 0x00100000u /**< compatibility; this flag is ignored. */
#define SDL_INIT_EVERYTHING ( \
SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | \
SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR \
)
/* @} */
/**
* This function initializes the subsystems specified by \c flags
*/
extern DECLSPEC int SDLCALL SDL_Init(Uint32 flags);
/**
* This function initializes specific SDL subsystems
*
* Subsystem initialization is ref-counted, you must call
* SDL_QuitSubSystem() for each SDL_InitSubSystem() to correctly
* shutdown a subsystem manually (or call SDL_Quit() to force shutdown).
* If a subsystem is already loaded then this call will
* increase the ref-count and return.
*/
extern DECLSPEC int SDLCALL SDL_InitSubSystem(Uint32 flags);
/**
* This function cleans up specific SDL subsystems
*/
extern DECLSPEC void SDLCALL SDL_QuitSubSystem(Uint32 flags);
/**
* This function returns a mask of the specified subsystems which have
* previously been initialized.
*
* If \c flags is 0, it returns a mask of all initialized subsystems.
*/
extern DECLSPEC Uint32 SDLCALL SDL_WasInit(Uint32 flags);
/**
* This function cleans up all initialized subsystems. You should
* call it upon all exit conditions.
*/
extern DECLSPEC void SDLCALL SDL_Quit(void);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_h_ */
/* vi: set ts=4 sw=4 expandtab: */
@@ -1,295 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_assert_h_
#define SDL_assert_h_
#include "SDL_config.h"
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
#ifndef SDL_ASSERT_LEVEL
#ifdef SDL_DEFAULT_ASSERT_LEVEL
#define SDL_ASSERT_LEVEL SDL_DEFAULT_ASSERT_LEVEL
#elif defined(_DEBUG) || defined(DEBUG) || \
(defined(__GNUC__) && !defined(__OPTIMIZE__))
#define SDL_ASSERT_LEVEL 2
#else
#define SDL_ASSERT_LEVEL 1
#endif
#endif /* SDL_ASSERT_LEVEL */
/*
These are macros and not first class functions so that the debugger breaks
on the assertion line and not in some random guts of SDL, and so each
assert can have unique static variables associated with it.
*/
#if defined(_MSC_VER)
/* Don't include intrin.h here because it contains C++ code */
extern void __cdecl __debugbreak(void);
#define SDL_TriggerBreakpoint() __debugbreak()
#elif ( (!defined(__NACL__)) && ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))) )
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "int $3\n\t" )
#elif ( defined(__APPLE__) && (defined(__arm64__) || defined(__aarch64__)) ) /* this might work on other ARM targets, but this is a known quantity... */
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "brk #22\n\t" )
#elif defined(__APPLE__) && defined(__arm__)
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "bkpt #22\n\t" )
#elif defined(__386__) && defined(__WATCOMC__)
#define SDL_TriggerBreakpoint() { _asm { int 0x03 } }
#elif defined(HAVE_SIGNAL_H) && !defined(__WATCOMC__)
#include <signal.h>
#define SDL_TriggerBreakpoint() raise(SIGTRAP)
#else
/* How do we trigger breakpoints on this platform? */
#define SDL_TriggerBreakpoint()
#endif
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 supports __func__ as a standard. */
# define SDL_FUNCTION __func__
#elif ((__GNUC__ >= 2) || defined(_MSC_VER) || defined (__WATCOMC__))
# define SDL_FUNCTION __FUNCTION__
#else
# define SDL_FUNCTION "???"
#endif
#define SDL_FILE __FILE__
#define SDL_LINE __LINE__
/*
sizeof (x) makes the compiler still parse the expression even without
assertions enabled, so the code is always checked at compile time, but
doesn't actually generate code for it, so there are no side effects or
expensive checks at run time, just the constant size of what x WOULD be,
which presumably gets optimized out as unused.
This also solves the problem of...
int somevalue = blah();
SDL_assert(somevalue == 1);
...which would cause compiles to complain that somevalue is unused if we
disable assertions.
*/
/* "while (0,0)" fools Microsoft's compiler's /W4 warning level into thinking
this condition isn't constant. And looks like an owl's face! */
#ifdef _MSC_VER /* stupid /W4 warnings. */
#define SDL_NULL_WHILE_LOOP_CONDITION (0,0)
#else
#define SDL_NULL_WHILE_LOOP_CONDITION (0)
#endif
#define SDL_disabled_assert(condition) \
do { (void) sizeof ((condition)); } while (SDL_NULL_WHILE_LOOP_CONDITION)
typedef enum
{
SDL_ASSERTION_RETRY, /**< Retry the assert immediately. */
SDL_ASSERTION_BREAK, /**< Make the debugger trigger a breakpoint. */
SDL_ASSERTION_ABORT, /**< Terminate the program. */
SDL_ASSERTION_IGNORE, /**< Ignore the assert. */
SDL_ASSERTION_ALWAYS_IGNORE /**< Ignore the assert from now on. */
} SDL_AssertState;
typedef struct SDL_AssertData
{
int always_ignore;
unsigned int trigger_count;
const char *condition;
const char *filename;
int linenum;
const char *function;
const struct SDL_AssertData *next;
} SDL_AssertData;
#if (SDL_ASSERT_LEVEL > 0)
/* Never call this directly. Use the SDL_assert* macros. */
extern DECLSPEC SDL_AssertState SDLCALL SDL_ReportAssertion(SDL_AssertData *,
const char *,
const char *, int)
#if defined(__clang__)
#if __has_feature(attribute_analyzer_noreturn)
/* this tells Clang's static analysis that we're a custom assert function,
and that the analyzer should assume the condition was always true past this
SDL_assert test. */
__attribute__((analyzer_noreturn))
#endif
#endif
;
/* the do {} while(0) avoids dangling else problems:
if (x) SDL_assert(y); else blah();
... without the do/while, the "else" could attach to this macro's "if".
We try to handle just the minimum we need here in a macro...the loop,
the static vars, and break points. The heavy lifting is handled in
SDL_ReportAssertion(), in SDL_assert.c.
*/
#define SDL_enabled_assert(condition) \
do { \
while ( !(condition) ) { \
static struct SDL_AssertData sdl_assert_data = { \
0, 0, #condition, 0, 0, 0, 0 \
}; \
const SDL_AssertState sdl_assert_state = SDL_ReportAssertion(&sdl_assert_data, SDL_FUNCTION, SDL_FILE, SDL_LINE); \
if (sdl_assert_state == SDL_ASSERTION_RETRY) { \
continue; /* go again. */ \
} else if (sdl_assert_state == SDL_ASSERTION_BREAK) { \
SDL_TriggerBreakpoint(); \
} \
break; /* not retrying. */ \
} \
} while (SDL_NULL_WHILE_LOOP_CONDITION)
#endif /* enabled assertions support code */
/* Enable various levels of assertions. */
#if SDL_ASSERT_LEVEL == 0 /* assertions disabled */
# define SDL_assert(condition) SDL_disabled_assert(condition)
# define SDL_assert_release(condition) SDL_disabled_assert(condition)
# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
#elif SDL_ASSERT_LEVEL == 1 /* release settings. */
# define SDL_assert(condition) SDL_disabled_assert(condition)
# define SDL_assert_release(condition) SDL_enabled_assert(condition)
# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
#elif SDL_ASSERT_LEVEL == 2 /* normal settings. */
# define SDL_assert(condition) SDL_enabled_assert(condition)
# define SDL_assert_release(condition) SDL_enabled_assert(condition)
# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
#elif SDL_ASSERT_LEVEL == 3 /* paranoid settings. */
# define SDL_assert(condition) SDL_enabled_assert(condition)
# define SDL_assert_release(condition) SDL_enabled_assert(condition)
# define SDL_assert_paranoid(condition) SDL_enabled_assert(condition)
#else
# error Unknown assertion level.
#endif
/* this assertion is never disabled at any level. */
#define SDL_assert_always(condition) SDL_enabled_assert(condition)
typedef SDL_AssertState (SDLCALL *SDL_AssertionHandler)(
const SDL_AssertData* data, void* userdata);
/**
* \brief Set an application-defined assertion handler.
*
* This allows an app to show its own assertion UI and/or force the
* response to an assertion failure. If the app doesn't provide this, SDL
* will try to do the right thing, popping up a system-specific GUI dialog,
* and probably minimizing any fullscreen windows.
*
* This callback may fire from any thread, but it runs wrapped in a mutex, so
* it will only fire from one thread at a time.
*
* Setting the callback to NULL restores SDL's original internal handler.
*
* This callback is NOT reset to SDL's internal handler upon SDL_Quit()!
*
* Return SDL_AssertState value of how to handle the assertion failure.
*
* \param handler Callback function, called when an assertion fails.
* \param userdata A pointer passed to the callback as-is.
*/
extern DECLSPEC void SDLCALL SDL_SetAssertionHandler(
SDL_AssertionHandler handler,
void *userdata);
/**
* \brief Get the default assertion handler.
*
* This returns the function pointer that is called by default when an
* assertion is triggered. This is an internal function provided by SDL,
* that is used for assertions when SDL_SetAssertionHandler() hasn't been
* used to provide a different function.
*
* \return The default SDL_AssertionHandler that is called when an assert triggers.
*/
extern DECLSPEC SDL_AssertionHandler SDLCALL SDL_GetDefaultAssertionHandler(void);
/**
* \brief Get the current assertion handler.
*
* This returns the function pointer that is called when an assertion is
* triggered. This is either the value last passed to
* SDL_SetAssertionHandler(), or if no application-specified function is
* set, is equivalent to calling SDL_GetDefaultAssertionHandler().
*
* \param puserdata Pointer to a void*, which will store the "userdata"
* pointer that was passed to SDL_SetAssertionHandler().
* This value will always be NULL for the default handler.
* If you don't care about this data, it is safe to pass
* a NULL pointer to this function to ignore it.
* \return The SDL_AssertionHandler that is called when an assert triggers.
*/
extern DECLSPEC SDL_AssertionHandler SDLCALL SDL_GetAssertionHandler(void **puserdata);
/**
* \brief Get a list of all assertion failures.
*
* Get all assertions triggered since last call to SDL_ResetAssertionReport(),
* or the start of the program.
*
* The proper way to examine this data looks something like this:
*
* <code>
* const SDL_AssertData *item = SDL_GetAssertionReport();
* while (item) {
* printf("'%s', %s (%s:%d), triggered %u times, always ignore: %s.\\n",
* item->condition, item->function, item->filename,
* item->linenum, item->trigger_count,
* item->always_ignore ? "yes" : "no");
* item = item->next;
* }
* </code>
*
* \return List of all assertions.
* \sa SDL_ResetAssertionReport
*/
extern DECLSPEC const SDL_AssertData * SDLCALL SDL_GetAssertionReport(void);
/**
* \brief Reset the list of all assertion failures.
*
* Reset list of all assertions triggered.
*
* \sa SDL_GetAssertionReport
*/
extern DECLSPEC void SDLCALL SDL_ResetAssertionReport(void);
/* these had wrong naming conventions until 2.0.4. Please update your app! */
#define SDL_assert_state SDL_AssertState
#define SDL_assert_data SDL_AssertData
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_assert_h_ */
/* vi: set ts=4 sw=4 expandtab: */
@@ -1,295 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_atomic.h
*
* Atomic operations.
*
* IMPORTANT:
* If you are not an expert in concurrent lockless programming, you should
* only be using the atomic lock and reference counting functions in this
* file. In all other cases you should be protecting your data structures
* with full mutexes.
*
* The list of "safe" functions to use are:
* SDL_AtomicLock()
* SDL_AtomicUnlock()
* SDL_AtomicIncRef()
* SDL_AtomicDecRef()
*
* Seriously, here be dragons!
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* You can find out a little more about lockless programming and the
* subtle issues that can arise here:
* http://msdn.microsoft.com/en-us/library/ee418650%28v=vs.85%29.aspx
*
* There's also lots of good information here:
* http://www.1024cores.net/home/lock-free-algorithms
* http://preshing.com/
*
* These operations may or may not actually be implemented using
* processor specific atomic operations. When possible they are
* implemented as true processor specific atomic operations. When that
* is not possible the are implemented using locks that *do* use the
* available atomic operations.
*
* All of the atomic operations that modify memory are full memory barriers.
*/
#ifndef SDL_atomic_h_
#define SDL_atomic_h_
#include "SDL_stdinc.h"
#include "SDL_platform.h"
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* \name SDL AtomicLock
*
* The atomic locks are efficient spinlocks using CPU instructions,
* but are vulnerable to starvation and can spin forever if a thread
* holding a lock has been terminated. For this reason you should
* minimize the code executed inside an atomic lock and never do
* expensive things like API or system calls while holding them.
*
* The atomic locks are not safe to lock recursively.
*
* Porting Note:
* The spin lock functions and type are required and can not be
* emulated because they are used in the atomic emulation code.
*/
/* @{ */
typedef int SDL_SpinLock;
/**
* \brief Try to lock a spin lock by setting it to a non-zero value.
*
* \param lock Points to the lock.
*
* \return SDL_TRUE if the lock succeeded, SDL_FALSE if the lock is already held.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_AtomicTryLock(SDL_SpinLock *lock);
/**
* \brief Lock a spin lock by setting it to a non-zero value.
*
* \param lock Points to the lock.
*/
extern DECLSPEC void SDLCALL SDL_AtomicLock(SDL_SpinLock *lock);
/**
* \brief Unlock a spin lock by setting it to 0. Always returns immediately
*
* \param lock Points to the lock.
*/
extern DECLSPEC void SDLCALL SDL_AtomicUnlock(SDL_SpinLock *lock);
/* @} *//* SDL AtomicLock */
/**
* The compiler barrier prevents the compiler from reordering
* reads and writes to globally visible variables across the call.
*/
#if defined(_MSC_VER) && (_MSC_VER > 1200) && !defined(__clang__)
void _ReadWriteBarrier(void);
#pragma intrinsic(_ReadWriteBarrier)
#define SDL_CompilerBarrier() _ReadWriteBarrier()
#elif (defined(__GNUC__) && !defined(__EMSCRIPTEN__)) || (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120))
/* This is correct for all CPUs when using GCC or Solaris Studio 12.1+. */
#define SDL_CompilerBarrier() __asm__ __volatile__ ("" : : : "memory")
#elif defined(__WATCOMC__)
extern _inline void SDL_CompilerBarrier (void);
#pragma aux SDL_CompilerBarrier = "" parm [] modify exact [];
#else
#define SDL_CompilerBarrier() \
{ SDL_SpinLock _tmp = 0; SDL_AtomicLock(&_tmp); SDL_AtomicUnlock(&_tmp); }
#endif
/**
* Memory barriers are designed to prevent reads and writes from being
* reordered by the compiler and being seen out of order on multi-core CPUs.
*
* A typical pattern would be for thread A to write some data and a flag,
* and for thread B to read the flag and get the data. In this case you
* would insert a release barrier between writing the data and the flag,
* guaranteeing that the data write completes no later than the flag is
* written, and you would insert an acquire barrier between reading the
* flag and reading the data, to ensure that all the reads associated
* with the flag have completed.
*
* In this pattern you should always see a release barrier paired with
* an acquire barrier and you should gate the data reads/writes with a
* single flag variable.
*
* For more information on these semantics, take a look at the blog post:
* http://preshing.com/20120913/acquire-and-release-semantics
*/
extern DECLSPEC void SDLCALL SDL_MemoryBarrierReleaseFunction(void);
extern DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void);
#if defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__))
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("lwsync" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("lwsync" : : : "memory")
#elif defined(__GNUC__) && defined(__aarch64__)
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
#elif defined(__GNUC__) && defined(__arm__)
#if 0 /* defined(__LINUX__) || defined(__ANDROID__) */
/* Information from:
https://chromium.googlesource.com/chromium/chromium/+/trunk/base/atomicops_internals_arm_gcc.h#19
The Linux kernel provides a helper function which provides the right code for a memory barrier,
hard-coded at address 0xffff0fa0
*/
typedef void (*SDL_KernelMemoryBarrierFunc)();
#define SDL_MemoryBarrierRelease() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)()
#define SDL_MemoryBarrierAcquire() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)()
#elif 0 /* defined(__QNXNTO__) */
#include <sys/cpuinline.h>
#define SDL_MemoryBarrierRelease() __cpu_membarrier()
#define SDL_MemoryBarrierAcquire() __cpu_membarrier()
#else
#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) || defined(__ARM_ARCH_8A__)
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
#elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_5TE__)
#ifdef __thumb__
/* The mcr instruction isn't available in thumb mode, use real functions */
#define SDL_MEMORY_BARRIER_USES_FUNCTION
#define SDL_MemoryBarrierRelease() SDL_MemoryBarrierReleaseFunction()
#define SDL_MemoryBarrierAcquire() SDL_MemoryBarrierAcquireFunction()
#else
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" : : "r"(0) : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" : : "r"(0) : "memory")
#endif /* __thumb__ */
#else
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("" : : : "memory")
#endif /* __LINUX__ || __ANDROID__ */
#endif /* __GNUC__ && __arm__ */
#else
#if (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120))
/* This is correct for all CPUs on Solaris when using Solaris Studio 12.1+. */
#include <mbarrier.h>
#define SDL_MemoryBarrierRelease() __machine_rel_barrier()
#define SDL_MemoryBarrierAcquire() __machine_acq_barrier()
#else
/* This is correct for the x86 and x64 CPUs, and we'll expand this over time. */
#define SDL_MemoryBarrierRelease() SDL_CompilerBarrier()
#define SDL_MemoryBarrierAcquire() SDL_CompilerBarrier()
#endif
#endif
/**
* \brief A type representing an atomic integer value. It is a struct
* so people don't accidentally use numeric operations on it.
*/
typedef struct { int value; } SDL_atomic_t;
/**
* \brief Set an atomic variable to a new value if it is currently an old value.
*
* \return SDL_TRUE if the atomic variable was set, SDL_FALSE otherwise.
*
* \note If you don't know what this function is for, you shouldn't use it!
*/
extern DECLSPEC SDL_bool SDLCALL SDL_AtomicCAS(SDL_atomic_t *a, int oldval, int newval);
/**
* \brief Set an atomic variable to a value.
*
* \return The previous value of the atomic variable.
*/
extern DECLSPEC int SDLCALL SDL_AtomicSet(SDL_atomic_t *a, int v);
/**
* \brief Get the value of an atomic variable
*/
extern DECLSPEC int SDLCALL SDL_AtomicGet(SDL_atomic_t *a);
/**
* \brief Add to an atomic variable.
*
* \return The previous value of the atomic variable.
*
* \note This same style can be used for any number operation
*/
extern DECLSPEC int SDLCALL SDL_AtomicAdd(SDL_atomic_t *a, int v);
/**
* \brief Increment an atomic variable used as a reference count.
*/
#ifndef SDL_AtomicIncRef
#define SDL_AtomicIncRef(a) SDL_AtomicAdd(a, 1)
#endif
/**
* \brief Decrement an atomic variable used as a reference count.
*
* \return SDL_TRUE if the variable reached zero after decrementing,
* SDL_FALSE otherwise
*/
#ifndef SDL_AtomicDecRef
#define SDL_AtomicDecRef(a) (SDL_AtomicAdd(a, -1) == 1)
#endif
/**
* \brief Set a pointer to a new value if it is currently an old value.
*
* \return SDL_TRUE if the pointer was set, SDL_FALSE otherwise.
*
* \note If you don't know what this function is for, you shouldn't use it!
*/
extern DECLSPEC SDL_bool SDLCALL SDL_AtomicCASPtr(void **a, void *oldval, void *newval);
/**
* \brief Set a pointer to a value atomically.
*
* \return The previous value of the pointer.
*/
extern DECLSPEC void* SDLCALL SDL_AtomicSetPtr(void **a, void* v);
/**
* \brief Get the value of a pointer atomically.
*/
extern DECLSPEC void* SDLCALL SDL_AtomicGetPtr(void **a);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_atomic_h_ */
/* vi: set ts=4 sw=4 expandtab: */
@@ -1,859 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_audio.h
*
* Access to the raw audio mixing buffer for the SDL library.
*/
#ifndef SDL_audio_h_
#define SDL_audio_h_
#include "SDL_stdinc.h"
#include "SDL_error.h"
#include "SDL_endian.h"
#include "SDL_mutex.h"
#include "SDL_thread.h"
#include "SDL_rwops.h"
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* \brief Audio format flags.
*
* These are what the 16 bits in SDL_AudioFormat currently mean...
* (Unspecified bits are always zero).
*
* \verbatim
++-----------------------sample is signed if set
||
|| ++-----------sample is bigendian if set
|| ||
|| || ++---sample is float if set
|| || ||
|| || || +---sample bit size---+
|| || || | |
15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
\endverbatim
*
* There are macros in SDL 2.0 and later to query these bits.
*/
typedef Uint16 SDL_AudioFormat;
/**
* \name Audio flags
*/
/* @{ */
#define SDL_AUDIO_MASK_BITSIZE (0xFF)
#define SDL_AUDIO_MASK_DATATYPE (1<<8)
#define SDL_AUDIO_MASK_ENDIAN (1<<12)
#define SDL_AUDIO_MASK_SIGNED (1<<15)
#define SDL_AUDIO_BITSIZE(x) (x & SDL_AUDIO_MASK_BITSIZE)
#define SDL_AUDIO_ISFLOAT(x) (x & SDL_AUDIO_MASK_DATATYPE)
#define SDL_AUDIO_ISBIGENDIAN(x) (x & SDL_AUDIO_MASK_ENDIAN)
#define SDL_AUDIO_ISSIGNED(x) (x & SDL_AUDIO_MASK_SIGNED)
#define SDL_AUDIO_ISINT(x) (!SDL_AUDIO_ISFLOAT(x))
#define SDL_AUDIO_ISLITTLEENDIAN(x) (!SDL_AUDIO_ISBIGENDIAN(x))
#define SDL_AUDIO_ISUNSIGNED(x) (!SDL_AUDIO_ISSIGNED(x))
/**
* \name Audio format flags
*
* Defaults to LSB byte order.
*/
/* @{ */
#define AUDIO_U8 0x0008 /**< Unsigned 8-bit samples */
#define AUDIO_S8 0x8008 /**< Signed 8-bit samples */
#define AUDIO_U16LSB 0x0010 /**< Unsigned 16-bit samples */
#define AUDIO_S16LSB 0x8010 /**< Signed 16-bit samples */
#define AUDIO_U16MSB 0x1010 /**< As above, but big-endian byte order */
#define AUDIO_S16MSB 0x9010 /**< As above, but big-endian byte order */
#define AUDIO_U16 AUDIO_U16LSB
#define AUDIO_S16 AUDIO_S16LSB
/* @} */
/**
* \name int32 support
*/
/* @{ */
#define AUDIO_S32LSB 0x8020 /**< 32-bit integer samples */
#define AUDIO_S32MSB 0x9020 /**< As above, but big-endian byte order */
#define AUDIO_S32 AUDIO_S32LSB
/* @} */
/**
* \name float32 support
*/
/* @{ */
#define AUDIO_F32LSB 0x8120 /**< 32-bit floating point samples */
#define AUDIO_F32MSB 0x9120 /**< As above, but big-endian byte order */
#define AUDIO_F32 AUDIO_F32LSB
/* @} */
/**
* \name Native audio byte ordering
*/
/* @{ */
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
#define AUDIO_U16SYS AUDIO_U16LSB
#define AUDIO_S16SYS AUDIO_S16LSB
#define AUDIO_S32SYS AUDIO_S32LSB
#define AUDIO_F32SYS AUDIO_F32LSB
#else
#define AUDIO_U16SYS AUDIO_U16MSB
#define AUDIO_S16SYS AUDIO_S16MSB
#define AUDIO_S32SYS AUDIO_S32MSB
#define AUDIO_F32SYS AUDIO_F32MSB
#endif
/* @} */
/**
* \name Allow change flags
*
* Which audio format changes are allowed when opening a device.
*/
/* @{ */
#define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001
#define SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002
#define SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004
#define SDL_AUDIO_ALLOW_SAMPLES_CHANGE 0x00000008
#define SDL_AUDIO_ALLOW_ANY_CHANGE (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)
/* @} */
/* @} *//* Audio flags */
/**
* This function is called when the audio device needs more data.
*
* \param userdata An application-specific parameter saved in
* the SDL_AudioSpec structure
* \param stream A pointer to the audio data buffer.
* \param len The length of that buffer in bytes.
*
* Once the callback returns, the buffer will no longer be valid.
* Stereo samples are stored in a LRLRLR ordering.
*
* You can choose to avoid callbacks and use SDL_QueueAudio() instead, if
* you like. Just open your audio device with a NULL callback.
*/
typedef void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream,
int len);
/**
* The calculated values in this structure are calculated by SDL_OpenAudio().
*
* For multi-channel audio, the default SDL channel mapping is:
* 2: FL FR (stereo)
* 3: FL FR LFE (2.1 surround)
* 4: FL FR BL BR (quad)
* 5: FL FR FC BL BR (quad + center)
* 6: FL FR FC LFE SL SR (5.1 surround - last two can also be BL BR)
* 7: FL FR FC LFE BC SL SR (6.1 surround)
* 8: FL FR FC LFE BL BR SL SR (7.1 surround)
*/
typedef struct SDL_AudioSpec
{
int freq; /**< DSP frequency -- samples per second */
SDL_AudioFormat format; /**< Audio data format */
Uint8 channels; /**< Number of channels: 1 mono, 2 stereo */
Uint8 silence; /**< Audio buffer silence value (calculated) */
Uint16 samples; /**< Audio buffer size in sample FRAMES (total samples divided by channel count) */
Uint16 padding; /**< Necessary for some compile environments */
Uint32 size; /**< Audio buffer size in bytes (calculated) */
SDL_AudioCallback callback; /**< Callback that feeds the audio device (NULL to use SDL_QueueAudio()). */
void *userdata; /**< Userdata passed to callback (ignored for NULL callbacks). */
} SDL_AudioSpec;
struct SDL_AudioCVT;
typedef void (SDLCALL * SDL_AudioFilter) (struct SDL_AudioCVT * cvt,
SDL_AudioFormat format);
/**
* \brief Upper limit of filters in SDL_AudioCVT
*
* The maximum number of SDL_AudioFilter functions in SDL_AudioCVT is
* currently limited to 9. The SDL_AudioCVT.filters array has 10 pointers,
* one of which is the terminating NULL pointer.
*/
#define SDL_AUDIOCVT_MAX_FILTERS 9
/**
* \struct SDL_AudioCVT
* \brief A structure to hold a set of audio conversion filters and buffers.
*
* Note that various parts of the conversion pipeline can take advantage
* of SIMD operations (like SSE2, for example). SDL_AudioCVT doesn't require
* you to pass it aligned data, but can possibly run much faster if you
* set both its (buf) field to a pointer that is aligned to 16 bytes, and its
* (len) field to something that's a multiple of 16, if possible.
*/
#ifdef __GNUC__
/* This structure is 84 bytes on 32-bit architectures, make sure GCC doesn't
pad it out to 88 bytes to guarantee ABI compatibility between compilers.
vvv
The next time we rev the ABI, make sure to size the ints and add padding.
*/
#define SDL_AUDIOCVT_PACKED __attribute__((packed))
#else
#define SDL_AUDIOCVT_PACKED
#endif
/* */
typedef struct SDL_AudioCVT
{
int needed; /**< Set to 1 if conversion possible */
SDL_AudioFormat src_format; /**< Source audio format */
SDL_AudioFormat dst_format; /**< Target audio format */
double rate_incr; /**< Rate conversion increment */
Uint8 *buf; /**< Buffer to hold entire audio data */
int len; /**< Length of original audio buffer */
int len_cvt; /**< Length of converted audio buffer */
int len_mult; /**< buffer must be len*len_mult big */
double len_ratio; /**< Given len, final size is len*len_ratio */
SDL_AudioFilter filters[SDL_AUDIOCVT_MAX_FILTERS + 1]; /**< NULL-terminated list of filter functions */
int filter_index; /**< Current audio conversion function */
} SDL_AUDIOCVT_PACKED SDL_AudioCVT;
/* Function prototypes */
/**
* \name Driver discovery functions
*
* These functions return the list of built in audio drivers, in the
* order that they are normally initialized by default.
*/
/* @{ */
extern DECLSPEC int SDLCALL SDL_GetNumAudioDrivers(void);
extern DECLSPEC const char *SDLCALL SDL_GetAudioDriver(int index);
/* @} */
/**
* \name Initialization and cleanup
*
* \internal These functions are used internally, and should not be used unless
* you have a specific need to specify the audio driver you want to
* use. You should normally use SDL_Init() or SDL_InitSubSystem().
*/
/* @{ */
extern DECLSPEC int SDLCALL SDL_AudioInit(const char *driver_name);
extern DECLSPEC void SDLCALL SDL_AudioQuit(void);
/* @} */
/**
* This function returns the name of the current audio driver, or NULL
* if no driver has been initialized.
*/
extern DECLSPEC const char *SDLCALL SDL_GetCurrentAudioDriver(void);
/**
* This function opens the audio device with the desired parameters, and
* returns 0 if successful, placing the actual hardware parameters in the
* structure pointed to by \c obtained. If \c obtained is NULL, the audio
* data passed to the callback function will be guaranteed to be in the
* requested format, and will be automatically converted to the hardware
* audio format if necessary. This function returns -1 if it failed
* to open the audio device, or couldn't set up the audio thread.
*
* When filling in the desired audio spec structure,
* - \c desired->freq should be the desired audio frequency in samples-per-
* second.
* - \c desired->format should be the desired audio format.
* - \c desired->samples is the desired size of the audio buffer, in
* samples. This number should be a power of two, and may be adjusted by
* the audio driver to a value more suitable for the hardware. Good values
* seem to range between 512 and 8096 inclusive, depending on the
* application and CPU speed. Smaller values yield faster response time,
* but can lead to underflow if the application is doing heavy processing
* and cannot fill the audio buffer in time. A stereo sample consists of
* both right and left channels in LR ordering.
* Note that the number of samples is directly related to time by the
* following formula: \code ms = (samples*1000)/freq \endcode
* - \c desired->size is the size in bytes of the audio buffer, and is
* calculated by SDL_OpenAudio().
* - \c desired->silence is the value used to set the buffer to silence,
* and is calculated by SDL_OpenAudio().
* - \c desired->callback should be set to a function that will be called
* when the audio device is ready for more data. It is passed a pointer
* to the audio buffer, and the length in bytes of the audio buffer.
* This function usually runs in a separate thread, and so you should
* protect data structures that it accesses by calling SDL_LockAudio()
* and SDL_UnlockAudio() in your code. Alternately, you may pass a NULL
* pointer here, and call SDL_QueueAudio() with some frequency, to queue
* more audio samples to be played (or for capture devices, call
* SDL_DequeueAudio() with some frequency, to obtain audio samples).
* - \c desired->userdata is passed as the first parameter to your callback
* function. If you passed a NULL callback, this value is ignored.
*
* The audio device starts out playing silence when it's opened, and should
* be enabled for playing by calling \c SDL_PauseAudio(0) when you are ready
* for your audio callback function to be called. Since the audio driver
* may modify the requested size of the audio buffer, you should allocate
* any local mixing buffers after you open the audio device.
*/
extern DECLSPEC int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired,
SDL_AudioSpec * obtained);
/**
* SDL Audio Device IDs.
*
* A successful call to SDL_OpenAudio() is always device id 1, and legacy
* SDL audio APIs assume you want this device ID. SDL_OpenAudioDevice() calls
* always returns devices >= 2 on success. The legacy calls are good both
* for backwards compatibility and when you don't care about multiple,
* specific, or capture devices.
*/
typedef Uint32 SDL_AudioDeviceID;
/**
* Get the number of available devices exposed by the current driver.
* Only valid after a successfully initializing the audio subsystem.
* Returns -1 if an explicit list of devices can't be determined; this is
* not an error. For example, if SDL is set up to talk to a remote audio
* server, it can't list every one available on the Internet, but it will
* still allow a specific host to be specified to SDL_OpenAudioDevice().
*
* In many common cases, when this function returns a value <= 0, it can still
* successfully open the default device (NULL for first argument of
* SDL_OpenAudioDevice()).
*/
extern DECLSPEC int SDLCALL SDL_GetNumAudioDevices(int iscapture);
/**
* Get the human-readable name of a specific audio device.
* Must be a value between 0 and (number of audio devices-1).
* Only valid after a successfully initializing the audio subsystem.
* The values returned by this function reflect the latest call to
* SDL_GetNumAudioDevices(); recall that function to redetect available
* hardware.
*
* The string returned by this function is UTF-8 encoded, read-only, and
* managed internally. You are not to free it. If you need to keep the
* string for any length of time, you should make your own copy of it, as it
* will be invalid next time any of several other SDL functions is called.
*/
extern DECLSPEC const char *SDLCALL SDL_GetAudioDeviceName(int index,
int iscapture);
/**
* Open a specific audio device. Passing in a device name of NULL requests
* the most reasonable default (and is equivalent to calling SDL_OpenAudio()).
*
* The device name is a UTF-8 string reported by SDL_GetAudioDeviceName(), but
* some drivers allow arbitrary and driver-specific strings, such as a
* hostname/IP address for a remote audio server, or a filename in the
* diskaudio driver.
*
* \return 0 on error, a valid device ID that is >= 2 on success.
*
* SDL_OpenAudio(), unlike this function, always acts on device ID 1.
*/
extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(const char
*device,
int iscapture,
const
SDL_AudioSpec *
desired,
SDL_AudioSpec *
obtained,
int
allowed_changes);
/**
* \name Audio state
*
* Get the current audio state.
*/
/* @{ */
typedef enum
{
SDL_AUDIO_STOPPED = 0,
SDL_AUDIO_PLAYING,
SDL_AUDIO_PAUSED
} SDL_AudioStatus;
extern DECLSPEC SDL_AudioStatus SDLCALL SDL_GetAudioStatus(void);
extern DECLSPEC SDL_AudioStatus SDLCALL
SDL_GetAudioDeviceStatus(SDL_AudioDeviceID dev);
/* @} *//* Audio State */
/**
* \name Pause audio functions
*
* These functions pause and unpause the audio callback processing.
* They should be called with a parameter of 0 after opening the audio
* device to start playing sound. This is so you can safely initialize
* data for your callback function after opening the audio device.
* Silence will be written to the audio device during the pause.
*/
/* @{ */
extern DECLSPEC void SDLCALL SDL_PauseAudio(int pause_on);
extern DECLSPEC void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID dev,
int pause_on);
/* @} *//* Pause audio functions */
/**
* \brief Load the audio data of a WAVE file into memory
*
* Loading a WAVE file requires \c src, \c spec, \c audio_buf and \c audio_len
* to be valid pointers. The entire data portion of the file is then loaded
* into memory and decoded if necessary.
*
* If \c freesrc is non-zero, the data source gets automatically closed and
* freed before the function returns.
*
* Supported are RIFF WAVE files with the formats PCM (8, 16, 24, and 32 bits),
* IEEE Float (32 bits), Microsoft ADPCM and IMA ADPCM (4 bits), and A-law and
* µ-law (8 bits). Other formats are currently unsupported and cause an error.
*
* If this function succeeds, the pointer returned by it is equal to \c spec
* and the pointer to the audio data allocated by the function is written to
* \c audio_buf and its length in bytes to \c audio_len. The \ref SDL_AudioSpec
* members \c freq, \c channels, and \c format are set to the values of the
* audio data in the buffer. The \c samples member is set to a sane default and
* all others are set to zero.
*
* It's necessary to use SDL_FreeWAV() to free the audio data returned in
* \c audio_buf when it is no longer used.
*
* Because of the underspecification of the Waveform format, there are many
* problematic files in the wild that cause issues with strict decoders. To
* provide compatibility with these files, this decoder is lenient in regards
* to the truncation of the file, the fact chunk, and the size of the RIFF
* chunk. The hints SDL_HINT_WAVE_RIFF_CHUNK_SIZE, SDL_HINT_WAVE_TRUNCATION,
* and SDL_HINT_WAVE_FACT_CHUNK can be used to tune the behavior of the
* loading process.
*
* Any file that is invalid (due to truncation, corruption, or wrong values in
* the headers), too big, or unsupported causes an error. Additionally, any
* critical I/O error from the data source will terminate the loading process
* with an error. The function returns NULL on error and in all cases (with the
* exception of \c src being NULL), an appropriate error message will be set.
*
* It is required that the data source supports seeking.
*
* Example:
* \code
* SDL_LoadWAV_RW(SDL_RWFromFile("sample.wav", "rb"), 1, ...);
* \endcode
*
* \param src The data source with the WAVE data
* \param freesrc A integer value that makes the function close the data source if non-zero
* \param spec A pointer filled with the audio format of the audio data
* \param audio_buf A pointer filled with the audio data allocated by the function
* \param audio_len A pointer filled with the length of the audio data buffer in bytes
* \return NULL on error, or non-NULL on success.
*/
extern DECLSPEC SDL_AudioSpec *SDLCALL SDL_LoadWAV_RW(SDL_RWops * src,
int freesrc,
SDL_AudioSpec * spec,
Uint8 ** audio_buf,
Uint32 * audio_len);
/**
* Loads a WAV from a file.
* Compatibility convenience function.
*/
#define SDL_LoadWAV(file, spec, audio_buf, audio_len) \
SDL_LoadWAV_RW(SDL_RWFromFile(file, "rb"),1, spec,audio_buf,audio_len)
/**
* This function frees data previously allocated with SDL_LoadWAV_RW()
*/
extern DECLSPEC void SDLCALL SDL_FreeWAV(Uint8 * audio_buf);
/**
* This function takes a source format and rate and a destination format
* and rate, and initializes the \c cvt structure with information needed
* by SDL_ConvertAudio() to convert a buffer of audio data from one format
* to the other. An unsupported format causes an error and -1 will be returned.
*
* \return 0 if no conversion is needed, 1 if the audio filter is set up,
* or -1 on error.
*/
extern DECLSPEC int SDLCALL SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
SDL_AudioFormat src_format,
Uint8 src_channels,
int src_rate,
SDL_AudioFormat dst_format,
Uint8 dst_channels,
int dst_rate);
/**
* Once you have initialized the \c cvt structure using SDL_BuildAudioCVT(),
* created an audio buffer \c cvt->buf, and filled it with \c cvt->len bytes of
* audio data in the source format, this function will convert it in-place
* to the desired format.
*
* The data conversion may expand the size of the audio data, so the buffer
* \c cvt->buf should be allocated after the \c cvt structure is initialized by
* SDL_BuildAudioCVT(), and should be \c cvt->len*cvt->len_mult bytes long.
*
* \return 0 on success or -1 if \c cvt->buf is NULL.
*/
extern DECLSPEC int SDLCALL SDL_ConvertAudio(SDL_AudioCVT * cvt);
/* SDL_AudioStream is a new audio conversion interface.
The benefits vs SDL_AudioCVT:
- it can handle resampling data in chunks without generating
artifacts, when it doesn't have the complete buffer available.
- it can handle incoming data in any variable size.
- You push data as you have it, and pull it when you need it
*/
/* this is opaque to the outside world. */
struct _SDL_AudioStream;
typedef struct _SDL_AudioStream SDL_AudioStream;
/**
* Create a new audio stream
*
* \param src_format The format of the source audio
* \param src_channels The number of channels of the source audio
* \param src_rate The sampling rate of the source audio
* \param dst_format The format of the desired audio output
* \param dst_channels The number of channels of the desired audio output
* \param dst_rate The sampling rate of the desired audio output
* \return 0 on success, or -1 on error.
*
* \sa SDL_AudioStreamPut
* \sa SDL_AudioStreamGet
* \sa SDL_AudioStreamAvailable
* \sa SDL_AudioStreamFlush
* \sa SDL_AudioStreamClear
* \sa SDL_FreeAudioStream
*/
extern DECLSPEC SDL_AudioStream * SDLCALL SDL_NewAudioStream(const SDL_AudioFormat src_format,
const Uint8 src_channels,
const int src_rate,
const SDL_AudioFormat dst_format,
const Uint8 dst_channels,
const int dst_rate);
/**
* Add data to be converted/resampled to the stream
*
* \param stream The stream the audio data is being added to
* \param buf A pointer to the audio data to add
* \param len The number of bytes to write to the stream
* \return 0 on success, or -1 on error.
*
* \sa SDL_NewAudioStream
* \sa SDL_AudioStreamGet
* \sa SDL_AudioStreamAvailable
* \sa SDL_AudioStreamFlush
* \sa SDL_AudioStreamClear
* \sa SDL_FreeAudioStream
*/
extern DECLSPEC int SDLCALL SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, int len);
/**
* Get converted/resampled data from the stream
*
* \param stream The stream the audio is being requested from
* \param buf A buffer to fill with audio data
* \param len The maximum number of bytes to fill
* \return The number of bytes read from the stream, or -1 on error
*
* \sa SDL_NewAudioStream
* \sa SDL_AudioStreamPut
* \sa SDL_AudioStreamAvailable
* \sa SDL_AudioStreamFlush
* \sa SDL_AudioStreamClear
* \sa SDL_FreeAudioStream
*/
extern DECLSPEC int SDLCALL SDL_AudioStreamGet(SDL_AudioStream *stream, void *buf, int len);
/**
* Get the number of converted/resampled bytes available. The stream may be
* buffering data behind the scenes until it has enough to resample
* correctly, so this number might be lower than what you expect, or even
* be zero. Add more data or flush the stream if you need the data now.
*
* \sa SDL_NewAudioStream
* \sa SDL_AudioStreamPut
* \sa SDL_AudioStreamGet
* \sa SDL_AudioStreamFlush
* \sa SDL_AudioStreamClear
* \sa SDL_FreeAudioStream
*/
extern DECLSPEC int SDLCALL SDL_AudioStreamAvailable(SDL_AudioStream *stream);
/**
* Tell the stream that you're done sending data, and anything being buffered
* should be converted/resampled and made available immediately.
*
* It is legal to add more data to a stream after flushing, but there will
* be audio gaps in the output. Generally this is intended to signal the
* end of input, so the complete output becomes available.
*
* \sa SDL_NewAudioStream
* \sa SDL_AudioStreamPut
* \sa SDL_AudioStreamGet
* \sa SDL_AudioStreamAvailable
* \sa SDL_AudioStreamClear
* \sa SDL_FreeAudioStream
*/
extern DECLSPEC int SDLCALL SDL_AudioStreamFlush(SDL_AudioStream *stream);
/**
* Clear any pending data in the stream without converting it
*
* \sa SDL_NewAudioStream
* \sa SDL_AudioStreamPut
* \sa SDL_AudioStreamGet
* \sa SDL_AudioStreamAvailable
* \sa SDL_AudioStreamFlush
* \sa SDL_FreeAudioStream
*/
extern DECLSPEC void SDLCALL SDL_AudioStreamClear(SDL_AudioStream *stream);
/**
* Free an audio stream
*
* \sa SDL_NewAudioStream
* \sa SDL_AudioStreamPut
* \sa SDL_AudioStreamGet
* \sa SDL_AudioStreamAvailable
* \sa SDL_AudioStreamFlush
* \sa SDL_AudioStreamClear
*/
extern DECLSPEC void SDLCALL SDL_FreeAudioStream(SDL_AudioStream *stream);
#define SDL_MIX_MAXVOLUME 128
/**
* This takes two audio buffers of the playing audio format and mixes
* them, performing addition, volume adjustment, and overflow clipping.
* The volume ranges from 0 - 128, and should be set to ::SDL_MIX_MAXVOLUME
* for full audio volume. Note this does not change hardware volume.
* This is provided for convenience -- you can mix your own audio data.
*/
extern DECLSPEC void SDLCALL SDL_MixAudio(Uint8 * dst, const Uint8 * src,
Uint32 len, int volume);
/**
* This works like SDL_MixAudio(), but you specify the audio format instead of
* using the format of audio device 1. Thus it can be used when no audio
* device is open at all.
*/
extern DECLSPEC void SDLCALL SDL_MixAudioFormat(Uint8 * dst,
const Uint8 * src,
SDL_AudioFormat format,
Uint32 len, int volume);
/**
* Queue more audio on non-callback devices.
*
* (If you are looking to retrieve queued audio from a non-callback capture
* device, you want SDL_DequeueAudio() instead. This will return -1 to
* signify an error if you use it with capture devices.)
*
* SDL offers two ways to feed audio to the device: you can either supply a
* callback that SDL triggers with some frequency to obtain more audio
* (pull method), or you can supply no callback, and then SDL will expect
* you to supply data at regular intervals (push method) with this function.
*
* There are no limits on the amount of data you can queue, short of
* exhaustion of address space. Queued data will drain to the device as
* necessary without further intervention from you. If the device needs
* audio but there is not enough queued, it will play silence to make up
* the difference. This means you will have skips in your audio playback
* if you aren't routinely queueing sufficient data.
*
* This function copies the supplied data, so you are safe to free it when
* the function returns. This function is thread-safe, but queueing to the
* same device from two threads at once does not promise which buffer will
* be queued first.
*
* You may not queue audio on a device that is using an application-supplied
* callback; doing so returns an error. You have to use the audio callback
* or queue audio with this function, but not both.
*
* You should not call SDL_LockAudio() on the device before queueing; SDL
* handles locking internally for this function.
*
* \param dev The device ID to which we will queue audio.
* \param data The data to queue to the device for later playback.
* \param len The number of bytes (not samples!) to which (data) points.
* \return 0 on success, or -1 on error.
*
* \sa SDL_GetQueuedAudioSize
* \sa SDL_ClearQueuedAudio
*/
extern DECLSPEC int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID dev, const void *data, Uint32 len);
/**
* Dequeue more audio on non-callback devices.
*
* (If you are looking to queue audio for output on a non-callback playback
* device, you want SDL_QueueAudio() instead. This will always return 0
* if you use it with playback devices.)
*
* SDL offers two ways to retrieve audio from a capture device: you can
* either supply a callback that SDL triggers with some frequency as the
* device records more audio data, (push method), or you can supply no
* callback, and then SDL will expect you to retrieve data at regular
* intervals (pull method) with this function.
*
* There are no limits on the amount of data you can queue, short of
* exhaustion of address space. Data from the device will keep queuing as
* necessary without further intervention from you. This means you will
* eventually run out of memory if you aren't routinely dequeueing data.
*
* Capture devices will not queue data when paused; if you are expecting
* to not need captured audio for some length of time, use
* SDL_PauseAudioDevice() to stop the capture device from queueing more
* data. This can be useful during, say, level loading times. When
* unpaused, capture devices will start queueing data from that point,
* having flushed any capturable data available while paused.
*
* This function is thread-safe, but dequeueing from the same device from
* two threads at once does not promise which thread will dequeued data
* first.
*
* You may not dequeue audio from a device that is using an
* application-supplied callback; doing so returns an error. You have to use
* the audio callback, or dequeue audio with this function, but not both.
*
* You should not call SDL_LockAudio() on the device before queueing; SDL
* handles locking internally for this function.
*
* \param dev The device ID from which we will dequeue audio.
* \param data A pointer into where audio data should be copied.
* \param len The number of bytes (not samples!) to which (data) points.
* \return number of bytes dequeued, which could be less than requested.
*
* \sa SDL_GetQueuedAudioSize
* \sa SDL_ClearQueuedAudio
*/
extern DECLSPEC Uint32 SDLCALL SDL_DequeueAudio(SDL_AudioDeviceID dev, void *data, Uint32 len);
/**
* Get the number of bytes of still-queued audio.
*
* For playback device:
*
* This is the number of bytes that have been queued for playback with
* SDL_QueueAudio(), but have not yet been sent to the hardware. This
* number may shrink at any time, so this only informs of pending data.
*
* Once we've sent it to the hardware, this function can not decide the
* exact byte boundary of what has been played. It's possible that we just
* gave the hardware several kilobytes right before you called this
* function, but it hasn't played any of it yet, or maybe half of it, etc.
*
* For capture devices:
*
* This is the number of bytes that have been captured by the device and
* are waiting for you to dequeue. This number may grow at any time, so
* this only informs of the lower-bound of available data.
*
* You may not queue audio on a device that is using an application-supplied
* callback; calling this function on such a device always returns 0.
* You have to queue audio with SDL_QueueAudio()/SDL_DequeueAudio(), or use
* the audio callback, but not both.
*
* You should not call SDL_LockAudio() on the device before querying; SDL
* handles locking internally for this function.
*
* \param dev The device ID of which we will query queued audio size.
* \return Number of bytes (not samples!) of queued audio.
*
* \sa SDL_QueueAudio
* \sa SDL_ClearQueuedAudio
*/
extern DECLSPEC Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID dev);
/**
* Drop any queued audio data. For playback devices, this is any queued data
* still waiting to be submitted to the hardware. For capture devices, this
* is any data that was queued by the device that hasn't yet been dequeued by
* the application.
*
* Immediately after this call, SDL_GetQueuedAudioSize() will return 0. For
* playback devices, the hardware will start playing silence if more audio
* isn't queued. Unpaused capture devices will start filling the queue again
* as soon as they have more data available (which, depending on the state
* of the hardware and the thread, could be before this function call
* returns!).
*
* This will not prevent playback of queued audio that's already been sent
* to the hardware, as we can not undo that, so expect there to be some
* fraction of a second of audio that might still be heard. This can be
* useful if you want to, say, drop any pending music during a level change
* in your game.
*
* You may not queue audio on a device that is using an application-supplied
* callback; calling this function on such a device is always a no-op.
* You have to queue audio with SDL_QueueAudio()/SDL_DequeueAudio(), or use
* the audio callback, but not both.
*
* You should not call SDL_LockAudio() on the device before clearing the
* queue; SDL handles locking internally for this function.
*
* This function always succeeds and thus returns void.
*
* \param dev The device ID of which to clear the audio queue.
*
* \sa SDL_QueueAudio
* \sa SDL_GetQueuedAudioSize
*/
extern DECLSPEC void SDLCALL SDL_ClearQueuedAudio(SDL_AudioDeviceID dev);
/**
* \name Audio lock functions
*
* The lock manipulated by these functions protects the callback function.
* During a SDL_LockAudio()/SDL_UnlockAudio() pair, you can be guaranteed that
* the callback function is not running. Do not call these from the callback
* function or you will cause deadlock.
*/
/* @{ */
extern DECLSPEC void SDLCALL SDL_LockAudio(void);
extern DECLSPEC void SDLCALL SDL_LockAudioDevice(SDL_AudioDeviceID dev);
extern DECLSPEC void SDLCALL SDL_UnlockAudio(void);
extern DECLSPEC void SDLCALL SDL_UnlockAudioDevice(SDL_AudioDeviceID dev);
/* @} *//* Audio lock functions */
/**
* This function shuts down audio processing and closes the audio device.
*/
extern DECLSPEC void SDLCALL SDL_CloseAudio(void);
extern DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID dev);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_audio_h_ */
/* vi: set ts=4 sw=4 expandtab: */
@@ -1,126 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_bits.h
*
* Functions for fiddling with bits and bitmasks.
*/
#ifndef SDL_bits_h_
#define SDL_bits_h_
#include "SDL_stdinc.h"
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file SDL_bits.h
*/
/**
* Get the index of the most significant bit. Result is undefined when called
* with 0. This operation can also be stated as "count leading zeroes" and
* "log base 2".
*
* \return Index of the most significant bit, or -1 if the value is 0.
*/
#if defined(__WATCOMC__) && defined(__386__)
extern _inline int _SDL_bsr_watcom (Uint32);
#pragma aux _SDL_bsr_watcom = \
"bsr eax, eax" \
parm [eax] nomemory \
value [eax] \
modify exact [eax] nomemory;
#endif
SDL_FORCE_INLINE int
SDL_MostSignificantBitIndex32(Uint32 x)
{
#if defined(__GNUC__) && (__GNUC__ >= 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))
/* Count Leading Zeroes builtin in GCC.
* http://gcc.gnu.org/onlinedocs/gcc-4.3.4/gcc/Other-Builtins.html
*/
if (x == 0) {
return -1;
}
return 31 - __builtin_clz(x);
#elif defined(__WATCOMC__) && defined(__386__)
if (x == 0) {
return -1;
}
return _SDL_bsr_watcom(x);
#elif defined(_MSC_VER)
unsigned long index;
if (_BitScanReverse(&index, x)) {
return index;
}
return -1;
#else
/* Based off of Bit Twiddling Hacks by Sean Eron Anderson
* <seander@cs.stanford.edu>, released in the public domain.
* http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
*/
const Uint32 b[] = {0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000};
const int S[] = {1, 2, 4, 8, 16};
int msbIndex = 0;
int i;
if (x == 0) {
return -1;
}
for (i = 4; i >= 0; i--)
{
if (x & b[i])
{
x >>= S[i];
msbIndex |= S[i];
}
}
return msbIndex;
#endif
}
SDL_FORCE_INLINE SDL_bool
SDL_HasExactlyOneBitSet32(Uint32 x)
{
if (x && !(x & (x - 1))) {
return SDL_TRUE;
}
return SDL_FALSE;
}
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_bits_h_ */
/* vi: set ts=4 sw=4 expandtab: */
@@ -1,123 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_blendmode.h
*
* Header file declaring the SDL_BlendMode enumeration
*/
#ifndef SDL_blendmode_h_
#define SDL_blendmode_h_
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* \brief The blend mode used in SDL_RenderCopy() and drawing operations.
*/
typedef enum
{
SDL_BLENDMODE_NONE = 0x00000000, /**< no blending
dstRGBA = srcRGBA */
SDL_BLENDMODE_BLEND = 0x00000001, /**< alpha blending
dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA))
dstA = srcA + (dstA * (1-srcA)) */
SDL_BLENDMODE_ADD = 0x00000002, /**< additive blending
dstRGB = (srcRGB * srcA) + dstRGB
dstA = dstA */
SDL_BLENDMODE_MOD = 0x00000004, /**< color modulate
dstRGB = srcRGB * dstRGB
dstA = dstA */
SDL_BLENDMODE_MUL = 0x00000008, /**< color multiply
dstRGB = (srcRGB * dstRGB) + (dstRGB * (1-srcA))
dstA = (srcA * dstA) + (dstA * (1-srcA)) */
SDL_BLENDMODE_INVALID = 0x7FFFFFFF
/* Additional custom blend modes can be returned by SDL_ComposeCustomBlendMode() */
} SDL_BlendMode;
/**
* \brief The blend operation used when combining source and destination pixel components
*/
typedef enum
{
SDL_BLENDOPERATION_ADD = 0x1, /**< dst + src: supported by all renderers */
SDL_BLENDOPERATION_SUBTRACT = 0x2, /**< dst - src : supported by D3D9, D3D11, OpenGL, OpenGLES */
SDL_BLENDOPERATION_REV_SUBTRACT = 0x3, /**< src - dst : supported by D3D9, D3D11, OpenGL, OpenGLES */
SDL_BLENDOPERATION_MINIMUM = 0x4, /**< min(dst, src) : supported by D3D11 */
SDL_BLENDOPERATION_MAXIMUM = 0x5 /**< max(dst, src) : supported by D3D11 */
} SDL_BlendOperation;
/**
* \brief The normalized factor used to multiply pixel components
*/
typedef enum
{
SDL_BLENDFACTOR_ZERO = 0x1, /**< 0, 0, 0, 0 */
SDL_BLENDFACTOR_ONE = 0x2, /**< 1, 1, 1, 1 */
SDL_BLENDFACTOR_SRC_COLOR = 0x3, /**< srcR, srcG, srcB, srcA */
SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR = 0x4, /**< 1-srcR, 1-srcG, 1-srcB, 1-srcA */
SDL_BLENDFACTOR_SRC_ALPHA = 0x5, /**< srcA, srcA, srcA, srcA */
SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA = 0x6, /**< 1-srcA, 1-srcA, 1-srcA, 1-srcA */
SDL_BLENDFACTOR_DST_COLOR = 0x7, /**< dstR, dstG, dstB, dstA */
SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR = 0x8, /**< 1-dstR, 1-dstG, 1-dstB, 1-dstA */
SDL_BLENDFACTOR_DST_ALPHA = 0x9, /**< dstA, dstA, dstA, dstA */
SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA = 0xA /**< 1-dstA, 1-dstA, 1-dstA, 1-dstA */
} SDL_BlendFactor;
/**
* \brief Create a custom blend mode, which may or may not be supported by a given renderer
*
* \param srcColorFactor source color factor
* \param dstColorFactor destination color factor
* \param colorOperation color operation
* \param srcAlphaFactor source alpha factor
* \param dstAlphaFactor destination alpha factor
* \param alphaOperation alpha operation
*
* The result of the blend mode operation will be:
* dstRGB = dstRGB * dstColorFactor colorOperation srcRGB * srcColorFactor
* and
* dstA = dstA * dstAlphaFactor alphaOperation srcA * srcAlphaFactor
*/
extern DECLSPEC SDL_BlendMode SDLCALL SDL_ComposeCustomBlendMode(SDL_BlendFactor srcColorFactor,
SDL_BlendFactor dstColorFactor,
SDL_BlendOperation colorOperation,
SDL_BlendFactor srcAlphaFactor,
SDL_BlendFactor dstAlphaFactor,
SDL_BlendOperation alphaOperation);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_blendmode_h_ */
/* vi: set ts=4 sw=4 expandtab: */
@@ -1,71 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_clipboard.h
*
* Include file for SDL clipboard handling
*/
#ifndef SDL_clipboard_h_
#define SDL_clipboard_h_
#include "SDL_stdinc.h"
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/* Function prototypes */
/**
* \brief Put UTF-8 text into the clipboard
*
* \sa SDL_GetClipboardText()
*/
extern DECLSPEC int SDLCALL SDL_SetClipboardText(const char *text);
/**
* \brief Get UTF-8 text from the clipboard, which must be freed with SDL_free()
*
* \sa SDL_SetClipboardText()
*/
extern DECLSPEC char * SDLCALL SDL_GetClipboardText(void);
/**
* \brief Returns a flag indicating whether the clipboard exists and contains a text string that is non-empty
*
* \sa SDL_GetClipboardText()
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasClipboardText(void);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_clipboard_h_ */
/* vi: set ts=4 sw=4 expandtab: */
@@ -1,55 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_config_h_
#define SDL_config_h_
#include "SDL_platform.h"
/**
* \file SDL_config.h
*/
/* Add any platform that doesn't build using the configure system. */
#if defined(__WIN32__)
#include "SDL_config_windows.h"
#elif defined(__WINRT__)
#include "SDL_config_winrt.h"
#elif defined(__MACOSX__)
#include "SDL_config_macosx.h"
#elif defined(__IPHONEOS__)
#include "SDL_config_iphoneos.h"
#elif defined(__ANDROID__)
#include "SDL_config_android.h"
#elif defined(__PSP__)
#include "SDL_config_psp.h"
#elif defined(__OS2__)
#include "SDL_config_os2.h"
#else
/* This is a minimal configuration just to get SDL running on new platforms. */
#include "SDL_config_minimal.h"
#endif /* platform config */
#ifdef USING_GENERATED_CONFIG_H
#error Wrong SDL_config.h, check your include path?
#endif
#endif /* SDL_config_h_ */
@@ -1,270 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_config_macosx_h_
#define SDL_config_macosx_h_
#define SDL_config_h_
#include "SDL_platform.h"
/* This gets us MAC_OS_X_VERSION_MIN_REQUIRED... */
#include <AvailabilityMacros.h>
/* This is a set of defines to configure the SDL features */
#ifdef __LP64__
#define SIZEOF_VOIDP 8
#else
#define SIZEOF_VOIDP 4
#endif
/* Useful headers */
#define STDC_HEADERS 1
#define HAVE_ALLOCA_H 1
#define HAVE_CTYPE_H 1
#define HAVE_FLOAT_H 1
#define HAVE_INTTYPES_H 1
#define HAVE_LIMITS_H 1
#define HAVE_MATH_H 1
#define HAVE_SIGNAL_H 1
#define HAVE_STDINT_H 1
#define HAVE_STDIO_H 1
#define HAVE_STRING_H 1
#define HAVE_SYS_TYPES_H 1
#define HAVE_LIBUNWIND_H 1
/* C library functions */
#define HAVE_MALLOC 1
#define HAVE_CALLOC 1
#define HAVE_REALLOC 1
#define HAVE_FREE 1
#define HAVE_ALLOCA 1
#define HAVE_GETENV 1
#define HAVE_SETENV 1
#define HAVE_PUTENV 1
#define HAVE_UNSETENV 1
#define HAVE_QSORT 1
#define HAVE_ABS 1
#define HAVE_BCOPY 1
#define HAVE_MEMSET 1
#define HAVE_MEMCPY 1
#define HAVE_MEMMOVE 1
#define HAVE_MEMCMP 1
#define HAVE_STRLEN 1
#define HAVE_STRLCPY 1
#define HAVE_STRLCAT 1
#define HAVE_STRCHR 1
#define HAVE_STRRCHR 1
#define HAVE_STRSTR 1
#define HAVE_STRTOK_R 1
#define HAVE_STRTOL 1
#define HAVE_STRTOUL 1
#define HAVE_STRTOLL 1
#define HAVE_STRTOULL 1
#define HAVE_STRTOD 1
#define HAVE_ATOI 1
#define HAVE_ATOF 1
#define HAVE_STRCMP 1
#define HAVE_STRNCMP 1
#define HAVE_STRCASECMP 1
#define HAVE_STRNCASECMP 1
#define HAVE_VSSCANF 1
#define HAVE_VSNPRINTF 1
#define HAVE_M_PI 1
#define HAVE_ACOS 1
#define HAVE_ACOSF 1
#define HAVE_ASIN 1
#define HAVE_ASINF 1
#define HAVE_ATAN 1
#define HAVE_ATANF 1
#define HAVE_ATAN2 1
#define HAVE_ATAN2F 1
#define HAVE_CEIL 1
#define HAVE_CEILF 1
#define HAVE_COPYSIGN 1
#define HAVE_COPYSIGNF 1
#define HAVE_COS 1
#define HAVE_COSF 1
#define HAVE_EXP 1
#define HAVE_EXPF 1
#define HAVE_FABS 1
#define HAVE_FABSF 1
#define HAVE_FLOOR 1
#define HAVE_FLOORF 1
#define HAVE_FMOD 1
#define HAVE_FMODF 1
#define HAVE_LOG 1
#define HAVE_LOGF 1
#define HAVE_LOG10 1
#define HAVE_LOG10F 1
#define HAVE_LROUND 1
#define HAVE_LROUNDF 1
#define HAVE_POW 1
#define HAVE_POWF 1
#define HAVE_ROUND 1
#define HAVE_ROUNDF 1
#define HAVE_SCALBN 1
#define HAVE_SCALBNF 1
#define HAVE_SIN 1
#define HAVE_SINF 1
#define HAVE_SQRT 1
#define HAVE_SQRTF 1
#define HAVE_TAN 1
#define HAVE_TANF 1
#define HAVE_TRUNC 1
#define HAVE_TRUNCF 1
#define HAVE_SIGACTION 1
#define HAVE_SETJMP 1
#define HAVE_NANOSLEEP 1
#define HAVE_SYSCONF 1
#define HAVE_SYSCTLBYNAME 1
#define HAVE_GCC_ATOMICS 1
/* Enable various audio drivers */
#define SDL_AUDIO_DRIVER_COREAUDIO 1
#define SDL_AUDIO_DRIVER_DISK 1
#define SDL_AUDIO_DRIVER_DUMMY 1
/* Enable various input drivers */
#define SDL_JOYSTICK_HIDAPI 1
#define SDL_JOYSTICK_IOKIT 1
#define SDL_JOYSTICK_VIRTUAL 1
#define SDL_HAPTIC_IOKIT 1
/* The MFI controller support requires ARC Objective C runtime */
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 && !defined(__i386__)
#define SDL_JOYSTICK_MFI 1
#endif
/* Enable the dummy sensor driver */
#define SDL_SENSOR_DUMMY 1
/* Enable various shared object loading systems */
#define SDL_LOADSO_DLOPEN 1
/* Enable various threading systems */
#define SDL_THREAD_PTHREAD 1
#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1
/* Enable various timer systems */
#define SDL_TIMER_UNIX 1
/* Enable various video drivers */
#define SDL_VIDEO_DRIVER_COCOA 1
#define SDL_VIDEO_DRIVER_DUMMY 1
#undef SDL_VIDEO_DRIVER_X11
#define SDL_VIDEO_DRIVER_X11_DYNAMIC "/opt/X11/lib/libX11.6.dylib"
#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT "/opt/X11/lib/libXext.6.dylib"
#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XINERAMA "/opt/X11/lib/libXinerama.1.dylib"
#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 "/opt/X11/lib/libXi.6.dylib"
#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR "/opt/X11/lib/libXrandr.2.dylib"
#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS "/opt/X11/lib/libXss.1.dylib"
#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XVIDMODE "/opt/X11/lib/libXxf86vm.1.dylib"
#define SDL_VIDEO_DRIVER_X11_XDBE 1
#define SDL_VIDEO_DRIVER_X11_XINERAMA 1
#define SDL_VIDEO_DRIVER_X11_XRANDR 1
#define SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1
#define SDL_VIDEO_DRIVER_X11_XSHAPE 1
#define SDL_VIDEO_DRIVER_X11_XVIDMODE 1
#define SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM 1
#ifdef MAC_OS_X_VERSION_10_8
/*
* No matter the versions targeted, this is the 10.8 or later SDK, so you have
* to use the external Xquartz, which is a more modern Xlib. Previous SDKs
* used an older Xlib.
*/
#define SDL_VIDEO_DRIVER_X11_XINPUT2 1
#define SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1
#define SDL_VIDEO_DRIVER_X11_CONST_PARAM_XEXTADDDISPLAY 1
#endif
#ifndef SDL_VIDEO_RENDER_OGL
#define SDL_VIDEO_RENDER_OGL 1
#endif
#ifndef SDL_VIDEO_RENDER_OGL_ES2
#define SDL_VIDEO_RENDER_OGL_ES2 1
#endif
/* Metal only supported on 64-bit architectures with 10.11+ */
#if TARGET_RT_64_BIT && (MAC_OS_X_VERSION_MAX_ALLOWED >= 101100)
#define SDL_PLATFORM_SUPPORTS_METAL 1
#else
#define SDL_PLATFORM_SUPPORTS_METAL 0
#endif
#ifndef SDL_VIDEO_RENDER_METAL
#if SDL_PLATFORM_SUPPORTS_METAL
#define SDL_VIDEO_RENDER_METAL 1
#else
#define SDL_VIDEO_RENDER_METAL 0
#endif
#endif
/* Enable OpenGL support */
#ifndef SDL_VIDEO_OPENGL
#define SDL_VIDEO_OPENGL 1
#endif
#ifndef SDL_VIDEO_OPENGL_ES2
#define SDL_VIDEO_OPENGL_ES2 1
#endif
#ifndef SDL_VIDEO_OPENGL_EGL
#define SDL_VIDEO_OPENGL_EGL 1
#endif
#ifndef SDL_VIDEO_OPENGL_CGL
#define SDL_VIDEO_OPENGL_CGL 1
#endif
#ifndef SDL_VIDEO_OPENGL_GLX
#define SDL_VIDEO_OPENGL_GLX 1
#endif
/* Enable Vulkan and Metal support */
#ifndef SDL_VIDEO_VULKAN
#if SDL_PLATFORM_SUPPORTS_METAL
#define SDL_VIDEO_VULKAN 1
#else
#define SDL_VIDEO_VULKAN 0
#endif
#endif
#ifndef SDL_VIDEO_METAL
#if SDL_PLATFORM_SUPPORTS_METAL
#define SDL_VIDEO_METAL 1
#else
#define SDL_VIDEO_METAL 0
#endif
#endif
/* Enable system power support */
#define SDL_POWER_MACOSX 1
/* enable filesystem support */
#define SDL_FILESYSTEM_COCOA 1
/* Enable assembly routines */
#define SDL_ASSEMBLY_ROUTINES 1
#ifdef __ppc__
#define SDL_ALTIVEC_BLITTERS 1
#endif
#endif /* SDL_config_macosx_h_ */
@@ -1,20 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
@@ -1,299 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_cpuinfo.h
*
* CPU feature detection for SDL.
*/
#ifndef SDL_cpuinfo_h_
#define SDL_cpuinfo_h_
#include "SDL_stdinc.h"
/* Need to do this here because intrin.h has C++ code in it */
/* Visual Studio 2005 has a bug where intrin.h conflicts with winnt.h */
#if defined(_MSC_VER) && (_MSC_VER >= 1500) && (defined(_M_IX86) || defined(_M_X64))
#ifdef __clang__
/* Many of the intrinsics SDL uses are not implemented by clang with Visual Studio */
#undef __MMX__
#undef __SSE__
#undef __SSE2__
#else
#include <intrin.h>
#ifndef _WIN64
#ifndef __MMX__
#define __MMX__
#endif
#ifndef __3dNOW__
#define __3dNOW__
#endif
#endif
#ifndef __SSE__
#define __SSE__
#endif
#ifndef __SSE2__
#define __SSE2__
#endif
#endif /* __clang__ */
#elif defined(__MINGW64_VERSION_MAJOR)
#include <intrin.h>
#else
/* altivec.h redefining bool causes a number of problems, see bugs 3993 and 4392, so you need to explicitly define SDL_ENABLE_ALTIVEC_H to have it included. */
#if defined(HAVE_ALTIVEC_H) && defined(__ALTIVEC__) && !defined(__APPLE_ALTIVEC__) && defined(SDL_ENABLE_ALTIVEC_H)
#include <altivec.h>
#endif
#if !defined(SDL_DISABLE_ARM_NEON_H)
# if defined(__ARM_NEON)
# include <arm_neon.h>
# elif defined(__WINDOWS__) || defined(__WINRT__)
/* Visual Studio doesn't define __ARM_ARCH, but _M_ARM (if set, always 7), and _M_ARM64 (if set, always 1). */
# if defined(_M_ARM)
# include <armintr.h>
# include <arm_neon.h>
# define __ARM_NEON 1 /* Set __ARM_NEON so that it can be used elsewhere, at compile time */
# endif
# if defined (_M_ARM64)
# include <arm64intr.h>
# include <arm64_neon.h>
# define __ARM_NEON 1 /* Set __ARM_NEON so that it can be used elsewhere, at compile time */
# endif
# endif
#endif
#if defined(__3dNOW__) && !defined(SDL_DISABLE_MM3DNOW_H)
#include <mm3dnow.h>
#endif
#if defined(HAVE_IMMINTRIN_H) && !defined(SDL_DISABLE_IMMINTRIN_H)
#include <immintrin.h>
#else
#if defined(__MMX__) && !defined(SDL_DISABLE_MMINTRIN_H)
#include <mmintrin.h>
#endif
#if defined(__SSE__) && !defined(SDL_DISABLE_XMMINTRIN_H)
#include <xmmintrin.h>
#endif
#if defined(__SSE2__) && !defined(SDL_DISABLE_EMMINTRIN_H)
#include <emmintrin.h>
#endif
#if defined(__SSE3__) && !defined(SDL_DISABLE_PMMINTRIN_H)
#include <pmmintrin.h>
#endif
#endif /* HAVE_IMMINTRIN_H */
#endif /* compiler version */
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/* This is a guess for the cacheline size used for padding.
* Most x86 processors have a 64 byte cache line.
* The 64-bit PowerPC processors have a 128 byte cache line.
* We'll use the larger value to be generally safe.
*/
#define SDL_CACHELINE_SIZE 128
/**
* This function returns the number of CPU cores available.
*/
extern DECLSPEC int SDLCALL SDL_GetCPUCount(void);
/**
* This function returns the L1 cache line size of the CPU
*
* This is useful for determining multi-threaded structure padding
* or SIMD prefetch sizes.
*/
extern DECLSPEC int SDLCALL SDL_GetCPUCacheLineSize(void);
/**
* This function returns true if the CPU has the RDTSC instruction.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasRDTSC(void);
/**
* This function returns true if the CPU has AltiVec features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasAltiVec(void);
/**
* This function returns true if the CPU has MMX features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasMMX(void);
/**
* This function returns true if the CPU has 3DNow! features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_Has3DNow(void);
/**
* This function returns true if the CPU has SSE features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE(void);
/**
* This function returns true if the CPU has SSE2 features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE2(void);
/**
* This function returns true if the CPU has SSE3 features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE3(void);
/**
* This function returns true if the CPU has SSE4.1 features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE41(void);
/**
* This function returns true if the CPU has SSE4.2 features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE42(void);
/**
* This function returns true if the CPU has AVX features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX(void);
/**
* This function returns true if the CPU has AVX2 features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX2(void);
/**
* This function returns true if the CPU has AVX-512F (foundation) features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX512F(void);
/**
* This function returns true if the CPU has ARM SIMD (ARMv6) features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasARMSIMD(void);
/**
* This function returns true if the CPU has NEON (ARM SIMD) features.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasNEON(void);
/**
* This function returns the amount of RAM configured in the system, in MB.
*/
extern DECLSPEC int SDLCALL SDL_GetSystemRAM(void);
/**
* \brief Report the alignment this system needs for SIMD allocations.
*
* This will return the minimum number of bytes to which a pointer must be
* aligned to be compatible with SIMD instructions on the current machine.
* For example, if the machine supports SSE only, it will return 16, but if
* it supports AVX-512F, it'll return 64 (etc). This only reports values for
* instruction sets SDL knows about, so if your SDL build doesn't have
* SDL_HasAVX512F(), then it might return 16 for the SSE support it sees and
* not 64 for the AVX-512 instructions that exist but SDL doesn't know about.
* Plan accordingly.
*/
extern DECLSPEC size_t SDLCALL SDL_SIMDGetAlignment(void);
/**
* \brief Allocate memory in a SIMD-friendly way.
*
* This will allocate a block of memory that is suitable for use with SIMD
* instructions. Specifically, it will be properly aligned and padded for
* the system's supported vector instructions.
*
* The memory returned will be padded such that it is safe to read or write
* an incomplete vector at the end of the memory block. This can be useful
* so you don't have to drop back to a scalar fallback at the end of your
* SIMD processing loop to deal with the final elements without overflowing
* the allocated buffer.
*
* You must free this memory with SDL_FreeSIMD(), not free() or SDL_free()
* or delete[], etc.
*
* Note that SDL will only deal with SIMD instruction sets it is aware of;
* for example, SDL 2.0.8 knows that SSE wants 16-byte vectors
* (SDL_HasSSE()), and AVX2 wants 32 bytes (SDL_HasAVX2()), but doesn't
* know that AVX-512 wants 64. To be clear: if you can't decide to use an
* instruction set with an SDL_Has*() function, don't use that instruction
* set with memory allocated through here.
*
* SDL_AllocSIMD(0) will return a non-NULL pointer, assuming the system isn't
* out of memory.
*
* \param len The length, in bytes, of the block to allocated. The actual
* allocated block might be larger due to padding, etc.
* \return Pointer to newly-allocated block, NULL if out of memory.
*
* \sa SDL_SIMDAlignment
* \sa SDL_SIMDRealloc
* \sa SDL_SIMDFree
*/
extern DECLSPEC void * SDLCALL SDL_SIMDAlloc(const size_t len);
/**
* \brief Reallocate memory obtained from SDL_SIMDAlloc
*
* It is not valid to use this function on a pointer from anything but
* SDL_SIMDAlloc(). It can't be used on pointers from malloc, realloc,
* SDL_malloc, memalign, new[], etc.
*
* \param mem The pointer obtained from SDL_SIMDAlloc. This function also
* accepts NULL, at which point this function is the same as
* calling SDL_realloc with a NULL pointer.
* \param len The length, in bytes, of the block to allocated. The actual
* allocated block might be larger due to padding, etc. Passing 0
* will return a non-NULL pointer, assuming the system isn't out of
* memory.
* \return Pointer to newly-reallocated block, NULL if out of memory.
*
* \sa SDL_SIMDAlignment
* \sa SDL_SIMDAlloc
* \sa SDL_SIMDFree
*/
extern DECLSPEC void * SDLCALL SDL_SIMDRealloc(void *mem, const size_t len);
/**
* \brief Deallocate memory obtained from SDL_SIMDAlloc
*
* It is not valid to use this function on a pointer from anything but
* SDL_SIMDAlloc(). It can't be used on pointers from malloc, realloc,
* SDL_malloc, memalign, new[], etc.
*
* However, SDL_SIMDFree(NULL) is a legal no-op.
*
* \sa SDL_SIMDAlloc
* \sa SDL_SIMDRealloc
*/
extern DECLSPEC void SDLCALL SDL_SIMDFree(void *ptr);
/* vi: set ts=4 sw=4 expandtab: */
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_cpuinfo_h_ */
/* vi: set ts=4 sw=4 expandtab: */
@@ -1,304 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_endian.h
*
* Functions for reading and writing endian-specific values
*/
#ifndef SDL_endian_h_
#define SDL_endian_h_
#include "SDL_stdinc.h"
#ifdef _MSC_VER
#include <intrin.h>
#endif
/**
* \name The two types of endianness
*/
/* @{ */
#define SDL_LIL_ENDIAN 1234
#define SDL_BIG_ENDIAN 4321
/* @} */
#ifndef SDL_BYTEORDER /* Not defined in SDL_config.h? */
#ifdef __linux__
#include <endian.h>
#define SDL_BYTEORDER __BYTE_ORDER
#elif defined(__OpenBSD__)
#include <endian.h>
#define SDL_BYTEORDER BYTE_ORDER
#elif defined(__FreeBSD__)
#include <sys/endian.h>
#define SDL_BYTEORDER BYTE_ORDER
#else
#if defined(__hppa__) || \
defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \
(defined(__MIPS__) && defined(__MIPSEB__)) || \
defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \
defined(__sparc__)
#define SDL_BYTEORDER SDL_BIG_ENDIAN
#else
#define SDL_BYTEORDER SDL_LIL_ENDIAN
#endif
#endif /* __linux__ */
#endif /* !SDL_BYTEORDER */
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file SDL_endian.h
*/
#if (defined(__clang__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 2))) || \
(defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)))
#define SDL_Swap16(x) __builtin_bswap16(x)
#elif defined(__GNUC__) && defined(__i386__) && \
!(__GNUC__ == 2 && __GNUC_MINOR__ <= 95 /* broken gcc version */)
SDL_FORCE_INLINE Uint16
SDL_Swap16(Uint16 x)
{
__asm__("xchgb %b0,%h0": "=q"(x):"0"(x));
return x;
}
#elif defined(__GNUC__) && defined(__x86_64__)
SDL_FORCE_INLINE Uint16
SDL_Swap16(Uint16 x)
{
__asm__("xchgb %b0,%h0": "=Q"(x):"0"(x));
return x;
}
#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__))
SDL_FORCE_INLINE Uint16
SDL_Swap16(Uint16 x)
{
int result;
__asm__("rlwimi %0,%2,8,16,23": "=&r"(result):"0"(x >> 8), "r"(x));
return (Uint16)result;
}
#elif defined(__GNUC__) && defined(__aarch64__)
SDL_FORCE_INLINE Uint16
SDL_Swap16(Uint16 x)
{
__asm__("rev16 %w1, %w0" : "=r"(x) : "r"(x));
return x;
}
#elif defined(__GNUC__) && (defined(__m68k__) && !defined(__mcoldfire__))
SDL_FORCE_INLINE Uint16
SDL_Swap16(Uint16 x)
{
__asm__("rorw #8,%0": "=d"(x): "0"(x):"cc");
return x;
}
#elif defined(_MSC_VER)
#pragma intrinsic(_byteswap_ushort)
#define SDL_Swap16(x) _byteswap_ushort(x)
#elif defined(__WATCOMC__) && defined(__386__)
extern _inline Uint16 SDL_Swap16(Uint16);
#pragma aux SDL_Swap16 = \
"xchg al, ah" \
parm [ax] \
modify [ax];
#else
SDL_FORCE_INLINE Uint16
SDL_Swap16(Uint16 x)
{
return SDL_static_cast(Uint16, ((x << 8) | (x >> 8)));
}
#endif
#if (defined(__clang__) && (__clang_major__ > 2 || (__clang_major__ == 2 && __clang_minor__ >= 6))) || \
(defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)))
#define SDL_Swap32(x) __builtin_bswap32(x)
#elif defined(__GNUC__) && defined(__i386__) && \
!(__GNUC__ == 2 && __GNUC_MINOR__ <= 95 /* broken gcc version */)
SDL_FORCE_INLINE Uint32
SDL_Swap32(Uint32 x)
{
__asm__("bswap %0": "=r"(x):"0"(x));
return x;
}
#elif defined(__GNUC__) && defined(__x86_64__)
SDL_FORCE_INLINE Uint32
SDL_Swap32(Uint32 x)
{
__asm__("bswapl %0": "=r"(x):"0"(x));
return x;
}
#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__))
SDL_FORCE_INLINE Uint32
SDL_Swap32(Uint32 x)
{
Uint32 result;
__asm__("rlwimi %0,%2,24,16,23": "=&r"(result):"0"(x >> 24), "r"(x));
__asm__("rlwimi %0,%2,8,8,15": "=&r"(result):"0"(result), "r"(x));
__asm__("rlwimi %0,%2,24,0,7": "=&r"(result):"0"(result), "r"(x));
return result;
}
#elif defined(__GNUC__) && defined(__aarch64__)
SDL_FORCE_INLINE Uint32
SDL_Swap32(Uint32 x)
{
__asm__("rev %w1, %w0": "=r"(x):"r"(x));
return x;
}
#elif defined(__GNUC__) && (defined(__m68k__) && !defined(__mcoldfire__))
SDL_FORCE_INLINE Uint32
SDL_Swap32(Uint32 x)
{
__asm__("rorw #8,%0\n\tswap %0\n\trorw #8,%0": "=d"(x): "0"(x):"cc");
return x;
}
#elif defined(__WATCOMC__) && defined(__386__)
extern _inline Uint32 SDL_Swap32(Uint32);
#ifndef __SW_3 /* 486+ */
#pragma aux SDL_Swap32 = \
"bswap eax" \
parm [eax] \
modify [eax];
#else /* 386-only */
#pragma aux SDL_Swap32 = \
"xchg al, ah" \
"ror eax, 16" \
"xchg al, ah" \
parm [eax] \
modify [eax];
#endif
#elif defined(_MSC_VER)
#pragma intrinsic(_byteswap_ulong)
#define SDL_Swap32(x) _byteswap_ulong(x)
#else
SDL_FORCE_INLINE Uint32
SDL_Swap32(Uint32 x)
{
return SDL_static_cast(Uint32, ((x << 24) | ((x << 8) & 0x00FF0000) |
((x >> 8) & 0x0000FF00) | (x >> 24)));
}
#endif
#if (defined(__clang__) && (__clang_major__ > 2 || (__clang_major__ == 2 && __clang_minor__ >= 6))) || \
(defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)))
#define SDL_Swap64(x) __builtin_bswap64(x)
#elif defined(__GNUC__) && defined(__i386__) && \
!(__GNUC__ == 2 && __GNUC_MINOR__ <= 95 /* broken gcc version */)
SDL_FORCE_INLINE Uint64
SDL_Swap64(Uint64 x)
{
union
{
struct
{
Uint32 a, b;
} s;
Uint64 u;
} v;
v.u = x;
__asm__("bswapl %0 ; bswapl %1 ; xchgl %0,%1": "=r"(v.s.a), "=r"(v.s.b):"0"(v.s.a),
"1"(v.s.
b));
return v.u;
}
#elif defined(__GNUC__) && defined(__x86_64__)
SDL_FORCE_INLINE Uint64
SDL_Swap64(Uint64 x)
{
__asm__("bswapq %0": "=r"(x):"0"(x));
return x;
}
#elif defined(_MSC_VER)
#pragma intrinsic(_byteswap_uint64)
#define SDL_Swap64(x) _byteswap_uint64(x)
#else
SDL_FORCE_INLINE Uint64
SDL_Swap64(Uint64 x)
{
Uint32 hi, lo;
/* Separate into high and low 32-bit values and swap them */
lo = SDL_static_cast(Uint32, x & 0xFFFFFFFF);
x >>= 32;
hi = SDL_static_cast(Uint32, x & 0xFFFFFFFF);
x = SDL_Swap32(lo);
x <<= 32;
x |= SDL_Swap32(hi);
return (x);
}
#endif
SDL_FORCE_INLINE float
SDL_SwapFloat(float x)
{
union
{
float f;
Uint32 ui32;
} swapper;
swapper.f = x;
swapper.ui32 = SDL_Swap32(swapper.ui32);
return swapper.f;
}
/**
* \name Swap to native
* Byteswap item from the specified endianness to the native endianness.
*/
/* @{ */
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
#define SDL_SwapLE16(X) (X)
#define SDL_SwapLE32(X) (X)
#define SDL_SwapLE64(X) (X)
#define SDL_SwapFloatLE(X) (X)
#define SDL_SwapBE16(X) SDL_Swap16(X)
#define SDL_SwapBE32(X) SDL_Swap32(X)
#define SDL_SwapBE64(X) SDL_Swap64(X)
#define SDL_SwapFloatBE(X) SDL_SwapFloat(X)
#else
#define SDL_SwapLE16(X) SDL_Swap16(X)
#define SDL_SwapLE32(X) SDL_Swap32(X)
#define SDL_SwapLE64(X) SDL_Swap64(X)
#define SDL_SwapFloatLE(X) SDL_SwapFloat(X)
#define SDL_SwapBE16(X) (X)
#define SDL_SwapBE32(X) (X)
#define SDL_SwapBE64(X) (X)
#define SDL_SwapFloatBE(X) (X)
#endif
/* @} *//* Swap to native */
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_endian_h_ */
/* vi: set ts=4 sw=4 expandtab: */
@@ -1,112 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_error.h
*
* Simple error message routines for SDL.
*/
#ifndef SDL_error_h_
#define SDL_error_h_
#include "SDL_stdinc.h"
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/* Public functions */
/**
* \brief Set the error message for the current thread
*
* \return -1, there is no error handling for this function
*/
extern DECLSPEC int SDLCALL SDL_SetError(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(1);
/**
* \brief Get the last error message that was set
*
* SDL API functions may set error messages and then succeed, so you should
* only use the error value if a function fails.
*
* This returns a pointer to a static buffer for convenience and should not
* be called by multiple threads simultaneously.
*
* \return a pointer to the last error message that was set
*/
extern DECLSPEC const char *SDLCALL SDL_GetError(void);
/**
* \brief Get the last error message that was set for the current thread
*
* SDL API functions may set error messages and then succeed, so you should
* only use the error value if a function fails.
*
* \param errstr A buffer to fill with the last error message that was set
* for the current thread
* \param maxlen The size of the buffer pointed to by the errstr parameter
*
* \return errstr
*/
extern DECLSPEC char * SDLCALL SDL_GetErrorMsg(char *errstr, int maxlen);
/**
* \brief Clear the error message for the current thread
*/
extern DECLSPEC void SDLCALL SDL_ClearError(void);
/**
* \name Internal error functions
*
* \internal
* Private error reporting function - used internally.
*/
/* @{ */
#define SDL_OutOfMemory() SDL_Error(SDL_ENOMEM)
#define SDL_Unsupported() SDL_Error(SDL_UNSUPPORTED)
#define SDL_InvalidParamError(param) SDL_SetError("Parameter '%s' is invalid", (param))
typedef enum
{
SDL_ENOMEM,
SDL_EFREAD,
SDL_EFWRITE,
SDL_EFSEEK,
SDL_UNSUPPORTED,
SDL_LASTERROR
} SDL_errorcode;
/* SDL_Error() unconditionally returns -1. */
extern DECLSPEC int SDLCALL SDL_Error(SDL_errorcode code);
/* @} *//* Internal error functions */
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_error_h_ */
/* vi: set ts=4 sw=4 expandtab: */
@@ -1,827 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_events.h
*
* Include file for SDL event handling.
*/
#ifndef SDL_events_h_
#define SDL_events_h_
#include "SDL_stdinc.h"
#include "SDL_error.h"
#include "SDL_video.h"
#include "SDL_keyboard.h"
#include "SDL_mouse.h"
#include "SDL_joystick.h"
#include "SDL_gamecontroller.h"
#include "SDL_quit.h"
#include "SDL_gesture.h"
#include "SDL_touch.h"
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/* General keyboard/mouse state definitions */
#define SDL_RELEASED 0
#define SDL_PRESSED 1
/**
* \brief The types of events that can be delivered.
*/
typedef enum
{
SDL_FIRSTEVENT = 0, /**< Unused (do not remove) */
/* Application events */
SDL_QUIT = 0x100, /**< User-requested quit */
/* These application events have special meaning on iOS, see README-ios.md for details */
SDL_APP_TERMINATING, /**< The application is being terminated by the OS
Called on iOS in applicationWillTerminate()
Called on Android in onDestroy()
*/
SDL_APP_LOWMEMORY, /**< The application is low on memory, free memory if possible.
Called on iOS in applicationDidReceiveMemoryWarning()
Called on Android in onLowMemory()
*/
SDL_APP_WILLENTERBACKGROUND, /**< The application is about to enter the background
Called on iOS in applicationWillResignActive()
Called on Android in onPause()
*/
SDL_APP_DIDENTERBACKGROUND, /**< The application did enter the background and may not get CPU for some time
Called on iOS in applicationDidEnterBackground()
Called on Android in onPause()
*/
SDL_APP_WILLENTERFOREGROUND, /**< The application is about to enter the foreground
Called on iOS in applicationWillEnterForeground()
Called on Android in onResume()
*/
SDL_APP_DIDENTERFOREGROUND, /**< The application is now interactive
Called on iOS in applicationDidBecomeActive()
Called on Android in onResume()
*/
SDL_LOCALECHANGED, /**< The user's locale preferences have changed. */
/* Display events */
SDL_DISPLAYEVENT = 0x150, /**< Display state change */
/* Window events */
SDL_WINDOWEVENT = 0x200, /**< Window state change */
SDL_SYSWMEVENT, /**< System specific event */
/* Keyboard events */
SDL_KEYDOWN = 0x300, /**< Key pressed */
SDL_KEYUP, /**< Key released */
SDL_TEXTEDITING, /**< Keyboard text editing (composition) */
SDL_TEXTINPUT, /**< Keyboard text input */
SDL_KEYMAPCHANGED, /**< Keymap changed due to a system event such as an
input language or keyboard layout change.
*/
/* Mouse events */
SDL_MOUSEMOTION = 0x400, /**< Mouse moved */
SDL_MOUSEBUTTONDOWN, /**< Mouse button pressed */
SDL_MOUSEBUTTONUP, /**< Mouse button released */
SDL_MOUSEWHEEL, /**< Mouse wheel motion */
/* Joystick events */
SDL_JOYAXISMOTION = 0x600, /**< Joystick axis motion */
SDL_JOYBALLMOTION, /**< Joystick trackball motion */
SDL_JOYHATMOTION, /**< Joystick hat position change */
SDL_JOYBUTTONDOWN, /**< Joystick button pressed */
SDL_JOYBUTTONUP, /**< Joystick button released */
SDL_JOYDEVICEADDED, /**< A new joystick has been inserted into the system */
SDL_JOYDEVICEREMOVED, /**< An opened joystick has been removed */
/* Game controller events */
SDL_CONTROLLERAXISMOTION = 0x650, /**< Game controller axis motion */
SDL_CONTROLLERBUTTONDOWN, /**< Game controller button pressed */
SDL_CONTROLLERBUTTONUP, /**< Game controller button released */
SDL_CONTROLLERDEVICEADDED, /**< A new Game controller has been inserted into the system */
SDL_CONTROLLERDEVICEREMOVED, /**< An opened Game controller has been removed */
SDL_CONTROLLERDEVICEREMAPPED, /**< The controller mapping was updated */
SDL_CONTROLLERTOUCHPADDOWN, /**< Game controller touchpad was touched */
SDL_CONTROLLERTOUCHPADMOTION, /**< Game controller touchpad finger was moved */
SDL_CONTROLLERTOUCHPADUP, /**< Game controller touchpad finger was lifted */
SDL_CONTROLLERSENSORUPDATE, /**< Game controller sensor was updated */
/* Touch events */
SDL_FINGERDOWN = 0x700,
SDL_FINGERUP,
SDL_FINGERMOTION,
/* Gesture events */
SDL_DOLLARGESTURE = 0x800,
SDL_DOLLARRECORD,
SDL_MULTIGESTURE,
/* Clipboard events */
SDL_CLIPBOARDUPDATE = 0x900, /**< The clipboard changed */
/* Drag and drop events */
SDL_DROPFILE = 0x1000, /**< The system requests a file open */
SDL_DROPTEXT, /**< text/plain drag-and-drop event */
SDL_DROPBEGIN, /**< A new set of drops is beginning (NULL filename) */
SDL_DROPCOMPLETE, /**< Current set of drops is now complete (NULL filename) */
/* Audio hotplug events */
SDL_AUDIODEVICEADDED = 0x1100, /**< A new audio device is available */
SDL_AUDIODEVICEREMOVED, /**< An audio device has been removed. */
/* Sensor events */
SDL_SENSORUPDATE = 0x1200, /**< A sensor was updated */
/* Render events */
SDL_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */
SDL_RENDER_DEVICE_RESET, /**< The device has been reset and all textures need to be recreated */
/** Events ::SDL_USEREVENT through ::SDL_LASTEVENT are for your use,
* and should be allocated with SDL_RegisterEvents()
*/
SDL_USEREVENT = 0x8000,
/**
* This last event is only for bounding internal arrays
*/
SDL_LASTEVENT = 0xFFFF
} SDL_EventType;
/**
* \brief Fields shared by every event
*/
typedef struct SDL_CommonEvent
{
Uint32 type;
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
} SDL_CommonEvent;
/**
* \brief Display state change event data (event.display.*)
*/
typedef struct SDL_DisplayEvent
{
Uint32 type; /**< ::SDL_DISPLAYEVENT */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Uint32 display; /**< The associated display index */
Uint8 event; /**< ::SDL_DisplayEventID */
Uint8 padding1;
Uint8 padding2;
Uint8 padding3;
Sint32 data1; /**< event dependent data */
} SDL_DisplayEvent;
/**
* \brief Window state change event data (event.window.*)
*/
typedef struct SDL_WindowEvent
{
Uint32 type; /**< ::SDL_WINDOWEVENT */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Uint32 windowID; /**< The associated window */
Uint8 event; /**< ::SDL_WindowEventID */
Uint8 padding1;
Uint8 padding2;
Uint8 padding3;
Sint32 data1; /**< event dependent data */
Sint32 data2; /**< event dependent data */
} SDL_WindowEvent;
/**
* \brief Keyboard button event structure (event.key.*)
*/
typedef struct SDL_KeyboardEvent
{
Uint32 type; /**< ::SDL_KEYDOWN or ::SDL_KEYUP */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Uint32 windowID; /**< The window with keyboard focus, if any */
Uint8 state; /**< ::SDL_PRESSED or ::SDL_RELEASED */
Uint8 repeat; /**< Non-zero if this is a key repeat */
Uint8 padding2;
Uint8 padding3;
SDL_Keysym keysym; /**< The key that was pressed or released */
} SDL_KeyboardEvent;
#define SDL_TEXTEDITINGEVENT_TEXT_SIZE (32)
/**
* \brief Keyboard text editing event structure (event.edit.*)
*/
typedef struct SDL_TextEditingEvent
{
Uint32 type; /**< ::SDL_TEXTEDITING */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Uint32 windowID; /**< The window with keyboard focus, if any */
char text[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; /**< The editing text */
Sint32 start; /**< The start cursor of selected editing text */
Sint32 length; /**< The length of selected editing text */
} SDL_TextEditingEvent;
#define SDL_TEXTINPUTEVENT_TEXT_SIZE (32)
/**
* \brief Keyboard text input event structure (event.text.*)
*/
typedef struct SDL_TextInputEvent
{
Uint32 type; /**< ::SDL_TEXTINPUT */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Uint32 windowID; /**< The window with keyboard focus, if any */
char text[SDL_TEXTINPUTEVENT_TEXT_SIZE]; /**< The input text */
} SDL_TextInputEvent;
/**
* \brief Mouse motion event structure (event.motion.*)
*/
typedef struct SDL_MouseMotionEvent
{
Uint32 type; /**< ::SDL_MOUSEMOTION */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Uint32 windowID; /**< The window with mouse focus, if any */
Uint32 which; /**< The mouse instance id, or SDL_TOUCH_MOUSEID */
Uint32 state; /**< The current button state */
Sint32 x; /**< X coordinate, relative to window */
Sint32 y; /**< Y coordinate, relative to window */
Sint32 xrel; /**< The relative motion in the X direction */
Sint32 yrel; /**< The relative motion in the Y direction */
} SDL_MouseMotionEvent;
/**
* \brief Mouse button event structure (event.button.*)
*/
typedef struct SDL_MouseButtonEvent
{
Uint32 type; /**< ::SDL_MOUSEBUTTONDOWN or ::SDL_MOUSEBUTTONUP */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Uint32 windowID; /**< The window with mouse focus, if any */
Uint32 which; /**< The mouse instance id, or SDL_TOUCH_MOUSEID */
Uint8 button; /**< The mouse button index */
Uint8 state; /**< ::SDL_PRESSED or ::SDL_RELEASED */
Uint8 clicks; /**< 1 for single-click, 2 for double-click, etc. */
Uint8 padding1;
Sint32 x; /**< X coordinate, relative to window */
Sint32 y; /**< Y coordinate, relative to window */
} SDL_MouseButtonEvent;
/**
* \brief Mouse wheel event structure (event.wheel.*)
*/
typedef struct SDL_MouseWheelEvent
{
Uint32 type; /**< ::SDL_MOUSEWHEEL */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Uint32 windowID; /**< The window with mouse focus, if any */
Uint32 which; /**< The mouse instance id, or SDL_TOUCH_MOUSEID */
Sint32 x; /**< The amount scrolled horizontally, positive to the right and negative to the left */
Sint32 y; /**< The amount scrolled vertically, positive away from the user and negative toward the user */
Uint32 direction; /**< Set to one of the SDL_MOUSEWHEEL_* defines. When FLIPPED the values in X and Y will be opposite. Multiply by -1 to change them back */
} SDL_MouseWheelEvent;
/**
* \brief Joystick axis motion event structure (event.jaxis.*)
*/
typedef struct SDL_JoyAxisEvent
{
Uint32 type; /**< ::SDL_JOYAXISMOTION */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
SDL_JoystickID which; /**< The joystick instance id */
Uint8 axis; /**< The joystick axis index */
Uint8 padding1;
Uint8 padding2;
Uint8 padding3;
Sint16 value; /**< The axis value (range: -32768 to 32767) */
Uint16 padding4;
} SDL_JoyAxisEvent;
/**
* \brief Joystick trackball motion event structure (event.jball.*)
*/
typedef struct SDL_JoyBallEvent
{
Uint32 type; /**< ::SDL_JOYBALLMOTION */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
SDL_JoystickID which; /**< The joystick instance id */
Uint8 ball; /**< The joystick trackball index */
Uint8 padding1;
Uint8 padding2;
Uint8 padding3;
Sint16 xrel; /**< The relative motion in the X direction */
Sint16 yrel; /**< The relative motion in the Y direction */
} SDL_JoyBallEvent;
/**
* \brief Joystick hat position change event structure (event.jhat.*)
*/
typedef struct SDL_JoyHatEvent
{
Uint32 type; /**< ::SDL_JOYHATMOTION */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
SDL_JoystickID which; /**< The joystick instance id */
Uint8 hat; /**< The joystick hat index */
Uint8 value; /**< The hat position value.
* \sa ::SDL_HAT_LEFTUP ::SDL_HAT_UP ::SDL_HAT_RIGHTUP
* \sa ::SDL_HAT_LEFT ::SDL_HAT_CENTERED ::SDL_HAT_RIGHT
* \sa ::SDL_HAT_LEFTDOWN ::SDL_HAT_DOWN ::SDL_HAT_RIGHTDOWN
*
* Note that zero means the POV is centered.
*/
Uint8 padding1;
Uint8 padding2;
} SDL_JoyHatEvent;
/**
* \brief Joystick button event structure (event.jbutton.*)
*/
typedef struct SDL_JoyButtonEvent
{
Uint32 type; /**< ::SDL_JOYBUTTONDOWN or ::SDL_JOYBUTTONUP */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
SDL_JoystickID which; /**< The joystick instance id */
Uint8 button; /**< The joystick button index */
Uint8 state; /**< ::SDL_PRESSED or ::SDL_RELEASED */
Uint8 padding1;
Uint8 padding2;
} SDL_JoyButtonEvent;
/**
* \brief Joystick device event structure (event.jdevice.*)
*/
typedef struct SDL_JoyDeviceEvent
{
Uint32 type; /**< ::SDL_JOYDEVICEADDED or ::SDL_JOYDEVICEREMOVED */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Sint32 which; /**< The joystick device index for the ADDED event, instance id for the REMOVED event */
} SDL_JoyDeviceEvent;
/**
* \brief Game controller axis motion event structure (event.caxis.*)
*/
typedef struct SDL_ControllerAxisEvent
{
Uint32 type; /**< ::SDL_CONTROLLERAXISMOTION */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
SDL_JoystickID which; /**< The joystick instance id */
Uint8 axis; /**< The controller axis (SDL_GameControllerAxis) */
Uint8 padding1;
Uint8 padding2;
Uint8 padding3;
Sint16 value; /**< The axis value (range: -32768 to 32767) */
Uint16 padding4;
} SDL_ControllerAxisEvent;
/**
* \brief Game controller button event structure (event.cbutton.*)
*/
typedef struct SDL_ControllerButtonEvent
{
Uint32 type; /**< ::SDL_CONTROLLERBUTTONDOWN or ::SDL_CONTROLLERBUTTONUP */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
SDL_JoystickID which; /**< The joystick instance id */
Uint8 button; /**< The controller button (SDL_GameControllerButton) */
Uint8 state; /**< ::SDL_PRESSED or ::SDL_RELEASED */
Uint8 padding1;
Uint8 padding2;
} SDL_ControllerButtonEvent;
/**
* \brief Controller device event structure (event.cdevice.*)
*/
typedef struct SDL_ControllerDeviceEvent
{
Uint32 type; /**< ::SDL_CONTROLLERDEVICEADDED, ::SDL_CONTROLLERDEVICEREMOVED, or ::SDL_CONTROLLERDEVICEREMAPPED */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Sint32 which; /**< The joystick device index for the ADDED event, instance id for the REMOVED or REMAPPED event */
} SDL_ControllerDeviceEvent;
/**
* \brief Game controller touchpad event structure (event.ctouchpad.*)
*/
typedef struct SDL_ControllerTouchpadEvent
{
Uint32 type; /**< ::SDL_CONTROLLERTOUCHPADDOWN or ::SDL_CONTROLLERTOUCHPADMOTION or ::SDL_CONTROLLERTOUCHPADUP */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
SDL_JoystickID which; /**< The joystick instance id */
Sint32 touchpad; /**< The index of the touchpad */
Sint32 finger; /**< The index of the finger on the touchpad */
float x; /**< Normalized in the range 0...1 with 0 being on the left */
float y; /**< Normalized in the range 0...1 with 0 being at the top */
float pressure; /**< Normalized in the range 0...1 */
} SDL_ControllerTouchpadEvent;
/**
* \brief Game controller sensor event structure (event.csensor.*)
*/
typedef struct SDL_ControllerSensorEvent
{
Uint32 type; /**< ::SDL_CONTROLLERSENSORUPDATE */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
SDL_JoystickID which; /**< The joystick instance id */
Sint32 sensor; /**< The type of the sensor, one of the values of ::SDL_SensorType */
float data[3]; /**< Up to 3 values from the sensor, as defined in SDL_sensor.h */
} SDL_ControllerSensorEvent;
/**
* \brief Audio device event structure (event.adevice.*)
*/
typedef struct SDL_AudioDeviceEvent
{
Uint32 type; /**< ::SDL_AUDIODEVICEADDED, or ::SDL_AUDIODEVICEREMOVED */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Uint32 which; /**< The audio device index for the ADDED event (valid until next SDL_GetNumAudioDevices() call), SDL_AudioDeviceID for the REMOVED event */
Uint8 iscapture; /**< zero if an output device, non-zero if a capture device. */
Uint8 padding1;
Uint8 padding2;
Uint8 padding3;
} SDL_AudioDeviceEvent;
/**
* \brief Touch finger event structure (event.tfinger.*)
*/
typedef struct SDL_TouchFingerEvent
{
Uint32 type; /**< ::SDL_FINGERMOTION or ::SDL_FINGERDOWN or ::SDL_FINGERUP */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
SDL_TouchID touchId; /**< The touch device id */
SDL_FingerID fingerId;
float x; /**< Normalized in the range 0...1 */
float y; /**< Normalized in the range 0...1 */
float dx; /**< Normalized in the range -1...1 */
float dy; /**< Normalized in the range -1...1 */
float pressure; /**< Normalized in the range 0...1 */
Uint32 windowID; /**< The window underneath the finger, if any */
} SDL_TouchFingerEvent;
/**
* \brief Multiple Finger Gesture Event (event.mgesture.*)
*/
typedef struct SDL_MultiGestureEvent
{
Uint32 type; /**< ::SDL_MULTIGESTURE */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
SDL_TouchID touchId; /**< The touch device id */
float dTheta;
float dDist;
float x;
float y;
Uint16 numFingers;
Uint16 padding;
} SDL_MultiGestureEvent;
/**
* \brief Dollar Gesture Event (event.dgesture.*)
*/
typedef struct SDL_DollarGestureEvent
{
Uint32 type; /**< ::SDL_DOLLARGESTURE or ::SDL_DOLLARRECORD */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
SDL_TouchID touchId; /**< The touch device id */
SDL_GestureID gestureId;
Uint32 numFingers;
float error;
float x; /**< Normalized center of gesture */
float y; /**< Normalized center of gesture */
} SDL_DollarGestureEvent;
/**
* \brief An event used to request a file open by the system (event.drop.*)
* This event is enabled by default, you can disable it with SDL_EventState().
* \note If this event is enabled, you must free the filename in the event.
*/
typedef struct SDL_DropEvent
{
Uint32 type; /**< ::SDL_DROPBEGIN or ::SDL_DROPFILE or ::SDL_DROPTEXT or ::SDL_DROPCOMPLETE */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
char *file; /**< The file name, which should be freed with SDL_free(), is NULL on begin/complete */
Uint32 windowID; /**< The window that was dropped on, if any */
} SDL_DropEvent;
/**
* \brief Sensor event structure (event.sensor.*)
*/
typedef struct SDL_SensorEvent
{
Uint32 type; /**< ::SDL_SENSORUPDATE */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Sint32 which; /**< The instance ID of the sensor */
float data[6]; /**< Up to 6 values from the sensor - additional values can be queried using SDL_SensorGetData() */
} SDL_SensorEvent;
/**
* \brief The "quit requested" event
*/
typedef struct SDL_QuitEvent
{
Uint32 type; /**< ::SDL_QUIT */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
} SDL_QuitEvent;
/**
* \brief OS Specific event
*/
typedef struct SDL_OSEvent
{
Uint32 type; /**< ::SDL_QUIT */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
} SDL_OSEvent;
/**
* \brief A user-defined event type (event.user.*)
*/
typedef struct SDL_UserEvent
{
Uint32 type; /**< ::SDL_USEREVENT through ::SDL_LASTEVENT-1 */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Uint32 windowID; /**< The associated window if any */
Sint32 code; /**< User defined event code */
void *data1; /**< User defined data pointer */
void *data2; /**< User defined data pointer */
} SDL_UserEvent;
struct SDL_SysWMmsg;
typedef struct SDL_SysWMmsg SDL_SysWMmsg;
/**
* \brief A video driver dependent system event (event.syswm.*)
* This event is disabled by default, you can enable it with SDL_EventState()
*
* \note If you want to use this event, you should include SDL_syswm.h.
*/
typedef struct SDL_SysWMEvent
{
Uint32 type; /**< ::SDL_SYSWMEVENT */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
SDL_SysWMmsg *msg; /**< driver dependent data, defined in SDL_syswm.h */
} SDL_SysWMEvent;
/**
* \brief General event structure
*/
typedef union SDL_Event
{
Uint32 type; /**< Event type, shared with all events */
SDL_CommonEvent common; /**< Common event data */
SDL_DisplayEvent display; /**< Display event data */
SDL_WindowEvent window; /**< Window event data */
SDL_KeyboardEvent key; /**< Keyboard event data */
SDL_TextEditingEvent edit; /**< Text editing event data */
SDL_TextInputEvent text; /**< Text input event data */
SDL_MouseMotionEvent motion; /**< Mouse motion event data */
SDL_MouseButtonEvent button; /**< Mouse button event data */
SDL_MouseWheelEvent wheel; /**< Mouse wheel event data */
SDL_JoyAxisEvent jaxis; /**< Joystick axis event data */
SDL_JoyBallEvent jball; /**< Joystick ball event data */
SDL_JoyHatEvent jhat; /**< Joystick hat event data */
SDL_JoyButtonEvent jbutton; /**< Joystick button event data */
SDL_JoyDeviceEvent jdevice; /**< Joystick device change event data */
SDL_ControllerAxisEvent caxis; /**< Game Controller axis event data */
SDL_ControllerButtonEvent cbutton; /**< Game Controller button event data */
SDL_ControllerDeviceEvent cdevice; /**< Game Controller device event data */
SDL_ControllerTouchpadEvent ctouchpad; /**< Game Controller touchpad event data */
SDL_ControllerSensorEvent csensor; /**< Game Controller sensor event data */
SDL_AudioDeviceEvent adevice; /**< Audio device event data */
SDL_SensorEvent sensor; /**< Sensor event data */
SDL_QuitEvent quit; /**< Quit request event data */
SDL_UserEvent user; /**< Custom event data */
SDL_SysWMEvent syswm; /**< System dependent window event data */
SDL_TouchFingerEvent tfinger; /**< Touch finger event data */
SDL_MultiGestureEvent mgesture; /**< Gesture event data */
SDL_DollarGestureEvent dgesture; /**< Gesture event data */
SDL_DropEvent drop; /**< Drag and drop event data */
/* This is necessary for ABI compatibility between Visual C++ and GCC
Visual C++ will respect the push pack pragma and use 52 bytes for
this structure, and GCC will use the alignment of the largest datatype
within the union, which is 8 bytes.
So... we'll add padding to force the size to be 56 bytes for both.
*/
Uint8 padding[56];
} SDL_Event;
/* Make sure we haven't broken binary compatibility */
SDL_COMPILE_TIME_ASSERT(SDL_Event, sizeof(SDL_Event) == 56);
/* Function prototypes */
/**
* Pumps the event loop, gathering events from the input devices.
*
* This function updates the event queue and internal input device state.
*
* This should only be run in the thread that sets the video mode.
*/
extern DECLSPEC void SDLCALL SDL_PumpEvents(void);
/* @{ */
typedef enum
{
SDL_ADDEVENT,
SDL_PEEKEVENT,
SDL_GETEVENT
} SDL_eventaction;
/**
* Checks the event queue for messages and optionally returns them.
*
* If \c action is ::SDL_ADDEVENT, up to \c numevents events will be added to
* the back of the event queue.
*
* If \c action is ::SDL_PEEKEVENT, up to \c numevents events at the front
* of the event queue, within the specified minimum and maximum type,
* will be returned and will not be removed from the queue.
*
* If \c action is ::SDL_GETEVENT, up to \c numevents events at the front
* of the event queue, within the specified minimum and maximum type,
* will be returned and will be removed from the queue.
*
* \return The number of events actually stored, or -1 if there was an error.
*
* This function is thread-safe.
*/
extern DECLSPEC int SDLCALL SDL_PeepEvents(SDL_Event * events, int numevents,
SDL_eventaction action,
Uint32 minType, Uint32 maxType);
/* @} */
/**
* Checks to see if certain event types are in the event queue.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_HasEvent(Uint32 type);
extern DECLSPEC SDL_bool SDLCALL SDL_HasEvents(Uint32 minType, Uint32 maxType);
/**
* This function clears events from the event queue
* This function only affects currently queued events. If you want to make
* sure that all pending OS events are flushed, you can call SDL_PumpEvents()
* on the main thread immediately before the flush call.
*/
extern DECLSPEC void SDLCALL SDL_FlushEvent(Uint32 type);
extern DECLSPEC void SDLCALL SDL_FlushEvents(Uint32 minType, Uint32 maxType);
/**
* \brief Polls for currently pending events.
*
* \return 1 if there are any pending events, or 0 if there are none available.
*
* \param event If not NULL, the next event is removed from the queue and
* stored in that area.
*/
extern DECLSPEC int SDLCALL SDL_PollEvent(SDL_Event * event);
/**
* \brief Waits indefinitely for the next available event.
*
* \return 1, or 0 if there was an error while waiting for events.
*
* \param event If not NULL, the next event is removed from the queue and
* stored in that area.
*/
extern DECLSPEC int SDLCALL SDL_WaitEvent(SDL_Event * event);
/**
* \brief Waits until the specified timeout (in milliseconds) for the next
* available event.
*
* \return 1, or 0 if there was an error while waiting for events.
*
* \param event If not NULL, the next event is removed from the queue and
* stored in that area.
* \param timeout The timeout (in milliseconds) to wait for next event.
*/
extern DECLSPEC int SDLCALL SDL_WaitEventTimeout(SDL_Event * event,
int timeout);
/**
* \brief Add an event to the event queue.
*
* \return 1 on success, 0 if the event was filtered, or -1 if the event queue
* was full or there was some other error.
*/
extern DECLSPEC int SDLCALL SDL_PushEvent(SDL_Event * event);
typedef int (SDLCALL * SDL_EventFilter) (void *userdata, SDL_Event * event);
/**
* Sets up a filter to process all events before they change internal state and
* are posted to the internal event queue.
*
* The filter is prototyped as:
* \code
* int SDL_EventFilter(void *userdata, SDL_Event * event);
* \endcode
*
* If the filter returns 1, then the event will be added to the internal queue.
* If it returns 0, then the event will be dropped from the queue, but the
* internal state will still be updated. This allows selective filtering of
* dynamically arriving events.
*
* \warning Be very careful of what you do in the event filter function, as
* it may run in a different thread!
*
* There is one caveat when dealing with the ::SDL_QuitEvent event type. The
* event filter is only called when the window manager desires to close the
* application window. If the event filter returns 1, then the window will
* be closed, otherwise the window will remain open if possible.
*
* If the quit event is generated by an interrupt signal, it will bypass the
* internal queue and be delivered to the application at the next event poll.
*/
extern DECLSPEC void SDLCALL SDL_SetEventFilter(SDL_EventFilter filter,
void *userdata);
/**
* Return the current event filter - can be used to "chain" filters.
* If there is no event filter set, this function returns SDL_FALSE.
*/
extern DECLSPEC SDL_bool SDLCALL SDL_GetEventFilter(SDL_EventFilter * filter,
void **userdata);
/**
* Add a function which is called when an event is added to the queue.
*/
extern DECLSPEC void SDLCALL SDL_AddEventWatch(SDL_EventFilter filter,
void *userdata);
/**
* Remove an event watch function added with SDL_AddEventWatch()
*/
extern DECLSPEC void SDLCALL SDL_DelEventWatch(SDL_EventFilter filter,
void *userdata);
/**
* Run the filter function on the current event queue, removing any
* events for which the filter returns 0.
*/
extern DECLSPEC void SDLCALL SDL_FilterEvents(SDL_EventFilter filter,
void *userdata);
/* @{ */
#define SDL_QUERY -1
#define SDL_IGNORE 0
#define SDL_DISABLE 0
#define SDL_ENABLE 1
/**
* This function allows you to set the state of processing certain events.
* - If \c state is set to ::SDL_IGNORE, that event will be automatically
* dropped from the event queue and will not be filtered.
* - If \c state is set to ::SDL_ENABLE, that event will be processed
* normally.
* - If \c state is set to ::SDL_QUERY, SDL_EventState() will return the
* current processing state of the specified event.
*/
extern DECLSPEC Uint8 SDLCALL SDL_EventState(Uint32 type, int state);
/* @} */
#define SDL_GetEventState(type) SDL_EventState(type, SDL_QUERY)
/**
* This function allocates a set of user-defined events, and returns
* the beginning event number for that set of events.
*
* If there aren't enough user-defined events left, this function
* returns (Uint32)-1
*/
extern DECLSPEC Uint32 SDLCALL SDL_RegisterEvents(int numevents);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_events_h_ */
/* vi: set ts=4 sw=4 expandtab: */
@@ -1,136 +0,0 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_filesystem.h
*
* \brief Include file for filesystem SDL API functions
*/
#ifndef SDL_filesystem_h_
#define SDL_filesystem_h_
#include "SDL_stdinc.h"
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* \brief Get the path where the application resides.
*
* Get the "base path". This is the directory where the application was run
* from, which is probably the installation directory, and may or may not
* be the process's current working directory.
*
* This returns an absolute path in UTF-8 encoding, and is guaranteed to
* end with a path separator ('\\' on Windows, '/' most other places).
*
* The pointer returned by this function is owned by you. Please call
* SDL_free() on the pointer when you are done with it, or it will be a
* memory leak. This is not necessarily a fast call, though, so you should
* call this once near startup and save the string if you need it.
*
* Some platforms can't determine the application's path, and on other
* platforms, this might be meaningless. In such cases, this function will
* return NULL.
*
* \return String of base dir in UTF-8 encoding, or NULL on error.
*
* \sa SDL_GetPrefPath
*/
extern DECLSPEC char *SDLCALL SDL_GetBasePath(void);
/**
* \brief Get the user-and-app-specific path where files can be written.
*
* Get the "pref dir". This is meant to be where users can write personal
* files (preferences and save games, etc) that are specific to your
* application. This directory is unique per user, per application.
*
* This function will decide the appropriate location in the native filesystem,
* create the directory if necessary, and return a string of the absolute
* path to the directory in UTF-8 encoding.
*
* On Windows, the string might look like:
* "C:\\Users\\bob\\AppData\\Roaming\\My Company\\My Program Name\\"
*
* On Linux, the string might look like:
* "/home/bob/.local/share/My Program Name/"
*
* On Mac OS X, the string might look like:
* "/Users/bob/Library/Application Support/My Program Name/"
*
* (etc.)
*
* You specify the name of your organization (if it's not a real organization,
* your name or an Internet domain you own might do) and the name of your
* application. These should be untranslated proper names.
*
* Both the org and app strings may become part of a directory name, so
* please follow these rules:
*
* - Try to use the same org string (including case-sensitivity) for
* all your applications that use this function.
* - Always use a unique app string for each one, and make sure it never
* changes for an app once you've decided on it.
* - Unicode characters are legal, as long as it's UTF-8 encoded, but...
* - ...only use letters, numbers, and spaces. Avoid punctuation like
* "Game Name 2: Bad Guy's Revenge!" ... "Game Name 2" is sufficient.
*
* This returns an absolute path in UTF-8 encoding, and is guaranteed to
* end with a path separator ('\\' on Windows, '/' most other places).
*
* The pointer returned by this function is owned by you. Please call
* SDL_free() on the pointer when you are done with it, or it will be a
* memory leak. This is not necessarily a fast call, though, so you should
* call this once near startup and save the string if you need it.
*
* You should assume the path returned by this function is the only safe
* place to write files (and that SDL_GetBasePath(), while it might be
* writable, or even the parent of the returned path, aren't where you
* should be writing things).
*
* Some platforms can't determine the pref path, and on other
* platforms, this might be meaningless. In such cases, this function will
* return NULL.
*
* \param org The name of your organization.
* \param app The name of your application.
* \return UTF-8 string of user dir in platform-dependent notation. NULL
* if there's a problem (creating directory failed, etc).
*
* \sa SDL_GetBasePath
*/
extern DECLSPEC char *SDLCALL SDL_GetPrefPath(const char *org, const char *app);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_filesystem_h_ */
/* vi: set ts=4 sw=4 expandtab: */

Some files were not shown because too many files have changed in this diff Show More