mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2025-07-01 15:26:09 +00:00
Compare commits
242 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1dbdcb5279 | ||
|
47452371db | ||
|
4001b05fca | ||
|
75e917622e | ||
|
75359bb1c4 | ||
|
c93f7e7385 | ||
|
48bcd725c9 | ||
|
e807a52cfa | ||
|
9c9bfd8428 | ||
|
29b1304337 | ||
|
cc0b574bb1 | ||
|
7a769172a1 | ||
|
d9c7a245ef | ||
|
469a5c78f1 | ||
|
fabb4fdadc | ||
|
9491884cb4 | ||
|
bf51577787 | ||
|
4c5bcee8dc | ||
|
fd70865026 | ||
|
351aaa6759 | ||
|
59bc625cc7 | ||
|
b2f765e8ef | ||
|
bdd9a3a994 | ||
|
2ecafabcab | ||
|
dd2a99a96b | ||
|
9ff2ac0974 | ||
|
5760d08c33 | ||
|
edd7a134d8 | ||
|
ffa87c5f01 | ||
|
ff7e61c6d9 | ||
|
6ad96fba42 | ||
|
85856114b2 | ||
|
7decfae792 | ||
|
b6008b15dc | ||
|
260ec3d80d | ||
|
7ec2e50334 | ||
|
e532b9167a | ||
|
52d5890372 | ||
|
f34d11994f | ||
|
359c92340d | ||
|
15e337fff8 | ||
|
707dd3cb83 | ||
|
98f6a09991 | ||
|
f1d0e97681 | ||
|
103f988dbf | ||
|
2257cb0cef | ||
|
b6a3369243 | ||
|
4af9623727 | ||
|
515db03fe5 | ||
|
e44d097683 | ||
|
208d048358 | ||
|
9936085aee | ||
|
3279d9c3f6 | ||
|
e571d5833c | ||
|
3531fe0a4f | ||
|
286676e5c1 | ||
|
6ce02616f0 | ||
|
13880353d8 | ||
|
ec69dad8d7 | ||
|
72ae324d71 | ||
|
901cbd255c | ||
|
2a63ad53d7 | ||
|
9b3d4c1ad7 | ||
|
054e334066 | ||
|
6d023c2dfa | ||
|
0e2d5bf441 | ||
|
023b6b2772 | ||
|
6f39d120cb | ||
|
9cf305865b | ||
|
6d47287b60 | ||
|
6b11f43302 | ||
|
60fb6881b2 | ||
|
b79c116bd9 | ||
|
895d0a6bf3 | ||
|
5a1ef55767 | ||
|
76deafbd7b | ||
|
ae2693a860 | ||
|
0783b28ba6 | ||
|
f786e94c7b | ||
|
1caee721e8 | ||
|
accd30176f | ||
|
7721fa6d7c | ||
|
8e295aab00 | ||
|
4838a75c58 | ||
|
c5ec8c0fdb | ||
|
b266f61ba6 | ||
|
d1dffdc34c | ||
|
268f8db26b | ||
|
de28eda266 | ||
|
3e9e497203 | ||
|
96fb6ee5e9 | ||
|
02853e74ba | ||
|
46910cf774 | ||
|
b1c77ff80e | ||
|
92ee4a3046 | ||
|
6ec0c79899 | ||
|
1c281ee3fc | ||
|
e2b7cbfe62 | ||
|
67e89d9e16 | ||
|
bbf6d9722a | ||
|
34549864ad | ||
|
1303a700e0 | ||
|
25132a1f7b | ||
|
7074463d0f | ||
|
99311403fa | ||
|
8b50eea485 | ||
|
302dca6c0c | ||
|
f756be87ff | ||
|
9d99ecbca6 | ||
|
1475bdfbba | ||
|
69a3406d4a | ||
|
7e17d82ee5 | ||
|
153db55519 | ||
|
4290a54ffa | ||
|
a6e549471c | ||
|
04edfdc4ca | ||
|
db30faf602 | ||
|
cdb610f121 | ||
|
e404722e7c | ||
|
1840c47751 | ||
|
3bd571bc90 | ||
|
224153e230 | ||
|
ef12850867 | ||
|
caf322d934 | ||
|
9043e8f663 | ||
|
76fd502262 | ||
|
9186feca80 | ||
|
df814fef4a | ||
|
d2b3bc962f | ||
|
876375f7e9 | ||
|
e662e93a53 | ||
|
7c6954b5f6 | ||
|
416003248b | ||
|
6d6cd6fc35 | ||
|
17448c02b0 | ||
|
f3a75e8e76 | ||
|
7da085480c | ||
|
bed3a6ecd8 | ||
|
e01c42153c | ||
|
9df65cb814 | ||
|
fddb4881fb | ||
|
5765c254cd | ||
|
ea724a05a6 | ||
|
369f614b59 | ||
|
8606b2c95e | ||
|
fafddddfe0 | ||
|
1bb16be183 | ||
|
8e2aa87c4f | ||
|
2aea070d93 | ||
|
37ace0060e | ||
|
28b4272123 | ||
|
19660174b7 | ||
|
2beaf10ea5 | ||
|
665352ec95 | ||
|
ab791cf4c8 | ||
|
ef7dff32aa | ||
|
9227ebfec9 | ||
|
f138827cdf | ||
|
e25919e0f9 | ||
|
99749d4730 | ||
|
9e811f54f1 | ||
|
0bb0d27d64 | ||
|
ede5ab8671 | ||
|
6c6f808365 | ||
|
dd9569913b | ||
|
778eb07c5f | ||
|
e2ffeae3f6 | ||
|
eb6d16fdcf | ||
|
e548697a36 | ||
|
c707dab70a | ||
|
da0244c538 | ||
|
ff332d45f8 | ||
|
9e92c07cb7 | ||
|
7f009a4b8e | ||
|
8ac378f467 | ||
|
82ec773119 | ||
|
3580286807 | ||
|
e226091c19 | ||
|
074b4520e5 | ||
|
de30eeaa66 | ||
|
fe9282e7d9 | ||
|
45ccd1a811 | ||
|
52583f5c71 | ||
|
952ebcd0d2 | ||
|
d5a198b764 | ||
|
b5b2731d5f | ||
|
d085722911 | ||
|
2e29ef8d74 | ||
|
34fa7167b1 | ||
|
27b173b76b | ||
|
d73df12367 | ||
|
725e51f9b3 | ||
|
0c6689a5b4 | ||
|
32115c639e | ||
|
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 |
36
README.md
36
README.md
@ -13,6 +13,7 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
|
||||
## Features
|
||||
- Hardware accelerated video decoding on Windows, Mac, and Linux
|
||||
- H.264, HEVC, and AV1 codec support (AV1 requires Sunshine and a supported host GPU)
|
||||
- YUV 4:4:4 support (Sunshine only)
|
||||
- HDR streaming support
|
||||
- 7.1 surround sound audio support
|
||||
- 10-point multitouch support (Sunshine only)
|
||||
@ -25,9 +26,9 @@ 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)
|
||||
- [Generic ARM SBC packages for Debian](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 SBC packages for Debian](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-RISC%E2%80%90V-Single-Board-Computers)
|
||||
- [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
|
||||
@ -39,24 +40,33 @@ Hosting for Moonlight's Debian and L4T package repositories is graciously provid
|
||||
## Building
|
||||
|
||||
### Windows Build Requirements
|
||||
* Qt 5.15 SDK or later. Qt 6 is also supported for x64 and ARM64 builds.
|
||||
* Qt 6.7 SDK or later (earlier versions may work but are not officially supported)
|
||||
* [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)
|
||||
* Graphics Tools (only if running debug builds)
|
||||
* Install "Graphics Tools" in the Optional Features page of the Windows Settings app.
|
||||
* Alternatively, run `dism /online /add-capability /capabilityname:Tools.Graphics.DirectX~~~~0.0.1.0` and reboot.
|
||||
|
||||
### macOS Build Requirements
|
||||
* Qt 6.4 SDK or later
|
||||
* Xcode 13 or later
|
||||
* Qt 6.7 SDK or later (earlier versions may work but are not officially supported)
|
||||
* Xcode 14 or later (earlier versions may work but are not officially supported)
|
||||
* [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 also supported.
|
||||
* 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 libavformat-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/
|
||||
* Building the Vulkan HDR renderer requires a `libplacebo-dev`/`libplacebo-devel` version of at least v338.0 and FFmpeg 6.1 or later.
|
||||
* Debian/Ubuntu:
|
||||
* Base Requirements: `libegl1-mesa-dev libgl1-mesa-dev libopus-dev libsdl2-dev libsdl2-ttf-dev libssl-dev libavcodec-dev libavformat-dev libswscale-dev libva-dev libvdpau-dev libxkbcommon-dev wayland-protocols libdrm-dev`
|
||||
* Qt 6 (Recommended): `qt6-base-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`
|
||||
* Qt 5: `qtbase5-dev qt5-qmake qtdeclarative5-dev qtquickcontrols2-5-dev qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtquick-window2 qml-module-qtquick2 qtwayland5`
|
||||
* RedHat/Fedora (RPM Fusion repo required):
|
||||
* Base Requirements: `openssl-devel SDL2-devel SDL2_ttf-devel ffmpeg-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel libdrm-devel`
|
||||
* Qt 6 (Recommended): `qt6-qtsvg-devel qt6-qtdeclarative-devel`
|
||||
* Qt 5: `qt5-qtsvg-devel qt5-qtquickcontrols2-devel`
|
||||
* Building the Vulkan renderer requires a `libplacebo-dev`/`libplacebo-devel` version of at least v7.349.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
|
||||
@ -73,8 +83,8 @@ Hosting for Moonlight's Debian and L4T package repositories is graciously provid
|
||||
* 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 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.
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.14</string>
|
||||
<string>11.0.0</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
@ -36,6 +36,8 @@
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Moonlight uses the local network to connect to your gaming PC for streaming.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>VERSION</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
|
67
app/SDL_compat.h
Normal file
67
app/SDL_compat.h
Normal file
@ -0,0 +1,67 @@
|
||||
//
|
||||
// Compatibility header for older version of SDL.
|
||||
// Include this instead of SDL.h directly.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
// SDL_FRect wasn't added until 2.0.10
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 10)
|
||||
typedef struct SDL_FRect
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float w;
|
||||
float h;
|
||||
} SDL_FRect;
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_VIDEO_X11_FORCE_EGL
|
||||
#define SDL_HINT_VIDEO_X11_FORCE_EGL "SDL_VIDEO_X11_FORCE_EGL"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER
|
||||
#define SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER "SDL_KMSDRM_REQUIRE_DRM_MASTER"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED
|
||||
#define SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED "SDL_ALLOW_ALT_TAB_WHILE_GRABBED"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE
|
||||
#define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE "SDL_JOYSTICK_HIDAPI_PS4_RUMBLE"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE
|
||||
#define SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE "SDL_JOYSTICK_HIDAPI_PS5_RUMBLE"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_WINDOWS_USE_D3D9EX
|
||||
#define SDL_HINT_WINDOWS_USE_D3D9EX "SDL_WINDOWS_USE_D3D9EX"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS
|
||||
#define SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS "SDL_GAMECONTROLLER_USE_BUTTON_LABELS"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_MOUSE_RELATIVE_SCALING
|
||||
#define SDL_HINT_MOUSE_RELATIVE_SCALING "SDL_MOUSE_RELATIVE_SCALING"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_AUDIO_DEVICE_APP_NAME
|
||||
#define SDL_HINT_AUDIO_DEVICE_APP_NAME "SDL_AUDIO_DEVICE_APP_NAME"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_APP_NAME
|
||||
#define SDL_HINT_APP_NAME "SDL_APP_NAME"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_MOUSE_AUTO_CAPTURE
|
||||
#define SDL_HINT_MOUSE_AUTO_CAPTURE "SDL_MOUSE_AUTO_CAPTURE"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP
|
||||
#define SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP "SDL_VIDEO_WAYLAND_EMULATE_MOUSE_WARP"
|
||||
#endif
|
62
app/app.pro
62
app/app.pro
@ -12,11 +12,14 @@ include(../globaldefs.pri)
|
||||
|
||||
# Precompile QML files to avoid writing qmlcache on portable versions.
|
||||
# Since this binds the app against the Qt runtime version, we will only
|
||||
# do this for Windows and Mac, since they ship with the Qt runtime.
|
||||
win32|macx {
|
||||
# do this for Windows and Mac (when disable-prebuilts is not defined),
|
||||
# since they always ship with the matching build of the Qt runtime.
|
||||
!disable-prebuilts {
|
||||
win32|macx {
|
||||
CONFIG(release, debug|release) {
|
||||
CONFIG += qtquickcompiler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEMPLATE = app
|
||||
@ -52,7 +55,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,13 +67,18 @@ 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
|
||||
PKGCONFIG += openssl sdl2 SDL2_ttf
|
||||
|
||||
# We have our own optimized libopus.a for Steam Link
|
||||
if(!config_SL|disable-prebuilts) {
|
||||
PKGCONFIG += opus
|
||||
}
|
||||
|
||||
!disable-ffmpeg {
|
||||
packagesExist(libavcodec) {
|
||||
PKGCONFIG += libavcodec libavutil
|
||||
PKGCONFIG += libavcodec libavutil libswscale
|
||||
CONFIG += ffmpeg
|
||||
|
||||
!disable-libva {
|
||||
@ -145,20 +153,24 @@ unix:!macx {
|
||||
}
|
||||
}
|
||||
win32 {
|
||||
LIBS += -llibssl -llibcrypto -lSDL2 -lSDL2_ttf -lavcodec -lavutil -lopus -ldxgi -ld3d11
|
||||
CONFIG += ffmpeg
|
||||
LIBS += -llibssl -llibcrypto -lSDL2 -lSDL2_ttf -lavcodec -lavutil -lswscale -lopus -ldxgi -ld3d11 -llibplacebo
|
||||
CONFIG += ffmpeg libplacebo
|
||||
}
|
||||
win32:!winrt {
|
||||
CONFIG += soundio discord-rpc
|
||||
}
|
||||
macx {
|
||||
LIBS += -lssl -lcrypto -lavcodec.61 -lavutil.59 -lopus -framework SDL2 -framework SDL2_ttf
|
||||
!disable-prebuilts {
|
||||
LIBS += -lssl.3 -lcrypto.3 -lavcodec.61 -lavutil.59 -lswscale.8 -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 += \
|
||||
@ -202,6 +214,7 @@ SOURCES += \
|
||||
wm.cpp
|
||||
|
||||
HEADERS += \
|
||||
SDL_compat.h \
|
||||
backend/nvaddress.h \
|
||||
backend/nvapp.h \
|
||||
cli/pair.h \
|
||||
@ -243,6 +256,7 @@ ffmpeg {
|
||||
DEFINES += HAVE_FFMPEG
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg.cpp \
|
||||
streaming/video/ffmpeg-renderers/genhwaccel.cpp \
|
||||
streaming/video/ffmpeg-renderers/sdlvid.cpp \
|
||||
streaming/video/ffmpeg-renderers/swframemapper.cpp \
|
||||
streaming/video/ffmpeg-renderers/pacer/pacer.cpp
|
||||
@ -250,6 +264,7 @@ ffmpeg {
|
||||
HEADERS += \
|
||||
streaming/video/ffmpeg.h \
|
||||
streaming/video/ffmpeg-renderers/renderer.h \
|
||||
streaming/video/ffmpeg-renderers/genhwaccel.h \
|
||||
streaming/video/ffmpeg-renderers/sdlvid.h \
|
||||
streaming/video/ffmpeg-renderers/swframemapper.h \
|
||||
streaming/video/ffmpeg-renderers/pacer/pacer.h
|
||||
@ -355,6 +370,13 @@ config_EGL {
|
||||
config_SL {
|
||||
message(Steam Link build configuration selected)
|
||||
|
||||
!disable-prebuilts {
|
||||
# Link against our NEON-optimized libopus build
|
||||
LIBS += -L$$PWD/../libs/steamlink/lib
|
||||
INCLUDEPATH += $$PWD/../libs/steamlink/include
|
||||
LIBS += -lopus -larmasm -lNE10
|
||||
}
|
||||
|
||||
DEFINES += EMBEDDED_BUILD STEAM_LINK HAVE_SLVIDEO HAVE_SLAUDIO
|
||||
LIBS += -lSLVideo -lSLAudio
|
||||
|
||||
@ -385,6 +407,7 @@ macx {
|
||||
message(VideoToolbox renderer selected)
|
||||
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg-renderers/vt_base.mm \
|
||||
streaming/video/ffmpeg-renderers/vt_avsamplelayer.mm \
|
||||
streaming/video/ffmpeg-renderers/vt_metal.mm
|
||||
|
||||
@ -461,7 +484,12 @@ TRANSLATIONS += \
|
||||
languages/qml_pl.ts \
|
||||
languages/qml_cs.ts \
|
||||
languages/qml_he.ts \
|
||||
languages/qml_ckb.ts
|
||||
languages/qml_ckb.ts \
|
||||
languages/qml_lt.ts \
|
||||
languages/qml_et.ts \
|
||||
languages/qml_bg.ts \
|
||||
languages/qml_eo.ts \
|
||||
languages/qml_ta.ts
|
||||
|
||||
# Additional import path used to resolve QML modules in Qt Creator's code model
|
||||
QML_IMPORT_PATH =
|
||||
@ -550,15 +578,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
|
||||
|
||||
!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)"
|
||||
|
@ -95,6 +95,14 @@ void IdentityManager::createCredentials(QSettings& settings)
|
||||
BIO_free(biokey);
|
||||
BIO_free(biocert);
|
||||
|
||||
// Check that the new keypair is valid before persisting it
|
||||
if (getSslCertificate().isNull()) {
|
||||
qFatal("Newly generated certificate is unreadable");
|
||||
}
|
||||
if (getSslKey().isNull()) {
|
||||
qFatal("Newly generated private key is unreadable");
|
||||
}
|
||||
|
||||
settings.setValue(SER_CERT, m_CachedPemCert);
|
||||
settings.setValue(SER_KEY, m_CachedPrivateKey);
|
||||
|
||||
@ -123,10 +131,10 @@ IdentityManager::IdentityManager()
|
||||
|
||||
// We should have valid credentials now. If not, we're screwed
|
||||
if (getSslCertificate().isNull()) {
|
||||
qFatal("Newly generated certificate is unreadable");
|
||||
qFatal("Certificate is unreadable");
|
||||
}
|
||||
if (getSslKey().isNull()) {
|
||||
qFatal("Newly generated private key is unreadable");
|
||||
qFatal("Private key is unreadable");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,13 +59,6 @@ public:
|
||||
|
||||
void showMessage(QString message, MessageType type) const
|
||||
{
|
||||
#if defined(Q_OS_WIN32)
|
||||
UINT flags = MB_OK | MB_TOPMOST | MB_SETFOREGROUND;
|
||||
flags |= (type == Info ? MB_ICONINFORMATION : MB_ICONERROR);
|
||||
QString title = "Moonlight";
|
||||
MessageBoxW(nullptr, reinterpret_cast<const wchar_t *>(message.utf16()),
|
||||
reinterpret_cast<const wchar_t *>(title.utf16()), flags);
|
||||
#endif
|
||||
message = message.endsWith('\n') ? message : message + '\n';
|
||||
fputs(qPrintable(message), type == Info ? stdout : stderr);
|
||||
}
|
||||
@ -94,7 +87,7 @@ public:
|
||||
|
||||
bool getToggleOptionValue(QString name, bool defaultValue) const
|
||||
{
|
||||
QRegularExpression re(QString("^(%1|no-%1)$").arg(name));
|
||||
static QRegularExpression re(QString("^(%1|no-%1)$").arg(name));
|
||||
QStringList options = optionNames().filter(re);
|
||||
if (options.isEmpty()) {
|
||||
return defaultValue;
|
||||
@ -113,7 +106,7 @@ public:
|
||||
|
||||
QPair<int,int> getResolutionOptionValue(QString name) const
|
||||
{
|
||||
QRegularExpression re("^(\\d+)x(\\d+)$", QRegularExpression::CaseInsensitiveOption);
|
||||
static QRegularExpression re("^(\\d+)x(\\d+)$", QRegularExpression::CaseInsensitiveOption);
|
||||
auto match = re.match(value(name));
|
||||
if (!match.hasMatch()) {
|
||||
showError(QString("Invalid %1 format: %2").arg(name, value(name)));
|
||||
@ -374,6 +367,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
||||
parser.addToggleOption("keep-awake", "prevent display sleep while streaming");
|
||||
parser.addToggleOption("performance-overlay", "show performance overlay");
|
||||
parser.addToggleOption("hdr", "HDR streaming");
|
||||
parser.addToggleOption("yuv444", "YUV 4:4:4 sampling, if supported");
|
||||
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());
|
||||
@ -385,7 +379,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
||||
parser.handleUnknownOptions();
|
||||
|
||||
// Resolve display's width and height
|
||||
QRegularExpression resolutionRexExp("^(720|1080|1440|4K|resolution)$");
|
||||
static QRegularExpression resolutionRexExp("^(720|1080|1440|4K|resolution)$");
|
||||
QStringList resoOptions = parser.optionNames().filter(resolutionRexExp);
|
||||
bool displaySet = !resoOptions.isEmpty();
|
||||
if (displaySet) {
|
||||
@ -412,20 +406,20 @@ 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)) {
|
||||
fprintf(stderr, "Warning: FPS is out of the supported range (10 - 480 FPS). Performance may suffer!\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve --bitrate option
|
||||
if (parser.isSet("bitrate")) {
|
||||
preferences->bitrateKbps = parser.getIntOption("bitrate");
|
||||
if (!inRange(preferences->bitrateKbps, 500, 150000)) {
|
||||
parser.showError("Bitrate must be in range: 500 - 150000");
|
||||
if (!inRange(preferences->bitrateKbps, 500, 500000)) {
|
||||
fprintf(stderr, "Warning: Bitrate is out of the supported range (500 - 500000 Kbps). Performance may suffer!\n");
|
||||
}
|
||||
} else if (displaySet || parser.isSet("fps")) {
|
||||
preferences->bitrateKbps = preferences->getDefaultBitrate(
|
||||
preferences->width, preferences->height, preferences->fps);
|
||||
preferences->width, preferences->height, preferences->fps, preferences->enableYUV444);
|
||||
}
|
||||
|
||||
// Resolve --packet-size option
|
||||
@ -494,6 +488,9 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
||||
// Resolve --hdr and --no-hdr options
|
||||
preferences->enableHdr = parser.getToggleOptionValue("hdr", preferences->enableHdr);
|
||||
|
||||
// Resolve --yuv444 and --no-yuv444 options
|
||||
preferences->enableYUV444 = parser.getToggleOptionValue("yuv444", preferences->enableYUV444);
|
||||
|
||||
// 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
|
||||
@ -18,6 +18,7 @@ enum State {
|
||||
StateSeekComputer,
|
||||
StateListApp,
|
||||
StateSeekApp,
|
||||
StateSeekEnded,
|
||||
StateFailure,
|
||||
};
|
||||
|
||||
@ -107,6 +108,7 @@ public:
|
||||
// Occurs when a computer is updated
|
||||
case Event::ComputerUpdated:
|
||||
if (m_State == StateSeekApp) {
|
||||
m_State = StateSeekEnded;
|
||||
m_Arguments.isPrintCSV() ? printAppsCSV(m_Computer->appList) : printApps(m_Computer->appList);
|
||||
|
||||
QCoreApplication::exit(0);
|
||||
|
@ -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
|
||||
|
@ -34,6 +34,40 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="6.1.0" date="2024-09-16">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>Experimental YUV 4:4:4 support for improved text clarity during remote desktop usage</li>
|
||||
<li>HDR is now supported with software decoding (requires GPU with Vulkan support)</li>
|
||||
<li>Bitrate limit can now be increased to 500 Mbps</li>
|
||||
<li>Audio decoding now uses 32-bit floating point format</li>
|
||||
</ul>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Fixed incorrect color range when streaming with AV1 from a host with an AMD GPU</li>
|
||||
<li>Updated community-contributed translations from Weblate</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<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>
|
||||
|
@ -5,5 +5,5 @@ Exec=moonlight
|
||||
Icon=moonlight
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Qt;Game;
|
||||
Categories=Qt;Game;Network;RemoteAccess;
|
||||
Keywords=nvidia;gamestream;stream;sunshine;remote play;
|
||||
|
@ -14,4 +14,4 @@ renice -10 -p $(pidof PE_Single_CPU)
|
||||
|
||||
# Renice Moonlight itself to avoid preemption by background tasks
|
||||
# Write output to a logfile in /tmp
|
||||
exec nice -n -10 ./bin/moonlight > /tmp/moonlight.log
|
||||
exec nice -n -10 ./bin/moonlight > /tmp/moonlight.log 2>&1
|
||||
|
@ -339,6 +339,19 @@ CenteredGridView {
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
visible: appGrid.count === 0
|
||||
|
||||
Label {
|
||||
text: qsTr("This computer doesn't seem to have any applications or some applications are hidden")
|
||||
font.pointSize: 20
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
NavigableMessageDialog {
|
||||
id: quitAppDialog
|
||||
property string appName : ""
|
||||
@ -350,7 +363,7 @@ CenteredGridView {
|
||||
|
||||
function quitApp() {
|
||||
var component = Qt.createComponent("QuitSegue.qml")
|
||||
var params = {"appName": appName, "quitRunningAppFn": () => appModel.quitRunningApp()}
|
||||
var params = {"appName": appName, "quitRunningAppFn": function() { appModel.quitRunningApp() }}
|
||||
if (segueToStream) {
|
||||
// Store the session and app name if we're going to stream after
|
||||
// successfully quitting the old app.
|
||||
|
@ -2,7 +2,6 @@ import QtQuick 2.0
|
||||
import QtQuick.Controls 2.2
|
||||
|
||||
import ComputerManager 1.0
|
||||
import SdlGamepadKeyNavigation 1.0
|
||||
|
||||
Item {
|
||||
function onSearchingComputer() {
|
||||
@ -39,10 +38,6 @@ Item {
|
||||
if (!launcher.isExecuted()) {
|
||||
toolBar.visible = false
|
||||
|
||||
// Normally this is enabled by PcView, but we will won't
|
||||
// load PcView when streaming from the command-line.
|
||||
SdlGamepadKeyNavigation.enable()
|
||||
|
||||
launcher.searchingComputer.connect(onSearchingComputer)
|
||||
launcher.pairing.connect(onPairing)
|
||||
launcher.failed.connect(onFailed)
|
||||
|
@ -2,7 +2,6 @@ import QtQuick 2.0
|
||||
import QtQuick.Controls 2.2
|
||||
|
||||
import ComputerManager 1.0
|
||||
import SdlGamepadKeyNavigation 1.0
|
||||
|
||||
Item {
|
||||
function onSearchingComputer() {
|
||||
@ -38,10 +37,6 @@ Item {
|
||||
if (!launcher.isExecuted()) {
|
||||
toolBar.visible = false
|
||||
|
||||
// Normally this is enabled by PcView, but we will won't
|
||||
// load PcView when streaming from the command-line.
|
||||
SdlGamepadKeyNavigation.enable()
|
||||
|
||||
launcher.searchingComputer.connect(onSearchingComputer)
|
||||
launcher.searchingApp.connect(onSearchingApp)
|
||||
launcher.sessionCreated.connect(onSessionCreated)
|
||||
@ -85,7 +80,7 @@ Item {
|
||||
|
||||
function quitApp() {
|
||||
var component = Qt.createComponent("QuitSegue.qml")
|
||||
var params = {"appName": appName, "quitRunningAppFn": () => launcher.quitRunningApp()}
|
||||
var params = {"appName": appName, "quitRunningAppFn": function() { launcher.quitRunningApp() }}
|
||||
stackView.push(component.createObject(stackView, params))
|
||||
}
|
||||
|
||||
|
@ -34,11 +34,6 @@ CenteredGridView {
|
||||
// Setup signals on CM
|
||||
ComputerManager.computerAddCompleted.connect(addComplete)
|
||||
|
||||
// This is a bit of a hack to do this here as opposed to main.qml, but
|
||||
// we need it enabled before calling getConnectedGamepads() and PcView
|
||||
// is never destroyed, so it should be okay.
|
||||
SdlGamepadKeyNavigation.enable()
|
||||
|
||||
// Highlight the first item if a gamepad is connected
|
||||
if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
|
||||
currentIndex = 0
|
||||
|
@ -281,11 +281,14 @@ Flickable {
|
||||
StreamingPreferences.width = selectedWidth
|
||||
StreamingPreferences.height = selectedHeight
|
||||
|
||||
if (StreamingPreferences.autoAdjustBitrate) {
|
||||
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
|
||||
StreamingPreferences.height,
|
||||
StreamingPreferences.fps);
|
||||
StreamingPreferences.fps,
|
||||
StreamingPreferences.enableYUV444);
|
||||
slider.value = StreamingPreferences.bitrateKbps
|
||||
}
|
||||
}
|
||||
|
||||
lastIndexValue = currentIndex
|
||||
}
|
||||
@ -446,11 +449,14 @@ Flickable {
|
||||
if (StreamingPreferences.fps !== selectedFps) {
|
||||
StreamingPreferences.fps = selectedFps
|
||||
|
||||
if (StreamingPreferences.autoAdjustBitrate) {
|
||||
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
|
||||
StreamingPreferences.height,
|
||||
StreamingPreferences.fps);
|
||||
StreamingPreferences.fps,
|
||||
StreamingPreferences.enableYUV444);
|
||||
slider.value = StreamingPreferences.bitrateKbps
|
||||
}
|
||||
}
|
||||
|
||||
lastIndexValue = currentIndex
|
||||
}
|
||||
@ -676,6 +682,10 @@ Flickable {
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: 5
|
||||
|
||||
Slider {
|
||||
id: slider
|
||||
|
||||
@ -683,22 +693,39 @@ Flickable {
|
||||
|
||||
stepSize: 500
|
||||
from : 500
|
||||
to: 150000
|
||||
to: StreamingPreferences.unlockBitrate ? 500000 : 150000
|
||||
|
||||
snapMode: "SnapOnRelease"
|
||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
||||
width: Math.min(bitrateDesc.implicitWidth, parent.width - (resetBitrateButton.visible ? resetBitrateButton.width + parent.spacing : 0))
|
||||
|
||||
onValueChanged: {
|
||||
bitrateTitle.text = qsTr("Video bitrate: %1 Mbps").arg(value / 1000.0)
|
||||
StreamingPreferences.bitrateKbps = value
|
||||
}
|
||||
|
||||
onMoved: {
|
||||
StreamingPreferences.autoAdjustBitrate = false
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Refresh the text after translations change
|
||||
languageChanged.connect(valueChanged)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: resetBitrateButton
|
||||
text: qsTr("Use Default (%1 Mbps)").arg(StreamingPreferences.getDefaultBitrate(StreamingPreferences.width, StreamingPreferences.height, StreamingPreferences.fps, StreamingPreferences.enableYUV444) / 1000.0)
|
||||
visible: StreamingPreferences.bitrateKbps !== StreamingPreferences.getDefaultBitrate(StreamingPreferences.width, StreamingPreferences.height, StreamingPreferences.fps, StreamingPreferences.enableYUV444)
|
||||
onClicked: {
|
||||
var defaultBitrate = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width, StreamingPreferences.height, StreamingPreferences.fps, StreamingPreferences.enableYUV444)
|
||||
StreamingPreferences.bitrateKbps = defaultBitrate
|
||||
StreamingPreferences.autoAdjustBitrate = true
|
||||
slider.value = defaultBitrate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
width: parent.width
|
||||
id: windowModeTitle
|
||||
@ -915,6 +942,46 @@ Flickable {
|
||||
}
|
||||
}
|
||||
|
||||
GroupBox {
|
||||
id: hostSettingsGroupBox
|
||||
width: (parent.width - (parent.leftPadding + parent.rightPadding))
|
||||
padding: 12
|
||||
title: "<font color=\"skyblue\">" + qsTr("Host Settings") + "</font>"
|
||||
font.pointSize: 12
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
CheckBox {
|
||||
id: optimizeGameSettingsCheck
|
||||
width: parent.width
|
||||
text: qsTr("Optimize game settings for streaming")
|
||||
font.pointSize: 12
|
||||
checked: StreamingPreferences.gameOptimizations
|
||||
onCheckedChanged: {
|
||||
StreamingPreferences.gameOptimizations = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: quitAppAfter
|
||||
width: parent.width
|
||||
text: qsTr("Quit app on host PC after ending stream")
|
||||
font.pointSize: 12
|
||||
checked: StreamingPreferences.quitAppAfter
|
||||
onCheckedChanged: {
|
||||
StreamingPreferences.quitAppAfter = checked
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 5000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("This will close the app or game you are streaming when you end your stream. You will lose any unsaved progress!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GroupBox {
|
||||
id: uiSettingsGroupBox
|
||||
width: (parent.width - (parent.leftPadding + parent.rightPadding))
|
||||
@ -1002,10 +1069,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
|
||||
@ -1014,10 +1081,10 @@ Flickable {
|
||||
text: "Svenska" // Swedish
|
||||
val: StreamingPreferences.LANG_SV
|
||||
}
|
||||
/* ListElement {
|
||||
ListElement {
|
||||
text: "Türkçe" // Turkish
|
||||
val: StreamingPreferences.LANG_TR
|
||||
} */
|
||||
}
|
||||
/* ListElement {
|
||||
text: "Українська" // Ukrainian
|
||||
val: StreamingPreferences.LANG_UK
|
||||
@ -1062,6 +1129,26 @@ Flickable {
|
||||
text: "کرمانجیی خواروو" // Central Kurdish
|
||||
val: StreamingPreferences.LANG_CKB
|
||||
} */
|
||||
/* ListElement {
|
||||
text: "Lietuvių kalba" // Lithuanian
|
||||
val: StreamingPreferences.LANG_LT
|
||||
} */
|
||||
/* ListElement {
|
||||
text: "Eesti" // Estonian
|
||||
val: StreamingPreferences.LANG_ET
|
||||
} */
|
||||
ListElement {
|
||||
text: "Български" // Bulgarian
|
||||
val: StreamingPreferences.LANG_BG
|
||||
}
|
||||
/* ListElement {
|
||||
text: "Esperanto"
|
||||
val: StreamingPreferences.LANG_EO
|
||||
} */
|
||||
ListElement {
|
||||
text: "தமிழ்" // Tamil
|
||||
val: StreamingPreferences.LANG_TA
|
||||
}
|
||||
}
|
||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||
onActivated : {
|
||||
@ -1416,46 +1503,6 @@ Flickable {
|
||||
}
|
||||
}
|
||||
|
||||
GroupBox {
|
||||
id: hostSettingsGroupBox
|
||||
width: (parent.width - (parent.leftPadding + parent.rightPadding))
|
||||
padding: 12
|
||||
title: "<font color=\"skyblue\">" + qsTr("Host Settings") + "</font>"
|
||||
font.pointSize: 12
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
CheckBox {
|
||||
id: optimizeGameSettingsCheck
|
||||
width: parent.width
|
||||
text: qsTr("Optimize game settings for streaming")
|
||||
font.pointSize: 12
|
||||
checked: StreamingPreferences.gameOptimizations
|
||||
onCheckedChanged: {
|
||||
StreamingPreferences.gameOptimizations = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: quitAppAfter
|
||||
width: parent.width
|
||||
text: qsTr("Quit app on host PC after ending stream")
|
||||
font.pointSize: 12
|
||||
checked: StreamingPreferences.quitAppAfter
|
||||
onCheckedChanged: {
|
||||
StreamingPreferences.quitAppAfter = checked
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 5000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("This will close the app or game you are streaming when you end your stream. You will lose any unsaved progress!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GroupBox {
|
||||
id: advancedSettingsGroupBox
|
||||
width: (parent.width - (parent.leftPadding + parent.rightPadding))
|
||||
@ -1492,7 +1539,6 @@ Flickable {
|
||||
|
||||
id: decoderComboBox
|
||||
textRole: "text"
|
||||
enabled: !enableHdr.checked
|
||||
model: ListModel {
|
||||
id: decoderListModel
|
||||
ListElement {
|
||||
@ -1514,21 +1560,6 @@ Flickable {
|
||||
StreamingPreferences.videoDecoderSelection = decoderListModel.get(currentIndex).val
|
||||
}
|
||||
}
|
||||
|
||||
// This handles the state of the enableHdr checkbox changing
|
||||
onEnabledChanged: {
|
||||
if (enabled) {
|
||||
StreamingPreferences.videoDecoderSelection = decoderListModel.get(currentIndex).val
|
||||
}
|
||||
else {
|
||||
StreamingPreferences.videoDecoderSelection = StreamingPreferences.VDS_AUTO
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 5000
|
||||
ToolTip.visible: hovered && !enabled
|
||||
ToolTip.text: qsTr("Enabling HDR overrides manual decoder selections.")
|
||||
}
|
||||
|
||||
Label {
|
||||
@ -1611,6 +1642,55 @@ Flickable {
|
||||
qsTr("HDR streaming is not supported on this PC.")
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: enableYUV444
|
||||
width: parent.width
|
||||
text: qsTr("Enable YUV 4:4:4 (Experimental)")
|
||||
font.pointSize: 12
|
||||
|
||||
checked: StreamingPreferences.enableYUV444
|
||||
onCheckedChanged: {
|
||||
// This is called on init, so only reset to default bitrate when checked state changes.
|
||||
if (StreamingPreferences.enableYUV444 != checked) {
|
||||
StreamingPreferences.enableYUV444 = checked
|
||||
if (StreamingPreferences.autoAdjustBitrate) {
|
||||
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
|
||||
StreamingPreferences.height,
|
||||
StreamingPreferences.fps,
|
||||
StreamingPreferences.enableYUV444);
|
||||
slider.value = StreamingPreferences.bitrateKbps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 5000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: enabled ?
|
||||
qsTr("Good for streaming desktop and text-heavy games, but not recommended for fast-paced games.")
|
||||
:
|
||||
qsTr("YUV 4:4:4 is not supported on this PC.")
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: unlockBitrate
|
||||
width: parent.width
|
||||
text: qsTr("Unlock bitrate limit (Experimental)")
|
||||
font.pointSize: 12
|
||||
|
||||
checked: StreamingPreferences.unlockBitrate
|
||||
onCheckedChanged: {
|
||||
StreamingPreferences.unlockBitrate = checked
|
||||
StreamingPreferences.bitrateKbps = Math.min(StreamingPreferences.bitrateKbps, slider.to)
|
||||
slider.value = StreamingPreferences.bitrateKbps
|
||||
}
|
||||
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.timeout: 5000
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("This unlocks extremely high video bitrates for use with Sunshine hosts. It should only be used when streaming over an Ethernet LAN connection.")
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: enableMdns
|
||||
width: parent.width
|
||||
|
@ -76,7 +76,7 @@ Item {
|
||||
streamSegueErrorDialog.text += "\n\n" + qsTr("This PC's Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network.")
|
||||
}
|
||||
|
||||
// Enable GUI gamepad usage now
|
||||
// Re-enable GUI gamepad usage now
|
||||
SdlGamepadKeyNavigation.enable()
|
||||
|
||||
if (quitAfter) {
|
||||
@ -119,7 +119,7 @@ Item {
|
||||
// Show the toolbar again when popped off the stack
|
||||
toolBar.visible = true
|
||||
|
||||
// Enable GUI gamepad usage now
|
||||
// Re-enable GUI gamepad usage now
|
||||
SdlGamepadKeyNavigation.enable()
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ Item {
|
||||
gc()
|
||||
|
||||
// Run the streaming session to completion
|
||||
session.exec(Window.window)
|
||||
session.exec(window)
|
||||
}
|
||||
|
||||
sourceComponent: Item {}
|
||||
|
@ -22,7 +22,8 @@ ApplicationWindow {
|
||||
width: 1280
|
||||
height: 600
|
||||
|
||||
Component.onCompleted: {
|
||||
// This function runs prior to creation of the initial StackView item
|
||||
function doEarlyInit() {
|
||||
// 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.
|
||||
@ -30,6 +31,10 @@ ApplicationWindow {
|
||||
Material.background = "#303030"
|
||||
}
|
||||
|
||||
SdlGamepadKeyNavigation.enable()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Show the window according to the user's preferences
|
||||
if (SystemProperties.hasDesktopEnvironment) {
|
||||
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) {
|
||||
@ -49,7 +54,7 @@ ApplicationWindow {
|
||||
if (SystemProperties.isWow64) {
|
||||
wow64Dialog.open()
|
||||
}
|
||||
else if (!SystemProperties.hasHardwareAcceleration) {
|
||||
else if (!SystemProperties.hasHardwareAcceleration && StreamingPreferences.videoDecoderSelection !== StreamingPreferences.VDS_FORCE_SOFTWARE) {
|
||||
if (SystemProperties.isRunningXWayland) {
|
||||
xWaylandDialog.open()
|
||||
}
|
||||
@ -64,9 +69,19 @@ ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
// It would be better to use TextMetrics here, but it always lays out
|
||||
// the text slightly more compactly than real Text does in ToolTip,
|
||||
// causing unexpected line breaks to be inserted
|
||||
Text {
|
||||
id: tooltipTextLayoutHelper
|
||||
visible: false
|
||||
font: ToolTip.toolTip.font
|
||||
text: ToolTip.toolTip.text
|
||||
}
|
||||
|
||||
// This configures the maximum width of the singleton attached QML ToolTip. If left unconstrained,
|
||||
// it will never insert a line break and just extend on forever.
|
||||
ToolTip.toolTip.contentWidth: ToolTip.toolTip.implicitContentWidth < 400 ? ToolTip.toolTip.implicitContentWidth : 400
|
||||
ToolTip.toolTip.contentWidth: Math.min(tooltipTextLayoutHelper.width, 400)
|
||||
|
||||
function goBack() {
|
||||
if (clearOnBack) {
|
||||
@ -81,10 +96,16 @@ ApplicationWindow {
|
||||
|
||||
StackView {
|
||||
id: stackView
|
||||
initialItem: initialView
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
Component.onCompleted: {
|
||||
// Perform our early initialization before constructing
|
||||
// the initial view and pushing it to the StackView
|
||||
doEarlyInit()
|
||||
push(initialView)
|
||||
}
|
||||
|
||||
onCurrentItemChanged: {
|
||||
// Ensure focus travels to the next view when going back
|
||||
if (currentItem) {
|
||||
@ -158,6 +179,9 @@ ApplicationWindow {
|
||||
pollingActive = true
|
||||
}
|
||||
}
|
||||
|
||||
// Poll for gamepad input only when the window is in focus
|
||||
SdlGamepadKeyNavigation.notifyWindowFocus(visible && active)
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
@ -176,6 +200,9 @@ ApplicationWindow {
|
||||
// if focus does not return within a few minutes.
|
||||
inactivityTimer.restart()
|
||||
}
|
||||
|
||||
// Poll for gamepad input only when the window is in focus
|
||||
SdlGamepadKeyNavigation.notifyWindowFocus(visible && active)
|
||||
}
|
||||
|
||||
// Workaround for lack of instanceof in Qt 5.9.
|
||||
|
@ -13,6 +13,7 @@ SdlGamepadKeyNavigation::SdlGamepadKeyNavigation(StreamingPreferences* prefs)
|
||||
m_Enabled(false),
|
||||
m_UiNavMode(false),
|
||||
m_FirstPoll(false),
|
||||
m_HasFocus(false),
|
||||
m_LastAxisNavigationEventTime(0)
|
||||
{
|
||||
m_PollingTimer = new QTimer(this);
|
||||
@ -54,7 +55,8 @@ void SdlGamepadKeyNavigation::enable()
|
||||
SDL_FlushEvent(SDL_CONTROLLERDEVICEADDED);
|
||||
|
||||
// Open all currently attached game controllers
|
||||
for (int i = 0; i < SDL_NumJoysticks(); i++) {
|
||||
int numJoysticks = SDL_NumJoysticks();
|
||||
for (int i = 0; i < numJoysticks; i++) {
|
||||
if (SDL_IsGameController(i)) {
|
||||
SDL_GameController* gc = SDL_GameControllerOpen(i);
|
||||
if (gc != nullptr) {
|
||||
@ -63,13 +65,10 @@ void SdlGamepadKeyNavigation::enable()
|
||||
}
|
||||
}
|
||||
|
||||
// Flush events on the first poll
|
||||
m_FirstPoll = true;
|
||||
|
||||
// Poll every 50 ms for a new joystick event
|
||||
m_PollingTimer->start(50);
|
||||
|
||||
m_Enabled = true;
|
||||
|
||||
// Start the polling timer if the window is focused
|
||||
updateTimerState();
|
||||
}
|
||||
|
||||
void SdlGamepadKeyNavigation::disable()
|
||||
@ -78,7 +77,9 @@ void SdlGamepadKeyNavigation::disable()
|
||||
return;
|
||||
}
|
||||
|
||||
m_PollingTimer->stop();
|
||||
m_Enabled = false;
|
||||
updateTimerState();
|
||||
Q_ASSERT(!m_PollingTimer->isActive());
|
||||
|
||||
while (!m_Gamepads.isEmpty()) {
|
||||
SDL_GameControllerClose(m_Gamepads[0]);
|
||||
@ -86,8 +87,12 @@ void SdlGamepadKeyNavigation::disable()
|
||||
}
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
||||
}
|
||||
|
||||
m_Enabled = false;
|
||||
void SdlGamepadKeyNavigation::notifyWindowFocus(bool hasFocus)
|
||||
{
|
||||
m_HasFocus = hasFocus;
|
||||
updateTimerState();
|
||||
}
|
||||
|
||||
void SdlGamepadKeyNavigation::onPollingTimerFired()
|
||||
@ -261,6 +266,20 @@ void SdlGamepadKeyNavigation::sendKey(QEvent::Type type, Qt::Key key, Qt::Keyboa
|
||||
}
|
||||
}
|
||||
|
||||
void SdlGamepadKeyNavigation::updateTimerState()
|
||||
{
|
||||
if (m_PollingTimer->isActive() && (!m_HasFocus || !m_Enabled)) {
|
||||
m_PollingTimer->stop();
|
||||
}
|
||||
else if (!m_PollingTimer->isActive() && m_HasFocus && m_Enabled) {
|
||||
// Flush events on the first poll
|
||||
m_FirstPoll = true;
|
||||
|
||||
// Poll every 50 ms for a new joystick event
|
||||
m_PollingTimer->start(50);
|
||||
}
|
||||
}
|
||||
|
||||
void SdlGamepadKeyNavigation::setUiNavMode(bool uiNavMode)
|
||||
{
|
||||
m_UiNavMode = uiNavMode;
|
||||
@ -271,7 +290,8 @@ int SdlGamepadKeyNavigation::getConnectedGamepads()
|
||||
Q_ASSERT(m_Enabled);
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < SDL_NumJoysticks(); i++) {
|
||||
int numJoysticks = SDL_NumJoysticks();
|
||||
for (int i = 0; i < numJoysticks; i++) {
|
||||
if (SDL_IsGameController(i)) {
|
||||
count++;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include <QTimer>
|
||||
#include <QEvent>
|
||||
|
||||
#include <SDL.h>
|
||||
#include "SDL_compat.h"
|
||||
|
||||
#include "settings/streamingpreferences.h"
|
||||
|
||||
@ -20,6 +20,8 @@ public:
|
||||
|
||||
Q_INVOKABLE void disable();
|
||||
|
||||
Q_INVOKABLE void notifyWindowFocus(bool hasFocus);
|
||||
|
||||
Q_INVOKABLE void setUiNavMode(bool settingsMode);
|
||||
|
||||
Q_INVOKABLE int getConnectedGamepads();
|
||||
@ -27,6 +29,8 @@ public:
|
||||
private:
|
||||
void sendKey(QEvent::Type type, Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
|
||||
|
||||
void updateTimerState();
|
||||
|
||||
private slots:
|
||||
void onPollingTimerFired();
|
||||
|
||||
@ -37,5 +41,6 @@ private:
|
||||
bool m_Enabled;
|
||||
bool m_UiNavMode;
|
||||
bool m_FirstPoll;
|
||||
bool m_HasFocus;
|
||||
Uint32 m_LastAxisNavigationEventTime;
|
||||
};
|
||||
|
BIN
app/languages/qml_bg.qm
Normal file
BIN
app/languages/qml_bg.qm
Normal file
Binary file not shown.
1331
app/languages/qml_bg.ts
Normal file
1331
app/languages/qml_bg.ts
Normal file
File diff suppressed because it is too large
Load Diff
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.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
app/languages/qml_eo.qm
Normal file
BIN
app/languages/qml_eo.qm
Normal file
Binary file not shown.
1331
app/languages/qml_eo.ts
Normal file
1331
app/languages/qml_eo.ts
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
app/languages/qml_et.qm
Normal file
BIN
app/languages/qml_et.qm
Normal file
Binary file not shown.
1323
app/languages/qml_et.ts
Normal file
1323
app/languages/qml_et.ts
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
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.
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.
File diff suppressed because it is too large
Load Diff
BIN
app/languages/qml_lt.qm
Normal file
BIN
app/languages/qml_lt.qm
Normal file
Binary file not shown.
1323
app/languages/qml_lt.ts
Normal file
1323
app/languages/qml_lt.ts
Normal file
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.
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.
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.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
app/languages/qml_ta.qm
Normal file
BIN
app/languages/qml_ta.qm
Normal file
Binary file not shown.
1331
app/languages/qml_ta.ts
Normal file
1331
app/languages/qml_ta.ts
Normal file
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.
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.
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.
File diff suppressed because it is too large
Load Diff
100
app/main.cpp
100
app/main.cpp
@ -17,7 +17,7 @@
|
||||
// doing the same thing. This needs to be before any headers
|
||||
// that might include SDL.h themselves.
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
#include "SDL_compat.h"
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
#include "streaming/video/ffmpeg.h"
|
||||
@ -25,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
|
||||
@ -59,47 +62,73 @@
|
||||
|
||||
static QElapsedTimer s_LoggerTime;
|
||||
static QTextStream s_LoggerStream(stderr);
|
||||
static QMutex s_LoggerLock;
|
||||
static QThreadPool s_LoggerThread;
|
||||
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)
|
||||
static int s_LogBytesWritten = 0;
|
||||
static bool s_LogLimitReached = false;
|
||||
static const uint64_t k_MaxLogSizeBytes = 10 * 1024 * 1024;
|
||||
static QAtomicInteger<uint64_t> s_LogBytesWritten = 0;
|
||||
static QFile* s_LoggerFile;
|
||||
#endif
|
||||
|
||||
class LoggerTask : public QRunnable
|
||||
{
|
||||
public:
|
||||
LoggerTask(const QString& msg) : m_Msg(msg)
|
||||
{
|
||||
setAutoDelete(true);
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
s_LoggerStream << m_Msg;
|
||||
s_LoggerStream.flush();
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_Msg;
|
||||
};
|
||||
|
||||
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) {
|
||||
auto oldLogSize = s_LogBytesWritten.fetchAndAddRelaxed(message.size());
|
||||
if (oldLogSize >= k_MaxLogSizeBytes) {
|
||||
return;
|
||||
}
|
||||
else if (s_LogBytesWritten >= MAX_LOG_SIZE_BYTES) {
|
||||
else if (oldLogSize >= k_MaxLogSizeBytes - message.size()) {
|
||||
s_LoggerThread.waitForDone();
|
||||
s_LoggerStream << "Log size limit reached!";
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
s_LoggerStream << Qt::endl;
|
||||
#else
|
||||
s_LoggerStream << endl;
|
||||
#endif
|
||||
s_LogLimitReached = true;
|
||||
s_LoggerStream.flush();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
s_LogBytesWritten += message.size();
|
||||
}
|
||||
#endif
|
||||
|
||||
s_LoggerStream << message;
|
||||
s_LoggerStream.flush();
|
||||
// Queue the log message to be written asynchronously
|
||||
s_LoggerThread.start(new LoggerTask(message));
|
||||
}
|
||||
|
||||
void sdlLogToDiskHandler(void*, int category, SDL_LogPriority priority, const char* message)
|
||||
@ -329,6 +358,9 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
#endif
|
||||
|
||||
// Serialize log messages on a single thread
|
||||
s_LoggerThread.setMaxThreadCount(1);
|
||||
|
||||
s_LoggerTime.start();
|
||||
qInstallMessageHandler(qtLogToDiskHandler);
|
||||
SDL_LogSetOutputFunction(sdlLogToDiskHandler, nullptr);
|
||||
@ -420,7 +452,7 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(Q_PROCESSOR_X86) && defined(SDL_HINT_VIDEO_X11_FORCE_EGL)
|
||||
#ifndef Q_PROCESSOR_X86
|
||||
// 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.
|
||||
@ -475,19 +507,13 @@ int main(int argc, char *argv[])
|
||||
// initializing the SDL video subsystem to have any effect.
|
||||
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
||||
|
||||
// For SDL backends that support it, use double buffering instead of triple buffering
|
||||
// to save a frame of latency. This doesn't matter for MMAL or DRM renderers since they
|
||||
// are drawing directly to the screen without involving SDL, but it may matter for other
|
||||
// future KMSDRM platforms that use SDL for rendering.
|
||||
SDL_SetHint(SDL_HINT_VIDEO_DOUBLE_BUFFER, "1");
|
||||
|
||||
// We use MMAL to render on Raspberry Pi, so we do not require DRM master.
|
||||
SDL_SetHint("SDL_KMSDRM_REQUIRE_DRM_MASTER", "0");
|
||||
SDL_SetHint(SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER, "0");
|
||||
|
||||
// Use Direct3D 9Ex to avoid a deadlock caused by the D3D device being reset when
|
||||
// the user triggers a UAC prompt. This option controls the software/SDL renderer.
|
||||
// The DXVA2 renderer uses Direct3D 9Ex itself directly.
|
||||
SDL_SetHint("SDL_WINDOWS_USE_D3D9EX", "1");
|
||||
SDL_SetHint(SDL_HINT_WINDOWS_USE_D3D9EX, "1");
|
||||
|
||||
if (SDL_InitSubSystem(SDL_INIT_TIMER) != 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@ -522,28 +548,28 @@ int main(int argc, char *argv[])
|
||||
// SDL 2.0.12 changes the default behavior to use the button label rather than the button
|
||||
// position as most other software does. Set this back to 0 to stay consistent with prior
|
||||
// releases of Moonlight.
|
||||
SDL_SetHint("SDL_GAMECONTROLLER_USE_BUTTON_LABELS", "0");
|
||||
SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
|
||||
|
||||
// Disable relative mouse scaling to renderer size or logical DPI. We want to send
|
||||
// the mouse motion exactly how it was given to us.
|
||||
SDL_SetHint("SDL_MOUSE_RELATIVE_SCALING", "0");
|
||||
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_SCALING, "0");
|
||||
|
||||
// Set our app name for SDL to use with PulseAudio and PipeWire. This matches what we
|
||||
// provide as our app name to libsoundio too. On SDL 2.0.18+, SDL_APP_NAME is also used
|
||||
// for screensaver inhibitor reporting.
|
||||
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "Moonlight");
|
||||
SDL_SetHint("SDL_APP_NAME", "Moonlight");
|
||||
SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, "Moonlight");
|
||||
SDL_SetHint(SDL_HINT_APP_NAME, "Moonlight");
|
||||
|
||||
// We handle capturing the mouse ourselves when it leaves the window, so we don't need
|
||||
// SDL doing it for us behind our backs.
|
||||
SDL_SetHint("SDL_MOUSE_AUTO_CAPTURE", "0");
|
||||
SDL_SetHint(SDL_HINT_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");
|
||||
SDL_SetHint(SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP, "0");
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
// Allow thread naming using exceptions on debug builds. SDL doesn't use SEH
|
||||
@ -562,12 +588,6 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
#endif
|
||||
|
||||
GlobalCommandLineParser parser;
|
||||
GlobalCommandLineParser::ParseResult commandLineParserResult = parser.parse(app.arguments());
|
||||
switch (commandLineParserResult) {
|
||||
case GlobalCommandLineParser::ListRequested:
|
||||
// Don't log to the console since it will jumble the command output
|
||||
s_SuppressVerboseOutput = true;
|
||||
#ifdef Q_OS_WIN32
|
||||
// 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
|
||||
@ -587,6 +607,13 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
GlobalCommandLineParser parser;
|
||||
GlobalCommandLineParser::ParseResult commandLineParserResult = parser.parse(app.arguments());
|
||||
switch (commandLineParserResult) {
|
||||
case GlobalCommandLineParser::ListRequested:
|
||||
// Don't log to the console since it will jumble the command output
|
||||
s_SuppressVerboseOutput = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -775,6 +802,9 @@ int main(int argc, char *argv[])
|
||||
// sometimes freezing and blocking process exit.
|
||||
QThreadPool::globalInstance()->waitForDone(30000);
|
||||
|
||||
// Wait for pending log messages to be printed
|
||||
s_LoggerThread.waitForDone();
|
||||
|
||||
#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).
|
||||
|
@ -15,10 +15,11 @@
|
||||
// redirection that happens when _FILE_OFFSET_BITS=64!
|
||||
// See masterhook_internal.c for details.
|
||||
|
||||
#include <SDL.h>
|
||||
#include "SDL_compat.h"
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <xf86drm.h>
|
||||
@ -36,8 +37,13 @@
|
||||
int g_QtDrmMasterFd = -1;
|
||||
struct stat g_DrmMasterStat;
|
||||
|
||||
// The DRM master FD created for SDL
|
||||
int g_SdlDrmMasterFd = -1;
|
||||
// Last CRTC state for us to restore later
|
||||
drmModeCrtcPtr g_QtCrtcState;
|
||||
uint32_t* g_QtCrtcConnectors;
|
||||
int g_QtCrtcConnectorCount;
|
||||
|
||||
bool removeSdlFd(int fd);
|
||||
int takeMasterFromSdlFd(void);
|
||||
|
||||
int drmIsMaster(int fd)
|
||||
{
|
||||
@ -62,7 +68,41 @@ int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
|
||||
}
|
||||
|
||||
// Call into the real thing
|
||||
return ((typeof(drmModeSetCrtc)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, crtcId, bufferId, x, y, connectors, count, mode);
|
||||
int err = ((typeof(drmModeSetCrtc)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, crtcId, bufferId, x, y, connectors, count, mode);
|
||||
if (err == 0 && fd == g_QtDrmMasterFd) {
|
||||
// Free old CRTC state (if any)
|
||||
if (g_QtCrtcState) {
|
||||
drmModeFreeCrtc(g_QtCrtcState);
|
||||
}
|
||||
if (g_QtCrtcConnectors) {
|
||||
free(g_QtCrtcConnectors);
|
||||
}
|
||||
|
||||
// Store the CRTC configuration so we can restore it later
|
||||
g_QtCrtcState = drmModeGetCrtc(fd, crtcId);
|
||||
g_QtCrtcConnectors = calloc(count, sizeof(*g_QtCrtcConnectors));
|
||||
memcpy(g_QtCrtcConnectors, connectors, count * sizeof(*connectors));
|
||||
g_QtCrtcConnectorCount = count;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
// This hook will temporarily retake DRM master to allow Qt to render while SDL has a DRM FD open
|
||||
int drmModePageFlip(int fd, uint32_t crtc_id, uint32_t fb_id, uint32_t flags, void *user_data)
|
||||
{
|
||||
// Call into the real thing
|
||||
int err = ((typeof(drmModePageFlip)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, crtc_id, fb_id, flags, user_data);
|
||||
if (err == -EACCES && fd == g_QtDrmMasterFd) {
|
||||
// If SDL took master from us, try to grab it back temporarily
|
||||
int oldMasterFd = takeMasterFromSdlFd();
|
||||
drmSetMaster(fd);
|
||||
err = ((typeof(drmModePageFlip)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, crtc_id, fb_id, flags, user_data);
|
||||
drmDropMaster(fd);
|
||||
if (oldMasterFd != -1) {
|
||||
drmSetMaster(oldMasterFd);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
// This hook will handle atomic DRM rendering
|
||||
@ -80,7 +120,18 @@ int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req,
|
||||
}
|
||||
|
||||
// Call into the real thing
|
||||
return ((typeof(drmModeAtomicCommit)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, req, flags, user_data);
|
||||
int err = ((typeof(drmModeAtomicCommit)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, req, flags, user_data);
|
||||
if (err == -EACCES && fd == g_QtDrmMasterFd) {
|
||||
// If SDL took master from us, try to grab it back temporarily
|
||||
int oldMasterFd = takeMasterFromSdlFd();
|
||||
drmSetMaster(fd);
|
||||
err = ((typeof(drmModeAtomicCommit)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, req, flags, user_data);
|
||||
drmDropMaster(fd);
|
||||
if (oldMasterFd != -1) {
|
||||
drmSetMaster(oldMasterFd);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
// This hook will handle SDL's open() on the DRM device. We just need to
|
||||
@ -111,23 +162,39 @@ int open64(const char *pathname, int flags, ...)
|
||||
// after SDL closes its DRM FD.
|
||||
int close(int fd)
|
||||
{
|
||||
// Remove this entry from the SDL FD table
|
||||
bool lastSdlFd = removeSdlFd(fd);
|
||||
|
||||
// Call the real thing
|
||||
int ret = ((typeof(close)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd);
|
||||
if (ret == 0) {
|
||||
// If we just closed the SDL DRM master FD, restore master
|
||||
// to the Qt DRM FD. This works because the Qt DRM master FD
|
||||
// was master once before, so we can set it as master again
|
||||
// using drmSetMaster() without CAP_SYS_ADMIN.
|
||||
if (g_SdlDrmMasterFd != -1 && fd == g_SdlDrmMasterFd) {
|
||||
|
||||
// If we closed the last SDL FD, restore master to the Qt FD
|
||||
if (ret == 0 && lastSdlFd) {
|
||||
if (drmSetMaster(g_QtDrmMasterFd) < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to restore master to Qt DRM FD: %d",
|
||||
errno);
|
||||
}
|
||||
|
||||
g_SdlDrmMasterFd = -1;
|
||||
// Reset the CRTC state to how Qt configured it
|
||||
if (g_QtCrtcState) {
|
||||
int err = ((typeof(drmModeSetCrtc)*)dlsym(RTLD_NEXT, "drmModeSetCrtc"))(g_QtDrmMasterFd,
|
||||
g_QtCrtcState->crtc_id,
|
||||
g_QtCrtcState->buffer_id,
|
||||
g_QtCrtcState->x,
|
||||
g_QtCrtcState->y,
|
||||
g_QtCrtcConnectors,
|
||||
g_QtCrtcConnectorCount,
|
||||
g_QtCrtcState->mode_valid ?
|
||||
&g_QtCrtcState->mode : NULL);
|
||||
if (err < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to restore CRTC state to Qt DRM FD: %d",
|
||||
errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,12 @@
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <SDL.h>
|
||||
#include "SDL_compat.h"
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <xf86drm.h>
|
||||
@ -28,7 +29,73 @@
|
||||
|
||||
extern int g_QtDrmMasterFd;
|
||||
extern struct stat g_DrmMasterStat;
|
||||
extern int g_SdlDrmMasterFd;
|
||||
|
||||
#define MAX_SDL_FD_COUNT 8
|
||||
int g_SdlDrmMasterFds[MAX_SDL_FD_COUNT];
|
||||
int g_SdlDrmMasterFdCount = 0;
|
||||
SDL_SpinLock g_FdTableLock = 0;
|
||||
|
||||
// Caller must hold g_FdTableLock
|
||||
int getSdlFdEntryIndex(bool unused)
|
||||
{
|
||||
for (int i = 0; i < MAX_SDL_FD_COUNT; i++) {
|
||||
// We slightly bend the FD rules here by treating 0
|
||||
// as invalid since that's our global default value.
|
||||
if (unused && g_SdlDrmMasterFds[i] <= 0) {
|
||||
return i;
|
||||
}
|
||||
else if (!unused && g_SdlDrmMasterFds[i] > 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Returns true if the final SDL FD was removed
|
||||
bool removeSdlFd(int fd)
|
||||
{
|
||||
SDL_AtomicLock(&g_FdTableLock);
|
||||
if (g_SdlDrmMasterFdCount != 0) {
|
||||
// Clear the entry for this fd from the table
|
||||
for (int i = 0; i < MAX_SDL_FD_COUNT; i++) {
|
||||
if (fd == g_SdlDrmMasterFds[i]) {
|
||||
g_SdlDrmMasterFds[i] = -1;
|
||||
g_SdlDrmMasterFdCount--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_SdlDrmMasterFdCount == 0) {
|
||||
SDL_AtomicUnlock(&g_FdTableLock);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
SDL_AtomicUnlock(&g_FdTableLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns the previous master FD or -1 if none
|
||||
int takeMasterFromSdlFd()
|
||||
{
|
||||
int fd = -1;
|
||||
|
||||
// Since all SDL FDs are actually dups of each other
|
||||
// we can take master from any one of them.
|
||||
SDL_AtomicLock(&g_FdTableLock);
|
||||
int fdIndex = getSdlFdEntryIndex(false);
|
||||
if (fdIndex != -1) {
|
||||
fd = g_SdlDrmMasterFds[fdIndex];
|
||||
}
|
||||
SDL_AtomicUnlock(&g_FdTableLock);
|
||||
|
||||
if (fd >= 0 && drmDropMaster(fd) == 0) {
|
||||
return fd;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int openHook(const char *funcname, const char *pathname, int flags, va_list va)
|
||||
{
|
||||
@ -55,14 +122,36 @@ int openHook(const char *funcname, const char *pathname, int flags, va_list va)
|
||||
fstat(fd, &fdstat);
|
||||
if (g_DrmMasterStat.st_dev == fdstat.st_dev &&
|
||||
g_DrmMasterStat.st_ino == fdstat.st_ino) {
|
||||
int freeFdIndex;
|
||||
int allocatedFdIndex;
|
||||
|
||||
// It is our device. Time to do the magic!
|
||||
SDL_AtomicLock(&g_FdTableLock);
|
||||
|
||||
// This code assumes SDL only ever opens a single FD
|
||||
// for a given DRM device.
|
||||
SDL_assert(g_SdlDrmMasterFd == -1);
|
||||
// Get a free index for us to put the new entry
|
||||
freeFdIndex = getSdlFdEntryIndex(true);
|
||||
if (freeFdIndex < 0) {
|
||||
SDL_AtomicUnlock(&g_FdTableLock);
|
||||
SDL_assert(freeFdIndex >= 0);
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No unused SDL FD table entries!");
|
||||
// Hope for the best
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Check if we have an allocated entry already
|
||||
allocatedFdIndex = getSdlFdEntryIndex(false);
|
||||
if (allocatedFdIndex >= 0) {
|
||||
// Close fd that we opened earlier (skipping our close() hook)
|
||||
((typeof(close)*)dlsym(RTLD_NEXT, "close"))(fd);
|
||||
|
||||
// dup() an existing FD into the unused slot
|
||||
fd = dup(g_SdlDrmMasterFds[allocatedFdIndex]);
|
||||
}
|
||||
else {
|
||||
// Drop master on Qt's FD so we can pick it up for SDL.
|
||||
if (drmDropMaster(g_QtDrmMasterFd) < 0) {
|
||||
SDL_AtomicUnlock(&g_FdTableLock);
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to drop master on Qt DRM FD: %d",
|
||||
errno);
|
||||
@ -70,17 +159,30 @@ int openHook(const char *funcname, const char *pathname, int flags, va_list va)
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Close fd that we opened earlier (skipping our close() hook)
|
||||
((typeof(close)*)dlsym(RTLD_NEXT, "close"))(fd);
|
||||
|
||||
// We are not allowed to call drmSetMaster() without CAP_SYS_ADMIN,
|
||||
// but since we just dropped the master, we can become master by
|
||||
// simply creating a new FD. Let's do it.
|
||||
close(fd);
|
||||
if (__OPEN_NEEDS_MODE(flags)) {
|
||||
fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags, mode);
|
||||
}
|
||||
else {
|
||||
fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags);
|
||||
}
|
||||
g_SdlDrmMasterFd = fd;
|
||||
}
|
||||
|
||||
if (fd >= 0) {
|
||||
// Start with DRM master on the new FD
|
||||
drmSetMaster(fd);
|
||||
|
||||
// Insert the FD into the table
|
||||
g_SdlDrmMasterFds[freeFdIndex] = fd;
|
||||
g_SdlDrmMasterFdCount++;
|
||||
}
|
||||
|
||||
SDL_AtomicUnlock(&g_FdTableLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,16 @@
|
||||
<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>
|
||||
<file>languages/qml_et.ts</file>
|
||||
<file>languages/qml_et.qm</file>
|
||||
<file>languages/qml_bg.ts</file>
|
||||
<file>languages/qml_bg.qm</file>
|
||||
<file>languages/qml_eo.ts</file>
|
||||
<file>languages/qml_eo.qm</file>
|
||||
<file>languages/qml_ta.ts</file>
|
||||
<file>languages/qml_ta.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-->
|
||||
@ -84,6 +94,8 @@
|
||||
<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="d3d11_ayuv_pixel.fxc">shaders/d3d11_ayuv_pixel.fxc</file>
|
||||
<file alias="d3d11_y410_pixel.fxc">shaders/d3d11_y410_pixel.fxc</file>
|
||||
<file alias="vt_renderer.metal">shaders/vt_renderer.metal</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include <QDir>
|
||||
|
||||
#include <SDL.h>
|
||||
#include "SDL_compat.h"
|
||||
|
||||
#define SER_GAMEPADMAPPING "gcmapping"
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
#define SER_HEIGHT "height"
|
||||
#define SER_FPS "fps"
|
||||
#define SER_BITRATE "bitrate"
|
||||
#define SER_UNLOCK_BITRATE "unlockbitrate"
|
||||
#define SER_AUTOADJUSTBITRATE "autoadjustbitrate"
|
||||
#define SER_FULLSCREEN "fullscreen"
|
||||
#define SER_VSYNC "vsync"
|
||||
#define SER_GAMEOPTS "gameopts"
|
||||
@ -23,6 +25,7 @@
|
||||
#define SER_AUDIOCFG "audiocfg"
|
||||
#define SER_VIDEOCFG "videocfg"
|
||||
#define SER_HDR "hdr"
|
||||
#define SER_YUV444 "yuv444"
|
||||
#define SER_VIDEODEC "videodec"
|
||||
#define SER_WINDOWMODE "windowmode"
|
||||
#define SER_MDNS "mdns"
|
||||
@ -117,7 +120,10 @@ void StreamingPreferences::reload()
|
||||
width = settings.value(SER_WIDTH, 1280).toInt();
|
||||
height = settings.value(SER_HEIGHT, 720).toInt();
|
||||
fps = settings.value(SER_FPS, 60).toInt();
|
||||
bitrateKbps = settings.value(SER_BITRATE, getDefaultBitrate(width, height, fps)).toInt();
|
||||
enableYUV444 = settings.value(SER_YUV444, false).toBool();
|
||||
bitrateKbps = settings.value(SER_BITRATE, getDefaultBitrate(width, height, fps, enableYUV444)).toInt();
|
||||
unlockBitrate = settings.value(SER_UNLOCK_BITRATE, false).toBool();
|
||||
autoAdjustBitrate = settings.value(SER_AUTOADJUSTBITRATE, true).toBool();
|
||||
enableVsync = settings.value(SER_VSYNC, true).toBool();
|
||||
gameOptimizations = settings.value(SER_GAMEOPTS, true).toBool();
|
||||
playAudioOnHost = settings.value(SER_HOSTAUDIO, false).toBool();
|
||||
@ -289,6 +295,16 @@ QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Langua
|
||||
return "he";
|
||||
case LANG_CKB:
|
||||
return "ckb";
|
||||
case LANG_LT:
|
||||
return "lt";
|
||||
case LANG_ET:
|
||||
return "et";
|
||||
case LANG_BG:
|
||||
return "bg";
|
||||
case LANG_EO:
|
||||
return "eo";
|
||||
case LANG_TA:
|
||||
return "ta";
|
||||
case LANG_AUTO:
|
||||
default:
|
||||
return QLocale::system().name();
|
||||
@ -303,6 +319,8 @@ void StreamingPreferences::save()
|
||||
settings.setValue(SER_HEIGHT, height);
|
||||
settings.setValue(SER_FPS, fps);
|
||||
settings.setValue(SER_BITRATE, bitrateKbps);
|
||||
settings.setValue(SER_UNLOCK_BITRATE, unlockBitrate);
|
||||
settings.setValue(SER_AUTOADJUSTBITRATE, autoAdjustBitrate);
|
||||
settings.setValue(SER_VSYNC, enableVsync);
|
||||
settings.setValue(SER_GAMEOPTS, gameOptimizations);
|
||||
settings.setValue(SER_HOSTAUDIO, playAudioOnHost);
|
||||
@ -320,6 +338,7 @@ void StreamingPreferences::save()
|
||||
settings.setValue(SER_SHOWPERFOVERLAY, showPerformanceOverlay);
|
||||
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
||||
settings.setValue(SER_HDR, enableHdr);
|
||||
settings.setValue(SER_YUV444, enableYUV444);
|
||||
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
|
||||
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
|
||||
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
|
||||
@ -335,7 +354,7 @@ void StreamingPreferences::save()
|
||||
settings.setValue(SER_KEEPAWAKE, keepAwake);
|
||||
}
|
||||
|
||||
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
||||
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps, bool yuv444)
|
||||
{
|
||||
// Don't scale bitrate linearly beyond 60 FPS. It's definitely not a linear
|
||||
// bitrate increase for frame rate once we get to values that high.
|
||||
@ -383,5 +402,10 @@ int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
||||
}
|
||||
}
|
||||
|
||||
if (yuv444) {
|
||||
// This is rough estimation based on the fact that 4:4:4 doubles the amount of raw YUV data compared to 4:2:0
|
||||
resolutionFactor *= 2;
|
||||
}
|
||||
|
||||
return qRound(resolutionFactor * frameRateFactor) * 1000;
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ public:
|
||||
static StreamingPreferences* get(QQmlEngine *qmlEngine = nullptr);
|
||||
|
||||
Q_INVOKABLE static int
|
||||
getDefaultBitrate(int width, int height, int fps);
|
||||
getDefaultBitrate(int width, int height, int fps, bool yuv444);
|
||||
|
||||
Q_INVOKABLE void save();
|
||||
|
||||
@ -92,6 +92,11 @@ public:
|
||||
LANG_CS,
|
||||
LANG_HE,
|
||||
LANG_CKB,
|
||||
LANG_LT,
|
||||
LANG_ET,
|
||||
LANG_BG,
|
||||
LANG_EO,
|
||||
LANG_TA,
|
||||
};
|
||||
Q_ENUM(Language);
|
||||
|
||||
@ -107,6 +112,8 @@ public:
|
||||
Q_PROPERTY(int height MEMBER height NOTIFY displayModeChanged)
|
||||
Q_PROPERTY(int fps MEMBER fps NOTIFY displayModeChanged)
|
||||
Q_PROPERTY(int bitrateKbps MEMBER bitrateKbps NOTIFY bitrateChanged)
|
||||
Q_PROPERTY(bool unlockBitrate MEMBER unlockBitrate NOTIFY unlockBitrateChanged)
|
||||
Q_PROPERTY(bool autoAdjustBitrate MEMBER autoAdjustBitrate NOTIFY autoAdjustBitrateChanged)
|
||||
Q_PROPERTY(bool enableVsync MEMBER enableVsync NOTIFY enableVsyncChanged)
|
||||
Q_PROPERTY(bool gameOptimizations MEMBER gameOptimizations NOTIFY gameOptimizationsChanged)
|
||||
Q_PROPERTY(bool playAudioOnHost MEMBER playAudioOnHost NOTIFY playAudioOnHostChanged)
|
||||
@ -124,6 +131,7 @@ public:
|
||||
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
||||
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
|
||||
Q_PROPERTY(bool enableHdr MEMBER enableHdr NOTIFY enableHdrChanged)
|
||||
Q_PROPERTY(bool enableYUV444 MEMBER enableYUV444 NOTIFY enableYUV444Changed)
|
||||
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
|
||||
Q_PROPERTY(WindowMode windowMode MEMBER windowMode NOTIFY windowModeChanged)
|
||||
Q_PROPERTY(WindowMode recommendedFullScreenMode MEMBER recommendedFullScreenMode CONSTANT)
|
||||
@ -144,6 +152,8 @@ public:
|
||||
int height;
|
||||
int fps;
|
||||
int bitrateKbps;
|
||||
bool unlockBitrate;
|
||||
bool autoAdjustBitrate;
|
||||
bool enableVsync;
|
||||
bool gameOptimizations;
|
||||
bool playAudioOnHost;
|
||||
@ -168,6 +178,7 @@ public:
|
||||
AudioConfig audioConfig;
|
||||
VideoCodecConfig videoCodecConfig;
|
||||
bool enableHdr;
|
||||
bool enableYUV444;
|
||||
VideoDecoderSelection videoDecoderSelection;
|
||||
WindowMode windowMode;
|
||||
WindowMode recommendedFullScreenMode;
|
||||
@ -178,6 +189,8 @@ public:
|
||||
signals:
|
||||
void displayModeChanged();
|
||||
void bitrateChanged();
|
||||
void unlockBitrateChanged();
|
||||
void autoAdjustBitrateChanged();
|
||||
void enableVsyncChanged();
|
||||
void gameOptimizationsChanged();
|
||||
void playAudioOnHostChanged();
|
||||
@ -190,6 +203,7 @@ signals:
|
||||
void audioConfigChanged();
|
||||
void videoCodecConfigChanged();
|
||||
void enableHdrChanged();
|
||||
void enableYUV444Changed();
|
||||
void videoDecoderSelectionChanged();
|
||||
void uiDisplayModeChanged();
|
||||
void windowModeChanged();
|
||||
|
@ -4,3 +4,5 @@ fxc /T ps_4_0_level_9_3 /Fo d3d11_overlay_pixel.fxc d3d11_overlay_pixel.hlsl
|
||||
fxc /T ps_4_0_level_9_3 /Fo d3d11_genyuv_pixel.fxc d3d11_genyuv_pixel.hlsl
|
||||
fxc /T ps_4_0_level_9_3 /Fo d3d11_bt601lim_pixel.fxc d3d11_bt601lim_pixel.hlsl
|
||||
fxc /T ps_4_0_level_9_3 /Fo d3d11_bt2020lim_pixel.fxc d3d11_bt2020lim_pixel.hlsl
|
||||
fxc /T ps_4_0_level_9_3 /Fo d3d11_ayuv_pixel.fxc d3d11_ayuv_pixel.hlsl
|
||||
fxc /T ps_4_0_level_9_3 /Fo d3d11_y410_pixel.fxc d3d11_y410_pixel.hlsl
|
BIN
app/shaders/d3d11_ayuv_pixel.fxc
Normal file
BIN
app/shaders/d3d11_ayuv_pixel.fxc
Normal file
Binary file not shown.
9
app/shaders/d3d11_ayuv_pixel.hlsl
Normal file
9
app/shaders/d3d11_ayuv_pixel.hlsl
Normal file
@ -0,0 +1,9 @@
|
||||
#include "d3d11_yuv444_pixel_start.hlsli"
|
||||
|
||||
min16float3 swizzle(min16float3 input)
|
||||
{
|
||||
// AYUV SRVs are in VUYA order
|
||||
return input.bgr;
|
||||
}
|
||||
|
||||
#include "d3d11_yuv444_pixel_end.hlsli"
|
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;
|
||||
|
@ -7,3 +7,8 @@ struct ShaderInput
|
||||
float4 pos : SV_POSITION;
|
||||
float2 tex : TEXCOORD0;
|
||||
};
|
||||
|
||||
cbuffer ChromaLimitBuf : register(b0)
|
||||
{
|
||||
min16float3 chromaTexMax;
|
||||
};
|
||||
|
BIN
app/shaders/d3d11_y410_pixel.fxc
Normal file
BIN
app/shaders/d3d11_y410_pixel.fxc
Normal file
Binary file not shown.
9
app/shaders/d3d11_y410_pixel.hlsl
Normal file
9
app/shaders/d3d11_y410_pixel.hlsl
Normal file
@ -0,0 +1,9 @@
|
||||
#include "d3d11_yuv444_pixel_start.hlsli"
|
||||
|
||||
min16float3 swizzle(min16float3 input)
|
||||
{
|
||||
// Y410 SRVs are in UYVA order
|
||||
return input.grb;
|
||||
}
|
||||
|
||||
#include "d3d11_yuv444_pixel_end.hlsli"
|
13
app/shaders/d3d11_yuv444_pixel_end.hlsli
Normal file
13
app/shaders/d3d11_yuv444_pixel_end.hlsli
Normal file
@ -0,0 +1,13 @@
|
||||
min16float4 main(ShaderInput input) : SV_TARGET
|
||||
{
|
||||
// Clamp the texcoords to avoid sampling the row of texels adjacent to the alignment padding
|
||||
min16float3 yuv = swizzle(videoTex.Sample(theSampler, min(input.tex, chromaTexMax.rg)));
|
||||
|
||||
// Subtract the YUV offset for limited vs full range
|
||||
yuv -= offsets;
|
||||
|
||||
// Multiply by the conversion matrix for this colorspace
|
||||
yuv = mul(yuv, cscMatrix);
|
||||
|
||||
return min16float4(yuv, 1.0);
|
||||
}
|
19
app/shaders/d3d11_yuv444_pixel_start.hlsli
Normal file
19
app/shaders/d3d11_yuv444_pixel_start.hlsli
Normal file
@ -0,0 +1,19 @@
|
||||
Texture2D<min16float3> videoTex : register(t0);
|
||||
SamplerState theSampler : register(s0);
|
||||
|
||||
struct ShaderInput
|
||||
{
|
||||
float4 pos : SV_POSITION;
|
||||
float2 tex : TEXCOORD0;
|
||||
};
|
||||
|
||||
cbuffer ChromaLimitBuf : register(b0)
|
||||
{
|
||||
min16float3 chromaTexMax;
|
||||
};
|
||||
|
||||
cbuffer CSC_CONST_BUF : register(b1)
|
||||
{
|
||||
min16float3x3 cscMatrix;
|
||||
min16float3 offsets;
|
||||
};
|
@ -10,6 +10,7 @@ struct CscParams
|
||||
{
|
||||
float3 matrix[3];
|
||||
float3 offsets;
|
||||
float bitnessScaleFactor;
|
||||
};
|
||||
|
||||
constexpr sampler s(coord::normalized, address::clamp_to_edge, filter::linear);
|
||||
@ -24,7 +25,28 @@ fragment float4 ps_draw_biplanar(Vertex v [[ stage_in ]],
|
||||
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);
|
||||
float3 yuv = float3(luminancePlane.sample(s, v.texCoords).r,
|
||||
chrominancePlane.sample(s, v.texCoords).rg);
|
||||
yuv *= cscParams.bitnessScaleFactor;
|
||||
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_triplanar(Vertex v [[ stage_in ]],
|
||||
constant CscParams &cscParams [[ buffer(0) ]],
|
||||
texture2d<float> luminancePlane [[ texture(0) ]],
|
||||
texture2d<float> chrominancePlaneU [[ texture(1) ]],
|
||||
texture2d<float> chrominancePlaneV [[ texture(2) ]])
|
||||
{
|
||||
float3 yuv = float3(luminancePlane.sample(s, v.texCoords).r,
|
||||
chrominancePlaneU.sample(s, v.texCoords).r,
|
||||
chrominancePlaneV.sample(s, v.texCoords).r);
|
||||
yuv *= cscParams.bitnessScaleFactor;
|
||||
yuv -= cscParams.offsets;
|
||||
|
||||
float3 rgb;
|
||||
|
@ -205,29 +205,41 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
}
|
||||
|
||||
if (s_ActiveSession->m_AudioRenderer != nullptr) {
|
||||
int desiredSize = sizeof(short) * s_ActiveSession->m_ActiveAudioConfig.samplesPerFrame * s_ActiveSession->m_ActiveAudioConfig.channelCount;
|
||||
void* buffer = s_ActiveSession->m_AudioRenderer->getAudioBuffer(&desiredSize);
|
||||
int sampleSize = s_ActiveSession->m_AudioRenderer->getAudioBufferSampleSize();
|
||||
int frameSize = sampleSize * s_ActiveSession->m_ActiveAudioConfig.channelCount;
|
||||
int desiredBufferSize = frameSize * s_ActiveSession->m_ActiveAudioConfig.samplesPerFrame;
|
||||
void* buffer = s_ActiveSession->m_AudioRenderer->getAudioBuffer(&desiredBufferSize);
|
||||
if (buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_ActiveSession->m_AudioRenderer->getAudioBufferFormat() == IAudioRenderer::AudioFormat::Float32NE) {
|
||||
samplesDecoded = opus_multistream_decode_float(s_ActiveSession->m_OpusDecoder,
|
||||
(unsigned char*)sampleData,
|
||||
sampleLength,
|
||||
(float*)buffer,
|
||||
desiredBufferSize / frameSize,
|
||||
0);
|
||||
}
|
||||
else {
|
||||
samplesDecoded = opus_multistream_decode(s_ActiveSession->m_OpusDecoder,
|
||||
(unsigned char*)sampleData,
|
||||
sampleLength,
|
||||
(short*)buffer,
|
||||
desiredSize / sizeof(short) / s_ActiveSession->m_ActiveAudioConfig.channelCount,
|
||||
desiredBufferSize / frameSize,
|
||||
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_ActiveAudioConfig.channelCount));
|
||||
desiredSize = sizeof(short) * samplesDecoded * s_ActiveSession->m_ActiveAudioConfig.channelCount;
|
||||
SDL_assert(desiredBufferSize >= frameSize * samplesDecoded);
|
||||
desiredBufferSize = frameSize * samplesDecoded;
|
||||
}
|
||||
else {
|
||||
desiredSize = 0;
|
||||
desiredBufferSize = 0;
|
||||
}
|
||||
|
||||
if (!s_ActiveSession->m_AudioRenderer->submitAudio(desiredSize)) {
|
||||
if (!s_ActiveSession->m_AudioRenderer->submitAudio(desiredBufferSize)) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Reinitializing audio renderer after failure");
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Limelight.h>
|
||||
#include <QtGlobal>
|
||||
|
||||
class IAudioRenderer
|
||||
{
|
||||
@ -25,4 +26,21 @@ public:
|
||||
// 4 - Surround Left
|
||||
// 5 - Surround Right
|
||||
}
|
||||
|
||||
enum class AudioFormat {
|
||||
Sint16NE, // 16-bit signed integer (native endian)
|
||||
Float32NE, // 32-bit floating point (native endian)
|
||||
};
|
||||
virtual AudioFormat getAudioBufferFormat() = 0;
|
||||
|
||||
int getAudioBufferSampleSize() {
|
||||
switch (getAudioBufferFormat()) {
|
||||
case IAudioRenderer::AudioFormat::Sint16NE:
|
||||
return sizeof(short);
|
||||
case IAudioRenderer::AudioFormat::Float32NE:
|
||||
return sizeof(float);
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user