mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-25 13:31:41 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb75b3d6d2 | ||
|
|
27708b9ecb | ||
|
|
5443165fce | ||
|
|
dfb006d264 | ||
|
|
4f00f62674 | ||
|
|
cec527ffd5 | ||
|
|
4f2fe5c988 | ||
|
|
2bf552f801 | ||
|
|
477fa8fedf | ||
|
|
f1c7c5f02c | ||
|
|
4e936193c7 | ||
|
|
f9895c22b6 | ||
|
|
6e46537112 | ||
|
|
92b3918b26 | ||
|
|
34c615a5fd | ||
|
|
fa29260e09 | ||
|
|
a05816b495 | ||
|
|
9c5d847dae | ||
|
|
082e330f9d | ||
|
|
8878c4a136 | ||
|
|
00926bac81 | ||
|
|
67b0907e52 | ||
|
|
dba479774b | ||
|
|
5c8a1e632b | ||
|
|
d65e29111f | ||
|
|
ed170b8092 | ||
|
|
ac36fef968 | ||
|
|
10f6b73cad | ||
|
|
13d933fc07 | ||
|
|
3ba98e86f2 | ||
|
|
1dfca525cf | ||
|
|
e51ad1a68a | ||
|
|
9c76700f74 | ||
|
|
773b1af6f1 | ||
|
|
b8b633a6cc | ||
|
|
ada2270bd1 | ||
|
|
25e5175c54 | ||
|
|
9dcd770df2 | ||
|
|
00a1948b73 | ||
|
|
859a5a5e0c | ||
|
|
6783cf57da | ||
|
|
a8dc5ac14a | ||
|
|
6182553d79 | ||
|
|
905a0cdac1 | ||
|
|
f20ddcf26d | ||
|
|
fe26098c2b | ||
|
|
2354a0a640 | ||
|
|
7c3f09c70c | ||
|
|
caf5350fe6 | ||
|
|
84084835ce | ||
|
|
7f38a67ede | ||
|
|
54383b84ec | ||
|
|
948910bfa2 | ||
|
|
ce5dac07d3 | ||
|
|
d4b0057423 | ||
|
|
de8b12f594 | ||
|
|
69e31a0a37 | ||
|
|
c383cd39f4 | ||
|
|
e88d2f0ab2 | ||
|
|
32535578a0 | ||
|
|
2e9af8fef0 | ||
|
|
0aa87aacc6 | ||
|
|
bc38c302ed | ||
|
|
c724902508 | ||
|
|
73604020d7 | ||
|
|
060a00b9c7 | ||
|
|
2cd32bf25a | ||
|
|
81ad1a18fa | ||
|
|
1d94903eef | ||
|
|
2d7dcb495c | ||
|
|
bc580c9aaa | ||
|
|
f8e2740532 | ||
|
|
a079ecd20b | ||
|
|
89c8725766 | ||
|
|
da7d532564 | ||
|
|
4752d4966d | ||
|
|
50f47f1ed5 | ||
|
|
26bdc3e1d5 | ||
|
|
764360c0f3 | ||
|
|
08482577eb | ||
|
|
cabbba2448 | ||
|
|
c48ed7bb0d | ||
|
|
fa4c0e82bd | ||
|
|
c0bf8b9c25 | ||
|
|
d2cff86c97 | ||
|
|
03c0be0720 | ||
|
|
a3361363e4 | ||
|
|
40e142c5c7 | ||
|
|
2703efedef | ||
|
|
c313f1a20b | ||
|
|
b7116657d9 | ||
|
|
2bf3f1b94b | ||
|
|
b78a7971d9 | ||
|
|
5079bc4976 | ||
|
|
555032c15c | ||
|
|
bcff327565 | ||
|
|
e95ff989b1 | ||
|
|
018e8999e2 | ||
|
|
f624a1817e | ||
|
|
202a643e5a | ||
|
|
c75b9c9221 | ||
|
|
9a6f5ba1a8 | ||
|
|
052194714a | ||
|
|
5ccb4a8b7f | ||
|
|
7c3cec0451 | ||
|
|
b79d3b31b3 | ||
|
|
bf5d43d0cf | ||
|
|
f2ac570949 | ||
|
|
37636ef1eb | ||
|
|
c540bec824 | ||
|
|
9288cfa684 | ||
|
|
b1de8b703b | ||
|
|
ccd0777ce9 | ||
|
|
49b67b3e18 | ||
|
|
bc97168dc7 | ||
|
|
0ec04df334 | ||
|
|
26d7946b4a | ||
|
|
292cc579cf | ||
|
|
8492482e55 | ||
|
|
ac8183feee | ||
|
|
8037fb76fc | ||
|
|
754a14941b | ||
|
|
e32bc1a0a3 | ||
|
|
e5a8b49c93 |
No files matched your search
@@ -22,18 +22,19 @@ List the games you've tried that exhibit the issue. To see if the issue is game-
|
|||||||
- Does the problem still occur after reverting settings back to default?
|
- Does the problem still occur after reverting settings back to default?
|
||||||
|
|
||||||
**Client PC details (please complete the following information)**
|
**Client PC details (please complete the following information)**
|
||||||
- OS: [e.g. Windows 10 1803]
|
- OS: [e.g. Windows 10 1809]
|
||||||
- Moonlight Version: [e.g. v0.2.0]
|
- Moonlight Version: [e.g. v0.9.0]
|
||||||
|
|
||||||
**Server PC details (please complete the following information)**
|
**Server PC details (please complete the following information)**
|
||||||
- OS: [e.g. Windows 10 1803]
|
- OS: [e.g. Windows 10 1809]
|
||||||
- GeForce Experience version: [e.g. 3.16.0.140]
|
- GeForce Experience version: [e.g. 3.16.0.140]
|
||||||
- Nvidia GPU driver: [e.g. 417.35]
|
- Nvidia GPU driver: [e.g. 417.35]
|
||||||
|
|
||||||
**Moonlight Logs (please attach)**
|
**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.
|
- On Windows, `Moonlight-###.log` files can be found in `%TEMP%`. Simply type that into the File Explorer path field to navigate there.
|
||||||
- On macOS, `Moonlight-###.log` files can be found in `/tmp`. In Finder, press Cmd+Shift+G, then type `/tmp` to navigate there.
|
- On macOS, `Moonlight-###.log` files can be found in `/tmp`. In Finder, press Cmd+Shift+G, then type `/tmp` to navigate there.
|
||||||
- On Linux, logs print to the terminal when running via `flatpak run com.moonlight_stream.Moonlight`
|
- On Linux with the Flatpak, logs print to the terminal when running with the command: `flatpak run com.moonlight_stream.Moonlight`
|
||||||
|
- On Linux with the Snap, logs print to the terminal when running with the command: `moonlight`
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Anything else you think may be relevant to the issue
|
Anything else you think may be relevant to the issue
|
||||||
@@ -3,11 +3,7 @@ QT -= core gui
|
|||||||
TARGET = AntiHooking
|
TARGET = AntiHooking
|
||||||
TEMPLATE = lib
|
TEMPLATE = lib
|
||||||
|
|
||||||
# Support debug and release builds from command line for CI
|
include(../globaldefs.pri)
|
||||||
CONFIG += debug_and_release
|
|
||||||
|
|
||||||
# Ensure symbols are always generated
|
|
||||||
CONFIG += force_debug_info
|
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD/../libs/windows/include
|
INCLUDEPATH += $$PWD/../libs/windows/include
|
||||||
contains(QT_ARCH, i386) {
|
contains(QT_ARCH, i386) {
|
||||||
|
|||||||
@@ -134,7 +134,11 @@ private:
|
|||||||
// This DLL shipped with ASUS Sonic Radar 3 improperly handles
|
// This DLL shipped with ASUS Sonic Radar 3 improperly handles
|
||||||
// D3D9 exclusive fullscreen in a way that causes CreateDeviceEx()
|
// D3D9 exclusive fullscreen in a way that causes CreateDeviceEx()
|
||||||
// to deadlock. https://github.com/moonlight-stream/moonlight-qt/issues/102
|
// to deadlock. https://github.com/moonlight-stream/moonlight-qt/issues/102
|
||||||
L"NahimicOSD.dll"
|
L"NahimicOSD.dll",
|
||||||
|
// This DLL has been seen in several crash reports. Some Googling
|
||||||
|
// suggests it's highly unstable and causes issues in many games.
|
||||||
|
L"EZFRD32.dll",
|
||||||
|
L"EZFRD64.dll",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
|
|||||||
- 5.1 surround sound audio
|
- 5.1 surround sound audio
|
||||||
- HEVC support for better image quality at reduced bandwidth
|
- HEVC support for better image quality at reduced bandwidth
|
||||||
- Keyboard and mouse support
|
- Keyboard and mouse support
|
||||||
- Gamepad support with SDL gamepad mappings
|
- Gamepad support with force feedback for up to 4 players
|
||||||
|
|
||||||
## Downloads
|
## Downloads
|
||||||
- [Windows and macOS](https://github.com/moonlight-stream/moonlight-qt/releases)
|
- [Windows, macOS, and Steam Link](https://github.com/moonlight-stream/moonlight-qt/releases)
|
||||||
- [Snap (for Ubuntu-based Linux distros)](https://snapcraft.io/moonlight)
|
- [Snap (for Ubuntu-based Linux distros)](https://snapcraft.io/moonlight)
|
||||||
- [Flatpak (for other Linux distros)](https://flathub.org/apps/details/com.moonlight_stream.Moonlight)
|
- [Flatpak (for other Linux distros)](https://flathub.org/apps/details/com.moonlight_stream.Moonlight)
|
||||||
|
|
||||||
@@ -27,33 +27,39 @@ You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
|
|||||||
### General Build Requirements
|
### General Build Requirements
|
||||||
* Qt 5.9 SDK or later
|
* Qt 5.9 SDK or later
|
||||||
|
|
||||||
### Windows-specific Requirements
|
### Windows-specific Build Requirements
|
||||||
* Windows 7 or later
|
* Windows 7 or later
|
||||||
* [Visual Studio](https://visualstudio.microsoft.com/downloads/) 2015 or later (Community edition is fine)
|
* [Visual Studio](https://visualstudio.microsoft.com/downloads/) 2017 (Community edition is fine)
|
||||||
* Select MSVC Desktop toolchain during Qt installation
|
* Select MSVC Desktop toolchain during Qt installation
|
||||||
* [DirectX SDK](https://www.microsoft.com/en-us/download/details.aspx?id=6812)
|
* [DirectX SDK](https://www.microsoft.com/en-us/download/details.aspx?id=6812)
|
||||||
* [7-Zip](https://www.7-zip.org/) (only if building installers for non-development PCs)
|
* [7-Zip](https://www.7-zip.org/) (only if building installers for non-development PCs)
|
||||||
* [WiX Toolset](http://wixtoolset.org/releases/) v3.11 or later (only if building installers for non-development PCs)
|
* [WiX Toolset](http://wixtoolset.org/releases/) v3.11 or later (only if building installers for non-development PCs)
|
||||||
|
|
||||||
### Mac-specific Requirements
|
### Mac-specific Build Requirements
|
||||||
* macOS Sierra (10.12) or later
|
* macOS Sierra (10.12) or later
|
||||||
* Xcode 10
|
* Xcode 10
|
||||||
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
|
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
|
||||||
|
|
||||||
### Linux-specific Requirements
|
### Linux-specific Build Requirements
|
||||||
* GCC or Clang
|
* GCC or Clang
|
||||||
* Install your distro equivalents of: `openssl-devel qt5-devel SDL2-devel ffmpeg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel SDL2_ttf-devel`
|
* Install your distro equivalents of: `openssl-devel qt5-devel SDL2-devel ffmpeg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel SDL2_ttf-devel`
|
||||||
* FFmpeg 4.0 is required to build. If your distro doesn't package FFmpeg 4.0, you can build and install it from source on http://ffmpeg.org/
|
* FFmpeg 4.0 is required to build. If your distro doesn't package FFmpeg 4.0, you can build and install it from source on http://ffmpeg.org/
|
||||||
|
|
||||||
|
### Steam Link-specific Build Requirements
|
||||||
|
* [Steam Link SDK](https://github.com/ValveSoftware/steamlink-sdk) cloned on your build system
|
||||||
|
* STEAMLINK_SDK_PATH environment variable set to the Steam Link SDK path
|
||||||
|
|
||||||
### Build Setup Steps
|
### Build Setup Steps
|
||||||
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
|
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
|
||||||
* You can install Qt via Homebrew on macOS, but you will need to use `brew install qt --with-debug` to be able to create debug builds of Moonlight.
|
* You can install Qt via Homebrew on macOS, but you will need to use `brew install qt --with-debug` to be able to create debug builds of Moonlight.
|
||||||
* You may also use your Linux distro's package manager for the Qt SDK as long as the packages are Qt 5.9 or later.
|
* You may also use your Linux distro's package manager for the Qt SDK as long as the packages are Qt 5.9 or later.
|
||||||
|
* This step is not required for building on Steam Link, because the Steam Link SDK includes Qt 5.9.
|
||||||
2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
|
2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
|
||||||
3. Open the project in Qt Creator or build from qmake on the command line.
|
3. Open the project in Qt Creator or build from qmake on the command line.
|
||||||
* To build a binary for use on non-development machines, use the scripts in the `scripts` folder.
|
* To build a binary for use on non-development machines, use the scripts in the `scripts` folder.
|
||||||
* For Windows builds, use `scripts\generate-installers.bat`. Execute this script from the root of the repository within a Qt command prompt. Ensure WiX and 7-Zip binary directories are in your `%PATH%`.
|
* For Windows builds, use `scripts\generate-installers.bat`. Execute this script from the root of the repository within a Qt command prompt. Ensure WiX and 7-Zip binary directories are in your `%PATH%`.
|
||||||
* 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 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 build from the command line for development use, run `qmake moonlight-qt.pro` then `make debug` or `make release`
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|||||||
+1
-1
Submodule app/SDL_GameControllerDB updated: 40e3a34962...cbebd79740.
+71
-56
@@ -8,11 +8,7 @@ unix:!macx {
|
|||||||
TARGET = Moonlight
|
TARGET = Moonlight
|
||||||
}
|
}
|
||||||
|
|
||||||
# Support debug and release builds from command line for CI
|
include(../globaldefs.pri)
|
||||||
CONFIG += debug_and_release
|
|
||||||
|
|
||||||
# Ensure symbols are always generated
|
|
||||||
CONFIG += force_debug_info
|
|
||||||
|
|
||||||
# Precompile QML files to avoid writing qmlcache on portable versions.
|
# Precompile QML files to avoid writing qmlcache on portable versions.
|
||||||
# Since this binds the app against the Qt runtime version, we will only
|
# Since this binds the app against the Qt runtime version, we will only
|
||||||
@@ -60,15 +56,21 @@ macx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unix:!macx {
|
unix:!macx {
|
||||||
CONFIG += link_pkgconfig soundio
|
CONFIG += link_pkgconfig
|
||||||
PKGCONFIG += openssl sdl2 SDL2_ttf opus
|
PKGCONFIG += openssl sdl2 SDL2_ttf opus
|
||||||
|
|
||||||
# For libsoundio
|
# SLAudio is used on Steam Link
|
||||||
packagesExist(libpulse) {
|
!config_SL {
|
||||||
PKGCONFIG += libpulse
|
CONFIG += soundio
|
||||||
}
|
}
|
||||||
packagesExist(alsa) {
|
|
||||||
PKGCONFIG += alsa
|
soundio {
|
||||||
|
packagesExist(libpulse) {
|
||||||
|
PKGCONFIG += libpulse
|
||||||
|
}
|
||||||
|
packagesExist(alsa) {
|
||||||
|
PKGCONFIG += alsa
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
packagesExist(libavcodec) {
|
packagesExist(libavcodec) {
|
||||||
@@ -88,11 +90,24 @@ unix:!macx {
|
|||||||
packagesExist(vdpau) {
|
packagesExist(vdpau) {
|
||||||
CONFIG += libvdpau
|
CONFIG += libvdpau
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packagesExist(mmal) {
|
||||||
|
PKGCONFIG += mmal
|
||||||
|
CONFIG += mmal
|
||||||
|
}
|
||||||
|
|
||||||
|
packagesExist(libdrm) {
|
||||||
|
PKGCONFIG += libdrm
|
||||||
|
CONFIG += libdrm
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
win32 {
|
win32 {
|
||||||
LIBS += -llibssl -llibcrypto -lSDL2 -lSDL2_ttf -lavcodec -lavutil -lopus -ld3dx9
|
LIBS += -llibssl -llibcrypto -lSDL2 -lSDL2_ttf -lavcodec -lavutil -lopus -ld3dx9
|
||||||
CONFIG += ffmpeg soundio
|
CONFIG += ffmpeg
|
||||||
|
}
|
||||||
|
win32:!winrt {
|
||||||
|
CONFIG += soundio
|
||||||
}
|
}
|
||||||
macx {
|
macx {
|
||||||
LIBS += -lssl -lcrypto -lavcodec.58 -lavutil.56 -lopus -framework SDL2 -framework SDL2_ttf
|
LIBS += -lssl -lcrypto -lavcodec.58 -lavutil.56 -lopus -framework SDL2 -framework SDL2_ttf
|
||||||
@@ -128,7 +143,8 @@ SOURCES += \
|
|||||||
path.cpp \
|
path.cpp \
|
||||||
settings/mappingmanager.cpp \
|
settings/mappingmanager.cpp \
|
||||||
gui/sdlgamepadkeynavigation.cpp \
|
gui/sdlgamepadkeynavigation.cpp \
|
||||||
streaming/video/overlaymanager.cpp
|
streaming/video/overlaymanager.cpp \
|
||||||
|
backend/systemproperties.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
utils.h \
|
utils.h \
|
||||||
@@ -155,7 +171,8 @@ HEADERS += \
|
|||||||
path.h \
|
path.h \
|
||||||
settings/mappingmanager.h \
|
settings/mappingmanager.h \
|
||||||
gui/sdlgamepadkeynavigation.h \
|
gui/sdlgamepadkeynavigation.h \
|
||||||
streaming/video/overlaymanager.h
|
streaming/video/overlaymanager.h \
|
||||||
|
backend/systemproperties.h
|
||||||
|
|
||||||
# Platform-specific renderers and decoders
|
# Platform-specific renderers and decoders
|
||||||
ffmpeg {
|
ffmpeg {
|
||||||
@@ -202,16 +219,34 @@ libvdpau {
|
|||||||
SOURCES += streaming/video/ffmpeg-renderers/vdpau.cpp
|
SOURCES += streaming/video/ffmpeg-renderers/vdpau.cpp
|
||||||
HEADERS += streaming/video/ffmpeg-renderers/vdpau.h
|
HEADERS += streaming/video/ffmpeg-renderers/vdpau.h
|
||||||
}
|
}
|
||||||
config_SLVideo {
|
mmal {
|
||||||
message(SLVideo decoder/renderer selected)
|
message(MMAL renderer selected)
|
||||||
|
|
||||||
DEFINES += HAVE_SLVIDEO
|
DEFINES += HAVE_MMAL
|
||||||
LIBS += -lSLVideo
|
SOURCES += streaming/video/ffmpeg-renderers/mmal.cpp
|
||||||
|
HEADERS += streaming/video/ffmpeg-renderers/mmal.h
|
||||||
SOURCES += streaming/video/sl.cpp
|
|
||||||
HEADERS += streaming/video/sl.h
|
|
||||||
}
|
}
|
||||||
win32 {
|
libdrm {
|
||||||
|
message(DRM renderer selected)
|
||||||
|
|
||||||
|
DEFINES += HAVE_DRM
|
||||||
|
SOURCES += streaming/video/ffmpeg-renderers/drm.cpp
|
||||||
|
HEADERS += streaming/video/ffmpeg-renderers/drm.h
|
||||||
|
}
|
||||||
|
config_SL {
|
||||||
|
message(Steam Link build configuration selected)
|
||||||
|
|
||||||
|
DEFINES += STEAM_LINK HAVE_SLVIDEO HAVE_SLAUDIO
|
||||||
|
LIBS += -lSLVideo -lSLAudio
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
streaming/video/slvid.cpp \
|
||||||
|
streaming/audio/renderers/slaud.cpp
|
||||||
|
HEADERS += \
|
||||||
|
streaming/video/slvid.h \
|
||||||
|
streaming/audio/renderers/slaud.h
|
||||||
|
}
|
||||||
|
win32:!winrt {
|
||||||
message(DXVA2 renderer selected)
|
message(DXVA2 renderer selected)
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
@@ -265,12 +300,14 @@ else:unix: LIBS += -L$$OUT_PWD/../qmdnsengine/ -lqmdnsengine
|
|||||||
INCLUDEPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
INCLUDEPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
||||||
DEPENDPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
DEPENDPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
||||||
|
|
||||||
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../soundio/release/ -lsoundio
|
soundio {
|
||||||
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../soundio/debug/ -lsoundio
|
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../soundio/release/ -lsoundio
|
||||||
else:unix: LIBS += -L$$OUT_PWD/../soundio/ -lsoundio
|
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../soundio/debug/ -lsoundio
|
||||||
|
else:unix: LIBS += -L$$OUT_PWD/../soundio/ -lsoundio
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD/../soundio/libsoundio
|
INCLUDEPATH += $$PWD/../soundio/libsoundio
|
||||||
DEPENDPATH += $$PWD/../soundio/libsoundio
|
DEPENDPATH += $$PWD/../soundio/libsoundio
|
||||||
|
}
|
||||||
|
|
||||||
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../h264bitstream/release/ -lh264bitstream
|
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../h264bitstream/release/ -lh264bitstream
|
||||||
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../h264bitstream/debug/ -lh264bitstream
|
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../h264bitstream/debug/ -lh264bitstream
|
||||||
@@ -279,22 +316,12 @@ else:unix: LIBS += -L$$OUT_PWD/../h264bitstream/ -lh264bitstream
|
|||||||
INCLUDEPATH += $$PWD/../h264bitstream/h264bitstream
|
INCLUDEPATH += $$PWD/../h264bitstream/h264bitstream
|
||||||
DEPENDPATH += $$PWD/../h264bitstream/h264bitstream
|
DEPENDPATH += $$PWD/../h264bitstream/h264bitstream
|
||||||
|
|
||||||
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../AntiHooking/release/ -lAntiHooking
|
!winrt {
|
||||||
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../AntiHooking/debug/ -lAntiHooking
|
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../AntiHooking/release/ -lAntiHooking
|
||||||
|
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../AntiHooking/debug/ -lAntiHooking
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD/../AntiHooking
|
INCLUDEPATH += $$PWD/../AntiHooking
|
||||||
DEPENDPATH += $$PWD/../AntiHooking
|
DEPENDPATH += $$PWD/../AntiHooking
|
||||||
|
|
||||||
defineTest(copyToDestDir) {
|
|
||||||
files = $$1
|
|
||||||
|
|
||||||
for(FILE, files) {
|
|
||||||
!equals(PWD, $$OUT_PWD) {
|
|
||||||
QMAKE_POST_LINK += $$QMAKE_COPY $$shell_quote($$shell_path($$PWD/$$FILE)) $$shell_quote($$shell_path($$OUT_PWD)) $$escape_expand(\\n\\t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export(QMAKE_POST_LINK)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unix:!macx: {
|
unix:!macx: {
|
||||||
@@ -319,16 +346,7 @@ unix:!macx: {
|
|||||||
appstream.files = deploy/linux/com.moonlight_stream.Moonlight.appdata.xml
|
appstream.files = deploy/linux/com.moonlight_stream.Moonlight.appdata.xml
|
||||||
appstream.path = $$PREFIX/$$DATADIR/metainfo/
|
appstream.path = $$PREFIX/$$DATADIR/metainfo/
|
||||||
|
|
||||||
appdata.files = SDL_GameControllerDB/gamecontrollerdb.txt ModeSeven.ttf
|
INSTALLS += target desktop icons appstream
|
||||||
appdata.path = "$$PREFIX/$$DATADIR/Moonlight Game Streaming Project/Moonlight/"
|
|
||||||
|
|
||||||
CONFIG(debug, debug|release) {
|
|
||||||
# Copy the required data files into the application directory only for debug builds.
|
|
||||||
# Release builds will use the INSTALLS variable to place these in the correct folders.
|
|
||||||
copyToDestDir(SDL_GameControllerDB/gamecontrollerdb.txt ModeSeven.ttf)
|
|
||||||
}
|
|
||||||
|
|
||||||
INSTALLS += target appdata desktop icons appstream
|
|
||||||
}
|
}
|
||||||
win32 {
|
win32 {
|
||||||
RC_ICONS = moonlight.ico
|
RC_ICONS = moonlight.ico
|
||||||
@@ -336,9 +354,6 @@ win32 {
|
|||||||
QMAKE_TARGET_DESCRIPTION = Moonlight Game Streaming Client
|
QMAKE_TARGET_DESCRIPTION = Moonlight Game Streaming Client
|
||||||
QMAKE_TARGET_PRODUCT = Moonlight
|
QMAKE_TARGET_PRODUCT = Moonlight
|
||||||
|
|
||||||
# Copy the required data files into the application directory
|
|
||||||
copyToDestDir(SDL_GameControllerDB/gamecontrollerdb.txt ModeSeven.ttf)
|
|
||||||
|
|
||||||
CONFIG -= embed_manifest_exe
|
CONFIG -= embed_manifest_exe
|
||||||
QMAKE_LFLAGS += /MANIFEST:embed /MANIFESTINPUT:$${PWD}/Moonlight.exe.manifest
|
QMAKE_LFLAGS += /MANIFEST:embed /MANIFESTINPUT:$${PWD}/Moonlight.exe.manifest
|
||||||
}
|
}
|
||||||
@@ -349,7 +364,7 @@ macx {
|
|||||||
|
|
||||||
QMAKE_INFO_PLIST = $$OUT_PWD/Info.plist
|
QMAKE_INFO_PLIST = $$OUT_PWD/Info.plist
|
||||||
|
|
||||||
APP_BUNDLE_RESOURCES.files = moonlight.icns SDL_GameControllerDB/gamecontrollerdb.txt ModeSeven.ttf
|
APP_BUNDLE_RESOURCES.files = moonlight.icns
|
||||||
APP_BUNDLE_RESOURCES.path = Contents/Resources
|
APP_BUNDLE_RESOURCES.path = Contents/Resources
|
||||||
|
|
||||||
APP_BUNDLE_FRAMEWORKS.files = $$files(../libs/mac/Frameworks/*.framework, true)
|
APP_BUNDLE_FRAMEWORKS.files = $$files(../libs/mac/Frameworks/*.framework, true)
|
||||||
|
|||||||
@@ -89,10 +89,9 @@ QUrl BoxArtManager::loadBoxArt(NvComputer* computer, NvApp& app)
|
|||||||
|
|
||||||
void BoxArtManager::handleBoxArtLoadComplete(NvComputer* computer, NvApp app, QUrl image)
|
void BoxArtManager::handleBoxArtLoadComplete(NvComputer* computer, NvApp app, QUrl image)
|
||||||
{
|
{
|
||||||
if (image.isEmpty()) {
|
if (!image.isEmpty()) {
|
||||||
image = QUrl("qrc:/res/no_app_image.png");
|
emit boxArtLoadComplete(computer, app, image);
|
||||||
}
|
}
|
||||||
emit boxArtLoadComplete(computer, app, image);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl BoxArtManager::loadBoxArtFromNetwork(NvComputer* computer, int appId)
|
QUrl BoxArtManager::loadBoxArtFromNetwork(NvComputer* computer, int appId)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
#define SER_HOSTS "hosts"
|
#define SER_HOSTS "hosts"
|
||||||
|
|
||||||
@@ -130,8 +131,12 @@ private:
|
|||||||
emit computerStateChanged(m_Computer);
|
emit computerStateChanged(m_Computer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait a bit to poll again
|
// Wait a bit to poll again, but do it in 100 ms chunks
|
||||||
QThread::sleep(3);
|
// so we can be interrupted reasonably quickly.
|
||||||
|
// FIXME: QWaitCondition would be better.
|
||||||
|
for (int i = 0; i < 30 && !isInterruptionRequested(); i++) {
|
||||||
|
QThread::msleep(100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +162,13 @@ ComputerManager::ComputerManager(QObject *parent)
|
|||||||
m_KnownHosts[computer->uuid] = computer;
|
m_KnownHosts[computer->uuid] = computer;
|
||||||
}
|
}
|
||||||
settings.endArray();
|
settings.endArray();
|
||||||
|
|
||||||
|
// To quit in a timely manner, we must block additional requests
|
||||||
|
// after we receive the aboutToQuit() signal. This is neccessary
|
||||||
|
// 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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
ComputerManager::~ComputerManager()
|
ComputerManager::~ComputerManager()
|
||||||
@@ -339,6 +351,17 @@ void ComputerManager::deleteHost(NvComputer* computer)
|
|||||||
QThreadPool::globalInstance()->start(new DeferredHostDeletionTask(this, computer));
|
QThreadPool::globalInstance()->start(new DeferredHostDeletionTask(this, computer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ComputerManager::handleAboutToQuit()
|
||||||
|
{
|
||||||
|
QWriteLocker lock(&m_Lock);
|
||||||
|
|
||||||
|
// Interrupt polling threads immediately, so they
|
||||||
|
// avoid making additional requests while quitting
|
||||||
|
for (ComputerPollingEntry* entry : m_PollEntries) {
|
||||||
|
entry->interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PendingPairingTask : public QObject, public QRunnable
|
class PendingPairingTask : public QObject, public QRunnable
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -378,7 +401,7 @@ private:
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (const GfeHttpResponseException& e) {
|
} catch (const GfeHttpResponseException& e) {
|
||||||
emit pairingCompleted(m_Computer, e.toQString());
|
emit pairingCompleted(m_Computer, "GeForce Experience returned error: " + e.toQString());
|
||||||
} catch (const QtNetworkReplyException& e) {
|
} catch (const QtNetworkReplyException& e) {
|
||||||
emit pairingCompleted(m_Computer, e.toQString());
|
emit pairingCompleted(m_Computer, e.toQString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,6 +161,8 @@ signals:
|
|||||||
void quitAppCompleted(QVariant error);
|
void quitAppCompleted(QVariant error);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
void handleAboutToQuit();
|
||||||
|
|
||||||
void handleComputerStateChanged(NvComputer* computer);
|
void handleComputerStateChanged(NvComputer* computer);
|
||||||
|
|
||||||
void handleMdnsServiceResolved(MdnsPendingComputer* computer, const QHostAddress& address);
|
void handleMdnsServiceResolved(MdnsPendingComputer* computer, const QHostAddress& address);
|
||||||
|
|||||||
+27
-19
@@ -33,6 +33,8 @@ NvHTTP::NvHTTP(QString address, QSslCertificate serverCert) :
|
|||||||
// Never use a proxy server
|
// Never use a proxy server
|
||||||
QNetworkProxy noProxy(QNetworkProxy::NoProxy);
|
QNetworkProxy noProxy(QNetworkProxy::NoProxy);
|
||||||
m_Nam.setProxy(noProxy);
|
m_Nam.setProxy(noProxy);
|
||||||
|
|
||||||
|
connect(&m_Nam, &QNetworkAccessManager::sslErrors, this, &NvHTTP::handleSslErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NvHTTP::setServerCert(QSslCertificate serverCert)
|
void NvHTTP::setServerCert(QSslCertificate serverCert)
|
||||||
@@ -377,6 +379,28 @@ NvHTTP::getXmlString(QString xml,
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NvHTTP::handleSslErrors(QNetworkReply* reply, const QList<QSslError>& errors)
|
||||||
|
{
|
||||||
|
bool ignoreErrors = true;
|
||||||
|
|
||||||
|
if (m_ServerCert.isNull()) {
|
||||||
|
// We should never make an HTTPS request without a cert
|
||||||
|
Q_ASSERT(!m_ServerCert.isNull());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto error : errors) {
|
||||||
|
if (m_ServerCert != error.certificate()) {
|
||||||
|
ignoreErrors = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignoreErrors) {
|
||||||
|
reply->ignoreSslErrors(errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString
|
QString
|
||||||
NvHTTP::openConnectionToString(QUrl baseUrl,
|
NvHTTP::openConnectionToString(QUrl baseUrl,
|
||||||
QString command,
|
QString command,
|
||||||
@@ -413,33 +437,17 @@ NvHTTP::openConnection(QUrl baseUrl,
|
|||||||
QUuid::createUuid().toRfc4122().toHex() +
|
QUuid::createUuid().toRfc4122().toHex() +
|
||||||
((arguments != nullptr) ? ("&" + arguments) : ""));
|
((arguments != nullptr) ? ("&" + arguments) : ""));
|
||||||
|
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
QNetworkRequest request(url);
|
||||||
|
|
||||||
// Add our client certificate
|
// Add our client certificate
|
||||||
request.setSslConfiguration(IdentityManager::get()->getSslConfig());
|
request.setSslConfiguration(IdentityManager::get()->getSslConfig());
|
||||||
|
|
||||||
QNetworkReply* reply = m_Nam.get(request);
|
QNetworkReply* reply = m_Nam.get(request);
|
||||||
|
|
||||||
// Assert that we always have a server cert for HTTPS, since
|
|
||||||
// the request will fail anyway if we do not.
|
|
||||||
Q_ASSERT(!m_ServerCert.isNull() || baseUrl == m_BaseUrlHttp);
|
|
||||||
|
|
||||||
if (!m_ServerCert.isNull()) {
|
|
||||||
// Pin the server certificate received during pairing
|
|
||||||
QList<QSslError> expectedSslErrors;
|
|
||||||
expectedSslErrors.append(QSslError(QSslError::HostNameMismatch, m_ServerCert));
|
|
||||||
expectedSslErrors.append(QSslError(QSslError::SelfSignedCertificate, m_ServerCert));
|
|
||||||
|
|
||||||
// The SecureTransport backend for Qt TLS on macOS throws CertificateUntrusted
|
|
||||||
// instead of SelfSignedCertificate, so we will need to allow that error too.
|
|
||||||
expectedSslErrors.append(QSslError(QSslError::CertificateUntrusted, m_ServerCert));
|
|
||||||
|
|
||||||
reply->ignoreSslErrors(expectedSslErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the request with a timeout if requested
|
// Run the request with a timeout if requested
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||||
|
connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), &loop, SLOT(quit()));
|
||||||
if (timeoutMs) {
|
if (timeoutMs) {
|
||||||
QTimer::singleShot(timeoutMs, &loop, SLOT(quit()));
|
QTimer::singleShot(timeoutMs, &loop, SLOT(quit()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,8 +113,10 @@ private:
|
|||||||
QString m_ErrorText;
|
QString m_ErrorText;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NvHTTP
|
class NvHTTP : public QObject
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum NvLogLevel {
|
enum NvLogLevel {
|
||||||
NVLL_NONE,
|
NVLL_NONE,
|
||||||
@@ -184,6 +186,9 @@ public:
|
|||||||
QUrl m_BaseUrlHttp;
|
QUrl m_BaseUrlHttp;
|
||||||
QUrl m_BaseUrlHttps;
|
QUrl m_BaseUrlHttps;
|
||||||
private:
|
private:
|
||||||
|
void
|
||||||
|
handleSslErrors(QNetworkReply* reply, const QList<QSslError>& errors);
|
||||||
|
|
||||||
QNetworkReply*
|
QNetworkReply*
|
||||||
openConnection(QUrl baseUrl,
|
openConnection(QUrl baseUrl,
|
||||||
QString command,
|
QString command,
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
#include "systemproperties.h"
|
||||||
|
|
||||||
|
#include <QGuiApplication>
|
||||||
|
|
||||||
|
#include "streaming/session.h"
|
||||||
|
#include "streaming/streamutils.h"
|
||||||
|
|
||||||
|
SystemProperties::SystemProperties()
|
||||||
|
{
|
||||||
|
isRunningWayland = qgetenv("XDG_SESSION_TYPE") == "wayland";
|
||||||
|
isRunningXWayland = qgetenv("XDG_SESSION_TYPE") == "wayland" && QGuiApplication::platformName() == "xcb";
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
isWow64 = QSysInfo::currentCpuArchitecture() != QSysInfo::buildCpuArchitecture();
|
||||||
|
#else
|
||||||
|
isWow64 = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef STEAM_LINK
|
||||||
|
hasBrowser = true;
|
||||||
|
#else
|
||||||
|
hasBrowser = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
unmappedGamepads = SdlInputHandler::getUnmappedGamepads();
|
||||||
|
|
||||||
|
// Populate data that requires talking to SDL. We do it all in one shot
|
||||||
|
// and cache the results to speed up future queries on this data.
|
||||||
|
querySdlVideoInfo();
|
||||||
|
|
||||||
|
Q_ASSERT(maximumStreamingFrameRate >= 60);
|
||||||
|
Q_ASSERT(!monitorDesktopResolutions.isEmpty());
|
||||||
|
Q_ASSERT(!monitorNativeResolutions.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect SystemProperties::getDesktopResolution(int displayIndex)
|
||||||
|
{
|
||||||
|
// Returns default constructed QRect if out of bounds
|
||||||
|
return monitorDesktopResolutions.value(displayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect SystemProperties::getNativeResolution(int displayIndex)
|
||||||
|
{
|
||||||
|
// Returns default constructed QRect if out of bounds
|
||||||
|
return monitorNativeResolutions.value(displayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemProperties::querySdlVideoInfo()
|
||||||
|
{
|
||||||
|
monitorDesktopResolutions.clear();
|
||||||
|
monitorNativeResolutions.clear();
|
||||||
|
hasHardwareAcceleration = false;
|
||||||
|
|
||||||
|
// Never let the maximum drop below 60 FPS
|
||||||
|
maximumStreamingFrameRate = 60;
|
||||||
|
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_DisplayMode bestMode;
|
||||||
|
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
|
||||||
|
SDL_DisplayMode desktopMode;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = SDL_GetDesktopDisplayMode(displayIndex, &desktopMode);
|
||||||
|
if (err == 0) {
|
||||||
|
monitorDesktopResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL_GetDesktopDisplayMode() failed: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StreamUtils::getRealDesktopMode(displayIndex, &desktopMode)) {
|
||||||
|
monitorNativeResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
|
||||||
|
|
||||||
|
// Start at desktop mode and work our way up
|
||||||
|
bestMode = desktopMode;
|
||||||
|
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
||||||
|
SDL_DisplayMode mode;
|
||||||
|
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
|
||||||
|
if (mode.w == desktopMode.w && mode.h == desktopMode.h) {
|
||||||
|
if (mode.refresh_rate > bestMode.refresh_rate) {
|
||||||
|
bestMode = mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maximumStreamingFrameRate = qMax(maximumStreamingFrameRate, bestMode.refresh_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Window* testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN);
|
||||||
|
if (!testWindow) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to create window for hardware decode test: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasHardwareAcceleration =
|
||||||
|
Session::isHardwareDecodeAvailable(testWindow,
|
||||||
|
StreamingPreferences::VDS_AUTO,
|
||||||
|
VIDEO_FORMAT_H264,
|
||||||
|
1920, 1080, 60);
|
||||||
|
|
||||||
|
SDL_DestroyWindow(testWindow);
|
||||||
|
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QRect>
|
||||||
|
|
||||||
|
class SystemProperties : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
SystemProperties();
|
||||||
|
|
||||||
|
Q_PROPERTY(bool hasHardwareAcceleration MEMBER hasHardwareAcceleration CONSTANT)
|
||||||
|
Q_PROPERTY(bool isRunningWayland MEMBER isRunningWayland CONSTANT)
|
||||||
|
Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT)
|
||||||
|
Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT)
|
||||||
|
Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT)
|
||||||
|
Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged)
|
||||||
|
Q_PROPERTY(int maximumStreamingFrameRate MEMBER maximumStreamingFrameRate CONSTANT)
|
||||||
|
|
||||||
|
Q_INVOKABLE QRect getDesktopResolution(int displayIndex);
|
||||||
|
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void unmappedGamepadsChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void querySdlVideoInfo();
|
||||||
|
|
||||||
|
bool hasHardwareAcceleration;
|
||||||
|
bool isRunningWayland;
|
||||||
|
bool isRunningXWayland;
|
||||||
|
bool isWow64;
|
||||||
|
bool hasBrowser;
|
||||||
|
QString unmappedGamepads;
|
||||||
|
int maximumStreamingFrameRate;
|
||||||
|
QList<QRect> monitorDesktopResolutions;
|
||||||
|
QList<QRect> monitorNativeResolutions;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ public:
|
|||||||
|
|
||||||
void showMessage(QString message, MessageType type) const
|
void showMessage(QString message, MessageType type) const
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN32)
|
||||||
UINT flags = MB_OK | MB_TOPMOST | MB_SETFOREGROUND;
|
UINT flags = MB_OK | MB_TOPMOST | MB_SETFOREGROUND;
|
||||||
flags |= (type == Info ? MB_ICONINFORMATION : MB_ICONERROR);
|
flags |= (type == Info ? MB_ICONINFORMATION : MB_ICONERROR);
|
||||||
QString title = "Moonlight";
|
QString title = "Moonlight";
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<li>Hardware accelerated video decoding with VAAPI and VDPAU support</li>
|
<li>Hardware accelerated video decoding with VAAPI and VDPAU support</li>
|
||||||
<li>5.1 surround sound audio support</li>
|
<li>5.1 surround sound audio support</li>
|
||||||
<li>HEVC support for better video compression efficiency</li>
|
<li>HEVC support for better video compression efficiency</li>
|
||||||
<li>Gamepad support with SDL mapping compatibility</li>
|
<li>Gamepad support with force feedback for up to 4 players</li>
|
||||||
</ul>
|
</ul>
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
@@ -33,6 +33,43 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.10.1" date="2019-04-28">
|
||||||
|
<description>
|
||||||
|
<p>New features:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Video performance optimizations</li>
|
||||||
|
<li>VAAPI support on Wayland</li>
|
||||||
|
</ul>
|
||||||
|
<p>Bugfixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Fixed a crash when attempting to dismiss the connection terminated dialog</li>
|
||||||
|
<li>Fixed a crash when using the overlay on the software decoder</li>
|
||||||
|
<li>Fixed software decoder not displaying output in some cases</li>
|
||||||
|
<li>Fixed some minor UI issues on the PC grid</li>
|
||||||
|
<li>Improved visibility of toolbar highlights</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="0.10.0" date="2019-04-07">
|
||||||
|
<description>
|
||||||
|
<p>New features:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Redesigned App Grid with larger game artwork and less blank space</li>
|
||||||
|
<li>Redesigned UI dialogs using Material Design</li>
|
||||||
|
<li>Added a connection warning overlay (which can be disabled in settings)</li>
|
||||||
|
<li>Streaming window no longer minimizes when it loses focus in full-screen mode</li>
|
||||||
|
<li>The Steam Link device is now a supported platform</li>
|
||||||
|
</ul>
|
||||||
|
<p>Bugfixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Fixed several bugs that could cause UI hangs or lag</li>
|
||||||
|
<li>Fixed rumble support on gamepads with limited rumble motor control</li>
|
||||||
|
<li>Fixed increased memory usage if the software renderer gets delayed</li>
|
||||||
|
<li>Fixed crashes on certain desktop configurations when loading the UI</li>
|
||||||
|
<li>Fixed handling of pairing certificates with a bogus date</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="0.9.1" date="2019-02-17">
|
<release version="0.9.1" date="2019-02-17">
|
||||||
<description>
|
<description>
|
||||||
<p>New features:</p>
|
<p>New features:</p>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# The default HOME is not persistent, so override
|
||||||
|
# it to a path on the onboard flash. Otherwise our
|
||||||
|
# pairing data will be lost each reboot.
|
||||||
|
HOME=/usr/local/moonlight
|
||||||
|
|
||||||
|
# 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
|
||||||
|
exec nice -n -10 ./bin/moonlight > /tmp/moonlight.log
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
name=Moonlight
|
||||||
|
icon=moonlight.png
|
||||||
|
run=moonlight.sh
|
||||||
+77
-65
@@ -1,29 +1,20 @@
|
|||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Dialogs 1.2
|
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
import AppModel 1.0
|
import AppModel 1.0
|
||||||
import ComputerManager 1.0
|
import ComputerManager 1.0
|
||||||
import SdlGamepadKeyNavigation 1.0
|
|
||||||
|
|
||||||
GridView {
|
CenteredGridView {
|
||||||
property int computerIndex
|
property int computerIndex
|
||||||
property AppModel appModel : createModel()
|
property AppModel appModel : createModel()
|
||||||
|
property bool activated
|
||||||
|
|
||||||
id: appGrid
|
id: appGrid
|
||||||
focus: true
|
focus: true
|
||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
anchors.fill: parent
|
topMargin: 20
|
||||||
anchors.leftMargin: (parent.width % (cellWidth + anchors.rightMargin)) / 2
|
bottomMargin: 5
|
||||||
anchors.topMargin: 20
|
cellWidth: 230; cellHeight: 297;
|
||||||
anchors.rightMargin: 5
|
|
||||||
anchors.bottomMargin: 5
|
|
||||||
cellWidth: 225; cellHeight: 385;
|
|
||||||
|
|
||||||
// The StackView will trigger a visibility change when
|
|
||||||
// we're pushed onto it, causing our onVisibleChanged
|
|
||||||
// routine to run, but only if we start as invisible
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
function computerLost()
|
function computerLost()
|
||||||
{
|
{
|
||||||
@@ -32,23 +23,20 @@ GridView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// Don't show any highlighted item until interacting with them
|
// Don't show any highlighted item until interacting with them.
|
||||||
|
// We do this here instead of onActivated to avoid losing the user's
|
||||||
|
// selection when backing out of a different page of the app.
|
||||||
currentIndex = -1
|
currentIndex = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
SdlGamepadKeyNavigation {
|
StackView.onActivated: {
|
||||||
id: gamepadKeyNav
|
appModel.computerLost.connect(computerLost)
|
||||||
|
activated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
StackView.onDeactivating: {
|
||||||
if (visible) {
|
appModel.computerLost.disconnect(computerLost)
|
||||||
appModel.computerLost.connect(computerLost)
|
activated = false
|
||||||
gamepadKeyNav.enable()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
appModel.computerLost.disconnect(computerLost)
|
|
||||||
gamepadKeyNav.disable()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createModel()
|
function createModel()
|
||||||
@@ -61,26 +49,44 @@ GridView {
|
|||||||
model: appModel
|
model: appModel
|
||||||
|
|
||||||
delegate: NavigableItemDelegate {
|
delegate: NavigableItemDelegate {
|
||||||
width: 200; height: 335;
|
width: 220; height: 287;
|
||||||
grid: appGrid
|
grid: appGrid
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
|
property bool isPlaceholder: false
|
||||||
|
|
||||||
id: appIcon
|
id: appIcon
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
y: 20
|
y: 10
|
||||||
source: model.boxart
|
source: model.boxart
|
||||||
sourceSize {
|
|
||||||
width: 150
|
onSourceSizeChanged: {
|
||||||
height: 200
|
if ((sourceSize.width == 130 && sourceSize.height == 180) || // GFE 2.0 placeholder image
|
||||||
|
(sourceSize.width == 628 && sourceSize.height == 888) || // GFE 3.0 placeholder image
|
||||||
|
(sourceSize.width == 200 && sourceSize.height == 266)) // Our no_app_image.png
|
||||||
|
{
|
||||||
|
isPlaceholder = true
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isPlaceholder = false
|
||||||
|
}
|
||||||
|
|
||||||
|
width = 200
|
||||||
|
height = 267
|
||||||
}
|
}
|
||||||
width: sourceSize.width
|
|
||||||
height: sourceSize.height
|
// Display a tooltip with the full name if it's truncated
|
||||||
fillMode: Image.Pad
|
ToolTip.text: model.name
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 5000
|
||||||
|
ToolTip.visible: (parent.hovered || parent.highlighted) && (!appNameText.visible || appNameText.truncated)
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
id: resumeButton
|
id: resumeButton
|
||||||
anchors.verticalCenterOffset: -50
|
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? -47 : 0
|
||||||
|
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : -60
|
||||||
anchors.centerIn: appIcon
|
anchors.centerIn: appIcon
|
||||||
visible: model.running
|
visible: model.running
|
||||||
implicitWidth: 125
|
implicitWidth: 125
|
||||||
@@ -107,7 +113,8 @@ GridView {
|
|||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
id: quitButton
|
id: quitButton
|
||||||
anchors.verticalCenterOffset: 50
|
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? 47 : 0
|
||||||
|
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : 60
|
||||||
anchors.centerIn: appIcon
|
anchors.centerIn: appIcon
|
||||||
visible: model.running
|
visible: model.running
|
||||||
implicitWidth: 125
|
implicitWidth: 125
|
||||||
@@ -134,20 +141,18 @@ GridView {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: appNameText
|
id: appNameText
|
||||||
|
visible: appIcon.isPlaceholder
|
||||||
text: model.name
|
text: model.name
|
||||||
width: parent.width
|
width: appIcon.width
|
||||||
height: 125
|
height: model.running ? 175 : appIcon.height
|
||||||
anchors.top: appIcon.bottom
|
anchors.left: appIcon.left
|
||||||
|
anchors.right: appIcon.right
|
||||||
|
anchors.bottom: appIcon.bottom
|
||||||
font.pointSize: 22
|
font.pointSize: 22
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
|
||||||
// Display a tooltip with the full name if it's truncated
|
|
||||||
ToolTip.text: model.name
|
|
||||||
ToolTip.delay: 1000
|
|
||||||
ToolTip.timeout: 5000
|
|
||||||
ToolTip.visible: (parent.hovered || parent.highlighted) && truncated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function launchOrResumeSelectedApp()
|
function launchOrResumeSelectedApp()
|
||||||
@@ -168,19 +173,29 @@ GridView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
// Only allow clicking on the box art for non-running games.
|
||||||
|
// For running games, buttons will appear to resume or quit which
|
||||||
|
// will handle starting the game and clicks on the box art will
|
||||||
|
// be ignored.
|
||||||
|
if (!model.running) {
|
||||||
|
launchOrResumeSelectedApp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
// Open the app context menu if activated via the gamepad or keyboard
|
||||||
|
// for running games. If the game isn't running, the above onClicked
|
||||||
|
// method will handle the launch.
|
||||||
if (model.running) {
|
if (model.running) {
|
||||||
// This will primarily be keyboard/gamepad driven so use
|
// This will be keyboard/gamepad driven so use
|
||||||
// open() instead of popup()
|
// open() instead of popup()
|
||||||
appContextMenu.open()
|
appContextMenu.open()
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
launchOrResumeSelectedApp()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onMenuPressed: {
|
Keys.onMenuPressed: {
|
||||||
if (model.running) {
|
if (model.running) {
|
||||||
// This will primarily be keyboard/gamepad driven so use
|
// This will be keyboard/gamepad driven so use
|
||||||
// open() instead of popup()
|
// open() instead of popup()
|
||||||
appContextMenu.open()
|
appContextMenu.open()
|
||||||
}
|
}
|
||||||
@@ -197,10 +212,7 @@ GridView {
|
|||||||
NavigableMenuItem {
|
NavigableMenuItem {
|
||||||
parentMenu: appContextMenu
|
parentMenu: appContextMenu
|
||||||
text: model.running ? "Resume Game" : "Launch Game"
|
text: model.running ? "Resume Game" : "Launch Game"
|
||||||
onTriggered: {
|
onTriggered: launchOrResumeSelectedApp()
|
||||||
appContextMenu.close()
|
|
||||||
launchOrResumeSelectedApp()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
NavigableMenuItem {
|
NavigableMenuItem {
|
||||||
parentMenu: appContextMenu
|
parentMenu: appContextMenu
|
||||||
@@ -211,15 +223,14 @@ GridView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
NavigableMessageDialog {
|
||||||
id: quitAppDialog
|
id: quitAppDialog
|
||||||
modality:Qt.WindowModal
|
|
||||||
property string appName : ""
|
property string appName : ""
|
||||||
property bool segueToStream : false
|
property bool segueToStream : false
|
||||||
property string nextAppName: ""
|
property string nextAppName: ""
|
||||||
property int nextAppIndex: 0
|
property int nextAppIndex: 0
|
||||||
text:"Are you sure you want to quit " + appName +"? Any unsaved progress will be lost."
|
text:"Are you sure you want to quit " + appName +"? Any unsaved progress will be lost."
|
||||||
standardButtons: StandardButton.Yes | StandardButton.No
|
standardButtons: Dialog.Yes | Dialog.No
|
||||||
|
|
||||||
function quitApp() {
|
function quitApp() {
|
||||||
var component = Qt.createComponent("QuitSegue.qml")
|
var component = Qt.createComponent("QuitSegue.qml")
|
||||||
@@ -241,20 +252,21 @@ GridView {
|
|||||||
appModel.quitRunningApp()
|
appModel.quitRunningApp()
|
||||||
}
|
}
|
||||||
|
|
||||||
onYes: quitApp()
|
|
||||||
|
|
||||||
// For keyboard/gamepad navigation
|
|
||||||
onAccepted: quitApp()
|
onAccepted: quitApp()
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
// Manually hide the scrollbar to prevent it from being drawn on top
|
||||||
|
// of the StreamSegue during the transition. It can sometimes get stuck
|
||||||
|
// there since we're not consistently pumping the event loop while
|
||||||
|
// starting the stream.
|
||||||
|
visible: activated
|
||||||
|
|
||||||
parent: appGrid.parent
|
parent: appGrid.parent
|
||||||
anchors {
|
anchors {
|
||||||
top: appGrid.top
|
top: parent.top
|
||||||
left: appGrid.right
|
right: parent.right
|
||||||
bottom: appGrid.bottom
|
bottom: parent.bottom
|
||||||
|
|
||||||
leftMargin: -10
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import QtQuick 2.9
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
|
GridView {
|
||||||
|
// Detect Qt 5.11 or earlier using presence of synchronousDrag.
|
||||||
|
// Prior to 5.12, the leftMargin and rightMargin values did not work.
|
||||||
|
property bool hasBrokenMargins: this.synchronousDrag === undefined
|
||||||
|
|
||||||
|
property int minMargin: 10
|
||||||
|
property real availableWidth: (parent.width - 2 * minMargin)
|
||||||
|
property int itemsPerRow: availableWidth / cellWidth
|
||||||
|
property real horizontalMargin: itemsPerRow < count && availableWidth >= cellWidth ?
|
||||||
|
(availableWidth % cellWidth) / 2 : minMargin
|
||||||
|
|
||||||
|
function updateMargins() {
|
||||||
|
leftMargin = horizontalMargin
|
||||||
|
rightMargin = horizontalMargin
|
||||||
|
|
||||||
|
if (hasBrokenMargins) {
|
||||||
|
anchors.leftMargin = leftMargin
|
||||||
|
anchors.rightMargin = rightMargin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onHorizontalMarginChanged: {
|
||||||
|
updateMargins()
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (hasBrokenMargins) {
|
||||||
|
// This will cause an anchor conflict with the parent StackView
|
||||||
|
// which breaks animation, but otherwise the grid will not be
|
||||||
|
// centered in the window.
|
||||||
|
anchors.fill = parent
|
||||||
|
anchors.topMargin = topMargin
|
||||||
|
anchors.bottomMargin = bottomMargin
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMargins()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Dialogs 1.2
|
|
||||||
|
|
||||||
import ComputerManager 1.0
|
import ComputerManager 1.0
|
||||||
import Session 1.0
|
import Session 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
function onSearchingComputer() {
|
function onSearchingComputer() {
|
||||||
stageLabel.text = "Establishing connection to PC..."
|
stageLabel.text = "Establishing connection to PC..."
|
||||||
}
|
}
|
||||||
@@ -21,13 +18,8 @@ Item {
|
|||||||
errorDialog.open()
|
errorDialog.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
// The StackView will trigger a visibility change when
|
StackView.onActivated: {
|
||||||
// we're pushed onto it, causing our onVisibleChanged
|
if (!launcher.isExecuted()) {
|
||||||
// routine to run, but only if we start as invisible
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible && !launcher.isExecuted()) {
|
|
||||||
toolBar.visible = false
|
toolBar.visible = false
|
||||||
launcher.searchingComputer.connect(onSearchingComputer)
|
launcher.searchingComputer.connect(onSearchingComputer)
|
||||||
launcher.quittingApp.connect(onQuittingApp)
|
launcher.quittingApp.connect(onQuittingApp)
|
||||||
@@ -55,16 +47,11 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
ErrorMessageDialog {
|
||||||
id: errorDialog
|
id: errorDialog
|
||||||
modality:Qt.WindowModal
|
|
||||||
icon: StandardIcon.Critical
|
|
||||||
standardButtons: StandardButton.Ok
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
onClosed: {
|
||||||
if (!visible) {
|
Qt.quit()
|
||||||
Qt.quit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,9 @@
|
|||||||
import QtQml 2.2
|
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Dialogs 1.2
|
|
||||||
|
|
||||||
import ComputerManager 1.0
|
import ComputerManager 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
visible: false
|
|
||||||
|
|
||||||
function onSearchingComputer() {
|
function onSearchingComputer() {
|
||||||
stageLabel.text = "Establishing connection to PC..."
|
stageLabel.text = "Establishing connection to PC..."
|
||||||
}
|
}
|
||||||
@@ -36,8 +32,8 @@ Item {
|
|||||||
quitAppDialog.open()
|
quitAppDialog.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
StackView.onActivated: {
|
||||||
if (visible && !launcher.isExecuted()) {
|
if (!launcher.isExecuted()) {
|
||||||
toolBar.visible = false
|
toolBar.visible = false
|
||||||
launcher.searchingComputer.connect(onSearchingComputer)
|
launcher.searchingComputer.connect(onSearchingComputer)
|
||||||
launcher.searchingApp.connect(onSearchingApp)
|
launcher.searchingApp.connect(onSearchingApp)
|
||||||
@@ -66,26 +62,18 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
ErrorMessageDialog {
|
||||||
id: errorDialog
|
id: errorDialog
|
||||||
modality:Qt.WindowModal
|
|
||||||
icon: StandardIcon.Critical
|
onClosed: {
|
||||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
Qt.quit();
|
||||||
onHelp: {
|
|
||||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
|
|
||||||
}
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (!visible) {
|
|
||||||
Qt.quit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
NavigableMessageDialog {
|
||||||
id: quitAppDialog
|
id: quitAppDialog
|
||||||
modality:Qt.WindowModal
|
|
||||||
text:"Are you sure you want to quit " + appName +"? Any unsaved progress will be lost."
|
text:"Are you sure you want to quit " + appName +"? Any unsaved progress will be lost."
|
||||||
standardButtons: StandardButton.Yes | StandardButton.No
|
standardButtons: Dialog.Yes | Dialog.No
|
||||||
property string appName : ""
|
property string appName : ""
|
||||||
|
|
||||||
function quitApp() {
|
function quitApp() {
|
||||||
@@ -96,29 +84,7 @@ Item {
|
|||||||
launcher.quitRunningApp()
|
launcher.quitRunningApp()
|
||||||
}
|
}
|
||||||
|
|
||||||
onYes: quitApp()
|
|
||||||
|
|
||||||
// For keyboard/gamepad navigation
|
|
||||||
onAccepted: quitApp()
|
onAccepted: quitApp()
|
||||||
|
onRejected: Qt.quit()
|
||||||
// Exit process if app quit is rejected (reacts also to closing of the
|
|
||||||
// dialog from title bar's close button).
|
|
||||||
// Note: this depends on undocumented behavior of visibleChanged()
|
|
||||||
// signal being emitted before yes() or accepted() has been emitted.
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (!visible) {
|
|
||||||
quitTimer.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Component.onCompleted: {
|
|
||||||
yes.connect(quitTimer.stop)
|
|
||||||
accepted.connect(quitTimer.stop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: quitTimer
|
|
||||||
interval: 100
|
|
||||||
onTriggered: Qt.quit()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import QtQuick 2.0
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
|
import SystemProperties 1.0
|
||||||
|
|
||||||
|
NavigableMessageDialog {
|
||||||
|
standardButtons: Dialog.Ok | (SystemProperties.hasBrowser ? Dialog.Help : 0)
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import QtQuick 2.0
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
parent: overlay
|
||||||
|
|
||||||
|
x: Math.round((parent.width - width) / 2)
|
||||||
|
y: Math.round((parent.height - height) / 2)
|
||||||
|
|
||||||
|
onAboutToHide: {
|
||||||
|
// We must force focus back to the last item for platforms without
|
||||||
|
// support for more than one active window like Steam Link. If
|
||||||
|
// we don't, gamepad and keyboard navigation will break after a
|
||||||
|
// dialog appears.
|
||||||
|
stackView.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,13 @@ MenuItem {
|
|||||||
height: visible ? implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
focusPolicy: visible ? Qt.TabFocus : Qt.NoFocus
|
focusPolicy: visible ? Qt.TabFocus : Qt.NoFocus
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
// We must close the context menu first or
|
||||||
|
// it can steal focus from any dialogs that
|
||||||
|
// onTriggered may spawn.
|
||||||
|
parentMenu.close()
|
||||||
|
}
|
||||||
|
|
||||||
Keys.onReturnPressed: {
|
Keys.onReturnPressed: {
|
||||||
triggered()
|
triggered()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import QtQuick 2.0
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
import QtQuick.Layouts 1.2
|
||||||
|
|
||||||
|
NavigableDialog {
|
||||||
|
id: dialog
|
||||||
|
|
||||||
|
property alias text: dialogLabel.dialogText
|
||||||
|
|
||||||
|
property string helpText
|
||||||
|
property string helpUrl : "https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting"
|
||||||
|
property string helpTextSeparator : " "
|
||||||
|
|
||||||
|
onOpened: {
|
||||||
|
// Force keyboard focus on the label so keyboard navigation works
|
||||||
|
dialogLabel.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Image {
|
||||||
|
source: (standardButtons & Dialog.Yes) ?
|
||||||
|
"qrc:/res/baseline-help_outline-24px.svg" :
|
||||||
|
"qrc:/res/baseline-error_outline-24px.svg"
|
||||||
|
sourceSize {
|
||||||
|
// The icon should be square so use the height as the width too
|
||||||
|
width: 50
|
||||||
|
height: 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
property string dialogText
|
||||||
|
|
||||||
|
id: dialogLabel
|
||||||
|
text: dialogText + ((helpText && (standardButtons & Dialog.Help)) ? (helpTextSeparator + helpText) : "")
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
elide: Label.ElideRight
|
||||||
|
|
||||||
|
// Cap the width so the dialog doesn't grow horizontally forever. This
|
||||||
|
// will cause word wrap to kick in.
|
||||||
|
Layout.maximumWidth: 400
|
||||||
|
Layout.maximumHeight: 400
|
||||||
|
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
accept()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onEscapePressed: {
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer: DialogButtonBox {
|
||||||
|
id: dialogButtonBox
|
||||||
|
standardButtons: dialog.standardButtons
|
||||||
|
|
||||||
|
onHelpRequested: {
|
||||||
|
Qt.openUrlExternally(helpUrl)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,27 @@ import QtQuick 2.0
|
|||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
|
property string iconSource
|
||||||
|
|
||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
|
|
||||||
|
// FIXME: We're using an Image here rather than icon.source because
|
||||||
|
// icons don't work on Qt 5.9 LTS.
|
||||||
|
Image {
|
||||||
|
id: image
|
||||||
|
source: iconSource
|
||||||
|
anchors.centerIn: parent.background
|
||||||
|
sourceSize {
|
||||||
|
width: parent.background.width * 1.10
|
||||||
|
height: parent.background.height * 1.10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This determines the size of the Material highlight. We increase it
|
||||||
|
// from the default because we use larger than normal icons for TV readability.
|
||||||
|
background.width: (parent.height - parent.anchors.bottomMargin - parent.anchors.topMargin) * 0.60
|
||||||
|
background.height: (parent.height - parent.anchors.bottomMargin - parent.anchors.topMargin) * 0.60
|
||||||
|
|
||||||
Keys.onReturnPressed: {
|
Keys.onReturnPressed: {
|
||||||
clicked()
|
clicked()
|
||||||
}
|
}
|
||||||
|
|||||||
+70
-119
@@ -1,57 +1,36 @@
|
|||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Dialogs 1.2
|
|
||||||
import QtQuick.Layouts 1.3
|
|
||||||
import QtQuick.Window 2.2
|
|
||||||
|
|
||||||
import ComputerModel 1.0
|
import ComputerModel 1.0
|
||||||
|
|
||||||
import ComputerManager 1.0
|
import ComputerManager 1.0
|
||||||
import StreamingPreferences 1.0
|
import StreamingPreferences 1.0
|
||||||
import SdlGamepadKeyNavigation 1.0
|
|
||||||
|
|
||||||
GridView {
|
CenteredGridView {
|
||||||
property ComputerModel computerModel : createModel()
|
property ComputerModel computerModel : createModel()
|
||||||
|
|
||||||
id: pcGrid
|
id: pcGrid
|
||||||
focus: true
|
focus: true
|
||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
anchors.fill: parent
|
topMargin: 20
|
||||||
anchors.leftMargin: (parent.width % (cellWidth + anchors.rightMargin)) / 2
|
bottomMargin: 5
|
||||||
anchors.topMargin: 20
|
cellWidth: 310; cellHeight: 330;
|
||||||
anchors.rightMargin: 5
|
|
||||||
anchors.bottomMargin: 5
|
|
||||||
cellWidth: 350; cellHeight: 350;
|
|
||||||
objectName: "Computers"
|
objectName: "Computers"
|
||||||
|
|
||||||
// The StackView will trigger a visibility change when
|
|
||||||
// we're pushed onto it, causing our onVisibleChanged
|
|
||||||
// routine to run, but only if we start as invisible
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
StreamingPreferences {
|
|
||||||
id: prefs
|
|
||||||
}
|
|
||||||
|
|
||||||
SdlGamepadKeyNavigation {
|
|
||||||
id: gamepadKeyNav
|
|
||||||
}
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible) {
|
|
||||||
gamepadKeyNav.enable()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
gamepadKeyNav.disable()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
// Don't show any highlighted item until interacting with them.
|
||||||
|
// We do this here instead of onActivated to avoid losing the user's
|
||||||
|
// selection when backing out of a different page of the app.
|
||||||
|
currentIndex = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
StackView.onActivated: {
|
||||||
// Setup signals on CM
|
// Setup signals on CM
|
||||||
ComputerManager.computerAddCompleted.connect(addComplete)
|
ComputerManager.computerAddCompleted.connect(addComplete)
|
||||||
|
}
|
||||||
|
|
||||||
// Don't show any highlighted item until interacting with them
|
StackView.onDeactivating: {
|
||||||
currentIndex = -1
|
ComputerManager.computerAddCompleted.disconnect(addComplete)
|
||||||
}
|
}
|
||||||
|
|
||||||
function pairingComplete(error)
|
function pairingComplete(error)
|
||||||
@@ -62,6 +41,7 @@ GridView {
|
|||||||
// Display a failed dialog if we got an error
|
// Display a failed dialog if we got an error
|
||||||
if (error !== undefined) {
|
if (error !== undefined) {
|
||||||
errorDialog.text = error
|
errorDialog.text = error
|
||||||
|
errorDialog.helpText = ""
|
||||||
errorDialog.open()
|
errorDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,7 +49,8 @@ GridView {
|
|||||||
function addComplete(success)
|
function addComplete(success)
|
||||||
{
|
{
|
||||||
if (!success) {
|
if (!success) {
|
||||||
errorDialog.text = "Unable to connect to the specified PC. Click the Help button for possible solutions."
|
errorDialog.text = "Unable to connect to the specified PC."
|
||||||
|
errorDialog.helpText = "Click the Help button for possible solutions."
|
||||||
errorDialog.open()
|
errorDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,18 +63,37 @@ GridView {
|
|||||||
return model
|
return model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 5
|
||||||
|
visible: pcGrid.count === 0
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
id: searchSpinner
|
||||||
|
visible: StreamingPreferences.enableMdns
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
height: searchSpinner.height
|
||||||
|
elide: Label.ElideRight
|
||||||
|
text: StreamingPreferences.enableMdns ? "Searching for PCs with NVIDIA GameStream enabled..."
|
||||||
|
: "Automatic PC discovery is disabled. Add your PC manually."
|
||||||
|
font.pointSize: 20
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
model: computerModel
|
model: computerModel
|
||||||
|
|
||||||
delegate: NavigableItemDelegate {
|
delegate: NavigableItemDelegate {
|
||||||
width: 300; height: 300;
|
width: 300; height: 320;
|
||||||
grid: pcGrid
|
grid: pcGrid
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: pcIcon
|
id: pcIcon
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
source: {
|
source: "qrc:/res/ic_tv_white_48px.svg"
|
||||||
model.addPc ? "qrc:/res/ic_add_to_queue_white_48px.svg" : "qrc:/res/ic_tv_white_48px.svg"
|
|
||||||
}
|
|
||||||
sourceSize {
|
sourceSize {
|
||||||
width: 200
|
width: 200
|
||||||
height: 200
|
height: 200
|
||||||
@@ -106,7 +106,7 @@ GridView {
|
|||||||
anchors.horizontalCenter: pcIcon.horizontalCenter
|
anchors.horizontalCenter: pcIcon.horizontalCenter
|
||||||
anchors.verticalCenter: pcIcon.verticalCenter
|
anchors.verticalCenter: pcIcon.verticalCenter
|
||||||
anchors.verticalCenterOffset: -10
|
anchors.verticalCenterOffset: -10
|
||||||
visible: !model.addPc && !model.statusUnknown && (!model.online || !model.paired)
|
visible: !model.statusUnknown && (!model.online || !model.paired)
|
||||||
source: !model.online ? "qrc:/res/baseline-warning-24px.svg" : "qrc:/res/baseline-lock-24px.svg"
|
source: !model.online ? "qrc:/res/baseline-warning-24px.svg" : "qrc:/res/baseline-lock-24px.svg"
|
||||||
sourceSize {
|
sourceSize {
|
||||||
width: 75
|
width: 75
|
||||||
@@ -121,7 +121,7 @@ GridView {
|
|||||||
anchors.verticalCenterOffset: -10
|
anchors.verticalCenterOffset: -10
|
||||||
width: 75
|
width: 75
|
||||||
height: 75
|
height: 75
|
||||||
visible: !model.addPc && model.statusUnknown
|
visible: model.statusUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -130,9 +130,11 @@ GridView {
|
|||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
anchors.top: pcIcon.bottom
|
anchors.top: pcIcon.bottom
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
font.pointSize: 36
|
font.pointSize: 36
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigableMenu {
|
NavigableMenu {
|
||||||
@@ -150,15 +152,12 @@ GridView {
|
|||||||
parentMenu: pcContextMenu
|
parentMenu: pcContextMenu
|
||||||
text: "Wake PC"
|
text: "Wake PC"
|
||||||
onTriggered: computerModel.wakeComputer(index)
|
onTriggered: computerModel.wakeComputer(index)
|
||||||
visible: !model.addPc && !model.online && model.wakeable
|
visible: !model.online && model.wakeable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (model.addPc) {
|
if (model.online) {
|
||||||
addPcDialog.open()
|
|
||||||
}
|
|
||||||
else if (model.online) {
|
|
||||||
if (model.paired) {
|
if (model.paired) {
|
||||||
// go to game view
|
// go to game view
|
||||||
var component = Qt.createComponent("AppView.qml")
|
var component = Qt.createComponent("AppView.qml")
|
||||||
@@ -178,7 +177,8 @@ GridView {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// cannot pair while something is streaming or attempting to pair
|
// cannot pair while something is streaming or attempting to pair
|
||||||
errorDialog.text = "This PC is currently busy. Make sure to quit any running games and try again."
|
errorDialog.text = "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."
|
||||||
|
errorDialog.helpText = ""
|
||||||
errorDialog.open()
|
errorDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,15 +189,13 @@ GridView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onPressAndHold: {
|
onPressAndHold: {
|
||||||
if (!model.addPc) {
|
// popup() ensures the menu appears under the mouse cursor
|
||||||
// popup() ensures the menu appears under the mouse cursor
|
if (pcContextMenu.popup) {
|
||||||
if (pcContextMenu.popup) {
|
pcContextMenu.popup()
|
||||||
pcContextMenu.popup()
|
}
|
||||||
}
|
else {
|
||||||
else {
|
// Qt 5.9 doesn't have popup()
|
||||||
// Qt 5.9 doesn't have popup()
|
pcContextMenu.open()
|
||||||
pcContextMenu.open()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,99 +208,52 @@ GridView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Keys.onMenuPressed: {
|
Keys.onMenuPressed: {
|
||||||
if (!model.addPc) {
|
// We must use open() here so the menu is positioned on
|
||||||
// We must use open() here so the menu is positioned on
|
// the ItemDelegate and not where the mouse cursor is
|
||||||
// the ItemDelegate and not where the mouse cursor is
|
pcContextMenu.open()
|
||||||
pcContextMenu.open()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
ErrorMessageDialog {
|
||||||
id: errorDialog
|
id: errorDialog
|
||||||
// don't allow edits to the rest of the window while open
|
|
||||||
modality:Qt.WindowModal
|
// Using Setup-Guide here instead of Troubleshooting because it's likely that users
|
||||||
icon: StandardIcon.Critical
|
// will arrive here by forgetting to enable GameStream or not forwarding ports.
|
||||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide"
|
||||||
onHelp: {
|
|
||||||
// Using Setup-Guide here instead of Troubleshooting because it's likely that users
|
|
||||||
// will arrive here by forgetting to enable GameStream or not forwarding ports.
|
|
||||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
NavigableMessageDialog {
|
||||||
id: pairDialog
|
id: pairDialog
|
||||||
// don't allow edits to the rest of the window while open
|
// don't allow edits to the rest of the window while open
|
||||||
modality:Qt.WindowModal
|
|
||||||
property string pin : "0000"
|
property string pin : "0000"
|
||||||
text:"Please enter " + pin + " on your GameStream PC. This dialog will close when pairing is completed."
|
text:"Please enter " + pin + " on your GameStream PC. This dialog will close when pairing is completed."
|
||||||
standardButtons: StandardButton.Cancel
|
standardButtons: Dialog.Cancel
|
||||||
onRejected: {
|
onRejected: {
|
||||||
// FIXME: We should interrupt pairing here
|
// FIXME: We should interrupt pairing here
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
NavigableMessageDialog {
|
||||||
id: deletePcDialog
|
id: deletePcDialog
|
||||||
// don't allow edits to the rest of the window while open
|
// don't allow edits to the rest of the window while open
|
||||||
modality:Qt.WindowModal
|
|
||||||
property int pcIndex : -1;
|
property int pcIndex : -1;
|
||||||
text:"Are you sure you want to remove this PC?"
|
text:"Are you sure you want to remove this PC?"
|
||||||
standardButtons: StandardButton.Yes | StandardButton.No
|
standardButtons: Dialog.Yes | Dialog.No
|
||||||
|
|
||||||
function deletePc() {
|
function deletePc() {
|
||||||
console.log("deleting PC pairing for PC at index: " + pcIndex)
|
console.log("deleting PC pairing for PC at index: " + pcIndex)
|
||||||
computerModel.deleteComputer(pcIndex);
|
computerModel.deleteComputer(pcIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
onYes: deletePc()
|
|
||||||
|
|
||||||
// For keyboard/gamepad activation
|
|
||||||
onAccepted: deletePc()
|
onAccepted: deletePc()
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog {
|
|
||||||
id: addPcDialog
|
|
||||||
property string label: "Enter the IP address of your GameStream PC"
|
|
||||||
property string hint: "192.168.1.100"
|
|
||||||
property alias editText : editTextItem
|
|
||||||
|
|
||||||
standardButtons: StandardButton.Ok | StandardButton.Cancel
|
|
||||||
onVisibleChanged: {
|
|
||||||
editTextItem.focus = true
|
|
||||||
editTextItem.selectAll()
|
|
||||||
}
|
|
||||||
onAccepted: {
|
|
||||||
ComputerManager.addNewHost(editText.text, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Text {
|
|
||||||
id: addPcDialogLabel
|
|
||||||
text: addPcDialog.label
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
implicitWidth: parent.parent.width
|
|
||||||
height: editTextItem.height
|
|
||||||
|
|
||||||
TextInput {
|
|
||||||
id: editTextItem
|
|
||||||
inputMethodHints: Qt.ImhPreferUppercase
|
|
||||||
text: addPcDialog.hint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
ScrollBar.vertical: ScrollBar {
|
||||||
parent: pcGrid.parent
|
parent: pcGrid.parent
|
||||||
anchors {
|
anchors {
|
||||||
top: pcGrid.top
|
top: parent.top
|
||||||
left: pcGrid.right
|
right: parent.right
|
||||||
bottom: pcGrid.bottom
|
bottom: parent.bottom
|
||||||
|
|
||||||
leftMargin: -10
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+14
-26
@@ -1,6 +1,5 @@
|
|||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Dialogs 1.2
|
|
||||||
|
|
||||||
import ComputerManager 1.0
|
import ComputerManager 1.0
|
||||||
import Session 1.0
|
import Session 1.0
|
||||||
@@ -12,13 +11,6 @@ Item {
|
|||||||
|
|
||||||
property string stageText : "Quitting " + appName + "..."
|
property string stageText : "Quitting " + appName + "..."
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
// The StackView will trigger a visibility change when
|
|
||||||
// we're pushed onto it, causing our onVisibleChanged
|
|
||||||
// routine to run, but only if we start as invisible
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
function quitAppCompleted(error)
|
function quitAppCompleted(error)
|
||||||
{
|
{
|
||||||
// Display a failed dialog if we got an error
|
// Display a failed dialog if we got an error
|
||||||
@@ -34,26 +26,25 @@ Item {
|
|||||||
stackView.replace(segue)
|
stackView.replace(segue)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Show the toolbar again
|
|
||||||
toolBar.visible = true
|
|
||||||
|
|
||||||
// Exit this view
|
// Exit this view
|
||||||
stackView.pop()
|
stackView.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
StackView.onActivated: {
|
||||||
if (visible) {
|
// Hide the toolbar before we start loading
|
||||||
// Hide the toolbar before we start loading
|
toolBar.visible = false
|
||||||
toolBar.visible = false
|
|
||||||
|
|
||||||
// Connect the quit completion signal
|
// Connect the quit completion signal
|
||||||
ComputerManager.quitAppCompleted.connect(quitAppCompleted)
|
ComputerManager.quitAppCompleted.connect(quitAppCompleted)
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// Disconnect the signal
|
StackView.onDeactivating: {
|
||||||
ComputerManager.quitAppCompleted.disconnect(quitAppCompleted)
|
// Show the toolbar again
|
||||||
}
|
toolBar.visible = true
|
||||||
|
|
||||||
|
// Disconnect the signal
|
||||||
|
ComputerManager.quitAppCompleted.disconnect(quitAppCompleted)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -75,10 +66,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
ErrorMessageDialog {
|
||||||
id: errorDialog
|
id: errorDialog
|
||||||
modality:Qt.WindowModal
|
|
||||||
icon: StandardIcon.Critical
|
|
||||||
standardButtons: StandardButton.Ok
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+88
-83
@@ -4,11 +4,11 @@ import QtQuick.Controls 2.2
|
|||||||
import StreamingPreferences 1.0
|
import StreamingPreferences 1.0
|
||||||
import ComputerManager 1.0
|
import ComputerManager 1.0
|
||||||
import SdlGamepadKeyNavigation 1.0
|
import SdlGamepadKeyNavigation 1.0
|
||||||
|
import SystemProperties 1.0
|
||||||
|
|
||||||
Flickable {
|
Flickable {
|
||||||
id: settingsPage
|
id: settingsPage
|
||||||
objectName: "Settings"
|
objectName: "Settings"
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
contentWidth: settingsColumn1.width > settingsColumn2.width ? settingsColumn1.width : settingsColumn2.width
|
contentWidth: settingsColumn1.width > settingsColumn2.width ? settingsColumn1.width : settingsColumn2.width
|
||||||
contentHeight: settingsColumn1.height > settingsColumn2.height ? settingsColumn1.height : settingsColumn2.height
|
contentHeight: settingsColumn1.height > settingsColumn2.height ? settingsColumn1.height : settingsColumn2.height
|
||||||
@@ -24,31 +24,15 @@ Flickable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamingPreferences {
|
StackView.onActivated: {
|
||||||
id: prefs
|
SdlGamepadKeyNavigation.setSettingsMode(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The StackView will trigger a visibility change when
|
StackView.onDeactivating: {
|
||||||
// we're pushed onto it, causing our onVisibleChanged
|
SdlGamepadKeyNavigation.setSettingsMode(false)
|
||||||
// routine to run, but only if we start as invisible
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
SdlGamepadKeyNavigation {
|
// Save the prefs so the Session can observe the changes
|
||||||
id: gamepadKeyNav
|
StreamingPreferences.save()
|
||||||
}
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible) {
|
|
||||||
gamepadKeyNav.setSettingsMode(true)
|
|
||||||
gamepadKeyNav.enable()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
gamepadKeyNav.disable()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onDestruction: {
|
|
||||||
prefs.save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@@ -98,10 +82,10 @@ Flickable {
|
|||||||
// Some platforms have different desktop resolutions
|
// Some platforms have different desktop resolutions
|
||||||
// and native resolutions (like macOS with Retina displays)
|
// and native resolutions (like macOS with Retina displays)
|
||||||
if (displayResIndex == 0) {
|
if (displayResIndex == 0) {
|
||||||
screenRect = prefs.getDesktopResolution(displayIndex)
|
screenRect = SystemProperties.getDesktopResolution(displayIndex)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
screenRect = prefs.getNativeResolution(displayIndex)
|
screenRect = SystemProperties.getNativeResolution(displayIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screenRect.width === 0) {
|
if (screenRect.width === 0) {
|
||||||
@@ -140,8 +124,8 @@ Flickable {
|
|||||||
|
|
||||||
// load the saved width/height, and iterate through the ComboBox until a match is found
|
// load the saved width/height, and iterate through the ComboBox until a match is found
|
||||||
// and set it to that index.
|
// and set it to that index.
|
||||||
var saved_width = prefs.width
|
var saved_width = StreamingPreferences.width
|
||||||
var saved_height = prefs.height
|
var saved_height = StreamingPreferences.height
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
for (var i = 0; i < resolutionListModel.count; i++) {
|
for (var i = 0; i < resolutionListModel.count; i++) {
|
||||||
var el_width = parseInt(resolutionListModel.get(i).video_width);
|
var el_width = parseInt(resolutionListModel.get(i).video_width);
|
||||||
@@ -190,12 +174,14 @@ Flickable {
|
|||||||
var selectedHeight = parseInt(resolutionListModel.get(currentIndex).video_height)
|
var selectedHeight = parseInt(resolutionListModel.get(currentIndex).video_height)
|
||||||
|
|
||||||
// Only modify the bitrate if the values actually changed
|
// Only modify the bitrate if the values actually changed
|
||||||
if (prefs.width !== selectedWidth || prefs.height !== selectedHeight) {
|
if (StreamingPreferences.width !== selectedWidth || StreamingPreferences.height !== selectedHeight) {
|
||||||
prefs.width = selectedWidth
|
StreamingPreferences.width = selectedWidth
|
||||||
prefs.height = selectedHeight
|
StreamingPreferences.height = selectedHeight
|
||||||
|
|
||||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
|
||||||
slider.value = prefs.bitrateKbps
|
StreamingPreferences.height,
|
||||||
|
StreamingPreferences.fps);
|
||||||
|
slider.value = StreamingPreferences.bitrateKbps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,15 +190,14 @@ Flickable {
|
|||||||
function createModel() {
|
function createModel() {
|
||||||
var fpsListModel = Qt.createQmlObject('import QtQuick 2.0; ListModel {}', parent, '')
|
var fpsListModel = Qt.createQmlObject('import QtQuick 2.0; ListModel {}', parent, '')
|
||||||
|
|
||||||
// Get the max supported FPS on this system
|
var max_fps = SystemProperties.maximumStreamingFrameRate
|
||||||
var max_fps = prefs.getMaximumStreamingFrameRate();
|
|
||||||
|
|
||||||
// Default entries
|
// Default entries
|
||||||
fpsListModel.append({"text": "30 FPS", "video_fps": "30"})
|
fpsListModel.append({"text": "30 FPS", "video_fps": "30"})
|
||||||
fpsListModel.append({"text": "60 FPS", "video_fps": "60"})
|
fpsListModel.append({"text": "60 FPS", "video_fps": "60"})
|
||||||
|
|
||||||
// Add unsupported FPS values that come before the display max FPS
|
// Add unsupported FPS values that come before the display max FPS
|
||||||
if (prefs.unsupportedFps) {
|
if (StreamingPreferences.unsupportedFps) {
|
||||||
if (max_fps > 90) {
|
if (max_fps > 90) {
|
||||||
fpsListModel.append({"text": "90 FPS (Unsupported)", "video_fps": "90"})
|
fpsListModel.append({"text": "90 FPS (Unsupported)", "video_fps": "90"})
|
||||||
}
|
}
|
||||||
@@ -225,7 +210,7 @@ Flickable {
|
|||||||
// handle wonky displays that report just over 60 Hz.
|
// handle wonky displays that report just over 60 Hz.
|
||||||
if (max_fps > 64) {
|
if (max_fps > 64) {
|
||||||
// Mark any FPS value greater than 120 as unsupported
|
// Mark any FPS value greater than 120 as unsupported
|
||||||
if (prefs.unsupportedFps && max_fps > 120) {
|
if (StreamingPreferences.unsupportedFps && max_fps > 120) {
|
||||||
fpsListModel.append({"text": max_fps+" FPS (Unsupported)", "video_fps": ""+max_fps})
|
fpsListModel.append({"text": max_fps+" FPS (Unsupported)", "video_fps": ""+max_fps})
|
||||||
}
|
}
|
||||||
else if (max_fps > 120) {
|
else if (max_fps > 120) {
|
||||||
@@ -237,7 +222,7 @@ Flickable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add unsupported FPS values that come after the display max FPS
|
// Add unsupported FPS values that come after the display max FPS
|
||||||
if (prefs.unsupportedFps) {
|
if (StreamingPreferences.unsupportedFps) {
|
||||||
if (max_fps < 90) {
|
if (max_fps < 90) {
|
||||||
fpsListModel.append({"text": "90 FPS (Unsupported)", "video_fps": "90"})
|
fpsListModel.append({"text": "90 FPS (Unsupported)", "video_fps": "90"})
|
||||||
}
|
}
|
||||||
@@ -252,7 +237,7 @@ Flickable {
|
|||||||
function reinitialize() {
|
function reinitialize() {
|
||||||
model = createModel()
|
model = createModel()
|
||||||
|
|
||||||
var saved_fps = prefs.fps
|
var saved_fps = StreamingPreferences.fps
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
for (var i = 0; i < model.count; i++) {
|
for (var i = 0; i < model.count; i++) {
|
||||||
var el_fps = parseInt(model.get(i).video_fps);
|
var el_fps = parseInt(model.get(i).video_fps);
|
||||||
@@ -279,11 +264,13 @@ Flickable {
|
|||||||
var selectedFps = parseInt(model.get(currentIndex).video_fps)
|
var selectedFps = parseInt(model.get(currentIndex).video_fps)
|
||||||
|
|
||||||
// Only modify the bitrate if the values actually changed
|
// Only modify the bitrate if the values actually changed
|
||||||
if (prefs.fps !== selectedFps) {
|
if (StreamingPreferences.fps !== selectedFps) {
|
||||||
prefs.fps = selectedFps
|
StreamingPreferences.fps = selectedFps
|
||||||
|
|
||||||
prefs.bitrateKbps = prefs.getDefaultBitrate(prefs.width, prefs.height, prefs.fps);
|
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
|
||||||
slider.value = prefs.bitrateKbps
|
StreamingPreferences.height,
|
||||||
|
StreamingPreferences.fps);
|
||||||
|
slider.value = StreamingPreferences.bitrateKbps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,7 +295,7 @@ Flickable {
|
|||||||
Slider {
|
Slider {
|
||||||
id: slider
|
id: slider
|
||||||
|
|
||||||
value: prefs.bitrateKbps
|
value: StreamingPreferences.bitrateKbps
|
||||||
|
|
||||||
stepSize: 500
|
stepSize: 500
|
||||||
from : 500
|
from : 500
|
||||||
@@ -319,7 +306,7 @@ Flickable {
|
|||||||
|
|
||||||
onValueChanged: {
|
onValueChanged: {
|
||||||
bitrateTitle.text = "Video bitrate: " + (value / 1000.0) + " Mbps"
|
bitrateTitle.text = "Video bitrate: " + (value / 1000.0) + " Mbps"
|
||||||
prefs.bitrateKbps = value
|
StreamingPreferences.bitrateKbps = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +321,7 @@ Flickable {
|
|||||||
AutoResizingComboBox {
|
AutoResizingComboBox {
|
||||||
// ignore setting the index at first, and actually set it when the component is loaded
|
// ignore setting the index at first, and actually set it when the component is loaded
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
var savedWm = prefs.windowMode
|
var savedWm = StreamingPreferences.windowMode
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
for (var i = 0; i < windowModeListModel.count; i++) {
|
for (var i = 0; i < windowModeListModel.count; i++) {
|
||||||
var thisWm = windowModeListModel.get(i).val;
|
var thisWm = windowModeListModel.get(i).val;
|
||||||
@@ -365,7 +352,7 @@ Flickable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onActivated: {
|
onActivated: {
|
||||||
prefs.windowMode = windowModeListModel.get(currentIndex).val
|
StreamingPreferences.windowMode = windowModeListModel.get(currentIndex).val
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
@@ -379,9 +366,9 @@ Flickable {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
text: "V-Sync"
|
text: "V-Sync"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.enableVsync
|
checked: StreamingPreferences.enableVsync
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
prefs.enableVsync = checked
|
StreamingPreferences.enableVsync = checked
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
@@ -395,10 +382,10 @@ Flickable {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
text: "Frame pacing"
|
text: "Frame pacing"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
enabled: prefs.enableVsync
|
enabled: StreamingPreferences.enableVsync
|
||||||
checked: prefs.enableVsync && prefs.framePacing
|
checked: StreamingPreferences.enableVsync && StreamingPreferences.framePacing
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
prefs.framePacing = checked
|
StreamingPreferences.framePacing = checked
|
||||||
}
|
}
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
ToolTip.timeout: 5000
|
ToolTip.timeout: 5000
|
||||||
@@ -431,7 +418,7 @@ Flickable {
|
|||||||
AutoResizingComboBox {
|
AutoResizingComboBox {
|
||||||
// ignore setting the index at first, and actually set it when the component is loaded
|
// ignore setting the index at first, and actually set it when the component is loaded
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
var saved_audio = prefs.audioConfig
|
var saved_audio = StreamingPreferences.audioConfig
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
for (var i = 0; i < audioListModel.count; i++) {
|
for (var i = 0; i < audioListModel.count; i++) {
|
||||||
var el_audio = audioListModel.get(i).val;
|
var el_audio = audioListModel.get(i).val;
|
||||||
@@ -458,7 +445,7 @@ Flickable {
|
|||||||
}
|
}
|
||||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||||
onActivated : {
|
onActivated : {
|
||||||
prefs.audioConfig = audioListModel.get(currentIndex).val
|
StreamingPreferences.audioConfig = audioListModel.get(currentIndex).val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,9 +467,19 @@ Flickable {
|
|||||||
id: startMaximizedCheck
|
id: startMaximizedCheck
|
||||||
text: "Maximize Moonlight window on startup"
|
text: "Maximize Moonlight window on startup"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: !prefs.startWindowed
|
checked: !StreamingPreferences.startWindowed
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
prefs.startWindowed = !checked
|
StreamingPreferences.startWindowed = !checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: connectionWarningsCheck
|
||||||
|
text: "Show connection quality warnings"
|
||||||
|
font.pointSize: 12
|
||||||
|
checked: StreamingPreferences.connectionWarnings
|
||||||
|
onCheckedChanged: {
|
||||||
|
StreamingPreferences.connectionWarnings = checked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -510,9 +507,9 @@ Flickable {
|
|||||||
id: singleControllerCheck
|
id: singleControllerCheck
|
||||||
text: "Force gamepad #1 always present"
|
text: "Force gamepad #1 always present"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: !prefs.multiController
|
checked: !StreamingPreferences.multiController
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
prefs.multiController = !checked
|
StreamingPreferences.multiController = !checked
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
@@ -527,9 +524,9 @@ Flickable {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
text: "Raw mouse input"
|
text: "Raw mouse input"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: !prefs.mouseAcceleration
|
checked: !StreamingPreferences.mouseAcceleration
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
prefs.mouseAcceleration = !checked
|
StreamingPreferences.mouseAcceleration = !checked
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
@@ -555,9 +552,9 @@ Flickable {
|
|||||||
id: optimizeGameSettingsCheck
|
id: optimizeGameSettingsCheck
|
||||||
text: "Optimize game settings"
|
text: "Optimize game settings"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.gameOptimizations
|
checked: StreamingPreferences.gameOptimizations
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
prefs.gameOptimizations = checked
|
StreamingPreferences.gameOptimizations = checked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,9 +562,9 @@ Flickable {
|
|||||||
id: audioPcCheck
|
id: audioPcCheck
|
||||||
text: "Play audio on host PC"
|
text: "Play audio on host PC"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.playAudioOnHost
|
checked: StreamingPreferences.playAudioOnHost
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
prefs.playAudioOnHost = checked
|
StreamingPreferences.playAudioOnHost = checked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -595,7 +592,7 @@ Flickable {
|
|||||||
AutoResizingComboBox {
|
AutoResizingComboBox {
|
||||||
// ignore setting the index at first, and actually set it when the component is loaded
|
// ignore setting the index at first, and actually set it when the component is loaded
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
var saved_vds = prefs.videoDecoderSelection
|
var saved_vds = StreamingPreferences.videoDecoderSelection
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
for (var i = 0; i < decoderListModel.count; i++) {
|
for (var i = 0; i < decoderListModel.count; i++) {
|
||||||
var el_vds = decoderListModel.get(i).val;
|
var el_vds = decoderListModel.get(i).val;
|
||||||
@@ -626,7 +623,7 @@ Flickable {
|
|||||||
}
|
}
|
||||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||||
onActivated : {
|
onActivated : {
|
||||||
prefs.videoDecoderSelection = decoderListModel.get(currentIndex).val
|
StreamingPreferences.videoDecoderSelection = decoderListModel.get(currentIndex).val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,7 +638,7 @@ Flickable {
|
|||||||
AutoResizingComboBox {
|
AutoResizingComboBox {
|
||||||
// ignore setting the index at first, and actually set it when the component is loaded
|
// ignore setting the index at first, and actually set it when the component is loaded
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
var saved_vcc = prefs.videoCodecConfig
|
var saved_vcc = StreamingPreferences.videoCodecConfig
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
for(var i = 0; i < codecListModel.count; i++) {
|
for(var i = 0; i < codecListModel.count; i++) {
|
||||||
var el_vcc = codecListModel.get(i).val;
|
var el_vcc = codecListModel.get(i).val;
|
||||||
@@ -679,7 +676,7 @@ Flickable {
|
|||||||
}
|
}
|
||||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||||
onActivated : {
|
onActivated : {
|
||||||
prefs.videoCodecConfig = codecListModel.get(currentIndex).val
|
StreamingPreferences.videoCodecConfig = codecListModel.get(currentIndex).val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -687,13 +684,17 @@ Flickable {
|
|||||||
id: unlockUnsupportedFps
|
id: unlockUnsupportedFps
|
||||||
text: "Unlock unsupported FPS options"
|
text: "Unlock unsupported FPS options"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.unsupportedFps
|
checked: StreamingPreferences.unsupportedFps
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
prefs.unsupportedFps = checked
|
// This is called on init, so only do the work if we've
|
||||||
|
// actually changed the value.
|
||||||
|
if (StreamingPreferences.unsupportedFps != checked) {
|
||||||
|
StreamingPreferences.unsupportedFps = checked
|
||||||
|
|
||||||
// The selectable FPS values depend on whether
|
// The selectable FPS values depend on whether
|
||||||
// this option is enabled or not
|
// this option is enabled or not
|
||||||
fpsComboBox.reinitialize()
|
fpsComboBox.reinitialize()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,18 +702,22 @@ Flickable {
|
|||||||
id: enableMdns
|
id: enableMdns
|
||||||
text: "Automatically find PCs on the local network (Recommended)"
|
text: "Automatically find PCs on the local network (Recommended)"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.enableMdns
|
checked: StreamingPreferences.enableMdns
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
prefs.enableMdns = checked
|
// This is called on init, so only do the work if we've
|
||||||
|
// actually changed the value.
|
||||||
|
if (StreamingPreferences.enableMdns != checked) {
|
||||||
|
StreamingPreferences.enableMdns = checked
|
||||||
|
|
||||||
// We must save the updated preference to ensure
|
// We must save the updated preference to ensure
|
||||||
// ComputerManager can observe the change internally.
|
// ComputerManager can observe the change internally.
|
||||||
prefs.save()
|
StreamingPreferences.save()
|
||||||
|
|
||||||
// Restart polling so the mDNS change takes effect
|
// Restart polling so the mDNS change takes effect
|
||||||
if (window.pollingActive) {
|
if (window.pollingActive) {
|
||||||
ComputerManager.stopPollingAsync()
|
ComputerManager.stopPollingAsync()
|
||||||
ComputerManager.startPolling()
|
ComputerManager.startPolling()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -721,9 +726,9 @@ Flickable {
|
|||||||
id: quitAppAfter
|
id: quitAppAfter
|
||||||
text: "Quit app after quitting session"
|
text: "Quit app after quitting session"
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: prefs.quitAppAfter
|
checked: StreamingPreferences.quitAppAfter
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
prefs.quitAppAfter = checked
|
StreamingPreferences.quitAppAfter = checked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-60
@@ -1,9 +1,7 @@
|
|||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Dialogs 1.2
|
|
||||||
import QtQuick.Window 2.2
|
import QtQuick.Window 2.2
|
||||||
|
|
||||||
import ComputerManager 1.0
|
|
||||||
import SdlGamepadKeyNavigation 1.0
|
import SdlGamepadKeyNavigation 1.0
|
||||||
import Session 1.0
|
import Session 1.0
|
||||||
|
|
||||||
@@ -13,13 +11,6 @@ Item {
|
|||||||
property string stageText : "Starting " + appName + "..."
|
property string stageText : "Starting " + appName + "..."
|
||||||
property bool quitAfter : false
|
property bool quitAfter : false
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
// The StackView will trigger a visibility change when
|
|
||||||
// we're pushed onto it, causing our onVisibleChanged
|
|
||||||
// routine to run, but only if we start as invisible
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
function stageStarting(stage)
|
function stageStarting(stage)
|
||||||
{
|
{
|
||||||
// Update the spinner text
|
// Update the spinner text
|
||||||
@@ -28,8 +19,8 @@ Item {
|
|||||||
|
|
||||||
function stageFailed(stage, errorCode)
|
function stageFailed(stage, errorCode)
|
||||||
{
|
{
|
||||||
errorDialog.text = "Starting " + stage + " failed: Error " + errorCode
|
// Display the error dialog after Session::exec() returns
|
||||||
errorDialog.open()
|
streamSegueErrorDialog.text = "Starting " + stage + " failed: Error " + errorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectionStarted()
|
function connectionStarted()
|
||||||
@@ -38,6 +29,7 @@ Item {
|
|||||||
// see them briefly when we pop off the StackView
|
// see them briefly when we pop off the StackView
|
||||||
stageSpinner.visible = false
|
stageSpinner.visible = false
|
||||||
stageLabel.visible = false
|
stageLabel.visible = false
|
||||||
|
hintText.visible = false
|
||||||
|
|
||||||
// Hide the window now that streaming has begun
|
// Hide the window now that streaming has begun
|
||||||
window.visible = false
|
window.visible = false
|
||||||
@@ -46,7 +38,7 @@ Item {
|
|||||||
function displayLaunchError(text)
|
function displayLaunchError(text)
|
||||||
{
|
{
|
||||||
// Display the error dialog after Session::exec() returns
|
// Display the error dialog after Session::exec() returns
|
||||||
errorDialog.text = text
|
streamSegueErrorDialog.text = text
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayLaunchWarning(text)
|
function displayLaunchWarning(text)
|
||||||
@@ -54,7 +46,7 @@ Item {
|
|||||||
// This toast appears for 3 seconds, just shorter than how long
|
// This toast appears for 3 seconds, just shorter than how long
|
||||||
// Session will wait for it to be displayed. This gives it time
|
// Session will wait for it to be displayed. This gives it time
|
||||||
// to transition to invisible before continuing.
|
// to transition to invisible before continuing.
|
||||||
var toast = Qt.createQmlObject('import QtQuick.Controls 2.3; ToolTip {}', parent, '')
|
var toast = Qt.createQmlObject('import QtQuick.Controls 2.2; ToolTip {}', parent, '')
|
||||||
toast.text = text
|
toast.text = text
|
||||||
toast.timeout = 3000
|
toast.timeout = 3000
|
||||||
toast.visible = true
|
toast.visible = true
|
||||||
@@ -72,10 +64,14 @@ Item {
|
|||||||
|
|
||||||
function sessionFinished()
|
function sessionFinished()
|
||||||
{
|
{
|
||||||
|
// Enable GUI gamepad usage now
|
||||||
|
SdlGamepadKeyNavigation.enable()
|
||||||
|
|
||||||
if (quitAfter) {
|
if (quitAfter) {
|
||||||
if (errorDialog.text) {
|
if (streamSegueErrorDialog.text) {
|
||||||
// Quit when the error dialog is acknowledged
|
// Quit when the error dialog is acknowledged
|
||||||
errorDialog.open()
|
streamSegueErrorDialog.quitAfter = quitAfter
|
||||||
|
streamSegueErrorDialog.open()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Quit immediately
|
// Quit immediately
|
||||||
@@ -92,48 +88,60 @@ Item {
|
|||||||
// the Qt UI is visible again to prevent losing
|
// the Qt UI is visible again to prevent losing
|
||||||
// focus on the dialog which would impact gamepad
|
// focus on the dialog which would impact gamepad
|
||||||
// users.
|
// users.
|
||||||
if (errorDialog.text) {
|
if (streamSegueErrorDialog.text) {
|
||||||
errorDialog.open()
|
streamSegueErrorDialog.quitAfter = quitAfter
|
||||||
|
streamSegueErrorDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's important that we don't call enable() here
|
StackView.onDeactivating: {
|
||||||
// or it may interfere with the Session instance
|
// Show the toolbar again when popped off the stack
|
||||||
// getting notified of initial connected gamepads.
|
toolBar.visible = true
|
||||||
SdlGamepadKeyNavigation {
|
|
||||||
id: gamepadKeyNav
|
// Enable GUI gamepad usage now
|
||||||
|
SdlGamepadKeyNavigation.enable()
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
StackView.onActivated: {
|
||||||
if (visible) {
|
// Hide the toolbar before we start loading
|
||||||
// Hide the toolbar before we start loading
|
toolBar.visible = false
|
||||||
toolBar.visible = false
|
|
||||||
|
|
||||||
|
// Hook up our signals
|
||||||
|
session.stageStarting.connect(stageStarting)
|
||||||
|
session.stageFailed.connect(stageFailed)
|
||||||
|
session.connectionStarted.connect(connectionStarted)
|
||||||
|
session.displayLaunchError.connect(displayLaunchError)
|
||||||
|
session.displayLaunchWarning.connect(displayLaunchWarning)
|
||||||
|
session.quitStarting.connect(quitStarting)
|
||||||
|
session.sessionFinished.connect(sessionFinished)
|
||||||
|
|
||||||
|
// Kick off the stream
|
||||||
|
streamLoader.active = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: streamLoader
|
||||||
|
active: false
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
onLoaded: {
|
||||||
// Set the hint text. We do this here rather than
|
// Set the hint text. We do this here rather than
|
||||||
// in the hintText control itself to synchronize
|
// in the hintText control itself to synchronize
|
||||||
// with Session.exec() which requires no concurrent
|
// with Session.exec() which requires no concurrent
|
||||||
// gamepad usage.
|
// gamepad usage.
|
||||||
hintText.text = gamepadKeyNav.getConnectedGamepads() > 0 ?
|
hintText.text = SdlGamepadKeyNavigation.getConnectedGamepads() > 0 ?
|
||||||
"Tip: Press Start+Select+L1+R1 to disconnect your session" :
|
"Tip: Press Start+Select+L1+R1 to disconnect your session" :
|
||||||
"Tip: Press Ctrl+Alt+Shift+Q to disconnect your session"
|
"Tip: Press Ctrl+Alt+Shift+Q to disconnect your session"
|
||||||
|
|
||||||
// Hook up our signals
|
// Stop GUI gamepad usage now
|
||||||
session.stageStarting.connect(stageStarting)
|
SdlGamepadKeyNavigation.disable()
|
||||||
session.stageFailed.connect(stageFailed)
|
|
||||||
session.connectionStarted.connect(connectionStarted)
|
|
||||||
session.displayLaunchError.connect(displayLaunchError)
|
|
||||||
session.displayLaunchWarning.connect(displayLaunchWarning)
|
|
||||||
session.quitStarting.connect(quitStarting)
|
|
||||||
session.sessionFinished.connect(sessionFinished)
|
|
||||||
|
|
||||||
// Run the streaming session to completion
|
// Run the streaming session to completion
|
||||||
session.exec(Screen.virtualX, Screen.virtualY)
|
session.exec(Screen.virtualX, Screen.virtualY)
|
||||||
}
|
}
|
||||||
else if (!quitAfter) {
|
|
||||||
// Show the toolbar again when we become hidden
|
sourceComponent: Item {}
|
||||||
toolBar.visible = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -165,25 +173,4 @@ Item {
|
|||||||
|
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
|
||||||
id: errorDialog
|
|
||||||
modality:Qt.WindowModal
|
|
||||||
icon: StandardIcon.Critical
|
|
||||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
|
||||||
|
|
||||||
onAccepted: {
|
|
||||||
if (quitAfter) {
|
|
||||||
Qt.quit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onHelp: {
|
|
||||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting");
|
|
||||||
|
|
||||||
if (quitAfter) {
|
|
||||||
Qt.quit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -22,18 +22,6 @@ QVariant ComputerModel::data(const QModelIndex& index, int role) const
|
|||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index.row() == m_Computers.count()) {
|
|
||||||
// We insert a synthetic item at the end for the Add PC option
|
|
||||||
switch (role) {
|
|
||||||
case NameRole:
|
|
||||||
return "Add PC";
|
|
||||||
case AddPcRole:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_ASSERT(index.row() < m_Computers.count());
|
Q_ASSERT(index.row() < m_Computers.count());
|
||||||
|
|
||||||
NvComputer* computer = m_Computers[index.row()];
|
NvComputer* computer = m_Computers[index.row()];
|
||||||
@@ -50,8 +38,6 @@ QVariant ComputerModel::data(const QModelIndex& index, int role) const
|
|||||||
return computer->currentGameId != 0;
|
return computer->currentGameId != 0;
|
||||||
case WakeableRole:
|
case WakeableRole:
|
||||||
return !computer->macAddress.isEmpty();
|
return !computer->macAddress.isEmpty();
|
||||||
case AddPcRole:
|
|
||||||
return false;
|
|
||||||
case StatusUnknownRole:
|
case StatusUnknownRole:
|
||||||
return computer->state == NvComputer::CS_UNKNOWN;
|
return computer->state == NvComputer::CS_UNKNOWN;
|
||||||
default:
|
default:
|
||||||
@@ -67,8 +53,7 @@ int ComputerModel::rowCount(const QModelIndex& parent) const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add PC placeholder counts as 1
|
return m_Computers.count();
|
||||||
return m_Computers.count() + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> ComputerModel::roleNames() const
|
QHash<int, QByteArray> ComputerModel::roleNames() const
|
||||||
@@ -79,7 +64,6 @@ QHash<int, QByteArray> ComputerModel::roleNames() const
|
|||||||
names[OnlineRole] = "online";
|
names[OnlineRole] = "online";
|
||||||
names[PairedRole] = "paired";
|
names[PairedRole] = "paired";
|
||||||
names[BusyRole] = "busy";
|
names[BusyRole] = "busy";
|
||||||
names[AddPcRole] = "addPc";
|
|
||||||
names[WakeableRole] = "wakeable";
|
names[WakeableRole] = "wakeable";
|
||||||
names[StatusUnknownRole] = "statusUnknown";
|
names[StatusUnknownRole] = "statusUnknown";
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ class ComputerModel : public QAbstractListModel
|
|||||||
PairedRole,
|
PairedRole,
|
||||||
BusyRole,
|
BusyRole,
|
||||||
WakeableRole,
|
WakeableRole,
|
||||||
StatusUnknownRole,
|
StatusUnknownRole
|
||||||
AddPcRole
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
+127
-96
@@ -1,12 +1,13 @@
|
|||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Dialogs 1.2
|
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import QtQuick.Window 2.2
|
import QtQuick.Window 2.2
|
||||||
|
|
||||||
import ComputerManager 1.0
|
import ComputerManager 1.0
|
||||||
import AutoUpdateChecker 1.0
|
import AutoUpdateChecker 1.0
|
||||||
import StreamingPreferences 1.0
|
import StreamingPreferences 1.0
|
||||||
|
import SystemProperties 1.0
|
||||||
|
import SdlGamepadKeyNavigation 1.0
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
property bool pollingActive: false
|
property bool pollingActive: false
|
||||||
@@ -16,10 +17,10 @@ ApplicationWindow {
|
|||||||
width: 1280
|
width: 1280
|
||||||
height: 600
|
height: 600
|
||||||
|
|
||||||
visibility: prefs.startWindowed ? "Windowed" : "Maximized"
|
visibility: StreamingPreferences.startWindowed ? "Windowed" : "Maximized"
|
||||||
|
|
||||||
StreamingPreferences {
|
Component.onCompleted: {
|
||||||
id: prefs
|
SdlGamepadKeyNavigation.enable()
|
||||||
}
|
}
|
||||||
|
|
||||||
StackView {
|
StackView {
|
||||||
@@ -137,19 +138,20 @@ ApplicationWindow {
|
|||||||
// onAfterRendering call and potentially reenter this code.
|
// onAfterRendering call and potentially reenter this code.
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
if (prefs.isRunningWayland()) {
|
if (SystemProperties.isWow64) {
|
||||||
waylandDialog.open()
|
|
||||||
}
|
|
||||||
else if (prefs.isWow64()) {
|
|
||||||
wow64Dialog.open()
|
wow64Dialog.open()
|
||||||
}
|
}
|
||||||
else if (!prefs.hasAnyHardwareAcceleration()) {
|
else if (!SystemProperties.hasHardwareAcceleration) {
|
||||||
noHwDecoderDialog.open()
|
if (SystemProperties.isRunningXWayland) {
|
||||||
|
xWaylandDialog.open()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
noHwDecoderDialog.open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var unmappedGamepads = prefs.getUnmappedGamepads()
|
if (SystemProperties.unmappedGamepads) {
|
||||||
if (unmappedGamepads) {
|
unmappedGamepadDialog.unmappedGamepads = SystemProperties.unmappedGamepads
|
||||||
unmappedGamepadDialog.unmappedGamepads = unmappedGamepads
|
|
||||||
unmappedGamepadDialog.open()
|
unmappedGamepadDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,28 +175,21 @@ ApplicationWindow {
|
|||||||
|
|
||||||
header: ToolBar {
|
header: ToolBar {
|
||||||
id: toolBar
|
id: toolBar
|
||||||
|
height: 60
|
||||||
anchors.topMargin: 5
|
anchors.topMargin: 5
|
||||||
anchors.bottomMargin: 5
|
anchors.bottomMargin: 5
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: 20
|
spacing: 20
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.rightMargin: 10
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
NavigableToolButton {
|
NavigableToolButton {
|
||||||
// Only make the button visible if the user has navigated somewhere.
|
// Only make the button visible if the user has navigated somewhere.
|
||||||
visible: stackView.depth > 1
|
visible: stackView.depth > 1
|
||||||
|
|
||||||
// FIXME: We're using an Image here rather than icon.source because
|
iconSource: "qrc:/res/arrow_left.svg"
|
||||||
// icons don't work on Qt 5.9 LTS.
|
|
||||||
Image {
|
|
||||||
source: "qrc:/res/arrow_left.svg"
|
|
||||||
anchors.centerIn: parent
|
|
||||||
sourceSize {
|
|
||||||
// The icon should be square so use the height as the width too
|
|
||||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
|
||||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: stackView.pop()
|
onClicked: stackView.pop()
|
||||||
|
|
||||||
@@ -213,19 +208,38 @@ ApplicationWindow {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NavigableToolButton {
|
||||||
|
id: addPcButton
|
||||||
|
visible: stackView.currentItem.objectName === "Computers"
|
||||||
|
|
||||||
|
iconSource: "qrc:/res/ic_add_to_queue_white_48px.svg"
|
||||||
|
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 3000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: "Add PC manually" + (newPcShortcut.nativeText ? (" ("+newPcShortcut.nativeText+")") : "")
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
id: newPcShortcut
|
||||||
|
sequence: StandardKey.New
|
||||||
|
onActivated: addPcButton.clicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
addPcDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onDownPressed: {
|
||||||
|
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NavigableToolButton {
|
NavigableToolButton {
|
||||||
property string browserUrl: ""
|
property string browserUrl: ""
|
||||||
|
|
||||||
id: updateButton
|
id: updateButton
|
||||||
|
|
||||||
Image {
|
iconSource: "qrc:/res/update.svg"
|
||||||
source: "qrc:/res/update.svg"
|
|
||||||
anchors.centerIn: parent
|
|
||||||
sourceSize {
|
|
||||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
|
||||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
ToolTip.timeout: 3000
|
ToolTip.timeout: 3000
|
||||||
@@ -256,15 +270,9 @@ ApplicationWindow {
|
|||||||
|
|
||||||
NavigableToolButton {
|
NavigableToolButton {
|
||||||
id: helpButton
|
id: helpButton
|
||||||
|
visible: SystemProperties.hasBrowser
|
||||||
|
|
||||||
Image {
|
iconSource: "qrc:/res/question_mark.svg"
|
||||||
source: "qrc:/res/question_mark.svg"
|
|
||||||
anchors.centerIn: parent
|
|
||||||
sourceSize {
|
|
||||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
|
||||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
ToolTip.timeout: 3000
|
ToolTip.timeout: 3000
|
||||||
@@ -294,14 +302,7 @@ ApplicationWindow {
|
|||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.text: "Gamepad Mapper"
|
ToolTip.text: "Gamepad Mapper"
|
||||||
|
|
||||||
Image {
|
iconSource: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
||||||
source: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
|
||||||
anchors.centerIn: parent
|
|
||||||
sourceSize {
|
|
||||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
|
||||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping")
|
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping")
|
||||||
|
|
||||||
@@ -313,14 +314,7 @@ ApplicationWindow {
|
|||||||
NavigableToolButton {
|
NavigableToolButton {
|
||||||
id: settingsButton
|
id: settingsButton
|
||||||
|
|
||||||
Image {
|
iconSource: "qrc:/res/settings.svg"
|
||||||
source: "qrc:/res/settings.svg"
|
|
||||||
anchors.centerIn: parent
|
|
||||||
sourceSize {
|
|
||||||
width: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
|
||||||
height: toolBar.height - toolBar.anchors.bottomMargin - toolBar.anchors.topMargin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: navigateTo("qrc:/gui/SettingsView.qml", "Settings")
|
onClicked: navigateTo("qrc:/gui/SettingsView.qml", "Settings")
|
||||||
|
|
||||||
@@ -342,67 +336,104 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
ErrorMessageDialog {
|
||||||
id: noHwDecoderDialog
|
id: noHwDecoderDialog
|
||||||
modality:Qt.WindowModal
|
|
||||||
icon: StandardIcon.Warning
|
|
||||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
|
||||||
text: "No functioning hardware accelerated H.264 video decoder was detected by Moonlight. " +
|
text: "No functioning hardware accelerated H.264 video decoder was detected by Moonlight. " +
|
||||||
"Your streaming performance may be severely degraded in this configuration. " +
|
"Your streaming performance may be severely degraded in this configuration."
|
||||||
"Click the Help button for more information on solving this problem."
|
helpText: "Click the Help button for more information on solving this problem."
|
||||||
onHelp: {
|
helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems"
|
||||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
ErrorMessageDialog {
|
||||||
id: waylandDialog
|
id: xWaylandDialog
|
||||||
modality:Qt.WindowModal
|
text: "Hardware acceleration doesn't work on XWayland. Continuing on XWayland may result in poor streaming performance. " +
|
||||||
icon: StandardIcon.Warning
|
"Try running with QT_QPA_PLATFORM=wayland or switch to X11."
|
||||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
helpText: "Click the Help button for more information."
|
||||||
text: "Moonlight does not support hardware acceleration on Wayland. Continuing on Wayland may result in poor streaming performance. " +
|
helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems"
|
||||||
"Please switch to an X session for optimal performance."
|
|
||||||
onHelp: {
|
|
||||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
NavigableMessageDialog {
|
||||||
id: wow64Dialog
|
id: wow64Dialog
|
||||||
modality:Qt.WindowModal
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||||
icon: StandardIcon.Warning
|
|
||||||
standardButtons: StandardButton.Ok | StandardButton.Cancel
|
|
||||||
text: "This PC is running a 64-bit version of Windows. Please download the x64 version of Moonlight for the best streaming performance."
|
text: "This PC is running a 64-bit version of Windows. Please download the x64 version of Moonlight for the best streaming performance."
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-qt/releases");
|
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-qt/releases");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
ErrorMessageDialog {
|
||||||
id: unmappedGamepadDialog
|
id: unmappedGamepadDialog
|
||||||
property string unmappedGamepads : ""
|
property string unmappedGamepads : ""
|
||||||
modality:Qt.WindowModal
|
text: "Moonlight detected gamepads without a mapping:\n" + unmappedGamepads
|
||||||
icon: StandardIcon.Warning
|
helpTextSeparator: "\n\n"
|
||||||
standardButtons: StandardButton.Ok | StandardButton.Help
|
helpText: "Click the Help button for information on how to map your gamepads."
|
||||||
text: "Moonlight detected gamepads without a proper mapping. " +
|
helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Gamepad-Mapping"
|
||||||
"The following gamepads will not function until this is resolved: " + unmappedGamepads + "\n\n" +
|
|
||||||
"Click the Help button for information on how to map your gamepads."
|
|
||||||
onHelp: {
|
|
||||||
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-docs/wiki/Gamepad-Mapping");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This dialog appears when quitting via keyboard or gamepad button
|
// This dialog appears when quitting via keyboard or gamepad button
|
||||||
MessageDialog {
|
NavigableMessageDialog {
|
||||||
id: quitConfirmationDialog
|
id: quitConfirmationDialog
|
||||||
modality:Qt.WindowModal
|
standardButtons: Dialog.Yes | Dialog.No
|
||||||
icon: StandardIcon.Warning
|
|
||||||
standardButtons: StandardButton.Yes | StandardButton.No
|
|
||||||
text: "Are you sure you want to quit?"
|
text: "Are you sure you want to quit?"
|
||||||
|
|
||||||
onYes: Qt.quit()
|
|
||||||
|
|
||||||
// For keyboard/gamepad navigation
|
// For keyboard/gamepad navigation
|
||||||
onAccepted: Qt.quit()
|
onAccepted: Qt.quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HACK: This belongs in StreamSegue but keeping a dialog around after the parent
|
||||||
|
// dies can trigger bugs in Qt 5.12 that cause the app to crash. For now, we will
|
||||||
|
// host this dialog in a QML component that is never destroyed.
|
||||||
|
//
|
||||||
|
// To repro: Start a stream, cut the network connection to trigger the "Connection
|
||||||
|
// terminated" dialog, wait until the app grid times out back to the PC grid, then
|
||||||
|
// try to dismiss the dialog.
|
||||||
|
ErrorMessageDialog {
|
||||||
|
id: streamSegueErrorDialog
|
||||||
|
|
||||||
|
property bool quitAfter: false
|
||||||
|
|
||||||
|
onClosed: {
|
||||||
|
if (quitAfter) {
|
||||||
|
Qt.quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamSegue assumes its dialog will be re-created each time we
|
||||||
|
// start streaming, so fake it by wiping out the text each time.
|
||||||
|
text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigableDialog {
|
||||||
|
id: addPcDialog
|
||||||
|
property string label: "Enter the IP address of your GameStream PC:"
|
||||||
|
|
||||||
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||||
|
|
||||||
|
onOpened: {
|
||||||
|
// Force keyboard focus on the textbox so keyboard navigation works
|
||||||
|
editText.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
if (editText.text) {
|
||||||
|
ComputerManager.addNewHost(editText.text, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Label {
|
||||||
|
text: addPcDialog.label
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: editText
|
||||||
|
Layout.fillWidth: true
|
||||||
|
focus: true
|
||||||
|
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
addPcDialog.accept()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -91,6 +91,12 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
|
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
|
case SDL_QUIT:
|
||||||
|
// SDL may send us a quit event since we initialize
|
||||||
|
// the video subsystem on startup. If we get one,
|
||||||
|
// forward it on for Qt to take care of.
|
||||||
|
QCoreApplication::instance()->quit();
|
||||||
|
break;
|
||||||
case SDL_CONTROLLERBUTTONDOWN:
|
case SDL_CONTROLLERBUTTONDOWN:
|
||||||
case SDL_CONTROLLERBUTTONUP:
|
case SDL_CONTROLLERBUTTONUP:
|
||||||
{
|
{
|
||||||
@@ -247,17 +253,7 @@ void SdlGamepadKeyNavigation::setSettingsMode(bool settingsMode)
|
|||||||
|
|
||||||
int SdlGamepadKeyNavigation::getConnectedGamepads()
|
int SdlGamepadKeyNavigation::getConnectedGamepads()
|
||||||
{
|
{
|
||||||
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) {
|
Q_ASSERT(m_Enabled);
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Applying mappings is necessary to ensure gamepad without
|
|
||||||
// a built-in mapping are properly counted.
|
|
||||||
MappingManager mappingManager;
|
|
||||||
mappingManager.applyMappings();
|
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < SDL_NumJoysticks(); i++) {
|
for (int i = 0; i < SDL_NumJoysticks(); i++) {
|
||||||
@@ -266,6 +262,5 @@ int SdlGamepadKeyNavigation::getConnectedGamepads()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
+92
-35
@@ -6,6 +6,9 @@
|
|||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
#include <QNetworkProxyFactory>
|
#include <QNetworkProxyFactory>
|
||||||
|
#include <QPalette>
|
||||||
|
#include <QFont>
|
||||||
|
#include <QCursor>
|
||||||
|
|
||||||
// Don't let SDL hook our main function, since Qt is already
|
// Don't let SDL hook our main function, since Qt is already
|
||||||
// doing the same thing. This needs to be before any headers
|
// doing the same thing. This needs to be before any headers
|
||||||
@@ -28,6 +31,7 @@
|
|||||||
#include "gui/computermodel.h"
|
#include "gui/computermodel.h"
|
||||||
#include "gui/appmodel.h"
|
#include "gui/appmodel.h"
|
||||||
#include "backend/autoupdatechecker.h"
|
#include "backend/autoupdatechecker.h"
|
||||||
|
#include "backend/systemproperties.h"
|
||||||
#include "streaming/session.h"
|
#include "streaming/session.h"
|
||||||
#include "settings/streamingpreferences.h"
|
#include "settings/streamingpreferences.h"
|
||||||
#include "gui/sdlgamepadkeynavigation.h"
|
#include "gui/sdlgamepadkeynavigation.h"
|
||||||
@@ -136,14 +140,6 @@ void qtLogToDiskHandler(QtMsgType type, const QMessageLogContext&, const QString
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: Avoid printing this internal Qt warning which seems to print every time we
|
|
||||||
// start our event loop for the QNetworkReply. I have found this warning in many
|
|
||||||
// unrelated bug reports, but none actually tackle this warning itself. It seems to be
|
|
||||||
// new in Qt 5.12.
|
|
||||||
if (msg.startsWith("QNetworkReplyHttpImplPrivate::_q_startOperation was called more than once")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QTime logTime = QTime::fromMSecsSinceStartOfDay(s_LoggerTime.elapsed());
|
QTime logTime = QTime::fromMSecsSinceStartOfDay(s_LoggerTime.elapsed());
|
||||||
QString txt = QString("%1 - Qt %2: %3\n").arg(logTime.toString()).arg(typeTxt).arg(msg);
|
QString txt = QString("%1 - Qt %2: %3\n").arg(logTime.toString()).arg(typeTxt).arg(msg);
|
||||||
|
|
||||||
@@ -231,6 +227,8 @@ LONG WINAPI UnhandledExceptionHandler(struct _EXCEPTION_POINTERS *ExceptionInfo)
|
|||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
SDL_SetMainReady();
|
||||||
|
|
||||||
// Set the app version for the QCommandLineParser's showVersion() command
|
// Set the app version for the QCommandLineParser's showVersion() command
|
||||||
QCoreApplication::setApplicationVersion(VERSION_STR);
|
QCoreApplication::setApplicationVersion(VERSION_STR);
|
||||||
|
|
||||||
@@ -311,19 +309,75 @@ int main(int argc, char *argv[])
|
|||||||
// Register custom metatypes for use in signals
|
// Register custom metatypes for use in signals
|
||||||
qRegisterMetaType<NvApp>("NvApp");
|
qRegisterMetaType<NvApp>("NvApp");
|
||||||
|
|
||||||
// Disable desktop settings awareness due to issues with QGnomePlatform
|
// Allow the display to sleep by default. We will manually use SDL_DisableScreenSaver()
|
||||||
// breaking our dialog rendering on Linux.
|
// and SDL_EnableScreenSaver() when appropriate. This hint must be set before
|
||||||
QGuiApplication::setDesktopSettingsAware(false);
|
// initializing the SDL video subsystem to have any effect.
|
||||||
|
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
||||||
|
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_TIMER) != 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL_InitSubSystem(SDL_INIT_TIMER) failed: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef STEAM_LINK
|
||||||
|
// Steam Link requires that we initialize video before creating our
|
||||||
|
// QGuiApplication in order to configure the framebuffer correctly.
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Use atexit() to ensure SDL_Quit() is called. This avoids
|
||||||
|
// racing with object destruction where SDL may be used.
|
||||||
|
atexit(SDL_Quit);
|
||||||
|
|
||||||
|
// Avoid the default behavior of changing the timer resolution to 1 ms.
|
||||||
|
// We don't want this all the time that Moonlight is open. We will set
|
||||||
|
// it manually when we start streaming.
|
||||||
|
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "0");
|
||||||
|
|
||||||
|
// Disable minimize on focus loss by default. Users seem to want this off by default.
|
||||||
|
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
|
||||||
|
|
||||||
QGuiApplication app(argc, argv);
|
QGuiApplication app(argc, argv);
|
||||||
|
|
||||||
|
// After the QGuiApplication is created, the platform stuff will be initialized
|
||||||
|
// and we can set the SDL video driver to match Qt.
|
||||||
|
if (qgetenv("XDG_SESSION_TYPE") == "wayland" && QGuiApplication::platformName() == "xcb") {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Detected XWayland. This will probably break hardware decoding! Try running with QT_QPA_PLATFORM=wayland or switch to X11.");
|
||||||
|
}
|
||||||
|
else if (QGuiApplication::platformName().startsWith("wayland")) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Detected Wayland. Performance may be worse than X11.");
|
||||||
|
qputenv("SDL_VIDEODRIVER", "wayland");
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef STEAM_LINK
|
||||||
|
// Qt 5.9 from the Steam Link SDK is not able to load any fonts
|
||||||
|
// since the Steam Link doesn't include any of the ones it looks
|
||||||
|
// for. We know it has NotoSans so we will explicitly ask for that.
|
||||||
|
if (app.font().family().isEmpty()) {
|
||||||
|
qWarning() << "SL HACK: No default font - using NotoSans";
|
||||||
|
|
||||||
|
QFont fon("NotoSans");
|
||||||
|
app.setFont(fon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the mouse to the bottom right so it's invisible when using
|
||||||
|
// gamepad-only navigation.
|
||||||
|
QCursor().setPos(0xFFFF, 0xFFFF);
|
||||||
|
#endif
|
||||||
|
|
||||||
app.setWindowIcon(QIcon(":/res/moonlight.svg"));
|
app.setWindowIcon(QIcon(":/res/moonlight.svg"));
|
||||||
|
|
||||||
// Register our C++ types for QML
|
// Register our C++ types for QML
|
||||||
qmlRegisterType<ComputerModel>("ComputerModel", 1, 0, "ComputerModel");
|
qmlRegisterType<ComputerModel>("ComputerModel", 1, 0, "ComputerModel");
|
||||||
qmlRegisterType<AppModel>("AppModel", 1, 0, "AppModel");
|
qmlRegisterType<AppModel>("AppModel", 1, 0, "AppModel");
|
||||||
qmlRegisterType<StreamingPreferences>("StreamingPreferences", 1, 0, "StreamingPreferences");
|
|
||||||
qmlRegisterType<SdlGamepadKeyNavigation>("SdlGamepadKeyNavigation", 1, 0, "SdlGamepadKeyNavigation");
|
|
||||||
qmlRegisterUncreatableType<Session>("Session", 1, 0, "Session", "Session cannot be created from QML");
|
qmlRegisterUncreatableType<Session>("Session", 1, 0, "Session", "Session cannot be created from QML");
|
||||||
qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0,
|
qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0,
|
||||||
"ComputerManager",
|
"ComputerManager",
|
||||||
@@ -335,11 +389,33 @@ int main(int argc, char *argv[])
|
|||||||
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
||||||
return new AutoUpdateChecker();
|
return new AutoUpdateChecker();
|
||||||
});
|
});
|
||||||
|
qmlRegisterSingletonType<SystemProperties>("SystemProperties", 1, 0,
|
||||||
|
"SystemProperties",
|
||||||
|
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
||||||
|
return new SystemProperties();
|
||||||
|
});
|
||||||
|
qmlRegisterSingletonType<SdlGamepadKeyNavigation>("SdlGamepadKeyNavigation", 1, 0,
|
||||||
|
"SdlGamepadKeyNavigation",
|
||||||
|
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
||||||
|
return new SdlGamepadKeyNavigation();
|
||||||
|
});
|
||||||
|
qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0,
|
||||||
|
"StreamingPreferences",
|
||||||
|
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
||||||
|
return new StreamingPreferences();
|
||||||
|
});
|
||||||
|
|
||||||
|
#ifndef Q_OS_WINRT
|
||||||
// Use the dense material dark theme by default
|
// Use the dense material dark theme by default
|
||||||
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_STYLE")) {
|
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_STYLE")) {
|
||||||
qputenv("QT_QUICK_CONTROLS_STYLE", "Material");
|
qputenv("QT_QUICK_CONTROLS_STYLE", "Material");
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
// Use universal dark on WinRT
|
||||||
|
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_STYLE")) {
|
||||||
|
qputenv("QT_QUICK_CONTROLS_STYLE", "Universal");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MATERIAL_THEME")) {
|
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MATERIAL_THEME")) {
|
||||||
qputenv("QT_QUICK_CONTROLS_MATERIAL_THEME", "Dark");
|
qputenv("QT_QUICK_CONTROLS_MATERIAL_THEME", "Dark");
|
||||||
}
|
}
|
||||||
@@ -352,9 +428,6 @@ int main(int argc, char *argv[])
|
|||||||
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_UNIVERSAL_THEME")) {
|
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_UNIVERSAL_THEME")) {
|
||||||
qputenv("QT_QUICK_CONTROLS_UNIVERSAL_THEME", "Dark");
|
qputenv("QT_QUICK_CONTROLS_UNIVERSAL_THEME", "Dark");
|
||||||
}
|
}
|
||||||
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_1_STYLE")) {
|
|
||||||
qputenv("QT_QUICK_CONTROLS_1_STYLE", "Base");
|
|
||||||
}
|
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
QString initialView;
|
QString initialView;
|
||||||
@@ -362,11 +435,11 @@ int main(int argc, char *argv[])
|
|||||||
GlobalCommandLineParser parser;
|
GlobalCommandLineParser parser;
|
||||||
switch (parser.parse(app.arguments())) {
|
switch (parser.parse(app.arguments())) {
|
||||||
case GlobalCommandLineParser::NormalStartRequested:
|
case GlobalCommandLineParser::NormalStartRequested:
|
||||||
initialView = "PcView.qml";
|
initialView = "qrc:/gui/PcView.qml";
|
||||||
break;
|
break;
|
||||||
case GlobalCommandLineParser::StreamRequested:
|
case GlobalCommandLineParser::StreamRequested:
|
||||||
{
|
{
|
||||||
initialView = "CliStartStreamSegue.qml";
|
initialView = "qrc:/gui/CliStartStreamSegue.qml";
|
||||||
StreamingPreferences* preferences = new StreamingPreferences(&app);
|
StreamingPreferences* preferences = new StreamingPreferences(&app);
|
||||||
StreamCommandLineParser streamParser;
|
StreamCommandLineParser streamParser;
|
||||||
streamParser.parse(app.arguments(), preferences);
|
streamParser.parse(app.arguments(), preferences);
|
||||||
@@ -378,7 +451,7 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
case GlobalCommandLineParser::QuitRequested:
|
case GlobalCommandLineParser::QuitRequested:
|
||||||
{
|
{
|
||||||
initialView = "CliQuitStreamSegue.qml";
|
initialView = "qrc:/gui/CliQuitStreamSegue.qml";
|
||||||
QuitCommandLineParser quitParser;
|
QuitCommandLineParser quitParser;
|
||||||
quitParser.parse(app.arguments());
|
quitParser.parse(app.arguments());
|
||||||
auto launcher = new CliQuitStream::Launcher(quitParser.getHost(), &app);
|
auto launcher = new CliQuitStream::Launcher(quitParser.getHost(), &app);
|
||||||
@@ -394,22 +467,6 @@ int main(int argc, char *argv[])
|
|||||||
if (engine.rootObjects().isEmpty())
|
if (engine.rootObjects().isEmpty())
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
SDL_SetMainReady();
|
|
||||||
if (SDL_Init(SDL_INIT_TIMER) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_Init() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use atexit() to ensure SDL_Quit() is called. This avoids
|
|
||||||
// racing with object destruction where SDL may be used.
|
|
||||||
atexit(SDL_Quit);
|
|
||||||
|
|
||||||
// Avoid the default behavior of changing the timer resolution to 1 ms.
|
|
||||||
// We don't want this all the time that Moonlight is open. We will set
|
|
||||||
// it manually when we start streaming.
|
|
||||||
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "0");
|
|
||||||
|
|
||||||
int err = app.exec();
|
int err = app.exec();
|
||||||
|
|
||||||
// Give worker tasks time to properly exit. Fixes PendingQuitTask
|
// Give worker tasks time to properly exit. Fixes PendingQuitTask
|
||||||
|
|||||||
+14
-12
@@ -21,6 +21,13 @@ QString Path::getBoxArtCacheDir()
|
|||||||
return s_BoxArtCacheDir;
|
return s_BoxArtCacheDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray Path::readDataFile(QString fileName)
|
||||||
|
{
|
||||||
|
QFile dataFile(getDataFilePath(fileName));
|
||||||
|
dataFile.open(QIODevice::ReadOnly);
|
||||||
|
return dataFile.readAll();
|
||||||
|
}
|
||||||
|
|
||||||
QString Path::getDataFilePath(QString fileName)
|
QString Path::getDataFilePath(QString fileName)
|
||||||
{
|
{
|
||||||
QString candidatePath;
|
QString candidatePath;
|
||||||
@@ -28,33 +35,28 @@ QString Path::getDataFilePath(QString fileName)
|
|||||||
// Check the current directory first
|
// Check the current directory first
|
||||||
candidatePath = QDir(QDir::currentPath()).absoluteFilePath(fileName);
|
candidatePath = QDir(QDir::currentPath()).absoluteFilePath(fileName);
|
||||||
if (QFile::exists(candidatePath)) {
|
if (QFile::exists(candidatePath)) {
|
||||||
|
qInfo() << "Found" << fileName << "at" << candidatePath;
|
||||||
return candidatePath;
|
return candidatePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now check the data directories (for Linux, in particular)
|
// Now check the data directories (for Linux, in particular)
|
||||||
candidatePath = QStandardPaths::locate(QStandardPaths::DataLocation, fileName);
|
candidatePath = QStandardPaths::locate(QStandardPaths::DataLocation, fileName);
|
||||||
if (!candidatePath.isEmpty() && QFile::exists(candidatePath)) {
|
if (!candidatePath.isEmpty() && QFile::exists(candidatePath)) {
|
||||||
|
qInfo() << "Found" << fileName << "at" << candidatePath;
|
||||||
return candidatePath;
|
return candidatePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now try the directory of our app installation (for Windows, if current dir doesn't find it)
|
// Now try the directory of our app installation (for Windows, if current dir doesn't find it)
|
||||||
candidatePath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(fileName);
|
candidatePath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(fileName);
|
||||||
if (QFile::exists(candidatePath)) {
|
if (QFile::exists(candidatePath)) {
|
||||||
|
qInfo() << "Found" << fileName << "at" << candidatePath;
|
||||||
return candidatePath;
|
return candidatePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now try the Resources folder in our app bundle for macOS
|
// Return the QRC embedded copy
|
||||||
QDir dir = QDir(QCoreApplication::applicationDirPath());
|
candidatePath = ":/data/" + fileName;
|
||||||
dir.cdUp();
|
qInfo() << "Found" << fileName << "at" << candidatePath;
|
||||||
dir.cd("Resources");
|
return QString(candidatePath);
|
||||||
dir.filePath(fileName);
|
|
||||||
candidatePath = dir.absoluteFilePath(fileName);
|
|
||||||
if (QFile::exists(candidatePath)) {
|
|
||||||
return candidatePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Couldn't find it
|
|
||||||
return QString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Path::initialize(bool portable)
|
void Path::initialize(bool portable)
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ public:
|
|||||||
|
|
||||||
static QString getBoxArtCacheDir();
|
static QString getBoxArtCacheDir();
|
||||||
|
|
||||||
|
static QByteArray readDataFile(QString fileName);
|
||||||
|
|
||||||
|
// Only safe to use directly for Qt classes
|
||||||
static QString getDataFilePath(QString fileName);
|
static QString getDataFilePath(QString fileName);
|
||||||
|
|
||||||
static void initialize(bool portable);
|
static void initialize(bool portable);
|
||||||
|
|||||||
@@ -14,5 +14,9 @@
|
|||||||
<file>gui/CliStartStreamSegue.qml</file>
|
<file>gui/CliStartStreamSegue.qml</file>
|
||||||
<file>gui/AutoResizingComboBox.qml</file>
|
<file>gui/AutoResizingComboBox.qml</file>
|
||||||
<file>gui/NavigableMenu.qml</file>
|
<file>gui/NavigableMenu.qml</file>
|
||||||
|
<file>gui/ErrorMessageDialog.qml</file>
|
||||||
|
<file>gui/NavigableMessageDialog.qml</file>
|
||||||
|
<file>gui/NavigableDialog.qml</file>
|
||||||
|
<file>gui/CenteredGridView.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FFFFFF"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 323 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FFFFFF"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"/></svg>
|
||||||
|
After Width: | Height: | Size: 397 B |
@@ -15,5 +15,11 @@
|
|||||||
<file>res/question_mark.svg</file>
|
<file>res/question_mark.svg</file>
|
||||||
<file>res/moonlight.svg</file>
|
<file>res/moonlight.svg</file>
|
||||||
<file>res/update.svg</file>
|
<file>res/update.svg</file>
|
||||||
|
<file>res/baseline-help_outline-24px.svg</file>
|
||||||
|
<file>res/baseline-error_outline-24px.svg</file>
|
||||||
|
</qresource>
|
||||||
|
<qresource prefix="/data">
|
||||||
|
<file alias="gamecontrollerdb.txt">SDL_GameControllerDB/gamecontrollerdb.txt</file>
|
||||||
|
<file alias="ModeSeven.ttf">ModeSeven.ttf</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
@@ -54,15 +54,10 @@ void MappingManager::save()
|
|||||||
|
|
||||||
void MappingManager::applyMappings()
|
void MappingManager::applyMappings()
|
||||||
{
|
{
|
||||||
QString mappingFile = Path::getDataFilePath("gamecontrollerdb.txt");
|
QByteArray mappingData = Path::readDataFile("gamecontrollerdb.txt");
|
||||||
if (!mappingFile.isEmpty()) {
|
if (!mappingData.isEmpty()) {
|
||||||
std::string mappingFileNative = QDir::toNativeSeparators(mappingFile).toStdString();
|
int newMappings = SDL_GameControllerAddMappingsFromRW(
|
||||||
|
SDL_RWFromConstMem(mappingData.constData(), mappingData.size()), 1);
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Loading gamepad mappings from: %s",
|
|
||||||
mappingFileNative.c_str());
|
|
||||||
|
|
||||||
int newMappings = SDL_GameControllerAddMappingsFromFile(mappingFileNative.c_str());
|
|
||||||
if (newMappings < 0) {
|
if (newMappings < 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Error loading gamepad mappings: %s",
|
"Error loading gamepad mappings: %s",
|
||||||
@@ -75,8 +70,8 @@ void MappingManager::applyMappings()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"No gamepad mapping file found");
|
"Unable to load gamepad mapping file");
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<SdlGamepadMapping> mappings = m_Mappings.values();
|
QList<SdlGamepadMapping> mappings = m_Mappings.values();
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#include "streamingpreferences.h"
|
#include "streamingpreferences.h"
|
||||||
#include "streaming/session.h"
|
|
||||||
#include "streaming/streamutils.h"
|
|
||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
@@ -24,6 +22,7 @@
|
|||||||
#define SER_MOUSEACCELERATION "mouseacceleration"
|
#define SER_MOUSEACCELERATION "mouseacceleration"
|
||||||
#define SER_STARTWINDOWED "startwindowed"
|
#define SER_STARTWINDOWED "startwindowed"
|
||||||
#define SER_FRAMEPACING "framepacing"
|
#define SER_FRAMEPACING "framepacing"
|
||||||
|
#define SER_CONNWARNINGS "connwarnings"
|
||||||
|
|
||||||
StreamingPreferences::StreamingPreferences(QObject *parent)
|
StreamingPreferences::StreamingPreferences(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
@@ -49,6 +48,7 @@ void StreamingPreferences::reload()
|
|||||||
mouseAcceleration = settings.value(SER_MOUSEACCELERATION, false).toBool();
|
mouseAcceleration = settings.value(SER_MOUSEACCELERATION, false).toBool();
|
||||||
startWindowed = settings.value(SER_STARTWINDOWED, false).toBool();
|
startWindowed = settings.value(SER_STARTWINDOWED, false).toBool();
|
||||||
framePacing = settings.value(SER_FRAMEPACING, false).toBool();
|
framePacing = settings.value(SER_FRAMEPACING, false).toBool();
|
||||||
|
connectionWarnings = settings.value(SER_CONNWARNINGS, true).toBool();
|
||||||
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
|
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
|
||||||
static_cast<int>(AudioConfig::AC_STEREO)).toInt());
|
static_cast<int>(AudioConfig::AC_STEREO)).toInt());
|
||||||
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
|
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
|
||||||
@@ -79,133 +79,13 @@ void StreamingPreferences::save()
|
|||||||
settings.setValue(SER_MOUSEACCELERATION, mouseAcceleration);
|
settings.setValue(SER_MOUSEACCELERATION, mouseAcceleration);
|
||||||
settings.setValue(SER_STARTWINDOWED, startWindowed);
|
settings.setValue(SER_STARTWINDOWED, startWindowed);
|
||||||
settings.setValue(SER_FRAMEPACING, framePacing);
|
settings.setValue(SER_FRAMEPACING, framePacing);
|
||||||
|
settings.setValue(SER_CONNWARNINGS, connectionWarnings);
|
||||||
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
||||||
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
|
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
|
||||||
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
|
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
|
||||||
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
|
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StreamingPreferences::hasAnyHardwareAcceleration()
|
|
||||||
{
|
|
||||||
// Always use VDS_AUTO to avoid spamming the user with warnings
|
|
||||||
// if they've forced software decoding.
|
|
||||||
return Session::isHardwareDecodeAvailable(VDS_AUTO,
|
|
||||||
VIDEO_FORMAT_H264,
|
|
||||||
1920, 1080, 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool StreamingPreferences::isRunningWayland()
|
|
||||||
{
|
|
||||||
return qgetenv("XDG_SESSION_TYPE") == QByteArray("wayland");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool StreamingPreferences::isWow64()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
return QSysInfo::currentCpuArchitecture() != QSysInfo::buildCpuArchitecture();
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
QString StreamingPreferences::getUnmappedGamepads()
|
|
||||||
{
|
|
||||||
return SdlInputHandler::getUnmappedGamepads();
|
|
||||||
}
|
|
||||||
|
|
||||||
int StreamingPreferences::getMaximumStreamingFrameRate()
|
|
||||||
{
|
|
||||||
// Never let the maximum drop below 60 FPS
|
|
||||||
int maxFrameRate = 60;
|
|
||||||
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return maxFrameRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_DisplayMode mode, bestMode, desktopMode;
|
|
||||||
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
|
|
||||||
// Get the native desktop resolution
|
|
||||||
if (StreamUtils::getRealDesktopMode(displayIndex, &desktopMode)) {
|
|
||||||
|
|
||||||
// Start at desktop mode and work our way up
|
|
||||||
bestMode = desktopMode;
|
|
||||||
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
|
||||||
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
|
|
||||||
if (mode.w == desktopMode.w && mode.h == desktopMode.h) {
|
|
||||||
if (mode.refresh_rate > bestMode.refresh_rate) {
|
|
||||||
bestMode = mode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
maxFrameRate = qMax(maxFrameRate, bestMode.refresh_rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
|
|
||||||
return maxFrameRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect StreamingPreferences::getDesktopResolution(int displayIndex)
|
|
||||||
{
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return QRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayIndex >= SDL_GetNumVideoDisplays()) {
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
return QRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_DisplayMode mode;
|
|
||||||
int err = SDL_GetDesktopDisplayMode(displayIndex, &mode);
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
|
|
||||||
if (err == 0) {
|
|
||||||
return QRect(0, 0, mode.w, mode.h);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetDesktopDisplayMode() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return QRect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect StreamingPreferences::getNativeResolution(int displayIndex)
|
|
||||||
{
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return QRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayIndex >= SDL_GetNumVideoDisplays()) {
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
return QRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_DisplayMode mode;
|
|
||||||
bool success = StreamUtils::getRealDesktopMode(displayIndex, &mode);
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
return QRect(0, 0, mode.w, mode.h);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return QRect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
||||||
{
|
{
|
||||||
// This table prefers 16:10 resolutions because they are
|
// This table prefers 16:10 resolutions because they are
|
||||||
|
|||||||
@@ -15,20 +15,6 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void save();
|
Q_INVOKABLE void save();
|
||||||
|
|
||||||
Q_INVOKABLE static bool hasAnyHardwareAcceleration();
|
|
||||||
|
|
||||||
Q_INVOKABLE static bool isRunningWayland();
|
|
||||||
|
|
||||||
Q_INVOKABLE static bool isWow64();
|
|
||||||
|
|
||||||
Q_INVOKABLE static int getMaximumStreamingFrameRate();
|
|
||||||
|
|
||||||
Q_INVOKABLE QRect getDesktopResolution(int displayIndex);
|
|
||||||
|
|
||||||
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
|
|
||||||
|
|
||||||
Q_INVOKABLE QString getUnmappedGamepads();
|
|
||||||
|
|
||||||
void reload();
|
void reload();
|
||||||
|
|
||||||
enum AudioConfig
|
enum AudioConfig
|
||||||
@@ -77,6 +63,7 @@ public:
|
|||||||
Q_PROPERTY(bool mouseAcceleration MEMBER mouseAcceleration NOTIFY mouseAccelerationChanged)
|
Q_PROPERTY(bool mouseAcceleration MEMBER mouseAcceleration NOTIFY mouseAccelerationChanged)
|
||||||
Q_PROPERTY(bool startWindowed MEMBER startWindowed NOTIFY startWindowedChanged)
|
Q_PROPERTY(bool startWindowed MEMBER startWindowed NOTIFY startWindowedChanged)
|
||||||
Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged)
|
Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged)
|
||||||
|
Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged)
|
||||||
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
||||||
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
|
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
|
||||||
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
|
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
|
||||||
@@ -97,6 +84,7 @@ public:
|
|||||||
bool mouseAcceleration;
|
bool mouseAcceleration;
|
||||||
bool startWindowed;
|
bool startWindowed;
|
||||||
bool framePacing;
|
bool framePacing;
|
||||||
|
bool connectionWarnings;
|
||||||
AudioConfig audioConfig;
|
AudioConfig audioConfig;
|
||||||
VideoCodecConfig videoCodecConfig;
|
VideoCodecConfig videoCodecConfig;
|
||||||
VideoDecoderSelection videoDecoderSelection;
|
VideoDecoderSelection videoDecoderSelection;
|
||||||
@@ -119,5 +107,6 @@ signals:
|
|||||||
void windowModeChanged();
|
void windowModeChanged();
|
||||||
void startWindowedChanged();
|
void startWindowedChanged();
|
||||||
void framePacingChanged();
|
void framePacingChanged();
|
||||||
|
void connectionWarningsChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -5,18 +5,24 @@
|
|||||||
#include "renderers/soundioaudiorenderer.h"
|
#include "renderers/soundioaudiorenderer.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_SLAUDIO
|
||||||
|
#include "renderers/slaud.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "renderers/sdl.h"
|
#include "renderers/sdl.h"
|
||||||
|
|
||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
|
|
||||||
IAudioRenderer* Session::createAudioRenderer()
|
IAudioRenderer* Session::createAudioRenderer()
|
||||||
{
|
{
|
||||||
#ifdef HAVE_SOUNDIO
|
#if defined(HAVE_SOUNDIO)
|
||||||
if (qgetenv("ML_AUDIO") == "SDL") {
|
if (qgetenv("ML_AUDIO") == "SDL") {
|
||||||
return new SdlAudioRenderer();
|
return new SdlAudioRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SoundIoAudioRenderer();
|
return new SoundIoAudioRenderer();
|
||||||
|
#elif defined(HAVE_SLAUDIO)
|
||||||
|
return new SLAudioRenderer();
|
||||||
#else
|
#else
|
||||||
return new SdlAudioRenderer();
|
return new SdlAudioRenderer();
|
||||||
#endif
|
#endif
|
||||||
@@ -105,8 +111,11 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
|||||||
{
|
{
|
||||||
int samplesDecoded;
|
int samplesDecoded;
|
||||||
|
|
||||||
// Set this thread to high priority to reduce
|
#ifndef STEAM_LINK
|
||||||
// the chance of missing our sample delivery time
|
// Set this thread to high priority to reduce the chance of missing
|
||||||
|
// our sample delivery time. On Steam Link, this causes starvation
|
||||||
|
// of other threads due to severely restricted CPU time available,
|
||||||
|
// so we will skip it on that platform.
|
||||||
if (s_ActiveSession->m_AudioSampleCount == 0) {
|
if (s_ActiveSession->m_AudioSampleCount == 0) {
|
||||||
if (SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH) < 0) {
|
if (SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH) < 0) {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -114,6 +123,7 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
|||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// See if we need to drop this sample
|
// See if we need to drop this sample
|
||||||
if (s_ActiveSession->m_DropAudioEndTime != 0) {
|
if (s_ActiveSession->m_DropAudioEndTime != 0) {
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#include "slaud.h"
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
// To reduce CPU load on the Steam Link, we need to accumulate several frames
|
||||||
|
// before submitting for playback. Higher frames per submission saves more CPU
|
||||||
|
// but increases audio latency.
|
||||||
|
#define FRAMES_PER_SUBMISSION 4
|
||||||
|
|
||||||
|
SLAudioRenderer::SLAudioRenderer()
|
||||||
|
: m_AudioContext(nullptr),
|
||||||
|
m_AudioStream(nullptr),
|
||||||
|
m_AudioBuffer(nullptr),
|
||||||
|
m_AudioBufferBytesFilled(0)
|
||||||
|
{
|
||||||
|
SLAudio_SetLogFunction(SLAudioRenderer::slLogCallback, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SLAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig)
|
||||||
|
{
|
||||||
|
m_AudioContext = SLAudio_CreateContext();
|
||||||
|
if (m_AudioContext == nullptr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SLAudio_CreateContext() failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_AudioBufferSize = SAMPLES_PER_FRAME * sizeof(short) * opusConfig->channelCount * FRAMES_PER_SUBMISSION;
|
||||||
|
m_AudioStream = SLAudio_CreateStream(m_AudioContext,
|
||||||
|
opusConfig->sampleRate,
|
||||||
|
opusConfig->channelCount,
|
||||||
|
m_AudioBufferSize,
|
||||||
|
1);
|
||||||
|
if (m_AudioStream == nullptr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SLAudio_CreateStream() failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Using SLAudio renderer");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SLAudioRenderer::~SLAudioRenderer()
|
||||||
|
{
|
||||||
|
if (m_AudioBufferBytesFilled != 0) {
|
||||||
|
// We had a buffer in flight when we quit. Just in case
|
||||||
|
// SLAudio doesn't handle this properly, we'll zero and submit
|
||||||
|
// it just to be safe.
|
||||||
|
memset(m_AudioBuffer, 0, m_AudioBufferSize);
|
||||||
|
SLAudio_SubmitFrame(m_AudioStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_AudioStream != nullptr) {
|
||||||
|
SLAudio_FreeStream(m_AudioStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_AudioContext != nullptr) {
|
||||||
|
SLAudio_FreeContext(m_AudioContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SLAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
||||||
|
{
|
||||||
|
if (m_AudioBufferBytesFilled == 0) {
|
||||||
|
// Get a new audio buffer from SLAudio
|
||||||
|
m_AudioBuffer = (char*)SLAudio_BeginFrame(m_AudioStream);
|
||||||
|
if (m_AudioBuffer == nullptr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SLAudio_BeginFrame() failed");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate several frames of audio before submitting to reduce CPU load
|
||||||
|
SDL_assert(audioSize <= m_AudioBufferSize - m_AudioBufferBytesFilled);
|
||||||
|
memcpy(&m_AudioBuffer[m_AudioBufferBytesFilled], audioBuffer, audioSize);
|
||||||
|
m_AudioBufferBytesFilled += audioSize;
|
||||||
|
|
||||||
|
// Submit the buffer when it's full
|
||||||
|
if (m_AudioBufferBytesFilled == m_AudioBufferSize) {
|
||||||
|
SLAudio_SubmitFrame(m_AudioStream);
|
||||||
|
m_AudioBufferBytesFilled = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SLAudioRenderer::slLogCallback(void *context, ESLAudioLog logLevel, const char *message)
|
||||||
|
{
|
||||||
|
SDL_LogPriority priority;
|
||||||
|
|
||||||
|
switch (logLevel)
|
||||||
|
{
|
||||||
|
case k_ESLAudioLogError:
|
||||||
|
priority = SDL_LOG_PRIORITY_ERROR;
|
||||||
|
break;
|
||||||
|
case k_ESLAudioLogWarning:
|
||||||
|
priority = SDL_LOG_PRIORITY_WARN;
|
||||||
|
break;
|
||||||
|
case k_ESLAudioLogInfo:
|
||||||
|
priority = SDL_LOG_PRIORITY_INFO;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case k_ESLAudioLogDebug:
|
||||||
|
priority = SDL_LOG_PRIORITY_DEBUG;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
priority,
|
||||||
|
"SLAudio: %s",
|
||||||
|
message);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "renderer.h"
|
||||||
|
#include <SLAudio.h>
|
||||||
|
|
||||||
|
class SLAudioRenderer : public IAudioRenderer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SLAudioRenderer();
|
||||||
|
|
||||||
|
virtual ~SLAudioRenderer();
|
||||||
|
|
||||||
|
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
||||||
|
|
||||||
|
virtual bool submitAudio(short* audioBuffer, int audioSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void slLogCallback(void* context, ESLAudioLog logLevel, const char* message);
|
||||||
|
|
||||||
|
CSLAudioContext* m_AudioContext;
|
||||||
|
CSLAudioStream* m_AudioStream;
|
||||||
|
char* m_AudioBuffer;
|
||||||
|
int m_AudioBufferSize;
|
||||||
|
int m_AudioBufferBytesFilled;
|
||||||
|
};
|
||||||
+103
-48
@@ -54,8 +54,17 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
|||||||
// is via normal motion events that are influenced by mouse acceleration).
|
// is via normal motion events that are influenced by mouse acceleration).
|
||||||
// Otherwise, we'll use raw input capture which is straight from the device
|
// Otherwise, we'll use raw input capture which is straight from the device
|
||||||
// without modification by the OS.
|
// without modification by the OS.
|
||||||
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP,
|
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP,
|
||||||
prefs.mouseAcceleration ? "1" : "0");
|
prefs.mouseAcceleration ? "1" : "0",
|
||||||
|
SDL_HINT_OVERRIDE);
|
||||||
|
|
||||||
|
#ifdef Q_OS_DARWIN
|
||||||
|
// SDL 2.0.9 on macOS has a broken HIDAPI mapping for the older Xbox One S
|
||||||
|
// firmware, so we have to disable HIDAPI for Xbox gamepads on macOS.
|
||||||
|
// https://github.com/moonlight-stream/moonlight-qt/issues/133
|
||||||
|
// https://bugzilla.libsdl.org/show_bug.cgi?id=4395
|
||||||
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
|
||||||
|
#endif
|
||||||
|
|
||||||
// We must initialize joystick explicitly before gamecontroller in order
|
// We must initialize joystick explicitly before gamecontroller in order
|
||||||
// to ensure we receive gamecontroller attach events for gamepads where
|
// to ensure we receive gamecontroller attach events for gamepads where
|
||||||
@@ -127,6 +136,9 @@ SdlInputHandler::~SdlInputHandler()
|
|||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_JOYSTICK));
|
SDL_assert(!SDL_WasInit(SDL_INIT_JOYSTICK));
|
||||||
|
|
||||||
|
// Return background event handling to off
|
||||||
|
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||||
@@ -648,41 +660,60 @@ Uint32 SdlInputHandler::mouseMoveTimerCallback(Uint32 interval, void *param)
|
|||||||
|
|
||||||
void SdlInputHandler::handleControllerAxisEvent(SDL_ControllerAxisEvent* event)
|
void SdlInputHandler::handleControllerAxisEvent(SDL_ControllerAxisEvent* event)
|
||||||
{
|
{
|
||||||
GamepadState* state = findStateForGamepad(event->which);
|
SDL_JoystickID gameControllerId = event->which;
|
||||||
|
GamepadState* state = findStateForGamepad(gameControllerId);
|
||||||
if (state == NULL) {
|
if (state == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (event->axis)
|
// Batch all pending axis motion events for this gamepad to save CPU time
|
||||||
{
|
SDL_Event nextEvent;
|
||||||
case SDL_CONTROLLER_AXIS_LEFTX:
|
for (;;) {
|
||||||
state->lsX = event->value;
|
switch (event->axis)
|
||||||
|
{
|
||||||
|
case SDL_CONTROLLER_AXIS_LEFTX:
|
||||||
|
state->lsX = event->value;
|
||||||
|
break;
|
||||||
|
case SDL_CONTROLLER_AXIS_LEFTY:
|
||||||
|
// Signed values have one more negative value than
|
||||||
|
// positive value, so inverting the sign on -32768
|
||||||
|
// could actually cause the value to overflow and
|
||||||
|
// wrap around to be negative again. Avoid that by
|
||||||
|
// capping the value at 32767.
|
||||||
|
state->lsY = -qMax(event->value, (short)-32767);
|
||||||
|
break;
|
||||||
|
case SDL_CONTROLLER_AXIS_RIGHTX:
|
||||||
|
state->rsX = event->value;
|
||||||
|
break;
|
||||||
|
case SDL_CONTROLLER_AXIS_RIGHTY:
|
||||||
|
state->rsY = -qMax(event->value, (short)-32767);
|
||||||
|
break;
|
||||||
|
case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
|
||||||
|
state->lt = (unsigned char)(event->value * 255UL / 32767);
|
||||||
|
break;
|
||||||
|
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
|
||||||
|
state->rt = (unsigned char)(event->value * 255UL / 32767);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Unhandled controller axis: %d",
|
||||||
|
event->axis);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for another event to batch with
|
||||||
|
if (SDL_PeepEvents(&nextEvent, 1, SDL_PEEKEVENT, SDL_CONTROLLERAXISMOTION, SDL_CONTROLLERAXISMOTION) <= 0) {
|
||||||
break;
|
break;
|
||||||
case SDL_CONTROLLER_AXIS_LEFTY:
|
}
|
||||||
// Signed values have one more negative value than
|
|
||||||
// positive value, so inverting the sign on -32768
|
event = &nextEvent.caxis;
|
||||||
// could actually cause the value to overflow and
|
if (event->which != gameControllerId) {
|
||||||
// wrap around to be negative again. Avoid that by
|
// Stop batching if a different gamepad interrupts us
|
||||||
// capping the value at 32767.
|
|
||||||
state->lsY = -qMax(event->value, (short)-32767);
|
|
||||||
break;
|
break;
|
||||||
case SDL_CONTROLLER_AXIS_RIGHTX:
|
}
|
||||||
state->rsX = event->value;
|
|
||||||
break;
|
// Remove the next event to batch
|
||||||
case SDL_CONTROLLER_AXIS_RIGHTY:
|
SDL_PeepEvents(&nextEvent, 1, SDL_GETEVENT, SDL_CONTROLLERAXISMOTION, SDL_CONTROLLERAXISMOTION);
|
||||||
state->rsY = -qMax(event->value, (short)-32767);
|
|
||||||
break;
|
|
||||||
case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
|
|
||||||
state->lt = (unsigned char)(event->value * 255UL / 32767);
|
|
||||||
break;
|
|
||||||
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
|
|
||||||
state->rt = (unsigned char)(event->value * 255UL / 32767);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Unhandled controller axis: %d",
|
|
||||||
event->axis);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendGamepadState(state);
|
sendGamepadState(state);
|
||||||
@@ -775,9 +806,21 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
|
|||||||
state->jsId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(state->controller));
|
state->jsId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(state->controller));
|
||||||
state->haptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(state->controller));
|
state->haptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(state->controller));
|
||||||
state->hapticEffectId = -1;
|
state->hapticEffectId = -1;
|
||||||
if (state->haptic != nullptr && (SDL_HapticQuery(state->haptic) & SDL_HAPTIC_LEFTRIGHT) == 0) {
|
state->hapticMethod = GAMEPAD_HAPTIC_METHOD_NONE;
|
||||||
SDL_HapticClose(state->haptic);
|
if (state->haptic != nullptr) {
|
||||||
state->haptic = nullptr;
|
if ((SDL_HapticQuery(state->haptic) & SDL_HAPTIC_LEFTRIGHT) == 0) {
|
||||||
|
if (SDL_HapticRumbleSupported(state->haptic)) {
|
||||||
|
if (SDL_HapticRumbleInit(state->haptic) == 0) {
|
||||||
|
state->hapticMethod = GAMEPAD_HAPTIC_METHOD_SIMPLERUMBLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state->hapticMethod == GAMEPAD_HAPTIC_METHOD_NONE) {
|
||||||
|
SDL_HapticClose(state->haptic);
|
||||||
|
state->haptic = nullptr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state->hapticMethod = GAMEPAD_HAPTIC_METHOD_LEFTRIGHT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(state->controller)),
|
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(state->controller)),
|
||||||
@@ -883,8 +926,12 @@ void SdlInputHandler::rumble(unsigned short controllerNumber, unsigned short low
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop the last effect we played
|
// Stop the last effect we played
|
||||||
if (m_GamepadState[controllerNumber].hapticEffectId >= 0) {
|
if (m_GamepadState[controllerNumber].hapticMethod == GAMEPAD_HAPTIC_METHOD_LEFTRIGHT) {
|
||||||
SDL_HapticDestroyEffect(haptic, m_GamepadState[controllerNumber].hapticEffectId);
|
if (m_GamepadState[controllerNumber].hapticEffectId >= 0) {
|
||||||
|
SDL_HapticDestroyEffect(haptic, m_GamepadState[controllerNumber].hapticEffectId);
|
||||||
|
}
|
||||||
|
} else if (m_GamepadState[controllerNumber].hapticMethod == GAMEPAD_HAPTIC_METHOD_SIMPLERUMBLE) {
|
||||||
|
SDL_HapticRumbleStop(haptic);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this callback is telling us to stop both motors, don't bother queuing a new effect
|
// If this callback is telling us to stop both motors, don't bother queuing a new effect
|
||||||
@@ -892,22 +939,30 @@ void SdlInputHandler::rumble(unsigned short controllerNumber, unsigned short low
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_HapticEffect effect;
|
if (m_GamepadState[controllerNumber].hapticMethod == GAMEPAD_HAPTIC_METHOD_LEFTRIGHT) {
|
||||||
SDL_memset(&effect, 0, sizeof(effect));
|
SDL_HapticEffect effect;
|
||||||
effect.type = SDL_HAPTIC_LEFTRIGHT;
|
SDL_memset(&effect, 0, sizeof(effect));
|
||||||
|
effect.type = SDL_HAPTIC_LEFTRIGHT;
|
||||||
|
|
||||||
// The effect should last until we are instructed to stop or change it
|
// The effect should last until we are instructed to stop or change it
|
||||||
effect.leftright.length = SDL_HAPTIC_INFINITY;
|
effect.leftright.length = SDL_HAPTIC_INFINITY;
|
||||||
|
|
||||||
// SDL haptics range from 0-32767 but XInput uses 0-65535, so divide by 2 to correct for SDL's scaling
|
// SDL haptics range from 0-32767 but XInput uses 0-65535, so divide by 2 to correct for SDL's scaling
|
||||||
effect.leftright.large_magnitude = lowFreqMotor / 2;
|
effect.leftright.large_magnitude = lowFreqMotor / 2;
|
||||||
effect.leftright.small_magnitude = highFreqMotor / 2;
|
effect.leftright.small_magnitude = highFreqMotor / 2;
|
||||||
|
|
||||||
// Play the new effect
|
// Play the new effect
|
||||||
m_GamepadState[controllerNumber].hapticEffectId = SDL_HapticNewEffect(haptic, &effect);
|
m_GamepadState[controllerNumber].hapticEffectId = SDL_HapticNewEffect(haptic, &effect);
|
||||||
if (m_GamepadState[controllerNumber].hapticEffectId >= 0) {
|
if (m_GamepadState[controllerNumber].hapticEffectId >= 0) {
|
||||||
SDL_HapticRunEffect(haptic, m_GamepadState[controllerNumber].hapticEffectId, 1);
|
SDL_HapticRunEffect(haptic, m_GamepadState[controllerNumber].hapticEffectId, 1);
|
||||||
|
}
|
||||||
|
} else if (m_GamepadState[controllerNumber].hapticMethod == GAMEPAD_HAPTIC_METHOD_SIMPLERUMBLE) {
|
||||||
|
SDL_HapticRumblePlay(haptic,
|
||||||
|
std::min(1.0, (GAMEPAD_HAPTIC_SIMPLE_HIFREQ_MOTOR_WEIGHT*highFreqMotor +
|
||||||
|
GAMEPAD_HAPTIC_SIMPLE_LOWFREQ_MOTOR_WEIGHT*lowFreqMotor) / 65535.0),
|
||||||
|
SDL_HAPTIC_INFINITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlInputHandler::handleTouchFingerEvent(SDL_TouchFingerEvent* event)
|
void SdlInputHandler::handleTouchFingerEvent(SDL_TouchFingerEvent* event)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ struct GamepadState {
|
|||||||
SDL_GameController* controller;
|
SDL_GameController* controller;
|
||||||
SDL_JoystickID jsId;
|
SDL_JoystickID jsId;
|
||||||
SDL_Haptic* haptic;
|
SDL_Haptic* haptic;
|
||||||
|
int hapticMethod;
|
||||||
int hapticEffectId;
|
int hapticEffectId;
|
||||||
short index;
|
short index;
|
||||||
|
|
||||||
@@ -21,6 +22,13 @@ struct GamepadState {
|
|||||||
#define MAX_GAMEPADS 4
|
#define MAX_GAMEPADS 4
|
||||||
#define MAX_FINGERS 2
|
#define MAX_FINGERS 2
|
||||||
|
|
||||||
|
#define GAMEPAD_HAPTIC_METHOD_NONE 0
|
||||||
|
#define GAMEPAD_HAPTIC_METHOD_LEFTRIGHT 1
|
||||||
|
#define GAMEPAD_HAPTIC_METHOD_SIMPLERUMBLE 2
|
||||||
|
|
||||||
|
#define GAMEPAD_HAPTIC_SIMPLE_HIFREQ_MOTOR_WEIGHT 0.33
|
||||||
|
#define GAMEPAD_HAPTIC_SIMPLE_LOWFREQ_MOTOR_WEIGHT 0.8
|
||||||
|
|
||||||
class SdlInputHandler
|
class SdlInputHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
+115
-112
@@ -11,7 +11,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_SLVIDEO
|
#ifdef HAVE_SLVIDEO
|
||||||
#include "video/sl.h"
|
#include "video/slvid.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
@@ -39,7 +39,8 @@ CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
|
|||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
Session::clLogMessage,
|
Session::clLogMessage,
|
||||||
Session::clRumble
|
Session::clRumble,
|
||||||
|
Session::clConnectionStatusUpdate
|
||||||
};
|
};
|
||||||
|
|
||||||
AUDIO_RENDERER_CALLBACKS Session::k_AudioCallbacks = {
|
AUDIO_RENDERER_CALLBACKS Session::k_AudioCallbacks = {
|
||||||
@@ -48,7 +49,12 @@ AUDIO_RENDERER_CALLBACKS Session::k_AudioCallbacks = {
|
|||||||
nullptr,
|
nullptr,
|
||||||
Session::arCleanup,
|
Session::arCleanup,
|
||||||
Session::arDecodeAndPlaySample,
|
Session::arDecodeAndPlaySample,
|
||||||
CAPABILITY_DIRECT_SUBMIT
|
CAPABILITY_DIRECT_SUBMIT |
|
||||||
|
#ifdef STEAM_LINK
|
||||||
|
CAPABILITY_SLOW_OPUS_DECODER
|
||||||
|
#else
|
||||||
|
0
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
Session* Session::s_ActiveSession;
|
Session* Session::s_ActiveSession;
|
||||||
@@ -114,19 +120,56 @@ void Session::clRumble(unsigned short controllerNumber, unsigned short lowFreqMo
|
|||||||
SDL_AtomicUnlock(&s_ActiveSession->m_InputHandlerLock);
|
SDL_AtomicUnlock(&s_ActiveSession->m_InputHandlerLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CALL_INITIALIZE(dec) (dec)->initialize(vds, window, videoFormat, width, height, frameRate, enableVsync, enableFramePacing)
|
void Session::clConnectionStatusUpdate(int connectionStatus)
|
||||||
|
{
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Connection status update: %d",
|
||||||
|
connectionStatus);
|
||||||
|
|
||||||
|
if (!s_ActiveSession->m_Preferences->connectionWarnings) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (connectionStatus)
|
||||||
|
{
|
||||||
|
case CONN_STATUS_POOR:
|
||||||
|
if (s_ActiveSession->m_StreamConfig.bitrate > 5000) {
|
||||||
|
strcpy(s_ActiveSession->m_OverlayManager.getOverlayText(Overlay::OverlayStatusUpdate), "Slow connection to PC\nReduce your bitrate");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
strcpy(s_ActiveSession->m_OverlayManager.getOverlayText(Overlay::OverlayStatusUpdate), "Poor connection to PC");
|
||||||
|
}
|
||||||
|
s_ActiveSession->m_OverlayManager.setOverlayTextUpdated(Overlay::OverlayStatusUpdate);
|
||||||
|
s_ActiveSession->m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
|
||||||
|
break;
|
||||||
|
case CONN_STATUS_OKAY:
|
||||||
|
s_ActiveSession->m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||||
SDL_Window* window, int videoFormat, int width, int height,
|
SDL_Window* window, int videoFormat, int width, int height,
|
||||||
int frameRate, bool enableVsync, bool enableFramePacing, bool testOnly, IVideoDecoder*& chosenDecoder)
|
int frameRate, bool enableVsync, bool enableFramePacing, bool testOnly, IVideoDecoder*& chosenDecoder)
|
||||||
{
|
{
|
||||||
|
DECODER_PARAMETERS params;
|
||||||
|
|
||||||
|
params.width = width;
|
||||||
|
params.height = height;
|
||||||
|
params.frameRate = frameRate;
|
||||||
|
params.videoFormat = videoFormat;
|
||||||
|
params.window = window;
|
||||||
|
params.enableVsync = enableVsync;
|
||||||
|
params.enableFramePacing = enableFramePacing;
|
||||||
|
params.vds = vds;
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"V-sync %s",
|
"V-sync %s",
|
||||||
enableVsync ? "enabled" : "disabled");
|
enableVsync ? "enabled" : "disabled");
|
||||||
|
|
||||||
#ifdef HAVE_SLVIDEO
|
#ifdef HAVE_SLVIDEO
|
||||||
chosenDecoder = new SLVideoDecoder(testOnly);
|
chosenDecoder = new SLVideoDecoder(testOnly);
|
||||||
if (CALL_INITIALIZE(chosenDecoder)) {
|
if (chosenDecoder->initialize(¶ms)) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SLVideo video decoder chosen");
|
"SLVideo video decoder chosen");
|
||||||
return true;
|
return true;
|
||||||
@@ -141,7 +184,7 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
|||||||
|
|
||||||
#ifdef HAVE_FFMPEG
|
#ifdef HAVE_FFMPEG
|
||||||
chosenDecoder = new FFmpegVideoDecoder(testOnly);
|
chosenDecoder = new FFmpegVideoDecoder(testOnly);
|
||||||
if (CALL_INITIALIZE(chosenDecoder)) {
|
if (chosenDecoder->initialize(¶ms)) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"FFmpeg-based video decoder chosen");
|
"FFmpeg-based video decoder chosen");
|
||||||
return true;
|
return true;
|
||||||
@@ -215,30 +258,13 @@ int Session::drSubmitDecodeUnit(PDECODE_UNIT du)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Session::isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelection vds,
|
bool Session::isHardwareDecodeAvailable(SDL_Window* window,
|
||||||
|
StreamingPreferences::VideoDecoderSelection vds,
|
||||||
int videoFormat, int width, int height, int frameRate)
|
int videoFormat, int width, int height, int frameRate)
|
||||||
{
|
{
|
||||||
IVideoDecoder* decoder;
|
IVideoDecoder* decoder;
|
||||||
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Window* window = SDL_CreateWindow("", 0, 0, width, height, SDL_WINDOW_HIDDEN);
|
|
||||||
if (!window) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to create window for hardware decode test: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, false, true, decoder)) {
|
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, false, true, decoder)) {
|
||||||
SDL_DestroyWindow(window);
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,39 +272,16 @@ bool Session::isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelect
|
|||||||
|
|
||||||
delete decoder;
|
delete decoder;
|
||||||
|
|
||||||
// This must be called after the decoder is deleted, because
|
|
||||||
// the renderer may want to interact with the window
|
|
||||||
SDL_DestroyWindow(window);
|
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Session::getDecoderCapabilities(StreamingPreferences::VideoDecoderSelection vds,
|
int Session::getDecoderCapabilities(SDL_Window* window,
|
||||||
|
StreamingPreferences::VideoDecoderSelection vds,
|
||||||
int videoFormat, int width, int height, int frameRate)
|
int videoFormat, int width, int height, int frameRate)
|
||||||
{
|
{
|
||||||
IVideoDecoder* decoder;
|
IVideoDecoder* decoder;
|
||||||
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Window* window = SDL_CreateWindow("", 0, 0, width, height, SDL_WINDOW_HIDDEN);
|
|
||||||
if (!window) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to create window for hardware decode test: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, false, true, decoder)) {
|
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, false, true, decoder)) {
|
||||||
SDL_DestroyWindow(window);
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,12 +289,6 @@ int Session::getDecoderCapabilities(StreamingPreferences::VideoDecoderSelection
|
|||||||
|
|
||||||
delete decoder;
|
delete decoder;
|
||||||
|
|
||||||
// This must be called after the decoder is deleted, because
|
|
||||||
// the renderer may want to interact with the window
|
|
||||||
SDL_DestroyWindow(window);
|
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
|
|
||||||
return caps;
|
return caps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,8 +324,25 @@ Session::~Session()
|
|||||||
s_ActiveSessionSemaphore.release();
|
s_ActiveSessionSemaphore.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::initialize()
|
bool Session::initialize()
|
||||||
{
|
{
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a hidden window to use for decoder initialization tests
|
||||||
|
SDL_Window* testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN);
|
||||||
|
if (!testWindow) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to create window for hardware decode test: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
qInfo() << "Server GPU:" << m_Computer->gpuModel;
|
qInfo() << "Server GPU:" << m_Computer->gpuModel;
|
||||||
qInfo() << "Server GFE version:" << m_Computer->gfeVersion;
|
qInfo() << "Server GFE version:" << m_Computer->gfeVersion;
|
||||||
|
|
||||||
@@ -381,7 +395,8 @@ void Session::initialize()
|
|||||||
case StreamingPreferences::VCC_AUTO:
|
case StreamingPreferences::VCC_AUTO:
|
||||||
// TODO: Determine if HEVC is better depending on the decoder
|
// TODO: Determine if HEVC is better depending on the decoder
|
||||||
m_StreamConfig.supportsHevc =
|
m_StreamConfig.supportsHevc =
|
||||||
isHardwareDecodeAvailable(m_Preferences->videoDecoderSelection,
|
isHardwareDecodeAvailable(testWindow,
|
||||||
|
m_Preferences->videoDecoderSelection,
|
||||||
VIDEO_FORMAT_H265,
|
VIDEO_FORMAT_H265,
|
||||||
m_StreamConfig.width,
|
m_StreamConfig.width,
|
||||||
m_StreamConfig.height,
|
m_StreamConfig.height,
|
||||||
@@ -418,6 +433,15 @@ void Session::initialize()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the capability flags from the chosen decoder/renderer
|
||||||
|
// Requires m_StreamConfig.supportsHevc to be initialized
|
||||||
|
m_VideoCallbacks.capabilities |= getDecoderCapabilities(testWindow,
|
||||||
|
m_Preferences->videoDecoderSelection,
|
||||||
|
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
||||||
|
m_StreamConfig.width,
|
||||||
|
m_StreamConfig.height,
|
||||||
|
m_StreamConfig.fps);
|
||||||
|
|
||||||
switch (m_Preferences->windowMode)
|
switch (m_Preferences->windowMode)
|
||||||
{
|
{
|
||||||
case StreamingPreferences::WM_FULLSCREEN_DESKTOP:
|
case StreamingPreferences::WM_FULLSCREEN_DESKTOP:
|
||||||
@@ -428,6 +452,19 @@ void Session::initialize()
|
|||||||
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
|
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for validation errors/warnings and emit
|
||||||
|
// signals for them, if appropriate
|
||||||
|
bool ret = validateLaunch(testWindow);
|
||||||
|
|
||||||
|
SDL_DestroyWindow(testWindow);
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::emitLaunchWarning(QString text)
|
void Session::emitLaunchWarning(QString text)
|
||||||
@@ -446,7 +483,7 @@ void Session::emitLaunchWarning(QString text)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Session::validateLaunch()
|
bool Session::validateLaunch(SDL_Window* testWindow)
|
||||||
{
|
{
|
||||||
QStringList warningList;
|
QStringList warningList;
|
||||||
|
|
||||||
@@ -466,7 +503,8 @@ bool Session::validateLaunch()
|
|||||||
bool hevcForced = m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
|
bool hevcForced = m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
|
||||||
m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR;
|
m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR;
|
||||||
|
|
||||||
if (!isHardwareDecodeAvailable(m_Preferences->videoDecoderSelection,
|
if (!isHardwareDecodeAvailable(testWindow,
|
||||||
|
m_Preferences->videoDecoderSelection,
|
||||||
VIDEO_FORMAT_H265,
|
VIDEO_FORMAT_H265,
|
||||||
m_StreamConfig.width,
|
m_StreamConfig.width,
|
||||||
m_StreamConfig.height,
|
m_StreamConfig.height,
|
||||||
@@ -507,7 +545,8 @@ bool Session::validateLaunch()
|
|||||||
emitLaunchWarning("Your host PC GPU doesn't support HDR streaming. "
|
emitLaunchWarning("Your host PC GPU doesn't support HDR streaming. "
|
||||||
"A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.");
|
"A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.");
|
||||||
}
|
}
|
||||||
else if (!isHardwareDecodeAvailable(m_Preferences->videoDecoderSelection,
|
else if (!isHardwareDecodeAvailable(testWindow,
|
||||||
|
m_Preferences->videoDecoderSelection,
|
||||||
VIDEO_FORMAT_H265_MAIN10,
|
VIDEO_FORMAT_H265_MAIN10,
|
||||||
m_StreamConfig.width,
|
m_StreamConfig.width,
|
||||||
m_StreamConfig.height,
|
m_StreamConfig.height,
|
||||||
@@ -556,7 +595,8 @@ bool Session::validateLaunch()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_FORCE_HARDWARE &&
|
if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_FORCE_HARDWARE &&
|
||||||
!isHardwareDecodeAvailable(m_Preferences->videoDecoderSelection,
|
!isHardwareDecodeAvailable(testWindow,
|
||||||
|
m_Preferences->videoDecoderSelection,
|
||||||
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
||||||
m_StreamConfig.width,
|
m_StreamConfig.width,
|
||||||
m_StreamConfig.height,
|
m_StreamConfig.height,
|
||||||
@@ -572,13 +612,6 @@ bool Session::validateLaunch()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the capability flags from the chosen decoder/renderer
|
|
||||||
m_VideoCallbacks.capabilities |= getDecoderCapabilities(m_Preferences->videoDecoderSelection,
|
|
||||||
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
|
||||||
m_StreamConfig.width,
|
|
||||||
m_StreamConfig.height,
|
|
||||||
m_StreamConfig.fps);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -800,11 +833,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
// Complete initialization in this deferred context to avoid
|
// Complete initialization in this deferred context to avoid
|
||||||
// calling expensive functions in the constructor (during the
|
// calling expensive functions in the constructor (during the
|
||||||
// process of loading the StreamSegue).
|
// process of loading the StreamSegue).
|
||||||
initialize();
|
if (!initialize()) {
|
||||||
|
|
||||||
// Check for validation errors/warnings and emit
|
|
||||||
// signals for them, if appropriate
|
|
||||||
if (!validateLaunch()) {
|
|
||||||
emit sessionFinished();
|
emit sessionFinished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -864,29 +893,19 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
} catch (const GfeHttpResponseException& e) {
|
} catch (const GfeHttpResponseException& e) {
|
||||||
delete m_InputHandler;
|
delete m_InputHandler;
|
||||||
m_InputHandler = nullptr;
|
m_InputHandler = nullptr;
|
||||||
emit displayLaunchError(e.toQString());
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
|
emit displayLaunchError("GeForce Experience returned error: " + e.toQString());
|
||||||
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
||||||
return;
|
return;
|
||||||
} catch (const QtNetworkReplyException& e) {
|
} catch (const QtNetworkReplyException& e) {
|
||||||
delete m_InputHandler;
|
delete m_InputHandler;
|
||||||
m_InputHandler = nullptr;
|
m_InputHandler = nullptr;
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
emit displayLaunchError(e.toQString());
|
emit displayLaunchError(e.toQString());
|
||||||
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_VIDEO));
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
delete m_InputHandler;
|
|
||||||
m_InputHandler = nullptr;
|
|
||||||
emit displayLaunchError(QString::fromLocal8Bit(SDL_GetError()));
|
|
||||||
QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray hostnameStr = m_Computer->activeAddress.toLatin1();
|
QByteArray hostnameStr = m_Computer->activeAddress.toLatin1();
|
||||||
QByteArray siAppVersion = m_Computer->appVersion.toLatin1();
|
QByteArray siAppVersion = m_Computer->appVersion.toLatin1();
|
||||||
|
|
||||||
@@ -1007,14 +1026,6 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
// sleep precision and more accurate callback timing.
|
// sleep precision and more accurate callback timing.
|
||||||
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "1");
|
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "1");
|
||||||
|
|
||||||
// Raise the priority of the main thread, since it handles
|
|
||||||
// time-sensitive video rendering
|
|
||||||
if (SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH) < 0) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Unable to set main thread to high priority: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
}
|
|
||||||
|
|
||||||
int currentDisplayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
int currentDisplayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||||
|
|
||||||
// Now that we're about to stream, any SDL_QUIT event is expected
|
// Now that we're about to stream, any SDL_QUIT event is expected
|
||||||
@@ -1031,7 +1042,13 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
// blocks this thread too long for high polling rate mice and high
|
// blocks this thread too long for high polling rate mice and high
|
||||||
// refresh rate displays.
|
// refresh rate displays.
|
||||||
if (!SDL_PollEvent(&event)) {
|
if (!SDL_PollEvent(&event)) {
|
||||||
|
#ifndef STEAM_LINK
|
||||||
SDL_Delay(1);
|
SDL_Delay(1);
|
||||||
|
#else
|
||||||
|
// Waking every 1 ms to process input is too much for the low performance
|
||||||
|
// ARM core in the Steam Link, so we will wait 10 ms instead.
|
||||||
|
SDL_Delay(10);
|
||||||
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
@@ -1039,25 +1056,11 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Quit event received");
|
"Quit event received");
|
||||||
goto DispatchDeferredCleanup;
|
goto DispatchDeferredCleanup;
|
||||||
case SDL_USEREVENT: {
|
|
||||||
SDL_Event nextEvent;
|
|
||||||
|
|
||||||
|
case SDL_USEREVENT:
|
||||||
SDL_assert(event.user.code == SDL_CODE_FRAME_READY);
|
SDL_assert(event.user.code == SDL_CODE_FRAME_READY);
|
||||||
|
m_VideoDecoder->renderFrameOnMainThread();
|
||||||
// Drop any earlier frames
|
|
||||||
while (SDL_PeepEvents(&nextEvent,
|
|
||||||
1,
|
|
||||||
SDL_GETEVENT,
|
|
||||||
SDL_USEREVENT,
|
|
||||||
SDL_USEREVENT) == 1) {
|
|
||||||
m_VideoDecoder->dropFrame(&event.user);
|
|
||||||
event = nextEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the last frame
|
|
||||||
m_VideoDecoder->renderFrame(&event.user);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case SDL_WINDOWEVENT:
|
case SDL_WINDOWEVENT:
|
||||||
// Capture mouse cursor when user actives the window by clicking on
|
// Capture mouse cursor when user actives the window by clicking on
|
||||||
@@ -1260,8 +1263,8 @@ DispatchDeferredCleanup:
|
|||||||
if (iconSurface != nullptr) {
|
if (iconSurface != nullptr) {
|
||||||
SDL_FreeSurface(iconSurface);
|
SDL_FreeSurface(iconSurface);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_VIDEO));
|
|
||||||
|
|
||||||
// Cleanup can take a while, so dispatch it to a worker thread.
|
// Cleanup can take a while, so dispatch it to a worker thread.
|
||||||
// When it is complete, it will release our s_ActiveSessionSemaphore
|
// When it is complete, it will release our s_ActiveSessionSemaphore
|
||||||
|
|||||||
+11
-8
@@ -25,13 +25,10 @@ public:
|
|||||||
Q_INVOKABLE void exec(int displayOriginX, int displayOriginY);
|
Q_INVOKABLE void exec(int displayOriginX, int displayOriginY);
|
||||||
|
|
||||||
static
|
static
|
||||||
bool isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelection vds,
|
bool isHardwareDecodeAvailable(SDL_Window* window,
|
||||||
|
StreamingPreferences::VideoDecoderSelection vds,
|
||||||
int videoFormat, int width, int height, int frameRate);
|
int videoFormat, int width, int height, int frameRate);
|
||||||
|
|
||||||
static
|
|
||||||
int getDecoderCapabilities(StreamingPreferences::VideoDecoderSelection vds,
|
|
||||||
int videoFormat, int width, int height, int frameRate);
|
|
||||||
|
|
||||||
static Session* get()
|
static Session* get()
|
||||||
{
|
{
|
||||||
return s_ActiveSession;
|
return s_ActiveSession;
|
||||||
@@ -58,13 +55,16 @@ signals:
|
|||||||
void sessionFinished();
|
void sessionFinished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initialize();
|
bool initialize();
|
||||||
|
|
||||||
bool validateLaunch();
|
bool validateLaunch(SDL_Window* testWindow);
|
||||||
|
|
||||||
void emitLaunchWarning(QString text);
|
void emitLaunchWarning(QString text);
|
||||||
|
|
||||||
int getDecoderCapabilities();
|
static
|
||||||
|
int getDecoderCapabilities(SDL_Window* window,
|
||||||
|
StreamingPreferences::VideoDecoderSelection vds,
|
||||||
|
int videoFormat, int width, int height, int frameRate);
|
||||||
|
|
||||||
IAudioRenderer* createAudioRenderer();
|
IAudioRenderer* createAudioRenderer();
|
||||||
|
|
||||||
@@ -101,6 +101,9 @@ private:
|
|||||||
static
|
static
|
||||||
void clRumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor);
|
void clRumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor);
|
||||||
|
|
||||||
|
static
|
||||||
|
void clConnectionStatusUpdate(int connectionStatus);
|
||||||
|
|
||||||
static
|
static
|
||||||
int arInit(int audioConfiguration,
|
int arInit(int audioConfiguration,
|
||||||
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||||
|
|||||||
@@ -26,33 +26,24 @@ typedef struct _VIDEO_STATS {
|
|||||||
uint32_t measurementStartTimestamp;
|
uint32_t measurementStartTimestamp;
|
||||||
} VIDEO_STATS, *PVIDEO_STATS;
|
} VIDEO_STATS, *PVIDEO_STATS;
|
||||||
|
|
||||||
|
typedef struct _DECODER_PARAMETERS {
|
||||||
|
SDL_Window* window;
|
||||||
|
StreamingPreferences::VideoDecoderSelection vds;
|
||||||
|
|
||||||
|
int videoFormat;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int frameRate;
|
||||||
|
bool enableVsync;
|
||||||
|
bool enableFramePacing;
|
||||||
|
} DECODER_PARAMETERS, *PDECODER_PARAMETERS;
|
||||||
|
|
||||||
class IVideoDecoder {
|
class IVideoDecoder {
|
||||||
public:
|
public:
|
||||||
virtual ~IVideoDecoder() {}
|
virtual ~IVideoDecoder() {}
|
||||||
virtual bool initialize(StreamingPreferences::VideoDecoderSelection vds,
|
virtual bool initialize(PDECODER_PARAMETERS params) = 0;
|
||||||
SDL_Window* window,
|
|
||||||
int videoFormat,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
int frameRate,
|
|
||||||
bool enableVsync,
|
|
||||||
bool enableFramePacing) = 0;
|
|
||||||
virtual bool isHardwareAccelerated() = 0;
|
virtual bool isHardwareAccelerated() = 0;
|
||||||
virtual int getDecoderCapabilities() = 0;
|
virtual int getDecoderCapabilities() = 0;
|
||||||
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
||||||
virtual void renderFrame(SDL_UserEvent* event) = 0;
|
virtual void renderFrameOnMainThread() = 0;
|
||||||
virtual void dropFrame(SDL_UserEvent* event) = 0;
|
|
||||||
|
|
||||||
virtual void queueFrame(void* data1 = nullptr,
|
|
||||||
void* data2 = nullptr)
|
|
||||||
{
|
|
||||||
SDL_Event event;
|
|
||||||
|
|
||||||
event.type = SDL_USEREVENT;
|
|
||||||
event.user.code = SDL_CODE_FRAME_READY;
|
|
||||||
event.user.data1 = data1;
|
|
||||||
event.user.data2 = data2;
|
|
||||||
|
|
||||||
SDL_PushEvent(&event);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
#include "drm.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavutil/hwcontext_drm.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <libdrm/drm_fourcc.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include "streaming/streamutils.h"
|
||||||
|
#include "streaming/session.h"
|
||||||
|
|
||||||
|
#include <Limelight.h>
|
||||||
|
|
||||||
|
DrmRenderer::DrmRenderer()
|
||||||
|
: m_DrmFd(-1),
|
||||||
|
m_CrtcId(0),
|
||||||
|
m_PlaneId(0),
|
||||||
|
m_CurrentFbId(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DrmRenderer::~DrmRenderer()
|
||||||
|
{
|
||||||
|
if (m_CurrentFbId != 0) {
|
||||||
|
drmModeRmFB(m_DrmFd, m_CurrentFbId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_DrmFd != -1) {
|
||||||
|
close(m_DrmFd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrmRenderer::prepareDecoderContext(AVCodecContext*)
|
||||||
|
{
|
||||||
|
/* Nothing to do */
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Using DRM renderer");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrmRenderer::initialize(PDECODER_PARAMETERS)
|
||||||
|
{
|
||||||
|
const char* device = SDL_getenv("DRM_DEV");
|
||||||
|
int i;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
drmModeRes* resources = drmModeGetResources(m_DrmFd);
|
||||||
|
if (resources == nullptr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"drmModeGetResources() failed: %d",
|
||||||
|
errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for a connected connector and get the associated encoder
|
||||||
|
uint32_t encoderId = 0;
|
||||||
|
for (i = 0; i < resources->count_connectors && encoderId == 0; i++) {
|
||||||
|
drmModeConnector* connector = drmModeGetConnector(m_DrmFd, resources->connectors[i]);
|
||||||
|
if (connector != nullptr) {
|
||||||
|
if (connector->connection == DRM_MODE_CONNECTED && connector->count_modes > 0) {
|
||||||
|
encoderId = connector->encoder_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
drmModeFreeConnector(connector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoderId == 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"No connected displays found!");
|
||||||
|
drmModeFreeResources(resources);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now find the CRTC from the encoder
|
||||||
|
m_CrtcId = 0;
|
||||||
|
for (i = 0; i < resources->count_encoders && m_CrtcId == 0; i++) {
|
||||||
|
drmModeEncoder* encoder = drmModeGetEncoder(m_DrmFd, resources->encoders[i]);
|
||||||
|
if (encoder != nullptr) {
|
||||||
|
if (encoder->encoder_id == encoderId) {
|
||||||
|
m_CrtcId = encoder->crtc_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
drmModeFreeEncoder(encoder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_CrtcId == 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"DRM encoder not found!");
|
||||||
|
drmModeFreeResources(resources);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crtcIndex = -1;
|
||||||
|
for (int i = 0; i < resources->count_crtcs; i++) {
|
||||||
|
if (resources->crtcs[i] == m_CrtcId) {
|
||||||
|
drmModeCrtc* crtc = drmModeGetCrtc(m_DrmFd, resources->crtcs[i]);
|
||||||
|
crtcIndex = i;
|
||||||
|
m_OutputRect.x = m_OutputRect.y = 0;
|
||||||
|
m_OutputRect.w = crtc->width;
|
||||||
|
m_OutputRect.h = crtc->height;
|
||||||
|
drmModeFreeCrtc(crtc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drmModeFreeResources(resources);
|
||||||
|
|
||||||
|
if (crtcIndex == -1) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to get CRTC!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
drmSetClientCap(m_DrmFd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
|
||||||
|
|
||||||
|
drmModePlaneRes* planeRes = drmModeGetPlaneResources(m_DrmFd);
|
||||||
|
if (planeRes == nullptr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"drmGetPlaneResources() failed: %d",
|
||||||
|
errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find an NV12 overlay plane to render on
|
||||||
|
m_PlaneId = 0;
|
||||||
|
for (uint32_t i = 0; i < planeRes->count_planes && m_PlaneId == 0; i++) {
|
||||||
|
drmModePlane* plane = drmModeGetPlane(m_DrmFd, planeRes->planes[i]);
|
||||||
|
if (plane != nullptr) {
|
||||||
|
bool matchingFormat = false;
|
||||||
|
for (uint32_t j = 0; j < plane->count_formats; j++) {
|
||||||
|
if (plane->formats[j] == DRM_FORMAT_NV12) {
|
||||||
|
matchingFormat = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingFormat == false) {
|
||||||
|
drmModeFreePlane(plane);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((plane->possible_crtcs & (1 << crtcIndex)) && plane->crtc_id == 0) {
|
||||||
|
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(m_DrmFd, planeRes->planes[i], DRM_MODE_OBJECT_PLANE);
|
||||||
|
if (props != nullptr) {
|
||||||
|
for (uint32_t j = 0; j < props->count_props && m_PlaneId == 0; j++) {
|
||||||
|
drmModePropertyPtr prop = drmModeGetProperty(m_DrmFd, props->props[j]);
|
||||||
|
if (prop != nullptr) {
|
||||||
|
if (!strcmp(prop->name, "type") && props->prop_values[j] == DRM_PLANE_TYPE_OVERLAY) {
|
||||||
|
m_PlaneId = plane->plane_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
drmModeFreeProperty(prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drmModeFreeObjectProperties(props);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drmModeFreePlane(plane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drmModeFreePlaneResources(planeRes);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AVPixelFormat DrmRenderer::getPreferredPixelFormat(int)
|
||||||
|
{
|
||||||
|
// DRM PRIME buffers
|
||||||
|
return AV_PIX_FMT_DRM_PRIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrmRenderer::renderFrame(AVFrame* frame)
|
||||||
|
{
|
||||||
|
AVDRMFrameDescriptor* drmFrame = (AVDRMFrameDescriptor*)frame->data[0];
|
||||||
|
int err;
|
||||||
|
uint32_t primeHandle;
|
||||||
|
uint32_t handles[4] = {};
|
||||||
|
uint32_t pitches[4] = {};
|
||||||
|
uint32_t offsets[4] = {};
|
||||||
|
|
||||||
|
SDL_Rect src, dst;
|
||||||
|
|
||||||
|
src.x = src.y = 0;
|
||||||
|
src.w = frame->width;
|
||||||
|
src.h = frame->height;
|
||||||
|
dst = m_OutputRect;
|
||||||
|
|
||||||
|
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||||
|
|
||||||
|
// Convert the FD in the AVDRMFrameDescriptor to a PRIME handle
|
||||||
|
// that can be used in drmModeAddFB2()
|
||||||
|
SDL_assert(drmFrame->nb_objects == 1);
|
||||||
|
err = drmPrimeFDToHandle(m_DrmFd, drmFrame->objects[0].fd, &primeHandle);
|
||||||
|
if (err < 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"drmPrimeFDToHandle() failed: %d",
|
||||||
|
errno);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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++) {
|
||||||
|
handles[i] = primeHandle;
|
||||||
|
pitches[i] = drmFrame->layers[0].planes[i].pitch;
|
||||||
|
offsets[i] = drmFrame->layers[0].planes[i].offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember the last FB object we created so we can free it
|
||||||
|
// when we are finished rendering this one (if successful).
|
||||||
|
uint32_t lastFbId = m_CurrentFbId;
|
||||||
|
|
||||||
|
// Create a frame buffer object from the PRIME buffer
|
||||||
|
err = drmModeAddFB2(m_DrmFd, frame->width, frame->height,
|
||||||
|
drmFrame->layers[0].format,
|
||||||
|
handles, pitches, offsets, &m_CurrentFbId, 0);
|
||||||
|
if (err < 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"drmModeAddFB2() failed: %d",
|
||||||
|
errno);
|
||||||
|
m_CurrentFbId = lastFbId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the overlay
|
||||||
|
err = drmModeSetPlane(m_DrmFd, m_PlaneId, m_CrtcId, m_CurrentFbId, 0,
|
||||||
|
dst.x, dst.y,
|
||||||
|
dst.w, dst.h,
|
||||||
|
0, 0,
|
||||||
|
frame->width << 16,
|
||||||
|
frame->height << 16);
|
||||||
|
if (err < 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"drmModeSetPlane() failed: %d",
|
||||||
|
errno);
|
||||||
|
drmModeRmFB(m_DrmFd, m_CurrentFbId);
|
||||||
|
m_CurrentFbId = lastFbId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the previous FB object which has now been superseded
|
||||||
|
drmModeRmFB(m_DrmFd, lastFbId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "renderer.h"
|
||||||
|
|
||||||
|
#include <xf86drm.h>
|
||||||
|
#include <xf86drmMode.h>
|
||||||
|
|
||||||
|
class DrmRenderer : public IFFmpegRenderer {
|
||||||
|
public:
|
||||||
|
DrmRenderer();
|
||||||
|
virtual ~DrmRenderer() override;
|
||||||
|
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||||
|
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
||||||
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
|
virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_DrmFd;
|
||||||
|
uint32_t m_CrtcId;
|
||||||
|
uint32_t m_PlaneId;
|
||||||
|
uint32_t m_CurrentFbId;
|
||||||
|
SDL_Rect m_OutputRect;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ DXVA2Renderer::DXVA2Renderer() :
|
|||||||
m_Processor(nullptr),
|
m_Processor(nullptr),
|
||||||
m_FrameIndex(0),
|
m_FrameIndex(0),
|
||||||
m_DebugOverlayFont(nullptr),
|
m_DebugOverlayFont(nullptr),
|
||||||
|
m_StatusOverlayFont(nullptr),
|
||||||
m_BlockingPresent(false)
|
m_BlockingPresent(false)
|
||||||
{
|
{
|
||||||
RtlZeroMemory(m_DecSurfaces, sizeof(m_DecSurfaces));
|
RtlZeroMemory(m_DecSurfaces, sizeof(m_DecSurfaces));
|
||||||
@@ -48,6 +49,7 @@ DXVA2Renderer::~DXVA2Renderer()
|
|||||||
SAFE_COM_RELEASE(m_ProcService);
|
SAFE_COM_RELEASE(m_ProcService);
|
||||||
SAFE_COM_RELEASE(m_Processor);
|
SAFE_COM_RELEASE(m_Processor);
|
||||||
SAFE_COM_RELEASE(m_DebugOverlayFont);
|
SAFE_COM_RELEASE(m_DebugOverlayFont);
|
||||||
|
SAFE_COM_RELEASE(m_StatusOverlayFont);
|
||||||
|
|
||||||
for (int i = 0; i < ARRAYSIZE(m_DecSurfaces); i++) {
|
for (int i = 0; i < ARRAYSIZE(m_DecSurfaces); i++) {
|
||||||
SAFE_COM_RELEASE(m_DecSurfaces[i]);
|
SAFE_COM_RELEASE(m_DecSurfaces[i]);
|
||||||
@@ -105,7 +107,7 @@ bool DXVA2Renderer::prepareDecoderContext(AVCodecContext* context)
|
|||||||
|
|
||||||
int DXVA2Renderer::ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int)
|
int DXVA2Renderer::ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int)
|
||||||
{
|
{
|
||||||
DXVA2Renderer* me = (DXVA2Renderer*)((FFmpegVideoDecoder*)context->opaque)->getRenderer();
|
DXVA2Renderer* me = (DXVA2Renderer*)((FFmpegVideoDecoder*)context->opaque)->getBackendRenderer();
|
||||||
|
|
||||||
frame->buf[0] = av_buffer_pool_get(me->m_Pool);
|
frame->buf[0] = av_buffer_pool_get(me->m_Pool);
|
||||||
if (!frame->buf[0]) {
|
if (!frame->buf[0]) {
|
||||||
@@ -330,6 +332,12 @@ bool DXVA2Renderer::isDXVideoProcessorAPIBlacklisted()
|
|||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|
||||||
|
if (qgetenv("DXVA2_DISABLE_VIDPROC_BLACKLIST") == "1") {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"IDirectXVideoProcessor blacklist is disabled");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
hr = m_Device->GetDirect3D(&d3d9);
|
hr = m_Device->GetDirect3D(&d3d9);
|
||||||
if (SUCCEEDED(hr)) {
|
if (SUCCEEDED(hr)) {
|
||||||
D3DCAPS9 caps;
|
D3DCAPS9 caps;
|
||||||
@@ -377,6 +385,12 @@ bool DXVA2Renderer::isDecoderBlacklisted()
|
|||||||
// TODO: Update for HEVC Main10
|
// TODO: Update for HEVC Main10
|
||||||
SDL_assert(m_VideoFormat != VIDEO_FORMAT_H265_MAIN10);
|
SDL_assert(m_VideoFormat != VIDEO_FORMAT_H265_MAIN10);
|
||||||
|
|
||||||
|
if (qgetenv("DXVA2_DISABLE_DECODER_BLACKLIST") == "1") {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"DXVA2 decoder blacklist is disabled");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
hr = m_Device->GetDirect3D(&d3d9);
|
hr = m_Device->GetDirect3D(&d3d9);
|
||||||
if (SUCCEEDED(hr)) {
|
if (SUCCEEDED(hr)) {
|
||||||
D3DCAPS9 caps;
|
D3DCAPS9 caps;
|
||||||
@@ -635,11 +649,11 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height, int, bool enableVsync)
|
bool DXVA2Renderer::initialize(PDECODER_PARAMETERS params)
|
||||||
{
|
{
|
||||||
m_VideoFormat = videoFormat;
|
m_VideoFormat = params->videoFormat;
|
||||||
m_VideoWidth = width;
|
m_VideoWidth = params->width;
|
||||||
m_VideoHeight = height;
|
m_VideoHeight = params->height;
|
||||||
|
|
||||||
RtlZeroMemory(&m_Desc, sizeof(m_Desc));
|
RtlZeroMemory(&m_Desc, sizeof(m_Desc));
|
||||||
|
|
||||||
@@ -668,7 +682,7 @@ bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, i
|
|||||||
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
||||||
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
|
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
|
||||||
|
|
||||||
if (!initializeDevice(window, enableVsync)) {
|
if (!initializeDevice(params->window, params->enableVsync)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,52 +699,66 @@ bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, i
|
|||||||
// input focus. This glitches out the renderer and a bunch of other stuff.
|
// input focus. This glitches out the renderer and a bunch of other stuff.
|
||||||
// Direct3D9Ex itself seems to have this minimize on focus loss behavior on its
|
// Direct3D9Ex itself seems to have this minimize on focus loss behavior on its
|
||||||
// own, so just disable SDL's handling of the focus loss event.
|
// own, so just disable SDL's handling of the focus loss event.
|
||||||
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
|
SDL_SetHintWithPriority(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0", SDL_HINT_OVERRIDE);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DXVA2Renderer::needsTestFrame()
|
void DXVA2Renderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
||||||
{
|
|
||||||
// We validate the DXVA2 profiles are supported
|
|
||||||
// in initialize() so no test frame is required
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int DXVA2Renderer::getDecoderCapabilities()
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
IFFmpegRenderer::FramePacingConstraint DXVA2Renderer::getFramePacingConstraint()
|
|
||||||
{
|
|
||||||
return PACING_ANY;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DXVA2Renderer::notifyOverlayUpdated(Overlay::OverlayType)
|
|
||||||
{
|
{
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
// Initialize the overlay font if it's not already created
|
switch (type)
|
||||||
if (m_DebugOverlayFont == nullptr) {
|
{
|
||||||
hr = D3DXCreateFontA(m_Device,
|
case Overlay::OverlayDebug:
|
||||||
Session::get()->getOverlayManager().getOverlayFontSize(Overlay::OverlayDebug),
|
if (m_DebugOverlayFont == nullptr) {
|
||||||
0,
|
hr = D3DXCreateFontA(m_Device,
|
||||||
FW_HEAVY,
|
Session::get()->getOverlayManager().getOverlayFontSize(Overlay::OverlayDebug),
|
||||||
1,
|
0,
|
||||||
false,
|
FW_HEAVY,
|
||||||
ANSI_CHARSET,
|
1,
|
||||||
OUT_DEFAULT_PRECIS,
|
false,
|
||||||
DEFAULT_QUALITY,
|
ANSI_CHARSET,
|
||||||
DEFAULT_PITCH | FF_DONTCARE,
|
OUT_DEFAULT_PRECIS,
|
||||||
"",
|
DEFAULT_QUALITY,
|
||||||
&m_DebugOverlayFont);
|
DEFAULT_PITCH | FF_DONTCARE,
|
||||||
if (FAILED(hr)) {
|
"",
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
&m_DebugOverlayFont);
|
||||||
"D3DXCreateFontA() failed: %x",
|
if (FAILED(hr)) {
|
||||||
hr);
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
m_DebugOverlayFont = nullptr;
|
"D3DXCreateFontA() failed: %x",
|
||||||
|
hr);
|
||||||
|
m_DebugOverlayFont = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Overlay::OverlayStatusUpdate:
|
||||||
|
if (m_StatusOverlayFont == nullptr) {
|
||||||
|
hr = D3DXCreateFontA(m_Device,
|
||||||
|
Session::get()->getOverlayManager().getOverlayFontSize(Overlay::OverlayStatusUpdate),
|
||||||
|
0,
|
||||||
|
FW_HEAVY,
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
ANSI_CHARSET,
|
||||||
|
OUT_DEFAULT_PRECIS,
|
||||||
|
DEFAULT_QUALITY,
|
||||||
|
DEFAULT_PITCH | FF_DONTCARE,
|
||||||
|
"",
|
||||||
|
&m_StatusOverlayFont);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"D3DXCreateFontA() failed: %x",
|
||||||
|
hr);
|
||||||
|
m_StatusOverlayFont = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
SDL_assert(false);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,6 +957,18 @@ void DXVA2Renderer::renderFrame(AVFrame *frame)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_StatusOverlayFont != nullptr) {
|
||||||
|
if (Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayStatusUpdate)) {
|
||||||
|
SDL_Color color = Session::get()->getOverlayManager().getOverlayColor(Overlay::OverlayStatusUpdate);
|
||||||
|
m_StatusOverlayFont->DrawTextA(nullptr,
|
||||||
|
Session::get()->getOverlayManager().getOverlayText(Overlay::OverlayStatusUpdate),
|
||||||
|
-1,
|
||||||
|
&sample.DstRect,
|
||||||
|
DT_RIGHT | DT_NOCLIP,
|
||||||
|
D3DCOLOR_ARGB(color.a, color.r, color.g, color.b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hr = m_Device->EndScene();
|
hr = m_Device->EndScene();
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
|||||||
@@ -15,18 +15,10 @@ class DXVA2Renderer : public IFFmpegRenderer
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DXVA2Renderer();
|
DXVA2Renderer();
|
||||||
virtual ~DXVA2Renderer();
|
virtual ~DXVA2Renderer() override;
|
||||||
virtual bool initialize(SDL_Window* window,
|
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||||
int videoFormat,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
int maxFps,
|
|
||||||
bool enableVsync) override;
|
|
||||||
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
||||||
virtual void renderFrame(AVFrame* frame) override;
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
virtual bool needsTestFrame() override;
|
|
||||||
virtual int getDecoderCapabilities() override;
|
|
||||||
virtual FramePacingConstraint getFramePacingConstraint() override;
|
|
||||||
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -71,5 +63,6 @@ private:
|
|||||||
DXVA2_VideoDesc m_Desc;
|
DXVA2_VideoDesc m_Desc;
|
||||||
REFERENCE_TIME m_FrameIndex;
|
REFERENCE_TIME m_FrameIndex;
|
||||||
LPD3DXFONT m_DebugOverlayFont;
|
LPD3DXFONT m_DebugOverlayFont;
|
||||||
|
LPD3DXFONT m_StatusOverlayFont;
|
||||||
bool m_BlockingPresent;
|
bool m_BlockingPresent;
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
#include "mmal.h"
|
||||||
|
|
||||||
|
#include "streaming/streamutils.h"
|
||||||
|
#include "streaming/session.h"
|
||||||
|
|
||||||
|
#include <Limelight.h>
|
||||||
|
|
||||||
|
MmalRenderer::MmalRenderer()
|
||||||
|
: m_Renderer(nullptr),
|
||||||
|
m_InputPort(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MmalRenderer::~MmalRenderer()
|
||||||
|
{
|
||||||
|
if (m_InputPort != nullptr) {
|
||||||
|
mmal_port_disable(m_InputPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Renderer != nullptr) {
|
||||||
|
mmal_component_destroy(m_Renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MmalRenderer::prepareDecoderContext(AVCodecContext*)
|
||||||
|
{
|
||||||
|
/* Nothing to do */
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Using MMAL renderer");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MmalRenderer::initialize(PDECODER_PARAMETERS params)
|
||||||
|
{
|
||||||
|
MMAL_STATUS_T status;
|
||||||
|
|
||||||
|
status = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &m_Renderer);
|
||||||
|
if (status != MMAL_SUCCESS) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"mmal_component_create() failed: %x (%s)",
|
||||||
|
status, mmal_status_to_string(status));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_InputPort = m_Renderer->input[0];
|
||||||
|
|
||||||
|
m_InputPort->format->encoding = MMAL_ENCODING_OPAQUE;
|
||||||
|
m_InputPort->format->es->video.width = params->width;
|
||||||
|
m_InputPort->format->es->video.height = params->height;
|
||||||
|
m_InputPort->format->es->video.crop.x = 0;
|
||||||
|
m_InputPort->format->es->video.crop.y = 0;
|
||||||
|
m_InputPort->format->es->video.crop.width = params->width;
|
||||||
|
m_InputPort->format->es->video.crop.height = params->height;
|
||||||
|
status = mmal_port_format_commit(m_InputPort);
|
||||||
|
if (status != MMAL_SUCCESS) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"mmal_port_format_commit() failed: %x (%s)",
|
||||||
|
status, mmal_status_to_string(status));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = mmal_component_enable(m_Renderer);
|
||||||
|
if (status != MMAL_SUCCESS) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"mmal_component_enable() failed: %x (%s)",
|
||||||
|
status, mmal_status_to_string(status));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MMAL_DISPLAYREGION_T dr;
|
||||||
|
|
||||||
|
dr.hdr.id = MMAL_PARAMETER_DISPLAYREGION;
|
||||||
|
dr.hdr.size = sizeof(MMAL_DISPLAYREGION_T);
|
||||||
|
|
||||||
|
dr.set = MMAL_DISPLAY_SET_LAYER;
|
||||||
|
dr.layer = 128;
|
||||||
|
|
||||||
|
dr.set |= MMAL_DISPLAY_SET_ALPHA;
|
||||||
|
dr.alpha = 255;
|
||||||
|
|
||||||
|
dr.set |= MMAL_DISPLAY_SET_FULLSCREEN;
|
||||||
|
dr.fullscreen = (SDL_GetWindowFlags(params->window) & SDL_WINDOW_FULLSCREEN) != 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
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_enable(m_InputPort, InputPortCallback);
|
||||||
|
if (status != MMAL_SUCCESS) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"mmal_port_enable() failed: %x (%s)",
|
||||||
|
status, mmal_status_to_string(status));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MmalRenderer::InputPortCallback(MMAL_PORT_T*, MMAL_BUFFER_HEADER_T* buffer)
|
||||||
|
{
|
||||||
|
mmal_buffer_header_release(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AVPixelFormat MmalRenderer::getPreferredPixelFormat(int videoFormat)
|
||||||
|
{
|
||||||
|
// Opaque MMAL buffers
|
||||||
|
SDL_assert(videoFormat == VIDEO_FORMAT_H264);
|
||||||
|
return AV_PIX_FMT_MMAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MmalRenderer::needsTestFrame()
|
||||||
|
{
|
||||||
|
// We won't be able to decode if the GPU memory is 64 MB or lower,
|
||||||
|
// so we must test before allowing the decoder to be used.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MmalRenderer::renderFrame(AVFrame* frame)
|
||||||
|
{
|
||||||
|
MMAL_BUFFER_HEADER_T* buffer = (MMAL_BUFFER_HEADER_T*)frame->data[3];
|
||||||
|
MMAL_STATUS_T status;
|
||||||
|
|
||||||
|
status = mmal_port_send_buffer(m_InputPort, buffer);
|
||||||
|
if (status != MMAL_SUCCESS) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"mmal_port_send_buffer() failed: %x (%s)",
|
||||||
|
status, mmal_status_to_string(status));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Prevent the buffer from being freed during av_frame_free()
|
||||||
|
// until rendering is complete. The reference is dropped in
|
||||||
|
// InputPortCallback().
|
||||||
|
mmal_buffer_header_acquire(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "renderer.h"
|
||||||
|
|
||||||
|
#include <interface/mmal/mmal.h>
|
||||||
|
#include <interface/mmal/util/mmal_util.h>
|
||||||
|
#include <interface/mmal/util/mmal_util_params.h>
|
||||||
|
#include <interface/mmal/util/mmal_default_components.h>
|
||||||
|
|
||||||
|
class MmalRenderer : public IFFmpegRenderer {
|
||||||
|
public:
|
||||||
|
MmalRenderer();
|
||||||
|
virtual ~MmalRenderer() override;
|
||||||
|
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||||
|
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
||||||
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
|
virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) override;
|
||||||
|
virtual bool needsTestFrame() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void InputPortCallback(MMAL_PORT_T* port, MMAL_BUFFER_HEADER_T* buffer);
|
||||||
|
|
||||||
|
MMAL_COMPONENT_T* m_Renderer;
|
||||||
|
MMAL_PORT_T* m_InputPort;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -76,7 +76,11 @@ int DxVsyncSource::vsyncThread(void* context)
|
|||||||
{
|
{
|
||||||
DxVsyncSource* me = reinterpret_cast<DxVsyncSource*>(context);
|
DxVsyncSource* me = reinterpret_cast<DxVsyncSource*>(context);
|
||||||
|
|
||||||
|
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
||||||
|
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL);
|
||||||
|
#else
|
||||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||||
|
#endif
|
||||||
|
|
||||||
D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {};
|
D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {};
|
||||||
HMONITOR lastMonitor = nullptr;
|
HMONITOR lastMonitor = nullptr;
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ int NullThreadedVsyncSource::vsyncThread(void* context)
|
|||||||
{
|
{
|
||||||
NullThreadedVsyncSource* me = reinterpret_cast<NullThreadedVsyncSource*>(context);
|
NullThreadedVsyncSource* me = reinterpret_cast<NullThreadedVsyncSource*>(context);
|
||||||
|
|
||||||
|
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
||||||
|
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL);
|
||||||
|
#else
|
||||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||||
|
#endif
|
||||||
|
|
||||||
while (SDL_AtomicGet(&me->m_Stopping) == 0) {
|
while (SDL_AtomicGet(&me->m_Stopping) == 0) {
|
||||||
me->m_Pacer->vsyncCallback(1000 / me->m_DisplayFps);
|
me->m_Pacer->vsyncCallback(1000 / me->m_DisplayFps);
|
||||||
|
|||||||
@@ -14,6 +14,10 @@
|
|||||||
#include "dxvsyncsource.h"
|
#include "dxvsyncsource.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Limit the number of queued frames to prevent excessive memory consumption
|
||||||
|
// if the V-Sync source or renderer is blocked for a while.
|
||||||
|
#define MAX_QUEUED_FRAMES 8
|
||||||
|
|
||||||
// We may be woken up slightly late so don't go all the way
|
// We may be woken up slightly late so don't go all the way
|
||||||
// up to the next V-sync since we may accidentally step into
|
// up to the next V-sync since we may accidentally step into
|
||||||
// the next V-sync period. It also takes some amount of time
|
// the next V-sync period. It also takes some amount of time
|
||||||
@@ -57,10 +61,34 @@ Pacer::~Pacer()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Pacer::renderOnMainThread()
|
||||||
|
{
|
||||||
|
// Ignore this call for renderers that work on a dedicated render thread
|
||||||
|
if (m_RenderThread != nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_FrameQueueLock.lock();
|
||||||
|
|
||||||
|
if (!m_RenderQueue.isEmpty()) {
|
||||||
|
// Releases m_FrameQueueLock
|
||||||
|
renderLastFrameAndUnlock();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_FrameQueueLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int Pacer::renderThread(void* context)
|
int Pacer::renderThread(void* context)
|
||||||
{
|
{
|
||||||
Pacer* me = reinterpret_cast<Pacer*>(context);
|
Pacer* me = reinterpret_cast<Pacer*>(context);
|
||||||
|
|
||||||
|
if (SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH) < 0) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Unable to set render thread to high priority: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
while (!me->m_Stopping) {
|
while (!me->m_Stopping) {
|
||||||
// Acquire the frame queue lock to protect the queue and
|
// Acquire the frame queue lock to protect the queue and
|
||||||
// the not empty condition
|
// the not empty condition
|
||||||
@@ -77,34 +105,61 @@ int Pacer::renderThread(void* context)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dequeue the most recent frame for rendering and free the others.
|
// Render the latest frame and discard the others
|
||||||
// NB: m_FrameQueueLock still held here!
|
// NB: m_FrameQueueLock still held here!
|
||||||
AVFrame* lastFrame = nullptr;
|
me->renderLastFrameAndUnlock();
|
||||||
while (!me->m_RenderQueue.isEmpty()) {
|
|
||||||
if (lastFrame != nullptr) {
|
|
||||||
// Don't hold the frame queue lock across av_frame_free(),
|
|
||||||
// since it could need to talk to the GPU driver. This is safe
|
|
||||||
// because we're guaranteed that the queue will not shrink during
|
|
||||||
// this time (and so dequeue() below will always get something).
|
|
||||||
me->m_FrameQueueLock.unlock();
|
|
||||||
av_frame_free(&lastFrame);
|
|
||||||
me->m_VideoStats->pacerDroppedFrames++;
|
|
||||||
me->m_FrameQueueLock.lock();
|
|
||||||
}
|
|
||||||
|
|
||||||
lastFrame = me->m_RenderQueue.dequeue();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release the frame queue lock before rendering
|
|
||||||
me->m_FrameQueueLock.unlock();
|
|
||||||
|
|
||||||
// Render and free the mot current frame
|
|
||||||
me->renderFrame(lastFrame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Pacer::enqueueFrameForRenderingAndUnlock(AVFrame *frame)
|
||||||
|
{
|
||||||
|
dropFrameForEnqueue(m_RenderQueue);
|
||||||
|
m_RenderQueue.enqueue(frame);
|
||||||
|
|
||||||
|
m_FrameQueueLock.unlock();
|
||||||
|
|
||||||
|
if (m_RenderThread != nullptr) {
|
||||||
|
m_RenderQueueNotEmpty.wakeOne();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_Event event;
|
||||||
|
|
||||||
|
// For main thread rendering, we'll push an event to trigger a callback
|
||||||
|
event.type = SDL_USEREVENT;
|
||||||
|
event.user.code = SDL_CODE_FRAME_READY;
|
||||||
|
SDL_PushEvent(&event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caller must hold m_FrameQueueLock
|
||||||
|
void Pacer::renderLastFrameAndUnlock()
|
||||||
|
{
|
||||||
|
// Dequeue the most recent frame for rendering and free the others.
|
||||||
|
AVFrame* lastFrame = nullptr;
|
||||||
|
while (!m_RenderQueue.isEmpty()) {
|
||||||
|
if (lastFrame != nullptr) {
|
||||||
|
// Don't hold the frame queue lock across av_frame_free(),
|
||||||
|
// since it could need to talk to the GPU driver. This is safe
|
||||||
|
// because we're guaranteed that the queue will not shrink during
|
||||||
|
// this time (and so dequeue() below will always get something).
|
||||||
|
m_FrameQueueLock.unlock();
|
||||||
|
av_frame_free(&lastFrame);
|
||||||
|
m_VideoStats->pacerDroppedFrames++;
|
||||||
|
m_FrameQueueLock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
lastFrame = m_RenderQueue.dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the frame queue lock before rendering
|
||||||
|
m_FrameQueueLock.unlock();
|
||||||
|
|
||||||
|
// Render and free the mot current frame
|
||||||
|
renderFrame(lastFrame);
|
||||||
|
}
|
||||||
|
|
||||||
// Called in an arbitrary thread by the IVsyncSource on V-sync
|
// Called in an arbitrary thread by the IVsyncSource on V-sync
|
||||||
// or an event synchronized with V-sync
|
// or an event synchronized with V-sync
|
||||||
void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
|
void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
|
||||||
@@ -162,9 +217,7 @@ void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Place the first frame on the render queue
|
// Place the first frame on the render queue
|
||||||
m_RenderQueue.enqueue(m_PacingQueue.dequeue());
|
enqueueFrameForRenderingAndUnlock(m_PacingQueue.dequeue());
|
||||||
m_FrameQueueLock.unlock();
|
|
||||||
m_RenderQueueNotEmpty.wakeOne();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
||||||
@@ -200,7 +253,9 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
|||||||
m_DisplayFps, m_MaxVideoFps);
|
m_DisplayFps, m_MaxVideoFps);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_RenderThread = SDL_CreateThread(Pacer::renderThread, "Pacer Render Thread", this);
|
if (m_VsyncRenderer->isRenderThreadSupported()) {
|
||||||
|
m_RenderThread = SDL_CreateThread(Pacer::renderThread, "Pacer Render Thread", this);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -253,6 +308,15 @@ void Pacer::renderFrame(AVFrame* frame)
|
|||||||
m_FrameQueueLock.unlock();
|
m_FrameQueueLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Pacer::dropFrameForEnqueue(QQueue<AVFrame*>& queue)
|
||||||
|
{
|
||||||
|
SDL_assert(queue.size() <= MAX_QUEUED_FRAMES);
|
||||||
|
if (queue.size() == MAX_QUEUED_FRAMES) {
|
||||||
|
AVFrame* frame = queue.dequeue();
|
||||||
|
av_frame_free(&frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Pacer::submitFrame(AVFrame* frame)
|
void Pacer::submitFrame(AVFrame* frame)
|
||||||
{
|
{
|
||||||
// Make sure initialize() has been called
|
// Make sure initialize() has been called
|
||||||
@@ -261,16 +325,12 @@ void Pacer::submitFrame(AVFrame* frame)
|
|||||||
// Queue the frame and possibly wake up the render thread
|
// Queue the frame and possibly wake up the render thread
|
||||||
m_FrameQueueLock.lock();
|
m_FrameQueueLock.lock();
|
||||||
if (m_VsyncSource != nullptr) {
|
if (m_VsyncSource != nullptr) {
|
||||||
|
dropFrameForEnqueue(m_PacingQueue);
|
||||||
m_PacingQueue.enqueue(frame);
|
m_PacingQueue.enqueue(frame);
|
||||||
}
|
m_FrameQueueLock.unlock();
|
||||||
else {
|
|
||||||
m_RenderQueue.enqueue(frame);
|
|
||||||
}
|
|
||||||
m_FrameQueueLock.unlock();
|
|
||||||
if (m_VsyncSource != nullptr) {
|
|
||||||
m_PacingQueueNotEmpty.wakeOne();
|
m_PacingQueueNotEmpty.wakeOne();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_RenderQueueNotEmpty.wakeOne();
|
enqueueFrameForRenderingAndUnlock(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,11 +26,19 @@ public:
|
|||||||
|
|
||||||
void vsyncCallback(int timeUntilNextVsyncMillis);
|
void vsyncCallback(int timeUntilNextVsyncMillis);
|
||||||
|
|
||||||
|
void renderOnMainThread();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static int renderThread(void* context);
|
static int renderThread(void* context);
|
||||||
|
|
||||||
|
void enqueueFrameForRenderingAndUnlock(AVFrame* frame);
|
||||||
|
|
||||||
|
void renderLastFrameAndUnlock();
|
||||||
|
|
||||||
void renderFrame(AVFrame* frame);
|
void renderFrame(AVFrame* frame);
|
||||||
|
|
||||||
|
void dropFrameForEnqueue(QQueue<AVFrame*>& queue);
|
||||||
|
|
||||||
QQueue<AVFrame*> m_RenderQueue;
|
QQueue<AVFrame*> m_RenderQueue;
|
||||||
QQueue<AVFrame*> m_PacingQueue;
|
QQueue<AVFrame*> m_PacingQueue;
|
||||||
QQueue<int> m_PacingQueueHistory;
|
QQueue<int> m_PacingQueueHistory;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
|
|
||||||
|
#include "streaming/video/decoder.h"
|
||||||
#include "streaming/video/overlaymanager.h"
|
#include "streaming/video/overlaymanager.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -16,17 +17,40 @@ public:
|
|||||||
PACING_ANY
|
PACING_ANY
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual bool initialize(SDL_Window* window,
|
virtual bool initialize(PDECODER_PARAMETERS params) = 0;
|
||||||
int videoFormat,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
int maxFps,
|
|
||||||
bool enableVsync) = 0;
|
|
||||||
virtual bool prepareDecoderContext(AVCodecContext* context) = 0;
|
virtual bool prepareDecoderContext(AVCodecContext* context) = 0;
|
||||||
virtual void renderFrame(AVFrame* frame) = 0;
|
virtual void renderFrame(AVFrame* frame) = 0;
|
||||||
virtual bool needsTestFrame() = 0;
|
|
||||||
virtual int getDecoderCapabilities() = 0;
|
virtual bool needsTestFrame() {
|
||||||
virtual FramePacingConstraint getFramePacingConstraint() = 0;
|
// No test frame required by default
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int getDecoderCapabilities() {
|
||||||
|
// No special capabilities by default
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual FramePacingConstraint getFramePacingConstraint() {
|
||||||
|
// No pacing preference
|
||||||
|
return PACING_ANY;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool isRenderThreadSupported() {
|
||||||
|
// Render thread is supported by default
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool isDirectRenderingSupported() {
|
||||||
|
// The renderer can render directly to the display
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) {
|
||||||
|
// Planar YUV 4:2:0
|
||||||
|
SDL_assert(videoFormat != VIDEO_FORMAT_H265_MAIN10);
|
||||||
|
return AV_PIX_FMT_YUV420P;
|
||||||
|
}
|
||||||
|
|
||||||
// IOverlayRenderer
|
// IOverlayRenderer
|
||||||
virtual void notifyOverlayUpdated(Overlay::OverlayType) override {
|
virtual void notifyOverlayUpdated(Overlay::OverlayType) override {
|
||||||
|
|||||||
@@ -7,12 +7,16 @@
|
|||||||
|
|
||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
|
|
||||||
|
const std::vector<int> SdlRenderer::k_SwFormats({
|
||||||
|
AV_PIX_FMT_YUV420P,
|
||||||
|
AV_PIX_FMT_NV12
|
||||||
|
});
|
||||||
|
|
||||||
SdlRenderer::SdlRenderer()
|
SdlRenderer::SdlRenderer()
|
||||||
: m_Renderer(nullptr),
|
: m_Renderer(nullptr),
|
||||||
m_Texture(nullptr),
|
m_Texture(nullptr),
|
||||||
m_DebugOverlayFont(nullptr),
|
m_SwPixelFormat(AV_PIX_FMT_YUV420P),
|
||||||
m_DebugOverlaySurface(nullptr),
|
m_FontData(Path::readDataFile("ModeSeven.ttf"))
|
||||||
m_DebugOverlayTexture(nullptr)
|
|
||||||
{
|
{
|
||||||
SDL_assert(TTF_WasInit() == 0);
|
SDL_assert(TTF_WasInit() == 0);
|
||||||
if (TTF_Init() != 0) {
|
if (TTF_Init() != 0) {
|
||||||
@@ -21,23 +25,33 @@ SdlRenderer::SdlRenderer()
|
|||||||
TTF_GetError());
|
TTF_GetError());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_zero(m_OverlayFonts);
|
||||||
|
SDL_zero(m_OverlaySurfaces);
|
||||||
|
SDL_zero(m_OverlayTextures);
|
||||||
}
|
}
|
||||||
|
|
||||||
SdlRenderer::~SdlRenderer()
|
SdlRenderer::~SdlRenderer()
|
||||||
{
|
{
|
||||||
if (m_DebugOverlayFont != nullptr) {
|
for (int i = 0; i < Overlay::OverlayMax; i++) {
|
||||||
TTF_CloseFont(m_DebugOverlayFont);
|
if (m_OverlayFonts[i] != nullptr) {
|
||||||
|
TTF_CloseFont(m_OverlayFonts[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TTF_Quit();
|
TTF_Quit();
|
||||||
SDL_assert(TTF_WasInit() == 0);
|
SDL_assert(TTF_WasInit() == 0);
|
||||||
|
|
||||||
if (m_DebugOverlayTexture != nullptr) {
|
for (int i = 0; i < Overlay::OverlayMax; i++) {
|
||||||
SDL_DestroyTexture(m_DebugOverlayTexture);
|
if (m_OverlayTextures[i] != nullptr) {
|
||||||
|
SDL_DestroyTexture(m_OverlayTextures[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_DebugOverlaySurface != nullptr) {
|
for (int i = 0; i < Overlay::OverlayMax; i++) {
|
||||||
SDL_FreeSurface(m_DebugOverlaySurface);
|
if (m_OverlaySurfaces[i] != nullptr) {
|
||||||
|
SDL_FreeSurface(m_OverlaySurfaces[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_Texture != nullptr) {
|
if (m_Texture != nullptr) {
|
||||||
@@ -54,17 +68,11 @@ bool SdlRenderer::prepareDecoderContext(AVCodecContext*)
|
|||||||
/* Nothing to do */
|
/* Nothing to do */
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Using SDL software renderer");
|
"Using SDL renderer");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SdlRenderer::needsTestFrame()
|
|
||||||
{
|
|
||||||
// This renderer should always work
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SdlRenderer::getDecoderCapabilities()
|
int SdlRenderer::getDecoderCapabilities()
|
||||||
{
|
{
|
||||||
// The FFmpeg CPU decoder can handle reference frame invalidation,
|
// The FFmpeg CPU decoder can handle reference frame invalidation,
|
||||||
@@ -72,74 +80,87 @@ int SdlRenderer::getDecoderCapabilities()
|
|||||||
return CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC;
|
return CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC;
|
||||||
}
|
}
|
||||||
|
|
||||||
IFFmpegRenderer::FramePacingConstraint SdlRenderer::getFramePacingConstraint()
|
|
||||||
{
|
|
||||||
return PACING_ANY;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SdlRenderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
void SdlRenderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
||||||
{
|
{
|
||||||
if (type == Overlay::OverlayDebug) {
|
// Construct the required font to render the overlay
|
||||||
if (m_DebugOverlayFont == nullptr) {
|
if (m_OverlayFonts[type] == nullptr) {
|
||||||
QByteArray fontPath = QDir::toNativeSeparators(Path::getDataFilePath("ModeSeven.ttf")).toUtf8();
|
if (m_FontData.isEmpty()) {
|
||||||
if (fontPath.isEmpty()) {
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
"SDL overlay font failed to load");
|
||||||
"Unable to locate SDL overlay font");
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_DebugOverlayFont = TTF_OpenFont(fontPath.data(),
|
|
||||||
Session::get()->getOverlayManager().getOverlayFontSize(Overlay::OverlayDebug));
|
|
||||||
if (m_DebugOverlayFont == nullptr) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"TTF_OpenFont() failed: %s",
|
|
||||||
TTF_GetError());
|
|
||||||
|
|
||||||
// Can't proceed without a font
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Surface* oldSurface = (SDL_Surface*)SDL_AtomicGetPtr((void**)&m_DebugOverlaySurface);
|
// m_FontData must stay around until the font is closed
|
||||||
|
m_OverlayFonts[type] = TTF_OpenFontRW(SDL_RWFromConstMem(m_FontData.constData(), m_FontData.size()),
|
||||||
|
1,
|
||||||
|
Session::get()->getOverlayManager().getOverlayFontSize(type));
|
||||||
|
if (m_OverlayFonts[type] == nullptr) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"TTF_OpenFont() failed: %s",
|
||||||
|
TTF_GetError());
|
||||||
|
|
||||||
// Free the old surface
|
// Can't proceed without a font
|
||||||
if (oldSurface != nullptr && SDL_AtomicCASPtr((void**)&m_DebugOverlaySurface, oldSurface, nullptr)) {
|
return;
|
||||||
SDL_FreeSurface(oldSurface);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Session::get()->getOverlayManager().isOverlayEnabled(type)) {
|
|
||||||
// The _Wrapped variant is required for line breaks to work
|
|
||||||
SDL_Surface* surface = TTF_RenderText_Blended_Wrapped(m_DebugOverlayFont,
|
|
||||||
Session::get()->getOverlayManager().getOverlayText(type),
|
|
||||||
Session::get()->getOverlayManager().getOverlayColor(type),
|
|
||||||
1000);
|
|
||||||
SDL_AtomicSetPtr((void**)&m_DebugOverlaySurface, surface);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
SDL_assert(false);
|
SDL_Surface* oldSurface = (SDL_Surface*)SDL_AtomicGetPtr((void**)&m_OverlaySurfaces[type]);
|
||||||
|
|
||||||
|
// Free the old surface
|
||||||
|
if (oldSurface != nullptr && SDL_AtomicCASPtr((void**)&m_OverlaySurfaces[type], oldSurface, nullptr)) {
|
||||||
|
SDL_FreeSurface(oldSurface);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Session::get()->getOverlayManager().isOverlayEnabled(type)) {
|
||||||
|
// The _Wrapped variant is required for line breaks to work
|
||||||
|
SDL_Surface* surface = TTF_RenderText_Blended_Wrapped(m_OverlayFonts[type],
|
||||||
|
Session::get()->getOverlayManager().getOverlayText(type),
|
||||||
|
Session::get()->getOverlayManager().getOverlayColor(type),
|
||||||
|
1000);
|
||||||
|
SDL_AtomicSetPtr((void**)&m_OverlaySurfaces[type], surface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SdlRenderer::initialize(SDL_Window* window,
|
bool SdlRenderer::isRenderThreadSupported()
|
||||||
int,
|
{
|
||||||
int width,
|
SDL_RendererInfo info;
|
||||||
int height,
|
SDL_GetRendererInfo(m_Renderer, &info);
|
||||||
int,
|
|
||||||
bool enableVsync)
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL renderer backend: %s",
|
||||||
|
info.name);
|
||||||
|
|
||||||
|
if (info.name != QString("direct3d")) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL renderer backend requires main thread rendering");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SdlRenderer::initialize(PDECODER_PARAMETERS params)
|
||||||
{
|
{
|
||||||
Uint32 rendererFlags = SDL_RENDERER_ACCELERATED;
|
Uint32 rendererFlags = SDL_RENDERER_ACCELERATED;
|
||||||
|
|
||||||
if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
if ((SDL_GetWindowFlags(params->window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
||||||
// In full-screen exclusive mode, we enable V-sync if requested. For other modes, Windows and Mac
|
// In full-screen exclusive mode, we enable V-sync if requested. For other modes, Windows and Mac
|
||||||
// have compositors that make rendering tear-free. Linux compositor varies by distro and user
|
// have compositors that make rendering tear-free. Linux compositor varies by distro and user
|
||||||
// configuration but doesn't seem feasible to detect here.
|
// configuration but doesn't seem feasible to detect here.
|
||||||
if (enableVsync) {
|
if (params->enableVsync) {
|
||||||
rendererFlags |= SDL_RENDERER_PRESENTVSYNC;
|
rendererFlags |= SDL_RENDERER_PRESENTVSYNC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Renderer = SDL_CreateRenderer(window, -1, rendererFlags);
|
#ifdef Q_OS_WIN32
|
||||||
|
// We render on a different thread than the main thread which is handling window
|
||||||
|
// messages. Without D3DCREATE_MULTITHREADED, this can cause a deadlock by blocking
|
||||||
|
// on a window message being processed while the main thread is blocked waiting for
|
||||||
|
// the render thread to finish.
|
||||||
|
SDL_SetHintWithPriority(SDL_HINT_RENDER_DIRECT3D_THREADSAFE, "1", SDL_HINT_OVERRIDE);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_Renderer = SDL_CreateRenderer(params->window, -1, rendererFlags);
|
||||||
if (!m_Renderer) {
|
if (!m_Renderer) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_CreateRenderer() failed: %s",
|
"SDL_CreateRenderer() failed: %s",
|
||||||
@@ -149,37 +170,169 @@ bool SdlRenderer::initialize(SDL_Window* window,
|
|||||||
|
|
||||||
// The window may be smaller than the stream size, so ensure our
|
// The window may be smaller than the stream size, so ensure our
|
||||||
// logical rendering surface size is equal to the stream size
|
// logical rendering surface size is equal to the stream size
|
||||||
SDL_RenderSetLogicalSize(m_Renderer, width, height);
|
SDL_RenderSetLogicalSize(m_Renderer, params->width, params->height);
|
||||||
|
|
||||||
// Draw a black frame until the video stream starts rendering
|
// Draw a black frame until the video stream starts rendering
|
||||||
SDL_SetRenderDrawColor(m_Renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
|
SDL_SetRenderDrawColor(m_Renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
|
||||||
SDL_RenderClear(m_Renderer);
|
SDL_RenderClear(m_Renderer);
|
||||||
SDL_RenderPresent(m_Renderer);
|
SDL_RenderPresent(m_Renderer);
|
||||||
|
|
||||||
m_Texture = SDL_CreateTexture(m_Renderer,
|
#ifdef Q_OS_WIN32
|
||||||
SDL_PIXELFORMAT_YV12,
|
// For some reason, using Direct3D9Ex breaks this with multi-monitor setups.
|
||||||
SDL_TEXTUREACCESS_STREAMING,
|
// When focus is lost, the window is minimized then immediately restored without
|
||||||
width,
|
// input focus. This glitches out the renderer and a bunch of other stuff.
|
||||||
height);
|
// Direct3D9Ex itself seems to have this minimize on focus loss behavior on its
|
||||||
if (!m_Texture) {
|
// own, so just disable SDL's handling of the focus loss event.
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_SetHintWithPriority(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0", SDL_HINT_OVERRIDE);
|
||||||
"SDL_CreateRenderer() failed: %s",
|
#endif
|
||||||
SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SdlRenderer::renderOverlay(Overlay::OverlayType type)
|
||||||
|
{
|
||||||
|
if (Session::get()->getOverlayManager().isOverlayEnabled(type)) {
|
||||||
|
// If a new surface has been created for updated overlay data, convert it into a texture.
|
||||||
|
// NB: We have to do this conversion at render-time because we can only interact
|
||||||
|
// with the renderer on a single thread.
|
||||||
|
SDL_Surface* surface = (SDL_Surface*)SDL_AtomicGetPtr((void**)&m_OverlaySurfaces[type]);
|
||||||
|
if (surface != nullptr && SDL_AtomicCASPtr((void**)&m_OverlaySurfaces[type], surface, nullptr)) {
|
||||||
|
if (m_OverlayTextures[type] != nullptr) {
|
||||||
|
SDL_DestroyTexture(m_OverlayTextures[type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == Overlay::OverlayStatusUpdate) {
|
||||||
|
// Bottom Left
|
||||||
|
int unused;
|
||||||
|
SDL_RenderGetLogicalSize(m_Renderer, &unused, &m_OverlayRects[type].y);
|
||||||
|
m_OverlayRects[type].x = 0;
|
||||||
|
m_OverlayRects[type].y -= surface->h;
|
||||||
|
}
|
||||||
|
else if (type == Overlay::OverlayDebug) {
|
||||||
|
// Top left
|
||||||
|
m_OverlayRects[type].x = 0;
|
||||||
|
m_OverlayRects[type].y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_OverlayRects[type].w = surface->w;
|
||||||
|
m_OverlayRects[type].h = surface->h;
|
||||||
|
|
||||||
|
m_OverlayTextures[type] = SDL_CreateTextureFromSurface(m_Renderer, surface);
|
||||||
|
SDL_FreeSurface(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have an overlay texture, render it too
|
||||||
|
if (m_OverlayTextures[type] != nullptr) {
|
||||||
|
SDL_RenderCopy(m_Renderer, m_OverlayTextures[type], nullptr, &m_OverlayRects[type]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SdlRenderer::renderFrame(AVFrame* frame)
|
void SdlRenderer::renderFrame(AVFrame* frame)
|
||||||
{
|
{
|
||||||
SDL_UpdateYUVTexture(m_Texture, nullptr,
|
int err;
|
||||||
frame->data[0],
|
AVFrame* swFrame = nullptr;
|
||||||
frame->linesize[0],
|
|
||||||
frame->data[1],
|
if (frame->hw_frames_ctx != nullptr) {
|
||||||
frame->linesize[1],
|
// If we are acting as the frontend for a hardware
|
||||||
frame->data[2],
|
// accelerated decoder, we'll need to read the frame
|
||||||
frame->linesize[2]);
|
// back to render it.
|
||||||
|
|
||||||
|
swFrame = av_frame_alloc();
|
||||||
|
if (swFrame == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
swFrame->width = frame->width;
|
||||||
|
swFrame->height = frame->height;
|
||||||
|
swFrame->format = m_SwPixelFormat;
|
||||||
|
|
||||||
|
err = av_hwframe_transfer_data(swFrame, frame, 0);
|
||||||
|
if (err != 0) {
|
||||||
|
// Try to find a supported format
|
||||||
|
for (uint32_t i = 0; i < k_SwFormats.size(); i++) {
|
||||||
|
swFrame->format = k_SwFormats.at(i);
|
||||||
|
err = av_hwframe_transfer_data(swFrame, frame, 0);
|
||||||
|
if (err == 0) {
|
||||||
|
// The format was supported. Use that automatically in the future.
|
||||||
|
m_SwPixelFormat = swFrame->format;
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Selected read-back format: %d",
|
||||||
|
m_SwPixelFormat);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"av_hwframe_transfer_data() failed: %d",
|
||||||
|
err);
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame = swFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Texture == nullptr) {
|
||||||
|
Uint32 sdlFormat;
|
||||||
|
|
||||||
|
switch (frame->format)
|
||||||
|
{
|
||||||
|
case AV_PIX_FMT_YUV420P:
|
||||||
|
sdlFormat = SDL_PIXELFORMAT_YV12;
|
||||||
|
break;
|
||||||
|
case AV_PIX_FMT_NV12:
|
||||||
|
sdlFormat = SDL_PIXELFORMAT_NV12;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SDL_assert(false);
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Texture = SDL_CreateTexture(m_Renderer,
|
||||||
|
sdlFormat,
|
||||||
|
SDL_TEXTUREACCESS_STREAMING,
|
||||||
|
frame->width,
|
||||||
|
frame->height);
|
||||||
|
if (!m_Texture) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL_CreateTexture() failed: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame->format == AV_PIX_FMT_YUV420P) {
|
||||||
|
SDL_UpdateYUVTexture(m_Texture, nullptr,
|
||||||
|
frame->data[0],
|
||||||
|
frame->linesize[0],
|
||||||
|
frame->data[1],
|
||||||
|
frame->linesize[1],
|
||||||
|
frame->data[2],
|
||||||
|
frame->linesize[2]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
char* pixels;
|
||||||
|
int pitch;
|
||||||
|
|
||||||
|
err = SDL_LockTexture(m_Texture, nullptr, (void**)&pixels, &pitch);
|
||||||
|
if (err < 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL_LockTexture() failed: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(pixels,
|
||||||
|
frame->data[0],
|
||||||
|
frame->linesize[0] * frame->height);
|
||||||
|
memcpy(pixels + (frame->linesize[0] * frame->height),
|
||||||
|
frame->data[1],
|
||||||
|
frame->linesize[1] * frame->height / 2);
|
||||||
|
|
||||||
|
SDL_UnlockTexture(m_Texture);
|
||||||
|
}
|
||||||
|
|
||||||
SDL_RenderClear(m_Renderer);
|
SDL_RenderClear(m_Renderer);
|
||||||
|
|
||||||
@@ -187,30 +340,14 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
|||||||
SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr);
|
SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr);
|
||||||
|
|
||||||
// Draw the overlays
|
// Draw the overlays
|
||||||
if (Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebug)) {
|
for (int i = 0; i < Overlay::OverlayMax; i++) {
|
||||||
// If a new surface has been created for updated overlay data, convert it into a texture.
|
renderOverlay((Overlay::OverlayType)i);
|
||||||
// NB: We have to do this conversion at render-time because we can only interact
|
|
||||||
// with the renderer on a single thread.
|
|
||||||
SDL_Surface* surface = (SDL_Surface*)SDL_AtomicGetPtr((void**)&m_DebugOverlaySurface);
|
|
||||||
if (surface != nullptr && SDL_AtomicCASPtr((void**)&m_DebugOverlaySurface, surface, nullptr)) {
|
|
||||||
if (m_DebugOverlayTexture != nullptr) {
|
|
||||||
SDL_DestroyTexture(m_DebugOverlayTexture);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_DebugOverlayRect.x = 0;
|
|
||||||
m_DebugOverlayRect.y = 0;
|
|
||||||
m_DebugOverlayRect.w = surface->w;
|
|
||||||
m_DebugOverlayRect.h = surface->h;
|
|
||||||
|
|
||||||
m_DebugOverlayTexture = SDL_CreateTextureFromSurface(m_Renderer, surface);
|
|
||||||
SDL_FreeSurface(surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a debug overlay texture, render it too
|
|
||||||
if (m_DebugOverlayTexture != nullptr) {
|
|
||||||
SDL_RenderCopy(m_Renderer, m_DebugOverlayTexture, nullptr, &m_DebugOverlayRect);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_RenderPresent(m_Renderer);
|
SDL_RenderPresent(m_Renderer);
|
||||||
|
|
||||||
|
Exit:
|
||||||
|
if (swFrame != nullptr) {
|
||||||
|
av_frame_free(&swFrame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,25 +8,25 @@ class SdlRenderer : public IFFmpegRenderer {
|
|||||||
public:
|
public:
|
||||||
SdlRenderer();
|
SdlRenderer();
|
||||||
virtual ~SdlRenderer() override;
|
virtual ~SdlRenderer() override;
|
||||||
virtual bool initialize(SDL_Window* window,
|
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||||
int videoFormat,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
int maxFps,
|
|
||||||
bool enableVsync) override;
|
|
||||||
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
||||||
virtual void renderFrame(AVFrame* frame) override;
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
virtual bool needsTestFrame() override;
|
|
||||||
virtual int getDecoderCapabilities() override;
|
virtual int getDecoderCapabilities() override;
|
||||||
virtual FramePacingConstraint getFramePacingConstraint() override;
|
|
||||||
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
||||||
|
virtual bool isRenderThreadSupported() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void renderOverlay(Overlay::OverlayType type);
|
||||||
|
|
||||||
SDL_Renderer* m_Renderer;
|
SDL_Renderer* m_Renderer;
|
||||||
SDL_Texture* m_Texture;
|
SDL_Texture* m_Texture;
|
||||||
TTF_Font* m_DebugOverlayFont;
|
int m_SwPixelFormat;
|
||||||
SDL_Surface* m_DebugOverlaySurface;
|
QByteArray m_FontData;
|
||||||
SDL_Texture* m_DebugOverlayTexture;
|
TTF_Font* m_OverlayFonts[Overlay::OverlayMax];
|
||||||
SDL_Rect m_DebugOverlayRect;
|
SDL_Surface* m_OverlaySurfaces[Overlay::OverlayMax];
|
||||||
|
SDL_Texture* m_OverlayTextures[Overlay::OverlayMax];
|
||||||
|
SDL_Rect m_OverlayRects[Overlay::OverlayMax];
|
||||||
|
|
||||||
|
static const std::vector<int> k_SwFormats;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,19 +29,19 @@ VAAPIRenderer::~VAAPIRenderer()
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height, int, bool)
|
VAAPIRenderer::initialize(PDECODER_PARAMETERS params)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
SDL_SysWMinfo info;
|
SDL_SysWMinfo info;
|
||||||
|
|
||||||
m_VideoWidth = width;
|
m_VideoWidth = params->width;
|
||||||
m_VideoHeight = height;
|
m_VideoHeight = params->height;
|
||||||
|
|
||||||
SDL_GetWindowSize(window, &m_DisplayWidth, &m_DisplayHeight);
|
SDL_GetWindowSize(params->window, &m_DisplayWidth, &m_DisplayHeight);
|
||||||
|
|
||||||
SDL_VERSION(&info.version);
|
SDL_VERSION(&info.version);
|
||||||
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
if (!SDL_GetWindowWMInfo(params->window, &info)) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
"SDL_GetWindowWMInfo() failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
@@ -76,8 +76,6 @@ VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height, int, b
|
|||||||
}
|
}
|
||||||
else if (info.subsystem == SDL_SYSWM_WAYLAND) {
|
else if (info.subsystem == SDL_SYSWM_WAYLAND) {
|
||||||
#ifdef HAVE_LIBVA_WAYLAND
|
#ifdef HAVE_LIBVA_WAYLAND
|
||||||
m_WaylandSurface = info.info.wl.surface;
|
|
||||||
m_WaylandDisplay = info.info.wl.display;
|
|
||||||
vaDeviceContext->display = vaGetDisplayWl(info.info.wl.display);
|
vaDeviceContext->display = vaGetDisplayWl(info.info.wl.display);
|
||||||
if (!vaDeviceContext->display) {
|
if (!vaDeviceContext->display) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -116,11 +114,12 @@ VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height, int, b
|
|||||||
"Driver: %s",
|
"Driver: %s",
|
||||||
vendorString ? vendorString : "<unknown>");
|
vendorString ? vendorString : "<unknown>");
|
||||||
|
|
||||||
// AMD's Gallium VAAPI driver has a nasty memory leak
|
// AMD's Gallium VAAPI driver has a nasty memory leak that causes memory
|
||||||
// that causes memory to be leaked for each submitted frame.
|
// to be leaked for each submitted frame. The Flatpak runtime has a VDPAU
|
||||||
// The Flatpak runtime has a VDPAU driver in place that works
|
// driver in place that works well, so use that instead on AMD systems. If
|
||||||
// well, so use that instead on AMD systems.
|
// we're using Wayland, we have no choice but to use VAAPI because VDPAU
|
||||||
if (vendorString && qgetenv("FORCE_VAAPI") != "1") {
|
// is only supported under X11.
|
||||||
|
if (vendorString && qgetenv("FORCE_VAAPI") != "1" && m_WindowSystem == SDL_SYSWM_X11) {
|
||||||
QString vendorStr(vendorString);
|
QString vendorStr(vendorString);
|
||||||
if (vendorStr.contains("AMD", Qt::CaseInsensitive) ||
|
if (vendorStr.contains("AMD", Qt::CaseInsensitive) ||
|
||||||
vendorStr.contains("Radeon", Qt::CaseInsensitive)) {
|
vendorStr.contains("Radeon", Qt::CaseInsensitive)) {
|
||||||
@@ -171,15 +170,12 @@ VAAPIRenderer::needsTestFrame()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
VAAPIRenderer::getDecoderCapabilities()
|
VAAPIRenderer::isDirectRenderingSupported()
|
||||||
{
|
{
|
||||||
return 0;
|
// Many Wayland renderers don't support YUV surfaces, so use
|
||||||
}
|
// another frontend renderer to draw our frames.
|
||||||
|
return m_WindowSystem == SDL_SYSWM_X11;
|
||||||
IFFmpegRenderer::FramePacingConstraint VAAPIRenderer::getFramePacingConstraint()
|
|
||||||
{
|
|
||||||
return PACING_ANY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -212,27 +208,10 @@ VAAPIRenderer::renderFrame(AVFrame* frame)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else if (m_WindowSystem == SDL_SYSWM_WAYLAND) {
|
else if (m_WindowSystem == SDL_SYSWM_WAYLAND) {
|
||||||
#ifdef HAVE_LIBVA_WAYLAND
|
// We don't support direct rendering on Wayland, so we should
|
||||||
struct wl_buffer* buffer;
|
// never get called there. Many common Wayland compositors don't
|
||||||
VAStatus status;
|
// support YUV surfaces, so direct rendering would fail.
|
||||||
|
SDL_assert(false);
|
||||||
status = vaGetSurfaceBufferWl(vaDeviceContext->display,
|
|
||||||
surface,
|
|
||||||
VA_FRAME_PICTURE,
|
|
||||||
&buffer);
|
|
||||||
if (status == VA_STATUS_SUCCESS) {
|
|
||||||
wl_surface_attach(m_WaylandSurface, buffer, 0, 0);
|
|
||||||
wl_surface_damage(m_WaylandSurface, dst.x, dst.y, dst.w, dst.h);
|
|
||||||
|
|
||||||
wl_display_flush(m_WaylandDisplay);
|
|
||||||
wl_surface_commit(m_WaylandSurface);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"vaGetSurfaceBufferWl failed(): %d",
|
|
||||||
status);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// We don't accept anything else in initialize().
|
// We don't accept anything else in initialize().
|
||||||
|
|||||||
@@ -29,18 +29,12 @@ class VAAPIRenderer : public IFFmpegRenderer
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
VAAPIRenderer();
|
VAAPIRenderer();
|
||||||
virtual ~VAAPIRenderer();
|
virtual ~VAAPIRenderer() override;
|
||||||
virtual bool initialize(SDL_Window* window,
|
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||||
int videoFormat,
|
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
||||||
int width,
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
int height,
|
virtual bool needsTestFrame() override;
|
||||||
int maxFps,
|
virtual bool isDirectRenderingSupported() override;
|
||||||
bool enableVsync);
|
|
||||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
|
||||||
virtual void renderFrame(AVFrame* frame);
|
|
||||||
virtual bool needsTestFrame();
|
|
||||||
virtual int getDecoderCapabilities();
|
|
||||||
virtual FramePacingConstraint getFramePacingConstraint();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_WindowSystem;
|
int m_WindowSystem;
|
||||||
@@ -50,11 +44,6 @@ private:
|
|||||||
Window m_XWindow;
|
Window m_XWindow;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_LIBVA_WAYLAND
|
|
||||||
struct wl_surface* m_WaylandSurface;
|
|
||||||
struct wl_display* m_WaylandDisplay;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int m_VideoWidth;
|
int m_VideoWidth;
|
||||||
int m_VideoHeight;
|
int m_VideoHeight;
|
||||||
int m_DisplayWidth;
|
int m_DisplayWidth;
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ VDPAURenderer::~VDPAURenderer()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VDPAURenderer::initialize(SDL_Window* window, int, int width, int height, int, bool)
|
bool VDPAURenderer::initialize(PDECODER_PARAMETERS params)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
VdpStatus status;
|
VdpStatus status;
|
||||||
SDL_SysWMinfo info;
|
SDL_SysWMinfo info;
|
||||||
|
|
||||||
m_VideoWidth = width;
|
m_VideoWidth = params->width;
|
||||||
m_VideoHeight = height;
|
m_VideoHeight = params->height;
|
||||||
|
|
||||||
err = av_hwdevice_ctx_create(&m_HwContext,
|
err = av_hwdevice_ctx_create(&m_HwContext,
|
||||||
AV_HWDEVICE_TYPE_VDPAU,
|
AV_HWDEVICE_TYPE_VDPAU,
|
||||||
@@ -93,11 +93,11 @@ bool VDPAURenderer::initialize(SDL_Window* window, int, int width, int height, i
|
|||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_SURFACE_GET_PARAMETERS, &m_VdpVideoSurfaceGetParameters);
|
GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_SURFACE_GET_PARAMETERS, &m_VdpVideoSurfaceGetParameters);
|
||||||
GET_PROC_ADDRESS(VDP_FUNC_ID_GET_INFORMATION_STRING, &m_VdpGetInformationString);
|
GET_PROC_ADDRESS(VDP_FUNC_ID_GET_INFORMATION_STRING, &m_VdpGetInformationString);
|
||||||
|
|
||||||
SDL_GetWindowSize(window, (int*)&m_DisplayWidth, (int*)&m_DisplayHeight);
|
SDL_GetWindowSize(params->window, (int*)&m_DisplayWidth, (int*)&m_DisplayHeight);
|
||||||
|
|
||||||
SDL_VERSION(&info.version);
|
SDL_VERSION(&info.version);
|
||||||
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
if (!SDL_GetWindowWMInfo(params->window, &info)) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
"SDL_GetWindowWMInfo() failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
@@ -241,16 +241,6 @@ bool VDPAURenderer::needsTestFrame()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int VDPAURenderer::getDecoderCapabilities()
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
IFFmpegRenderer::FramePacingConstraint VDPAURenderer::getFramePacingConstraint()
|
|
||||||
{
|
|
||||||
return PACING_ANY;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VDPAURenderer::renderFrame(AVFrame* frame)
|
void VDPAURenderer::renderFrame(AVFrame* frame)
|
||||||
{
|
{
|
||||||
VdpStatus status;
|
VdpStatus status;
|
||||||
|
|||||||
@@ -12,18 +12,11 @@ class VDPAURenderer : public IFFmpegRenderer
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
VDPAURenderer();
|
VDPAURenderer();
|
||||||
virtual ~VDPAURenderer();
|
virtual ~VDPAURenderer() override;
|
||||||
virtual bool initialize(SDL_Window* window,
|
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||||
int videoFormat,
|
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
||||||
int width,
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
int height,
|
virtual bool needsTestFrame() override;
|
||||||
int maxFps,
|
|
||||||
bool enableVsync);
|
|
||||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
|
||||||
virtual void renderFrame(AVFrame* frame);
|
|
||||||
virtual bool needsTestFrame();
|
|
||||||
virtual int getDecoderCapabilities();
|
|
||||||
virtual FramePacingConstraint getFramePacingConstraint();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t m_VideoWidth, m_VideoHeight;
|
uint32_t m_VideoWidth, m_VideoHeight;
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ public:
|
|||||||
: m_HwContext(nullptr),
|
: m_HwContext(nullptr),
|
||||||
m_DisplayLayer(nullptr),
|
m_DisplayLayer(nullptr),
|
||||||
m_FormatDesc(nullptr),
|
m_FormatDesc(nullptr),
|
||||||
m_StreamView(nullptr),
|
m_StreamView(nullptr)
|
||||||
m_DebugOverlayTextField(nullptr)
|
|
||||||
{
|
{
|
||||||
|
SDL_zero(m_OverlayTextFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~VTRenderer()
|
virtual ~VTRenderer() override
|
||||||
{
|
{
|
||||||
if (m_HwContext != nullptr) {
|
if (m_HwContext != nullptr) {
|
||||||
av_buffer_unref(&m_HwContext);
|
av_buffer_unref(&m_HwContext);
|
||||||
@@ -37,8 +37,10 @@ public:
|
|||||||
CFRelease(m_FormatDesc);
|
CFRelease(m_FormatDesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_DebugOverlayTextField != nullptr) {
|
for (int i = 0; i < Overlay::OverlayMax; i++) {
|
||||||
[m_DebugOverlayTextField removeFromSuperview];
|
if (m_OverlayTextFields[i] != nullptr) {
|
||||||
|
[m_OverlayTextFields[i] removeFromSuperview];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_StreamView != nullptr) {
|
if (m_StreamView != nullptr) {
|
||||||
@@ -104,16 +106,11 @@ public:
|
|||||||
CFRelease(sampleBuffer);
|
CFRelease(sampleBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool initialize(SDL_Window* window,
|
virtual bool initialize(PDECODER_PARAMETERS params) override
|
||||||
int videoFormat,
|
|
||||||
int,
|
|
||||||
int,
|
|
||||||
int,
|
|
||||||
bool) override
|
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||||
// Prior to 10.13, we'll just assume everything has
|
// Prior to 10.13, we'll just assume everything has
|
||||||
// H.264 support and fail open to allow VT decode.
|
// H.264 support and fail open to allow VT decode.
|
||||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
|
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
|
||||||
@@ -131,7 +128,7 @@ public:
|
|||||||
"Assuming H.264 HW decode on < macOS 10.13");
|
"Assuming H.264 HW decode on < macOS 10.13");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
else if (params->videoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
|
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
|
||||||
if (__builtin_available(macOS 10.13, *)) {
|
if (__builtin_available(macOS 10.13, *)) {
|
||||||
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) {
|
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) {
|
||||||
@@ -154,7 +151,7 @@ public:
|
|||||||
|
|
||||||
SDL_VERSION(&info.version);
|
SDL_VERSION(&info.version);
|
||||||
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
if (!SDL_GetWindowWMInfo(params->window, &info)) {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
"SDL_GetWindowWMInfo() failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
@@ -197,38 +194,68 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateOverlayOnMainThread(Overlay::OverlayType type)
|
||||||
|
{
|
||||||
|
// Lazy initialization for the overlay
|
||||||
|
if (m_OverlayTextFields[type] == nullptr) {
|
||||||
|
m_OverlayTextFields[type] = [[NSTextField alloc] initWithFrame:m_StreamView.bounds];
|
||||||
|
[m_OverlayTextFields[type] setBezeled:NO];
|
||||||
|
[m_OverlayTextFields[type] setDrawsBackground:NO];
|
||||||
|
[m_OverlayTextFields[type] setEditable:NO];
|
||||||
|
[m_OverlayTextFields[type] setSelectable:NO];
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case Overlay::OverlayDebug:
|
||||||
|
[m_OverlayTextFields[type] setAlignment:NSLeftTextAlignment];
|
||||||
|
break;
|
||||||
|
case Overlay::OverlayStatusUpdate:
|
||||||
|
[m_OverlayTextFields[type] setAlignment:NSRightTextAlignment];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Color color = Session::get()->getOverlayManager().getOverlayColor(type);
|
||||||
|
[m_OverlayTextFields[type] setTextColor:[NSColor colorWithSRGBRed:color.r / 255.0 green:color.g / 255.0 blue:color.b / 255.0 alpha:color.a / 255.0]];
|
||||||
|
[m_OverlayTextFields[type] setFont:[NSFont messageFontOfSize:Session::get()->getOverlayManager().getOverlayFontSize(type)]];
|
||||||
|
|
||||||
|
[m_StreamView addSubview: m_OverlayTextFields[type]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update text contents
|
||||||
|
[m_OverlayTextFields[type] setStringValue: [NSString stringWithUTF8String:Session::get()->getOverlayManager().getOverlayText(type)]];
|
||||||
|
|
||||||
|
// Unhide if it's enabled
|
||||||
|
[m_OverlayTextFields[type] setHidden: !Session::get()->getOverlayManager().isOverlayEnabled(type)];
|
||||||
|
}
|
||||||
|
|
||||||
static void updateDebugOverlayOnMainThread(void* context)
|
static void updateDebugOverlayOnMainThread(void* context)
|
||||||
{
|
{
|
||||||
VTRenderer* me = (VTRenderer*)context;
|
VTRenderer* me = (VTRenderer*)context;
|
||||||
|
|
||||||
// Lazy initialization for the overlay
|
me->updateOverlayOnMainThread(Overlay::OverlayDebug);
|
||||||
if (me->m_DebugOverlayTextField == nullptr) {
|
}
|
||||||
me->m_DebugOverlayTextField = [[NSTextField alloc] initWithFrame:me->m_StreamView.bounds];
|
|
||||||
[me->m_DebugOverlayTextField setBezeled:NO];
|
|
||||||
[me->m_DebugOverlayTextField setDrawsBackground:NO];
|
|
||||||
[me->m_DebugOverlayTextField setEditable:NO];
|
|
||||||
[me->m_DebugOverlayTextField setSelectable:NO];
|
|
||||||
|
|
||||||
SDL_Color color = Session::get()->getOverlayManager().getOverlayColor(Overlay::OverlayDebug);
|
static void updateStatusOverlayOnMainThread(void* context)
|
||||||
[me->m_DebugOverlayTextField setTextColor:[NSColor colorWithSRGBRed:color.r / 255.0 green:color.g / 255.0 blue:color.b / 255.0 alpha:color.a / 255.0]];
|
{
|
||||||
[me->m_DebugOverlayTextField setFont:[NSFont messageFontOfSize:Session::get()->getOverlayManager().getOverlayFontSize(Overlay::OverlayDebug)]];
|
VTRenderer* me = (VTRenderer*)context;
|
||||||
|
|
||||||
[me->m_StreamView addSubview: me->m_DebugOverlayTextField];
|
me->updateOverlayOnMainThread(Overlay::OverlayStatusUpdate);
|
||||||
}
|
|
||||||
|
|
||||||
// Update text contents
|
|
||||||
[me->m_DebugOverlayTextField setStringValue: [NSString stringWithUTF8String:Session::get()->getOverlayManager().getOverlayText(Overlay::OverlayDebug)]];
|
|
||||||
|
|
||||||
// Unhide if it's enabled
|
|
||||||
[me->m_DebugOverlayTextField setHidden: !Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebug)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void notifyOverlayUpdated(Overlay::OverlayType type) override
|
virtual void notifyOverlayUpdated(Overlay::OverlayType type) override
|
||||||
{
|
{
|
||||||
// We must do the actual UI updates on the main thread, so queue an
|
// We must do the actual UI updates on the main thread, so queue an
|
||||||
// async callback on the main thread via GCD to do the UI update.
|
// async callback on the main thread via GCD to do the UI update.
|
||||||
if (type == Overlay::OverlayDebug) {
|
switch (type) {
|
||||||
|
case Overlay::OverlayDebug:
|
||||||
dispatch_async_f(dispatch_get_main_queue(), this, updateDebugOverlayOnMainThread);
|
dispatch_async_f(dispatch_get_main_queue(), this, updateDebugOverlayOnMainThread);
|
||||||
|
break;
|
||||||
|
case Overlay::OverlayStatusUpdate:
|
||||||
|
dispatch_async_f(dispatch_get_main_queue(), this, updateStatusOverlayOnMainThread);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,11 +278,6 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual int getDecoderCapabilities() override
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual IFFmpegRenderer::FramePacingConstraint getFramePacingConstraint() override
|
virtual IFFmpegRenderer::FramePacingConstraint getFramePacingConstraint() override
|
||||||
{
|
{
|
||||||
// This renderer is inherently tied to V-sync due how we're
|
// This renderer is inherently tied to V-sync due how we're
|
||||||
@@ -269,7 +291,7 @@ private:
|
|||||||
AVSampleBufferDisplayLayer* m_DisplayLayer;
|
AVSampleBufferDisplayLayer* m_DisplayLayer;
|
||||||
CMVideoFormatDescriptionRef m_FormatDesc;
|
CMVideoFormatDescriptionRef m_FormatDesc;
|
||||||
NSView* m_StreamView;
|
NSView* m_StreamView;
|
||||||
NSTextField* m_DebugOverlayTextField;
|
NSTextField* m_OverlayTextFields[Overlay::OverlayMax];
|
||||||
};
|
};
|
||||||
|
|
||||||
IFFmpegRenderer* VTRendererFactory::createRenderer() {
|
IFFmpegRenderer* VTRendererFactory::createRenderer() {
|
||||||
|
|||||||
+178
-106
@@ -23,6 +23,14 @@
|
|||||||
#include "ffmpeg-renderers/vdpau.h"
|
#include "ffmpeg-renderers/vdpau.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_MMAL
|
||||||
|
#include "ffmpeg-renderers/mmal.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_DRM
|
||||||
|
#include "ffmpeg-renderers/drm.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// This is gross but it allows us to use sizeof()
|
// This is gross but it allows us to use sizeof()
|
||||||
#include "ffmpeg_videosamples.cpp"
|
#include "ffmpeg_videosamples.cpp"
|
||||||
|
|
||||||
@@ -32,12 +40,13 @@
|
|||||||
|
|
||||||
bool FFmpegVideoDecoder::isHardwareAccelerated()
|
bool FFmpegVideoDecoder::isHardwareAccelerated()
|
||||||
{
|
{
|
||||||
return m_HwDecodeCfg != nullptr;
|
return m_HwDecodeCfg != nullptr ||
|
||||||
|
(m_VideoDecoderCtx->codec->capabilities & AV_CODEC_CAP_HARDWARE) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int FFmpegVideoDecoder::getDecoderCapabilities()
|
int FFmpegVideoDecoder::getDecoderCapabilities()
|
||||||
{
|
{
|
||||||
return CAPABILITY_DIRECT_SUBMIT | m_Renderer->getDecoderCapabilities();
|
return m_BackendRenderer->getDecoderCapabilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AVPixelFormat FFmpegVideoDecoder::ffGetFormat(AVCodecContext* context,
|
enum AVPixelFormat FFmpegVideoDecoder::ffGetFormat(AVCodecContext* context,
|
||||||
@@ -65,7 +74,8 @@ FFmpegVideoDecoder::FFmpegVideoDecoder(bool testOnly)
|
|||||||
: m_VideoDecoderCtx(nullptr),
|
: m_VideoDecoderCtx(nullptr),
|
||||||
m_DecodeBuffer(1024 * 1024, 0),
|
m_DecodeBuffer(1024 * 1024, 0),
|
||||||
m_HwDecodeCfg(nullptr),
|
m_HwDecodeCfg(nullptr),
|
||||||
m_Renderer(nullptr),
|
m_BackendRenderer(nullptr),
|
||||||
|
m_FrontendRenderer(nullptr),
|
||||||
m_ConsecutiveFailedDecodes(0),
|
m_ConsecutiveFailedDecodes(0),
|
||||||
m_Pacer(nullptr),
|
m_Pacer(nullptr),
|
||||||
m_LastFrameNumber(0),
|
m_LastFrameNumber(0),
|
||||||
@@ -94,9 +104,9 @@ FFmpegVideoDecoder::~FFmpegVideoDecoder()
|
|||||||
av_log_set_level(AV_LOG_INFO);
|
av_log_set_level(AV_LOG_INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
IFFmpegRenderer* FFmpegVideoDecoder::getRenderer()
|
IFFmpegRenderer* FFmpegVideoDecoder::getBackendRenderer()
|
||||||
{
|
{
|
||||||
return m_Renderer;
|
return m_BackendRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFmpegVideoDecoder::reset()
|
void FFmpegVideoDecoder::reset()
|
||||||
@@ -115,8 +125,14 @@ void FFmpegVideoDecoder::reset()
|
|||||||
Session::get()->getOverlayManager().setOverlayRenderer(nullptr);
|
Session::get()->getOverlayManager().setOverlayRenderer(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete m_Renderer;
|
// If we have a separate frontend renderer, free that first
|
||||||
m_Renderer = nullptr;
|
if (m_FrontendRenderer != m_BackendRenderer) {
|
||||||
|
delete m_FrontendRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete m_BackendRenderer;
|
||||||
|
|
||||||
|
m_FrontendRenderer = m_BackendRenderer = nullptr;
|
||||||
|
|
||||||
if (!m_TestOnly) {
|
if (!m_TestOnly) {
|
||||||
logVideoStats(m_GlobalVideoStats, "Global video stats");
|
logVideoStats(m_GlobalVideoStats, "Global video stats");
|
||||||
@@ -127,35 +143,57 @@ void FFmpegVideoDecoder::reset()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* window,
|
bool FFmpegVideoDecoder::createFrontendRenderer(PDECODER_PARAMETERS params)
|
||||||
int videoFormat, int width, int height,
|
{
|
||||||
int maxFps, bool enableFramePacing, bool testFrame)
|
if (m_BackendRenderer->isDirectRenderingSupported()) {
|
||||||
|
// The backend renderer can render to the display
|
||||||
|
m_FrontendRenderer = m_BackendRenderer;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// The backend renderer cannot directly render to the display, so
|
||||||
|
// we will create an SDL renderer to draw the frames.
|
||||||
|
m_FrontendRenderer = new SdlRenderer();
|
||||||
|
if (!m_FrontendRenderer->initialize(params)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether the frontend renderer prefers frame pacing
|
||||||
|
auto vsyncConstraint = m_FrontendRenderer->getFramePacingConstraint();
|
||||||
|
if (vsyncConstraint == IFFmpegRenderer::PACING_FORCE_OFF && params->enableFramePacing) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Frame pacing is forcefully disabled by the frontend renderer");
|
||||||
|
params->enableFramePacing = false;
|
||||||
|
}
|
||||||
|
else if (vsyncConstraint == IFFmpegRenderer::PACING_FORCE_ON && !params->enableFramePacing) {
|
||||||
|
// FIXME: This duplicates logic in Session.cpp
|
||||||
|
int displayHz = StreamUtils::getDisplayRefreshRate(params->window);
|
||||||
|
if (displayHz + 5 >= params->frameRate) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Frame pacing is forcefully enabled by the frontend renderer");
|
||||||
|
params->enableFramePacing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAMETERS params, bool testFrame)
|
||||||
{
|
{
|
||||||
// In test-only mode, we should only see test frames
|
// In test-only mode, we should only see test frames
|
||||||
SDL_assert(!m_TestOnly || testFrame);
|
SDL_assert(!m_TestOnly || testFrame);
|
||||||
|
|
||||||
auto vsyncConstraint = m_Renderer->getFramePacingConstraint();
|
// Create the frontend renderer based on the capabilities of the backend renderer
|
||||||
if (vsyncConstraint == IFFmpegRenderer::PACING_FORCE_OFF && enableFramePacing) {
|
if (!createFrontendRenderer(params)) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
return false;
|
||||||
"Frame pacing is forcefully disabled by the active renderer");
|
|
||||||
enableFramePacing = false;
|
|
||||||
}
|
|
||||||
else if (vsyncConstraint == IFFmpegRenderer::PACING_FORCE_ON && !enableFramePacing) {
|
|
||||||
// FIXME: This duplicates logic in Session.cpp
|
|
||||||
int displayHz = StreamUtils::getDisplayRefreshRate(window);
|
|
||||||
if (displayHz + 5 >= maxFps) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Frame pacing is forcefully enabled by the active renderer");
|
|
||||||
enableFramePacing = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_StreamFps = maxFps;
|
m_StreamFps = params->frameRate;
|
||||||
|
|
||||||
// Don't bother initializing Pacer if we're not actually going to render
|
// Don't bother initializing Pacer if we're not actually going to render
|
||||||
if (!testFrame) {
|
if (!testFrame) {
|
||||||
m_Pacer = new Pacer(m_Renderer, &m_ActiveWndVideoStats);
|
m_Pacer = new Pacer(m_FrontendRenderer, &m_ActiveWndVideoStats);
|
||||||
if (!m_Pacer->initialize(window, maxFps, enableFramePacing)) {
|
if (!m_Pacer->initialize(params->window, params->frameRate, params->enableFramePacing)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,7 +213,7 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* wi
|
|||||||
m_VideoDecoderCtx->flags2 |= AV_CODEC_FLAG2_SHOW_ALL;
|
m_VideoDecoderCtx->flags2 |= AV_CODEC_FLAG2_SHOW_ALL;
|
||||||
|
|
||||||
// Enable slice multi-threading for software decoding
|
// Enable slice multi-threading for software decoding
|
||||||
if (!m_HwDecodeCfg) {
|
if (!isHardwareAccelerated()) {
|
||||||
m_VideoDecoderCtx->thread_type = FF_THREAD_SLICE;
|
m_VideoDecoderCtx->thread_type = FF_THREAD_SLICE;
|
||||||
m_VideoDecoderCtx->thread_count = qMin(MAX_SLICES, SDL_GetCPUCount());
|
m_VideoDecoderCtx->thread_count = qMin(MAX_SLICES, SDL_GetCPUCount());
|
||||||
}
|
}
|
||||||
@@ -185,13 +223,13 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* wi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup decoding parameters
|
// Setup decoding parameters
|
||||||
m_VideoDecoderCtx->width = width;
|
m_VideoDecoderCtx->width = params->width;
|
||||||
m_VideoDecoderCtx->height = height;
|
m_VideoDecoderCtx->height = params->height;
|
||||||
m_VideoDecoderCtx->pix_fmt = AV_PIX_FMT_YUV420P; // FIXME: HDR
|
m_VideoDecoderCtx->pix_fmt = m_FrontendRenderer->getPreferredPixelFormat(params->videoFormat);
|
||||||
m_VideoDecoderCtx->get_format = ffGetFormat;
|
m_VideoDecoderCtx->get_format = ffGetFormat;
|
||||||
|
|
||||||
// Allow the renderer to attach data to this decoder
|
// Allow the backend renderer to attach data to this decoder
|
||||||
if (!m_Renderer->prepareDecoderContext(m_VideoDecoderCtx)) {
|
if (!m_BackendRenderer->prepareDecoderContext(m_VideoDecoderCtx)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +244,7 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* wi
|
|||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Unable to open decoder for format: %x",
|
"Unable to open decoder for format: %x",
|
||||||
videoFormat);
|
params->videoFormat);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +254,7 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* wi
|
|||||||
// now to see if things will actually work when the video stream
|
// now to see if things will actually work when the video stream
|
||||||
// comes in.
|
// comes in.
|
||||||
if (testFrame) {
|
if (testFrame) {
|
||||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||||
m_Pkt.data = (uint8_t*)k_H264TestFrame;
|
m_Pkt.data = (uint8_t*)k_H264TestFrame;
|
||||||
m_Pkt.size = sizeof(k_H264TestFrame);
|
m_Pkt.size = sizeof(k_H264TestFrame);
|
||||||
}
|
}
|
||||||
@@ -235,8 +273,8 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* wi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if ((videoFormat & VIDEO_FORMAT_MASK_H264) &&
|
if ((params->videoFormat & VIDEO_FORMAT_MASK_H264) &&
|
||||||
!(m_Renderer->getDecoderCapabilities() & CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC)) {
|
!(m_BackendRenderer->getDecoderCapabilities() & CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC)) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Using H.264 SPS fixup");
|
"Using H.264 SPS fixup");
|
||||||
m_NeedsSpsFixup = true;
|
m_NeedsSpsFixup = true;
|
||||||
@@ -245,8 +283,8 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* wi
|
|||||||
m_NeedsSpsFixup = false;
|
m_NeedsSpsFixup = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell overlay manager to use this renderer
|
// Tell overlay manager to use this frontend renderer
|
||||||
Session::get()->getOverlayManager().setOverlayRenderer(m_Renderer);
|
Session::get()->getOverlayManager().setOverlayRenderer(m_FrontendRenderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -331,7 +369,7 @@ void FFmpegVideoDecoder::logVideoStats(VIDEO_STATS& stats, const char* title)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IFFmpegRenderer* FFmpegVideoDecoder::createAcceleratedRenderer(const AVCodecHWConfig* hwDecodeCfg)
|
IFFmpegRenderer* FFmpegVideoDecoder::createHwAccelRenderer(const AVCodecHWConfig* hwDecodeCfg)
|
||||||
{
|
{
|
||||||
if (!(hwDecodeCfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
|
if (!(hwDecodeCfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -360,25 +398,61 @@ IFFmpegRenderer* FFmpegVideoDecoder::createAcceleratedRenderer(const AVCodecHWCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FFmpegVideoDecoder::initialize(
|
bool FFmpegVideoDecoder::tryInitializeRenderer(AVCodec* decoder,
|
||||||
StreamingPreferences::VideoDecoderSelection vds,
|
PDECODER_PARAMETERS params,
|
||||||
SDL_Window* window,
|
const AVCodecHWConfig* hwConfig,
|
||||||
int videoFormat,
|
std::function<IFFmpegRenderer*()> createRendererFunc)
|
||||||
int width,
|
{
|
||||||
int height,
|
m_BackendRenderer = createRendererFunc();
|
||||||
int maxFps,
|
m_HwDecodeCfg = hwConfig;
|
||||||
bool enableVsync,
|
|
||||||
bool enableFramePacing)
|
if (m_BackendRenderer != nullptr &&
|
||||||
|
m_BackendRenderer->initialize(params) &&
|
||||||
|
completeInitialization(decoder, params, m_TestOnly || m_BackendRenderer->needsTestFrame())) {
|
||||||
|
if (m_TestOnly) {
|
||||||
|
// This decoder is only for testing capabilities, so don't bother
|
||||||
|
// creating a usable renderer
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_BackendRenderer->needsTestFrame()) {
|
||||||
|
// The test worked, so now let's initialize it for real
|
||||||
|
reset();
|
||||||
|
if ((m_BackendRenderer = createRendererFunc()) != nullptr &&
|
||||||
|
m_BackendRenderer->initialize(params) &&
|
||||||
|
completeInitialization(decoder, params, false)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Decoder failed to initialize after successful test");
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// No test required. Good to go now.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Failed to initialize or test frame failed, so keep looking
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
||||||
{
|
{
|
||||||
AVCodec* decoder;
|
AVCodec* decoder;
|
||||||
|
|
||||||
// Increase log level until the first frame is decoded
|
// Increase log level until the first frame is decoded
|
||||||
av_log_set_level(AV_LOG_DEBUG);
|
av_log_set_level(AV_LOG_DEBUG);
|
||||||
|
|
||||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||||
decoder = avcodec_find_decoder(AV_CODEC_ID_H264);
|
decoder = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||||
}
|
}
|
||||||
else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
else if (params->videoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||||
decoder = avcodec_find_decoder(AV_CODEC_ID_HEVC);
|
decoder = avcodec_find_decoder(AV_CODEC_ID_HEVC);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -389,67 +463,71 @@ bool FFmpegVideoDecoder::initialize(
|
|||||||
if (!decoder) {
|
if (!decoder) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Unable to find decoder for format: %x",
|
"Unable to find decoder for format: %x",
|
||||||
videoFormat);
|
params->videoFormat);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0;; i++) {
|
// Look for a hardware decoder first unless software-only
|
||||||
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
|
if (params->vds != StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
||||||
if (!config || vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
// Look for the first matching hwaccel hardware decoder
|
||||||
// No matching hardware acceleration support.
|
for (int i = 0;; i++) {
|
||||||
// This is not an error.
|
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
|
||||||
m_HwDecodeCfg = nullptr;
|
if (!config) {
|
||||||
m_Renderer = new SdlRenderer();
|
// No remaing hwaccel options
|
||||||
if (vds != StreamingPreferences::VDS_FORCE_HARDWARE &&
|
break;
|
||||||
m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) &&
|
|
||||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableFramePacing, m_TestOnly)) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
reset();
|
// Initialize the hardware codec and submit a test frame if the renderer needs it
|
||||||
return false;
|
if (tryInitializeRenderer(decoder, params, config,
|
||||||
|
[config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config); })) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Renderer = createAcceleratedRenderer(config);
|
// Continue with special non-hwaccel hardware decoders
|
||||||
if (!m_Renderer) {
|
|
||||||
continue;
|
#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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
m_HwDecodeCfg = config;
|
#ifdef HAVE_DRM
|
||||||
// Initialize the hardware codec and submit a test frame if the renderer needs it
|
// RKMPP is a hardware accelerated decoder that outputs DRI PRIME buffers
|
||||||
if (m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) &&
|
AVCodec* rkmppDecoder;
|
||||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableFramePacing, m_TestOnly || m_Renderer->needsTestFrame())) {
|
|
||||||
if (m_TestOnly) {
|
|
||||||
// This decoder is only for testing capabilities, so don't bother
|
|
||||||
// creating a usable renderer
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_Renderer->needsTestFrame()) {
|
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||||
// The test worked, so now let's initialize it for real
|
rkmppDecoder = avcodec_find_decoder_by_name("h264_rkmpp");
|
||||||
reset();
|
|
||||||
if ((m_Renderer = createAcceleratedRenderer(config)) != nullptr &&
|
|
||||||
m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) &&
|
|
||||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableFramePacing, false)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Decoder failed to initialize after successful test");
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// No test required. Good to go now.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Failed to initialize or test frame failed, so keep looking
|
rkmppDecoder = avcodec_find_decoder_by_name("hevc_rkmpp");
|
||||||
reset();
|
}
|
||||||
|
|
||||||
|
if (rkmppDecoder != nullptr &&
|
||||||
|
tryInitializeRenderer(rkmppDecoder, params, nullptr,
|
||||||
|
[]() -> IFFmpegRenderer* { return new DrmRenderer(); })) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to software if no matching hardware decoder was found
|
||||||
|
// and if software fallback is allowed
|
||||||
|
if (params->vds != StreamingPreferences::VDS_FORCE_HARDWARE) {
|
||||||
|
if (tryInitializeRenderer(decoder, params, nullptr,
|
||||||
|
[]() -> IFFmpegRenderer* { return new SdlRenderer(); })) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No decoder worked
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFmpegVideoDecoder::writeBuffer(PLENTRY entry, int& offset)
|
void FFmpegVideoDecoder::writeBuffer(PLENTRY entry, int& offset)
|
||||||
@@ -630,14 +708,8 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
|||||||
return DR_OK;
|
return DR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called on main thread
|
void FFmpegVideoDecoder::renderFrameOnMainThread()
|
||||||
void FFmpegVideoDecoder::renderFrame(SDL_UserEvent*)
|
|
||||||
{
|
{
|
||||||
SDL_assert(false);
|
m_Pacer->renderOnMainThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called on main thread
|
|
||||||
void FFmpegVideoDecoder::dropFrame(SDL_UserEvent*)
|
|
||||||
{
|
|
||||||
SDL_assert(false);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "ffmpeg-renderers/renderer.h"
|
#include "ffmpeg-renderers/renderer.h"
|
||||||
#include "ffmpeg-renderers/pacer/pacer.h"
|
#include "ffmpeg-renderers/pacer/pacer.h"
|
||||||
@@ -12,26 +14,16 @@ class FFmpegVideoDecoder : public IVideoDecoder {
|
|||||||
public:
|
public:
|
||||||
FFmpegVideoDecoder(bool testOnly);
|
FFmpegVideoDecoder(bool testOnly);
|
||||||
virtual ~FFmpegVideoDecoder() override;
|
virtual ~FFmpegVideoDecoder() override;
|
||||||
virtual bool initialize(StreamingPreferences::VideoDecoderSelection vds,
|
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||||
SDL_Window* window,
|
|
||||||
int videoFormat,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
int maxFps,
|
|
||||||
bool enableVsync,
|
|
||||||
bool enableFramePacing) override;
|
|
||||||
virtual bool isHardwareAccelerated() override;
|
virtual bool isHardwareAccelerated() override;
|
||||||
virtual int getDecoderCapabilities() override;
|
virtual int getDecoderCapabilities() override;
|
||||||
virtual int submitDecodeUnit(PDECODE_UNIT du) override;
|
virtual int submitDecodeUnit(PDECODE_UNIT du) override;
|
||||||
virtual void renderFrame(SDL_UserEvent* event) override;
|
virtual void renderFrameOnMainThread() override;
|
||||||
virtual void dropFrame(SDL_UserEvent* event) override;
|
|
||||||
|
|
||||||
virtual IFFmpegRenderer* getRenderer();
|
virtual IFFmpegRenderer* getBackendRenderer();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool completeInitialization(AVCodec* decoder, SDL_Window* window,
|
bool completeInitialization(AVCodec* decoder, PDECODER_PARAMETERS params, bool testFrame);
|
||||||
int videoFormat, int width, int height,
|
|
||||||
int maxFps, bool enableFramePacing, bool testFrame);
|
|
||||||
|
|
||||||
void stringifyVideoStats(VIDEO_STATS& stats, char* output);
|
void stringifyVideoStats(VIDEO_STATS& stats, char* output);
|
||||||
|
|
||||||
@@ -39,7 +31,14 @@ private:
|
|||||||
|
|
||||||
void addVideoStats(VIDEO_STATS& src, VIDEO_STATS& dst);
|
void addVideoStats(VIDEO_STATS& src, VIDEO_STATS& dst);
|
||||||
|
|
||||||
IFFmpegRenderer* createAcceleratedRenderer(const AVCodecHWConfig* hwDecodeCfg);
|
bool createFrontendRenderer(PDECODER_PARAMETERS params);
|
||||||
|
|
||||||
|
bool tryInitializeRenderer(AVCodec* decoder,
|
||||||
|
PDECODER_PARAMETERS params,
|
||||||
|
const AVCodecHWConfig* hwConfig,
|
||||||
|
std::function<IFFmpegRenderer*()> createRendererFunc);
|
||||||
|
|
||||||
|
static IFFmpegRenderer* createHwAccelRenderer(const AVCodecHWConfig* hwDecodeCfg);
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
@@ -53,7 +52,8 @@ private:
|
|||||||
AVCodecContext* m_VideoDecoderCtx;
|
AVCodecContext* m_VideoDecoderCtx;
|
||||||
QByteArray m_DecodeBuffer;
|
QByteArray m_DecodeBuffer;
|
||||||
const AVCodecHWConfig* m_HwDecodeCfg;
|
const AVCodecHWConfig* m_HwDecodeCfg;
|
||||||
IFFmpegRenderer* m_Renderer;
|
IFFmpegRenderer* m_BackendRenderer;
|
||||||
|
IFFmpegRenderer* m_FrontendRenderer;
|
||||||
int m_ConsecutiveFailedDecodes;
|
int m_ConsecutiveFailedDecodes;
|
||||||
Pacer* m_Pacer;
|
Pacer* m_Pacer;
|
||||||
VIDEO_STATS m_ActiveWndVideoStats;
|
VIDEO_STATS m_ActiveWndVideoStats;
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ OverlayManager::OverlayManager() :
|
|||||||
|
|
||||||
m_Overlays[OverlayType::OverlayDebug].color = {0xD0, 0xD0, 0x00, 0xFF};
|
m_Overlays[OverlayType::OverlayDebug].color = {0xD0, 0xD0, 0x00, 0xFF};
|
||||||
m_Overlays[OverlayType::OverlayDebug].fontSize = 20;
|
m_Overlays[OverlayType::OverlayDebug].fontSize = 20;
|
||||||
|
|
||||||
|
m_Overlays[OverlayType::OverlayStatusUpdate].color = {0xCC, 0x00, 0x00, 0xFF};
|
||||||
|
m_Overlays[OverlayType::OverlayStatusUpdate].fontSize = 36;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OverlayManager::isOverlayEnabled(OverlayType type)
|
bool OverlayManager::isOverlayEnabled(OverlayType type)
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ namespace Overlay {
|
|||||||
|
|
||||||
enum OverlayType {
|
enum OverlayType {
|
||||||
OverlayDebug,
|
OverlayDebug,
|
||||||
OverlayMinorNotification,
|
OverlayStatusUpdate,
|
||||||
OverlayMajorNotification,
|
|
||||||
OverlayMax
|
OverlayMax
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "decoder.h"
|
|
||||||
|
|
||||||
#include <SLVideo.h>
|
|
||||||
|
|
||||||
class SLVideoDecoder : public IVideoDecoder
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SLVideoDecoder(bool testOnly);
|
|
||||||
virtual ~SLVideoDecoder();
|
|
||||||
virtual bool initialize(StreamingPreferences::VideoDecoderSelection vds,
|
|
||||||
SDL_Window* window,
|
|
||||||
int videoFormat,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
int frameRate,
|
|
||||||
bool enableVsync,
|
|
||||||
bool enableFramePacing);
|
|
||||||
virtual bool isHardwareAccelerated();
|
|
||||||
virtual int getDecoderCapabilities();
|
|
||||||
virtual int submitDecodeUnit(PDECODE_UNIT du);
|
|
||||||
|
|
||||||
// Unused since rendering is done directly from the decode thread
|
|
||||||
virtual void renderFrame(SDL_UserEvent*) {}
|
|
||||||
virtual void dropFrame(SDL_UserEvent*) {}
|
|
||||||
|
|
||||||
private:
|
|
||||||
CSLVideoContext* m_VideoContext;
|
|
||||||
CSLVideoStream* m_VideoStream;
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
#include "sl.h"
|
#include "slvid.h"
|
||||||
|
|
||||||
SLVideoDecoder::SLVideoDecoder(bool)
|
SLVideoDecoder::SLVideoDecoder(bool)
|
||||||
: m_VideoContext(nullptr),
|
: m_VideoContext(nullptr),
|
||||||
m_VideoStream(nullptr)
|
m_VideoStream(nullptr)
|
||||||
{
|
{
|
||||||
|
SLVideo_SetLogFunction(SLVideoDecoder::slLogCallback, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
SLVideoDecoder::~SLVideoDecoder()
|
SLVideoDecoder::~SLVideoDecoder()
|
||||||
@@ -32,17 +32,15 @@ SLVideoDecoder::getDecoderCapabilities()
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
SLVideoDecoder::initialize(StreamingPreferences::VideoDecoderSelection vds,
|
SLVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
||||||
SDL_Window*,
|
|
||||||
int videoFormat, int, int, int frameRate, bool, bool)
|
|
||||||
{
|
{
|
||||||
// SLVideo only supports hardware decoding
|
// SLVideo only supports hardware decoding
|
||||||
if (vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
if (params->vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SLVideo only supports H.264
|
// SLVideo only supports H.264
|
||||||
if (videoFormat != VIDEO_FORMAT_H264) {
|
if (params->videoFormat != VIDEO_FORMAT_H264) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +59,7 @@ SLVideoDecoder::initialize(StreamingPreferences::VideoDecoderSelection vds,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SLVideo_SetStreamTargetFramerate(m_VideoStream, frameRate, 1);
|
SLVideo_SetStreamTargetFramerate(m_VideoStream, params->frameRate, 1);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -110,3 +108,30 @@ SLVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
|||||||
|
|
||||||
return DR_OK;
|
return DR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SLVideoDecoder::slLogCallback(void *context, ESLVideoLog logLevel, const char *message)
|
||||||
|
{
|
||||||
|
SDL_LogPriority priority;
|
||||||
|
|
||||||
|
switch (logLevel)
|
||||||
|
{
|
||||||
|
case k_ESLVideoLogError:
|
||||||
|
priority = SDL_LOG_PRIORITY_ERROR;
|
||||||
|
break;
|
||||||
|
case k_ESLVideoLogWarning:
|
||||||
|
priority = SDL_LOG_PRIORITY_WARN;
|
||||||
|
break;
|
||||||
|
case k_ESLVideoLogInfo:
|
||||||
|
priority = SDL_LOG_PRIORITY_INFO;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case k_ESLVideoLogDebug:
|
||||||
|
priority = SDL_LOG_PRIORITY_DEBUG;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
priority,
|
||||||
|
"SLVideo: %s",
|
||||||
|
message);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "decoder.h"
|
||||||
|
|
||||||
|
#include <SLVideo.h>
|
||||||
|
|
||||||
|
class SLVideoDecoder : public IVideoDecoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SLVideoDecoder(bool testOnly);
|
||||||
|
virtual ~SLVideoDecoder();
|
||||||
|
virtual bool initialize(PDECODER_PARAMETERS params);
|
||||||
|
virtual bool isHardwareAccelerated();
|
||||||
|
virtual int getDecoderCapabilities();
|
||||||
|
virtual int submitDecodeUnit(PDECODE_UNIT du);
|
||||||
|
|
||||||
|
// Unused since rendering is done directly from the decode thread
|
||||||
|
virtual void renderFrameOnMainThread() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void slLogCallback(void* context, ESLVideoLog logLevel, const char* message);
|
||||||
|
|
||||||
|
CSLVideoContext* m_VideoContext;
|
||||||
|
CSLVideoStream* m_VideoStream;
|
||||||
|
};
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
0.9.1
|
0.10.1
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
SOURCES = main.cpp
|
||||||
|
LIBS += -lSLVideo -lSLAudio
|
||||||
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
#include <SLVideo.h>
|
#include <SLVideo.h>
|
||||||
|
#include <SLAudio.h>
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
SLVideo_CreateContext();
|
SLVideo_CreateContext();
|
||||||
|
SLAudio_CreateContext();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
SOURCES = main.cpp
|
|
||||||
LIBS += -lSLVideo
|
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Support debug and release builds from command line for CI
|
||||||
|
CONFIG += debug_and_release
|
||||||
|
|
||||||
|
# Ensure symbols are always generated
|
||||||
|
CONFIG += force_debug_info
|
||||||
|
|
||||||
|
# Disable asserts on release builds
|
||||||
|
CONFIG(release, debug|release) {
|
||||||
|
DEFINES += NDEBUG
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enable ASan if desired
|
||||||
|
#CONFIG += sanitizer sanitize_address
|
||||||
@@ -10,18 +10,15 @@ QT -= core gui
|
|||||||
TARGET = h264bitstream
|
TARGET = h264bitstream
|
||||||
TEMPLATE = lib
|
TEMPLATE = lib
|
||||||
|
|
||||||
# Support debug and release builds from command line for CI
|
|
||||||
CONFIG += debug_and_release
|
|
||||||
|
|
||||||
# Ensure symbols are always generated
|
|
||||||
CONFIG += force_debug_info
|
|
||||||
|
|
||||||
# Build a static library
|
# Build a static library
|
||||||
CONFIG += staticlib
|
CONFIG += staticlib
|
||||||
|
|
||||||
# Disable warnings
|
# Disable warnings
|
||||||
CONFIG += warn_off
|
CONFIG += warn_off
|
||||||
|
|
||||||
|
# Include global qmake defs
|
||||||
|
include(../globaldefs.pri)
|
||||||
|
|
||||||
# Older GCC versions defaulted to GNU89
|
# Older GCC versions defaulted to GNU89
|
||||||
*-g++ {
|
*-g++ {
|
||||||
QMAKE_CFLAGS += -std=gnu99
|
QMAKE_CFLAGS += -std=gnu99
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
#include "SDL_power.h"
|
#include "SDL_power.h"
|
||||||
#include "SDL_render.h"
|
#include "SDL_render.h"
|
||||||
#include "SDL_rwops.h"
|
#include "SDL_rwops.h"
|
||||||
|
#include "SDL_sensor.h"
|
||||||
#include "SDL_shape.h"
|
#include "SDL_shape.h"
|
||||||
#include "SDL_system.h"
|
#include "SDL_system.h"
|
||||||
#include "SDL_thread.h"
|
#include "SDL_thread.h"
|
||||||
@@ -80,10 +81,11 @@ extern "C" {
|
|||||||
#define SDL_INIT_HAPTIC 0x00001000u
|
#define SDL_INIT_HAPTIC 0x00001000u
|
||||||
#define SDL_INIT_GAMECONTROLLER 0x00002000u /**< SDL_INIT_GAMECONTROLLER implies SDL_INIT_JOYSTICK */
|
#define SDL_INIT_GAMECONTROLLER 0x00002000u /**< SDL_INIT_GAMECONTROLLER implies SDL_INIT_JOYSTICK */
|
||||||
#define SDL_INIT_EVENTS 0x00004000u
|
#define SDL_INIT_EVENTS 0x00004000u
|
||||||
|
#define SDL_INIT_SENSOR 0x00008000u
|
||||||
#define SDL_INIT_NOPARACHUTE 0x00100000u /**< compatibility; this flag is ignored. */
|
#define SDL_INIT_NOPARACHUTE 0x00100000u /**< compatibility; this flag is ignored. */
|
||||||
#define SDL_INIT_EVERYTHING ( \
|
#define SDL_INIT_EVERYTHING ( \
|
||||||
SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | \
|
SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | \
|
||||||
SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER \
|
SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR \
|
||||||
)
|
)
|
||||||
/* @} */
|
/* @} */
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,8 @@ typedef Uint16 SDL_AudioFormat;
|
|||||||
#define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001
|
#define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001
|
||||||
#define SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002
|
#define SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002
|
||||||
#define SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004
|
#define SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004
|
||||||
#define SDL_AUDIO_ALLOW_ANY_CHANGE (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE)
|
#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 */
|
/* @} *//* Audio flags */
|
||||||
|
|||||||
@@ -41,8 +41,10 @@
|
|||||||
#include "SDL_config_android.h"
|
#include "SDL_config_android.h"
|
||||||
#elif defined(__PSP__)
|
#elif defined(__PSP__)
|
||||||
#include "SDL_config_psp.h"
|
#include "SDL_config_psp.h"
|
||||||
|
#elif defined(__OS2__)
|
||||||
|
#include "SDL_config_os2.h"
|
||||||
#else
|
#else
|
||||||
/* This is a minimal configuration just to get SDL running on new platforms */
|
/* This is a minimal configuration just to get SDL running on new platforms. */
|
||||||
#include "SDL_config_minimal.h"
|
#include "SDL_config_minimal.h"
|
||||||
#endif /* platform config */
|
#endif /* platform config */
|
||||||
|
|
||||||
|
|||||||
@@ -102,6 +102,8 @@
|
|||||||
#define HAVE_COPYSIGNF 1
|
#define HAVE_COPYSIGNF 1
|
||||||
#define HAVE_COS 1
|
#define HAVE_COS 1
|
||||||
#define HAVE_COSF 1
|
#define HAVE_COSF 1
|
||||||
|
#define HAVE_EXP 1
|
||||||
|
#define HAVE_EXPF 1
|
||||||
#define HAVE_FABS 1
|
#define HAVE_FABS 1
|
||||||
#define HAVE_FABSF 1
|
#define HAVE_FABSF 1
|
||||||
#define HAVE_FLOOR 1
|
#define HAVE_FLOOR 1
|
||||||
@@ -135,8 +137,12 @@
|
|||||||
|
|
||||||
/* Enable various input drivers */
|
/* Enable various input drivers */
|
||||||
#define SDL_JOYSTICK_IOKIT 1
|
#define SDL_JOYSTICK_IOKIT 1
|
||||||
|
#define SDL_JOYSTICK_HIDAPI 1
|
||||||
#define SDL_HAPTIC_IOKIT 1
|
#define SDL_HAPTIC_IOKIT 1
|
||||||
|
|
||||||
|
/* Enable the dummy sensor driver */
|
||||||
|
#define SDL_SENSOR_DUMMY 1
|
||||||
|
|
||||||
/* Enable various shared object loading systems */
|
/* Enable various shared object loading systems */
|
||||||
#define SDL_LOADSO_DLOPEN 1
|
#define SDL_LOADSO_DLOPEN 1
|
||||||
|
|
||||||
|
|||||||
@@ -51,16 +51,19 @@
|
|||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
#else
|
#else
|
||||||
#ifdef __ALTIVEC__
|
#ifdef __ALTIVEC__
|
||||||
#if HAVE_ALTIVEC_H && !defined(__APPLE_ALTIVEC__) && !defined(SDL_DISABLE_ALTIVEC_H)
|
#if defined(HAVE_ALTIVEC_H) && !defined(__APPLE_ALTIVEC__) && !defined(SDL_DISABLE_ALTIVEC_H)
|
||||||
#include <altivec.h>
|
#include <altivec.h>
|
||||||
#undef pixel
|
#undef pixel
|
||||||
#undef bool
|
#undef bool
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(__ARM_NEON__) && !defined(SDL_DISABLE_ARM_NEON_H)
|
||||||
|
#include <arm_neon.h>
|
||||||
|
#endif
|
||||||
#if defined(__3dNOW__) && !defined(SDL_DISABLE_MM3DNOW_H)
|
#if defined(__3dNOW__) && !defined(SDL_DISABLE_MM3DNOW_H)
|
||||||
#include <mm3dnow.h>
|
#include <mm3dnow.h>
|
||||||
#endif
|
#endif
|
||||||
#if HAVE_IMMINTRIN_H && !defined(SDL_DISABLE_IMMINTRIN_H)
|
#if defined(HAVE_IMMINTRIN_H) && !defined(SDL_DISABLE_IMMINTRIN_H)
|
||||||
#include <immintrin.h>
|
#include <immintrin.h>
|
||||||
#else
|
#else
|
||||||
#if defined(__MMX__) && !defined(SDL_DISABLE_MMINTRIN_H)
|
#if defined(__MMX__) && !defined(SDL_DISABLE_MMINTRIN_H)
|
||||||
@@ -159,6 +162,11 @@ extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX(void);
|
|||||||
*/
|
*/
|
||||||
extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX2(void);
|
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 NEON (ARM SIMD) features.
|
* This function returns true if the CPU has NEON (ARM SIMD) features.
|
||||||
*/
|
*/
|
||||||
@@ -169,7 +177,6 @@ extern DECLSPEC SDL_bool SDLCALL SDL_HasNEON(void);
|
|||||||
*/
|
*/
|
||||||
extern DECLSPEC int SDLCALL SDL_GetSystemRAM(void);
|
extern DECLSPEC int SDLCALL SDL_GetSystemRAM(void);
|
||||||
|
|
||||||
|
|
||||||
/* Ends C function definitions when using C++ */
|
/* Ends C function definitions when using C++ */
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ typedef enum
|
|||||||
Called on Android in onResume()
|
Called on Android in onResume()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Display events */
|
||||||
|
SDL_DISPLAYEVENT = 0x150, /**< Display state change */
|
||||||
|
|
||||||
/* Window events */
|
/* Window events */
|
||||||
SDL_WINDOWEVENT = 0x200, /**< Window state change */
|
SDL_WINDOWEVENT = 0x200, /**< Window state change */
|
||||||
SDL_SYSWMEVENT, /**< System specific event */
|
SDL_SYSWMEVENT, /**< System specific event */
|
||||||
@@ -144,6 +147,9 @@ typedef enum
|
|||||||
SDL_AUDIODEVICEADDED = 0x1100, /**< A new audio device is available */
|
SDL_AUDIODEVICEADDED = 0x1100, /**< A new audio device is available */
|
||||||
SDL_AUDIODEVICEREMOVED, /**< An audio device has been removed. */
|
SDL_AUDIODEVICEREMOVED, /**< An audio device has been removed. */
|
||||||
|
|
||||||
|
/* Sensor events */
|
||||||
|
SDL_SENSORUPDATE = 0x1200, /**< A sensor was updated */
|
||||||
|
|
||||||
/* Render events */
|
/* Render events */
|
||||||
SDL_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */
|
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 */
|
SDL_RENDER_DEVICE_RESET, /**< The device has been reset and all textures need to be recreated */
|
||||||
@@ -168,6 +174,21 @@ typedef struct SDL_CommonEvent
|
|||||||
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
|
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
|
||||||
} SDL_CommonEvent;
|
} 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.*)
|
* \brief Window state change event data (event.window.*)
|
||||||
*/
|
*/
|
||||||
@@ -471,6 +492,17 @@ typedef struct SDL_DropEvent
|
|||||||
} SDL_DropEvent;
|
} 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
|
* \brief The "quit requested" event
|
||||||
*/
|
*/
|
||||||
@@ -526,6 +558,7 @@ typedef union SDL_Event
|
|||||||
{
|
{
|
||||||
Uint32 type; /**< Event type, shared with all events */
|
Uint32 type; /**< Event type, shared with all events */
|
||||||
SDL_CommonEvent common; /**< Common event data */
|
SDL_CommonEvent common; /**< Common event data */
|
||||||
|
SDL_DisplayEvent display; /**< Window event data */
|
||||||
SDL_WindowEvent window; /**< Window event data */
|
SDL_WindowEvent window; /**< Window event data */
|
||||||
SDL_KeyboardEvent key; /**< Keyboard event data */
|
SDL_KeyboardEvent key; /**< Keyboard event data */
|
||||||
SDL_TextEditingEvent edit; /**< Text editing event data */
|
SDL_TextEditingEvent edit; /**< Text editing event data */
|
||||||
@@ -542,6 +575,7 @@ typedef union SDL_Event
|
|||||||
SDL_ControllerButtonEvent cbutton; /**< Game Controller button event data */
|
SDL_ControllerButtonEvent cbutton; /**< Game Controller button event data */
|
||||||
SDL_ControllerDeviceEvent cdevice; /**< Game Controller device event data */
|
SDL_ControllerDeviceEvent cdevice; /**< Game Controller device event data */
|
||||||
SDL_AudioDeviceEvent adevice; /**< Audio device event data */
|
SDL_AudioDeviceEvent adevice; /**< Audio device event data */
|
||||||
|
SDL_SensorEvent sensor; /**< Sensor event data */
|
||||||
SDL_QuitEvent quit; /**< Quit request event data */
|
SDL_QuitEvent quit; /**< Quit request event data */
|
||||||
SDL_UserEvent user; /**< Custom event data */
|
SDL_UserEvent user; /**< Custom event data */
|
||||||
SDL_SysWMEvent syswm; /**< System dependent window event data */
|
SDL_SysWMEvent syswm; /**< System dependent window event data */
|
||||||
|
|||||||
@@ -175,6 +175,14 @@ extern DECLSPEC SDL_bool SDLCALL SDL_IsGameController(int joystick_index);
|
|||||||
*/
|
*/
|
||||||
extern DECLSPEC const char *SDLCALL SDL_GameControllerNameForIndex(int joystick_index);
|
extern DECLSPEC const char *SDLCALL SDL_GameControllerNameForIndex(int joystick_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mapping of a game controller.
|
||||||
|
* This can be called before any controllers are opened.
|
||||||
|
*
|
||||||
|
* \return the mapping string. Must be freed with SDL_free(). Returns NULL if no mapping is available
|
||||||
|
*/
|
||||||
|
extern DECLSPEC char *SDLCALL SDL_GameControllerMappingForDeviceIndex(int joystick_index);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a game controller for use.
|
* Open a game controller for use.
|
||||||
* The index passed as an argument refers to the N'th game controller on the system.
|
* The index passed as an argument refers to the N'th game controller on the system.
|
||||||
@@ -196,6 +204,13 @@ extern DECLSPEC SDL_GameController *SDLCALL SDL_GameControllerFromInstanceID(SDL
|
|||||||
*/
|
*/
|
||||||
extern DECLSPEC const char *SDLCALL SDL_GameControllerName(SDL_GameController *gamecontroller);
|
extern DECLSPEC const char *SDLCALL SDL_GameControllerName(SDL_GameController *gamecontroller);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player index of an opened game controller, or -1 if it's not available
|
||||||
|
*
|
||||||
|
* For XInput controllers this returns the XInput user index.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_GameControllerGetPlayerIndex(SDL_GameController *gamecontroller);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the USB vendor ID of an opened controller, if available.
|
* Get the USB vendor ID of an opened controller, if available.
|
||||||
* If the vendor ID isn't available this function returns 0.
|
* If the vendor ID isn't available this function returns 0.
|
||||||
@@ -345,6 +360,19 @@ SDL_GameControllerGetBindForButton(SDL_GameController *gamecontroller,
|
|||||||
extern DECLSPEC Uint8 SDLCALL SDL_GameControllerGetButton(SDL_GameController *gamecontroller,
|
extern DECLSPEC Uint8 SDLCALL SDL_GameControllerGetButton(SDL_GameController *gamecontroller,
|
||||||
SDL_GameControllerButton button);
|
SDL_GameControllerButton button);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger a rumble effect
|
||||||
|
* Each call to this function cancels any previous rumble effect, and calling it with 0 intensity stops any rumbling.
|
||||||
|
*
|
||||||
|
* \param gamecontroller The controller to vibrate
|
||||||
|
* \param low_frequency_rumble The intensity of the low frequency (left) rumble motor, from 0 to 0xFFFF
|
||||||
|
* \param high_frequency_rumble The intensity of the high frequency (right) rumble motor, from 0 to 0xFFFF
|
||||||
|
* \param duration_ms The duration of the rumble effect, in milliseconds
|
||||||
|
*
|
||||||
|
* \return 0, or -1 if rumble isn't supported on this joystick
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_GameControllerRumble(SDL_GameController *gamecontroller, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close a controller previously opened with SDL_GameControllerOpen().
|
* Close a controller previously opened with SDL_GameControllerOpen().
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -117,6 +117,17 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif /* __cplusplus */
|
#endif /* __cplusplus */
|
||||||
|
|
||||||
|
/* FIXME: For SDL 2.1, adjust all the magnitude variables to be Uint16 (0xFFFF).
|
||||||
|
*
|
||||||
|
* At the moment the magnitude variables are mixed between signed/unsigned, and
|
||||||
|
* it is also not made clear that ALL of those variables expect a max of 0x7FFF.
|
||||||
|
*
|
||||||
|
* Some platforms may have higher precision than that (Linux FF, Windows XInput)
|
||||||
|
* so we should fix the inconsistency in favor of higher possible precision,
|
||||||
|
* adjusting for platforms that use different scales.
|
||||||
|
* -flibit
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \typedef SDL_Haptic
|
* \typedef SDL_Haptic
|
||||||
*
|
*
|
||||||
@@ -656,8 +667,8 @@ typedef struct SDL_HapticRamp
|
|||||||
* This struct is exclusively for the ::SDL_HAPTIC_LEFTRIGHT effect.
|
* This struct is exclusively for the ::SDL_HAPTIC_LEFTRIGHT effect.
|
||||||
*
|
*
|
||||||
* The Left/Right effect is used to explicitly control the large and small
|
* The Left/Right effect is used to explicitly control the large and small
|
||||||
* motors, commonly found in modern game controllers. One motor is high
|
* motors, commonly found in modern game controllers. The small (right) motor
|
||||||
* frequency, the other is low frequency.
|
* is high frequency, and the large (left) motor is low frequency.
|
||||||
*
|
*
|
||||||
* \sa SDL_HAPTIC_LEFTRIGHT
|
* \sa SDL_HAPTIC_LEFTRIGHT
|
||||||
* \sa SDL_HapticEffect
|
* \sa SDL_HapticEffect
|
||||||
@@ -668,7 +679,7 @@ typedef struct SDL_HapticLeftRight
|
|||||||
Uint16 type; /**< ::SDL_HAPTIC_LEFTRIGHT */
|
Uint16 type; /**< ::SDL_HAPTIC_LEFTRIGHT */
|
||||||
|
|
||||||
/* Replay */
|
/* Replay */
|
||||||
Uint32 length; /**< Duration of the effect. */
|
Uint32 length; /**< Duration of the effect in milliseconds. */
|
||||||
|
|
||||||
/* Rumble */
|
/* Rumble */
|
||||||
Uint16 large_magnitude; /**< Control of the large controller motor. */
|
Uint16 large_magnitude; /**< Control of the large controller motor. */
|
||||||
|
|||||||
@@ -262,6 +262,16 @@ extern "C" {
|
|||||||
*/
|
*/
|
||||||
#define SDL_HINT_GRAB_KEYBOARD "SDL_GRAB_KEYBOARD"
|
#define SDL_HINT_GRAB_KEYBOARD "SDL_GRAB_KEYBOARD"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable setting the double click time, in milliseconds.
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_MOUSE_DOUBLE_CLICK_TIME "SDL_MOUSE_DOUBLE_CLICK_TIME"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable setting the double click radius, in pixels.
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS "SDL_MOUSE_DOUBLE_CLICK_RADIUS"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief A variable setting the speed scale for mouse motion, in floating point, when the mouse is not in relative mode
|
* \brief A variable setting the speed scale for mouse motion, in floating point, when the mouse is not in relative mode
|
||||||
*/
|
*/
|
||||||
@@ -329,7 +339,7 @@ extern "C" {
|
|||||||
#define SDL_HINT_IDLE_TIMER_DISABLED "SDL_IOS_IDLE_TIMER_DISABLED"
|
#define SDL_HINT_IDLE_TIMER_DISABLED "SDL_IOS_IDLE_TIMER_DISABLED"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief A variable controlling which orientations are allowed on iOS.
|
* \brief A variable controlling which orientations are allowed on iOS/Android.
|
||||||
*
|
*
|
||||||
* In some circumstances it is necessary to be able to explicitly control
|
* In some circumstances it is necessary to be able to explicitly control
|
||||||
* which UI orientations are allowed.
|
* which UI orientations are allowed.
|
||||||
@@ -465,6 +475,88 @@ extern "C" {
|
|||||||
*/
|
*/
|
||||||
#define SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS"
|
#define SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable controlling whether the HIDAPI joystick drivers should be used.
|
||||||
|
*
|
||||||
|
* This variable can be set to the following values:
|
||||||
|
* "0" - HIDAPI drivers are not used
|
||||||
|
* "1" - HIDAPI drivers are used (the default)
|
||||||
|
*
|
||||||
|
* This variable is the default for all drivers, but can be overridden by the hints for specific drivers below.
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_JOYSTICK_HIDAPI "SDL_JOYSTICK_HIDAPI"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable controlling whether the HIDAPI driver for PS4 controllers should be used.
|
||||||
|
*
|
||||||
|
* This variable can be set to the following values:
|
||||||
|
* "0" - HIDAPI driver is not used
|
||||||
|
* "1" - HIDAPI driver is used
|
||||||
|
*
|
||||||
|
* The default is the value of SDL_HINT_JOYSTICK_HIDAPI
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_JOYSTICK_HIDAPI_PS4 "SDL_JOYSTICK_HIDAPI_PS4"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable controlling whether extended input reports should be used for PS4 controllers when using the HIDAPI driver.
|
||||||
|
*
|
||||||
|
* This variable can be set to the following values:
|
||||||
|
* "0" - extended reports are not enabled (the default)
|
||||||
|
* "1" - extended reports
|
||||||
|
*
|
||||||
|
* Extended input reports allow rumble on Bluetooth PS4 controllers, but
|
||||||
|
* break DirectInput handling for applications that don't use SDL.
|
||||||
|
*
|
||||||
|
* Once extended reports are enabled, they can not be disabled without
|
||||||
|
* power cycling the controller.
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE "SDL_JOYSTICK_HIDAPI_PS4_RUMBLE"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable controlling whether the HIDAPI driver for Steam Controllers should be used.
|
||||||
|
*
|
||||||
|
* This variable can be set to the following values:
|
||||||
|
* "0" - HIDAPI driver is not used
|
||||||
|
* "1" - HIDAPI driver is used
|
||||||
|
*
|
||||||
|
* The default is the value of SDL_HINT_JOYSTICK_HIDAPI
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_JOYSTICK_HIDAPI_STEAM "SDL_JOYSTICK_HIDAPI_STEAM"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable controlling whether the HIDAPI driver for Nintendo Switch controllers should be used.
|
||||||
|
*
|
||||||
|
* This variable can be set to the following values:
|
||||||
|
* "0" - HIDAPI driver is not used
|
||||||
|
* "1" - HIDAPI driver is used
|
||||||
|
*
|
||||||
|
* The default is the value of SDL_HINT_JOYSTICK_HIDAPI
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH "SDL_JOYSTICK_HIDAPI_SWITCH"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable controlling whether the HIDAPI driver for XBox controllers should be used.
|
||||||
|
*
|
||||||
|
* This variable can be set to the following values:
|
||||||
|
* "0" - HIDAPI driver is not used
|
||||||
|
* "1" - HIDAPI driver is used
|
||||||
|
*
|
||||||
|
* The default is the value of SDL_HINT_JOYSTICK_HIDAPI
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_JOYSTICK_HIDAPI_XBOX "SDL_JOYSTICK_HIDAPI_XBOX"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable that controls whether Steam Controllers should be exposed using the SDL joystick and game controller APIs
|
||||||
|
*
|
||||||
|
* The variable can be set to the following values:
|
||||||
|
* "0" - Do not scan for Steam Controllers
|
||||||
|
* "1" - Scan for Steam Controllers (the default)
|
||||||
|
*
|
||||||
|
* The default value is "1". This hint must be set before initializing the joystick subsystem.
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_ENABLE_STEAM_CONTROLLERS "SDL_ENABLE_STEAM_CONTROLLERS"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief If set to "0" then never set the top most bit on a SDL Window, even if the video mode expects it.
|
* \brief If set to "0" then never set the top most bit on a SDL Window, even if the video mode expects it.
|
||||||
* This is a debugging aid for developers and not expected to be used by end users. The default is "1"
|
* This is a debugging aid for developers and not expected to be used by end users. The default is "1"
|
||||||
@@ -527,6 +619,10 @@ extern "C" {
|
|||||||
* This is specially useful if you build SDL against a non glibc libc library (such as musl) which
|
* This is specially useful if you build SDL against a non glibc libc library (such as musl) which
|
||||||
* provides a relatively small default thread stack size (a few kilobytes versus the default 8MB glibc uses).
|
* provides a relatively small default thread stack size (a few kilobytes versus the default 8MB glibc uses).
|
||||||
* Support for this hint is currently available only in the pthread, Windows, and PSP backend.
|
* Support for this hint is currently available only in the pthread, Windows, and PSP backend.
|
||||||
|
*
|
||||||
|
* Instead of this hint, in 2.0.9 and later, you can use
|
||||||
|
* SDL_CreateThreadWithStackSize(). This hint only works with the classic
|
||||||
|
* SDL_CreateThread().
|
||||||
*/
|
*/
|
||||||
#define SDL_HINT_THREAD_STACK_SIZE "SDL_THREAD_STACK_SIZE"
|
#define SDL_HINT_THREAD_STACK_SIZE "SDL_THREAD_STACK_SIZE"
|
||||||
|
|
||||||
@@ -752,6 +848,23 @@ extern "C" {
|
|||||||
*/
|
*/
|
||||||
#define SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH "SDL_ANDROID_SEPARATE_MOUSE_AND_TOUCH"
|
#define SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH "SDL_ANDROID_SEPARATE_MOUSE_AND_TOUCH"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable to control whether we trap the Android back button to handle it manually.
|
||||||
|
* This is necessary for the right mouse button to work on some Android devices, or
|
||||||
|
* to be able to trap the back button for use in your code reliably. If set to true,
|
||||||
|
* the back button will show up as an SDL_KEYDOWN / SDL_KEYUP pair with a keycode of
|
||||||
|
* SDL_SCANCODE_AC_BACK.
|
||||||
|
*
|
||||||
|
* The variable can be set to the following values:
|
||||||
|
* "0" - Back button will be handled as usual for system. (default)
|
||||||
|
* "1" - Back button will be trapped, allowing you to handle the key press
|
||||||
|
* manually. (This will also let right mouse click work on systems
|
||||||
|
* where the right mouse button functions as back.)
|
||||||
|
*
|
||||||
|
* The value of this hint is used at runtime, so it can be changed at any time.
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief A variable to control whether the return key on the soft keyboard
|
* \brief A variable to control whether the return key on the soft keyboard
|
||||||
* should hide the soft keyboard on Android and iOS.
|
* should hide the soft keyboard on Android and iOS.
|
||||||
|
|||||||
@@ -97,10 +97,10 @@ typedef enum
|
|||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
SDL_JOYSTICK_POWER_UNKNOWN = -1,
|
SDL_JOYSTICK_POWER_UNKNOWN = -1,
|
||||||
SDL_JOYSTICK_POWER_EMPTY,
|
SDL_JOYSTICK_POWER_EMPTY, /* <= 5% */
|
||||||
SDL_JOYSTICK_POWER_LOW,
|
SDL_JOYSTICK_POWER_LOW, /* <= 20% */
|
||||||
SDL_JOYSTICK_POWER_MEDIUM,
|
SDL_JOYSTICK_POWER_MEDIUM, /* <= 70% */
|
||||||
SDL_JOYSTICK_POWER_FULL,
|
SDL_JOYSTICK_POWER_FULL, /* <= 100% */
|
||||||
SDL_JOYSTICK_POWER_WIRED,
|
SDL_JOYSTICK_POWER_WIRED,
|
||||||
SDL_JOYSTICK_POWER_MAX
|
SDL_JOYSTICK_POWER_MAX
|
||||||
} SDL_JoystickPowerLevel;
|
} SDL_JoystickPowerLevel;
|
||||||
@@ -132,6 +132,12 @@ extern DECLSPEC int SDLCALL SDL_NumJoysticks(void);
|
|||||||
*/
|
*/
|
||||||
extern DECLSPEC const char *SDLCALL SDL_JoystickNameForIndex(int device_index);
|
extern DECLSPEC const char *SDLCALL SDL_JoystickNameForIndex(int device_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player index of a joystick, or -1 if it's not available
|
||||||
|
* This can be called before any joysticks are opened.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_JoystickGetDevicePlayerIndex(int device_index);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the GUID for the joystick at this index
|
* Return the GUID for the joystick at this index
|
||||||
* This can be called before any joysticks are opened.
|
* This can be called before any joysticks are opened.
|
||||||
@@ -194,6 +200,13 @@ extern DECLSPEC SDL_Joystick *SDLCALL SDL_JoystickFromInstanceID(SDL_JoystickID
|
|||||||
*/
|
*/
|
||||||
extern DECLSPEC const char *SDLCALL SDL_JoystickName(SDL_Joystick * joystick);
|
extern DECLSPEC const char *SDLCALL SDL_JoystickName(SDL_Joystick * joystick);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player index of an opened joystick, or -1 if it's not available
|
||||||
|
*
|
||||||
|
* For XInput controllers this returns the XInput user index.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_JoystickGetPlayerIndex(SDL_Joystick * joystick);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the GUID for this opened joystick
|
* Return the GUID for this opened joystick
|
||||||
*/
|
*/
|
||||||
@@ -361,6 +374,19 @@ extern DECLSPEC int SDLCALL SDL_JoystickGetBall(SDL_Joystick * joystick,
|
|||||||
extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetButton(SDL_Joystick * joystick,
|
extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetButton(SDL_Joystick * joystick,
|
||||||
int button);
|
int button);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger a rumble effect
|
||||||
|
* Each call to this function cancels any previous rumble effect, and calling it with 0 intensity stops any rumbling.
|
||||||
|
*
|
||||||
|
* \param joystick The joystick to vibrate
|
||||||
|
* \param low_frequency_rumble The intensity of the low frequency (left) rumble motor, from 0 to 0xFFFF
|
||||||
|
* \param high_frequency_rumble The intensity of the high frequency (right) rumble motor, from 0 to 0xFFFF
|
||||||
|
* \param duration_ms The duration of the rumble effect, in milliseconds
|
||||||
|
*
|
||||||
|
* \return 0, or -1 if rumble isn't supported on this joystick
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close a joystick previously opened with SDL_JoystickOpen().
|
* Close a joystick previously opened with SDL_JoystickOpen().
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
#define SDL_REVISION "hg-11914:f1084c419f33"
|
#define SDL_REVISION "hg-0:aaaaaaaaaaah"
|
||||||
#define SDL_REVISION_NUMBER 11914
|
#define SDL_REVISION_NUMBER 0
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
Simple DirectMedia Layer
|
||||||
|
Copyright (C) 1997-2018 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_sensor.h
|
||||||
|
*
|
||||||
|
* Include file for SDL sensor event handling
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _SDL_sensor_h
|
||||||
|
#define _SDL_sensor_h
|
||||||
|
|
||||||
|
#include "SDL_stdinc.h"
|
||||||
|
#include "SDL_error.h"
|
||||||
|
|
||||||
|
#include "begin_code.h"
|
||||||
|
/* Set up for C function definitions, even when using C++ */
|
||||||
|
#ifdef __cplusplus
|
||||||
|
/* *INDENT-OFF* */
|
||||||
|
extern "C" {
|
||||||
|
/* *INDENT-ON* */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief SDL_sensor.h
|
||||||
|
*
|
||||||
|
* In order to use these functions, SDL_Init() must have been called
|
||||||
|
* with the ::SDL_INIT_SENSOR flag. This causes SDL to scan the system
|
||||||
|
* for sensors, and load appropriate drivers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct _SDL_Sensor;
|
||||||
|
typedef struct _SDL_Sensor SDL_Sensor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a unique ID for a sensor for the time it is connected to the system,
|
||||||
|
* and is never reused for the lifetime of the application.
|
||||||
|
*
|
||||||
|
* The ID value starts at 0 and increments from there. The value -1 is an invalid ID.
|
||||||
|
*/
|
||||||
|
typedef Sint32 SDL_SensorID;
|
||||||
|
|
||||||
|
/* The different sensors defined by SDL
|
||||||
|
*
|
||||||
|
* Additional sensors may be available, using platform dependent semantics.
|
||||||
|
*
|
||||||
|
* Hare are the additional Android sensors:
|
||||||
|
* https://developer.android.com/reference/android/hardware/SensorEvent.html#values
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SDL_SENSOR_INVALID = -1, /**< Returned for an invalid sensor */
|
||||||
|
SDL_SENSOR_UNKNOWN, /**< Unknown sensor type */
|
||||||
|
SDL_SENSOR_ACCEL, /**< Accelerometer */
|
||||||
|
SDL_SENSOR_GYRO /**< Gyroscope */
|
||||||
|
} SDL_SensorType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accelerometer sensor
|
||||||
|
*
|
||||||
|
* The accelerometer returns the current acceleration in SI meters per
|
||||||
|
* second squared. This includes gravity, so a device at rest will have
|
||||||
|
* an acceleration of SDL_STANDARD_GRAVITY straight down.
|
||||||
|
*
|
||||||
|
* values[0]: Acceleration on the x axis
|
||||||
|
* values[1]: Acceleration on the y axis
|
||||||
|
* values[2]: Acceleration on the z axis
|
||||||
|
*
|
||||||
|
* For phones held in portrait mode, the axes are defined as follows:
|
||||||
|
* -X ... +X : left ... right
|
||||||
|
* -Y ... +Y : bottom ... top
|
||||||
|
* -Z ... +Z : farther ... closer
|
||||||
|
*
|
||||||
|
* The axis data is not changed when the phone is rotated.
|
||||||
|
*
|
||||||
|
* \sa SDL_GetDisplayOrientation()
|
||||||
|
*/
|
||||||
|
#define SDL_STANDARD_GRAVITY 9.80665f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gyroscope sensor
|
||||||
|
*
|
||||||
|
* The gyroscope returns the current rate of rotation in radians per second.
|
||||||
|
* The rotation is positive in the counter-clockwise direction. That is,
|
||||||
|
* an observer looking from a positive location on one of the axes would
|
||||||
|
* see positive rotation on that axis when it appeared to be rotating
|
||||||
|
* counter-clockwise.
|
||||||
|
*
|
||||||
|
* values[0]: Angular speed around the x axis
|
||||||
|
* values[1]: Angular speed around the y axis
|
||||||
|
* values[2]: Angular speed around the z axis
|
||||||
|
*
|
||||||
|
* For phones held in portrait mode, the axes are defined as follows:
|
||||||
|
* -X ... +X : left ... right
|
||||||
|
* -Y ... +Y : bottom ... top
|
||||||
|
* -Z ... +Z : farther ... closer
|
||||||
|
*
|
||||||
|
* The axis data is not changed when the phone is rotated.
|
||||||
|
*
|
||||||
|
* \sa SDL_GetDisplayOrientation()
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Function prototypes */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Count the number of sensors attached to the system right now
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_NumSensors(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get the implementation dependent name of a sensor.
|
||||||
|
*
|
||||||
|
* This can be called before any sensors are opened.
|
||||||
|
*
|
||||||
|
* \return The sensor name, or NULL if device_index is out of range.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC const char *SDLCALL SDL_SensorGetDeviceName(int device_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get the type of a sensor.
|
||||||
|
*
|
||||||
|
* This can be called before any sensors are opened.
|
||||||
|
*
|
||||||
|
* \return The sensor type, or SDL_SENSOR_INVALID if device_index is out of range.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC SDL_SensorType SDLCALL SDL_SensorGetDeviceType(int device_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get the platform dependent type of a sensor.
|
||||||
|
*
|
||||||
|
* This can be called before any sensors are opened.
|
||||||
|
*
|
||||||
|
* \return The sensor platform dependent type, or -1 if device_index is out of range.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_SensorGetDeviceNonPortableType(int device_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get the instance ID of a sensor.
|
||||||
|
*
|
||||||
|
* This can be called before any sensors are opened.
|
||||||
|
*
|
||||||
|
* \return The sensor instance ID, or -1 if device_index is out of range.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC SDL_SensorID SDLCALL SDL_SensorGetDeviceInstanceID(int device_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Open a sensor for use.
|
||||||
|
*
|
||||||
|
* The index passed as an argument refers to the N'th sensor on the system.
|
||||||
|
*
|
||||||
|
* \return A sensor identifier, or NULL if an error occurred.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC SDL_Sensor *SDLCALL SDL_SensorOpen(int device_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the SDL_Sensor associated with an instance id.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC SDL_Sensor *SDLCALL SDL_SensorFromInstanceID(SDL_SensorID instance_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get the implementation dependent name of a sensor.
|
||||||
|
*
|
||||||
|
* \return The sensor name, or NULL if the sensor is NULL.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC const char *SDLCALL SDL_SensorGetName(SDL_Sensor *sensor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get the type of a sensor.
|
||||||
|
*
|
||||||
|
* This can be called before any sensors are opened.
|
||||||
|
*
|
||||||
|
* \return The sensor type, or SDL_SENSOR_INVALID if the sensor is NULL.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC SDL_SensorType SDLCALL SDL_SensorGetType(SDL_Sensor *sensor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get the platform dependent type of a sensor.
|
||||||
|
*
|
||||||
|
* This can be called before any sensors are opened.
|
||||||
|
*
|
||||||
|
* \return The sensor platform dependent type, or -1 if the sensor is NULL.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_SensorGetNonPortableType(SDL_Sensor *sensor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get the instance ID of a sensor.
|
||||||
|
*
|
||||||
|
* This can be called before any sensors are opened.
|
||||||
|
*
|
||||||
|
* \return The sensor instance ID, or -1 if the sensor is NULL.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC SDL_SensorID SDLCALL SDL_SensorGetInstanceID(SDL_Sensor *sensor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current state of an opened sensor.
|
||||||
|
*
|
||||||
|
* The number of values and interpretation of the data is sensor dependent.
|
||||||
|
*
|
||||||
|
* \param sensor The sensor to query
|
||||||
|
* \param data A pointer filled with the current sensor state
|
||||||
|
* \param num_values The number of values to write to data
|
||||||
|
*
|
||||||
|
* \return 0 or -1 if an error occurred.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_SensorGetData(SDL_Sensor * sensor, float *data, int num_values);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close a sensor previously opened with SDL_SensorOpen()
|
||||||
|
*/
|
||||||
|
extern DECLSPEC void SDLCALL SDL_SensorClose(SDL_Sensor * sensor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current state of the open sensors.
|
||||||
|
*
|
||||||
|
* This is called automatically by the event loop if sensor events are enabled.
|
||||||
|
*
|
||||||
|
* This needs to be called from the thread that initialized the sensor subsystem.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC void SDLCALL SDL_SensorUpdate(void);
|
||||||
|
|
||||||
|
|
||||||
|
/* Ends C function definitions when using C++ */
|
||||||
|
#ifdef __cplusplus
|
||||||
|
/* *INDENT-OFF* */
|
||||||
|
}
|
||||||
|
/* *INDENT-ON* */
|
||||||
|
#endif
|
||||||
|
#include "close_code.h"
|
||||||
|
|
||||||
|
#endif /* _SDL_sensor_h */
|
||||||
|
|
||||||
|
/* vi: set ts=4 sw=4 expandtab: */
|
||||||
@@ -86,6 +86,28 @@
|
|||||||
#ifdef HAVE_FLOAT_H
|
#ifdef HAVE_FLOAT_H
|
||||||
# include <float.h>
|
# include <float.h>
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(HAVE_ALLOCA) && !defined(alloca)
|
||||||
|
# if defined(HAVE_ALLOCA_H)
|
||||||
|
# include <alloca.h>
|
||||||
|
# elif defined(__GNUC__)
|
||||||
|
# define alloca __builtin_alloca
|
||||||
|
# elif defined(_MSC_VER)
|
||||||
|
# include <malloc.h>
|
||||||
|
# define alloca _alloca
|
||||||
|
# elif defined(__WATCOMC__)
|
||||||
|
# include <malloc.h>
|
||||||
|
# elif defined(__BORLANDC__)
|
||||||
|
# include <malloc.h>
|
||||||
|
# elif defined(__DMC__)
|
||||||
|
# include <stdlib.h>
|
||||||
|
# elif defined(__AIX__)
|
||||||
|
#pragma alloca
|
||||||
|
# elif defined(__MRC__)
|
||||||
|
void *alloca(unsigned);
|
||||||
|
# else
|
||||||
|
char *alloca();
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of elements in an array.
|
* The number of elements in an array.
|
||||||
@@ -328,28 +350,6 @@ SDL_COMPILE_TIME_ASSERT(enum, sizeof(SDL_DUMMY_ENUM) == sizeof(int));
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(HAVE_ALLOCA) && !defined(alloca)
|
|
||||||
# if defined(HAVE_ALLOCA_H)
|
|
||||||
# include <alloca.h>
|
|
||||||
# elif defined(__GNUC__)
|
|
||||||
# define alloca __builtin_alloca
|
|
||||||
# elif defined(_MSC_VER)
|
|
||||||
# include <malloc.h>
|
|
||||||
# define alloca _alloca
|
|
||||||
# elif defined(__WATCOMC__)
|
|
||||||
# include <malloc.h>
|
|
||||||
# elif defined(__BORLANDC__)
|
|
||||||
# include <malloc.h>
|
|
||||||
# elif defined(__DMC__)
|
|
||||||
# include <stdlib.h>
|
|
||||||
# elif defined(__AIX__)
|
|
||||||
#pragma alloca
|
|
||||||
# elif defined(__MRC__)
|
|
||||||
void *alloca(unsigned);
|
|
||||||
# else
|
|
||||||
char *alloca();
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
#ifdef HAVE_ALLOCA
|
#ifdef HAVE_ALLOCA
|
||||||
#define SDL_stack_alloc(type, count) (type*)alloca(sizeof(type)*(count))
|
#define SDL_stack_alloc(type, count) (type*)alloca(sizeof(type)*(count))
|
||||||
#define SDL_stack_free(data)
|
#define SDL_stack_free(data)
|
||||||
@@ -445,12 +445,12 @@ SDL_FORCE_INLINE void SDL_memset4(void *dst, Uint32 val, size_t dwords)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
extern DECLSPEC void *SDLCALL SDL_memcpy(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
|
extern DECLSPEC void *SDLCALL SDL_memcpy(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
|
||||||
|
|
||||||
extern DECLSPEC void *SDLCALL SDL_memmove(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
|
extern DECLSPEC void *SDLCALL SDL_memmove(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
|
||||||
extern DECLSPEC int SDLCALL SDL_memcmp(const void *s1, const void *s2, size_t len);
|
extern DECLSPEC int SDLCALL SDL_memcmp(const void *s1, const void *s2, size_t len);
|
||||||
|
|
||||||
|
extern DECLSPEC wchar_t *SDLCALL SDL_wcsdup(const wchar_t *wstr);
|
||||||
extern DECLSPEC size_t SDLCALL SDL_wcslen(const wchar_t *wstr);
|
extern DECLSPEC size_t SDLCALL SDL_wcslen(const wchar_t *wstr);
|
||||||
extern DECLSPEC size_t SDLCALL SDL_wcslcpy(SDL_OUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
|
extern DECLSPEC size_t SDLCALL SDL_wcslcpy(SDL_OUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
|
||||||
extern DECLSPEC size_t SDLCALL SDL_wcslcat(SDL_INOUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
|
extern DECLSPEC size_t SDLCALL SDL_wcslcat(SDL_INOUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
|
||||||
@@ -514,6 +514,8 @@ extern DECLSPEC double SDLCALL SDL_copysign(double x, double y);
|
|||||||
extern DECLSPEC float SDLCALL SDL_copysignf(float x, float y);
|
extern DECLSPEC float SDLCALL SDL_copysignf(float x, float y);
|
||||||
extern DECLSPEC double SDLCALL SDL_cos(double x);
|
extern DECLSPEC double SDLCALL SDL_cos(double x);
|
||||||
extern DECLSPEC float SDLCALL SDL_cosf(float x);
|
extern DECLSPEC float SDLCALL SDL_cosf(float x);
|
||||||
|
extern DECLSPEC double SDLCALL SDL_exp(double x);
|
||||||
|
extern DECLSPEC float SDLCALL SDL_expf(float x);
|
||||||
extern DECLSPEC double SDLCALL SDL_fabs(double x);
|
extern DECLSPEC double SDLCALL SDL_fabs(double x);
|
||||||
extern DECLSPEC float SDLCALL SDL_fabsf(float x);
|
extern DECLSPEC float SDLCALL SDL_fabsf(float x);
|
||||||
extern DECLSPEC double SDLCALL SDL_floor(double x);
|
extern DECLSPEC double SDLCALL SDL_floor(double x);
|
||||||
|
|||||||
@@ -248,6 +248,13 @@ extern DECLSPEC int SDLCALL SDL_SetSurfaceRLE(SDL_Surface * surface,
|
|||||||
extern DECLSPEC int SDLCALL SDL_SetColorKey(SDL_Surface * surface,
|
extern DECLSPEC int SDLCALL SDL_SetColorKey(SDL_Surface * surface,
|
||||||
int flag, Uint32 key);
|
int flag, Uint32 key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Returns whether the surface has a color key
|
||||||
|
*
|
||||||
|
* \return SDL_TRUE if the surface has a color key, or SDL_FALSE if the surface is NULL or has no color key
|
||||||
|
*/
|
||||||
|
extern DECLSPEC SDL_bool SDLCALL SDL_HasColorKey(SDL_Surface * surface);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Gets the color key (transparent pixel) in a blittable surface.
|
* \brief Gets the color key (transparent pixel) in a blittable surface.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -76,6 +76,18 @@ extern DECLSPEC SDL_bool SDLCALL SDL_DXGIGetOutputInfo( int displayIndex, int *a
|
|||||||
#endif /* __WIN32__ */
|
#endif /* __WIN32__ */
|
||||||
|
|
||||||
|
|
||||||
|
/* Platform specific functions for Linux */
|
||||||
|
#ifdef __LINUX__
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Sets the UNIX nice value for a thread, using setpriority() if possible, and RealtimeKit if available.
|
||||||
|
|
||||||
|
\return 0 on success, or -1 on error.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_LinuxSetThreadPriority(Sint64 threadID, int priority);
|
||||||
|
|
||||||
|
#endif /* __LINUX__ */
|
||||||
|
|
||||||
/* Platform specific functions for iOS */
|
/* Platform specific functions for iOS */
|
||||||
#if defined(__IPHONEOS__) && __IPHONEOS__
|
#if defined(__IPHONEOS__) && __IPHONEOS__
|
||||||
|
|
||||||
@@ -113,6 +125,21 @@ extern DECLSPEC void * SDLCALL SDL_AndroidGetActivity(void);
|
|||||||
*/
|
*/
|
||||||
extern DECLSPEC SDL_bool SDLCALL SDL_IsAndroidTV(void);
|
extern DECLSPEC SDL_bool SDLCALL SDL_IsAndroidTV(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Return true if the application is running on a Chromebook
|
||||||
|
*/
|
||||||
|
extern DECLSPEC SDL_bool SDLCALL SDL_IsChromebook(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Return true is the application is running on a Samsung DeX docking station
|
||||||
|
*/
|
||||||
|
extern DECLSPEC SDL_bool SDLCALL SDL_IsDeXMode(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Trigger the Android system back button behavior.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC void SDLCALL SDL_AndroidBackButton(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
See the official Android developer guide for more information:
|
See the official Android developer guide for more information:
|
||||||
http://developer.android.com/guide/topics/data/data-storage.html
|
http://developer.android.com/guide/topics/data/data-storage.html
|
||||||
@@ -236,6 +263,11 @@ extern DECLSPEC SDL_WinRT_DeviceFamily SDLCALL SDL_WinRTGetDeviceFamily();
|
|||||||
|
|
||||||
#endif /* __WINRT__ */
|
#endif /* __WINRT__ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Return true if the current device is a tablet.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC SDL_bool SDLCALL SDL_IsTablet(void);
|
||||||
|
|
||||||
/* Ends C function definitions when using C++ */
|
/* Ends C function definitions when using C++ */
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,12 +33,6 @@
|
|||||||
#include "SDL_video.h"
|
#include "SDL_video.h"
|
||||||
#include "SDL_version.h"
|
#include "SDL_version.h"
|
||||||
|
|
||||||
#include "begin_code.h"
|
|
||||||
/* Set up for C function definitions, even when using C++ */
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \file SDL_syswm.h
|
* \file SDL_syswm.h
|
||||||
*
|
*
|
||||||
@@ -110,6 +104,12 @@ typedef void *EGLSurface;
|
|||||||
#include "SDL_egl.h"
|
#include "SDL_egl.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "begin_code.h"
|
||||||
|
/* Set up for C function definitions, even when using C++ */
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These are the various supported windowing subsystems
|
* These are the various supported windowing subsystems
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loaded 100 of 156 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user