mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-12 03:24:00 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17a2548480 | ||
|
|
753deb915f | ||
|
|
7d6f4135d5 | ||
|
|
622ec843ff | ||
|
|
5c18bf1608 | ||
|
|
d184730a82 | ||
|
|
335ed0e8e6 | ||
|
|
0abb9fd7c2 | ||
|
|
1a1ce05959 | ||
|
|
93408386ae | ||
|
|
415b512c75 | ||
|
|
d389f9a6e9 | ||
|
|
ba01558bc5 | ||
|
|
dff02eab7d | ||
|
|
ecfcedad58 | ||
|
|
e76780e105 | ||
|
|
1d1fa0577b | ||
|
|
a9d7c8e495 | ||
|
|
ffdf683597 | ||
|
|
ac0e1098b9 | ||
|
|
35695642d5 | ||
|
|
e458682a23 | ||
|
|
fbe5e6f01f | ||
|
|
52756e7f45 | ||
|
|
0f57abf6f8 | ||
|
|
94943d2865 | ||
|
|
c3bd7edc4e | ||
|
|
b6bb96223d | ||
|
|
f07f3c843a | ||
|
|
7712a2dce4 | ||
|
|
d86e13a3a7 | ||
|
|
3aaa09bb7d | ||
|
|
377abf2155 | ||
|
|
d3219ae24a | ||
|
|
bd60b873ec | ||
|
|
3abf925810 | ||
|
|
d5cbcc8199 | ||
|
|
640ac3f9fe | ||
|
|
83811e2a07 | ||
|
|
31641f5246 | ||
|
|
d7bc735edc | ||
|
|
b59de38e0b | ||
|
|
17af71fe7a | ||
|
|
a0c77d0ad8 | ||
|
|
ec7137e693 | ||
|
|
1b56f28fd4 | ||
|
|
486c46781b | ||
|
|
6397031e46 | ||
|
|
e7a2d9f44d | ||
|
|
ee5d0acc1f | ||
|
|
6a5a69cabd | ||
|
|
cd7391f908 | ||
|
|
d6d28f1afd | ||
|
|
5a6620a9ae | ||
|
|
5a47ddca33 | ||
|
|
b58f021fc8 | ||
|
|
12b93337d1 | ||
|
|
807fc6b31c | ||
|
|
959080aa95 | ||
|
|
18a772aeba | ||
|
|
9bdd17f1c3 | ||
|
|
3d1967bd1e | ||
|
|
469bd32bde | ||
|
|
351b4edf0c | ||
|
|
a0a8b70bc1 | ||
|
|
e07f069222 | ||
|
|
47ccd1e481 | ||
|
|
96fba43186 | ||
|
|
9e7c212c6d | ||
|
|
e67d83e097 | ||
|
|
4474f815af | ||
|
|
e130a15037 | ||
|
|
845ef147c9 | ||
|
|
fb17c15cfd | ||
|
|
bb74cedf59 | ||
|
|
9af6906380 | ||
|
|
c1ae3cceda | ||
|
|
e1e3bd512f | ||
|
|
cf3ff32340 | ||
|
|
2c98deeae3 | ||
|
|
a572681737 | ||
|
|
e78b389bc0 | ||
|
|
83ec929758 | ||
|
|
9117f6565e | ||
|
|
aa33432c81 | ||
|
|
e25d9b0da2 | ||
|
|
0dfa8a7b53 | ||
|
|
85a9f85c54 | ||
|
|
ed68f920f1 | ||
|
|
f9a4498f9b | ||
|
|
4d1acf2e30 | ||
|
|
db4739b82d | ||
|
|
38bf833708 | ||
|
|
364093aef8 | ||
|
|
6d220a9062 | ||
|
|
abec8b3fe1 | ||
|
|
93ed985043 | ||
|
|
23c737cab9 | ||
|
|
cb850f013a | ||
|
|
6bd964a094 | ||
|
|
419c788ab5 | ||
|
|
a412100a11 | ||
|
|
0531666f38 | ||
|
|
c096238998 | ||
|
|
211fec0ff0 | ||
|
|
58e27875ae | ||
|
|
954e6608a1 | ||
|
|
1cd588f25c | ||
|
|
ebfe035a18 | ||
|
|
6f22ed13c1 | ||
|
|
4aa2b8570c | ||
|
|
fe6562e31c | ||
|
|
654e386263 | ||
|
|
76d0eb6b63 | ||
|
|
02d867fe09 | ||
|
|
76ff53f09f | ||
|
|
37d92dee16 | ||
|
|
afbc49e39a | ||
|
|
d1ccd19fcc | ||
|
|
ebe270bec5 | ||
|
|
784234c857 | ||
|
|
6a85996b4a | ||
|
|
c6710f93dc | ||
|
|
5e46c4ec2b | ||
|
|
011feab6ce | ||
|
|
f8c5d3c0ce | ||
|
|
74d452bf35 | ||
|
|
fd2ff61a40 | ||
|
|
8a87a09947 | ||
|
|
cf544a8703 | ||
|
|
0e1190a059 | ||
|
|
f2535f1e6e | ||
|
|
cefa567f95 | ||
|
|
9be782ae8e | ||
|
|
610e07d4bd | ||
|
|
75eb921a04 | ||
|
|
97bd515b11 | ||
|
|
a45695a22a | ||
|
|
0d67fd1ff0 | ||
|
|
a093a0ae59 | ||
|
|
c9ad8ffa69 | ||
|
|
9987914ebb | ||
|
|
fbfd6fb462 | ||
|
|
bec053e3cf | ||
|
|
d364b24378 | ||
|
|
8c67586cd0 | ||
|
|
89a628a0dd | ||
|
|
4fb9681b7f | ||
|
|
c13128ca1d | ||
|
|
ceca83346b | ||
|
|
fc6c51c75a | ||
|
|
161d91acf8 | ||
|
|
06ef10a2ad | ||
|
|
7b29b22192 | ||
|
|
289a7165b3 | ||
|
|
62011c37f4 | ||
|
|
919e44b29b | ||
|
|
32be1b158b | ||
|
|
f635860afd | ||
|
|
3b078f095b | ||
|
|
72fc7ea31a | ||
|
|
f4edd1e63a | ||
|
|
6011384afe | ||
|
|
896456a8ac | ||
|
|
8959ddbeb6 | ||
|
|
767215fce5 | ||
|
|
3ef05f3a35 | ||
|
|
12624bc5c4 | ||
|
|
5bd0dea85d | ||
|
|
80242f2e90 | ||
|
|
cf3e328f9d | ||
|
|
150002d4d4 | ||
|
|
b972a22459 | ||
|
|
db06239018 | ||
|
|
bdc7d53515 | ||
|
|
7f83b79c19 | ||
|
|
47b3aff352 | ||
|
|
679f237c1b | ||
|
|
3d8de56f25 | ||
|
|
801cebbbc7 | ||
|
|
66fb0465c4 | ||
|
|
8bbc9b2c57 | ||
|
|
be7694dd8b | ||
|
|
ce8b24dfa7 | ||
|
|
75ddac54c5 | ||
|
|
b01a83ff39 | ||
|
|
c3e886fbcc | ||
|
|
e20d56041e | ||
|
|
114074bfd8 | ||
|
|
e44e29e7eb | ||
|
|
03663d5552 | ||
|
|
dce2a857f7 | ||
|
|
61f71a318e | ||
|
|
646ce2bf66 | ||
|
|
2bbae90e2c | ||
|
|
bb8e229814 | ||
|
|
9ed8252cc6 | ||
|
|
2cef09471b | ||
|
|
2d15382e31 | ||
|
|
770872238d | ||
|
|
2a05b890d8 | ||
|
|
481f23b6e9 | ||
|
|
4e7107a09e | ||
|
|
0223394725 | ||
|
|
058739321c | ||
|
|
708bec2937 | ||
|
|
c939537c5c | ||
|
|
9497657c83 | ||
|
|
d4050f3a4f | ||
|
|
c74c4ef041 | ||
|
|
d75663052d | ||
|
|
8f2db99406 | ||
|
|
6a58dbb05e | ||
|
|
8aeb976128 | ||
|
|
7d51a4b67d | ||
|
|
f7d412e3bf | ||
|
|
4469c7fae3 | ||
|
|
ee95efc0f1 | ||
|
|
3ddc8ad68a | ||
|
|
333d97160e | ||
|
|
aac5ad3fd0 | ||
|
|
1ef083c952 | ||
|
|
c63b918bb2 | ||
|
|
4dba4ad538 | ||
|
|
83fcad6bd1 | ||
|
|
e08178474d | ||
|
|
f374e88bac | ||
|
|
3cb169ea26 | ||
|
|
58f87e39a9 | ||
|
|
b1c8005d1a | ||
|
|
f8a2e22a18 | ||
|
|
7ea118fbae | ||
|
|
2149024c35 | ||
|
|
f5d71c8c9a | ||
|
|
e108a05895 | ||
|
|
ea8bccd508 | ||
|
|
bca919c48c | ||
|
|
511ffe03b5 |
@@ -37,12 +37,13 @@ List the games you've tried that exhibit the issue. To see if the issue is game-
|
||||
- OS: [e.g. Windows 10 1809]
|
||||
- Moonlight Version: [e.g. v0.9.0]
|
||||
- GPU: [e.g. Intel HD Graphics 520]
|
||||
- Linux package type (if applicable): [e.g. Flatpak]
|
||||
|
||||
**Server PC details (please complete the following information)**
|
||||
- OS: [e.g. Windows 10 1809]
|
||||
- GeForce Experience version: [e.g. 3.16.0.140]
|
||||
- Nvidia GPU driver: [e.g. 417.35]
|
||||
- Antivirus and firewall software: [e.g. Windows Defender and Windows Firewall]
|
||||
- Sunshine or GeForce Experience version: [e.g. Sunshine v0.21.0]
|
||||
- GPU: [e.g. AMD Radeon RX 7900 XT]
|
||||
- GPU driver: [e.g. 24.1.1]
|
||||
|
||||
**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.
|
||||
|
||||
@@ -158,17 +158,6 @@ private:
|
||||
L"EZFRD32.dll",
|
||||
L"EZFRD64.dll",
|
||||
|
||||
// These are the newer dList DLLs for Optimus hybrid graphics DDI.
|
||||
// https://docs.microsoft.com/en-us/windows-hardware/drivers/display/hybrid-system-ddi
|
||||
//
|
||||
// We forcefully block them from loading because Optimus has a bug that
|
||||
// deadlocks DXVA2 when we present with D3DPRESENT_DONOTWAIT. This will prevent
|
||||
// Optimus from ever using the dGPU even if the user has requested it.
|
||||
// https://github.com/moonlight-stream/moonlight-qt/issues/240
|
||||
// https://github.com/moonlight-stream/moonlight-qt/issues/235
|
||||
L"nvdlist.dll",
|
||||
L"nvdlistx.dll",
|
||||
|
||||
// These are the older dList/AppInit DLLs for Optimus hybrid graphics DDI.
|
||||
// https://docs.microsoft.com/en-us/windows-hardware/drivers/display/hybrid-system-ddi
|
||||
//
|
||||
|
||||
@@ -25,37 +25,38 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
|
||||
- [Snap (for Ubuntu-based Linux distros)](https://snapcraft.io/moonlight)
|
||||
- [Flatpak (for other Linux distros)](https://flathub.org/apps/details/com.moonlight_stream.Moonlight)
|
||||
- [AppImage](https://github.com/moonlight-stream/moonlight-qt/releases)
|
||||
- [Raspberry Pi 4](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-Raspberry-Pi-4)
|
||||
- [Raspberry Pi 4 and 5](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-Raspberry-Pi-4)
|
||||
- [Generic ARM 32-bit and 64-bit Debian packages](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-ARM%E2%80%90based-Single-Board-Computers) (not for Raspberry Pi)
|
||||
- [Experimental RISC-V Debian packages](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-RISC%E2%80%90V-Single-Board-Computers)
|
||||
- [NVIDIA Jetson and Nintendo Switch (Ubuntu L4T)](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-Linux4Tegra-(L4T)-Ubuntu)
|
||||
|
||||
#### Special Thanks
|
||||
|
||||
[](https://cloudsmith.com)
|
||||
|
||||
Hosting for Moonlight's Raspberry Pi and L4T package repositories is graciously provided for free by [Cloudsmith](https://cloudsmith.com).
|
||||
Hosting for Moonlight's Debian and L4T package repositories is graciously provided for free by [Cloudsmith](https://cloudsmith.com).
|
||||
|
||||
## Building
|
||||
|
||||
### Windows Build Requirements
|
||||
* Qt 5.15 SDK or later
|
||||
* Windows 7 or later
|
||||
* Qt 5.15 SDK or later. Qt 6 is also supported for x64 and ARM64 builds.
|
||||
* [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) (Community edition is fine)
|
||||
* Select **MSVC** option during Qt installation. MinGW is not supported.
|
||||
* [7-Zip](https://www.7-zip.org/) (only if building installers for non-development PCs)
|
||||
|
||||
### macOS Build Requirements
|
||||
* Qt 6.2 SDK or later
|
||||
* macOS Mojave (10.14) or later
|
||||
* Qt 6.4 SDK or later
|
||||
* Xcode 13 or later
|
||||
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
|
||||
|
||||
### Linux/Unix Build Requirements
|
||||
* Qt 5.9 SDK or later
|
||||
* Qt 6 is recommended, but Qt 5.9 or later is also supported (replace `qmake6` with `qmake` when using Qt 5).
|
||||
* GCC or Clang
|
||||
* FFmpeg 4.0 or later
|
||||
* Install the required packages:
|
||||
* Debian/Ubuntu: `libegl1-mesa-dev libgl1-mesa-dev libopus-dev libqt5svg5-dev libsdl2-dev libsdl2-ttf-dev libssl-dev libavcodec-dev libva-dev libvdpau-dev libxkbcommon-dev qtwayland5 qt5-qmake qtbase5-dev qtdeclarative5-dev qtquickcontrols2-5-dev wayland-protocols qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtquick-window2 qml-module-qtquick2`
|
||||
* RedHat/Fedora: `openssl-devel SDL2-devel SDL2_ttf-devel ffmpeg-devel qt5-qtsvg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel`
|
||||
* FFmpeg 4.0 or later is required to build. If your distro doesn't package FFmpeg 4.0 or later, you can build and install it from source on https://ffmpeg.org/
|
||||
* Debian/Ubuntu: `libegl1-mesa-dev libgl1-mesa-dev libopus-dev libsdl2-dev libsdl2-ttf-dev libssl-dev libavcodec-dev libavformat-dev libva-dev libvdpau-dev libxkbcommon-dev wayland-protocols libdrm-dev qtbase6-dev qt6-declarative-dev libqt6svg6-dev qml6-module-qtquick-controls qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qtqml-workerscript qml6-module-qtquick-window qml6-module-qtquick`
|
||||
* RedHat/Fedora (RPM Fusion repo required): `openssl-devel SDL2-devel SDL2_ttf-devel ffmpeg-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel libdrm-devel qt6-qtsvg-devel qt6-qtdeclarative-devel`
|
||||
* Building the Vulkan HDR renderer requires a `libplacebo-dev`/`libplacebo-devel` version of at least v338.0 and FFmpeg 6.1 or later.
|
||||
|
||||
### Steam Link Build Requirements
|
||||
* [Steam Link SDK](https://github.com/ValveSoftware/steamlink-sdk) cloned on your build system
|
||||
@@ -65,17 +66,17 @@ Hosting for Moonlight's Raspberry Pi and L4T package repositories is graciously
|
||||
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 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.
|
||||
* This step is not required for building on Steam Link, because the Steam Link SDK includes Qt 5.14.
|
||||
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.
|
||||
* To build a binary for use on non-development machines, use the scripts in the `scripts` folder.
|
||||
* For Windows builds, use `scripts\build-arch.bat` and `scripts\generate-bundle.bat`. Execute these scripts from the root of the repository within a Qt command prompt. Ensure 7-Zip binary directory is on 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 on macOS or Linux, run `qmake moonlight-qt.pro` then `make debug` or `make release`
|
||||
* To create an embedded build for a single-purpose device, use `qmake "CONFIG+=embedded" moonlight-qt.pro` and build normally.
|
||||
* To build from the command line for development use on macOS or Linux, run `qmake6 moonlight-qt.pro` then `make debug` or `make release`
|
||||
* To create an embedded build for a single-purpose device, use `qmake6 "CONFIG+=embedded" moonlight-qt.pro` and build normally.
|
||||
* This build will lack windowed mode, Discord/Help links, and other features that don't make sense on an embedded device.
|
||||
* For platforms with poor GL performance, add `"CONFIG+=glslow"` to prefer direct KMSDRM rendering over EGL/GLES renderers. Direct KMSDRM rendering can use dedicated YUV/RGB conversion and scaling hardware rather than slower GPU shaders for these operations.
|
||||
* For platforms with poor GPU performance, add `"CONFIG+=gpuslow"` to prefer direct KMSDRM rendering over GL/Vulkan renderers. Direct KMSDRM rendering can use dedicated YUV/RGB conversion and scaling hardware rather than slower GPU shaders for these operations.
|
||||
|
||||
## Contribute
|
||||
1. Fork us
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
</array>
|
||||
<key>GCSupportsControllerUserInteraction</key>
|
||||
<true/>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.games</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
||||
+1
-1
Submodule app/SDL_GameControllerDB updated: dbcf31a670...e5a5fa2ac6
+55
-14
@@ -52,7 +52,7 @@ win32 {
|
||||
# Work around a conflict with math.h inclusion between SDL and Qt 6
|
||||
DEFINES += _USE_MATH_DEFINES
|
||||
}
|
||||
macx {
|
||||
macx:!disable-prebuilts {
|
||||
INCLUDEPATH += $$PWD/../libs/mac/include
|
||||
INCLUDEPATH += $$PWD/../libs/mac/Frameworks/SDL2.framework/Versions/A/Headers
|
||||
INCLUDEPATH += $$PWD/../libs/mac/Frameworks/SDL2_ttf.framework/Versions/A/Headers
|
||||
@@ -64,7 +64,7 @@ macx {
|
||||
QMAKE_OBJECTIVE_CFLAGS += -F$$PWD/../libs/mac/Frameworks
|
||||
}
|
||||
|
||||
unix:!macx {
|
||||
unix:if(!macx|disable-prebuilts) {
|
||||
CONFIG += link_pkgconfig
|
||||
PKGCONFIG += openssl sdl2 SDL2_ttf opus
|
||||
|
||||
@@ -120,6 +120,13 @@ unix:!macx {
|
||||
CONFIG += cuda
|
||||
}
|
||||
}
|
||||
|
||||
!disable-libplacebo {
|
||||
packagesExist(libplacebo) {
|
||||
PKGCONFIG += libplacebo
|
||||
CONFIG += libplacebo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
!disable-wayland {
|
||||
@@ -145,13 +152,17 @@ win32:!winrt {
|
||||
CONFIG += soundio discord-rpc
|
||||
}
|
||||
macx {
|
||||
LIBS += -lssl -lcrypto -lavcodec.60 -lavutil.58 -lopus -framework SDL2 -framework SDL2_ttf
|
||||
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal
|
||||
!disable-prebuilts {
|
||||
LIBS += -lssl -lcrypto -lavcodec.61 -lavutil.59 -lopus -framework SDL2 -framework SDL2_ttf
|
||||
CONFIG += discord-rpc
|
||||
}
|
||||
|
||||
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal -framework QuartzCore
|
||||
|
||||
# For libsoundio
|
||||
LIBS += -framework CoreAudio -framework AudioUnit
|
||||
|
||||
CONFIG += ffmpeg soundio discord-rpc
|
||||
CONFIG += ffmpeg soundio
|
||||
}
|
||||
|
||||
SOURCES += \
|
||||
@@ -267,7 +278,7 @@ libva-wayland {
|
||||
PKGCONFIG += libva-wayland
|
||||
DEFINES += HAVE_LIBVA_WAYLAND
|
||||
}
|
||||
libva-wayland {
|
||||
libva-drm {
|
||||
message(VAAPI DRM support enabled)
|
||||
|
||||
PKGCONFIG += libva-drm
|
||||
@@ -291,7 +302,7 @@ mmal {
|
||||
# significantly better performance than EGL on the Pi. Setting
|
||||
# this option allows EGL usage even if built with MMAL support.
|
||||
#
|
||||
# It is highly recommended to also build with 'glslow' to avoid
|
||||
# It is highly recommended to also build with 'gpuslow' to avoid
|
||||
# EGL being preferred if direct DRM rendering is available.
|
||||
allow-egl-with-mmal {
|
||||
message(Allowing EGL usage with MMAL enabled)
|
||||
@@ -318,6 +329,19 @@ cuda {
|
||||
DEFINES += HAVE_CUDA
|
||||
SOURCES += streaming/video/ffmpeg-renderers/cuda.cpp
|
||||
HEADERS += streaming/video/ffmpeg-renderers/cuda.h
|
||||
|
||||
# ffnvcodec uses libdl in cuda_load_functions()/cuda_free_functions()
|
||||
LIBS += -ldl
|
||||
}
|
||||
libplacebo {
|
||||
message(Vulkan support enabled via libplacebo)
|
||||
|
||||
DEFINES += HAVE_LIBPLACEBO_VULKAN
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg-renderers/plvk.cpp \
|
||||
streaming/video/ffmpeg-renderers/plvk_c.c
|
||||
HEADERS += \
|
||||
streaming/video/ffmpeg-renderers/plvk.h
|
||||
}
|
||||
config_EGL {
|
||||
message(EGL renderer selected)
|
||||
@@ -365,7 +389,8 @@ macx {
|
||||
message(VideoToolbox renderer selected)
|
||||
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg-renderers/vt.mm
|
||||
streaming/video/ffmpeg-renderers/vt_avsamplelayer.mm \
|
||||
streaming/video/ffmpeg-renderers/vt_metal.mm
|
||||
|
||||
HEADERS += \
|
||||
streaming/video/ffmpeg-renderers/vt.h
|
||||
@@ -393,6 +418,16 @@ glslow {
|
||||
|
||||
DEFINES += GL_IS_SLOW
|
||||
}
|
||||
vkslow {
|
||||
message(Vulkan slow build)
|
||||
|
||||
DEFINES += VULKAN_IS_SLOW
|
||||
}
|
||||
gpuslow {
|
||||
message(GPU slow build)
|
||||
|
||||
DEFINES += GL_IS_SLOW VULKAN_IS_SLOW
|
||||
}
|
||||
wayland {
|
||||
message(Wayland extensions enabled)
|
||||
|
||||
@@ -429,7 +464,9 @@ TRANSLATIONS += \
|
||||
languages/qml_pt_BR.ts \
|
||||
languages/qml_pl.ts \
|
||||
languages/qml_cs.ts \
|
||||
languages/qml_he.ts
|
||||
languages/qml_he.ts \
|
||||
languages/qml_ckb.ts \
|
||||
languages/qml_lt.ts
|
||||
|
||||
# Additional import path used to resolve QML modules in Qt Creator's code model
|
||||
QML_IMPORT_PATH =
|
||||
@@ -518,15 +555,19 @@ macx {
|
||||
APP_BUNDLE_RESOURCES.files = moonlight.icns
|
||||
APP_BUNDLE_RESOURCES.path = Contents/Resources
|
||||
|
||||
APP_BUNDLE_FRAMEWORKS.files = $$files(../libs/mac/Frameworks/*.framework, true) $$files(../libs/mac/lib/*.dylib, true)
|
||||
APP_BUNDLE_FRAMEWORKS.path = Contents/Frameworks
|
||||
|
||||
APP_BUNDLE_PLIST.files = $$OUT_PWD/Info.plist
|
||||
APP_BUNDLE_PLIST.path = Contents
|
||||
|
||||
QMAKE_BUNDLE_DATA += APP_BUNDLE_RESOURCES APP_BUNDLE_FRAMEWORKS APP_BUNDLE_PLIST
|
||||
QMAKE_BUNDLE_DATA += APP_BUNDLE_RESOURCES APP_BUNDLE_PLIST
|
||||
|
||||
QMAKE_RPATHDIR += @executable_path/../Frameworks
|
||||
!disable-prebuilts {
|
||||
APP_BUNDLE_FRAMEWORKS.files = $$files(../libs/mac/Frameworks/*.framework, true) $$files(../libs/mac/lib/*.dylib, true)
|
||||
APP_BUNDLE_FRAMEWORKS.path = Contents/Frameworks
|
||||
|
||||
QMAKE_BUNDLE_DATA += APP_BUNDLE_FRAMEWORKS
|
||||
|
||||
QMAKE_RPATHDIR += @executable_path/../Frameworks
|
||||
}
|
||||
}
|
||||
|
||||
VERSION = "$$cat(version.txt)"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "computermanager.h"
|
||||
#include "boxartmanager.h"
|
||||
#include "nvhttp.h"
|
||||
#include "settings/streamingpreferences.h"
|
||||
#include "nvpairingmanager.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
#include <QtEndian>
|
||||
@@ -144,8 +144,8 @@ private:
|
||||
NvComputer* m_Computer;
|
||||
};
|
||||
|
||||
ComputerManager::ComputerManager(QObject *parent)
|
||||
: QObject(parent),
|
||||
ComputerManager::ComputerManager(StreamingPreferences* prefs)
|
||||
: m_Prefs(prefs),
|
||||
m_PollingRef(0),
|
||||
m_MdnsBrowser(nullptr),
|
||||
m_CompatFetcher(nullptr),
|
||||
@@ -352,9 +352,7 @@ void ComputerManager::startPolling()
|
||||
return;
|
||||
}
|
||||
|
||||
StreamingPreferences prefs;
|
||||
|
||||
if (prefs.enableMdns) {
|
||||
if (m_Prefs->enableMdns) {
|
||||
// Start an MDNS query for GameStream hosts
|
||||
m_MdnsServer.reset(new QMdnsEngine::Server());
|
||||
m_MdnsBrowser = new QMdnsEngine::Browser(m_MdnsServer.data(), "_nvstream._tcp.local.");
|
||||
@@ -784,10 +782,9 @@ private:
|
||||
return serverInfo;
|
||||
} catch (...) {
|
||||
if (!m_Mdns) {
|
||||
StreamingPreferences prefs;
|
||||
int portTestResult;
|
||||
unsigned int portTestResult;
|
||||
|
||||
if (prefs.detectNetworkBlocking) {
|
||||
if (m_ComputerManager->m_Prefs->detectNetworkBlocking) {
|
||||
// We failed to connect to the specified PC. Let's test to make sure this network
|
||||
// isn't blocking Moonlight, so we can tell the user about it.
|
||||
portTestResult = LiTestClientConnectivity("qt.conntest.moonlight-stream.org", 443,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "nvcomputer.h"
|
||||
#include "nvpairingmanager.h"
|
||||
#include "settings/streamingpreferences.h"
|
||||
#include "settings/compatfetcher.h"
|
||||
|
||||
#include <qmdnsengine/server.h>
|
||||
@@ -72,9 +72,15 @@ private slots:
|
||||
void handleResolvedTimeout()
|
||||
{
|
||||
if (m_Addresses.isEmpty()) {
|
||||
// Try again
|
||||
qInfo() << "Resolving" << hostname() << "timed out. Retrying...";
|
||||
resolve();
|
||||
if (m_Retries-- > 0) {
|
||||
// Try again
|
||||
qInfo() << "Resolving" << hostname() << "timed out. Retrying...";
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
qWarning() << "Giving up on resolving" << hostname() << "after repeated failures";
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
else {
|
||||
Q_ASSERT(!m_Addresses.isEmpty());
|
||||
@@ -92,7 +98,7 @@ signals:
|
||||
void resolvedHost(MdnsPendingComputer*,QVector<QHostAddress>&);
|
||||
|
||||
private:
|
||||
void resolve()
|
||||
void cleanup()
|
||||
{
|
||||
// Delete our resolver, so we're guaranteed that nothing is referencing m_Server.
|
||||
delete m_Resolver;
|
||||
@@ -101,6 +107,12 @@ private:
|
||||
// Now delete our strong reference that we held on behalf of m_Resolver.
|
||||
// The server may be destroyed after we make this call.
|
||||
m_Server.reset();
|
||||
}
|
||||
|
||||
void resolve()
|
||||
{
|
||||
// Clean up any existing resolver object and server references
|
||||
cleanup();
|
||||
|
||||
// Re-acquire a strong reference if the server still exists.
|
||||
m_Server = m_ServerWeak.toStrongRef();
|
||||
@@ -120,6 +132,7 @@ private:
|
||||
QSharedPointer<QMdnsEngine::Server> m_Server;
|
||||
QMdnsEngine::Resolver* m_Resolver;
|
||||
QVector<QHostAddress> m_Addresses;
|
||||
int m_Retries = 10;
|
||||
};
|
||||
|
||||
class ComputerPollingEntry
|
||||
@@ -205,7 +218,7 @@ class ComputerManager : public QObject
|
||||
friend class DelayedFlushThread;
|
||||
|
||||
public:
|
||||
explicit ComputerManager(QObject *parent = nullptr);
|
||||
explicit ComputerManager(StreamingPreferences* prefs);
|
||||
|
||||
virtual ~ComputerManager();
|
||||
|
||||
@@ -257,6 +270,7 @@ private:
|
||||
|
||||
void startPollingComputer(NvComputer* computer);
|
||||
|
||||
StreamingPreferences* m_Prefs;
|
||||
int m_PollingRef;
|
||||
QReadWriteLock m_Lock;
|
||||
QMap<QString, NvComputer*> m_KnownHosts;
|
||||
|
||||
@@ -6,6 +6,13 @@ ComputerSeeker::ComputerSeeker(ComputerManager *manager, QString computerName, Q
|
||||
: QObject(parent), m_ComputerManager(manager), m_ComputerName(computerName),
|
||||
m_TimeoutTimer(new QTimer(this))
|
||||
{
|
||||
// If we know this computer, send a WOL packet to wake it up in case it is asleep.
|
||||
for (NvComputer * computer: m_ComputerManager->getComputers()) {
|
||||
if (this->matchComputer(computer)) {
|
||||
computer->wake();
|
||||
}
|
||||
}
|
||||
|
||||
m_TimeoutTimer->setSingleShot(true);
|
||||
connect(m_TimeoutTimer, &QTimer::timeout,
|
||||
this, &ComputerSeeker::onTimeout);
|
||||
|
||||
@@ -373,6 +373,7 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
|
||||
s.connectToHost(copyOfActiveAddress.address(), copyOfActiveAddress.port());
|
||||
if (s.waitForConnected(3000)) {
|
||||
Q_ASSERT(!s.localAddress().isNull());
|
||||
Q_ASSERT(!s.peerAddress().isNull());
|
||||
|
||||
for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) {
|
||||
// Ensure the interface is up
|
||||
@@ -406,6 +407,19 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
|
||||
return ReachabilityType::RI_VPN;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
if (nic.name().startsWith("iftype53_") || nic.name().startsWith("iftype131_")) {
|
||||
// Match by NDIS interface type. These values are Microsoft's recommended values for VPN connections:
|
||||
// https://learn.microsoft.com/en-US/troubleshoot/windows-client/networking/windows-connection-manager-disconnects-wlan#more-information
|
||||
//
|
||||
// The following VPNs use IF_TYPE_PROP_VIRTUAL under Windows:
|
||||
// - WireguardNT VPNs
|
||||
// - All WinTun-based VPNs (such as Slack Nebula)
|
||||
// - OpenVPN with tap-windows6
|
||||
return ReachabilityType::RI_VPN;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (nic.hardwareAddress().startsWith("00:FF", Qt::CaseInsensitive)) {
|
||||
// OpenVPN TAP interfaces have a MAC address starting with 00:FF on Windows
|
||||
return ReachabilityType::RI_VPN;
|
||||
@@ -421,9 +435,9 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
|
||||
return ReachabilityType::RI_VPN;
|
||||
}
|
||||
|
||||
// Didn't meet any of our VPN heuristics. Let's see if it's on-link.
|
||||
// Didn't meet any of our VPN heuristics. Let's see if the peer address is on-link.
|
||||
Q_ASSERT(addr.prefixLength() >= 0);
|
||||
if (addr.prefixLength() >= 0 && s.localAddress().isInSubnet(addr.ip(), addr.prefixLength())) {
|
||||
if (addr.prefixLength() >= 0 && s.localAddress().isInSubnet(s.peerAddress(), addr.prefixLength())) {
|
||||
return ReachabilityType::RI_LAN;
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,8 @@ NvHTTP::startApp(QString verb,
|
||||
"&surroundAudioInfo="+QString::number(SURROUNDAUDIOINFO_FROM_AUDIO_CONFIGURATION(streamConfig->audioConfiguration))+
|
||||
"&remoteControllersBitmap="+QString::number(gamepadMask)+
|
||||
"&gcmap="+QString::number(gamepadMask)+
|
||||
"&gcpersist="+QString::number(persistGameControllersOnDisconnect ? 1 : 0),
|
||||
"&gcpersist="+QString::number(persistGameControllersOnDisconnect ? 1 : 0)+
|
||||
LiGetLaunchUrlQueryParameters(),
|
||||
LAUNCH_TIMEOUT_MS);
|
||||
|
||||
qInfo() << "Launch response:" << response;
|
||||
|
||||
@@ -298,8 +298,8 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
|
||||
}
|
||||
|
||||
QByteArray pairingSecret = NvHTTP::getXmlStringFromHex(respXml, "pairingsecret");
|
||||
QByteArray serverSecret = QByteArray(pairingSecret.data(), 16);
|
||||
QByteArray serverSignature = QByteArray(&pairingSecret.data()[16], 256);
|
||||
QByteArray serverSecret = pairingSecret.left(16);
|
||||
QByteArray serverSignature = pairingSecret.mid(16);
|
||||
|
||||
if (!verifySignature(serverSecret,
|
||||
serverSignature,
|
||||
|
||||
@@ -74,6 +74,7 @@ SystemProperties::SystemProperties()
|
||||
|
||||
Q_ASSERT(!monitorRefreshRates.isEmpty());
|
||||
Q_ASSERT(!monitorNativeResolutions.isEmpty());
|
||||
Q_ASSERT(!monitorSafeAreaResolutions.isEmpty());
|
||||
}
|
||||
|
||||
QRect SystemProperties::getNativeResolution(int displayIndex)
|
||||
@@ -82,6 +83,12 @@ QRect SystemProperties::getNativeResolution(int displayIndex)
|
||||
return monitorNativeResolutions.value(displayIndex);
|
||||
}
|
||||
|
||||
QRect SystemProperties::getSafeAreaResolution(int displayIndex)
|
||||
{
|
||||
// Returns default constructed QRect if out of bounds
|
||||
return monitorSafeAreaResolutions.value(displayIndex);
|
||||
}
|
||||
|
||||
int SystemProperties::getRefreshRate(int displayIndex)
|
||||
{
|
||||
// Returns 0 if out of bounds
|
||||
@@ -199,10 +206,12 @@ void SystemProperties::refreshDisplaysInternal()
|
||||
SDL_DisplayMode bestMode;
|
||||
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
|
||||
SDL_DisplayMode desktopMode;
|
||||
SDL_Rect safeArea;
|
||||
|
||||
if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode)) {
|
||||
if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode, &safeArea)) {
|
||||
if (desktopMode.w <= 8192 && desktopMode.h <= 8192) {
|
||||
monitorNativeResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
|
||||
monitorSafeAreaResolutions.insert(displayIndex, QRect(0, 0, safeArea.w, safeArea.h));
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
|
||||
Q_INVOKABLE void refreshDisplays();
|
||||
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
|
||||
Q_INVOKABLE QRect getSafeAreaResolution(int displayIndex);
|
||||
Q_INVOKABLE int getRefreshRate(int displayIndex);
|
||||
|
||||
signals:
|
||||
@@ -52,6 +53,7 @@ private:
|
||||
QString unmappedGamepads;
|
||||
QSize maximumResolution;
|
||||
QList<QRect> monitorNativeResolutions;
|
||||
QList<QRect> monitorSafeAreaResolutions;
|
||||
QList<int> monitorRefreshRates;
|
||||
QString versionString;
|
||||
bool supportsHdr;
|
||||
|
||||
@@ -316,6 +316,7 @@ StreamCommandLineParser::StreamCommandLineParser()
|
||||
{"auto", StreamingPreferences::VCC_AUTO},
|
||||
{"H.264", StreamingPreferences::VCC_FORCE_H264},
|
||||
{"HEVC", StreamingPreferences::VCC_FORCE_HEVC},
|
||||
{"AV1", StreamingPreferences::VCC_FORCE_AV1},
|
||||
};
|
||||
m_VideoDecoderMap = {
|
||||
{"auto", StreamingPreferences::VDS_AUTO},
|
||||
@@ -371,6 +372,8 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
||||
parser.addToggleOption("reverse-scroll-direction", "inverted scroll direction");
|
||||
parser.addToggleOption("swap-gamepad-buttons", "swap A/B and X/Y gamepad buttons (Nintendo-style)");
|
||||
parser.addToggleOption("keep-awake", "prevent display sleep while streaming");
|
||||
parser.addToggleOption("performance-overlay", "show performance overlay");
|
||||
parser.addToggleOption("hdr", "HDR streaming");
|
||||
parser.addChoiceOption("capture-system-keys", "capture system key combos", m_CaptureSysKeysModeMap.keys());
|
||||
parser.addChoiceOption("video-codec", "video codec", m_VideoCodecMap.keys());
|
||||
parser.addChoiceOption("video-decoder", "video decoder", m_VideoDecoderMap.keys());
|
||||
@@ -409,8 +412,8 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
||||
// Resolve --fps option
|
||||
if (parser.isSet("fps")) {
|
||||
preferences->fps = parser.getIntOption("fps");
|
||||
if (!inRange(preferences->fps, 30, 240)) {
|
||||
parser.showError("FPS must be in range: 30 - 240");
|
||||
if (!inRange(preferences->fps, 10, 480)) {
|
||||
parser.showError("FPS must be in range: 10 - 480");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,6 +488,12 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
||||
// Resolve --keep-awake and --no-keep-awake options
|
||||
preferences->keepAwake = parser.getToggleOptionValue("keep-awake", preferences->keepAwake);
|
||||
|
||||
// Resolve --performance-overlay and --no-performance-overlay options
|
||||
preferences->showPerformanceOverlay = parser.getToggleOptionValue("performance-overlay", preferences->showPerformanceOverlay);
|
||||
|
||||
// Resolve --hdr and --no-hdr options
|
||||
preferences->enableHdr = parser.getToggleOptionValue("hdr", preferences->enableHdr);
|
||||
|
||||
// Resolve --capture-system-keys option
|
||||
if (parser.isSet("capture-system-keys")) {
|
||||
preferences->captureSysKeysMode = mapValue(m_CaptureSysKeysModeMap, parser.getChoiceOptionValue("capture-system-keys"));
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QTimer>
|
||||
|
||||
#define COMPUTER_SEEK_TIMEOUT 10000
|
||||
#define COMPUTER_SEEK_TIMEOUT 30000
|
||||
#define APP_SEEK_TIMEOUT 10000
|
||||
|
||||
namespace CliListApps
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QTimer>
|
||||
|
||||
#define COMPUTER_SEEK_TIMEOUT 10000
|
||||
#define COMPUTER_SEEK_TIMEOUT 30000
|
||||
#define APP_SEEK_TIMEOUT 10000
|
||||
|
||||
namespace CliStartStream
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
From 78261eb6e2fe728fa5d3843b5b8962b1ca559b7e Mon Sep 17 00:00:00 2001
|
||||
From: David Rosca <nowrep@gmail.com>
|
||||
Date: Sat, 16 Dec 2023 14:52:01 +0100
|
||||
Subject: [PATCH] Vulkan: Don't try to reuse old swapchain
|
||||
|
||||
---
|
||||
src/vulkan/swapchain.c | 12 ++++++++++--
|
||||
1 file changed, 10 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/vulkan/swapchain.c b/src/vulkan/swapchain.c
|
||||
index 0646e57c..c0b07ce4 100644
|
||||
--- a/src/vulkan/swapchain.c
|
||||
+++ b/src/vulkan/swapchain.c
|
||||
@@ -612,13 +612,21 @@ static bool vk_sw_recreate(pl_swapchain sw, int w, int h)
|
||||
}
|
||||
#endif
|
||||
|
||||
+ {
|
||||
+ pl_gpu_flush(gpu);
|
||||
+ vk_wait_idle(vk);
|
||||
+ for (int i = 0; i < vk->pool_graphics->num_queues; i++)
|
||||
+ vk->QueueWaitIdle(vk->pool_graphics->queues[i]);
|
||||
+ vk->DestroySwapchainKHR(vk->dev, p->swapchain, PL_VK_ALLOC);
|
||||
+ }
|
||||
+
|
||||
// Calling `vkCreateSwapchainKHR` puts sinfo.oldSwapchain into a retired
|
||||
// state whether the call succeeds or not, so we always need to garbage
|
||||
// collect it afterwards - asynchronously as it may still be in use
|
||||
- sinfo.oldSwapchain = p->swapchain;
|
||||
+ /* sinfo.oldSwapchain = p->swapchain; */
|
||||
p->swapchain = VK_NULL_HANDLE;
|
||||
VkResult res = vk->CreateSwapchainKHR(vk->dev, &sinfo, PL_VK_ALLOC, &p->swapchain);
|
||||
- vk_dev_callback(vk, VK_CB_FUNC(destroy_swapchain), vk, vk_wrap_handle(sinfo.oldSwapchain));
|
||||
+ /* vk_dev_callback(vk, VK_CB_FUNC(destroy_swapchain), vk, vk_wrap_handle(sinfo.oldSwapchain)); */
|
||||
PL_VK_ASSERT(res, "vk->CreateSwapchainKHR(...)");
|
||||
|
||||
// Get the new swapchain images
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -34,6 +34,71 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="6.0.1" date="2024-06-30">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>HDR to SDR tonemapping is now supported by the Vulkan renderer on Linux</li>
|
||||
</ul>
|
||||
<p>Behavior Changes:</p>
|
||||
<ul>
|
||||
<li>More time is now provided for PCs to wake up before displaying an error when starting a stream from the CLI</li>
|
||||
<li>Moonlight will no longer try to guess VA driver names when running libva 2.20 or later</li>
|
||||
</ul>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Fixed crash when the Vulkan renderer executed on a system with no Vulkan devices</li>
|
||||
<li>Updated community-contributed translations from Weblate</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="6.0.0" date="2024-06-06">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>HDR is now supported on Steam Deck and other HDR-capable Linux systems using a new Vulkan-based renderer</li>
|
||||
<li>Vulkan Video decoding of H.264, HEVC, and AV1 is now supported using the new Vulkan renderer</li>
|
||||
<li>Full E2E stream encryption is now supported when streaming from Sunshine v0.22 or later</li>
|
||||
<li>Initiating a stream via the command-line will now automatically wake the target machine if it is sleeping</li>
|
||||
<li>Error codes are now displayed in the connection termination dialog</li>
|
||||
<li>Detailed PC state information is now available via the new "View Details" context menu option (similar to the Android client)</li>
|
||||
<li>The performance overlay can now be configured to be shown by default when starting a stream</li>
|
||||
</ul>
|
||||
<p>Behavior Changes:</p>
|
||||
<ul>
|
||||
<li>The selected bitrate is no longer silently adjusted by Moonlight based upon the video codec or HDR mode</li>
|
||||
<li>The selected mouse mode when starting a stream no longer influences the acceleration curve when the mouse mode is toggled using the Ctrl+Alt+Shift+M key combo while streaming</li>
|
||||
<li>Hardcoded fallback paths for VAAPI and VDPAU drivers are no longer used</li>
|
||||
<li>Per-session keys (rikey and rikeyid values) are now automatically stripped from logs</li>
|
||||
<li>Log messages will now be logged to stderr rather than stdout</li>
|
||||
<li>EGL is now always used instead of GLX for loading OpenGL in X11 environments on non-x86 platforms</li>
|
||||
</ul>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Improved reliability of streaming during momentary drops of WiFi connections</li>
|
||||
<li>Improved performance with high polling rate mice</li>
|
||||
<li>Improved input latency when a gamepad is connected</li>
|
||||
<li>Added missing command-line options to control HDR and AV1 codec usage</li>
|
||||
<li>Fixed instant connection termination error when streaming over the Internet behind certain Carrier-Grade NAT devices</li>
|
||||
<li>Fixed incorrect MTU detection when streaming over the Internet without a VPN</li>
|
||||
<li>Fixed recovery of audio when no audio output device is present when the stream starts</li>
|
||||
<li>Fixed merging of gamepad state when multi-controller mode is disabled</li>
|
||||
<li>Fixed indefinitely resending mDNS queries when a host could not be resolved</li>
|
||||
<li>Fixed inability to pair when the host is not using a RSA-2048 key</li>
|
||||
<li>Updated included gamepad mappings</li>
|
||||
<li>Updated community-contributed translations from Weblate</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="5.0.1" date="2023-11-02">
|
||||
<description>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Fixed connection issue streaming from some hosts running GeForce Experience or Sunshine v0.20 and earlier</li>
|
||||
<li>Updated community-contributed translations from Weblate</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="5.0.0" date="2023-10-18">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
# pairing data will be lost each reboot.
|
||||
HOME=/usr/local/moonlight
|
||||
|
||||
# Enable Steam Controller detection in SDL
|
||||
# https://github.com/moonlight-stream/moonlight-qt/issues/697
|
||||
export SDL_ENABLE_STEAM_CONTROLLERS=1
|
||||
|
||||
# Renice PE_Single_CPU which seems to host A/V stuff
|
||||
renice -10 -p $(pidof PE_Single_CPU)
|
||||
|
||||
|
||||
+2
-2
@@ -256,7 +256,7 @@ CenteredGridView {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton;
|
||||
onClicked: {
|
||||
parent.onPressAndHold()
|
||||
parent.pressAndHold()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ CenteredGridView {
|
||||
|
||||
function quitApp() {
|
||||
var component = Qt.createComponent("QuitSegue.qml")
|
||||
var params = {"appName": appName, "quitRunningAppFn": appModel.quitRunningApp}
|
||||
var params = {"appName": appName, "quitRunningAppFn": () => appModel.quitRunningApp()}
|
||||
if (segueToStream) {
|
||||
// Store the session and app name if we're going to stream after
|
||||
// successfully quitting the old app.
|
||||
|
||||
@@ -85,7 +85,7 @@ Item {
|
||||
|
||||
function quitApp() {
|
||||
var component = Qt.createComponent("QuitSegue.qml")
|
||||
var params = {"appName": appName, "quitRunningAppFn": launcher.quitRunningApp}
|
||||
var params = {"appName": appName, "quitRunningAppFn": () => launcher.quitRunningApp()}
|
||||
stackView.push(component.createObject(stackView, params))
|
||||
}
|
||||
|
||||
|
||||
+18
-1
@@ -6,6 +6,7 @@ import ComputerModel 1.0
|
||||
|
||||
import ComputerManager 1.0
|
||||
import StreamingPreferences 1.0
|
||||
import SystemProperties 1.0
|
||||
import SdlGamepadKeyNavigation 1.0
|
||||
|
||||
CenteredGridView {
|
||||
@@ -215,6 +216,14 @@ CenteredGridView {
|
||||
deletePcDialog.open()
|
||||
}
|
||||
}
|
||||
NavigableMenuItem {
|
||||
parentMenu: pcContextMenu
|
||||
text: qsTr("View Details")
|
||||
onTriggered: {
|
||||
showPcDetailsDialog.pcDetails = model.details
|
||||
showPcDetailsDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +271,7 @@ CenteredGridView {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton;
|
||||
onClicked: {
|
||||
parent.onPressAndHold()
|
||||
parent.pressAndHold()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,5 +404,13 @@ CenteredGridView {
|
||||
}
|
||||
}
|
||||
|
||||
NavigableMessageDialog {
|
||||
id: showPcDetailsDialog
|
||||
property string pcDetails : "";
|
||||
text: showPcDetailsDialog.pcDetails
|
||||
imageSrc: "qrc:/res/baseline-help_outline-24px.svg"
|
||||
standardButtons: Dialog.Ok
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
}
|
||||
|
||||
+66
-54
@@ -133,15 +133,45 @@ Flickable {
|
||||
AutoResizingComboBox {
|
||||
property int lastIndexValue
|
||||
|
||||
function addDetectedResolution(friendlyNamePrefix, rect) {
|
||||
var indexToAdd = 0
|
||||
for (var j = 0; j < resolutionComboBox.count; j++) {
|
||||
var existing_width = parseInt(resolutionListModel.get(j).video_width);
|
||||
var existing_height = parseInt(resolutionListModel.get(j).video_height);
|
||||
|
||||
if (rect.width === existing_width && rect.height === existing_height) {
|
||||
// Duplicate entry, skip
|
||||
indexToAdd = -1
|
||||
break
|
||||
}
|
||||
else if (rect.width * rect.height > existing_width * existing_height) {
|
||||
// Candidate entrypoint after this entry
|
||||
indexToAdd = j + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Insert this display's resolution if it's not a duplicate
|
||||
if (indexToAdd >= 0) {
|
||||
resolutionListModel.insert(indexToAdd,
|
||||
{
|
||||
"text": friendlyNamePrefix+" ("+rect.width+"x"+rect.height+")",
|
||||
"video_width": ""+rect.width,
|
||||
"video_height": ""+rect.height,
|
||||
"is_custom": false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ignore setting the index at first, and actually set it when the component is loaded
|
||||
Component.onCompleted: {
|
||||
// Refresh display data before using it to build the list
|
||||
SystemProperties.refreshDisplays()
|
||||
|
||||
// Add native resolutions for all attached displays
|
||||
// Add native and safe area resolutions for all attached displays
|
||||
var done = false
|
||||
for (var displayIndex = 0; !done; displayIndex++) {
|
||||
var screenRect = SystemProperties.getNativeResolution(displayIndex);
|
||||
var safeAreaRect = SystemProperties.getSafeAreaResolution(displayIndex);
|
||||
|
||||
if (screenRect.width === 0) {
|
||||
// Exceeded max count of displays
|
||||
@@ -149,32 +179,8 @@ Flickable {
|
||||
break
|
||||
}
|
||||
|
||||
var indexToAdd = 0
|
||||
for (var j = 0; j < resolutionComboBox.count; j++) {
|
||||
var existing_width = parseInt(resolutionListModel.get(j).video_width);
|
||||
var existing_height = parseInt(resolutionListModel.get(j).video_height);
|
||||
|
||||
if (screenRect.width === existing_width && screenRect.height === existing_height) {
|
||||
// Duplicate entry, skip
|
||||
indexToAdd = -1
|
||||
break
|
||||
}
|
||||
else if (screenRect.width * screenRect.height > existing_width * existing_height) {
|
||||
// Candidate entrypoint after this entry
|
||||
indexToAdd = j + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Insert this display's resolution if it's not a duplicate
|
||||
if (indexToAdd >= 0) {
|
||||
resolutionListModel.insert(indexToAdd,
|
||||
{
|
||||
"text": "Native ("+screenRect.width+"x"+screenRect.height+")",
|
||||
"video_width": ""+screenRect.width,
|
||||
"video_height": ""+screenRect.height,
|
||||
"is_custom": false
|
||||
})
|
||||
}
|
||||
addDetectedResolution(qsTr("Native"), screenRect)
|
||||
addDetectedResolution(qsTr("Native (Excluding Notch)"), safeAreaRect)
|
||||
}
|
||||
|
||||
// Prune resolutions that are over the decoder's maximum
|
||||
@@ -210,7 +216,7 @@ Flickable {
|
||||
if (!index_set) {
|
||||
// We did not find a match. This must be a custom resolution.
|
||||
resolutionListModel.append({
|
||||
"text": "Custom ("+StreamingPreferences.width+"x"+StreamingPreferences.height+")",
|
||||
"text": qsTr("Custom")+" ("+StreamingPreferences.width+"x"+StreamingPreferences.height+")",
|
||||
"video_width": ""+StreamingPreferences.width,
|
||||
"video_height": ""+StreamingPreferences.height,
|
||||
"is_custom": true
|
||||
@@ -219,7 +225,7 @@ Flickable {
|
||||
}
|
||||
else {
|
||||
resolutionListModel.append({
|
||||
"text": "Custom",
|
||||
"text": qsTr("Custom"),
|
||||
"video_width": "",
|
||||
"video_height": "",
|
||||
"is_custom": true
|
||||
@@ -689,7 +695,7 @@ Flickable {
|
||||
|
||||
Component.onCompleted: {
|
||||
// Refresh the text after translations change
|
||||
languageChanged.connect(onValueChanged)
|
||||
languageChanged.connect(valueChanged)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -996,10 +1002,10 @@ Flickable {
|
||||
text: "한국어" // Korean
|
||||
val: StreamingPreferences.LANG_KO
|
||||
}
|
||||
/* ListElement {
|
||||
ListElement {
|
||||
text: "Magyar" // Hungarian
|
||||
val: StreamingPreferences.LANG_HU
|
||||
} */
|
||||
}
|
||||
ListElement {
|
||||
text: "Nederlands" // Dutch
|
||||
val: StreamingPreferences.LANG_NL
|
||||
@@ -1052,6 +1058,14 @@ Flickable {
|
||||
text: "עִבְרִית" // Hebrew
|
||||
val: StreamingPreferences.LANG_HE
|
||||
} */
|
||||
/* ListElement {
|
||||
text: "کرمانجیی خواروو" // Central Kurdish
|
||||
val: StreamingPreferences.LANG_CKB
|
||||
} */
|
||||
/* ListElement {
|
||||
text: "Lietuvių kalba" // Lithuanian
|
||||
val: StreamingPreferences.LANG_LT
|
||||
} */
|
||||
}
|
||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||
onActivated : {
|
||||
@@ -1349,15 +1363,7 @@ Flickable {
|
||||
font.pointSize: 12
|
||||
checked: StreamingPreferences.swapFaceButtons
|
||||
onCheckedChanged: {
|
||||
// Check if the value changed (this is called on init too)
|
||||
if (StreamingPreferences.swapFaceButtons !== checked) {
|
||||
StreamingPreferences.swapFaceButtons = checked
|
||||
|
||||
// Save and restart SdlGamepadKeyNavigation so it can pull the new value
|
||||
StreamingPreferences.save()
|
||||
SdlGamepadKeyNavigation.disable()
|
||||
SdlGamepadKeyNavigation.enable()
|
||||
}
|
||||
StreamingPreferences.swapFaceButtons = checked
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
@@ -1621,10 +1627,6 @@ Flickable {
|
||||
if (StreamingPreferences.enableMdns != checked) {
|
||||
StreamingPreferences.enableMdns = checked
|
||||
|
||||
// We must save the updated preference to ensure
|
||||
// ComputerManager can observe the change internally.
|
||||
StreamingPreferences.save()
|
||||
|
||||
// Restart polling so the mDNS change takes effect
|
||||
if (window.pollingActive) {
|
||||
ComputerManager.stopPollingAsync()
|
||||
@@ -1641,17 +1643,27 @@ Flickable {
|
||||
font.pointSize: 12
|
||||
checked: StreamingPreferences.detectNetworkBlocking
|
||||
onCheckedChanged: {
|
||||
// This is called on init, so only do the work if we've
|
||||
// actually changed the value.
|
||||
if (StreamingPreferences.detectNetworkBlocking != checked) {
|
||||
StreamingPreferences.detectNetworkBlocking = checked
|
||||
|
||||
// We must save the updated preference to ensure
|
||||
// ComputerManager can observe the change internally.
|
||||
StreamingPreferences.save()
|
||||
}
|
||||
StreamingPreferences.detectNetworkBlocking = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: showPerformanceOverlay
|
||||
width: parent.width
|
||||
text: qsTr("Show performance stats while streaming")
|
||||
font.pointSize: 12
|
||||
checked: StreamingPreferences.showPerformanceOverlay
|
||||
onCheckedChanged: {
|
||||
StreamingPreferences.showPerformanceOverlay = checked
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 5000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Display real-time stream performance information while streaming.") + "\n\n" +
|
||||
qsTr("You can toggle it at any time while streaming using Ctrl+Alt+Shift+S or Select+L1+R1+X.") + "\n\n" +
|
||||
qsTr("The performance overlay is not supported on Steam Link or Raspberry Pi.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ Item {
|
||||
gc()
|
||||
|
||||
// Run the streaming session to completion
|
||||
session.exec(Screen.virtualX, Screen.virtualY)
|
||||
session.exec(Window.window)
|
||||
}
|
||||
|
||||
sourceComponent: Item {}
|
||||
|
||||
@@ -42,6 +42,46 @@ QVariant ComputerModel::data(const QModelIndex& index, int role) const
|
||||
return computer->state == NvComputer::CS_UNKNOWN;
|
||||
case ServerSupportedRole:
|
||||
return computer->isSupportedServerVersion;
|
||||
case DetailsRole: {
|
||||
QString state, pairState;
|
||||
|
||||
switch (computer->state) {
|
||||
case NvComputer::CS_ONLINE:
|
||||
state = tr("Online");
|
||||
break;
|
||||
case NvComputer::CS_OFFLINE:
|
||||
state = tr("Offline");
|
||||
break;
|
||||
default:
|
||||
state = tr("Unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (computer->pairState) {
|
||||
case NvComputer::PS_PAIRED:
|
||||
pairState = tr("Paired");
|
||||
break;
|
||||
case NvComputer::PS_NOT_PAIRED:
|
||||
pairState = tr("Unpaired");
|
||||
break;
|
||||
default:
|
||||
pairState = tr("Unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
return tr("Name: %1").arg(computer->name) + '\n' +
|
||||
tr("Status: %1").arg(state) + '\n' +
|
||||
tr("Active Address: %1").arg(computer->activeAddress.toString()) + '\n' +
|
||||
tr("UUID: %1").arg(computer->uuid) + '\n' +
|
||||
tr("Local Address: %1").arg(computer->localAddress.toString()) + '\n' +
|
||||
tr("Remote Address: %1").arg(computer->remoteAddress.toString()) + '\n' +
|
||||
tr("IPv6 Address: %1").arg(computer->ipv6Address.toString()) + '\n' +
|
||||
tr("Manual Address: %1").arg(computer->manualAddress.toString()) + '\n' +
|
||||
tr("MAC Address: %1").arg(computer->macAddress.isEmpty() ? tr("Unknown") : QString(computer->macAddress.toHex(':'))) + '\n' +
|
||||
tr("Pair State: %1").arg(pairState) + '\n' +
|
||||
tr("Running Game ID: %1").arg(computer->state == NvComputer::CS_ONLINE ? QString::number(computer->currentGameId) : tr("Unknown")) + '\n' +
|
||||
tr("HTTPS Port: %1").arg(computer->state == NvComputer::CS_ONLINE ? QString::number(computer->activeHttpsPort) : tr("Unknown"));
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@@ -69,6 +109,7 @@ QHash<int, QByteArray> ComputerModel::roleNames() const
|
||||
names[WakeableRole] = "wakeable";
|
||||
names[StatusUnknownRole] = "statusUnknown";
|
||||
names[ServerSupportedRole] = "serverSupported";
|
||||
names[DetailsRole] = "details";
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ class ComputerModel : public QAbstractListModel
|
||||
BusyRole,
|
||||
WakeableRole,
|
||||
StatusUnknownRole,
|
||||
ServerSupportedRole
|
||||
ServerSupportedRole,
|
||||
DetailsRole
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
+33
-46
@@ -19,26 +19,48 @@ ApplicationWindow {
|
||||
property bool clearOnBack: false
|
||||
|
||||
id: window
|
||||
visible: true
|
||||
width: 1280
|
||||
height: 600
|
||||
|
||||
// Override the background color to Material 2 colors for Qt 6.5+
|
||||
// in order to improve contrast between GFE's placeholder box art
|
||||
// and the background of the app grid.
|
||||
Component.onCompleted: {
|
||||
// Override the background color to Material 2 colors for Qt 6.5+
|
||||
// in order to improve contrast between GFE's placeholder box art
|
||||
// and the background of the app grid.
|
||||
if (SystemProperties.usesMaterial3Theme) {
|
||||
Material.background = "#303030"
|
||||
}
|
||||
}
|
||||
|
||||
visibility: {
|
||||
// Show the window according to the user's preferences
|
||||
if (SystemProperties.hasDesktopEnvironment) {
|
||||
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_WINDOWED) return "Windowed"
|
||||
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) return "Maximized"
|
||||
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_FULLSCREEN) return "FullScreen"
|
||||
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) {
|
||||
window.showMaximized()
|
||||
}
|
||||
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_FULLSCREEN) {
|
||||
window.showFullScreen()
|
||||
}
|
||||
else {
|
||||
window.show()
|
||||
}
|
||||
} else {
|
||||
return "FullScreen"
|
||||
window.showFullScreen()
|
||||
}
|
||||
|
||||
// Display any modal dialogs for configuration warnings
|
||||
if (SystemProperties.isWow64) {
|
||||
wow64Dialog.open()
|
||||
}
|
||||
else if (!SystemProperties.hasHardwareAcceleration) {
|
||||
if (SystemProperties.isRunningXWayland) {
|
||||
xWaylandDialog.open()
|
||||
}
|
||||
else {
|
||||
noHwDecoderDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
if (SystemProperties.unmappedGamepads) {
|
||||
unmappedGamepadDialog.unmappedGamepads = SystemProperties.unmappedGamepads
|
||||
unmappedGamepadDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,41 +178,6 @@ ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
property bool initialized: false
|
||||
|
||||
// BUG: Using onAfterSynchronizing: here causes very strange
|
||||
// failures on Linux. Many shaders fail to compile and we
|
||||
// eventually segfault deep inside the Qt OpenGL code.
|
||||
onAfterRendering: {
|
||||
// We use this callback to trigger dialog display because
|
||||
// it only happens once the window is fully constructed.
|
||||
// Doing it earlier can lead to the dialog appearing behind
|
||||
// the window or otherwise without input focus.
|
||||
if (!initialized) {
|
||||
// Set initialized before calling anything else, because
|
||||
// pumping the event loop can cause us to get another
|
||||
// onAfterRendering call and potentially reenter this code.
|
||||
initialized = true;
|
||||
|
||||
if (SystemProperties.isWow64) {
|
||||
wow64Dialog.open()
|
||||
}
|
||||
else if (!SystemProperties.hasHardwareAcceleration) {
|
||||
if (SystemProperties.isRunningXWayland) {
|
||||
xWaylandDialog.open()
|
||||
}
|
||||
else {
|
||||
noHwDecoderDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
if (SystemProperties.unmappedGamepads) {
|
||||
unmappedGamepadDialog.unmappedGamepads = SystemProperties.unmappedGamepads
|
||||
unmappedGamepadDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for lack of instanceof in Qt 5.9.
|
||||
//
|
||||
// Based on https://stackoverflow.com/questions/13923794/how-to-do-a-is-a-typeof-or-instanceof-in-qml
|
||||
@@ -283,7 +270,7 @@ ApplicationWindow {
|
||||
visible: SystemProperties.hasBrowser &&
|
||||
qmltypeof(stackView.currentItem, "SettingsView")
|
||||
|
||||
iconSource: "qrc:/res/Discord-Logo-White.svg"
|
||||
iconSource: "qrc:/res/discord.svg"
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 3000
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QWindow>
|
||||
|
||||
#include "settings/streamingpreferences.h"
|
||||
#include "settings/mappingmanager.h"
|
||||
|
||||
#define AXIS_NAVIGATION_REPEAT_DELAY 150
|
||||
|
||||
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation()
|
||||
: m_Enabled(false),
|
||||
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation(StreamingPreferences* prefs)
|
||||
: m_Prefs(prefs),
|
||||
m_Enabled(false),
|
||||
m_UiNavMode(false),
|
||||
m_FirstPoll(false),
|
||||
m_LastAxisNavigationEventTime(0)
|
||||
@@ -93,7 +93,6 @@ void SdlGamepadKeyNavigation::disable()
|
||||
void SdlGamepadKeyNavigation::onPollingTimerFired()
|
||||
{
|
||||
SDL_Event event;
|
||||
StreamingPreferences prefs;
|
||||
|
||||
// Discard any pending button events on the first poll to avoid picking up
|
||||
// stale input data from the stream session (like the quit combo).
|
||||
@@ -121,7 +120,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
||||
QEvent::Type::KeyPress : QEvent::Type::KeyRelease;
|
||||
|
||||
// Swap face buttons if needed
|
||||
if (prefs.swapFaceButtons) {
|
||||
if (m_Prefs->swapFaceButtons) {
|
||||
switch (event.cbutton.button) {
|
||||
case SDL_CONTROLLER_BUTTON_A:
|
||||
event.cbutton.button = SDL_CONTROLLER_BUTTON_B;
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "settings/streamingpreferences.h"
|
||||
|
||||
class SdlGamepadKeyNavigation : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SdlGamepadKeyNavigation();
|
||||
SdlGamepadKeyNavigation(StreamingPreferences* prefs);
|
||||
|
||||
~SdlGamepadKeyNavigation();
|
||||
|
||||
@@ -29,6 +31,7 @@ private slots:
|
||||
void onPollingTimerFired();
|
||||
|
||||
private:
|
||||
StreamingPreferences* m_Prefs;
|
||||
QTimer* m_PollingTimer;
|
||||
QList<SDL_GameController*> m_Gamepads;
|
||||
bool m_Enabled;
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+331
-197
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+381
-247
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+338
-204
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+355
-221
File diff suppressed because it is too large
Load Diff
+366
-232
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+366
-232
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+338
-204
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+374
-240
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+370
-236
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+331
-197
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+331
-197
File diff suppressed because it is too large
Load Diff
+369
-235
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+369
-235
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+378
-244
File diff suppressed because it is too large
Load Diff
+315
-181
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+326
-192
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+344
-210
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large
Load Diff
+116
-37
@@ -10,7 +10,8 @@
|
||||
#include <QFont>
|
||||
#include <QCursor>
|
||||
#include <QElapsedTimer>
|
||||
#include <QFile>
|
||||
#include <QTemporaryFile>
|
||||
#include <QRegularExpression>
|
||||
|
||||
// Don't let SDL hook our main function, since Qt is already
|
||||
// doing the same thing. This needs to be before any headers
|
||||
@@ -24,6 +25,9 @@
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
#include "antihookingprotection.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#elif defined(Q_OS_LINUX)
|
||||
#include <openssl/ssl.h>
|
||||
#endif
|
||||
@@ -44,26 +48,24 @@
|
||||
#include "settings/streamingpreferences.h"
|
||||
#include "gui/sdlgamepadkeynavigation.h"
|
||||
|
||||
#if !defined(QT_DEBUG) && defined(Q_OS_WIN32)
|
||||
// Log to file for release Windows builds
|
||||
#define USE_CUSTOM_LOGGER
|
||||
#if defined(Q_OS_WIN32)
|
||||
#define IS_UNSPECIFIED_HANDLE(x) ((x) == INVALID_HANDLE_VALUE || (x) == NULL)
|
||||
|
||||
// Log to file or console dynamically for Windows builds
|
||||
#define LOG_TO_FILE
|
||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
|
||||
// Use stdout logger on all Linux/BSD builds
|
||||
#define USE_CUSTOM_LOGGER
|
||||
#elif !defined(QT_DEBUG) && defined(Q_OS_DARWIN)
|
||||
// Log to file for release Mac builds
|
||||
#define USE_CUSTOM_LOGGER
|
||||
#define LOG_TO_FILE
|
||||
#else
|
||||
// For debug Windows and Mac builds, use default logger
|
||||
// Log to console for debug Mac builds
|
||||
#endif
|
||||
|
||||
#ifdef USE_CUSTOM_LOGGER
|
||||
static QElapsedTimer s_LoggerTime;
|
||||
static QTextStream s_LoggerStream(stdout);
|
||||
static QTextStream s_LoggerStream(stderr);
|
||||
static QMutex s_LoggerLock;
|
||||
static bool s_SuppressVerboseOutput;
|
||||
static QRegularExpression k_RikeyRegex("&rikey=\\w+");
|
||||
static QRegularExpression k_RikeyIdRegex("&rikeyid=[\\d-]+");
|
||||
#ifdef LOG_TO_FILE
|
||||
// Max log file size of 10 MB
|
||||
#define MAX_LOG_SIZE_BYTES (10 * 1024 * 1024)
|
||||
@@ -76,6 +78,22 @@ void logToLoggerStream(QString& message)
|
||||
{
|
||||
QMutexLocker lock(&s_LoggerLock);
|
||||
|
||||
#if defined(QT_DEBUG) && defined(Q_OS_WIN32)
|
||||
// Output log messages to a debugger if attached
|
||||
if (IsDebuggerPresent()) {
|
||||
static QString lineBuffer;
|
||||
lineBuffer += message;
|
||||
if (message.endsWith('\n')) {
|
||||
OutputDebugStringW(lineBuffer.toStdWString().c_str());
|
||||
lineBuffer.clear();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Strip session encryption keys and IVs from the logs
|
||||
message.replace(k_RikeyRegex, "&rikey=REDACTED");
|
||||
message.replace(k_RikeyIdRegex, "&rikeyid=REDACTED");
|
||||
|
||||
#ifdef LOG_TO_FILE
|
||||
if (s_LogLimitReached) {
|
||||
return;
|
||||
@@ -216,8 +234,6 @@ void ffmpegLogToDiskHandler(void* ptr, int level, const char* fmt, va_list vl)
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
@@ -306,13 +322,25 @@ int main(int argc, char *argv[])
|
||||
qputenv("QML_DISK_CACHE_PATH", Path::getQmlCacheDir().toUtf8());
|
||||
}
|
||||
|
||||
#ifdef USE_CUSTOM_LOGGER
|
||||
#ifdef Q_OS_WIN32
|
||||
// Grab the original std handles before we potentially redirect them later
|
||||
HANDLE oldConOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
HANDLE oldConErr = GetStdHandle(STD_ERROR_HANDLE);
|
||||
#endif
|
||||
|
||||
#ifdef LOG_TO_FILE
|
||||
QDir tempDir(Path::getLogDir());
|
||||
s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch())));
|
||||
if (s_LoggerFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QTextStream(stderr) << "Redirecting log output to " << s_LoggerFile->fileName() << Qt::endl;
|
||||
s_LoggerStream.setDevice(s_LoggerFile);
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Only log to a file if the user didn't redirect stderr somewhere else
|
||||
if (IS_UNSPECIFIED_HANDLE(oldConErr))
|
||||
#endif
|
||||
{
|
||||
s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch())));
|
||||
if (s_LoggerFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QTextStream(stderr) << "Redirecting log output to " << s_LoggerFile->fileName() << Qt::endl;
|
||||
s_LoggerStream.setDevice(s_LoggerFile);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -324,8 +352,6 @@ int main(int argc, char *argv[])
|
||||
av_log_set_callback(ffmpegLogToDiskHandler);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Create a crash dump when we crash on Windows
|
||||
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
|
||||
@@ -351,6 +377,11 @@ int main(int argc, char *argv[])
|
||||
SSL_free(nullptr);
|
||||
#endif
|
||||
|
||||
// We keep this at function scope to ensure it stays around while we're running,
|
||||
// becaue the Qt QPA will need to read it. Since the temporary file is only
|
||||
// created when open() is called, this doesn't do any harm for other platforms.
|
||||
QTemporaryFile eglfsConfigFile("eglfs_override_XXXXXX.conf");
|
||||
|
||||
// Avoid using High DPI on EGLFS. It breaks font rendering.
|
||||
// https://bugreports.qt.io/browse/QTBUG-64377
|
||||
//
|
||||
@@ -384,6 +415,17 @@ int main(int argc, char *argv[])
|
||||
qWarning() << "Unable to find a KMSDRM display device!";
|
||||
qWarning() << "On the Raspberry Pi, you must enable the 'fake KMS' driver in raspi-config to use Moonlight outside of the GUI environment.";
|
||||
}
|
||||
else if (!qEnvironmentVariableIsSet("QT_QPA_EGLFS_KMS_CONFIG")) {
|
||||
// HACK: Remove this when Qt is fixed to properly check for display support before picking a card
|
||||
QString cardOverride = WMUtils::getDrmCardOverride();
|
||||
if (!cardOverride.isEmpty()) {
|
||||
if (eglfsConfigFile.open()) {
|
||||
qInfo() << "Overriding default Qt EGLFS card selection to" << cardOverride;
|
||||
QTextStream(&eglfsConfigFile) << "{ \"device\": \"" << cardOverride << "\" }";
|
||||
qputenv("QT_QPA_EGLFS_KMS_CONFIG", eglfsConfigFile.fileName().toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EGLFS uses OpenGLES 2.0, so we will too. Some embedded platforms may not
|
||||
@@ -393,9 +435,18 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(Q_PROCESSOR_X86) && defined(SDL_HINT_VIDEO_X11_FORCE_EGL)
|
||||
// Some ARM and RISC-V embedded devices don't have working GLX which can cause
|
||||
// SDL to fail to find a working OpenGL implementation at all. Let's force EGL
|
||||
// on non-x86 platforms, since GLX is deprecated anyway.
|
||||
SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1");
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
// This avoids using the default keychain for SSL, which may cause
|
||||
// password prompts on macOS.
|
||||
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN32) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
if (!qEnvironmentVariableIsSet("QT_OPENGL")) {
|
||||
@@ -418,6 +469,12 @@ int main(int argc, char *argv[])
|
||||
qputenv("QSG_RENDER_LOOP", "basic");
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_DARWIN) && defined(QT_DEBUG)
|
||||
// Enable Metal valiation for debug builds
|
||||
qputenv("MTL_DEBUG_LAYER", "1");
|
||||
qputenv("MTL_SHADER_VALIDATION", "1");
|
||||
#endif
|
||||
|
||||
// We don't want system proxies to apply to us
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(false);
|
||||
|
||||
@@ -496,6 +553,13 @@ int main(int argc, char *argv[])
|
||||
// SDL doing it for us behind our backs.
|
||||
SDL_SetHint("SDL_MOUSE_AUTO_CAPTURE", "0");
|
||||
|
||||
// SDL will try to lock the mouse cursor on Wayland if it's not visible in order to
|
||||
// support applications that assume they can warp the cursor (which isn't possible
|
||||
// on Wayland). We don't want this behavior because it interferes with seamless mouse
|
||||
// mode when toggling between windowed and fullscreen modes by unexpectedly locking
|
||||
// the mouse cursor.
|
||||
SDL_SetHint("SDL_VIDEO_WAYLAND_EMULATE_MOUSE_WARP", "0");
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
// Allow thread naming using exceptions on debug builds. SDL doesn't use SEH
|
||||
// when throwing the exceptions, so we don't enable it for release builds out
|
||||
@@ -505,25 +569,34 @@ int main(int argc, char *argv[])
|
||||
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
#ifndef STEAM_LINK
|
||||
// Force use of the KMSDRM backend for SDL when using Qt platform plugins
|
||||
// that directly draw to the display without a windowing system.
|
||||
if (QGuiApplication::platformName() == "eglfs" || QGuiApplication::platformName() == "linuxfb") {
|
||||
qputenv("SDL_VIDEODRIVER", "kmsdrm");
|
||||
}
|
||||
#endif
|
||||
|
||||
GlobalCommandLineParser parser;
|
||||
GlobalCommandLineParser::ParseResult commandLineParserResult = parser.parse(app.arguments());
|
||||
switch (commandLineParserResult) {
|
||||
case GlobalCommandLineParser::ListRequested:
|
||||
#ifdef USE_CUSTOM_LOGGER
|
||||
// Don't log to the console since it will jumble the command output
|
||||
s_SuppressVerboseOutput = true;
|
||||
#endif
|
||||
#ifdef Q_OS_WIN32
|
||||
// Attach to the console to be able to print output.
|
||||
// Since we're a /SUBSYSTEM:WINDOWS app, we won't be attached by default.
|
||||
// If we don't have stdout or stderr handles (which will normally be the case
|
||||
// since we're a /SUBSYSTEM:WINDOWS app), attach to our parent console and use
|
||||
// that for stdout and stderr.
|
||||
//
|
||||
// If we do have stdout or stderr handles, that means the user has used standard
|
||||
// handle redirection. In that case, we don't want to override those handles.
|
||||
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||
HANDLE conOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (conOut != INVALID_HANDLE_VALUE && conOut != NULL) {
|
||||
// If we didn't have an old stdout/stderr handle, use the new CONOUT$ handle
|
||||
if (IS_UNSPECIFIED_HANDLE(oldConOut)) {
|
||||
freopen("CONOUT$", "w", stdout);
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
}
|
||||
HANDLE conErr = GetStdHandle(STD_ERROR_HANDLE);
|
||||
if (conErr != INVALID_HANDLE_VALUE && conErr != NULL) {
|
||||
if (IS_UNSPECIFIED_HANDLE(oldConErr)) {
|
||||
freopen("CONOUT$", "w", stderr);
|
||||
setvbuf(stderr, NULL, _IONBF, 0);
|
||||
}
|
||||
@@ -547,8 +620,7 @@ int main(int argc, char *argv[])
|
||||
runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch);
|
||||
|
||||
// Apply the initial translation based on user preference
|
||||
StreamingPreferences prefs;
|
||||
prefs.retranslate();
|
||||
StreamingPreferences::get()->retranslate();
|
||||
|
||||
// Trickily declare the translation for dialog buttons
|
||||
QCoreApplication::translate("QPlatformTheme", "&Yes");
|
||||
@@ -613,8 +685,8 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterUncreatableType<Session>("Session", 1, 0, "Session", "Session cannot be created from QML");
|
||||
qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0,
|
||||
"ComputerManager",
|
||||
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
||||
return new ComputerManager();
|
||||
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
|
||||
return new ComputerManager(StreamingPreferences::get(qmlEngine));
|
||||
});
|
||||
qmlRegisterSingletonType<AutoUpdateChecker>("AutoUpdateChecker", 1, 0,
|
||||
"AutoUpdateChecker",
|
||||
@@ -628,13 +700,13 @@ int main(int argc, char *argv[])
|
||||
});
|
||||
qmlRegisterSingletonType<SdlGamepadKeyNavigation>("SdlGamepadKeyNavigation", 1, 0,
|
||||
"SdlGamepadKeyNavigation",
|
||||
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
||||
return new SdlGamepadKeyNavigation();
|
||||
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
|
||||
return new SdlGamepadKeyNavigation(StreamingPreferences::get(qmlEngine));
|
||||
});
|
||||
qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0,
|
||||
"StreamingPreferences",
|
||||
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
|
||||
return new StreamingPreferences(qmlEngine);
|
||||
return StreamingPreferences::get(qmlEngine);
|
||||
});
|
||||
|
||||
// Create the identity manager on the main thread
|
||||
@@ -665,7 +737,7 @@ int main(int argc, char *argv[])
|
||||
case GlobalCommandLineParser::StreamRequested:
|
||||
{
|
||||
initialView = "qrc:/gui/CliStartStreamSegue.qml";
|
||||
StreamingPreferences* preferences = new StreamingPreferences(&app);
|
||||
StreamingPreferences* preferences = StreamingPreferences::get();
|
||||
StreamCommandLineParser streamParser;
|
||||
streamParser.parse(app.arguments(), preferences);
|
||||
QString host = streamParser.getHost();
|
||||
@@ -697,7 +769,7 @@ int main(int argc, char *argv[])
|
||||
ListCommandLineParser listParser;
|
||||
listParser.parse(app.arguments());
|
||||
auto launcher = new CliListApps::Launcher(listParser.getHost(), listParser, &app);
|
||||
launcher->execute(new ComputerManager(&app));
|
||||
launcher->execute(new ComputerManager(StreamingPreferences::get()));
|
||||
hasGUI = false;
|
||||
break;
|
||||
}
|
||||
@@ -718,5 +790,12 @@ int main(int argc, char *argv[])
|
||||
// sometimes freezing and blocking process exit.
|
||||
QThreadPool::globalInstance()->waitForDone(30000);
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Without an explicit flush, console redirection for the list command
|
||||
// doesn't work reliably (sometimes the target file contains no text).
|
||||
fflush(stderr);
|
||||
fflush(stdout);
|
||||
#endif
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240" fill="white"><path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="white"><path d="M20.317 4.3698a19.7913 19.7913 0 0 0-4.8851-1.5152.0741.0741 0 0 0-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 0 0-.0785-.037 19.7363 19.7363 0 0 0-4.8852 1.515.0699.0699 0 0 0-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 0 0 .0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 0 0 .0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 0 0-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 0 1-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 0 1 .0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 0 1 .0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 0 1-.0066.1276 12.2986 12.2986 0 0 1-1.873.8914.0766.0766 0 0 0-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 0 0 .0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 0 0 .0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 0 0-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
+6
-1
@@ -17,7 +17,7 @@
|
||||
<file>res/baseline-help_outline-24px.svg</file>
|
||||
<file>res/baseline-error_outline-24px.svg</file>
|
||||
<file>res/baseline-check_circle_outline-24px.svg</file>
|
||||
<file>res/Discord-Logo-White.svg</file>
|
||||
<file>res/discord.svg</file>
|
||||
<file>languages/qml_de.ts</file>
|
||||
<file>languages/qml_de.qm</file>
|
||||
<file>languages/qml_fr.ts</file>
|
||||
@@ -64,6 +64,10 @@
|
||||
<file>languages/qml_cs.qm</file>
|
||||
<file>languages/qml_he.ts</file>
|
||||
<file>languages/qml_he.qm</file>
|
||||
<file>languages/qml_ckb.ts</file>
|
||||
<file>languages/qml_ckb.qm</file>
|
||||
<file>languages/qml_lt.ts</file>
|
||||
<file>languages/qml_lt.qm</file>
|
||||
<!-- Don't include pt_BR until it is more complete -->
|
||||
<!--file>languages/qml_pt_BR.qm</file-->
|
||||
<!--file>languages/qml_pt_BR.ts</file-->
|
||||
@@ -82,5 +86,6 @@
|
||||
<file alias="d3d11_genyuv_pixel.fxc">shaders/d3d11_genyuv_pixel.fxc</file>
|
||||
<file alias="d3d11_bt601lim_pixel.fxc">shaders/d3d11_bt601lim_pixel.fxc</file>
|
||||
<file alias="d3d11_bt2020lim_pixel.fxc">shaders/d3d11_bt2020lim_pixel.fxc</file>
|
||||
<file alias="vt_renderer.metal">shaders/vt_renderer.metal</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QTranslator>
|
||||
#include <QCoreApplication>
|
||||
#include <QLocale>
|
||||
#include <QReadWriteLock>
|
||||
#include <QtMath>
|
||||
|
||||
#include <QtDebug>
|
||||
@@ -37,6 +38,7 @@
|
||||
#define SER_DEFAULTVER "defaultver"
|
||||
#define SER_PACKETSIZE "packetsize"
|
||||
#define SER_DETECTNETBLOCKING "detectnetblocking"
|
||||
#define SER_SHOWPERFOVERLAY "showperfoverlay"
|
||||
#define SER_SWAPMOUSEBUTTONS "swapmousebuttons"
|
||||
#define SER_MUTEONFOCUSLOSS "muteonfocusloss"
|
||||
#define SER_BACKGROUNDGAMEPAD "backgroundgamepad"
|
||||
@@ -48,18 +50,50 @@
|
||||
|
||||
#define CURRENT_DEFAULT_VER 2
|
||||
|
||||
StreamingPreferences::StreamingPreferences(QObject *parent)
|
||||
: QObject(parent),
|
||||
m_QmlEngine(nullptr)
|
||||
static StreamingPreferences* s_GlobalPrefs;
|
||||
static QReadWriteLock s_GlobalPrefsLock;
|
||||
|
||||
StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine)
|
||||
: m_QmlEngine(qmlEngine)
|
||||
{
|
||||
reload();
|
||||
}
|
||||
|
||||
StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent)
|
||||
: QObject(parent),
|
||||
m_QmlEngine(qmlEngine)
|
||||
StreamingPreferences* StreamingPreferences::get(QQmlEngine *qmlEngine)
|
||||
{
|
||||
reload();
|
||||
{
|
||||
QReadLocker readGuard(&s_GlobalPrefsLock);
|
||||
|
||||
// If we have a preference object and it's associated with a QML engine or
|
||||
// if the caller didn't specify a QML engine, return the existing object.
|
||||
if (s_GlobalPrefs && (s_GlobalPrefs->m_QmlEngine || !qmlEngine)) {
|
||||
// The lifetime logic here relies on the QML engine also being a singleton.
|
||||
Q_ASSERT(!qmlEngine || s_GlobalPrefs->m_QmlEngine == qmlEngine);
|
||||
return s_GlobalPrefs;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
QWriteLocker writeGuard(&s_GlobalPrefsLock);
|
||||
|
||||
// If we already have an preference object but the QML engine is now available,
|
||||
// associate the QML engine with the preferences.
|
||||
if (s_GlobalPrefs) {
|
||||
if (!s_GlobalPrefs->m_QmlEngine) {
|
||||
s_GlobalPrefs->m_QmlEngine = qmlEngine;
|
||||
}
|
||||
else {
|
||||
// We could reach this codepath if another thread raced with us
|
||||
// and created the object while we were outside the pref lock.
|
||||
Q_ASSERT(!qmlEngine || s_GlobalPrefs->m_QmlEngine == qmlEngine);
|
||||
}
|
||||
}
|
||||
else {
|
||||
s_GlobalPrefs = new StreamingPreferences(qmlEngine);
|
||||
}
|
||||
|
||||
return s_GlobalPrefs;
|
||||
}
|
||||
}
|
||||
|
||||
void StreamingPreferences::reload()
|
||||
@@ -97,6 +131,7 @@ void StreamingPreferences::reload()
|
||||
richPresence = settings.value(SER_RICHPRESENCE, true).toBool();
|
||||
gamepadMouse = settings.value(SER_GAMEPADMOUSE, true).toBool();
|
||||
detectNetworkBlocking = settings.value(SER_DETECTNETBLOCKING, true).toBool();
|
||||
showPerformanceOverlay = settings.value(SER_SHOWPERFOVERLAY, false).toBool();
|
||||
packetSize = settings.value(SER_PACKETSIZE, 0).toInt();
|
||||
swapMouseButtons = settings.value(SER_SWAPMOUSEBUTTONS, false).toBool();
|
||||
muteOnFocusLoss = settings.value(SER_MUTEONFOCUSLOSS, false).toBool();
|
||||
@@ -252,6 +287,10 @@ QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Langua
|
||||
return "cs";
|
||||
case LANG_HE:
|
||||
return "he";
|
||||
case LANG_CKB:
|
||||
return "ckb";
|
||||
case LANG_LT:
|
||||
return "lt";
|
||||
case LANG_AUTO:
|
||||
default:
|
||||
return QLocale::system().name();
|
||||
@@ -280,6 +319,7 @@ void StreamingPreferences::save()
|
||||
settings.setValue(SER_GAMEPADMOUSE, gamepadMouse);
|
||||
settings.setValue(SER_PACKETSIZE, packetSize);
|
||||
settings.setValue(SER_DETECTNETBLOCKING, detectNetworkBlocking);
|
||||
settings.setValue(SER_SHOWPERFOVERLAY, showPerformanceOverlay);
|
||||
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
||||
settings.setValue(SER_HDR, enableHdr);
|
||||
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
|
||||
|
||||
@@ -9,8 +9,7 @@ class StreamingPreferences : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
StreamingPreferences(QObject *parent = nullptr);
|
||||
StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent = nullptr);
|
||||
static StreamingPreferences* get(QQmlEngine *qmlEngine = nullptr);
|
||||
|
||||
Q_INVOKABLE static int
|
||||
getDefaultBitrate(int width, int height, int fps);
|
||||
@@ -92,6 +91,8 @@ public:
|
||||
LANG_PL,
|
||||
LANG_CS,
|
||||
LANG_HE,
|
||||
LANG_CKB,
|
||||
LANG_LT,
|
||||
};
|
||||
Q_ENUM(Language);
|
||||
|
||||
@@ -120,6 +121,7 @@ public:
|
||||
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged)
|
||||
Q_PROPERTY(bool gamepadMouse MEMBER gamepadMouse NOTIFY gamepadMouseChanged)
|
||||
Q_PROPERTY(bool detectNetworkBlocking MEMBER detectNetworkBlocking NOTIFY detectNetworkBlockingChanged)
|
||||
Q_PROPERTY(bool showPerformanceOverlay MEMBER showPerformanceOverlay NOTIFY showPerformanceOverlayChanged)
|
||||
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
||||
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
|
||||
Q_PROPERTY(bool enableHdr MEMBER enableHdr NOTIFY enableHdrChanged)
|
||||
@@ -156,6 +158,7 @@ public:
|
||||
bool richPresence;
|
||||
bool gamepadMouse;
|
||||
bool detectNetworkBlocking;
|
||||
bool showPerformanceOverlay;
|
||||
bool swapMouseButtons;
|
||||
bool muteOnFocusLoss;
|
||||
bool backgroundGamepad;
|
||||
@@ -196,6 +199,7 @@ signals:
|
||||
void richPresenceChanged();
|
||||
void gamepadMouseChanged();
|
||||
void detectNetworkBlockingChanged();
|
||||
void showPerformanceOverlayChanged();
|
||||
void mouseButtonsChanged();
|
||||
void muteOnFocusLossChanged();
|
||||
void backgroundGamepadChanged();
|
||||
@@ -206,6 +210,8 @@ signals:
|
||||
void languageChanged();
|
||||
|
||||
private:
|
||||
explicit StreamingPreferences(QQmlEngine *qmlEngine);
|
||||
|
||||
QString getSuffixFromLanguage(Language lang);
|
||||
|
||||
QQmlEngine* m_QmlEngine;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
#include "d3d11_video_pixel_start.hlsli"
|
||||
|
||||
cbuffer CSC_CONST_BUF : register(b0)
|
||||
cbuffer CSC_CONST_BUF : register(b1)
|
||||
{
|
||||
min16float3x3 cscMatrix;
|
||||
min16float3 offsets;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
min16float4 main(ShaderInput input) : SV_TARGET
|
||||
{
|
||||
// Clamp the chrominance texcoords to avoid sampling the row of texels adjacent to the alignment padding
|
||||
min16float3 yuv = min16float3(luminancePlane.Sample(theSampler, input.tex),
|
||||
chrominancePlane.Sample(theSampler, input.tex));
|
||||
chrominancePlane.Sample(theSampler, min(input.tex, chromaTexMax.rg)));
|
||||
|
||||
// Subtract the YUV offset for limited vs full range
|
||||
yuv -= offsets;
|
||||
|
||||
@@ -6,4 +6,9 @@ struct ShaderInput
|
||||
{
|
||||
float4 pos : SV_POSITION;
|
||||
float2 tex : TEXCOORD0;
|
||||
};
|
||||
};
|
||||
|
||||
cbuffer ChromaLimitBuf : register(b0)
|
||||
{
|
||||
min16float3 chromaTexMax;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using namespace metal;
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
float4 position [[ position ]];
|
||||
float2 texCoords;
|
||||
};
|
||||
|
||||
struct CscParams
|
||||
{
|
||||
float3 matrix[3];
|
||||
float3 offsets;
|
||||
};
|
||||
|
||||
constexpr sampler s(coord::normalized, address::clamp_to_edge, filter::linear);
|
||||
|
||||
vertex Vertex vs_draw(constant Vertex *vertices [[ buffer(0) ]], uint id [[ vertex_id ]])
|
||||
{
|
||||
return vertices[id];
|
||||
}
|
||||
|
||||
fragment float4 ps_draw_biplanar(Vertex v [[ stage_in ]],
|
||||
constant CscParams &cscParams [[ buffer(0) ]],
|
||||
texture2d<float> luminancePlane [[ texture(0) ]],
|
||||
texture2d<float> chrominancePlane [[ texture(1) ]])
|
||||
{
|
||||
float3 yuv = float3(luminancePlane.sample(s, v.texCoords).r, chrominancePlane.sample(s, v.texCoords).rg);
|
||||
yuv -= cscParams.offsets;
|
||||
|
||||
float3 rgb;
|
||||
rgb.r = dot(yuv, cscParams.matrix[0]);
|
||||
rgb.g = dot(yuv, cscParams.matrix[1]);
|
||||
rgb.b = dot(yuv, cscParams.matrix[2]);
|
||||
return float4(rgb, 1.0f);
|
||||
}
|
||||
|
||||
fragment float4 ps_draw_rgb(Vertex v [[ stage_in ]],
|
||||
texture2d<float> rgbTexture [[ texture(0) ]])
|
||||
{
|
||||
return rgbTexture.sample(s, v.texCoords);
|
||||
}
|
||||
@@ -64,6 +64,48 @@ IAudioRenderer* Session::createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATI
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Session::initializeAudioRenderer()
|
||||
{
|
||||
int error;
|
||||
|
||||
SDL_assert(m_OriginalAudioConfig.channelCount > 0);
|
||||
SDL_assert(m_AudioRenderer == nullptr);
|
||||
SDL_assert(m_OpusDecoder == nullptr);
|
||||
|
||||
m_AudioRenderer = createAudioRenderer(&m_OriginalAudioConfig);
|
||||
|
||||
// We may be unable to create an audio renderer right now
|
||||
if (m_AudioRenderer == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow the chosen renderer to remap Opus channels as needed to ensure proper output
|
||||
m_ActiveAudioConfig = m_OriginalAudioConfig;
|
||||
m_AudioRenderer->remapChannels(&m_ActiveAudioConfig);
|
||||
|
||||
// Create the Opus decoder with the renderer's preferred channel mapping
|
||||
m_OpusDecoder =
|
||||
opus_multistream_decoder_create(m_ActiveAudioConfig.sampleRate,
|
||||
m_ActiveAudioConfig.channelCount,
|
||||
m_ActiveAudioConfig.streams,
|
||||
m_ActiveAudioConfig.coupledStreams,
|
||||
m_ActiveAudioConfig.mapping,
|
||||
&error);
|
||||
if (m_OpusDecoder == nullptr) {
|
||||
delete m_AudioRenderer;
|
||||
m_AudioRenderer = nullptr;
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create decoder: %d",
|
||||
error);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio stream has %d channels",
|
||||
m_ActiveAudioConfig.channelCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
int Session::getAudioRendererCapabilities(int audioConfiguration)
|
||||
{
|
||||
// Build a fake OPUS_MULTISTREAM_CONFIGURATION to give
|
||||
@@ -108,39 +150,8 @@ int Session::arInit(int /* audioConfiguration */,
|
||||
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||
void* /* arContext */, int /* arFlags */)
|
||||
{
|
||||
int error;
|
||||
|
||||
SDL_memcpy(&s_ActiveSession->m_AudioConfig, opusConfig, sizeof(*opusConfig));
|
||||
|
||||
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(&s_ActiveSession->m_AudioConfig);
|
||||
if (s_ActiveSession->m_AudioRenderer == nullptr) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Allow the chosen renderer to remap Opus channels as needed to ensure proper output
|
||||
s_ActiveSession->m_AudioRenderer->remapChannels(&s_ActiveSession->m_AudioConfig);
|
||||
|
||||
// Create the Opus decoder with the renderer's preferred channel mapping
|
||||
s_ActiveSession->m_OpusDecoder =
|
||||
opus_multistream_decoder_create(s_ActiveSession->m_AudioConfig.sampleRate,
|
||||
s_ActiveSession->m_AudioConfig.channelCount,
|
||||
s_ActiveSession->m_AudioConfig.streams,
|
||||
s_ActiveSession->m_AudioConfig.coupledStreams,
|
||||
s_ActiveSession->m_AudioConfig.mapping,
|
||||
&error);
|
||||
if (s_ActiveSession->m_OpusDecoder == NULL) {
|
||||
delete s_ActiveSession->m_AudioRenderer;
|
||||
s_ActiveSession->m_AudioRenderer = nullptr;
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to create decoder: %d",
|
||||
error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio stream has %d channels",
|
||||
s_ActiveSession->m_AudioConfig.channelCount);
|
||||
|
||||
SDL_memcpy(&s_ActiveSession->m_OriginalAudioConfig, opusConfig, sizeof(*opusConfig));
|
||||
s_ActiveSession->initializeAudioRenderer();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -194,7 +205,7 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
}
|
||||
|
||||
if (s_ActiveSession->m_AudioRenderer != nullptr) {
|
||||
int desiredSize = sizeof(short) * s_ActiveSession->m_AudioConfig.samplesPerFrame * s_ActiveSession->m_AudioConfig.channelCount;
|
||||
int desiredSize = sizeof(short) * s_ActiveSession->m_ActiveAudioConfig.samplesPerFrame * s_ActiveSession->m_ActiveAudioConfig.channelCount;
|
||||
void* buffer = s_ActiveSession->m_AudioRenderer->getAudioBuffer(&desiredSize);
|
||||
if (buffer == nullptr) {
|
||||
return;
|
||||
@@ -204,13 +215,13 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
(unsigned char*)sampleData,
|
||||
sampleLength,
|
||||
(short*)buffer,
|
||||
desiredSize / sizeof(short) / s_ActiveSession->m_AudioConfig.channelCount,
|
||||
desiredSize / sizeof(short) / s_ActiveSession->m_ActiveAudioConfig.channelCount,
|
||||
0);
|
||||
|
||||
// Update desiredSize with the number of bytes actually populated by the decoding operation
|
||||
if (samplesDecoded > 0) {
|
||||
SDL_assert(desiredSize >= (int)(sizeof(short) * samplesDecoded * s_ActiveSession->m_AudioConfig.channelCount));
|
||||
desiredSize = sizeof(short) * samplesDecoded * s_ActiveSession->m_AudioConfig.channelCount;
|
||||
SDL_assert(desiredSize >= (int)(sizeof(short) * samplesDecoded * s_ActiveSession->m_ActiveAudioConfig.channelCount));
|
||||
desiredSize = sizeof(short) * samplesDecoded * s_ActiveSession->m_ActiveAudioConfig.channelCount;
|
||||
}
|
||||
else {
|
||||
desiredSize = 0;
|
||||
@@ -220,6 +231,9 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Reinitializing audio renderer after failure");
|
||||
|
||||
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
||||
s_ActiveSession->m_OpusDecoder = nullptr;
|
||||
|
||||
delete s_ActiveSession->m_AudioRenderer;
|
||||
s_ActiveSession->m_AudioRenderer = nullptr;
|
||||
}
|
||||
@@ -234,14 +248,13 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
// to drop samples to account for the time we've spent blocking audio rendering
|
||||
// so we return to real-time playback and don't accumulate latency.
|
||||
Uint32 audioReinitStartTime = SDL_GetTicks();
|
||||
if (s_ActiveSession->initializeAudioRenderer()) {
|
||||
Uint32 audioReinitStopTime = SDL_GetTicks();
|
||||
|
||||
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(&s_ActiveSession->m_AudioConfig);
|
||||
|
||||
Uint32 audioReinitStopTime = SDL_GetTicks();
|
||||
|
||||
s_ActiveSession->m_DropAudioEndTime = audioReinitStopTime + (audioReinitStopTime - audioReinitStartTime);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio reinitialization took %d ms - starting drop window",
|
||||
audioReinitStopTime - audioReinitStartTime);
|
||||
s_ActiveSession->m_DropAudioEndTime = audioReinitStopTime + (audioReinitStopTime - audioReinitStartTime);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Audio reinitialization took %d ms - starting drop window",
|
||||
audioReinitStopTime - audioReinitStartTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,12 +100,6 @@ void* SdlAudioRenderer::getAudioBuffer(int*)
|
||||
|
||||
bool SdlAudioRenderer::submitAudio(int bytesWritten)
|
||||
{
|
||||
// Our device may enter a permanent error status upon removal, so we need
|
||||
// to recreate the audio device to pick up the new default audio device.
|
||||
if (SDL_GetAudioDeviceStatus(m_AudioDevice) == SDL_AUDIO_STOPPED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytesWritten == 0) {
|
||||
// Nothing to do
|
||||
return true;
|
||||
@@ -118,8 +112,20 @@ bool SdlAudioRenderer::submitAudio(int bytesWritten)
|
||||
}
|
||||
|
||||
// Provide backpressure on the queue to ensure too many frames don't build up
|
||||
// in SDL's audio queue.
|
||||
while (SDL_GetQueuedAudioSize(m_AudioDevice) / m_FrameSize > 10) {
|
||||
// in SDL's audio queue, but don't wait forever to avoid a deadlock if the
|
||||
// audio device fails.
|
||||
for (int i = 0; i < 100; i++) {
|
||||
// Our device may enter a permanent error status upon removal, so we need
|
||||
// to recreate the audio device to pick up the new default audio device.
|
||||
if (SDL_GetAudioDeviceStatus(m_AudioDevice) == SDL_AUDIO_STOPPED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only queue more samples where there are 10 frames or less in SDL's queue
|
||||
if (SDL_GetQueuedAudioSize(m_AudioDevice) / m_FrameSize <= 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_Delay(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -112,15 +112,42 @@ void SdlInputHandler::handleAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
|
||||
pointerId = (uint32_t)event->fingerId;
|
||||
}
|
||||
|
||||
// Try to send it as a native touch event, otherwise fall back to our touch emulation
|
||||
if (LiSendTouchEvent(eventType, pointerId, vidrelx / dst.w, vidrely / dst.h, event->pressure,
|
||||
0.0f, 0.0f, LI_ROT_UNKNOWN) == LI_ERR_UNSUPPORTED) {
|
||||
emulateAbsoluteFingerEvent(event);
|
||||
// Try to send it as a native pen/touch event, otherwise fall back to our touch emulation
|
||||
if (LiGetHostFeatureFlags() & LI_FF_PEN_TOUCH_EVENTS) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 22)
|
||||
bool isPen = false;
|
||||
|
||||
int numTouchDevices = SDL_GetNumTouchDevices();
|
||||
for (int i = 0; i < numTouchDevices; i++) {
|
||||
if (event->touchId == SDL_GetTouchDevice(i)) {
|
||||
const char* touchName = SDL_GetTouchName(i);
|
||||
|
||||
// SDL will report "pen" as the name of pen input devices on Windows.
|
||||
// https://github.com/libsdl-org/SDL/pull/5926
|
||||
isPen = touchName && SDL_strcmp(touchName, "pen") == 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPen) {
|
||||
LiSendPenEvent(eventType, LI_TOOL_TYPE_PEN, 0, vidrelx / dst.w, vidrely / dst.h, event->pressure,
|
||||
0.0f, 0.0f, LI_ROT_UNKNOWN, LI_TILT_UNKNOWN);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
LiSendTouchEvent(eventType, pointerId, vidrelx / dst.w, vidrely / dst.h, event->pressure,
|
||||
0.0f, 0.0f, LI_ROT_UNKNOWN);
|
||||
}
|
||||
|
||||
if (!m_DisabledTouchFeedback) {
|
||||
// Disable touch feedback when passing touch natively
|
||||
disableTouchFeedback();
|
||||
m_DisabledTouchFeedback = true;
|
||||
}
|
||||
}
|
||||
else if (!m_DisabledTouchFeedback) {
|
||||
// Disable touch feedback when passing touch natively
|
||||
disableTouchFeedback();
|
||||
m_DisabledTouchFeedback = true;
|
||||
else {
|
||||
emulateAbsoluteFingerEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,15 +68,48 @@ void SdlInputHandler::sendGamepadState(GamepadState* state)
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char lt = state->lt;
|
||||
unsigned char rt = state->rt;
|
||||
short lsX = state->lsX;
|
||||
short lsY = state->lsY;
|
||||
short rsX = state->rsX;
|
||||
short rsY = state->rsY;
|
||||
|
||||
// When in single controller mode, merge all gamepad state together
|
||||
if (!m_MultiController) {
|
||||
for (int i = 0; i < MAX_GAMEPADS; i++) {
|
||||
if (m_GamepadState[i].index == state->index) {
|
||||
buttons |= m_GamepadState[i].buttons;
|
||||
if (lt < m_GamepadState[i].lt) {
|
||||
lt = m_GamepadState[i].lt;
|
||||
}
|
||||
if (rt < m_GamepadState[i].rt) {
|
||||
rt = m_GamepadState[i].rt;
|
||||
}
|
||||
|
||||
// We use abs() here instead of qAbs() for get proper integer promotion to
|
||||
// correctly handle abs(-32768), which is not representable in a short.
|
||||
if (abs(lsX) < abs(m_GamepadState[i].lsX) || abs(lsY) < abs(m_GamepadState[i].lsY)) {
|
||||
lsX = m_GamepadState[i].lsX;
|
||||
lsY = m_GamepadState[i].lsY;
|
||||
}
|
||||
if (abs(rsX) < abs(m_GamepadState[i].rsX) || abs(rsY) < abs(m_GamepadState[i].rsY)) {
|
||||
rsX = m_GamepadState[i].rsX;
|
||||
rsY = m_GamepadState[i].rsY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LiSendMultiControllerEvent(state->index,
|
||||
m_GamepadMask,
|
||||
buttons,
|
||||
state->lt,
|
||||
state->rt,
|
||||
state->lsX,
|
||||
state->lsY,
|
||||
state->rsX,
|
||||
state->rsY);
|
||||
lt,
|
||||
rt,
|
||||
lsX,
|
||||
lsY,
|
||||
rsX,
|
||||
rsY);
|
||||
}
|
||||
|
||||
void SdlInputHandler::sendGamepadBatteryState(GamepadState* state, SDL_JoystickPowerLevel level)
|
||||
@@ -124,11 +157,11 @@ Uint32 SdlInputHandler::mouseEmulationTimerCallback(Uint32 interval, void *param
|
||||
{
|
||||
auto gamepad = reinterpret_cast<GamepadState*>(param);
|
||||
|
||||
short rawX;
|
||||
short rawY;
|
||||
int rawX;
|
||||
int rawY;
|
||||
|
||||
// Determine which analog stick is currently receiving the strongest input
|
||||
if ((uint32_t)qAbs(gamepad->lsX) + qAbs(gamepad->lsY) > (uint32_t)qAbs(gamepad->rsX) + qAbs(gamepad->rsY)) {
|
||||
if (abs(gamepad->lsX) + abs(gamepad->lsY) > abs(gamepad->rsX) + abs(gamepad->rsY)) {
|
||||
rawX = gamepad->lsX;
|
||||
rawY = -gamepad->lsY;
|
||||
}
|
||||
@@ -922,6 +955,10 @@ QString SdlInputHandler::getUnmappedGamepads()
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
||||
|
||||
// Flush stale events so they aren't processed by the main session event loop
|
||||
SDL_FlushEvents(SDL_JOYDEVICEADDED, SDL_JOYDEVICEREMOVED);
|
||||
SDL_FlushEvents(SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEREMAPPED);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,14 +42,6 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i
|
||||
// Allow gamepad input when the app doesn't have focus if requested
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, prefs.backgroundGamepad ? "1" : "0");
|
||||
|
||||
// If absolute mouse mode is enabled, use relative mode warp (which
|
||||
// is via normal motion events that are influenced by mouse acceleration).
|
||||
// Otherwise, we'll use raw input capture which is straight from the device
|
||||
// without modification by the OS.
|
||||
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP,
|
||||
prefs.absoluteMouseMode ? "1" : "0",
|
||||
SDL_HINT_OVERRIDE);
|
||||
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 15)
|
||||
// For older versions of SDL (2.0.14 and earlier), use SDL_HINT_GRAB_KEYBOARD
|
||||
SDL_SetHintWithPriority(SDL_HINT_GRAB_KEYBOARD,
|
||||
|
||||
@@ -114,7 +114,7 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
|
||||
}
|
||||
|
||||
// Send this text to the PC
|
||||
LiSendUtf8TextEvent(text, strlen(text));
|
||||
LiSendUtf8TextEvent(text, (unsigned int)strlen(text));
|
||||
|
||||
// SDL_GetClipboardText() allocates, so we must free
|
||||
SDL_free((void*)text);
|
||||
|
||||
@@ -79,6 +79,24 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||
return;
|
||||
}
|
||||
|
||||
// Batch all pending mouse motion events to save CPU time
|
||||
Sint32 x = event->x, y = event->y, xrel = event->xrel, yrel = event->yrel;
|
||||
SDL_Event nextEvent;
|
||||
while (SDL_PeepEvents(&nextEvent, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) {
|
||||
event = &nextEvent.motion;
|
||||
|
||||
// Ignore synthetic mouse events
|
||||
if (event->which != SDL_TOUCH_MOUSEID) {
|
||||
x = event->x;
|
||||
y = event->y;
|
||||
xrel += event->xrel;
|
||||
yrel += event->yrel;
|
||||
}
|
||||
}
|
||||
|
||||
// We should not reference the original event anymore
|
||||
event = nullptr;
|
||||
|
||||
if (m_AbsoluteMouseMode) {
|
||||
int windowWidth, windowHeight;
|
||||
SDL_GetWindowSize(m_Window, &windowWidth, &windowHeight);
|
||||
@@ -97,14 +115,11 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||
// Use the stream and window sizes to determine the video region
|
||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||
|
||||
mouseInVideoRegion = isMouseInVideoRegion(event->x,
|
||||
event->y,
|
||||
windowWidth,
|
||||
windowHeight);
|
||||
mouseInVideoRegion = isMouseInVideoRegion(x, y, windowWidth, windowHeight);
|
||||
|
||||
// Clamp motion to the video region
|
||||
short x = qMin(qMax(event->x - dst.x, 0), dst.w);
|
||||
short y = qMin(qMax(event->y - dst.y, 0), dst.h);
|
||||
x = qMin(qMax(x - dst.x, 0), dst.w);
|
||||
y = qMin(qMax(y - dst.y, 0), dst.h);
|
||||
|
||||
// Send the mouse position update if one of the following is true:
|
||||
// a) it is in the video region now
|
||||
@@ -119,7 +134,7 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||
}
|
||||
}
|
||||
if (mouseInVideoRegion || m_MouseWasInVideoRegion || m_PendingMouseButtonsAllUpOnVideoRegionLeave) {
|
||||
LiSendMousePositionEvent(x, y, dst.w, dst.h);
|
||||
LiSendMousePositionEvent((short)x, (short)y, dst.w, dst.h);
|
||||
}
|
||||
|
||||
// Adjust the cursor visibility if applicable
|
||||
@@ -135,7 +150,7 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||
m_MouseWasInVideoRegion = mouseInVideoRegion;
|
||||
}
|
||||
else {
|
||||
LiSendMouseMoveEvent(event->xrel, event->yrel);
|
||||
LiSendMouseMoveEvent(xrel, yrel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user