Compare commits

...
465 Commits
Author SHA1 Message Date
Cameron Gutman 413993ab6f Bump release date in appstream 2023-10-18 00:45:10 -05:00
Cameron Gutman 948b9c818a Add workarounds for some SDL joystick index issues 2023-10-18 00:42:59 -05:00
Cameron Gutman cf615ea88d Update SDL2 to fix HIDAPI joystick crash 2023-10-18 00:41:53 -05:00
Cameron Gutman 2d1e16fe50 Update moonlight-common-c 2023-10-16 22:42:46 -05:00
Cameron Gutman 089c3d3ad3 Prepare for v5.0.0 2023-10-16 19:04:40 -05:00
Cameron Gutman 63e3fa6609 Disable ATS 2023-10-16 18:13:13 -05:00
Cameron Gutman 8a7fa7e559 Update moonlight-common-c 2023-10-16 18:06:45 -05:00
Cameron Gutman 989a90a0f7 Rerun lupdate and lrelease 2023-10-15 20:30:46 -05:00
Cameron Gutman a707720577 Merge remote-tracking branch 'origin/weblate' 2023-10-15 20:28:04 -05:00
Cameron Gutman eef270de26 Fix HDR with external displays on macOS Sonoma
Fixes #1079
2023-10-15 15:30:41 -05:00
Cameron Gutman 0ac765bc32 Revert SDL audio buffer size on macOS to v4.3.1 value
Fixes #1071
2023-10-15 13:17:00 -05:00
Cameron Gutman e76551cb64 Use the PC name alone as the streaming window title on macOS 2023-10-15 13:15:38 -05:00
Cameron Gutman 4fd902a0d3 Update moonlight-common-c 2023-10-15 13:04:31 -05:00
Cameron Gutman 9cba449092 Add config option to allow EGL usage when built with MMAL 2023-10-15 13:03:47 -05:00
Cameron Gutman 905b2ecf9e Change AV1 bitrate multiplier to match HEVC 2023-10-15 12:51:23 -05:00
Cameron Gutman 53c2c612c9 Add Share+PS combo for clickpad button emulation on PS4/5 controllers 2023-10-14 01:46:50 -05:00
Cameron Gutman 8ffadde9cc Update SDL2 in AppImage for PulseAudio fix and gamepad sensor support 2023-10-14 00:39:42 -05:00
Jorys Paulin f569bf633c Translated using Weblate (French)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2023-10-13 12:01:28 +02:00
Cameron Gutman 6eb5e07007 Fix warnings in 32-bit builds 2023-10-12 19:05:52 -05:00
Cameron Gutman f07201baa9 Update moonlight-common-c 2023-10-12 18:46:35 -05:00
Cameron Gutman 9120fd01a8 Adjust MMAL and RPi workarounds for Pi 5 and Bookworm support 2023-10-12 18:46:11 -05:00
Cameron Gutman c14cc70a6a Also check for V3D compatible IDs 2023-10-08 16:52:54 -05:00
Cameron Gutman a66f91ae3c Update moonlight-common-c 2023-10-08 16:29:59 -05:00
Cameron Gutman 7e4fddbe62 Only block EGL rendering on Pi 4 and earlier
Hopefully Pi 5 is powerful enough to at least render 1080p 60 FPS.
2023-10-08 16:16:33 -05:00
Cameron Gutman 0ff9f8c55a Update dav1d to 1.3.0 2023-10-06 00:26:44 -05:00
Cameron Gutman 6f710faa8e Fix EGL loading in the AppImage 2023-10-05 23:05:07 -05:00
Cameron Gutman 2127e1d90b Allow h264_v4l2m2m to be used in RPi builds if manually specified 2023-10-05 18:37:14 -05:00
Cameron Gutman 9d73aa6b4a Fix DrmRenderer not falling back to swframes if DRM PRIME is available 2023-10-05 18:13:52 -05:00
Cameron Gutman a2f23b7053 Fix automatic VA DRM driver selection with libva 2.20 2023-10-05 18:12:12 -05:00
Cameron Gutman 5974980aff Use BT.709 in DrmRenderer only on Starfive devices 2023-10-05 18:10:36 -05:00
Cameron Gutman 6fa53691aa Fix implicit default constructor warning 2023-10-02 17:42:35 -05:00
Cameron Gutman 49e0679864 Synchronize with possible concurrent writers to the NvComputer object 2023-10-01 17:46:27 -05:00
Cameron Gutman 0976bc39b3 Only reserialize hosts if a serializable attribute changed 2023-10-01 17:28:06 -05:00
Cameron Gutman facd6e4e56 Revert "Batch delayed flushes to improve performance on macOS"
This reverts commit aa74e6930b.
2023-10-01 16:33:14 -05:00
Cameron Gutman d2dc0aa1b1 Don't serialize apps for the host backup
This takes a ton of time on macOS and most of the data is just a local cache anyway
2023-10-01 16:13:22 -05:00
Cameron Gutman aa74e6930b Batch delayed flushes to improve performance on macOS 2023-10-01 15:26:28 -05:00
Jorys Paulin b7811bf196 Translated using Weblate (French)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2023-10-01 10:59:44 +00:00
Cameron Gutman 18130fd8de Use BT.601 for planes that lack COLOR_ENCODING properties 2023-09-29 18:30:39 -05:00
Cameron Gutman b6d4f97e96 Check that the chosen plane supports the decoded frame pixel format 2023-09-29 17:22:49 -05:00
Cameron Gutman 0dd0112518 Don't export EGLImages for software frames 2023-09-29 17:20:50 -05:00
Cameron Gutman 5711e6064e Allow reuse of an active plane for devices with only one usable plane per CRTC 2023-09-29 17:19:44 -05:00
Cameron Gutman 517b684f85 Handle EGL driver returning no supported formats or modifiers for DMA-BUF import 2023-09-27 01:31:01 -05:00
Cameron Gutman 39465f6d8a Override some Material 3 colors to improve contrast 2023-09-27 00:51:51 -05:00
Cameron Gutman 738813cbac Add fallback definition for EGLuint64KHR 2023-09-26 23:22:25 -05:00
Cameron Gutman c2778e040a Build fix for old EGL headers 2023-09-26 23:16:22 -05:00
Cameron Gutman 1efdeeb9dc Export composed VA surface layers for EGL import if supported
Importing composed formats is more efficient and performant because it allows
tiled and/or compressed surfaces to be directly sampled by shaders without
requiring a conversion to a linear planar format first.
2023-09-26 22:58:13 -05:00
Cameron Gutman b01dfea343 Refactor EGLImage creation into a separate shared class 2023-09-26 21:03:19 -05:00
Cameron Gutman e143293e91 Update our plist to activate Game Mode on Sonoma 2023-09-26 18:28:14 -05:00
Cameron Gutman f420dc740a Set EGL_IMAGE_PRESERVED_KHR=EGL_TRUE on our EGLImages
They're mapped read-only and may be saved internally as reference frames,
so they must not be modified by the EGL implementation.
2023-09-26 02:38:46 -05:00
Jen Kung-chih 2e532224b7 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2023-09-24 19:59:50 +00:00
Jorys Paulin cb8366a7e5 Translated using Weblate (French)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2023-09-24 19:59:49 +00:00
Cameron Gutman fee54a9d76 Update moonlight-common-c to pick up various fixes 2023-09-23 13:33:54 -04:00
Cameron Gutman dc556d3c9b Try to fix Windows build 2023-09-23 13:33:21 -04:00
Cameron Gutman 4fed3802aa Fix signed vs unsigned comparison warning 2023-09-23 11:15:41 -04:00
Cameron Gutman f7bb4611e9 Replace uses of strcpy() and sprintf() 2023-09-23 11:15:21 -04:00
Artur Szulc 57791eec62 Translated using Weblate (Polish)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/pl/
2023-09-22 20:59:07 +00:00
Cameron Gutman e1e3d8e710 Update AppImage to libva 2.20.0 2023-09-20 19:29:43 -05:00
Cameron Gutman 654be492a0 Avoid using LFS64 interfaces with Musl 2023-09-18 20:07:56 -05:00
Cameron Gutman 1df0da4d4a Also use test frame decoding parameters when creating a test renderer 2023-09-14 18:21:53 -05:00
Cameron Gutman 1dd6cdb567 Use the correct width and height of the test frame when performing the test decode
Using the stream resolution instead of the test frame resolution was causing all non-720p streams to fail on the VisionFive 2 board with the wave5 driver.

It also likely fixes a similar interoperability issue with the qcom-venus driver that was reported in #1042
2023-09-14 18:16:45 -05:00
Cameron Gutman edfb3cfdc8 Update OpenSSL to 1.1.1w for Windows and Mac builds 2023-09-12 19:36:48 -05:00
Cameron Gutman c6a8f44640 Don't set currentIndex to -1 while reinitializing
It causes warnings in other code that have property bindings to currentIndex
2023-09-12 19:28:33 -05:00
Cameron Gutman b7301d4415 Rerun lupdate and lrelease 2023-09-12 19:19:31 -05:00
Cameron Gutman 4d668702b5 Merge remote-tracking branch 'origin/weblate' 2023-09-12 19:18:14 -05:00
Cameron Gutman f4b0f4d78f Functionally revert f60e7241
Switching to indirect rendering has a high risk of performance and functionality regressions,
so let's just continue using AVSampleBufferDisplayLayer until we have a Metal renderer
since #973 is fixed.

This isn't an actual revert because we want to still keep the indirect override around.
2023-09-12 18:36:53 -05:00
Cameron Gutman 7100262e09 Update build requirements 2023-09-12 18:26:03 -05:00
Cameron Gutman e516943d53 Guard SDL_ControllerSensorEvent usage by SDL version check 2023-09-12 18:10:06 -05:00
Cameron Gutman fd41226cdd Support fully-planar output in DrmRenderer 2023-09-12 18:08:48 -05:00
Cameron Gutman 47b04eee3e Remove use of deprecated QByteArray::count() 2023-09-12 18:07:44 -05:00
Cameron Gutman 24ee6f96e1 Don't filter SOPS option by active resolutions with Sunshine 2023-09-11 21:25:29 -05:00
Cameron Gutman 6e34c21175 Fix signed integer overflow found by UBSan 2023-09-11 21:23:39 -05:00
Cameron Gutman 64789ea8bc Destroy the VTRenderer before leaving fullscreen
Fixes #973
2023-09-11 21:07:54 -05:00
Cameron Gutman 2ac7176787 Fix a bunch of memory leaks in the VTRenderer
It appears the leaked AVSampleBufferDisplayLayer may be causing the hang in #973
2023-09-11 20:49:35 -05:00
Cameron Gutman b968886594 Round default bitrates to the nearest megabit 2023-09-11 01:18:52 -05:00
Cameron Gutman 2f9c44103b Automatically scroll the SettingsView to ensure the focused item is visible
Also removed old manual scrolling code from SdlGamepadKeyNavigation as it was broken on Qt 6 anyway.
2023-09-11 00:08:20 -05:00
Cameron Gutman e9ed4940cd Interpolate between entries in our default bitrate table 2023-09-10 22:34:20 -05:00
Cameron Gutman 85b5845ebb Switch to Qt 6.5 for Windows ARM64 build 2023-09-10 18:43:51 -05:00
Cameron Gutman 6f4db913aa Update jom and vswhere
jom -> 1.1.4
vswhere -> 3.1.7
2023-09-10 16:55:19 -05:00
Cameron Gutman 2d20c711a6 Move Qt ARM64 patching into the install section 2023-09-10 16:50:41 -05:00
Cameron Gutman 2e829bc85f Fix corecrt_math_defines.h macro redefinition warnings with MSVC Qt 6 builds 2023-09-10 16:48:24 -05:00
Cameron Gutman acddc3cabd Fix ARM64 builds in AppVeyor
We have to replace some files that were built with incorrect paths
2023-09-10 16:32:42 -05:00
Cameron Gutman a16647f16c Always include ARM64 in the installer bundle 2023-09-10 15:16:02 -05:00
Cameron Gutman 3e3f384218 Fix indentation after WiX v4 autoconversion 2023-09-10 15:16:02 -05:00
Cameron Gutman 45a98a391a Produce Windows ARM64 binaries in CI 2023-09-10 15:15:53 -05:00
Cameron Gutman c9992201ce Support building with Qt 6's official ARM64 SDK binaries 2023-09-10 15:06:42 -05:00
Cameron Gutman 80062d262e Remove manual qmlcachegen invocation for internal Qt QML files
It's not safe to always assume we can always precompile everything.
Instead set QML_CACHE_DIR to ensure the files go to our desired location.
2023-09-10 15:03:53 -05:00
Zan 1456 652f7a7710 Translated using Weblate (Hungarian)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/hu/
2023-09-09 21:55:43 +02:00
gallegonovato 7e7e50ada2 Translated using Weblate (Spanish)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2023-09-09 21:55:42 +02:00
Cameron Gutman 87eba9bb89 Fix build on Qt 5.13 and earlier 2023-09-09 14:22:47 -05:00
Cameron Gutman da1fcd49fc Increase audio buffer size to reduce the chance of underruns 2023-09-09 12:56:05 -05:00
Cameron Gutman c98289a9e7 Use DMA_BUF_IOCTL_SYNC to synchronize swframe copies 2023-09-09 12:19:40 -05:00
Cameron Gutman 1d0b65f798 Log the active SDL audio driver 2023-09-09 11:46:06 -05:00
Cameron Gutman 3094185cd6 Also skip SDL_RENDERER_PRESENTVSYNC on Wayland 2023-09-09 10:41:33 -05:00
Cameron Gutman aaff04167b Only skip SDL_RENDERER_PRESENTVSYNC on Windows
macOS can still tear when using the Metal renderer backend.
2023-09-09 08:00:13 -05:00
Cameron Gutman 21bba02e6f Allow render thread usage with Metal 2023-09-09 07:45:42 -05:00
Cameron Gutman f60e724167 Default to indirect rendering on Apple Silicon
Hopefully the UMA makes the performance impact of reimporting frames negligible
until we can get a proper Metal renderer to replace AVSampleBufferDisplayLayer.
2023-09-09 07:44:11 -05:00
Cameron Gutman 7551e90899 Adjust AV1 surface alignment to match FFmpeg behavior 2023-09-08 23:13:29 -05:00
Cameron Gutman 715429c13c Print error dialogs and warning toasts to the log 2023-09-08 23:00:00 -05:00
Cameron Gutman 7976189d56 Bump macOS nightlies to Qt 6.4 2023-09-08 19:54:58 -05:00
Cameron Gutman 47d1eff45d Don't rasterize layers in fullscreen
The original artifacting bug didn't manifest in fullscreen anyway.
2023-09-08 19:53:01 -05:00
bittin1ddc447d824349b2 073df32b01 Translated using Weblate (Swedish)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/sv/
2023-09-08 11:59:30 +02:00
Zan 1456 e8ec7efa5c Translated using Weblate (Hungarian)
Currently translated at 33.4% (72 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/hu/
2023-09-08 11:59:29 +02:00
sanhoe f08cdae2b4 Translated using Weblate (Korean)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2023-09-08 11:59:29 +02:00
weng weng 1389d29d8c Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2023-09-08 11:59:28 +02:00
Jorys Paulin d4bfde7906 Translated using Weblate (French)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2023-09-08 11:59:28 +02:00
reloxx13 fe73c36958 Translated using Weblate (German)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2023-09-08 11:59:28 +02:00
Cameron Gutman a97f6d90f4 Fix Linux build break 2023-09-08 02:04:15 -05:00
Cameron Gutman 0735f164a8 Revert back to model reset for updating the computer list
It's possible that multiple changes occur in parallel, so we can't rely on receiving them one at a time
even if the callbacks are synchronized. Handling this substantially complicates the logic such that
it's not really worth doing individual insertions and deletions anymore.

This reverts 94d821a4a9 and replaces it with a solution
that is basically the old code except that it properly handles changes to the structure of the list.
2023-09-08 02:02:21 -05:00
Cameron Gutman 9de0c215ba Don't allow custom frame rates below 10 FPS 2023-09-08 00:59:56 -05:00
Cameron Gutman 56474822f7 Don't scale bitrate linearly with frame rate beyond 60 FPS 2023-09-08 00:59:33 -05:00
Cameron Gutman 98b18d89eb Fix libva-x11 build in AppImage 2023-09-06 19:25:42 -05:00
Cameron Gutman 78ea79b95a Quiet new -Wswitch warnings 2023-09-05 22:51:08 -05:00
Cameron Gutman acbe503c12 Fix NVIDIA VAAPI X11 deprioritization check to actually check for X11 2023-09-05 20:46:43 -05:00
Cameron Gutman 0261c41574 Revert "Prefer nvidia-vaapi-driver over VDPAU for AV1"
libvdpau 1.5 has supported this for a while and we'll fall
back to VAAPI if VDPAU doesn't work.

This reverts commit 42960b7bf1.
2023-09-05 20:39:28 -05:00
Cameron Gutman c8182dfab3 Use BT.709 for DrmRenderer 2023-09-05 19:28:22 -05:00
Cameron Gutman 2eee9c019f Force the Material theme since our UI relies on it 2023-09-05 19:25:09 -05:00
Cameron Gutman 9ed40b5d9f Add chroma siting metadata to EGL exported frames 2023-09-05 18:30:01 -05:00
Cameron Gutman ebc4c71c72 Fix handling of non-standard hwaccel decoders that also set AV_CODEC_CAP_HARDWARE 2023-09-04 23:40:59 -05:00
Cameron Gutman dec5a6370e Fix build with Qt 5 2023-09-03 19:42:19 -05:00
Cameron Gutman 519858b708 Link to Sunshine 2023-09-03 18:04:31 -05:00
Cameron Gutman 8fcc4cf7fe Update readme and metadata with new features 2023-09-03 18:03:34 -05:00
Cameron Gutman 2f78350696 Change PC offline overlay to match new Play and Stop icons 2023-09-03 17:44:15 -05:00
FrogTheFrog 85d4037a89 Allow to specify gamepad GUIDs to be ignored when streaming 2023-09-03 16:24:05 -05:00
Cameron Gutman d165bf7498 Sort the host list 2023-09-03 16:20:29 -05:00
Cameron Gutman 94d821a4a9 Process insertions and moves in the ComputerModel without resetting 2023-09-03 16:19:24 -05:00
Cameron Gutman 6a854f7a64 Limit default bitrate to one decimal place 2023-09-02 20:15:18 -05:00
Cameron Gutman d76675f4b1 Fix handling of frame rates below 30 FPS 2023-09-02 20:05:27 -05:00
Cameron Gutman 4a7e43ccb6 Improve play and quit button visibility 2023-09-02 19:57:08 -05:00
Cameron Gutman b53333d1ba Refresh the bitrate text when the language changes 2023-09-02 18:47:42 -05:00
Cameron Gutman f1f3c8bfb9 Rerun lrelease 2023-09-02 18:42:39 -05:00
Cameron Gutman e580e7c672 Prune obsolete strings 2023-09-02 18:42:01 -05:00
Cameron Gutman a62fd96269 Rerun lupdate 2023-09-02 18:41:27 -05:00
Cameron Gutman a1d592bb87 Fix loss of all paired computers if Moonlight is terminated while writing the computer list 2023-09-02 16:43:25 -05:00
Cameron Gutman 83f5f6cefc Don't scale default bitrate further beyond 90 FPS 2023-09-02 15:54:12 -05:00
Cameron Gutman 9166e70524 Replace fixed "unsupported FPS" options with fully custom FPS option
Fixes #1003
2023-09-02 15:53:30 -05:00
Cameron Gutman 41f8fa95fe Rerun lupdate and lrelease 2023-08-29 23:46:18 -05:00
Cameron Gutman b140c21ed9 Merge remote-tracking branch 'origin/weblate' 2023-08-29 23:44:30 -05:00
Cameron Gutman 863712cb6e Update SDL2 to f032e8c1 2023-08-29 23:43:27 -05:00
Cameron Gutman 43f0aa7f06 Update moonlight-common-c 2023-08-29 23:41:56 -05:00
Cameron Gutman 12535d8142 Fix warning when quitting app after streaming is enabled 2023-08-29 18:19:43 -05:00
Cameron Gutman 8cf6a8bc98 Clean up old log files if more than 10 exist 2023-08-29 18:11:27 -05:00
Cameron Gutman 7073dd26de Fix race condition that could cause quit segue to never dismiss
Fixes #1060
2023-08-29 18:10:16 -05:00
Jorys Paulin 66bfd2fad8 Translated using Weblate (French)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2023-08-22 14:52:37 +02:00
Cameron Gutman bb444f306a Update SDL_GameControllerDB 2023-08-21 21:58:12 -05:00
Cameron Gutman 626fba4a8f Fix DXVA2 chroma subsampling assuming interlaced content 2023-08-19 18:09:07 -05:00
Cameron Gutman 9dc96bda58 Update moonlight-common-c 2023-08-19 18:08:57 -05:00
Cameron Gutman 1a2f1ed620 Enable AV1 RFI on all decoders capable of HEVC RFI 2023-08-13 16:41:48 -05:00
Cameron Gutman 50255dd896 Set Colorspace connector property when streaming HDR 2023-08-09 23:02:43 -05:00
Cameron Gutman c8b9fda258 Fall back to HEVC if AV1 is forced and unavailable
Fixes #1053
2023-08-05 22:56:12 -05:00
sanhoe bb41732e59 Translated using Weblate (Korean)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2023-08-04 17:06:03 +02:00
ld892012 7d9b984747 Translated using Weblate (French)
Currently translated at 96.7% (208 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2023-08-04 17:06:03 +02:00
Cameron Gutman 7404e5fce2 Prefer D3D11VA for FSE V-Sync off 2023-08-03 19:41:35 -05:00
Cameron Gutman dc5414bd0d Force immediate destruction of the D3D11 swapchain
Possible fix for #959
2023-08-03 19:18:07 -05:00
Cameron Gutman d147d36867 Fix leaking /dev/dri fds on each vaInitialize() failure 2023-08-01 22:53:22 -05:00
Cameron Gutman 7763ec3c5c Fix mapping frames on RPi with 64-bit kernel and 32-bit userland 2023-08-01 22:01:34 -05:00
weng weng c61083ccd9 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2023-07-26 12:06:20 +02:00
Cameron Gutman 841aeccd6b Don't call QHostInfo::fromName() to "resolve" an IP address literal
Fixes #1037
2023-07-26 01:59:00 -05:00
Cameron Gutman bfcd373eec Add PC name to streaming window title
Fixes #1039
2023-07-23 01:16:24 -05:00
Cameron Gutman 202ef41894 Fix scaling of touch coordinates 2023-07-23 01:13:54 -05:00
Cameron Gutman f112edc3c3 Disable touch feedback when sending touch natively 2023-07-23 00:17:58 -05:00
Cameron Gutman 05fd7b18b9 Update touch API 2023-07-22 17:23:09 -05:00
ns6089 9764174070 Add host processing latency to ffmpeg stats overlay 2023-07-20 22:30:26 -05:00
Luna Jernberg 6b0370f0bc Translated using Weblate (Swedish)
Currently translated at 96.7% (208 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/sv/
2023-07-19 22:04:39 +02:00
gallegonovato 2e4313e7c3 Translated using Weblate (Spanish)
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2023-07-19 22:04:39 +02:00
Jen Kung-chih 2684d1874a Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (215 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2023-07-18 07:04:33 +02:00
Jen Kung-chih 882539a700 Translated using Weblate (Chinese (Traditional))
Currently translated at 97.2% (209 of 215 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2023-07-18 03:06:10 +02:00
Cameron Gutman 42960b7bf1 Prefer nvidia-vaapi-driver over VDPAU for AV1 2023-07-16 16:54:13 -05:00
Cameron Gutman 333f978186 Add dav1d software AV1 decoder to FFmpeg libraries 2023-07-16 16:41:55 -05:00
Cameron Gutman e7e9df3261 Populate AV1 bitrate multiplier 2023-07-16 15:54:58 -05:00
Cameron Gutman 24dbc96fe5 Regenerate translations 2023-07-16 15:47:31 -05:00
Cameron Gutman abc391f32a Improve AV1 support 2023-07-16 15:38:58 -05:00
Cameron Gutman a589e8e3de Merge remote-tracking branch 'origin/weblate' 2023-07-16 12:31:52 -05:00
Fernando Lopes 39fa9a91b8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 19.6% (41 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/pt_BR/
2023-07-16 16:02:55 +02:00
Angus Wan e10d98fb79 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (209 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2023-07-16 16:02:55 +02:00
Cameron Gutman d17cdbda93 Replace another QSharedPointer::get() call with Qt 5.10 alternative 2023-07-16 00:18:17 -05:00
Cameron Gutman 2252abefbf Fix build break on Qt 5.10 and earlier 2023-07-16 00:13:09 -05:00
Cameron Gutman ce60a146dc Move RPi decoder hacks to correct location for new selection logic 2023-07-15 15:22:02 -05:00
Cameron Gutman cd4a6f3e17 Rewrite FFmpeg decoder selection logic to dynamically discover compatible decoders
This avoids having to maintain a hardcoded list of all known H.264/HEVC/AV1 decoders
and ensures new/unknown decoders will just work automatically (assuming we have
a renderer that can process their output pixel format).
2023-07-15 15:12:58 -05:00
Cameron Gutman fc5e1ed5d8 Work around broken APT config on AppVeyor Ubuntu2004 image 2023-07-15 01:20:26 -05:00
Cameron Gutman d70b98075e Upgrade AppImage to Ubuntu 20.04 base 2023-07-15 00:47:26 -05:00
Cameron Gutman a4d9952061 Migrate from altool to notarytool for notarization 2023-07-14 21:44:14 -05:00
Cameron Gutman 45e4dfe09c Don't globally install wix.exe in CI anymore 2023-07-14 20:32:18 -05:00
Cameron Gutman 34ba9063ad Move installer signing into MSBuild 2023-07-14 20:31:32 -05:00
Cameron Gutman d5e2d59fe9 Fixes for WiX v4 migration 2023-07-14 20:05:49 -05:00
Cameron Gutman d39bdb28d6 Update AppImage to libva 2.19 2023-07-14 19:55:44 -05:00
Cameron Gutman 8bf5622d21 Upgrade installer to WiX v4 2023-07-14 19:55:00 -05:00
Maciej Błędkowski 9cafd5e099 Translated using Weblate (Polish)
Currently translated at 100.0% (209 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/pl/
2023-07-11 17:50:13 +02:00
gallegonovato d1b3bfd700 Translated using Weblate (Spanish)
Currently translated at 100.0% (209 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2023-07-11 17:50:12 +02:00
Cameron Gutman e60caf06f9 Update libraries
OpenSSL -> 1.1.1u
SDL -> 156c7ba
2023-07-09 17:49:04 -05:00
Cameron Gutman a243c6bb90 Don't build unneeded sources
Fixes #1010
2023-07-09 17:23:21 -05:00
Cameron Gutman 10523a7e27 Correct guards around newer SDL functionality 2023-07-09 16:36:41 -05:00
Casey Korver ea06ec0133 Correct spelling mistakes found with Codespell 2023-07-09 13:21:18 -05:00
jcj83429 e287ebcded Add support for Applications (context menu) key
I'm adding corresponding support in Sunshine
2023-07-08 20:08:02 -05:00
Cameron Gutman 2ee90289a1 Update moonlight-common-c 2023-07-08 19:17:22 -05:00
Cameron Gutman 193df861d1 Update moonlight-common-c 2023-07-05 19:48:25 -05:00
Cameron Gutman 9055496a19 Add AV1 test frames 2023-07-05 19:47:45 -05:00
Cameron Gutman 5424576f73 Ensure serverCodecModeSupport is always non-zero 2023-07-05 19:46:30 -05:00
Cameron Gutman a81c6a1c5e Initial work on AV1 support 2023-07-03 00:45:36 -05:00
Cameron Gutman 53a572a4a4 Add controller motion deduplication and fix gyro units 2023-07-02 19:22:00 -05:00
Cameron Gutman df283d80c6 Implement controller LED and battery protocol extensions 2023-07-02 16:44:41 -05:00
Cameron Gutman b6d203b6dd Pull latest protocol extension changes 2023-06-27 22:51:50 -05:00
Cameron Gutman ef988cc8af Destroy the QMdnsEngine Server and Cache when not polling
These set background timers to refresh state, rebind sockets,
and refresh the cache. This is all stuff that we don't want to
be doing while streaming.
2023-06-21 00:54:11 -05:00
Kung-chih ba46905243 Update SettingsView.qml 2023-06-20 21:58:10 -05:00
Cameron Gutman df0c4c8208 Delete the QNetworkAccessManager when we're done with it
Apparently having this object around can lead to background
network scans happening that cause WiFi perf degradation.
2023-06-20 21:51:28 -05:00
Cameron Gutman e5aaa1fc53 Implement controller arrival events using Sunshine protocol extension 2023-06-18 23:34:28 -05:00
James Le Cuirot 8b6df34d8b Allow forced disabling of features instead of relying on pkg-config
So-called "automagic" behaviour is considered unhelpful by
distributions. A user may unintentionally build Moonlight with VDPAU
support simply because they had that library installed, only for the
whole application to break when they uninstall the library later. This
could happen on any distribution, but it should especially be avoided on
source-based distributions, where the user would not expect the package
manager to encounter such issues.

This commit is best viewed without whitespace changes.
2023-06-18 16:43:16 -05:00
Cameron Gutman 7a719aa886 Allow up to 16 gamepads with Sunshine hosts 2023-06-18 16:39:24 -05:00
Cameron Gutman 9af58af5e4 Implement gyro/accel and touchpad input using Sunshine extension 2023-06-18 16:08:32 -05:00
Cameron Gutman b945c8c2dc Implement trigger rumble using Sunshine protocol extension 2023-06-18 16:04:49 -05:00
Cameron Gutman 2165f56aee Implement full multi-touch support using Sunshine protocol extension 2023-06-18 16:02:21 -05:00
Cameron Gutman e42de9d155 Add extended button support with Sunshine 2023-06-18 16:00:51 -05:00
sanhoe 7fd95b9de2 Translated using Weblate (Korean)
Currently translated at 100.0% (209 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2023-05-22 11:50:36 +02:00
Jorys Paulin a42c85802e Translated using Weblate (French)
Currently translated at 100.0% (209 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2023-05-22 11:50:36 +02:00
shakedex cb3730100d Translated using Weblate (Hebrew)
Currently translated at 2.8% (6 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/he/
2023-04-29 18:51:13 +02:00
Yutaro Urata 7a04d2ad2c Translated using Weblate (Japanese)
Currently translated at 89.4% (187 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ja/
2023-04-29 18:51:12 +02:00
shakedex 1f93976919 Translated using Weblate (Hebrew)
Currently translated at 1.4% (3 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/he/
2023-04-27 20:48:43 +02:00
shakedex 0b7505f109 Added translation using Weblate (Hebrew) 2023-04-26 00:54:28 +02:00
Jen Kung-chih 392ca2d8b7 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (209 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2023-04-14 18:48:56 +02:00
Cameron Gutman 68daa90b7e Refactor non-hwaccel decoders to use a single list of known codec names 2023-04-02 17:31:31 -05:00
Cameron Gutman fb39ad896e Add h264_omx and hevc_omx decoders used by the Vision Five 2 2023-04-02 17:17:25 -05:00
Simon Nilsson cb199054e3 Translated using Weblate (Swedish)
Currently translated at 100.0% (209 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/sv/
2023-04-01 20:38:06 +02:00
Cameron Gutman 7cf77211b0 Update AppImage to libva 2.18.0 2023-03-28 20:06:09 -05:00
Cameron Gutman a3d1e9a117 Allow negotation of 36 and 48 bit deep color modes 2023-03-27 01:02:50 -05:00
Cameron Gutman 755c7c9895 Set max BPC property when streaming in 10-bit color modes 2023-03-27 00:44:02 -05:00
Cameron Gutman 45b5136233 Update libs for Windows, macOS, and AppImage
SDL2_ttf -> 2.20.2
SDL2 -> b57a6a1a
OpenSSL -> 1.1.1t (eec3403 for WinARM64)
opus -> 8cf872a1
FFmpeg -> 6.0 (with DXVA2/D3D11VA AV1 decoders enabled)
2023-03-26 20:49:25 -05:00
Cameron Gutman f16b5cd337 Expose glslow config option 2023-03-26 20:27:03 -05:00
Cameron Gutman 21cda8141d Always request AV_PIX_FMT_DRM_PRIME to ensure we get it if available 2023-03-26 19:59:12 -05:00
Cameron Gutman 90f26dfa45 Support direct KMSDRM output for platforms with slow GL implementations 2023-03-26 19:46:22 -05:00
Cameron Gutman ba507d8046 Move hw->sw frame mapping into a separate class 2023-03-26 14:41:02 -05:00
Cameron Gutman 5639bcc0f5 Default SDL render driver to GLES2 when using EGLFS
Fixes #868
2023-03-26 13:43:09 -05:00
Cameron Gutman 4cd030582d Allow matching primary planes for direct rendering 2023-03-26 13:28:26 -05:00
Jen Kung-chih 7908076b39 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (209 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2023-03-26 10:40:35 +02:00
Cameron Gutman ca91f9b702 Allow use of the delete key to delete PCs 2023-03-25 16:33:16 -05:00
Jen Kung-chih 0e86af1c9b Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (209 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2023-03-22 09:37:22 +01:00
weng weng c5ad84e2ee Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (209 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2023-03-22 09:37:21 +01:00
gallegonovato bfb5d6e8fe Translated using Weblate (Spanish)
Currently translated at 100.0% (209 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2023-03-20 18:41:02 +01:00
weng weng d63b0b5978 Translated using Weblate (Chinese (Simplified))
Currently translated at 96.6% (202 of 209 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2023-03-20 18:41:02 +01:00
Cameron Gutman 5e6b60bae0 Fix handling of IPv6 literals with port numbers 2023-03-18 14:47:23 -05:00
Cameron Gutman 65b6885c61 Update VCRedist 2023-03-18 14:36:01 -05:00
Cameron Gutman e8ceaa4085 Update SDL_GameControllerDB 2023-03-18 14:34:13 -05:00
Cameron Gutman db2b120b68 Remove unused variable 2023-03-18 14:29:45 -05:00
Cameron Gutman 5f315d0093 Rerun lupdate and lrelease 2023-03-18 14:24:29 -05:00
Cameron Gutman 016b212ff7 Merge remote-tracking branch 'origin/weblate' 2023-03-18 14:22:11 -05:00
Cameron Gutman bccffe09e2 Purge NVIDIA-specific strings 2023-03-18 14:20:51 -05:00
Cameron Gutman a0365c8e1c Evaluate D3D9 quirks before calling CreateDeviceEx() 2023-03-17 00:53:47 -05:00
Cameron Gutman 38156c9f7f Default to D3D11VA on multi-GPU systems 2023-03-13 22:56:28 -05:00
Cameron Gutman 640db09a64 Update moonlight-common-c 2023-03-11 11:45:07 -06:00
Cameron Gutman 5baa8c680a Add upstreamed NV15 format used by Rockchip devices 2023-03-11 11:44:36 -06:00
Cameron Gutman e1c4a488ed Remove mouse throttling code
This should be managed inside moonlight-common-c instead.
2023-03-11 11:40:37 -06:00
Cameron Gutman 332d4433c4 Add horizontal scrolling in mouse emulation mode 2023-03-04 12:28:53 -06:00
Karnosio f5d535b1ef Translated using Weblate (Ukrainian)
Currently translated at 34.1% (71 of 208 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/uk/
2023-02-21 19:36:41 +01:00
Cameron Gutman 35e226273c Tell the host if we want gamepads to persist after disconnection 2023-02-20 16:52:28 -06:00
Cameron Gutman 90e25e60d6 Add FPS values for all attached displays and support custom FPS values
Fixes #926
2023-02-03 00:45:27 -06:00
Cameron Gutman 80659160d3 Consolidate launch and resume and skip FPS hack for Sunshine 2023-02-03 00:36:30 -06:00
Cameron Gutman 5de29928cd Plumb HDR metadata for VideoToolbox 2023-02-02 19:16:18 -06:00
Cameron Gutman c65cea0f66 Update AppImage to libva 2.17 2023-01-17 21:45:29 -06:00
Cameron Gutman b3cfdbb835 Fix DRM renderer build 2023-01-17 21:45:02 -06:00
Cameron Gutman 9c15cceee7 Don't batch mouse motion for Sunshine 2023-01-16 22:01:34 -06:00
Cameron Gutman 8a0142bd0f Plumb HDR metadata and horizontal scrolling with Sunshine 2023-01-16 21:51:18 -06:00
ssantos 3d6e05d8c0 Translated using Weblate (Portuguese)
Currently translated at 94.2% (196 of 208 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/pt/
2023-01-11 16:51:06 +01:00
Jen Kung-chih ddf0e6a935 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (208 of 208 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2023-01-02 07:47:19 +01:00
Dan 51852f044e Translated using Weblate (Ukrainian)
Currently translated at 32.6% (68 of 208 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/uk/
2022-12-21 01:50:35 +01:00
Cameron Gutman 17cad6b3ca Fix a missed ZH_TW rename
Fixes #906
2022-12-15 22:48:44 -06:00
Jen Kung-chih 8912869077 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (208 of 208 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2022-12-07 11:48:30 +01:00
Zaraza225 a2697634ba Translated using Weblate (Ukrainian)
Currently translated at 32.6% (68 of 208 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/uk/
2022-12-02 21:48:32 +01:00
Cameron Gutman 1bfb798107 Update SDL_GameControllerDB 2022-11-29 19:22:39 -06:00
Suzuran cf60568f53 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (208 of 208 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2022-11-29 11:47:36 +01:00
Cameron Gutman ddd18fdaf6 Rerun lupdate and lrelease 2022-11-25 14:18:44 -06:00
Cameron Gutman b04bb30930 Rename zh_Hant to zh_TW to match Qt's expected name 2022-11-25 14:18:06 -06:00
Cameron Gutman e41aaf980b Merge remote-tracking branch 'origin/weblate' 2022-11-25 14:06:13 -06:00
happyharryh 46df6601a1 Update translation for dialog buttons
Reference: https://github.com/qt/qttranslations/tree/dev/translations
2022-11-25 14:05:41 -06:00
happyharryh 5eb8b97469 Fix LANG_AUTO for Simplified Chinese 2022-11-25 14:05:41 -06:00
happyharryh 8cd0536fb1 Fix the translation of standard buttons in Dialogs 2022-11-25 14:05:41 -06:00
Zaraza225 99a6789720 Translated using Weblate (Ukrainian)
Currently translated at 30.0% (61 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/uk/
2022-11-24 07:47:40 +01:00
Cameron Gutman 92670cc051 Add support for Qt 6's ARM64 qmake.bat 2022-11-21 21:37:16 -06:00
Cameron Gutman 89cbbfc291 Derive target architecture based on Qt path 2022-11-21 21:30:24 -06:00
Cameron Gutman 31869a7da2 Update to SDL 2.26.0 2022-11-21 20:50:44 -06:00
Cameron Gutman 68c6d6c932 Attempt to fix build with SDL 2.26 on systems with old GL headers 2022-11-21 20:32:10 -06:00
Loïc Hesling e4e140fa53 Translated using Weblate (French)
Currently translated at 97.0% (197 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2022-11-17 12:47:09 +01:00
Cameron Gutman b9709d0825 Fix build without SDL_VIDEO_DRIVER_WAYLAND 2022-11-14 21:11:26 -06:00
Cameron Gutman 324e3dffa0 Update MSVC Redist 2022-11-13 11:41:03 -06:00
Cameron Gutman dd9de3fcb4 Offset the ports used for WOL if the host is using alternate ports 2022-11-13 11:39:28 -06:00
Cameron Gutman 6710d0561a Use current HTTP port if ExternalPort is not present 2022-11-13 11:35:47 -06:00
Translator-3000 a30f97db0d Translated using Weblate (Italian)
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/it/
2022-11-13 13:49:04 +01:00
Cameron Gutman cae666b834 Wake pacing queue waiters when Pacer is being destroyed 2022-11-12 15:55:36 -06:00
Cameron Gutman 5970ca49e9 Don't use swap interval on Wayland 2022-11-12 15:45:54 -06:00
Cameron Gutman 6ae6218043 Refactor Pacer to handle both blocking and non-blocking VsyncSources 2022-11-12 15:08:50 -06:00
Cameron Gutman 8e3e19a7f7 Removed unused NullThreadedVsyncSource 2022-11-12 14:30:38 -06:00
Cameron Gutman e3d51fd7f7 Implement a Wayland Vsync source using frame callbacks 2022-11-12 13:03:42 -06:00
Brandon Goldberg 95f5c1d104 Translated using Weblate (Spanish)
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2022-11-09 16:47:06 +01:00
Jen Kung-chih 02a14069a3 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2022-11-05 14:04:04 +01:00
Cameron Gutman 394f28339e Update v4.3.1 changelog 2022-11-02 20:43:25 -05:00
Cameron Gutman 411998d4e3 Implement detection of local IPv6 addresses 2022-11-02 20:35:41 -05:00
Cameron Gutman 371906445e Prepare for v4.3.1 2022-11-02 19:44:44 -05:00
Cameron Gutman 34d91e58c0 Rerun lupdate and lrelease 2022-11-02 19:34:47 -05:00
Cameron Gutman f3f25bec7a Mark unfinished translation in zh_cn 2022-11-02 19:34:14 -05:00
Cameron Gutman 08d74bea75 Merge remote-tracking branch 'origin/weblate' 2022-11-02 19:30:25 -05:00
Dominik Chrástecký 63f917b317 Translated using Weblate (Czech)
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/cs/
2022-11-03 01:28:51 +01:00
Cameron Gutman 448633ec3c Fix some log messages with extra spaces 2022-11-02 19:25:44 -05:00
Cameron Gutman 09ce3c9ce4 Use CRLF line endings for the log file on Windows 2022-11-02 19:19:00 -05:00
Cameron Gutman 8fcc98102b Limit log by size rather than line count 2022-11-02 19:18:22 -05:00
Cameron Gutman e14075b464 Update moonlight-common-c to fix audio FEC OOS bug 2022-11-01 23:47:51 -05:00
Howard Wu ca89d14ec8 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.5% (202 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2022-11-02 03:03:38 +01:00
Cameron Gutman 3f51998065 Start the streaming window maximized if the GUI was maximized
Fixes #889
2022-10-30 11:56:15 -05:00
이정희 6e5b6a1a5c Translated using Weblate (Korean)
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2022-10-30 15:02:46 +01:00
sanhoe 660f6c5bbf Translated using Weblate (Korean)
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2022-10-29 06:02:07 +02:00
Cameron Gutman 5034bd72ed Bump v4.3.0 release date 2022-10-27 19:44:58 -05:00
Cameron Gutman 5b7c144796 Add workaround for DWM dark mode bug on Win10 2022-10-27 19:26:51 -05:00
Cameron Gutman e45b6b9b85 Avoid white flash when starting streaming on Windows 2022-10-27 18:18:47 -05:00
Cameron Gutman 245f242fb8 Add support for dark mode on Windows
Fixes #886
2022-10-27 18:12:26 -05:00
Cameron Gutman a6fccf93d1 Use a separate texture for rendering to avoid blending in the alignment padding
Fixes #885
2022-10-26 21:48:55 -05:00
Cameron Gutman 3dcab90752 Fix appdata for v4.3.0 2022-10-25 23:50:33 -05:00
Cameron Gutman 30a5f405f5 Enable HEVC RFI for the FFmpeg software decoder 2022-10-25 23:19:19 -05:00
Cameron Gutman bad1c9628e Prepare for v4.3.0 2022-10-25 23:11:12 -05:00
Cameron Gutman 1ab7bca43f Update SDL in AppImage 2022-10-25 22:47:29 -05:00
Cameron Gutman 7d0a53773c Rerun lupdate and lrelease 2022-10-25 22:45:03 -05:00
Cameron Gutman 789236ee8f Merge remote-tracking branch 'origin/weblate' 2022-10-25 22:44:21 -05:00
weng weng 21cba0b080 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2022-10-26 05:43:12 +02:00
Cameron Gutman 31eb0891e5 Update moonlight-common-c for improved audio FEC recovery 2022-10-25 22:42:12 -05:00
Cameron Gutman a13f8e7937 Ensure delayed flushes complete during shutdown 2022-10-25 02:43:55 -05:00
Cameron Gutman 5bfe56472f Fix build 2022-10-25 02:08:46 -05:00
Cameron Gutman 16a9ed682e Use a single flush thread for ComputerManager
Some QSettings backends don't like concurrent writes and using a
single thread improves write batching behavior.
2022-10-25 02:07:16 -05:00
Cameron Gutman 59cae610d2 Clarify encoding resolution limit comment 2022-10-25 01:24:57 -05:00
Cameron Gutman 29ac7893d7 Don't enforce the NVENC H.264 resolution limit on Sunshine hosts
Closes #882
2022-10-25 01:18:10 -05:00
Cameron Gutman c8aeca8ace Add an environment variable opt-out for the gamepad quit combo
See #870
2022-10-25 00:27:40 -05:00
Cameron Gutman 3da505c4c6 Minor perf optimization suggested by Clazy 2022-10-25 00:26:03 -05:00
Cameron Gutman 06bc05b708 Rerun lupdate and lrelease 2022-10-25 00:14:22 -05:00
Cameron Gutman a3dc9593c2 Merge remote-tracking branch 'origin/weblate' 2022-10-25 00:10:40 -05:00
Cameron Gutman 18a271a0ec Update moonlight-common-c for improved audio FEC recovery 2022-10-25 00:10:29 -05:00
Cameron Gutman cebd3bf751 Save hosts on a separate thread
QSettings on macOS Ventura performs horribly when saving a lot of values
(like several hosts or a host with a large app list). This change considerably
improves UI performance by moving the long operation off the main thread.
2022-10-25 00:07:07 -05:00
Cameron Gutman cb04925c6e Optimize locking in ComputerManager to reduce reader contention 2022-10-25 00:07:07 -05:00
Cameron Gutman b7a340bd00 Improve locking in NvComputer class 2022-10-24 23:53:02 -05:00
Cameron Gutman 2a0f837120 Stop issuing further HTTP requests when quitting 2022-10-24 23:46:57 -05:00
Cameron Gutman d00bdff495 Update SDL and FFmpeg for AppImage 2022-10-23 16:59:54 -05:00
Cameron Gutman 7e9c977b3d Update moonlight-common-c 2022-10-23 16:55:52 -05:00
reloxx13 1629151528 Translated using Weblate (German)
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2022-10-21 20:05:49 +02:00
reloxx13 24295197bd Translated using Weblate (German)
Currently translated at 98.0% (199 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2022-10-20 19:02:35 +02:00
Eric 807dd84daa Translated using Weblate (Chinese (Simplified))
Currently translated at 98.5% (200 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2022-10-18 13:05:01 +02:00
Cameron Gutman 17848c097c Don't switch between GLX and EGL if EGL worked 2022-10-16 22:57:08 -05:00
Cameron Gutman 563b760d31 Update packages for recent versions of Ubuntu 2022-10-16 22:23:27 -05:00
Cameron Gutman 8465a27a97 Deprioritize VAAPI for Gallium and Nvidia drivers and work around RFI latency bug 2022-10-16 21:51:10 -05:00
Cameron Gutman 255cbd1e7e Fix building with SDL 2.0.20 or earlier 2022-10-15 15:36:09 -05:00
Cameron Gutman 7717749b72 Reduce max queued frames to avoid exhausting the decoder buffer pool 2022-10-15 14:34:42 -05:00
Cameron Gutman 9e4337babf Revert "Increase the number of decoder surfaces for DXVA2/D3D11VA"
This reverts commit 02c781b8f4.
2022-10-15 14:32:12 -05:00
Cameron Gutman 6771877a84 Make scaleSourceToDestinationSurface() idempotent
Deviation between scaling done during window sizing and renderer
viewport configuration can lead to small errors that create visible
artifacts such as a single pixel pillarbox/letterbox on one side.
2022-10-15 13:12:18 -05:00
Cameron Gutman a860bd8dd5 Enable HEVC RFI on Tegra 2022-10-14 23:41:09 -05:00
Cameron Gutman 704ce4ff0c Add environment variable for overriding decoder capabilities 2022-10-14 23:39:49 -05:00
Cameron Gutman 21edcc8113 Add workaround for macOS fullscreen exit bug 2022-10-14 23:01:55 -05:00
Cameron Gutman 125738f13d Cancel overlay update callbacks before destroying VTRenderer 2022-10-14 22:49:33 -05:00
Cameron Gutman 02c781b8f4 Increase the number of decoder surfaces for DXVA2/D3D11VA
We forgot to take into account frames buffered within Moonlight itself
2022-10-14 21:20:35 -05:00
Cameron Gutman 453fe6aea0 Ensure the target window size is always even 2022-10-14 21:09:32 -05:00
Cameron Gutman e0fb7dfd14 Rework window creation, sizing, and fullscreen transition logic
- Fixes window position/size being lost when toggling full-screen
- Fixes initial window size not respecting stream aspect ratio
2022-10-14 20:24:12 -05:00
Cameron Gutman c4012225c2 Rerun lupdate and lrelease 2022-10-14 19:27:04 -05:00
Cameron Gutman 1750243537 Merge remote-tracking branch 'origin/weblate' 2022-10-14 19:26:38 -05:00
Cameron Gutman 84fc5f3008 Use colorspace helper with VAAPI direct rendering 2022-10-14 19:23:51 -05:00
Cameron Gutman 8a224fbae4 Revert "Fix macOS build"
This reverts commit 0928d1fab2.
2022-10-13 01:19:56 -05:00
Cameron Gutman e6c4332445 Revert "Use Rec 2020 colorspace for WCG support even if HDR is off on the host"
Rec 2020 conversion causes colors to be blown out in SDR

This reverts commit 472e8ee92e.
2022-10-13 01:19:49 -05:00
Cameron Gutman 0928d1fab2 Fix macOS build 2022-10-13 01:11:04 -05:00
Cameron Gutman 40dd588bef Use 10-bit format mask instead of hardcoding specific formats 2022-10-13 00:17:05 -05:00
Cameron Gutman 472e8ee92e Use Rec 2020 colorspace for WCG support even if HDR is off on the host 2022-10-13 00:14:10 -05:00
Cameron Gutman c55af7fff3 Add a decoder/renderer callback to specify the desired color range 2022-10-12 23:59:01 -05:00
Cameron Gutman 7a39daf179 Implement Rec 601 Full Range support in SDLRenderer 2022-10-12 23:52:29 -05:00
Cameron Gutman 8d087ba5b1 Allow localization of pairing error strings 2022-10-12 22:20:15 -05:00
Cameron Gutman c7a30d8728 Allow a pairing attempt even if the PC is busy
Pairing while busy doesn't work with GFE but works with Sunshine
2022-10-12 22:15:58 -05:00
LedyBacer 1f678cafba Translated using Weblate (Russian)
Currently translated at 100.0% (200 of 200 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2022-10-12 10:23:12 +02:00
Jen Kung-chih 579e0bc464 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (200 of 200 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2022-10-10 19:59:11 +02:00
Nikita Epifanov 7f36dc5aad Translated using Weblate (Russian)
Currently translated at 97.0% (194 of 200 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2022-10-10 19:59:11 +02:00
Eric a7c2026128 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (200 of 200 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2022-10-10 19:59:11 +02:00
Sargon-Isa 7b0d67ebbe Translated using Weblate (German)
Currently translated at 100.0% (200 of 200 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2022-10-10 19:59:10 +02:00
Cameron Gutman 63f0910eac Update AppImage to libva 2.16.0 2022-10-07 22:22:13 -05:00
Cameron Gutman b16cf9fcc5 Update moonlight-common-c with speculative RFI support 2022-10-07 22:21:16 -05:00
Cameron Gutman 5501d7bc6b Include opt-out variable in VAAPI avoidance log output 2022-10-05 18:50:53 -05:00
Cameron Gutman 413ef42a34 Enable HEVC RFI for CUDA, VAAPI, and VDPAU decoders 2022-10-05 18:47:43 -05:00
Cameron Gutman a1694ab951 Use nvidia-vaapi-driver on Wayland systems if available 2022-10-05 18:44:11 -05:00
Cameron Gutman fbbbd5e0b8 Use new LiRequestIdrFrame() API 2022-10-05 00:53:31 -05:00
Cameron Gutman f8c7e2ee41 Enable HEVC RFI for VideoToolbox 2022-10-05 00:25:59 -05:00
Cameron Gutman 2a4af0bc2c Use colorspace helpers for DRM EGL export 2022-10-05 00:20:33 -05:00
Cameron Gutman d24db13e9d Update SDL_GameControllerDB 2022-10-05 00:00:39 -05:00
Cameron Gutman bc68aad67b Print the frame number if decoding fails 2022-10-04 23:38:54 -05:00
Cameron Gutman 9e8a4cab7b Enable HEVC RFI for DXVA2 and D3D11VA 2022-10-04 22:56:21 -05:00
Cameron Gutman 3679964cf8 Rerun lupdate and lrelease 2022-10-04 22:06:57 -05:00
Cameron Gutman 225ec1a87f Merge remote-tracking branch 'origin/weblate' 2022-10-04 22:05:48 -05:00
Cameron Gutman 071c76c06a Add error text for ML_ERROR_FRAME_CONVERSION 2022-10-04 22:05:18 -05:00
Cameron Gutman d77a255721 Update moonlight-common-c with GFE 3.26 and HEVC RFI fixes 2022-10-04 22:03:27 -05:00
Cameron Gutman 26ee1aee25 Fix protected content error on GFE 3.22+ 2022-10-04 22:02:44 -05:00
Jen Kung-chih b047395938 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2022-09-29 15:16:28 +02:00
Cameron Gutman 483a601867 Display SDR/HDR status in performance overlay 2022-09-28 17:44:22 -05:00
bruh c4bc2f6adf Translated using Weblate (Vietnamese)
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/vi/
2022-09-25 17:18:44 +02:00
Cameron Gutman 439e8fe33f Constify frame colorspace helper functions 2022-09-24 18:37:51 -05:00
Cameron Gutman 99885f5b4b Centralize colorspace and color range handling 2022-09-24 12:28:23 -05:00
Cameron Gutman 78b522ec1d Fix large frame header size for GFE 3.26 2022-09-22 23:48:41 -05:00
Jorys Paulin dbfa5a9358 Translated using Weblate (French)
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2022-09-14 15:20:55 +02:00
Howard Wu 3910b3862a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2022-09-13 07:18:54 +02:00
Cameron Gutman 3b8e3b7d5c Prepare for v4.2.1 2022-09-12 00:12:35 -05:00
Cameron Gutman 6129000dde Rerun lupdate and lrelease 2022-09-12 00:08:31 -05:00
Cameron Gutman 65b274d695 Merge remote-tracking branch 'origin/weblate' 2022-09-12 00:07:42 -05:00
reloxx13 a13dd0076b Translated using Weblate (German)
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2022-09-12 06:41:15 +02:00
Cameron Gutman cc026c995c Fix mishandling of IDR frames with a SEI or AUD NAL
Fixes #865
2022-09-11 23:36:20 -05:00
Cameron Gutman 81cfacaa3d Add fallback logic when the host doesn't send valid colorspace data 2022-09-11 23:13:10 -05:00
Cameron Gutman fabebc05d2 Prepare for v4.2.0 2022-09-10 12:19:04 -05:00
Cameron Gutman 07a97bab03 Rerun lupdate and lrelease 2022-09-10 12:06:22 -05:00
Cameron Gutman 69ac810c82 Merge remote-tracking branch 'origin/weblate' 2022-09-10 12:05:24 -05:00
Daniel Saburi 3f4609dc6e Translated using Weblate (Portuguese (Brazil))
Currently translated at 16.1% (32 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/pt_BR/
2022-09-09 04:16:43 +02:00
Daniel Saburi 1ddb529d26 Translated using Weblate (Portuguese)
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/pt/
2022-09-09 04:16:42 +02:00
Cameron Gutman ab6b88073c Fix build with _FILE_OFFSET_BITS=64 2022-09-05 22:04:24 -05:00
Cameron Gutman ad3ed97472 Fix handling of 3 byte Annex B start sequences 2022-09-05 17:56:42 -05:00
Cameron Gutman 07e902fe95 Fix build with open() defined to open64() 2022-09-04 23:45:18 -05:00
Cameron Gutman eb781ce689 Use __FUNCTION__ macro instead of hardcoding 2022-09-03 18:09:01 -05:00
Cameron Gutman 555a65c8ea Add open64() hook for _FILE_OFFSET_BITS=64 2022-09-03 18:05:03 -05:00
Cameron Gutman 58429f4172 Fix handling of the optional mode argument in our open() hook 2022-09-03 17:52:54 -05:00
sanhoe e2692a0abd Translated using Weblate (Korean)
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2022-09-03 00:21:34 +02:00
Brandon Goldberg d218c43edf Translated using Weblate (Spanish)
Currently translated at 97.4% (193 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2022-09-03 00:21:34 +02:00
Cameron Gutman fae9813127 Rebuild libraries built with CMake to get working PDBs and better optimizations
Additionally, disable WGI to work around an SDL bug

https://github.com/cgutman/moonlight-deps/commit/1810c6fba8b2377791e68d8829ee4c9a796788af
2022-08-28 20:34:44 -05:00
Cameron Gutman adc56d4ee5 Rerun lupdate and lrelease 2022-08-28 20:22:39 -05:00
Cameron Gutman f982e33fab Merge remote-tracking branch 'origin/weblate' 2022-08-28 20:20:53 -05:00
Cameron Gutman 9ab4479fcf Fix excessive flickering when switching between full-screen and windowed on Windows 2022-08-28 20:10:26 -05:00
Cameron Gutman 3e330b7929 Attach to the parent console to print list output on Win32 2022-08-27 14:54:45 -05:00
Cameron Gutman ecddada150 Improve verbose log suppression 2022-08-25 23:02:32 -05:00
Cameron Gutman 0c828cbb37 Suppress non-critical log output while running the list command 2022-08-25 22:45:42 -05:00
Cameron Gutman 84a32f8c16 Minor cleanup of new list code 2022-08-25 22:39:04 -05:00
Anselm Busse 4ee36fd405 Avoid GUI display for App listing 2022-08-25 22:18:05 -05:00
Anselm Busse 0802609fd8 Correcting for PR comments 2022-08-25 22:18:05 -05:00
Anselm Busse de88176995 Add a 'list' option for the CLI
This commit addresses Issue #448 by adding a command line option that allows the listing of all the Apps reported by the remote host as a CSV.
2022-08-25 22:18:00 -05:00
Cameron Gutman aca82f400a Never use fewer than 480 samples to avoid causing issues on PulseAudio systems
Fixes #830
Fixes #858
2022-08-25 21:27:05 -05:00
Brandon Goldberg 54bb6fe527 Translated using Weblate (Spanish)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2022-08-25 02:17:55 +02:00
Cameron Gutman c1f663cec1 Add missing files from previous commit 2022-08-24 17:31:22 -05:00
Cameron Gutman aaf59c2d01 Add CLI pairing support 2022-08-24 00:22:02 -05:00
Cameron Gutman 692942e5be Move PIN generation into ComputerManager to be shared with CLI pairing 2022-08-24 00:21:25 -05:00
Cameron Gutman f4acd629a1 Save hosts immediately after pairing concludes to ensure the server cert is persisted 2022-08-24 00:20:22 -05:00
Cameron Gutman 0ff8a44ec3 Allow 10-bit in DXVA2 as a fallback 2022-08-22 00:23:07 -05:00
Cameron Gutman c6d8f6e618 Allow D3D11VA to be disabled via environment variable 2022-08-22 00:19:46 -05:00
Cameron Gutman 1baa23cc43 Update SDL to 84004d1 for AppImage 2022-08-21 20:58:50 -05:00
Cameron Gutman 3be696e6bb Update SDL to 84004d1 2022-08-21 20:55:38 -05:00
Cameron Gutman 3cfcb01603 Add simple script to help updating libs 2022-08-21 20:55:25 -05:00
Cameron Gutman d39a29759c Don't draw with test-only renderers 2022-08-21 18:38:09 -05:00
Cameron Gutman bf4332b9e7 Prefer D3D11VA in non-FSE modes for improved performance 2022-08-21 17:50:56 -05:00
Cameron Gutman feaae7b684 Update moonlight-common-c to improve control stream failure diagnostics 2022-08-21 15:10:49 -05:00
Cameron Gutman be7852dfc0 Remove desktop resolution in favor of native (unscaled) resolution
Seeing 2 "native" resolutions for a single high DPI display is confusing.

If someone wants a lower resolution aspect-ratio match of a display, they
can use a custom resolution.
2022-08-16 01:22:26 -05:00
Cameron Gutman b0804ce048 Fix native resolution detection on high DPI wayland systems 2022-08-16 00:25:57 -05:00
Cameron Gutman aa7d5fa924 Don't use swap interval 1 by default on Wayland systems 2022-08-14 13:09:31 -05:00
Sargon-Isa b4cab12e9d Translated using Weblate (German)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2022-08-11 19:15:27 +02:00
Cameron Gutman 133dda2de0 Update SDL_GameControllerDB 2022-08-10 19:35:58 -05:00
Cameron Gutman ef5605ae39 Update FFmpeg to 5.1 2022-08-10 00:14:53 -05:00
Cameron Gutman 9b29b2702e Update VCRedist and add a script to update it in the future 2022-08-09 20:09:12 -05:00
Jorys Paulin abafa270e5 Translated using Weblate (French)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2022-08-06 14:19:54 +02:00
Cameron Gutman 15852ccf81 Update dependencies
SDL -> 602b7dd0f4
OpenSSL -> 1.1.1q
SDL_ttf -> 2.20.0
2022-08-02 00:38:33 -05:00
Kirill Korolev a427ecd029 Translated using Weblate (Russian)
Currently translated at 98.9% (190 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2022-07-22 18:18:21 +02:00
Wen-haur Chiu 77228dcb2c Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2022-07-21 14:19:41 +02:00
Cameron Gutman 3948cfe495 Update libva to 2.15 in AppImage 2022-07-02 13:05:47 -05:00
Cameron Gutman 36218b54f8 Don't hardcode /dev/dri/card0 and /dev/dri/renderD128 2022-06-30 19:25:33 -05:00
Cameron Gutman be2f4433db Avoid hardcoding VIDEO_FORMAT_H265_MAIN10 for HDR/10-bit color 2022-06-26 16:22:05 -05:00
Artur Szulc dd5d36cf07 Translated using Weblate (Polish)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/pl/
2022-06-20 23:14:50 +02:00
Adam Lantos 685136d98f Make drmDropMaster() more reliable by checking for DRM master fds.
This should fix the majority of EACCES permission denied errors
when attempting to drop the DRM master.
2022-06-12 12:29:13 -07:00
Mattia Rombi 852937421e Translated using Weblate (Italian)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/it/
2022-06-07 11:15:33 +02:00
Jorys Paulin a5389b7be5 Translated using Weblate (French)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2022-05-31 10:15:58 +02:00
Cameron Gutman 1b96b18bc0 Fix handling of colorspace changes when using CUDA+SDL 2022-05-28 22:55:55 -05:00
Cameron Gutman e9edad6f8c Fix surface leak if SDLVideo_CreateOverlay() fails 2022-05-28 22:48:49 -05:00
Cameron Gutman f8a6012c3a Improve overlay format handling 2022-05-28 22:48:23 -05:00
Cameron Gutman e103f6c9b6 Avoid holding the overlay lock during vaPutSurface 2022-05-28 22:28:17 -05:00
Cameron Gutman 9daf7b5a36 Fix modesetting for non-EGL HEVC streams 2022-05-28 21:42:27 -05:00
Mattia Rombi d6142e1af0 Translated using Weblate (Italian)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/it/
2022-05-28 12:14:50 +02:00
Jorys Paulin b0491210ff Translated using Weblate (French)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2022-05-28 12:14:50 +02:00
Mattia Rombi 91d8550290 Translated using Weblate (Italian)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/it/
2022-05-26 19:36:07 +02:00
sanhoe dfaaa7d72a Translated using Weblate (Korean)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2022-05-26 19:15:29 +02:00
Dominik Chrástecký 5174a4f58a Translated using Weblate (Czech)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/cs/
2022-05-25 14:20:23 +02:00
178 changed files with 17118 additions and 9996 deletions
+12 -14
View File
@@ -1,6 +1,6 @@
# Moonlight PC
[Moonlight PC](https://moonlight-stream.org) is an open source PC client for NVIDIA GameStream, as used by the NVIDIA Shield.
[Moonlight PC](https://moonlight-stream.org) is an open source PC client for NVIDIA GameStream and [Sunshine](https://github.com/LizardByte/Sunshine).
Moonlight also has mobile versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [iOS](https://github.com/moonlight-stream/moonlight-ios).
@@ -12,15 +12,13 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
## Features
- Hardware accelerated video decoding on Windows, Mac, and Linux
- Supports streaming at up to 120 FPS (high refresh rate monitor recommended)
- Supports streaming at 720p, 1080p, 1440p, 4K, and the client PC's native screen resolution
- H.264, HEVC, and AV1 codec support (AV1 requires Sunshine and a supported host GPU)
- HDR streaming support
- 7.1 surround sound audio support
- 10-point multitouch support (Sunshine only)
- Gamepad support with force feedback and motion controls for up to 16 players
- Support for both pointer capture (for games) and direct mouse control (for remote desktop)
- Support for passing system-wide keyboard shortcuts like Alt+Tab to the host
- Direct touchscreen mouse control
- HEVC support for better image quality at reduced bandwidth
- Gamepad support with force feedback for up to 4 players
## Downloads
- [Windows, macOS, and Steam Link](https://github.com/moonlight-stream/moonlight-qt/releases)
@@ -41,24 +39,23 @@ Hosting for Moonlight's Raspberry Pi and L4T package repositories is graciously
### Windows Build Requirements
* Qt 5.15 SDK or later
* Windows 7 or later
* [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) (Community edition is fine)
* Select **MSVC 2019** option during Qt installation. MinGW is not 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)
* [WiX Toolset](https://wixtoolset.org/releases/v3-14-0-5722/) v3.14.0.5722 or later (only if building installers for non-development PCs)
### macOS Build Requirements
* Qt 6.2 SDK or later
* macOS Mojave (10.14) or later
* Xcode 13
* 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
* GCC or Clang
* 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 qt5-default 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`
* 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+ 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/
* 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/
### Steam Link Build Requirements
* [Steam Link SDK](https://github.com/ValveSoftware/steamlink-sdk) cloned on your build system
@@ -72,12 +69,13 @@ Hosting for Moonlight's Raspberry Pi and L4T package repositories is graciously
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 WiX and 7-Zip binary directories are in your `%PATH%`.
* 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, run `qmake moonlight-qt.pro` then `make debug` or `make release`
* 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.
* 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.
## Contribute
1. Fork us
+17 -1
View File
@@ -5,7 +5,7 @@
<key>CFBundleExecutable</key>
<string>Moonlight</string>
<key>CFBundleGetInfoString</key>
<string>Stream games from your NVIDIA GameStream-enabled PC</string>
<string>Stream games and other applications from another PC</string>
<key>CFBundleIconFile</key>
<string>moonlight</string>
<key>CFBundleIdentifier</key>
@@ -22,6 +22,22 @@
<string>NSApplication</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
<key>GCSupportedGameControllers</key>
<array>
<dict>
<key>ProfileName</key>
<string>ExtendedGamepad</string>
</dict>
</array>
<key>GCSupportsControllerUserInteraction</key>
<true/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.games</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>CFBundleVersion</key>
<string>VERSION</string>
<key>CFBundleShortVersionString</key>
+104 -47
View File
@@ -48,6 +48,9 @@ win32 {
INCLUDEPATH += $$PWD/../libs/windows/include
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib dbghelp.lib
# Work around a conflict with math.h inclusion between SDL and Qt 6
DEFINES += _USE_MATH_DEFINES
}
macx {
INCLUDEPATH += $$PWD/../libs/mac/include
@@ -65,51 +68,73 @@ unix:!macx {
CONFIG += link_pkgconfig
PKGCONFIG += openssl sdl2 SDL2_ttf opus
packagesExist(libavcodec) {
PKGCONFIG += libavcodec libavutil
CONFIG += ffmpeg
!disable-ffmpeg {
packagesExist(libavcodec) {
PKGCONFIG += libavcodec libavutil
CONFIG += ffmpeg
packagesExist(libva) {
packagesExist(libva-x11) {
CONFIG += libva-x11
!disable-libva {
packagesExist(libva) {
!disable-x11 {
packagesExist(libva-x11) {
CONFIG += libva-x11
}
}
!disable-wayland {
packagesExist(libva-wayland) {
CONFIG += libva-wayland
}
}
!disable-libdrm {
packagesExist(libva-drm) {
CONFIG += libva-drm
}
}
CONFIG += libva
}
}
packagesExist(libva-wayland) {
CONFIG += libva-wayland
!disable-libvdpau {
packagesExist(vdpau) {
CONFIG += libvdpau
}
}
packagesExist(libva-drm) {
CONFIG += libva-drm
!disable-mmal {
packagesExist(mmal) {
PKGCONFIG += mmal
CONFIG += mmal
}
}
!disable-libdrm {
packagesExist(libdrm) {
PKGCONFIG += libdrm
CONFIG += libdrm
}
}
!disable-cuda {
packagesExist(ffnvcodec) {
PKGCONFIG += ffnvcodec
CONFIG += cuda
}
}
CONFIG += libva
}
packagesExist(vdpau) {
CONFIG += libvdpau
!disable-wayland {
packagesExist(wayland-client) {
CONFIG += wayland
PKGCONFIG += wayland-client
}
}
packagesExist(mmal) {
PKGCONFIG += mmal
CONFIG += mmal
!disable-x11 {
packagesExist(x11) {
DEFINES += HAS_X11
PKGCONFIG += x11
}
}
packagesExist(libdrm) {
PKGCONFIG += libdrm
CONFIG += libdrm
}
packagesExist(ffnvcodec) {
PKGCONFIG += ffnvcodec
CONFIG += cuda
}
}
packagesExist(wayland-client) {
DEFINES += HAS_WAYLAND
PKGCONFIG += wayland-client
}
packagesExist(x11) {
DEFINES += HAS_X11
PKGCONFIG += x11
}
}
win32 {
@@ -120,7 +145,7 @@ win32:!winrt {
CONFIG += soundio discord-rpc
}
macx {
LIBS += -lssl -lcrypto -lavcodec.59 -lavutil.57 -lopus -framework SDL2 -framework SDL2_ttf
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
# For libsoundio
@@ -132,6 +157,7 @@ macx {
SOURCES += \
backend/nvaddress.cpp \
backend/nvapp.cpp \
cli/pair.cpp \
main.cpp \
backend/computerseeker.cpp \
backend/identitymanager.cpp \
@@ -142,6 +168,7 @@ SOURCES += \
backend/boxartmanager.cpp \
backend/richpresencemanager.cpp \
cli/commandlineparser.cpp \
cli/listapps.cpp \
cli/quitstream.cpp \
cli/startstream.cpp \
settings/compatfetcher.cpp \
@@ -170,6 +197,7 @@ SOURCES += \
HEADERS += \
backend/nvaddress.h \
backend/nvapp.h \
cli/pair.h \
settings/compatfetcher.h \
settings/mappingfetcher.h \
utils.h \
@@ -182,6 +210,7 @@ HEADERS += \
backend/boxartmanager.h \
backend/richpresencemanager.h \
cli/commandlineparser.h \
cli/listapps.h \
cli/quitstream.h \
cli/startstream.h \
settings/streamingpreferences.h \
@@ -208,15 +237,15 @@ ffmpeg {
SOURCES += \
streaming/video/ffmpeg.cpp \
streaming/video/ffmpeg-renderers/sdlvid.cpp \
streaming/video/ffmpeg-renderers/pacer/pacer.cpp \
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.cpp
streaming/video/ffmpeg-renderers/swframemapper.cpp \
streaming/video/ffmpeg-renderers/pacer/pacer.cpp
HEADERS += \
streaming/video/ffmpeg.h \
streaming/video/ffmpeg-renderers/renderer.h \
streaming/video/ffmpeg-renderers/sdlvid.h \
streaming/video/ffmpeg-renderers/pacer/pacer.h \
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.h
streaming/video/ffmpeg-renderers/swframemapper.h \
streaming/video/ffmpeg-renderers/pacer/pacer.h
}
libva {
message(VAAPI renderer selected)
@@ -257,6 +286,18 @@ mmal {
DEFINES += HAVE_MMAL
SOURCES += streaming/video/ffmpeg-renderers/mmal.cpp
HEADERS += streaming/video/ffmpeg-renderers/mmal.h
# We suppress EGL usage when MMAL is available because MMAL has
# 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
# EGL being preferred if direct DRM rendering is available.
allow-egl-with-mmal {
message(Allowing EGL usage with MMAL enabled)
DEFINES += ALLOW_EGL_WITH_MMAL
}
}
libdrm {
message(DRM renderer selected)
@@ -267,7 +308,7 @@ libdrm {
linux {
message(Master hooks enabled)
SOURCES += masterhook.c
SOURCES += masterhook.c masterhook_internal.c
LIBS += -ldl
}
}
@@ -285,8 +326,11 @@ config_EGL {
DEFINES += HAVE_EGL
SOURCES += \
streaming/video/ffmpeg-renderers/eglvid.cpp \
streaming/video/ffmpeg-renderers/egl_extensions.cpp
HEADERS += streaming/video/ffmpeg-renderers/eglvid.h
streaming/video/ffmpeg-renderers/egl_extensions.cpp \
streaming/video/ffmpeg-renderers/eglimagefactory.cpp
HEADERS += \
streaming/video/ffmpeg-renderers/eglvid.h \
streaming/video/ffmpeg-renderers/eglimagefactory.h
}
config_SL {
message(Steam Link build configuration selected)
@@ -344,13 +388,25 @@ embedded {
DEFINES += EMBEDDED_BUILD
}
glslow {
message(GL slow build)
DEFINES += GL_IS_SLOW
}
wayland {
message(Wayland extensions enabled)
DEFINES += HAS_WAYLAND
SOURCES += streaming/video/ffmpeg-renderers/pacer/waylandvsyncsource.cpp
HEADERS += streaming/video/ffmpeg-renderers/pacer/waylandvsyncsource.h
}
RESOURCES += \
resources.qrc \
qml.qrc
TRANSLATIONS += \
languages/qml_zh_cn.ts \
languages/qml_zh_CN.ts \
languages/qml_de.ts \
languages/qml_fr.ts \
languages/qml_nb_NO.ts \
@@ -365,14 +421,15 @@ TRANSLATIONS += \
languages/qml_sv.ts \
languages/qml_tr.ts \
languages/qml_uk.ts \
languages/qml_zh_Hant.ts \
languages/qml_zh_TW.ts \
languages/qml_el.ts \
languages/qml_hi.ts \
languages/qml_it.ts \
languages/qml_pt.ts \
languages/qml_pt_BR.ts \
languages/qml_pl.ts \
languages/qml_cs.ts
languages/qml_cs.ts \
languages/qml_he.ts
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
+19 -8
View File
@@ -6,16 +6,17 @@
#include <QJsonObject>
AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
QObject(parent),
m_Nam(this)
QObject(parent)
{
m_Nam = new QNetworkAccessManager(this);
// Never communicate over HTTP
m_Nam.setStrictTransportSecurityEnabled(true);
m_Nam->setStrictTransportSecurityEnabled(true);
// Allow HTTP redirects
m_Nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
m_Nam->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
connect(&m_Nam, &QNetworkAccessManager::finished,
connect(m_Nam, &QNetworkAccessManager::finished,
this, &AutoUpdateChecker::handleUpdateCheckRequestFinished);
QString currentVersion(VERSION_STR);
@@ -28,12 +29,17 @@ AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
void AutoUpdateChecker::start()
{
if (!m_Nam) {
Q_ASSERT(m_Nam);
return;
}
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) || defined(STEAM_LINK) || defined(APP_IMAGE) // Only run update checker on platforms without auto-update
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && QT_VERSION < QT_VERSION_CHECK(5, 15, 1) && !defined(QT_NO_BEARERMANAGEMENT)
// HACK: Set network accessibility to work around QTBUG-80947 (introduced in Qt 5.14.0 and fixed in Qt 5.15.1)
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
m_Nam.setNetworkAccessible(QNetworkAccessManager::Accessible);
m_Nam->setNetworkAccessible(QNetworkAccessManager::Accessible);
QT_WARNING_POP
#endif
@@ -45,7 +51,7 @@ void AutoUpdateChecker::start()
#else
request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
#endif
m_Nam.get(request);
m_Nam->get(request);
#endif
}
@@ -102,6 +108,11 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
{
Q_ASSERT(reply->isFinished());
// Delete the QNetworkAccessManager to free resources and
// prevent the bearer plugin from polling in the background.
m_Nam->deleteLater();
m_Nam = nullptr;
if (reply->error() == QNetworkReply::NoError) {
QTextStream stream(reply);
@@ -201,7 +212,7 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
<< QSysInfo::buildCpuArchitecture() << getPlatform() << QSysInfo::kernelVersion();
}
else {
qWarning() << "Update checking failed with error: " << reply->error();
qWarning() << "Update checking failed with error:" << reply->error();
reply->deleteLater();
}
}
+1 -1
View File
@@ -25,5 +25,5 @@ private:
QString getPlatform();
QVector<int> m_CurrentVersionQuad;
QNetworkAccessManager m_Nam;
QNetworkAccessManager* m_Nam;
};
+199 -41
View File
@@ -10,7 +10,10 @@
#include <QThreadPool>
#include <QCoreApplication>
#include <random>
#define SER_HOSTS "hosts"
#define SER_HOSTS_BACKUP "hostsbackup"
class PcMonitorThread : public QThread
{
@@ -42,7 +45,7 @@ private:
// Ensure the machine that responded is the one we intended to contact
if (m_Computer->uuid != newState.uuid) {
qInfo() << "Found unexpected PC " << newState.name << " looking for " << m_Computer->name;
qInfo() << "Found unexpected PC" << newState.name << "looking for" << m_Computer->name;
return false;
}
@@ -145,25 +148,39 @@ ComputerManager::ComputerManager(QObject *parent)
: QObject(parent),
m_PollingRef(0),
m_MdnsBrowser(nullptr),
m_CompatFetcher(nullptr)
m_CompatFetcher(nullptr),
m_NeedsDelayedFlush(false)
{
QSettings settings;
// If there's a hosts backup copy, we must have failed to commit
// a previous update before exiting. Restore the backup now.
int hosts = settings.beginReadArray(SER_HOSTS_BACKUP);
if (hosts == 0) {
// If there's no host backup, read from the primary location.
settings.endArray();
hosts = settings.beginReadArray(SER_HOSTS);
}
// Inflate our hosts from QSettings
int hosts = settings.beginReadArray(SER_HOSTS);
for (int i = 0; i < hosts; i++) {
settings.setArrayIndex(i);
NvComputer* computer = new NvComputer(settings);
m_KnownHosts[computer->uuid] = computer;
m_LastSerializedHosts[computer->uuid] = *computer;
}
settings.endArray();
// Fetch latest compatibility data asynchronously
m_CompatFetcher.start();
// Start the delayed flush thread to handle saveHosts() calls
m_DelayedFlushThread = new DelayedFlushThread(this);
m_DelayedFlushThread->start();
// To quit in a timely manner, we must block additional requests
// after we receive the aboutToQuit() signal. This is neccessary
// because NvHTTP uses aboutToQuit() to abort requests in progres
// after we receive the aboutToQuit() signal. This is necessary
// because NvHTTP uses aboutToQuit() to abort requests in progress
// while quitting, however this is a one time signal - additional
// requests would not be aborted and block termination.
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &ComputerManager::handleAboutToQuit);
@@ -171,6 +188,21 @@ ComputerManager::ComputerManager(QObject *parent)
ComputerManager::~ComputerManager()
{
// Stop the delayed flush thread before acquiring the lock in write mode
// to avoid deadlocking with a flush that needs the lock in read mode.
{
// Wake the delayed flush thread
m_DelayedFlushThread->requestInterruption();
m_DelayedFlushCondition.wakeOne();
// Wait for it to terminate (and finish any pending flush)
m_DelayedFlushThread->wait();
delete m_DelayedFlushThread;
// Delayed flushes should have completed by now
Q_ASSERT(!m_NeedsDelayedFlush);
}
QWriteLocker lock(&m_Lock);
// Delete machines that haven't been resolved yet
@@ -200,19 +232,80 @@ ComputerManager::~ComputerManager()
}
}
void DelayedFlushThread::run() {
for (;;) {
// Wait for a delayed flush request or an interruption
{
QMutexLocker locker(&m_ComputerManager->m_DelayedFlushMutex);
while (!QThread::currentThread()->isInterruptionRequested() && !m_ComputerManager->m_NeedsDelayedFlush) {
m_ComputerManager->m_DelayedFlushCondition.wait(&m_ComputerManager->m_DelayedFlushMutex);
}
// Bail without flushing if we woke up for an interruption alone.
// If we have both an interruption and a flush request, do the flush.
if (!m_ComputerManager->m_NeedsDelayedFlush) {
Q_ASSERT(QThread::currentThread()->isInterruptionRequested());
break;
}
// Reset the delayed flush flag to ensure any racing saveHosts() call will set it again
m_ComputerManager->m_NeedsDelayedFlush = false;
// Update the last serialized hosts map under the delayed flush mutex
m_ComputerManager->m_LastSerializedHosts.clear();
for (const NvComputer* computer : m_ComputerManager->m_KnownHosts) {
// Copy the current state of the NvComputer to allow us to check later if we need
// to serialize it again when attribute updates occur.
QReadLocker computerLock(&computer->lock);
m_ComputerManager->m_LastSerializedHosts[computer->uuid] = *computer;
}
}
// Perform the flush
{
QSettings settings;
// First, write to the backup location
settings.beginWriteArray(SER_HOSTS_BACKUP);
{
QReadLocker lock(&m_ComputerManager->m_Lock);
int i = 0;
for (const NvComputer* computer : m_ComputerManager->m_KnownHosts) {
settings.setArrayIndex(i++);
computer->serialize(settings, false);
}
}
settings.endArray();
// Next, write to the primary location
settings.remove(SER_HOSTS);
settings.beginWriteArray(SER_HOSTS);
{
QReadLocker lock(&m_ComputerManager->m_Lock);
int i = 0;
for (const NvComputer* computer : m_ComputerManager->m_KnownHosts) {
settings.setArrayIndex(i++);
computer->serialize(settings, true);
}
}
settings.endArray();
// Finally, delete the backup copy
settings.remove(SER_HOSTS_BACKUP);
}
}
}
void ComputerManager::saveHosts()
{
QSettings settings;
QReadLocker lock(&m_Lock);
Q_ASSERT(m_DelayedFlushThread != nullptr && m_DelayedFlushThread->isRunning());
settings.remove(SER_HOSTS);
settings.beginWriteArray(SER_HOSTS);
int i = 0;
for (const NvComputer* computer : m_KnownHosts) {
settings.setArrayIndex(i++);
computer->serialize(settings);
}
settings.endArray();
// Punt to a worker thread because QSettings on macOS can take ages (> 500 ms)
// to persist our host list to disk (especially when a host has a bunch of apps).
QMutexLocker locker(&m_DelayedFlushMutex);
m_NeedsDelayedFlush = true;
m_DelayedFlushCondition.wakeOne();
}
QHostAddress ComputerManager::getBestGlobalAddressV6(QVector<QHostAddress> &addresses)
@@ -263,12 +356,13 @@ void ComputerManager::startPolling()
if (prefs.enableMdns) {
// Start an MDNS query for GameStream hosts
m_MdnsBrowser = new QMdnsEngine::Browser(&m_MdnsServer, "_nvstream._tcp.local.", &m_MdnsCache);
m_MdnsServer.reset(new QMdnsEngine::Server());
m_MdnsBrowser = new QMdnsEngine::Browser(m_MdnsServer.data(), "_nvstream._tcp.local.");
connect(m_MdnsBrowser, &QMdnsEngine::Browser::serviceAdded,
this, [this](const QMdnsEngine::Service& service) {
qInfo() << "Discovered mDNS host:" << service.hostname();
MdnsPendingComputer* pendingComputer = new MdnsPendingComputer(&m_MdnsServer, service);
MdnsPendingComputer* pendingComputer = new MdnsPendingComputer(m_MdnsServer, service);
connect(pendingComputer, &MdnsPendingComputer::resolvedHost,
this, &ComputerManager::handleMdnsServiceResolved);
m_PendingResolution.append(pendingComputer);
@@ -349,6 +443,19 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
computer->deleteLater();
}
void ComputerManager::saveHost(NvComputer *computer)
{
// If no serializable properties changed, don't bother saving hosts
QMutexLocker lock(&m_DelayedFlushMutex);
QReadLocker computerLock(&computer->lock);
if (!m_LastSerializedHosts.value(computer->uuid).isEqualSerialized(*computer)) {
// Queue a request for a delayed flush to QSettings outside of the lock
computerLock.unlock();
lock.unlock();
saveHosts();
}
}
void ComputerManager::handleComputerStateChanged(NvComputer* computer)
{
emit computerStateChanged(computer);
@@ -358,15 +465,20 @@ void ComputerManager::handleComputerStateChanged(NvComputer* computer)
emit quitAppCompleted(QVariant());
}
// Save updated hosts to QSettings
saveHosts();
// Save updates to this host
saveHost(computer);
}
QVector<NvComputer*> ComputerManager::getComputers()
{
QReadLocker lock(&m_Lock);
return QVector<NvComputer*>::fromList(m_KnownHosts.values());
// Return a sorted host list
auto hosts = QVector<NvComputer*>::fromList(m_KnownHosts.values());
std::stable_sort(hosts.begin(), hosts.end(), [](const NvComputer* host1, const NvComputer* host2) {
return host1->name.toLower() < host2->name.toLower();
});
return hosts;
}
class DeferredHostDeletionTask : public QRunnable
@@ -390,7 +502,7 @@ public:
m_ComputerManager->m_KnownHosts.remove(m_Computer->uuid);
}
// Persist the new host list
// Persist the new host list with this computer deleted
m_ComputerManager->saveHosts();
// Delete the polling entry first. This will stop all polling threads too.
@@ -431,16 +543,13 @@ void ComputerManager::renameHost(NvComputer* computer, QString name)
void ComputerManager::clientSideAttributeUpdated(NvComputer* computer)
{
// Persist the change
saveHosts();
// Notify the UI of the state change
handleComputerStateChanged(computer);
}
void ComputerManager::handleAboutToQuit()
{
QWriteLocker lock(&m_Lock);
QReadLocker lock(&m_Lock);
// Interrupt polling threads immediately, so they
// avoid making additional requests while quitting
@@ -455,7 +564,8 @@ class PendingPairingTask : public QObject, public QRunnable
public:
PendingPairingTask(ComputerManager* computerManager, NvComputer* computer, QString pin)
: m_Computer(computer),
: m_ComputerManager(computerManager),
m_Computer(computer),
m_Pin(pin)
{
connect(this, &PendingPairingTask::pairingCompleted,
@@ -475,25 +585,34 @@ private:
switch (result)
{
case NvPairingManager::PairState::PIN_WRONG:
emit pairingCompleted(m_Computer, "The PIN from the PC didn't match. Please try again.");
emit pairingCompleted(m_Computer, tr("The PIN from the PC didn't match. Please try again."));
break;
case NvPairingManager::PairState::FAILED:
emit pairingCompleted(m_Computer, "Pairing failed. Please try again.");
if (m_Computer->currentGameId != 0) {
emit pairingCompleted(m_Computer, tr("You cannot pair while a previous session is still running on the host PC. Quit any running games or reboot the host PC, then try pairing again."));
}
else {
emit pairingCompleted(m_Computer, tr("Pairing failed. Please try again."));
}
break;
case NvPairingManager::PairState::ALREADY_IN_PROGRESS:
emit pairingCompleted(m_Computer, "Another pairing attempt is already in progress.");
emit pairingCompleted(m_Computer, tr("Another pairing attempt is already in progress."));
break;
case NvPairingManager::PairState::PAIRED:
// Persist the newly pinned server certificate for this host
m_ComputerManager->saveHost(m_Computer);
emit pairingCompleted(m_Computer, nullptr);
break;
}
} catch (const GfeHttpResponseException& e) {
emit pairingCompleted(m_Computer, "GeForce Experience returned error: " + e.toQString());
emit pairingCompleted(m_Computer, tr("GeForce Experience returned error: %1").arg(e.toQString()));
} catch (const QtNetworkReplyException& e) {
emit pairingCompleted(m_Computer, e.toQString());
}
}
ComputerManager* m_ComputerManager;
NvComputer* m_Computer;
QString m_Pin;
};
@@ -580,9 +699,10 @@ void ComputerManager::stopPollingAsync()
m_PendingResolution.removeFirst();
}
// Delete the browser to stop discovery
// Delete the browser and server to stop discovery and refresh polling
delete m_MdnsBrowser;
m_MdnsBrowser = nullptr;
m_MdnsServer.reset();
// Interrupt all threads, but don't wait for them to terminate
for (ComputerPollingEntry* entry : m_PollEntries) {
@@ -592,8 +712,8 @@ void ComputerManager::stopPollingAsync()
void ComputerManager::addNewHostManually(QString address)
{
QUrl url = QUrl::fromUserInput(address);
if (url.isValid() && !url.host().isEmpty()) {
QUrl url = QUrl::fromUserInput("moonlight://" + address);
if (url.isValid() && !url.host().isEmpty() && url.scheme() == "moonlight") {
// If there wasn't a port specified, use the default
addNewHost(NvAddress(url.host(), url.port(DEFAULT_HTTP_PORT)), false);
}
@@ -611,12 +731,15 @@ public:
: m_ComputerManager(computerManager),
m_Address(address),
m_MdnsIpv6Address(mdnsIpv6Address),
m_Mdns(mdns)
m_Mdns(mdns),
m_AboutToQuit(false)
{
connect(this, &PendingAddTask::computerAddCompleted,
computerManager, &ComputerManager::computerAddCompleted);
connect(this, &PendingAddTask::computerStateChanged,
computerManager, &ComputerManager::handleComputerStateChanged);
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
this, &PendingAddTask::handleAboutToQuit);
}
signals:
@@ -625,9 +748,20 @@ signals:
void computerStateChanged(NvComputer* computer);
private:
void handleAboutToQuit()
{
m_AboutToQuit = true;
}
QString fetchServerInfo(NvHTTP& http)
{
QString serverInfo;
// Do nothing if we're quitting
if (m_AboutToQuit) {
return QString();
}
try {
// There's a race condition between GameStream servers reporting presence over
// mDNS and the HTTPS server being ready to respond to our queries. To work
@@ -749,16 +883,31 @@ private:
hostAddress.isInSubnet(QHostAddress("192.168.0.0"), 16);
{
// Check if this PC already exists
QWriteLocker lock(&m_ComputerManager->m_Lock);
// Check if this PC already exists using opportunistic read lock
m_ComputerManager->m_Lock.lockForRead();
NvComputer* existingComputer = m_ComputerManager->m_KnownHosts.value(newComputer->uuid);
// If it doesn't already exist, convert to a write lock in preparation for updating.
//
// NB: ComputerManager's lock protects the host map itself, not the elements inside.
// Those are protected by their individual locks. Since we only mutate the map itself
// when the PC doesn't exist, we need the lock in write-mode for that case only.
if (existingComputer == nullptr) {
m_ComputerManager->m_Lock.unlock();
m_ComputerManager->m_Lock.lockForWrite();
// Since we had to unlock to lock for write, someone could have raced and added
// this PC before us. We have to check again whether it already exists.
existingComputer = m_ComputerManager->m_KnownHosts.value(newComputer->uuid);
}
if (existingComputer != nullptr) {
// Fold it into the existing PC
bool changed = existingComputer->update(*newComputer);
delete newComputer;
// Drop the lock before notifying
lock.unlock();
m_ComputerManager->m_Lock.unlock();
// For non-mDNS clients, let them know it succeeded
if (!m_Mdns) {
@@ -779,17 +928,15 @@ private:
m_ComputerManager->startPollingComputer(newComputer);
// Drop the lock before notifying
lock.unlock();
m_ComputerManager->m_Lock.unlock();
// If this wasn't added via mDNS but it is a RFC 1918 IPv4 address and not a VPN,
// go ahead and do the STUN request now to populate an external address.
if (!m_Mdns && addressIsSiteLocalV4 && !newComputer->isReachableOverVpn()) {
if (!m_Mdns && addressIsSiteLocalV4 && newComputer->getActiveAddressReachability() != NvComputer::RI_VPN) {
quint32 addr;
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
if (err == 0) {
lock.relock();
newComputer->setRemoteAddress(QHostAddress(qFromBigEndian(addr)));
lock.unlock();
}
else {
qWarning() << "STUN failed to get WAN address:" << err;
@@ -811,6 +958,7 @@ private:
NvAddress m_Address;
NvAddress m_MdnsIpv6Address;
bool m_Mdns;
bool m_AboutToQuit;
};
void ComputerManager::addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address)
@@ -821,4 +969,14 @@ void ComputerManager::addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv
QThreadPool::globalInstance()->start(addTask);
}
// TODO: Use QRandomGenerator when we drop Qt 5.9 support
QString ComputerManager::generatePinString()
{
std::uniform_int_distribution<int> dist(0, 9999);
std::random_device rd;
std::mt19937 engine(rd());
return QString::asprintf("%04u", dist(engine));
}
#include "computermanager.moc"
+51 -6
View File
@@ -15,17 +15,38 @@
#include <QSettings>
#include <QRunnable>
#include <QTimer>
#include <QMutex>
#include <QWaitCondition>
class ComputerManager;
class DelayedFlushThread : public QThread
{
Q_OBJECT
public:
DelayedFlushThread(ComputerManager* cm)
: m_ComputerManager(cm)
{
setObjectName("CM Delayed Flush Thread");
}
void run();
private:
ComputerManager* m_ComputerManager;
};
class MdnsPendingComputer : public QObject
{
Q_OBJECT
public:
explicit MdnsPendingComputer(QMdnsEngine::Server* server,
explicit MdnsPendingComputer(const QSharedPointer<QMdnsEngine::Server> server,
const QMdnsEngine::Service& service)
: m_Hostname(service.hostname()),
m_Port(service.port()),
m_Server(server),
m_ServerWeak(server),
m_Resolver(nullptr)
{
// Start resolving
@@ -73,8 +94,21 @@ signals:
private:
void resolve()
{
// Delete our resolver, so we're guaranteed that nothing is referencing m_Server.
delete m_Resolver;
m_Resolver = new QMdnsEngine::Resolver(m_Server, m_Hostname);
m_Resolver = nullptr;
// 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();
// Re-acquire a strong reference if the server still exists.
m_Server = m_ServerWeak.toStrongRef();
if (!m_Server) {
return;
}
m_Resolver = new QMdnsEngine::Resolver(m_Server.data(), m_Hostname);
connect(m_Resolver, &QMdnsEngine::Resolver::resolved,
this, &MdnsPendingComputer::handleResolvedAddress);
QTimer::singleShot(2000, this, &MdnsPendingComputer::handleResolvedTimeout);
@@ -82,7 +116,8 @@ private:
QByteArray m_Hostname;
uint16_t m_Port;
QMdnsEngine::Server* m_Server;
QWeakPointer<QMdnsEngine::Server> m_ServerWeak;
QSharedPointer<QMdnsEngine::Server> m_Server;
QMdnsEngine::Resolver* m_Resolver;
QVector<QHostAddress> m_Addresses;
};
@@ -166,6 +201,8 @@ class ComputerManager : public QObject
friend class DeferredHostDeletionTask;
friend class PendingAddTask;
friend class PendingPairingTask;
friend class DelayedFlushThread;
public:
explicit ComputerManager(QObject *parent = nullptr);
@@ -180,6 +217,8 @@ public:
void addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address = NvAddress());
QString generatePinString();
void pairHost(NvComputer* computer, QString pin);
void quitRunningApp(NvComputer* computer);
@@ -212,6 +251,8 @@ private slots:
private:
void saveHosts();
void saveHost(NvComputer* computer);
QHostAddress getBestGlobalAddressV6(QVector<QHostAddress>& addresses);
void startPollingComputer(NvComputer* computer);
@@ -220,9 +261,13 @@ private:
QReadWriteLock m_Lock;
QMap<QString, NvComputer*> m_KnownHosts;
QMap<QString, ComputerPollingEntry*> m_PollEntries;
QMdnsEngine::Server m_MdnsServer;
QHash<QString, NvComputer> m_LastSerializedHosts; // Protected by m_DelayedFlushMutex
QSharedPointer<QMdnsEngine::Server> m_MdnsServer;
QMdnsEngine::Browser* m_MdnsBrowser;
QMdnsEngine::Cache m_MdnsCache;
QVector<MdnsPendingComputer*> m_PendingResolution;
CompatFetcher m_CompatFetcher;
DelayedFlushThread* m_DelayedFlushThread;
QMutex m_DelayedFlushMutex; // Lock ordering: Must never be acquired while holding NvComputer lock
QWaitCondition m_DelayedFlushCondition;
bool m_NeedsDelayedFlush;
};
+146 -54
View File
@@ -21,6 +21,7 @@
#define SER_APPLIST "apps"
#define SER_SRVCERT "srvcert"
#define SER_CUSTOMNAME "customname"
#define SER_NVIDIASOFTWARE "nvidiasw"
NvComputer::NvComputer(QSettings& settings)
{
@@ -37,6 +38,7 @@ NvComputer::NvComputer(QSettings& settings)
this->manualAddress = NvAddress(settings.value(SER_MANUALADDR).toString(),
settings.value(SER_MANUALPORT, QVariant(DEFAULT_HTTP_PORT)).toUInt());
this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray());
this->isNvidiaServerSoftware = settings.value(SER_NVIDIASOFTWARE).toBool();
int appCount = settings.beginReadArray(SER_APPLIST);
this->appList.reserve(appCount);
@@ -65,12 +67,14 @@ NvComputer::NvComputer(QSettings& settings)
void NvComputer::setRemoteAddress(QHostAddress address)
{
QWriteLocker lock(&this->lock);
Q_ASSERT(this->externalPort != 0);
this->remoteAddress = NvAddress(address, this->externalPort);
}
void NvComputer::serialize(QSettings& settings) const
void NvComputer::serialize(QSettings& settings, bool serializeApps) const
{
QReadLocker lock(&this->lock);
@@ -87,9 +91,10 @@ void NvComputer::serialize(QSettings& settings) const
settings.setValue(SER_MANUALADDR, manualAddress.address());
settings.setValue(SER_MANUALPORT, manualAddress.port());
settings.setValue(SER_SRVCERT, serverCert.toPem());
settings.setValue(SER_NVIDIASOFTWARE, isNvidiaServerSoftware);
// Avoid deleting an existing applist if we couldn't get one
if (!appList.isEmpty()) {
if (!appList.isEmpty() && serializeApps) {
settings.remove(SER_APPLIST);
settings.beginWriteArray(SER_APPLIST);
for (int i = 0; i < appList.count(); i++) {
@@ -100,6 +105,21 @@ void NvComputer::serialize(QSettings& settings) const
}
}
bool NvComputer::isEqualSerialized(const NvComputer &that) const
{
return this->name == that.name &&
this->hasCustomName == that.hasCustomName &&
this->uuid == that.uuid &&
this->macAddress == that.macAddress &&
this->localAddress == that.localAddress &&
this->remoteAddress == that.remoteAddress &&
this->ipv6Address == that.ipv6Address &&
this->manualAddress == that.manualAddress &&
this->serverCert == that.serverCert &&
this->isNvidiaServerSoftware == that.isNvidiaServerSoftware &&
this->appList == that.appList;
}
void NvComputer::sortAppList()
{
std::stable_sort(appList.begin(), appList.end(), [](const NvApp& app1, const NvApp& app2) {
@@ -131,7 +151,8 @@ NvComputer::NvComputer(NvHTTP& http, QString serverInfo)
this->serverCodecModeSupport = codecSupport.toInt();
}
else {
this->serverCodecModeSupport = 0;
// Assume H.264 is always supported
this->serverCodecModeSupport = SCM_H264;
}
QString maxLumaPixelsHEVC = NvHTTP::getXmlString(serverInfo, "MaxLumaPixelsHEVC");
@@ -145,8 +166,8 @@ NvComputer::NvComputer(NvHTTP& http, QString serverInfo)
this->displayModes = NvHTTP::getDisplayModeList(serverInfo);
std::stable_sort(this->displayModes.begin(), this->displayModes.end(),
[](const NvDisplayMode& mode1, const NvDisplayMode& mode2) {
return mode1.width * mode1.height * mode1.refreshRate <
mode2.width * mode2.height * mode2.refreshRate;
return (uint64_t)mode1.width * mode1.height * mode1.refreshRate <
(uint64_t)mode2.width * mode2.height * mode2.refreshRate;
});
// We can get an IPv4 loopback address if we're using the GS IPv6 Forwarder
@@ -164,7 +185,7 @@ NvComputer::NvComputer(NvHTTP& http, QString serverInfo)
// to support dynamic HTTP WAN ports without requiring the user to manually enter the port.
QString remotePortStr = NvHTTP::getXmlString(serverInfo, "ExternalPort");
if (remotePortStr.isEmpty() || (this->externalPort = remotePortStr.toUShort()) == 0) {
this->externalPort = DEFAULT_HTTP_PORT;
this->externalPort = http.httpPort();
}
QString remoteAddress = NvHTTP::getXmlString(serverInfo, "ExternalIP");
@@ -175,6 +196,11 @@ NvComputer::NvComputer(NvHTTP& http, QString serverInfo)
this->remoteAddress = NvAddress();
}
// Real Nvidia host software (GeForce Experience and RTX Experience) both use the 'Mjolnir'
// codename in the state field and no version of Sunshine does. We can use this to bypass
// some assumptions about Nvidia hardware that don't apply to Sunshine hosts.
this->isNvidiaServerSoftware = NvHTTP::getXmlString(serverInfo, "state").contains("MJOLNIR");
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
PS_PAIRED : PS_NOT_PAIRED;
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
@@ -187,40 +213,52 @@ NvComputer::NvComputer(NvHTTP& http, QString serverInfo)
this->isSupportedServerVersion = CompatFetcher::isGfeVersionSupported(this->gfeVersion);
}
bool NvComputer::wake()
bool NvComputer::wake() const
{
if (state == NvComputer::CS_ONLINE) {
qWarning() << name << "is already online";
return true;
QByteArray wolPayload;
{
QReadLocker readLocker(&lock);
if (state == NvComputer::CS_ONLINE) {
qWarning() << name << "is already online";
return true;
}
if (macAddress.isEmpty()) {
qWarning() << name << "has no MAC address stored";
return false;
}
// Create the WoL payload
wolPayload.append(QByteArray::fromHex("FFFFFFFFFFFF"));
for (int i = 0; i < 16; i++) {
wolPayload.append(macAddress);
}
Q_ASSERT(wolPayload.size() == 102);
}
if (macAddress.isEmpty()) {
qWarning() << name << "has no MAC address stored";
return false;
}
const quint16 WOL_PORTS[] = {
// Ports used as-is
const quint16 STATIC_WOL_PORTS[] = {
9, // Standard WOL port (privileged port)
47998, 47999, 48000, 48002, 48010, // Ports opened by GFE
47009, // Port opened by Moonlight Internet Hosting Tool for WoL (non-privileged port)
};
// Create the WoL payload
QByteArray wolPayload;
wolPayload.append(QByteArray::fromHex("FFFFFFFFFFFF"));
for (int i = 0; i < 16; i++) {
wolPayload.append(macAddress);
}
Q_ASSERT(wolPayload.count() == 102);
// Ports offset by the HTTP base port for hosts using alternate ports
const quint16 DYNAMIC_WOL_PORTS[] = {
47998, 47999, 48000, 48002, 48010, // Ports opened by GFE
};
// Add the addresses that we know this host to be
// and broadcast addresses for this link just in
// case the host has timed out in ARP entries.
QVector<QString> addressList;
QMap<QString, quint16> addressMap;
QSet<quint16> basePortSet;
for (const NvAddress& addr : uniqueAddresses()) {
addressList.append(addr.address());
addressMap.insert(addr.address(), addr.port());
basePortSet.insert(addr.port());
}
addressList.append("255.255.255.255");
addressMap.insert("255.255.255.255", 0);
// Try to broadcast on all available NICs
for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) {
@@ -239,31 +277,42 @@ bool NvComputer::wake()
// Skip IPv6 which doesn't support broadcast
if (!addr.broadcast().isNull()) {
addressList.append(addr.broadcast().toString());
addressMap.insert(addr.broadcast().toString(), 0);
}
}
if (!allNodesMulticast.scopeId().isEmpty()) {
addressList.append(allNodesMulticast.toString());
addressMap.insert(allNodesMulticast.toString(), 0);
}
}
// Try all unique address strings or host names
bool success = false;
for (QString& addressString : addressList) {
QHostInfo hostInfo = QHostInfo::fromName(addressString);
for (auto i = addressMap.constBegin(); i != addressMap.constEnd(); i++) {
QHostAddress literalAddress;
QList<QHostAddress> addressList;
if (hostInfo.error() != QHostInfo::NoError) {
qWarning() << "Error resolving" << addressString << ":" << hostInfo.errorString();
continue;
// If this is an IPv4/IPv6 literal, don't use QHostInfo::fromName() because that will
// try to perform a reverse DNS lookup that leads to delays sending WoL packets.
if (literalAddress.setAddress(i.key())) {
addressList.append(literalAddress);
}
else {
QHostInfo hostInfo = QHostInfo::fromName(i.key());
if (hostInfo.error() != QHostInfo::NoError) {
qWarning() << "Error resolving" << i.key() << ":" << hostInfo.errorString();
continue;
}
addressList.append(hostInfo.addresses());
}
// Try all IP addresses that this string resolves to
for (QHostAddress& address : hostInfo.addresses()) {
for (QHostAddress& address : addressList) {
QUdpSocket sock;
// Send to all ports
for (quint16 port : WOL_PORTS) {
// Send to all static ports
for (quint16 port : STATIC_WOL_PORTS) {
if (sock.writeDatagram(wolPayload, address, port)) {
qInfo().nospace().noquote() << "Sent WoL packet to " << name << " via " << address.toString() << ":" << port;
success = true;
@@ -272,22 +321,56 @@ bool NvComputer::wake()
qWarning() << "Send failed:" << sock.error();
}
}
QList<quint16> basePorts;
if (i.value() != 0) {
// If we have a known base port for this address, use only that port
basePorts.append(i.value());
}
else {
// If this is a broadcast address without a known HTTP port, try all of them
basePorts.append(basePortSet.values());
}
// Send to all dynamic ports using the HTTP port offset(s) for this address
for (quint16 basePort : basePorts) {
for (quint16 port : DYNAMIC_WOL_PORTS) {
port = (port - 47989) + basePort;
if (sock.writeDatagram(wolPayload, address, port)) {
qInfo().nospace().noquote() << "Sent WoL packet to " << name << " via " << address.toString() << ":" << port;
success = true;
}
else {
qWarning() << "Send failed:" << sock.error();
}
}
}
}
}
return success;
}
bool NvComputer::isReachableOverVpn()
NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
{
if (activeAddress.isNull()) {
return false;
NvAddress copyOfActiveAddress;
{
QReadLocker readLocker(&lock);
if (activeAddress.isNull()) {
return ReachabilityType::RI_UNKNOWN;
}
// Grab a copy of the active address to avoid having to hold
// the computer lock while doing socket operations
copyOfActiveAddress = activeAddress;
}
QTcpSocket s;
s.setProxy(QNetworkProxy::NoProxy);
s.connectToHost(activeAddress.address(), activeAddress.port());
s.connectToHost(copyOfActiveAddress.address(), copyOfActiveAddress.port());
if (s.waitForConnected(3000)) {
Q_ASSERT(!s.localAddress().isNull());
@@ -308,48 +391,55 @@ bool NvComputer::isReachableOverVpn()
if (nic.type() == QNetworkInterface::Virtual ||
nic.type() == QNetworkInterface::Ppp) {
// Treat PPP and virtual interfaces as likely VPNs
return true;
return ReachabilityType::RI_VPN;
}
if (nic.maximumTransmissionUnit() != 0 && nic.maximumTransmissionUnit() < 1500) {
// Treat MTUs under 1500 as likely VPNs
return true;
return ReachabilityType::RI_VPN;
}
#endif
if (nic.flags() & QNetworkInterface::IsPointToPoint) {
// Treat point-to-point links as likely VPNs.
// This check detects OpenVPN on Unix-like OSes.
return true;
return ReachabilityType::RI_VPN;
}
if (nic.hardwareAddress().startsWith("00:FF", Qt::CaseInsensitive)) {
// OpenVPN TAP interfaces have a MAC address starting with 00:FF on Windows
return true;
return ReachabilityType::RI_VPN;
}
if (nic.humanReadableName().startsWith("ZeroTier")) {
// ZeroTier interfaces always start with "ZeroTier"
return true;
return ReachabilityType::RI_VPN;
}
if (nic.humanReadableName().contains("VPN")) {
// This one is just a final heuristic if all else fails
return true;
// This one is just a final VPN heuristic if all else fails
return ReachabilityType::RI_VPN;
}
// Didn't meet any of our VPN heuristics
return false;
// Didn't meet any of our VPN heuristics. Let's see if it's on-link.
Q_ASSERT(addr.prefixLength() >= 0);
if (addr.prefixLength() >= 0 && s.localAddress().isInSubnet(addr.ip(), addr.prefixLength())) {
return ReachabilityType::RI_LAN;
}
// Default to unknown if nothing else matched
return ReachabilityType::RI_UNKNOWN;
}
}
}
qWarning() << "No match found for address:" << s.localAddress();
return false;
return ReachabilityType::RI_UNKNOWN;
}
else {
// If we fail to connect, just pretend that it's not a VPN
return false;
qWarning() << "Unable to check for reachability within 3 seconds";
return ReachabilityType::RI_UNKNOWN;
}
}
@@ -375,6 +465,7 @@ bool NvComputer::updateAppList(QVector<NvApp> newAppList) {
QVector<NvAddress> NvComputer::uniqueAddresses() const
{
QReadLocker readLocker(&lock);
QVector<NvAddress> uniqueAddressList;
// Start with addresses correctly ordered
@@ -406,7 +497,7 @@ QVector<NvAddress> NvComputer::uniqueAddresses() const
return uniqueAddressList;
}
bool NvComputer::update(NvComputer& that)
bool NvComputer::update(const NvComputer& that)
{
bool changed = false;
@@ -456,6 +547,7 @@ bool NvComputer::update(NvComputer& that)
ASSIGN_IF_CHANGED(gfeVersion);
ASSIGN_IF_CHANGED(appVersion);
ASSIGN_IF_CHANGED(isSupportedServerVersion);
ASSIGN_IF_CHANGED(isNvidiaServerSoftware);
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
ASSIGN_IF_CHANGED(gpuModel);
ASSIGN_IF_CHANGED_AND_NONNULL(serverCert);
+37 -6
View File
@@ -8,6 +8,16 @@
#include <QSettings>
#include <QRunnable>
class CopySafeReadWriteLock : public QReadWriteLock
{
public:
CopySafeReadWriteLock() = default;
// Don't actually copy the QReadWriteLock
CopySafeReadWriteLock(const CopySafeReadWriteLock&) : QReadWriteLock() {}
CopySafeReadWriteLock& operator=(const CopySafeReadWriteLock &) { return *this; }
};
class NvComputer
{
friend class PcMonitorThread;
@@ -22,6 +32,14 @@ private:
bool pendingQuit;
public:
NvComputer() = default;
// Caller is responsible for synchronizing read access to the other host
NvComputer(const NvComputer&) = default;
// Caller is responsible for synchronizing read access to the other host
NvComputer& operator=(const NvComputer &) = default;
explicit NvComputer(NvHTTP& http, QString serverInfo);
explicit NvComputer(QSettings& settings);
@@ -30,19 +48,30 @@ public:
setRemoteAddress(QHostAddress);
bool
update(NvComputer& that);
update(const NvComputer& that);
bool
wake();
wake() const;
bool
isReachableOverVpn();
enum ReachabilityType
{
RI_UNKNOWN,
RI_LAN,
RI_VPN,
};
ReachabilityType
getActiveAddressReachability() const;
QVector<NvAddress>
uniqueAddresses() const;
void
serialize(QSettings& settings) const;
serialize(QSettings& settings, bool serializeApps) const;
// Caller is responsible for synchronizing read access to both hosts
bool
isEqualSerialized(const NvComputer& that) const;
enum PairState
{
@@ -83,9 +112,11 @@ public:
QString uuid;
QSslCertificate serverCert;
QVector<NvApp> appList;
bool isNvidiaServerSoftware;
// Remember to update isEqualSerialized() when adding fields here!
// Synchronization
mutable QReadWriteLock lock;
mutable CopySafeReadWriteLock lock;
private:
uint16_t externalPort;
+18 -38
View File
@@ -187,12 +187,15 @@ NvHTTP::getServerInfo(NvLogLevel logLevel, bool fastFail)
}
void
NvHTTP::launchApp(int appId,
PSTREAM_CONFIGURATION streamConfig,
bool sops,
bool localAudio,
int gamepadMask,
QString& rtspSessionUrl)
NvHTTP::startApp(QString verb,
bool isGfe,
int appId,
PSTREAM_CONFIGURATION streamConfig,
bool sops,
bool localAudio,
int gamepadMask,
bool persistGameControllersOnDisconnect,
QString& rtspSessionUrl)
{
int riKeyId;
@@ -201,25 +204,26 @@ NvHTTP::launchApp(int appId,
QString response =
openConnectionToString(m_BaseUrlHttps,
"launch",
verb,
"appid="+QString::number(appId)+
"&mode="+QString::number(streamConfig->width)+"x"+
QString::number(streamConfig->height)+"x"+
// Using an FPS value over 60 causes SOPS to default to 720p60,
// so force it to 0 to ensure the correct resolution is set. We
// used to use 60 here but that locked the frame rate to 60 FPS
// on GFE 3.20.3.
QString::number(streamConfig->fps > 60 ? 0 : streamConfig->fps)+
// on GFE 3.20.3. We don't need this hack for Sunshine.
QString::number((streamConfig->fps > 60 && isGfe) ? 0 : streamConfig->fps)+
"&additionalStates=1&sops="+QString::number(sops ? 1 : 0)+
"&rikey="+QByteArray(streamConfig->remoteInputAesKey, sizeof(streamConfig->remoteInputAesKey)).toHex()+
"&rikeyid="+QString::number(riKeyId)+
(streamConfig->enableHdr ?
((streamConfig->supportedVideoFormats & VIDEO_FORMAT_MASK_10BIT) ?
"&hdrMode=1&clientHdrCapVersion=0&clientHdrCapSupportedFlagsInUint32=0&clientHdrCapMetaDataId=NV_STATIC_METADATA_TYPE_1&clientHdrCapDisplayData=0x0x0x0x0x0x0x0x0x0x0" :
"")+
"&localAudioPlayMode="+QString::number(localAudio ? 1 : 0)+
"&surroundAudioInfo="+QString::number(SURROUNDAUDIOINFO_FROM_AUDIO_CONFIGURATION(streamConfig->audioConfiguration))+
"&remoteControllersBitmap="+QString::number(gamepadMask)+
"&gcmap="+QString::number(gamepadMask),
"&gcmap="+QString::number(gamepadMask)+
"&gcpersist="+QString::number(persistGameControllersOnDisconnect ? 1 : 0),
LAUNCH_TIMEOUT_MS);
qInfo() << "Launch response:" << response;
@@ -230,30 +234,6 @@ NvHTTP::launchApp(int appId,
rtspSessionUrl = getXmlString(response, "sessionUrl0");
}
void
NvHTTP::resumeApp(PSTREAM_CONFIGURATION streamConfig, QString& rtspSessionUrl)
{
int riKeyId;
memcpy(&riKeyId, streamConfig->remoteInputAesIv, sizeof(riKeyId));
riKeyId = qFromBigEndian(riKeyId);
QString response =
openConnectionToString(m_BaseUrlHttps,
"resume",
"rikey="+QString(QByteArray(streamConfig->remoteInputAesKey, sizeof(streamConfig->remoteInputAesKey)).toHex())+
"&rikeyid="+QString::number(riKeyId)+
"&surroundAudioInfo="+QString::number(SURROUNDAUDIOINFO_FROM_AUDIO_CONFIGURATION(streamConfig->audioConfiguration)),
RESUME_TIMEOUT_MS);
qInfo() << "Resume response:" << response;
// Throws if the request failed
verifyResponseStatus(response);
rtspSessionUrl = getXmlString(response, "sessionUrl0");
}
void
NvHTTP::quitApp()
{
@@ -269,7 +249,7 @@ NvHTTP::quitApp()
verifyResponseStatus(response);
// Newer GFE versions will just return success even if quitting fails
// if we're not the original requestor.
// if we're not the original requester.
if (getCurrentGame(getServerInfo(NvHTTP::NVLL_ERROR)) != 0) {
// Generate a synthetic GfeResponseException letting the caller know
// that they can't kill someone else's stream.
@@ -382,7 +362,7 @@ NvHTTP::verifyResponseStatus(QString xml)
}
}
throw GfeHttpResponseException(-1, "Malformed GFE XML (missing root element)");
throw GfeHttpResponseException(-1, "Malformed XML (missing root element)");
}
QImage
@@ -551,7 +531,7 @@ NvHTTP::openConnection(QUrl baseUrl,
if (reply->error() != QNetworkReply::NoError)
{
if (logLevel >= NvLogLevel::NVLL_ERROR) {
qWarning() << command << " request failed with error " << reply->error();
qWarning() << command << "request failed with error:" << reply->error();
}
if (reply->error() == QNetworkReply::SslHandshakeFailedError) {
+9 -10
View File
@@ -162,16 +162,15 @@ public:
quitApp();
void
resumeApp(PSTREAM_CONFIGURATION streamConfig,
QString& rtspSessionUrl);
void
launchApp(int appId,
PSTREAM_CONFIGURATION streamConfig,
bool sops,
bool localAudio,
int gamepadMask,
QString& rtspSessionUrl);
startApp(QString verb,
bool isGfe,
int appId,
PSTREAM_CONFIGURATION streamConfig,
bool sops,
bool localAudio,
int gamepadMask,
bool persistGameControllersOnDisconnect,
QString& rtspSessionUrl);
QVector<NvApp>
getAppList();
+21 -32
View File
@@ -2,6 +2,7 @@
#include "utils.h"
#include <QGuiApplication>
#include <QLibraryInfo>
#include "streaming/session.h"
#include "streaming/streamutils.h"
@@ -17,6 +18,7 @@ SystemProperties::SystemProperties()
hasDesktopEnvironment = WMUtils::isRunningDesktopEnvironment();
isRunningWayland = WMUtils::isRunningWayland();
isRunningXWayland = isRunningWayland && QGuiApplication::platformName() == "xcb";
usesMaterial3Theme = QLibraryInfo::version() >= QVersionNumber(6, 5, 0);
QString nativeArch = QSysInfo::currentCpuArchitecture();
#ifdef Q_OS_WIN32
@@ -70,23 +72,22 @@ SystemProperties::SystemProperties()
// and cache the results to speed up future queries on this data.
querySdlVideoInfo();
Q_ASSERT(maximumStreamingFrameRate >= 60);
Q_ASSERT(!monitorDesktopResolutions.isEmpty());
Q_ASSERT(!monitorRefreshRates.isEmpty());
Q_ASSERT(!monitorNativeResolutions.isEmpty());
}
QRect SystemProperties::getDesktopResolution(int displayIndex)
{
// Returns default constructed QRect if out of bounds
return monitorDesktopResolutions.value(displayIndex);
}
QRect SystemProperties::getNativeResolution(int displayIndex)
{
// Returns default constructed QRect if out of bounds
return monitorNativeResolutions.value(displayIndex);
}
int SystemProperties::getRefreshRate(int displayIndex)
{
// Returns 0 if out of bounds
return monitorRefreshRates.value(displayIndex);
}
class QuerySdlVideoThread : public QThread
{
public:
@@ -193,35 +194,13 @@ void SystemProperties::refreshDisplaysInternal()
return;
}
monitorDesktopResolutions.clear();
monitorNativeResolutions.clear();
// Never let the maximum drop below 60 FPS
maximumStreamingFrameRate = 60;
SDL_DisplayMode bestMode;
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
SDL_DisplayMode desktopMode;
int err;
err = SDL_GetDesktopDisplayMode(displayIndex, &desktopMode);
if (err == 0) {
if (desktopMode.w <= 8192 && desktopMode.h <= 8192) {
monitorDesktopResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Skipping resolution over 8K: %dx%d",
desktopMode.w, desktopMode.h);
}
}
else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_GetDesktopDisplayMode() failed: %s",
SDL_GetError());
}
if (StreamUtils::getRealDesktopMode(displayIndex, &desktopMode)) {
if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode)) {
if (desktopMode.w <= 8192 && desktopMode.h <= 8192) {
monitorNativeResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
}
@@ -244,7 +223,17 @@ void SystemProperties::refreshDisplaysInternal()
}
}
maximumStreamingFrameRate = qMax(maximumStreamingFrameRate, bestMode.refresh_rate);
// Try to normalize values around our our standard refresh rates.
// Some displays/OSes report values that are slightly off.
if (bestMode.refresh_rate >= 58 && bestMode.refresh_rate <= 62) {
monitorRefreshRates.append(60);
}
else if (bestMode.refresh_rate >= 28 && bestMode.refresh_rate <= 32) {
monitorRefreshRates.append(30);
}
else {
monitorRefreshRates.append(bestMode.refresh_rate);
}
}
}
+4 -4
View File
@@ -23,14 +23,14 @@ public:
Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT)
Q_PROPERTY(bool hasDiscordIntegration MEMBER hasDiscordIntegration CONSTANT)
Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged)
Q_PROPERTY(int maximumStreamingFrameRate MEMBER maximumStreamingFrameRate CONSTANT)
Q_PROPERTY(QSize maximumResolution MEMBER maximumResolution CONSTANT)
Q_PROPERTY(QString versionString MEMBER versionString CONSTANT)
Q_PROPERTY(bool supportsHdr MEMBER supportsHdr CONSTANT)
Q_PROPERTY(bool usesMaterial3Theme MEMBER usesMaterial3Theme CONSTANT)
Q_INVOKABLE void refreshDisplays();
Q_INVOKABLE QRect getDesktopResolution(int displayIndex);
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
Q_INVOKABLE int getRefreshRate(int displayIndex);
signals:
void unmappedGamepadsChanged();
@@ -50,11 +50,11 @@ private:
bool hasBrowser;
bool hasDiscordIntegration;
QString unmappedGamepads;
int maximumStreamingFrameRate;
QSize maximumResolution;
QList<QRect> monitorDesktopResolutions;
QList<QRect> monitorNativeResolutions;
QList<int> monitorRefreshRates;
QString versionString;
bool supportsHdr;
bool usesMaterial3Theme;
};
+117
View File
@@ -164,8 +164,10 @@ GlobalCommandLineParser::ParseResult GlobalCommandLineParser::parse(const QStrin
"Starts Moonlight normally if no arguments are given.\n"
"\n"
"Available actions:\n"
" list List the available apps on a host\n"
" quit Quit the currently running app\n"
" stream Start streaming an app\n"
" pair Pair a new host\n"
"\n"
"See 'moonlight <action> --help' for help of specific action."
);
@@ -193,6 +195,10 @@ GlobalCommandLineParser::ParseResult GlobalCommandLineParser::parse(const QStrin
return QuitRequested;
} else if (action == "stream") {
return StreamRequested;
} else if (action == "pair") {
return PairRequested;
} else if (action == "list") {
return ListRequested;
}
}
@@ -242,6 +248,58 @@ QString QuitCommandLineParser::getHost() const
return m_Host;
}
PairCommandLineParser::PairCommandLineParser()
{
}
PairCommandLineParser::~PairCommandLineParser()
{
}
void PairCommandLineParser::parse(const QStringList &args)
{
CommandLineParser parser;
parser.setupCommonOptions();
parser.setApplicationDescription(
"\n"
"Pair with the specified host."
);
parser.addPositionalArgument("pair", "pair host");
parser.addPositionalArgument("host", "Host computer name, UUID, or IP address", "<host>");
parser.addValueOption("pin", "4 digit pairing PIN");
if (!parser.parse(args)) {
parser.showError(parser.errorText());
}
parser.handleUnknownOptions();
// This method will not return and terminates the process if --version or
// --help is specified
parser.handleHelpAndVersionOptions();
// Verify that host has been provided
auto posArgs = parser.positionalArguments();
if (posArgs.length() < 2) {
parser.showError("Host not provided");
}
m_Host = parser.positionalArguments().at(1);
m_PredefinedPin = parser.value("pin");
if (!m_PredefinedPin.isEmpty() && m_PredefinedPin.length() != 4) {
parser.showError("PIN must be 4 digits");
}
}
QString PairCommandLineParser::getHost() const
{
return m_Host;
}
QString PairCommandLineParser::getPredefinedPin() const
{
return m_PredefinedPin;
}
StreamCommandLineParser::StreamCommandLineParser()
{
m_WindowModeMap = {
@@ -468,3 +526,62 @@ QString StreamCommandLineParser::getAppName() const
{
return m_AppName;
}
ListCommandLineParser::ListCommandLineParser()
{
}
ListCommandLineParser::~ListCommandLineParser()
{
}
void ListCommandLineParser::parse(const QStringList &args)
{
CommandLineParser parser;
parser.setupCommonOptions();
parser.setApplicationDescription(
"\n"
"List the available apps on the given host."
);
parser.addPositionalArgument("list", "list available apps");
parser.addPositionalArgument("host", "Host computer name, UUID, or IP address", "<host>");
parser.addFlagOption("csv", "Print as CSV with additional information");
parser.addFlagOption("verbose", "Displays additional information");
if (!parser.parse(args)) {
parser.showError(parser.errorText());
}
parser.handleUnknownOptions();
m_PrintCSV = parser.isSet("csv");
m_Verbose = parser.isSet("verbose");
// This method will not return and terminates the process if --version or
// --help is specified
parser.handleHelpAndVersionOptions();
// Verify that host has been provided
auto posArgs = parser.positionalArguments();
if (posArgs.length() < 2) {
parser.showError("Host not provided");
}
m_Host = parser.positionalArguments().at(1);
}
QString ListCommandLineParser::getHost() const
{
return m_Host;
}
bool ListCommandLineParser::isPrintCSV() const
{
return m_PrintCSV;
}
bool ListCommandLineParser::isVerbose() const
{
return m_Verbose;
}
+36
View File
@@ -12,6 +12,8 @@ public:
NormalStartRequested,
StreamRequested,
QuitRequested,
PairRequested,
ListRequested,
};
GlobalCommandLineParser();
@@ -35,6 +37,22 @@ private:
QString m_Host;
};
class PairCommandLineParser
{
public:
PairCommandLineParser();
virtual ~PairCommandLineParser();
void parse(const QStringList &args);
QString getHost() const;
QString getPredefinedPin() const;
private:
QString m_Host;
QString m_PredefinedPin;
};
class StreamCommandLineParser
{
public:
@@ -55,3 +73,21 @@ private:
QMap<QString, StreamingPreferences::VideoDecoderSelection> m_VideoDecoderMap;
QMap<QString, StreamingPreferences::CaptureSysKeysMode> m_CaptureSysKeysModeMap;
};
class ListCommandLineParser
{
public:
ListCommandLineParser();
virtual ~ListCommandLineParser();
void parse(const QStringList &args);
QString getHost() const;
bool isPrintCSV() const;
bool isVerbose() const;
private:
QString m_Host;
bool m_PrintCSV;
bool m_Verbose;
};
+208
View File
@@ -0,0 +1,208 @@
#include "listapps.h"
#include "backend/boxartmanager.h"
#include "backend/computermanager.h"
#include "backend/computerseeker.h"
#include <QCoreApplication>
#include <QTimer>
#define COMPUTER_SEEK_TIMEOUT 10000
#define APP_SEEK_TIMEOUT 10000
namespace CliListApps
{
enum State {
StateInit,
StateSeekComputer,
StateListApp,
StateSeekApp,
StateFailure,
};
class Event
{
public:
enum Type {
ComputerFound,
ComputerUpdated,
ComputerSeekTimedout,
Executed,
};
Event(Type type)
: type(type), computerManager(nullptr), computer(nullptr) {}
Type type;
ComputerManager *computerManager;
NvComputer *computer;
QString errorMessage;
};
class LauncherPrivate
{
Q_DECLARE_PUBLIC(Launcher)
public:
LauncherPrivate(Launcher *q) : q_ptr(q) {}
void handleEvent(Event event)
{
Q_Q(Launcher);
NvApp app;
switch (event.type) {
// Occurs when CLI main calls execute
case Event::Executed:
if (m_State == StateInit) {
m_State = StateSeekComputer;
m_ComputerManager = event.computerManager;
m_ComputerSeeker = new ComputerSeeker(m_ComputerManager, m_ComputerName, q);
q->connect(m_ComputerSeeker, &ComputerSeeker::computerFound,
q, &Launcher::onComputerFound);
q->connect(m_ComputerSeeker, &ComputerSeeker::errorTimeout,
q, &Launcher::onComputerSeekTimeout);
m_ComputerSeeker->start(COMPUTER_SEEK_TIMEOUT);
q->connect(m_ComputerManager, &ComputerManager::computerStateChanged,
q, &Launcher::onComputerUpdated);
m_BoxArtManager = new BoxArtManager(q);
if (m_Arguments.isVerbose()) {
fprintf(stdout, "Establishing connection to PC...\n");
}
}
break;
// Occurs when computer search timed out
case Event::ComputerSeekTimedout:
if (m_State == StateSeekComputer) {
fprintf(stderr, "%s\n", qPrintable(QString("Failed to connect to %1").arg(m_ComputerName)));
QCoreApplication::exit(-1);
}
break;
// Occurs when searched computer is found
case Event::ComputerFound:
if (m_State == StateSeekComputer) {
if (event.computer->pairState == NvComputer::PS_PAIRED) {
m_State = StateSeekApp;
m_Computer = event.computer;
m_TimeoutTimer->start(APP_SEEK_TIMEOUT);
if (m_Arguments.isVerbose()) {
fprintf(stdout, "Loading app list...\n");
}
} else {
m_State = StateFailure;
fprintf(stderr, "%s\n", qPrintable(QObject::tr("Computer %1 has not been paired. "
"Please open Moonlight to pair before retrieving games list.")
.arg(event.computer->name)));
QCoreApplication::exit(-1);
}
}
break;
// Occurs when a computer is updated
case Event::ComputerUpdated:
if (m_State == StateSeekApp) {
m_Arguments.isPrintCSV() ? printAppsCSV(m_Computer->appList) : printApps(m_Computer->appList);
QCoreApplication::exit(0);
}
break;
}
}
void printApps(QVector<NvApp> apps) {
for (int i = 0; i < apps.length(); i++) {
fprintf(stdout, "%s\n", qPrintable(apps[i].name));
}
}
void printAppsCSV(QVector<NvApp> apps) {
fprintf(stdout, "Name, ID, HDR Support, App Collection Game, Hidden, Direct Launch, Boxart URL\n");
for (int i = 0; i < apps.length(); i++) {
printAppCSV(apps[i]);
}
}
void printAppCSV(NvApp app) const
{
fprintf(stdout, "\"%s\",%d,%s,%s,%s,%s,\"%s\"\n", qPrintable(app.name),
app.id,
app.hdrSupported ? "true" : "false",
app.isAppCollectorGame ? "true" : "false",
app.hidden ? "true" : "false",
app.directLaunch ? "true" : "false",
qPrintable(m_BoxArtManager->loadBoxArt(m_Computer, app).toDisplayString()));
}
Launcher *q_ptr;
ComputerManager *m_ComputerManager;
QString m_ComputerName;
ComputerSeeker *m_ComputerSeeker;
BoxArtManager *m_BoxArtManager;
NvComputer *m_Computer;
State m_State;
QTimer *m_TimeoutTimer;
ListCommandLineParser m_Arguments;
};
Launcher::Launcher(QString computer, ListCommandLineParser arguments, QObject *parent)
: QObject(parent),
m_DPtr(new LauncherPrivate(this))
{
Q_D(Launcher);
d->m_ComputerName = computer;
d->m_State = StateInit;
d->m_TimeoutTimer = new QTimer(this);
d->m_TimeoutTimer->setSingleShot(true);
d->m_Arguments = arguments;
connect(d->m_TimeoutTimer, &QTimer::timeout,
this, &Launcher::onComputerSeekTimeout);
}
Launcher::~Launcher()
{
}
void Launcher::execute(ComputerManager *manager)
{
Q_D(Launcher);
Event event(Event::Executed);
event.computerManager = manager;
d->handleEvent(event);
}
bool Launcher::isExecuted() const
{
Q_D(const Launcher);
return d->m_State != StateInit;
}
void Launcher::onComputerFound(NvComputer *computer)
{
Q_D(Launcher);
Event event(Event::ComputerFound);
event.computer = computer;
d->handleEvent(event);
}
void Launcher::onComputerSeekTimeout()
{
Q_D(Launcher);
Event event(Event::ComputerSeekTimedout);
d->handleEvent(event);
}
void Launcher::onComputerUpdated(NvComputer *computer)
{
Q_D(Launcher);
Event event(Event::ComputerUpdated);
event.computer = computer;
d->handleEvent(event);
}
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include "commandlineparser.h"
#include <QObject>
#include <QVariant>
class ComputerManager;
class NvComputer;
namespace CliListApps
{
class LauncherPrivate;
class Launcher : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE_D(m_DPtr, Launcher)
public:
explicit Launcher(QString computer, ListCommandLineParser arguments, QObject *parent = nullptr);
~Launcher();
Q_INVOKABLE void execute(ComputerManager *manager);
Q_INVOKABLE bool isExecuted() const;
private slots:
void onComputerFound(NvComputer *computer);
void onComputerUpdated(NvComputer *computer);
void onComputerSeekTimeout();
private:
QScopedPointer<LauncherPrivate> m_DPtr;
ListCommandLineParser m_Arguments;
};
}
+181
View File
@@ -0,0 +1,181 @@
#include "pair.h"
#include "backend/computermanager.h"
#include "backend/computerseeker.h"
#include <QCoreApplication>
#include <QTimer>
#define COMPUTER_SEEK_TIMEOUT 10000
namespace CliPair
{
enum State {
StateInit,
StateSeekComputer,
StatePairing,
StateFailure,
StateComplete,
};
class Event
{
public:
enum Type {
ComputerFound,
Executed,
PairingCompleted,
Timedout,
};
Event(Type type)
: type(type), computerManager(nullptr), computer(nullptr) {}
Type type;
ComputerManager *computerManager;
NvComputer *computer;
QString errorMessage;
};
class LauncherPrivate
{
Q_DECLARE_PUBLIC(Launcher)
public:
LauncherPrivate(Launcher *q) : q_ptr(q) {}
void handleEvent(Event event)
{
Q_Q(Launcher);
switch (event.type) {
// Occurs when CliPair becomes visible and the UI calls launcher's execute()
case Event::Executed:
if (m_State == StateInit) {
m_State = StateSeekComputer;
m_ComputerManager = event.computerManager;
q->connect(m_ComputerManager, &ComputerManager::pairingCompleted,
q, &Launcher::onPairingCompleted);
// If we weren't provided a predefined PIN, generate one now
if (m_PredefinedPin.isEmpty()) {
m_PredefinedPin = m_ComputerManager->generatePinString();
}
m_ComputerSeeker = new ComputerSeeker(m_ComputerManager, m_ComputerName, q);
q->connect(m_ComputerSeeker, &ComputerSeeker::computerFound,
q, &Launcher::onComputerFound);
q->connect(m_ComputerSeeker, &ComputerSeeker::errorTimeout,
q, &Launcher::onTimeout);
m_ComputerSeeker->start(COMPUTER_SEEK_TIMEOUT);
emit q->searchingComputer();
}
break;
// Occurs when searched computer is found
case Event::ComputerFound:
if (m_State == StateSeekComputer) {
if (event.computer->pairState == NvComputer::PS_PAIRED) {
m_State = StateFailure;
QString msg = QObject::tr("%1 is already paired").arg(event.computer->name);
emit q->failed(msg);
}
else {
Q_ASSERT(!m_PredefinedPin.isEmpty());
m_State = StatePairing;
m_ComputerManager->pairHost(event.computer, m_PredefinedPin);
emit q->pairing(event.computer->name, m_PredefinedPin);
}
}
break;
// Occurs when pairing operation completes
case Event::PairingCompleted:
if (m_State == StatePairing) {
if (event.errorMessage.isEmpty()) {
m_State = StateComplete;
emit q->success();
}
else {
m_State = StateFailure;
emit q->failed(event.errorMessage);
}
}
break;
// Occurs when computer search timed out
case Event::Timedout:
if (m_State == StateSeekComputer) {
m_State = StateFailure;
emit q->failed(QObject::tr("Failed to connect to %1").arg(m_ComputerName));
}
break;
}
}
Launcher *q_ptr;
QString m_ComputerName;
QString m_PredefinedPin;
ComputerManager *m_ComputerManager;
ComputerSeeker *m_ComputerSeeker;
NvComputer *m_Computer;
State m_State;
QTimer *m_TimeoutTimer;
};
Launcher::Launcher(QString computer, QString predefinedPin, QObject *parent)
: QObject(parent),
m_DPtr(new LauncherPrivate(this))
{
Q_D(Launcher);
d->m_ComputerName = computer;
d->m_PredefinedPin = predefinedPin;
d->m_State = StateInit;
d->m_TimeoutTimer = new QTimer(this);
d->m_TimeoutTimer->setSingleShot(true);
connect(d->m_TimeoutTimer, &QTimer::timeout,
this, &Launcher::onTimeout);
}
Launcher::~Launcher()
{
}
void Launcher::execute(ComputerManager *manager)
{
Q_D(Launcher);
Event event(Event::Executed);
event.computerManager = manager;
d->handleEvent(event);
}
bool Launcher::isExecuted() const
{
Q_D(const Launcher);
return d->m_State != StateInit;
}
void Launcher::onComputerFound(NvComputer *computer)
{
Q_D(Launcher);
Event event(Event::ComputerFound);
event.computer = computer;
d->handleEvent(event);
}
void Launcher::onTimeout()
{
Q_D(Launcher);
Event event(Event::Timedout);
d->handleEvent(event);
}
void Launcher::onPairingCompleted(NvComputer* computer, QString error)
{
Q_D(Launcher);
Event event(Event::PairingCompleted);
event.computer = computer;
event.errorMessage = error;
d->handleEvent(event);
}
}
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include <QObject>
#include <QVariant>
class ComputerManager;
class NvComputer;
class Session;
class StreamingPreferences;
namespace CliPair
{
class Event;
class LauncherPrivate;
class Launcher : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE_D(m_DPtr, Launcher)
public:
explicit Launcher(QString computer, QString predefinedPin,
QObject *parent = nullptr);
~Launcher();
Q_INVOKABLE void execute(ComputerManager *manager);
Q_INVOKABLE bool isExecuted() const;
signals:
void searchingComputer();
void pairing(QString pcName, QString pin);
void failed(QString text);
void success();
private slots:
void onComputerFound(NvComputer *computer);
void onPairingCompleted(NvComputer *computer, QString error);
void onTimeout();
private:
QScopedPointer<LauncherPrivate> m_DPtr;
};
}
+2 -2
View File
@@ -118,7 +118,7 @@ public:
}
break;
// Occurs when the previous app quit has been completed, handles quitting errors if any
// happended. ComputerUpdated event's handler handles session start when previous app has
// happened. ComputerUpdated event's handler handles session start when previous app has
// quit.
case Event::AppQuitCompleted:
if (m_State == StateSeekApp && !event.errorMessage.isEmpty()) {
@@ -162,7 +162,7 @@ public:
QString getCurrentAppName() const
{
for (NvApp app : m_Computer->appList) {
for (const NvApp& app : m_Computer->appList) {
if (m_Computer->currentGameId == app.id) {
return app.name;
}
@@ -4,21 +4,19 @@
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0+</project_license>
<name>Moonlight</name>
<summary>Play games remotely from your NVIDIA GameStream-enabled PC</summary>
<summary>Stream games and other applications from another PC running Sunshine or GeForce Experience</summary>
<description>
<p>Moonlight can stream games and other applications from a PC with an NVIDIA GeForce GTX/RTX or NVIDIA Quadro GPU and GeForce/Quadro Experience installed.</p>
<p>Moonlight can stream games and other applications from another PC with Sunshine or GeForce Experience software installed.</p>
<p>Features include:</p>
<ul>
<li>Streaming at up to 4K resolution</li>
<li>Support for up to 120 FPS streaming (high refresh rate monitor recommended)</li>
<li>Hardware accelerated video decoding with VAAPI, VDPAU, and NVDEC support</li>
<li>H.264, HEVC, and AV1 codec support</li>
<li>7.1 surround sound audio support</li>
<li>10-point multitouch support</li>
<li>Gamepad support with force feedback for up to 16 players</li>
<li>Support for both pointer capture (for games) and direct mouse control (for remote desktop)</li>
<li>Support for passing system-wide keyboard shortcuts like Alt+Tab to the host</li>
<li>Direct touchscreen mouse control</li>
<li>HEVC support for better video compression efficiency</li>
<li>Gamepad support with force feedback for up to 4 players</li>
</ul>
</description>
@@ -36,6 +34,115 @@
</screenshots>
<releases>
<release version="5.0.0" date="2023-10-18">
<description>
<p>New features:</p>
<ul>
<li>Custom FPS values</li>
<li>Streaming using the AV1 codec from Sunshine hosts</li>
<li>Horizontal scrolling with Sunshine hosts</li>
<li>Native multi-touch input with Sunshine hosts</li>
<li>Controller type information is passed to Sunshine for controller type autoselection</li>
<li>Extended features of PS4/PS5 controllers (touchpad, battery state, gyro/accelerometer motion, and RGB LED) are natively passed to Sunshine hosts</li>
<li>Support for up to 16 gamepads with Sunshine hosts</li>
<li>Frame pacing support on Wayland</li>
<li>Host-side processing latency is now displayed in the performance overlay with Sunshine hosts</li>
<li>Window title now includes the PC name when streaming</li>
<li>Alphabetical sorting of the PC list</li>
<li>Updated Play and Stop button design to improve contrast with app box art</li>
<li>The settings page automatically scrolls to the selected element when using keyboard or gamepad navigation</li>
<li>Pressing Share+PS buttons on PS4/PS5 controllers can be used to emulate a clickpad press</li>
<li>Improved audio quality when streaming with a high bitrate from remote hosts</li>
<li>Increased mouse polling rate</li>
</ul>
<p>Behavior Changes:</p>
<ul>
<li>IPv6 addresses must be provided in URL-escaped form. Example: [2001:db8::1234:5678] or [2001:db8::1234:5678]:47989</li>
<li>Default bitrate logic has been adjusted to behave better with custom resolutions and frame rates</li>
</ul>
<p>Bugfixes:</p>
<ul>
<li>Reduced the frequency of audio underruns (clicks and pops) by increasing audio buffer size</li>
<li>Fixed "Quitting App" message never dismissing in some cases</li>
<li>Fixed parsing IPv6 literals with port numbers</li>
<li>Fixed unnecessary WiFi activity causing stream disruptions</li>
<li>Fixed paired hosts being forgotten if Moonlight terminates while writing hosts to disk</li>
<li>Fixed freeze when closing the stream on Windows with some software that uses DX11 hooks</li>
<li>Fixed delayed PC wake when activating Wake-On-LAN feature</li>
<li>Fixed Wake-on-LAN when Sunshine is running on alternate ports</li>
<li>Fixed leak of /dev/dri fds when trying to initialize VAAPI decoder</li>
<li>Fixed support for the Applications/Menu key</li>
<li>Fixed automatic language detection for Simplified Chinese and Traditional Chinese</li>
<li>Fixed streaming from Sunshine when the client PC has multiple network interfaces or IPv6 privacy extensions enabled</li>
<li>Fixed small receive buffer causing video packet loss on some systems</li>
<li>Fixed theme overrides causing the UI to be unusable on some systems</li>
<li>Fixed tearing with V-Sync enabled on macOS and Linux when using the SDL renderer (software decoding and some hardware decoders)</li>
<li>Fixed stateful V4L2 hardware decoding using h264_v4l2m2m and hevc_v4l2m2m decoders</li>
<li>Fixed CVE-2023-42801</li>
<li>Updated included gamepad mappings</li>
<li>Updated community-contributed translations from Weblate</li>
</ul>
</description>
</release>
<release version="4.3.1" date="2022-11-03">
<description>
<p>Changes:</p>
<ul>
<li>Fixed crash during specific patterns of audio packet loss</li>
<li>Fixed detection of local streaming when using IPv6 addresses</li>
<li>Windowed mode streaming now starts maximized if the GUI window was maximized</li>
<li>Updated community-contributed translations from Weblate</li>
</ul>
</description>
</release>
<release version="4.3.0" date="2022-10-28">
<description>
<p>New features:</p>
<ul>
<li>Improved support for GeForce Experience 3.26</li>
<li>Improved image quality and performance when recovering from video packet loss using HEVC</li>
<li>Improved error concealment after audio packet loss</li>
<li>Improved UI performance when paired with many hosts or hosts with large numbers of apps</li>
<li>Pairing with Sunshine hosts is now supported even if the host is in a streaming session</li>
</ul>
<p>Bugfixes:</p>
<ul>
<li>Fixed streaming at resolutions below 720x480 with GeForce Experience 3.26</li>
<li>Fixed window position and size being lost when toggling between full-screen and windowed mode</li>
<li>Fixed initial window size not always respecting the stream aspect ratio</li>
<li>Fixed unexpected error when streaming at resolutions above 4096x4096 using Sunshine</li>
<li>Updated included gamepad mappings</li>
<li>Updated community-contributed translations from Weblate</li>
</ul>
</description>
</release>
<release version="4.2.1" date="2022-09-12">
<description>
<p>Bugfixes:</p>
<ul>
<li>Fixed streaming from hosts running Sunshine</li>
<li>Updated community-contributed translations from Weblate</li>
</ul>
</description>
</release>
<release version="4.2.0" date="2022-09-10">
<description>
<p>New features:</p>
<ul>
<li>Added support for pairing via the terminal</li>
<li>Added support for listing installed apps on a host via the terminal</li>
<li>Improved handling of V-Sync on Wayland systems to reduce latency</li>
<li>Improved performance using the legacy non-EGL VAAPI rendering code</li>
</ul>
<p>Bugfixes:</p>
<ul>
<li>Fixed native resolution detection on high DPI Wayland systems</li>
<li>Fixed audio crackling with SDL 2.24.0 on systems using PulseAudio</li>
<li>Updated included gamepad mappings</li>
<li>Updated community-contributed translations from Weblate</li>
</ul>
</description>
</release>
<release version="4.1.0" date="2022-05-23">
<description>
<p>New features:</p>
@@ -1,9 +1,9 @@
[Desktop Entry]
Name=Moonlight
Comment=Stream games from your NVIDIA GameStream-enabled PC
Comment=Stream games and other applications from another PC running Sunshine or GeForce Experience
Exec=moonlight
Icon=moonlight
Terminal=false
Type=Application
Categories=Qt;Game;
Keywords=nvidia;gamestream;stream;
Keywords=nvidia;gamestream;stream;sunshine;remote play;
+15 -13
View File
@@ -1,5 +1,6 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2
import AppModel 1.0
import ComputerManager 1.0
@@ -89,7 +90,7 @@ CenteredGridView {
onSourceSizeChanged: {
// Nearly all of Nvidia's official box art does not match the dimensions of placeholder
// images, however the one known exeception is Overcooked. Therefore, we only execute
// images, however the one known exception is Overcooked. Therefore, we only execute
// the image size checks if this is not an app collector game. We know the officially
// supported games all have box art, so this check is not required.
if (!model.isAppCollectorGame &&
@@ -121,15 +122,15 @@ CenteredGridView {
anchors.fill: appIcon
sourceComponent: Item {
ToolButton {
RoundButton {
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? -47 : 0
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : -60
anchors.centerIn: parent
implicitWidth: 125
implicitHeight: 125
implicitWidth: 85
implicitHeight: 85
Image {
source: "qrc:/res/baseline-play_circle_filled_white-48px.svg"
source: "qrc:/res/play_arrow_FILL1_wght700_GRAD200_opsz48.svg"
anchors.centerIn: parent
sourceSize {
width: 75
@@ -145,17 +146,19 @@ CenteredGridView {
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
Material.background: "#D0808080"
}
ToolButton {
RoundButton {
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? 47 : 0
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : 60
anchors.centerIn: parent
implicitWidth: 125
implicitHeight: 125
implicitWidth: 85
implicitHeight: 85
Image {
source: "qrc:/res/baseline-cancel-24px.svg"
source: "qrc:/res/stop_FILL1_wght700_GRAD200_opsz48.svg"
anchors.centerIn: parent
sourceSize {
width: 75
@@ -171,6 +174,8 @@ CenteredGridView {
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
Material.background: "#D0808080"
}
}
}
@@ -345,7 +350,7 @@ CenteredGridView {
function quitApp() {
var component = Qt.createComponent("QuitSegue.qml")
var params = {"appName": appName}
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.
@@ -358,9 +363,6 @@ CenteredGridView {
}
stackView.push(component.createObject(stackView, params))
// Trigger the quit after pushing the quit segue on screen
appModel.quitRunningApp()
}
onAccepted: quitApp()
+7
View File
@@ -2,6 +2,7 @@ import QtQuick 2.9
import QtQuick.Controls 2.2
import SdlGamepadKeyNavigation 1.0
import SystemProperties 1.0
// https://stackoverflow.com/questions/45029968/how-do-i-set-the-combobox-width-to-fit-the-largest-item
ComboBox {
@@ -38,6 +39,12 @@ ComboBox {
popup.onAboutToShow: {
// Switch to normal navigation for combo boxes
SdlGamepadKeyNavigation.setUiNavMode(false)
// Override the popup color to improve contrast with the overridden
// Material 2 background color set in main.qml.
if (SystemProperties.usesMaterial3Theme) {
popup.background.color = "#424242"
}
}
popup.onAboutToHide: {
+91
View File
@@ -0,0 +1,91 @@
import QtQuick 2.0
import QtQuick.Controls 2.2
import ComputerManager 1.0
import SdlGamepadKeyNavigation 1.0
Item {
function onSearchingComputer() {
stageLabel.text = qsTr("Establishing connection to PC...")
}
function onPairing(pcName, pin) {
stageLabel.text = qsTr("Pairing... Please enter '%1' on %2.").arg(pin).arg(pcName)
}
function onFailed(message) {
stageIndicator.visible = false
errorDialog.text = message
errorDialog.open()
}
function onSuccess(appName) {
stageIndicator.visible = false
pairCompleteDialog.open()
}
// Allow user to back out of pairing
Keys.onEscapePressed: {
Qt.quit()
}
Keys.onBackPressed: {
Qt.quit()
}
Keys.onCancelPressed: {
Qt.quit()
}
StackView.onActivated: {
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)
launcher.success.connect(onSuccess)
launcher.execute(ComputerManager)
}
}
Row {
anchors.centerIn: parent
spacing: 5
id: stageIndicator
BusyIndicator {
id: stageSpinner
}
Label {
id: stageLabel
height: stageSpinner.height
font.pointSize: 20
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
}
ErrorMessageDialog {
id: errorDialog
onClosed: {
Qt.quit();
}
}
NavigableMessageDialog {
id: pairCompleteDialog
closePolicy: Popup.CloseOnEscape
text:qsTr("Pairing completed successfully")
standardButtons: Dialog.Ok
onClosed: {
Qt.quit()
}
}
}
+2 -3
View File
@@ -26,6 +26,7 @@ Item {
function onLaunchFailed(message) {
errorDialog.text = message
errorDialog.open()
console.error(message)
}
function onAppQuitRequired(appName) {
@@ -84,10 +85,8 @@ Item {
function quitApp() {
var component = Qt.createComponent("QuitSegue.qml")
var params = {"appName": appName}
var params = {"appName": appName, "quitRunningAppFn": launcher.quitRunningApp}
stackView.push(component.createObject(stackView, params))
// Trigger the quit after pushing the quit segue on screen
launcher.quitRunningApp()
}
onAccepted: quitApp()
+26 -29
View File
@@ -99,7 +99,7 @@ CenteredGridView {
Label {
height: searchSpinner.height
elide: Label.ElideRight
text: StreamingPreferences.enableMdns ? qsTr("Searching for PCs on your local network with NVIDIA GameStream enabled...")
text: StreamingPreferences.enableMdns ? qsTr("Searching for compatible hosts on your local network...")
: qsTr("Automatic PC discovery is disabled. Add your PC manually.")
font.pointSize: 20
verticalAlignment: Text.AlignVCenter
@@ -130,12 +130,12 @@ CenteredGridView {
id: stateIcon
anchors.horizontalCenter: pcIcon.horizontalCenter
anchors.verticalCenter: pcIcon.verticalCenter
anchors.verticalCenterOffset: -15
anchors.verticalCenterOffset: !model.online ? -18 : -16
visible: !model.statusUnknown && (!model.online || !model.paired)
source: !model.online ? "qrc:/res/baseline-warning-24px.svg" : "qrc:/res/baseline-lock-24px.svg"
source: !model.online ? "qrc:/res/warning_FILL1_wght300_GRAD200_opsz24.svg" : "qrc:/res/baseline-lock-24px.svg"
sourceSize {
width: 75
height: 75
width: !model.online ? 75 : 70
height: !model.online ? 75 : 70
}
}
@@ -211,7 +211,7 @@ CenteredGridView {
text: qsTr("Delete PC")
onTriggered: {
deletePcDialog.pcIndex = index
// get confirmation first, actual closing is called from the dialog
deletePcDialog.pcName = model.name
deletePcDialog.open()
}
}
@@ -232,22 +232,14 @@ CenteredGridView {
stackView.push(appView)
}
else {
if (!model.busy) {
var pin = computerModel.generatePinString()
var pin = computerModel.generatePinString()
// Kick off pairing in the background
computerModel.pairComputer(index, pin)
// Kick off pairing in the background
computerModel.pairComputer(index, pin)
// Display the pairing dialog
pairDialog.pin = pin
pairDialog.open()
}
else {
// cannot pair while something is streaming or attempting to pair
errorDialog.text = qsTr("You cannot pair while a previous session is still running on the host PC. Quit any running games or reboot the host PC, then try pairing again.")
errorDialog.helpText = ""
errorDialog.open()
}
// Display the pairing dialog
pairDialog.pin = pin
pairDialog.open()
}
} else if (!model.online) {
// Using open() here because it may be activated by keyboard
@@ -279,6 +271,12 @@ CenteredGridView {
// the ItemDelegate and not where the mouse cursor is
pcContextMenu.open()
}
Keys.onDeletePressed: {
deletePcDialog.pcIndex = index
deletePcDialog.pcName = model.name
deletePcDialog.open()
}
}
ErrorMessageDialog {
@@ -299,7 +297,8 @@ CenteredGridView {
// don't allow edits to the rest of the window while open
property string pin : "0000"
text:qsTr("Please enter %1 on your GameStream PC. This dialog will close when pairing is completed.").arg(pin)
text:qsTr("Please enter %1 on your host PC. This dialog will close when pairing is completed.").arg(pin)+"\n\n"+
qsTr("If your host PC is running Sunshine, navigate to the Sunshine web UI to enter the PIN.")
standardButtons: Dialog.Cancel
onRejected: {
// FIXME: We should interrupt pairing here
@@ -309,16 +308,14 @@ CenteredGridView {
NavigableMessageDialog {
id: deletePcDialog
// don't allow edits to the rest of the window while open
property int pcIndex : -1;
text:qsTr("Are you sure you want to remove this PC?")
property int pcIndex : -1
property string pcName : ""
text: qsTr("Are you sure you want to remove '%1'?").arg(pcName)
standardButtons: Dialog.Yes | Dialog.No
function deletePc() {
console.log("deleting PC pairing for PC at index: " + pcIndex)
computerModel.deleteComputer(pcIndex);
onAccepted: {
computerModel.deleteComputer(pcIndex)
}
onAccepted: deletePc()
}
NavigableMessageDialog {
@@ -327,7 +324,7 @@ CenteredGridView {
standardButtons: Dialog.Ok
onAboutToShow: {
testConnectionDialog.text = qsTr("Moonlight is testing your network connection to determine if NVIDIA GameStream is blocked.") + "\n\n" + qsTr("This may take a few seconds…")
testConnectionDialog.text = qsTr("Moonlight is testing your network connection to determine if any required ports are blocked.") + "\n\n" + qsTr("This may take a few seconds…")
showSpinner = true
}
+7
View File
@@ -6,6 +6,7 @@ import Session 1.0
Item {
property string appName
property var quitRunningAppFn
property Session nextSession : null
property string nextAppName : ""
@@ -17,6 +18,7 @@ Item {
if (error !== undefined) {
errorDialog.text = error
errorDialog.open()
console.error(error)
}
// If we're supposed to launch another game after this, do so now
@@ -37,6 +39,11 @@ Item {
// Connect the quit completion signal
ComputerManager.quitAppCompleted.connect(quitAppCompleted)
// Start the quit operation if requested
if (quitRunningAppFn) {
quitRunningAppFn()
}
}
StackView.onDeactivating: {
+269 -123
View File
@@ -1,6 +1,7 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.2
import QtQuick.Window 2.2
import StreamingPreferences 1.0
import ComputerManager 1.0
@@ -25,6 +26,49 @@ Flickable {
}
}
function isChildOfFlickable(item) {
while (item) {
if (item.parent === contentItem) {
return true
}
item = item.parent
}
return false
}
NumberAnimation on contentY {
id: autoScrollAnimation
duration: 100
}
Window.onActiveFocusItemChanged: {
var item = Window.activeFocusItem
if (item) {
// Ignore non-child elements like the toolbar buttons
if (!isChildOfFlickable(item)) {
return
}
// Map the focus item's position into our content item's coordinate space
var pos = item.mapToItem(contentItem, 0, 0)
// Ensure some extra space is visible around the element we're scrolling to
var scrollMargin = height > 100 ? 50 : 0
if (pos.y - scrollMargin < contentY) {
autoScrollAnimation.from = contentY
autoScrollAnimation.to = Math.max(pos.y - scrollMargin, 0)
autoScrollAnimation.start()
}
else if (pos.y + item.height + scrollMargin > contentY + height) {
autoScrollAnimation.from = contentY
autoScrollAnimation.to = Math.min(pos.y + item.height + scrollMargin - height, contentHeight - height)
autoScrollAnimation.start()
}
}
}
StackView.onActivated: {
// This enables Tab and BackTab based navigation rather than arrow keys.
// It is required to shift focus between controls on the settings page.
@@ -97,50 +141,39 @@ Flickable {
// Add native resolutions for all attached displays
var done = false
for (var displayIndex = 0; !done; displayIndex++) {
for (var displayResIndex = 0; displayResIndex < 2; displayResIndex++) {
var screenRect;
var screenRect = SystemProperties.getNativeResolution(displayIndex);
// Some platforms have different desktop resolutions
// and native resolutions (like macOS with Retina displays)
if (displayResIndex === 0) {
screenRect = SystemProperties.getDesktopResolution(displayIndex)
}
else {
screenRect = SystemProperties.getNativeResolution(displayIndex)
}
if (screenRect.width === 0) {
// Exceeded max count of displays
done = true
break
}
if (screenRect.width === 0) {
// Exceeded max count of displays
done = true
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
}
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
}
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
})
}
// 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
})
}
}
@@ -399,69 +432,190 @@ Flickable {
}
AutoResizingComboBox {
function createModel() {
var fpsListModel = Qt.createQmlObject('import QtQuick 2.0; ListModel {}', parent, '')
property int lastIndexValue
var max_fps = SystemProperties.maximumStreamingFrameRate
function updateBitrateForSelection() {
// Only modify the bitrate if the values actually changed
var selectedFps = parseInt(model.get(fpsComboBox.currentIndex).video_fps)
if (StreamingPreferences.fps !== selectedFps) {
StreamingPreferences.fps = selectedFps
// Default entries
fpsListModel.append({"text": qsTr("%1 FPS").arg("30"), "video_fps": "30"})
fpsListModel.append({"text": qsTr("%1 FPS").arg("60"), "video_fps": "60"})
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
StreamingPreferences.height,
StreamingPreferences.fps);
slider.value = StreamingPreferences.bitrateKbps
}
// Add unsupported FPS values that come before the display max FPS
if (StreamingPreferences.unsupportedFps) {
if (max_fps > 90) {
fpsListModel.append({"text": qsTr("%1 FPS (Unsupported)").arg("90"), "video_fps": "90"})
lastIndexValue = currentIndex
}
NavigableDialog {
function isInputValid() {
// If we have text that isn't valid, reject the input.
if (!fpsField.acceptableInput && fpsField.text) {
return false
}
if (max_fps > 120) {
fpsListModel.append({"text": qsTr("%1 FPS (Unsupported)").arg("120"), "video_fps": "120"})
// The textbox needs to have text or placeholder text
if (!fpsField.text && !fpsField.placeholderText) {
return false
}
return true
}
id: customFpsDialog
standardButtons: Dialog.Ok | Dialog.Cancel
onOpened: {
// Force keyboard focus on the textbox so keyboard navigation works
fpsField.forceActiveFocus()
// standardButton() was added in Qt 5.10, so we must check for it first
if (customFpsDialog.standardButton) {
customFpsDialog.standardButton(Dialog.Ok).enabled = customFpsDialog.isInputValid()
}
}
// Use 64 as the cutoff for adding a separate option to
// handle wonky displays that report just over 60 Hz.
if (max_fps > 64) {
// Mark any FPS value greater than 120 as unsupported
if (StreamingPreferences.unsupportedFps && max_fps > 120) {
fpsListModel.append({"text": qsTr("%1 FPS (Unsupported)").arg(max_fps), "video_fps": ""+max_fps})
onClosed: {
fpsField.clear()
}
onRejected: {
fpsComboBox.currentIndex = fpsComboBox.lastIndexValue
}
onAccepted: {
// Reject if there's invalid input
if (!isInputValid()) {
reject()
return
}
else if (max_fps > 120) {
fpsListModel.append({"text": qsTr("%1 FPS").arg("120"), "video_fps": "120"})
}
else {
fpsListModel.append({"text": qsTr("%1 FPS").arg(max_fps), "video_fps": ""+max_fps})
var fps = fpsField.text ? fpsField.text : fpsField.placeholderText
// Find and update the custom entry
for (var i = 0; i < fpsListModel.count; i++) {
if (fpsListModel.get(i).is_custom) {
fpsListModel.setProperty(i, "video_fps", fps)
fpsListModel.setProperty(i, "text", qsTr("Custom (%1 FPS)").arg(fps))
// Now update the bitrate using the custom resolution
fpsComboBox.currentIndex = i
fpsComboBox.updateBitrateForSelection()
// Update the combobox width too
fpsComboBox.recalculateWidth()
break
}
}
}
// Add unsupported FPS values that come after the display max FPS
if (StreamingPreferences.unsupportedFps) {
if (max_fps < 90) {
fpsListModel.append({"text":qsTr("%1 FPS (Unsupported)").arg("90"), "video_fps": "90"})
ColumnLayout {
Label {
text: qsTr("Enter a custom frame rate:")
font.bold: true
}
if (max_fps < 120) {
fpsListModel.append({"text":qsTr("%1 FPS (Unsupported)").arg("120"), "video_fps": "120"})
RowLayout {
TextField {
id: fpsField
maximumLength: 4
inputMethodHints: Qt.ImhDigitsOnly
placeholderText: fpsListModel.get(fpsComboBox.currentIndex).video_fps
validator: IntValidator{bottom:10; top:9999}
focus: true
onTextChanged: {
// standardButton() was added in Qt 5.10, so we must check for it first
if (customFpsDialog.standardButton) {
customFpsDialog.standardButton(Dialog.Ok).enabled = customFpsDialog.isInputValid()
}
}
Keys.onReturnPressed: {
customFpsDialog.accept()
}
Keys.onEnterPressed: {
customFpsDialog.accept()
}
}
}
}
}
function addRefreshRateOrdered(fpsListModel, refreshRate, description, custom) {
var indexToAdd = 0
for (var j = 0; j < fpsListModel.count; j++) {
var existing_fps = parseInt(fpsListModel.get(j).video_fps);
if (refreshRate === existing_fps || (custom && fpsListModel.get(j).is_custom)) {
// Duplicate entry, skip
indexToAdd = -1
break
}
else if (refreshRate > existing_fps) {
// Candidate entrypoint after this entry
indexToAdd = j + 1
}
}
return fpsListModel
// Insert this frame rate if it's not a duplicate
if (indexToAdd >= 0) {
// Custom values always go at the end of the list
if (custom) {
indexToAdd = fpsListModel.count
}
fpsListModel.insert(indexToAdd,
{
"text": description,
"video_fps": ""+refreshRate,
"is_custom": custom
})
}
return indexToAdd
}
function reinitialize() {
model = createModel()
// Add native refresh rate for all attached displays
var done = false
for (var displayIndex = 0; !done; displayIndex++) {
var refreshRate = SystemProperties.getRefreshRate(displayIndex);
if (refreshRate === 0) {
// Exceeded max count of displays
done = true
break
}
addRefreshRateOrdered(fpsListModel, refreshRate, qsTr("%1 FPS").arg(refreshRate), false)
}
var saved_fps = StreamingPreferences.fps
currentIndex = 0
var found = false
for (var i = 0; i < model.count; i++) {
var el_fps = parseInt(model.get(i).video_fps);
// Pick the highest value lesser or equal to the saved FPS
if (saved_fps >= el_fps) {
// Look for a matching frame rate
if (saved_fps === el_fps) {
currentIndex = i
found = true
break
}
}
// Persist the selected value
activated(currentIndex)
// If we didn't find one, add a custom frame rate for the current value
if (!found) {
currentIndex = addRefreshRateOrdered(model, saved_fps, qsTr("Custom (%1 FPS)").arg(saved_fps), true)
}
else {
addRefreshRateOrdered(model, "", qsTr("Custom"), true)
}
recalculateWidth()
lastIndexValue = currentIndex
}
// ignore setting the index at first, and actually set it when the component is loaded
@@ -470,21 +624,31 @@ Flickable {
languageChanged.connect(reinitialize)
}
model: ListModel {
id: fpsListModel
// Other elements may be added at runtime
ListElement {
text: qsTr("30 FPS")
video_fps: "30"
is_custom: false
}
ListElement {
text: qsTr("60 FPS")
video_fps: "60"
is_custom: false
}
}
id: fpsComboBox
maximumWidth: parent.width / 2
textRole: "text"
// ::onActivated must be used, as it only listens for when the index is changed by a human
onActivated : {
var selectedFps = parseInt(model.get(currentIndex).video_fps)
// Only modify the bitrate if the values actually changed
if (StreamingPreferences.fps !== selectedFps) {
StreamingPreferences.fps = selectedFps
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
StreamingPreferences.height,
StreamingPreferences.fps);
slider.value = StreamingPreferences.bitrateKbps
if (model.get(currentIndex).is_custom) {
customFpsDialog.open()
}
else {
updateBitrateForSelection()
}
}
}
@@ -522,6 +686,11 @@ Flickable {
bitrateTitle.text = qsTr("Video bitrate: %1 Mbps").arg(value / 1000.0)
StreamingPreferences.bitrateKbps = value
}
Component.onCompleted: {
// Refresh the text after translations change
languageChanged.connect(onValueChanged)
}
}
Label {
@@ -557,7 +726,7 @@ Flickable {
for (var i = 0; i < model.count; i++) {
var thisWm = model.get(i).val;
if (thisWm === StreamingPreferences.recommendedFullScreenMode) {
model.get(i).text += qsTr(" (Recommended)")
model.get(i).text += " " + qsTr("(Recommended)")
model.move(i, 0, 1)
break
}
@@ -848,8 +1017,8 @@ Flickable {
val: StreamingPreferences.LANG_UK
} */
ListElement {
text: "繁体字" // Traditional Chinese
val: StreamingPreferences.LANG_ZH_HANT
text: "繁體中文" // Traditional Chinese
val: StreamingPreferences.LANG_ZH_TW
}
ListElement {
text: "Português" // Portuguese
@@ -879,6 +1048,10 @@ Flickable {
text: "Čeština" // Czech
val: StreamingPreferences.LANG_CS
}
/* ListElement {
text: "עִבְרִית" // Hebrew
val: StreamingPreferences.LANG_HE
} */
}
// ::onActivated must be used, as it only listens for when the index is changed by a human
onActivated : {
@@ -1386,7 +1559,6 @@ Flickable {
id: codecComboBox
textRole: "text"
enabled: !enableHdr.checked
model: ListModel {
id: codecListModel
ListElement {
@@ -1401,6 +1573,10 @@ Flickable {
text: qsTr("HEVC (H.265)")
val: StreamingPreferences.VCC_FORCE_HEVC
}
ListElement {
text: qsTr("AV1 (Experimental)")
val: StreamingPreferences.VCC_FORCE_AV1
}
}
// ::onActivated must be used, as it only listens for when the index is changed by a human
onActivated : {
@@ -1408,21 +1584,6 @@ Flickable {
StreamingPreferences.videoCodecConfig = codecListModel.get(currentIndex).val
}
}
// This handles the state of the enableHdr checkbox changing
onEnabledChanged: {
if (enabled) {
StreamingPreferences.videoCodecConfig = codecListModel.get(currentIndex).val
}
else {
StreamingPreferences.videoCodecConfig = StreamingPreferences.VCC_FORCE_HEVC_HDR
}
}
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered && !enabled
ToolTip.text: qsTr("Enabling HDR overrides manual codec selections.")
}
CheckBox {
@@ -1430,8 +1591,12 @@ Flickable {
width: parent.width
text: qsTr("Enable HDR (Experimental)")
font.pointSize: 12
enabled: SystemProperties.supportsHdr
checked: enabled && StreamingPreferences.videoCodecConfig == StreamingPreferences.VCC_FORCE_HEVC_HDR
checked: enabled && StreamingPreferences.enableHdr
onCheckedChanged: {
StreamingPreferences.enableHdr = checked
}
// Updating StreamingPreferences.videoCodecConfig is handled above
@@ -1444,25 +1609,6 @@ Flickable {
qsTr("HDR streaming is not supported on this PC.")
}
CheckBox {
id: unlockUnsupportedFps
width: parent.width
text: qsTr("Unlock unsupported FPS options")
font.pointSize: 12
checked: StreamingPreferences.unsupportedFps
onCheckedChanged: {
// This is called on init, so only do the work if we've
// actually changed the value.
if (StreamingPreferences.unsupportedFps != checked) {
StreamingPreferences.unsupportedFps = checked
// The selectable FPS values depend on whether
// this option is enabled or not
fpsComboBox.reinitialize()
}
}
}
CheckBox {
id: enableMdns
width: parent.width
+2
View File
@@ -45,6 +45,7 @@ Item {
{
// Display the error dialog after Session::exec() returns
streamSegueErrorDialog.text = text
console.error(text)
}
function displayLaunchWarning(text)
@@ -56,6 +57,7 @@ Item {
toast.text = text
toast.timeout = 3000
toast.visible = true
console.warn(text)
}
function quitStarting()
+11 -19
View File
@@ -2,8 +2,6 @@
#include <QThreadPool>
#include <random>
ComputerModel::ComputerModel(QObject* object)
: QAbstractListModel(object) {}
@@ -140,14 +138,9 @@ void ComputerModel::renameComputer(int computerIndex, QString name)
m_ComputerManager->renameHost(m_Computers[computerIndex], name);
}
// TODO: Use QRandomGenerator when we drop Qt 5.9 support
QString ComputerModel::generatePinString()
{
std::uniform_int_distribution<int> dist(0, 9999);
std::random_device rd;
std::mt19937 engine(rd());
return QString::asprintf("%04u", dist(engine));
return m_ComputerManager->generatePinString();
}
class DeferredTestConnectionTask : public QObject, public QRunnable
@@ -193,19 +186,18 @@ void ComputerModel::handlePairingCompleted(NvComputer*, QString error)
void ComputerModel::handleComputerStateChanged(NvComputer* computer)
{
// If this is an existing computer, we can report the data changed
int index = m_Computers.indexOf(computer);
if (index >= 0) {
// Let the view know that this specific computer changed
emit dataChanged(createIndex(index, 0), createIndex(index, 0));
QVector<NvComputer*> newComputerList = m_ComputerManager->getComputers();
// Reset the model if the structural layout of the list has changed
if (m_Computers != newComputerList) {
beginResetModel();
m_Computers = newComputerList;
endResetModel();
}
else {
// This is a new PC which may be inserted at an arbitrary point
// in our computer list (since it comes from CM's QMap). Reload
// the whole model state to ensure it stays consistent.
beginResetModel();
m_Computers = m_ComputerManager->getComputers();
endResetModel();
// Let the view know that this specific computer changed
int index = m_Computers.indexOf(computer);
emit dataChanged(createIndex(index, 0), createIndex(index, 0));
}
}
+11 -1
View File
@@ -2,6 +2,7 @@ import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
import QtQuick.Controls.Material 2.2
import ComputerManager 1.0
import AutoUpdateChecker 1.0
@@ -22,6 +23,15 @@ ApplicationWindow {
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: {
if (SystemProperties.usesMaterial3Theme) {
Material.background = "#303030"
}
}
visibility: {
if (SystemProperties.hasDesktopEnvironment) {
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_WINDOWED) return "Windowed"
@@ -488,7 +498,7 @@ ApplicationWindow {
NavigableDialog {
id: addPcDialog
property string label: qsTr("Enter the IP address of your GameStream PC:")
property string label: qsTr("Enter the IP address of your host PC:")
standardButtons: Dialog.Ok | Dialog.Cancel
+11 -42
View File
@@ -191,7 +191,17 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
case SDL_CONTROLLERDEVICEADDED:
SDL_GameController* gc = SDL_GameControllerOpen(event.cdevice.which);
if (gc != nullptr) {
m_Gamepads.append(gc);
// SDL_CONTROLLERDEVICEADDED can be reported multiple times for the same
// gamepad in rare cases, because SDL doesn't fixup the device index in
// the SDL_CONTROLLERDEVICEADDED event if an unopened gamepad disappears
// before we've processed the add event.
if (!m_Gamepads.contains(gc)) {
m_Gamepads.append(gc);
}
else {
// We already have this game controller open
SDL_GameControllerClose(gc);
}
}
break;
}
@@ -239,31 +249,6 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
sendKey(QEvent::Type::KeyRelease, Qt::Key_Right);
m_LastAxisNavigationEventTime = SDL_GetTicks();
}
// In UI navigation mode (settings page), use the right stick to scroll
if (m_UiNavMode) {
short rightX = SDL_GameControllerGetAxis(gc, SDL_CONTROLLER_AXIS_RIGHTX);
short rightY = SDL_GameControllerGetAxis(gc, SDL_CONTROLLER_AXIS_RIGHTY);
QPoint wheelDelta;
if (rightX > 30000) {
wheelDelta.setX(30);
}
else if (rightX < -30000) {
wheelDelta.setX(-30);
}
if (rightY > 30000) {
wheelDelta.setY(-30);
}
else if (rightY < -30000) {
wheelDelta.setY(30);
}
if (!wheelDelta.isNull()) {
sendWheel(wheelDelta);
}
}
}
}
@@ -277,22 +262,6 @@ void SdlGamepadKeyNavigation::sendKey(QEvent::Type type, Qt::Key key, Qt::Keyboa
}
}
void SdlGamepadKeyNavigation::sendWheel(QPoint& angleDelta)
{
QGuiApplication* app = static_cast<QGuiApplication*>(QGuiApplication::instance());
QWindow* focusWindow = app->focusWindow();
if (focusWindow != nullptr) {
QPoint mousePos(focusWindow->width() / 2, focusWindow->height() / 2);
QPoint globalPos(focusWindow->mapToGlobal(mousePos));
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
QWheelEvent wheelEvent(mousePos, globalPos, QPoint(), angleDelta, Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false, Qt::MouseEventSynthesizedByApplication);
#else
QWheelEvent wheelEvent(mousePos, globalPos, QPoint(), angleDelta, angleDelta.y(), Qt::Vertical, Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, Qt::MouseEventSynthesizedByApplication, false);
#endif
app->sendEvent(focusWindow, &wheelEvent);
}
}
void SdlGamepadKeyNavigation::setUiNavMode(bool uiNavMode)
{
m_UiNavMode = uiNavMode;
-1
View File
@@ -24,7 +24,6 @@ public:
private:
void sendKey(QEvent::Type type, Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
void sendWheel(QPoint& angleDelta);
private slots:
void onPollingTimerFired();
Binary file not shown.
+353 -283
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+419 -349
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+347 -281
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+353 -287
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+363 -332
View 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.
+347 -273
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+650 -560
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+354 -288
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+348 -278
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+355 -285
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+347 -281
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+348 -282
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+351 -281
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+363 -297
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+366 -288
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+362 -296
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+490 -408
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+348 -274
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+351 -265
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+360 -270
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+350 -284
View 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.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
+137 -43
View File
@@ -28,14 +28,17 @@
#include <openssl/ssl.h>
#endif
#include "cli/listapps.h"
#include "cli/quitstream.h"
#include "cli/startstream.h"
#include "cli/pair.h"
#include "cli/commandlineparser.h"
#include "path.h"
#include "utils.h"
#include "gui/computermodel.h"
#include "gui/appmodel.h"
#include "backend/autoupdatechecker.h"
#include "backend/computermanager.h"
#include "backend/systemproperties.h"
#include "streaming/session.h"
#include "settings/streamingpreferences.h"
@@ -60,9 +63,11 @@
static QElapsedTimer s_LoggerTime;
static QTextStream s_LoggerStream(stdout);
static QMutex s_LoggerLock;
static bool s_SuppressVerboseOutput;
#ifdef LOG_TO_FILE
#define MAX_LOG_LINES 10000
static int s_LogLinesWritten = 0;
// 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 QFile* s_LoggerFile;
#endif
@@ -75,7 +80,7 @@ void logToLoggerStream(QString& message)
if (s_LogLimitReached) {
return;
}
else if (s_LogLinesWritten == MAX_LOG_LINES) {
else if (s_LogBytesWritten >= MAX_LOG_SIZE_BYTES) {
s_LoggerStream << "Log size limit reached!";
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
s_LoggerStream << Qt::endl;
@@ -86,7 +91,7 @@ void logToLoggerStream(QString& message)
return;
}
else {
s_LogLinesWritten++;
s_LogBytesWritten += message.size();
}
#endif
@@ -100,15 +105,27 @@ void sdlLogToDiskHandler(void*, int category, SDL_LogPriority priority, const ch
switch (priority) {
case SDL_LOG_PRIORITY_VERBOSE:
if (s_SuppressVerboseOutput) {
return;
}
priorityTxt = "Verbose";
break;
case SDL_LOG_PRIORITY_DEBUG:
if (s_SuppressVerboseOutput) {
return;
}
priorityTxt = "Debug";
break;
case SDL_LOG_PRIORITY_INFO:
if (s_SuppressVerboseOutput) {
return;
}
priorityTxt = "Info";
break;
case SDL_LOG_PRIORITY_WARN:
if (s_SuppressVerboseOutput) {
return;
}
priorityTxt = "Warn";
break;
case SDL_LOG_PRIORITY_ERROR:
@@ -134,12 +151,21 @@ void qtLogToDiskHandler(QtMsgType type, const QMessageLogContext&, const QString
switch (type) {
case QtDebugMsg:
if (s_SuppressVerboseOutput) {
return;
}
typeTxt = "Debug";
break;
case QtInfoMsg:
if (s_SuppressVerboseOutput) {
return;
}
typeTxt = "Info";
break;
case QtWarningMsg:
if (s_SuppressVerboseOutput) {
return;
}
typeTxt = "Warning";
break;
case QtCriticalMsg:
@@ -166,6 +192,9 @@ void ffmpegLogToDiskHandler(void* ptr, int level, const char* fmt, va_list vl)
if ((level & 0xFF) > av_log_get_level()) {
return;
}
else if ((level & 0xFF) > AV_LOG_WARNING && s_SuppressVerboseOutput) {
return;
}
// We need to use the *previous* printPrefix value to determine whether to
// print the prefix this time. av_log_format_line() will set the printPrefix
@@ -260,7 +289,6 @@ int main(int argc, char *argv[])
QCoreApplication::setApplicationName("Moonlight");
if (QFile(QDir::currentPath() + "/portable.dat").exists()) {
qInfo() << "Running in portable mode from:" << QDir::currentPath();
QSettings::setDefaultFormat(QSettings::IniFormat);
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, QDir::currentPath());
QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, QDir::currentPath());
@@ -273,12 +301,17 @@ int main(int argc, char *argv[])
Path::initialize(false);
}
// Override the default QML cache directory with the one we chose
if (qEnvironmentVariableIsEmpty("QML_DISK_CACHE_PATH")) {
qputenv("QML_DISK_CACHE_PATH", Path::getQmlCacheDir().toUtf8());
}
#ifdef USE_CUSTOM_LOGGER
#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)) {
qInfo() << "Redirecting log output to " << s_LoggerFile->fileName();
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
@@ -298,6 +331,15 @@ int main(int argc, char *argv[])
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
#endif
#ifdef LOG_TO_FILE
// Prune the oldest existing logs if there are more than 10
QStringList existingLogNames = tempDir.entryList(QStringList("Moonlight-*.log"), QDir::NoFilter, QDir::SortFlag::Time);
for (int i = 10; i < existingLogNames.size(); i++) {
qInfo() << "Removing old log file:" << existingLogNames.at(i);
QFile(tempDir.filePath(existingLogNames.at(i))).remove();
}
#endif
#if defined(Q_OS_WIN32)
// Force AntiHooking.dll to be statically imported and loaded
// by ntdll on Win32 platforms by calling a dummy function.
@@ -343,6 +385,11 @@ int main(int argc, char *argv[])
qWarning() << "On the Raspberry Pi, you must enable the 'fake KMS' driver in raspi-config to use Moonlight outside of the GUI environment.";
}
}
// EGLFS uses OpenGLES 2.0, so we will too. Some embedded platforms may not
// even have working OpenGL implementations, so GLES is the only option.
// See https://github.com/moonlight-stream/moonlight-qt/issues/868
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengles2");
#endif
}
@@ -381,18 +428,6 @@ int main(int argc, char *argv[])
// Register custom metatypes for use in signals
qRegisterMetaType<NvApp>("NvApp");
SDL_version compileVersion;
SDL_VERSION(&compileVersion);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Compiled with SDL %d.%d.%d",
compileVersion.major, compileVersion.minor, compileVersion.patch);
SDL_version runtimeVersion;
SDL_GetVersion(&runtimeVersion);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Running with SDL %d.%d.%d",
runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch);
// Allow the display to sleep by default. We will manually use SDL_DisableScreenSaver()
// and SDL_EnableScreenSaver() when appropriate. This hint must be set before
// initializing the SDL video subsystem to have any effect.
@@ -470,10 +505,58 @@ int main(int argc, char *argv[])
QGuiApplication app(argc, argv);
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 (AttachConsole(ATTACH_PARENT_PROCESS)) {
HANDLE conOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (conOut != INVALID_HANDLE_VALUE && conOut != NULL) {
freopen("CONOUT$", "w", stdout);
setvbuf(stdout, NULL, _IONBF, 0);
}
HANDLE conErr = GetStdHandle(STD_ERROR_HANDLE);
if (conErr != INVALID_HANDLE_VALUE && conErr != NULL) {
freopen("CONOUT$", "w", stderr);
setvbuf(stderr, NULL, _IONBF, 0);
}
}
#endif
break;
default:
break;
}
SDL_version compileVersion;
SDL_VERSION(&compileVersion);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Compiled with SDL %d.%d.%d",
compileVersion.major, compileVersion.minor, compileVersion.patch);
SDL_version runtimeVersion;
SDL_GetVersion(&runtimeVersion);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Running with SDL %d.%d.%d",
runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch);
// Apply the initial translation based on user preference
StreamingPreferences prefs;
prefs.retranslate();
// Trickily declare the translation for dialog buttons
QCoreApplication::translate("QPlatformTheme", "&Yes");
QCoreApplication::translate("QPlatformTheme", "&No");
QCoreApplication::translate("QPlatformTheme", "OK");
QCoreApplication::translate("QPlatformTheme", "Help");
QCoreApplication::translate("QPlatformTheme", "Cancel");
// After the QGuiApplication is created, the platform stuff will be initialized
// and we can set the SDL video driver to match Qt.
if (WMUtils::isRunningWayland() && QGuiApplication::platformName() == "xcb") {
@@ -557,35 +640,25 @@ int main(int argc, char *argv[])
// Create the identity manager on the main thread
IdentityManager::get();
#ifndef Q_OS_WINRT
// Use the dense material dark theme by default
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_STYLE")) {
qputenv("QT_QUICK_CONTROLS_STYLE", "Material");
}
#else
// Use universal dark on WinRT
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_STYLE")) {
qputenv("QT_QUICK_CONTROLS_STYLE", "Universal");
}
#endif
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MATERIAL_THEME")) {
qputenv("QT_QUICK_CONTROLS_MATERIAL_THEME", "Dark");
}
// We require the Material theme
QQuickStyle::setStyle("Material");
// Our icons are styled for a dark theme, so we do not allow the user to override this
qputenv("QT_QUICK_CONTROLS_MATERIAL_THEME", "Dark");
// These are defaults that we allow the user to override
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MATERIAL_ACCENT")) {
qputenv("QT_QUICK_CONTROLS_MATERIAL_ACCENT", "Purple");
}
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MATERIAL_VARIANT")) {
qputenv("QT_QUICK_CONTROLS_MATERIAL_VARIANT", "Dense");
}
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_UNIVERSAL_THEME")) {
qputenv("QT_QUICK_CONTROLS_UNIVERSAL_THEME", "Dark");
}
QQmlApplicationEngine engine;
QString initialView;
bool hasGUI = true;
GlobalCommandLineParser parser;
switch (parser.parse(app.arguments())) {
switch (commandLineParserResult) {
case GlobalCommandLineParser::NormalStartRequested:
initialView = "qrc:/gui/PcView.qml";
break;
@@ -610,14 +683,35 @@ int main(int argc, char *argv[])
engine.rootContext()->setContextProperty("launcher", launcher);
break;
}
case GlobalCommandLineParser::PairRequested:
{
initialView = "qrc:/gui/CliPair.qml";
PairCommandLineParser pairParser;
pairParser.parse(app.arguments());
auto launcher = new CliPair::Launcher(pairParser.getHost(), pairParser.getPredefinedPin(), &app);
engine.rootContext()->setContextProperty("launcher", launcher);
break;
}
case GlobalCommandLineParser::ListRequested:
{
ListCommandLineParser listParser;
listParser.parse(app.arguments());
auto launcher = new CliListApps::Launcher(listParser.getHost(), listParser, &app);
launcher->execute(new ComputerManager(&app));
hasGUI = false;
break;
}
}
engine.rootContext()->setContextProperty("initialView", initialView);
if (hasGUI) {
engine.rootContext()->setContextProperty("initialView", initialView);
// Load the main.qml file
engine.load(QUrl(QStringLiteral("qrc:/gui/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
}
// Load the main.qml file
engine.load(QUrl(QStringLiteral("qrc:/gui/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
int err = app.exec();
// Give worker tasks time to properly exit. Fixes PendingQuitTask
+38 -50
View File
@@ -11,6 +11,10 @@
#define _GNU_SOURCE
// NOTE: This file MUST NOT include fcntl.h due to open() -> open64()
// redirection that happens when _FILE_OFFSET_BITS=64!
// See masterhook_internal.c for details.
#include <SDL.h>
#include <dlfcn.h>
#include <unistd.h>
@@ -30,92 +34,76 @@
// Qt's DRM master FD grabbed by our hook
int g_QtDrmMasterFd = -1;
struct stat64 g_DrmMasterStat;
struct stat g_DrmMasterStat;
// The DRM master FD created for SDL
int g_SdlDrmMasterFd = -1;
int drmIsMaster(int fd)
{
// Detect master by attempting something that requires master.
// This method is available in Mesa DRM since Feb 2019.
return drmAuthMagic(fd, 0) != -EACCES;
}
// This hook will handle legacy DRM rendering
int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
uint32_t x, uint32_t y, uint32_t *connectors, int count,
drmModeModeInfoPtr mode)
{
// Grab the first DRM FD that makes it in here. This will be the Qt
// EGLFS backend's DRM FD, which we will need later.
if (g_QtDrmMasterFd == -1) {
// Grab the first DRM Master FD that makes it in here. This will be the Qt
// EGLFS backend's DRM FD, on which we will call drmDropMaster() later.
if (g_QtDrmMasterFd == -1 && drmIsMaster(fd)) {
g_QtDrmMasterFd = fd;
fstat64(g_QtDrmMasterFd, &g_DrmMasterStat);
fstat(g_QtDrmMasterFd, &g_DrmMasterStat);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Captured Qt EGLFS DRM master fd (legacy): %d",
g_QtDrmMasterFd);
}
// Call into the real thing
return ((typeof(drmModeSetCrtc)*)dlsym(RTLD_NEXT, "drmModeSetCrtc"))(fd, crtcId, bufferId, x, y, connectors, count, mode);
return ((typeof(drmModeSetCrtc)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, crtcId, bufferId, x, y, connectors, count, mode);
}
// This hook will handle atomic DRM rendering
int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req,
uint32_t flags, void *user_data)
{
// Grab the first DRM FD that makes it in here. This will be the Qt
// EGLFS backend's DRM FD, which we will need later.
if (g_QtDrmMasterFd == -1) {
// Grab the first DRM Master FD that makes it in here. This will be the Qt
// EGLFS backend's DRM FD, on which we will call drmDropMaster() later.
if (g_QtDrmMasterFd == -1 && drmIsMaster(fd)) {
g_QtDrmMasterFd = fd;
fstat64(g_QtDrmMasterFd, &g_DrmMasterStat);
fstat(g_QtDrmMasterFd, &g_DrmMasterStat);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Captured Qt EGLFS DRM master fd (atomic): %d",
g_QtDrmMasterFd);
}
// Call into the real thing
return ((typeof(drmModeAtomicCommit)*)dlsym(RTLD_NEXT, "drmModeAtomicCommit"))(fd, req, flags, user_data);
return ((typeof(drmModeAtomicCommit)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, req, flags, user_data);
}
// This hook will handle SDL's open() on the DRM device. We just need to
// hook this variant of open(), since that's what SDL uses. When we see
// the open a FD for the same card as the Qt DRM master FD, we'll drop
// master on the Qt FD to allow the new FD to have master.
int open(const char *pathname, int flags)
int openHook(const char *funcname, const char *pathname, int flags, va_list va);
int open(const char *pathname, int flags, ...)
{
// Call the real thing to do the open operation
int fd = ((typeof(open)*)dlsym(RTLD_NEXT, "open"))(pathname, flags);
// If the file was successfully opened and we have a DRM master FD,
// check if the FD we just opened is a DRM device.
if (fd >= 0 && g_QtDrmMasterFd != -1) {
if (strncmp(pathname, "/dev/dri/card", 13) == 0) {
// It's a DRM device, but is it _our_ DRM device?
struct stat64 fdstat;
fstat64(fd, &fdstat);
if (g_DrmMasterStat.st_dev == fdstat.st_dev &&
g_DrmMasterStat.st_ino == fdstat.st_ino) {
// It is our device. Time to do the magic!
// This code assumes SDL only ever opens a single FD
// for a given DRM device.
SDL_assert(g_SdlDrmMasterFd == -1);
// Drop master on Qt's FD so we can pick it up for SDL.
if (drmDropMaster(g_QtDrmMasterFd) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to drop master on Qt DRM FD: %d",
errno);
// Hope for the best
return fd;
}
// We are not allowed to call drmSetMaster() without CAP_SYS_ADMIN,
// but since we just dropped the master, we can become master by
// simply creating a new FD. Let's do it.
close(fd);
fd = ((typeof(open)*)dlsym(RTLD_NEXT, "open"))(pathname, flags);
g_SdlDrmMasterFd = fd;
}
}
}
va_list va;
va_start(va, flags);
int fd = openHook(__FUNCTION__, pathname, flags, va);
va_end(va);
return fd;
}
int open64(const char *pathname, int flags, ...)
{
va_list va;
va_start(va, flags);
int fd = openHook(__FUNCTION__, pathname, flags, va);
va_end(va);
return fd;
}
@@ -124,7 +112,7 @@ int open(const char *pathname, int flags)
int close(int fd)
{
// Call the real thing
int ret = ((typeof(close)*)dlsym(RTLD_NEXT, "close"))(fd);
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
+90
View File
@@ -0,0 +1,90 @@
// This file contains the private implementation of the open() hook
// which must be in a separate compilation unit due to fcntl.h doing
// unwanted redirection of open() to open64().
#define _GNU_SOURCE
#include <SDL.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
// __OPEN_NEEDS_MODE is a glibc-ism, so define it ourselves for other libc
#ifndef __OPEN_NEEDS_MODE
#ifdef __O_TMPFILE
# define __OPEN_NEEDS_MODE(oflag) \
(((oflag) & O_CREAT) != 0 || ((oflag) & __O_TMPFILE) == __O_TMPFILE)
#else
# define __OPEN_NEEDS_MODE(oflag) (((oflag) & O_CREAT) != 0)
#endif
#endif
#if SDL_VERSION_ATLEAST(2, 0, 15)
extern int g_QtDrmMasterFd;
extern struct stat g_DrmMasterStat;
extern int g_SdlDrmMasterFd;
int openHook(const char *funcname, const char *pathname, int flags, va_list va)
{
int fd;
mode_t mode;
// Call the real thing to do the open operation
if (__OPEN_NEEDS_MODE(flags)) {
mode = va_arg(va, mode_t);
fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags, mode);
}
else {
mode = 0;
fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags);
}
// If the file was successfully opened and we have a DRM master FD,
// check if the FD we just opened is a DRM device.
if (fd >= 0 && g_QtDrmMasterFd != -1) {
if (strncmp(pathname, "/dev/dri/card", 13) == 0) {
// It's a DRM device, but is it _our_ DRM device?
struct stat fdstat;
fstat(fd, &fdstat);
if (g_DrmMasterStat.st_dev == fdstat.st_dev &&
g_DrmMasterStat.st_ino == fdstat.st_ino) {
// It is our device. Time to do the magic!
// This code assumes SDL only ever opens a single FD
// for a given DRM device.
SDL_assert(g_SdlDrmMasterFd == -1);
// Drop master on Qt's FD so we can pick it up for SDL.
if (drmDropMaster(g_QtDrmMasterFd) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to drop master on Qt DRM FD: %d",
errno);
// Hope for the best
return fd;
}
// We are not allowed to call drmSetMaster() without CAP_SYS_ADMIN,
// but since we just dropped the master, we can become master by
// simply creating a new FD. Let's do it.
close(fd);
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;
}
}
}
return fd;
}
#endif
+9
View File
@@ -9,6 +9,7 @@
QString Path::s_CacheDir;
QString Path::s_LogDir;
QString Path::s_BoxArtCacheDir;
QString Path::s_QmlCacheDir;
QString Path::getLogDir()
{
@@ -22,6 +23,12 @@ QString Path::getBoxArtCacheDir()
return s_BoxArtCacheDir;
}
QString Path::getQmlCacheDir()
{
Q_ASSERT(!s_QmlCacheDir.isEmpty());
return s_QmlCacheDir;
}
QByteArray Path::readDataFile(QString fileName)
{
QFile dataFile(getDataFilePath(fileName));
@@ -97,6 +104,7 @@ void Path::initialize(bool portable)
if (portable) {
s_LogDir = QDir::currentPath();
s_BoxArtCacheDir = QDir::currentPath() + "/boxart";
s_QmlCacheDir = QDir::currentPath() + "/qmlcache";
// In order for the If-Modified-Since logic to work in MappingFetcher,
// the cache directory must be different than the current directory.
@@ -112,5 +120,6 @@ void Path::initialize(bool portable)
#endif
s_CacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
s_BoxArtCacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/boxart";
s_QmlCacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/qmlcache";
}
}
+2 -1
View File
@@ -7,8 +7,8 @@ class Path
{
public:
static QString getLogDir();
static QString getBoxArtCacheDir();
static QString getQmlCacheDir();
static QByteArray readDataFile(QString fileName);
static void writeCacheFile(QString fileName, QByteArray data);
@@ -24,4 +24,5 @@ private:
static QString s_CacheDir;
static QString s_LogDir;
static QString s_BoxArtCacheDir;
static QString s_QmlCacheDir;
};
+1
View File
@@ -18,5 +18,6 @@
<file>gui/NavigableMessageDialog.qml</file>
<file>gui/NavigableDialog.qml</file>
<file>gui/CenteredGridView.qml</file>
<file>gui/CliPair.qml</file>
</qresource>
</RCC>
+2
View File
@@ -0,0 +1,2 @@
[Platforms]
WindowsArguments = darkmode=1
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#fff"><path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/><path d="M0 0h24v24H0z" fill="none"/></svg>

Before

Width:  |  Height:  |  Size: 325 B

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="#fff">
<path d="M0 0h48v48H0z" fill="none"/>
<path d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm-4 29V15l12 9-12 9z"/>
</svg>

Before

Width:  |  Height:  |  Size: 250 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48" fill="#FFFFFF"><path d="M370-206q-26 14-49 1t-23-40v-477q0-26 23-39t49 1l372 239q23 14 23 38.5T742-445L370-206Z"/></svg>

After

Width:  |  Height:  |  Size: 208 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48" fill="#FFFFFF"><path d="M309-218q-38.425 0-64.712-26.288Q218-270.575 218-309v-342q0-38.838 26.288-65.419Q270.575-743 309-743h342q38.838 0 65.419 26.581Q743-689.838 743-651v342q0 38.425-26.581 64.712Q689.838-218 651-218H309Z"/></svg>

After

Width:  |  Height:  |  Size: 320 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="white"><path d="M108.018-98.001q-14.17 0-25.267-7.472-11.097-7.471-17.595-17.605-6.68-10.077-7.147-23-.468-12.923 7.085-26.165l372.43-640.512q7.553-13.242 18.399-18.665 10.846-5.424 24.077-5.424 13.231 0 24.077 5.424 10.846 5.423 18.399 18.665l372.43 640.512q7.553 13.242 7.085 26.165-.468 12.923-7.147 23-6.498 10.134-17.595 17.605-11.097 7.472-25.267 7.472H108.018ZM481-220.693q18.731 0 32.019-13.288 13.288-13.288 13.288-32.019 0-18.731-13.288-32.019-13.288-13.288-32.019-13.288-18.731 0-32.019 13.288-13.288 13.288-13.288 32.019 0 18.731 13.288 32.019 13.288 13.288 32.019 13.288Zm.013-127.614q17.756 0 30.371-12.625 12.615-12.625 12.615-30.374v-118.002q0-17.75-12.628-30.374-12.629-12.625-30.384-12.625-17.756 0-30.371 12.625-12.615 12.624-12.615 30.374v118.002q0 17.749 12.628 30.374 12.629 12.625 30.384 12.625Z"/></svg>

After

Width:  |  Height:  |  Size: 921 B

+9 -6
View File
@@ -4,9 +4,10 @@
<file>res/desktop_windows-48px.svg</file>
<file>res/ic_add_to_queue_white_48px.svg</file>
<file>res/baseline-lock-24px.svg</file>
<file>res/baseline-play_circle_filled_white-48px.svg</file>
<file>res/play_arrow_FILL1_wght700_GRAD200_opsz48.svg</file>
<file>res/baseline-warning-24px.svg</file>
<file>res/baseline-cancel-24px.svg</file>
<file>res/warning_FILL1_wght300_GRAD200_opsz24.svg</file>
<file>res/stop_FILL1_wght700_GRAD200_opsz48.svg</file>
<file>res/no_app_image.png</file>
<file>res/settings.svg</file>
<file>res/arrow_left.svg</file>
@@ -21,8 +22,8 @@
<file>languages/qml_de.qm</file>
<file>languages/qml_fr.ts</file>
<file>languages/qml_fr.qm</file>
<file>languages/qml_zh_cn.ts</file>
<file>languages/qml_zh_cn.qm</file>
<file>languages/qml_zh_CN.ts</file>
<file>languages/qml_zh_CN.qm</file>
<file>languages/qml_nb_NO.ts</file>
<file>languages/qml_nb_NO.qm</file>
<file>languages/qml_ru.ts</file>
@@ -47,8 +48,8 @@
<file>languages/qml_tr.qm</file>
<file>languages/qml_uk.ts</file>
<file>languages/qml_uk.qm</file>
<file>languages/qml_zh_Hant.ts</file>
<file>languages/qml_zh_Hant.qm</file>
<file>languages/qml_zh_TW.ts</file>
<file>languages/qml_zh_TW.qm</file>
<file>languages/qml_el.ts</file>
<file>languages/qml_el.qm</file>
<file>languages/qml_hi.ts</file>
@@ -61,6 +62,8 @@
<file>languages/qml_pl.qm</file>
<file>languages/qml_cs.ts</file>
<file>languages/qml_cs.qm</file>
<file>languages/qml_he.ts</file>
<file>languages/qml_he.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-->
+18 -7
View File
@@ -8,26 +8,32 @@
#define COMPAT_KEY "latestsupportedversion-"
CompatFetcher::CompatFetcher(QObject *parent) :
QObject(parent),
m_Nam(this)
QObject(parent)
{
m_Nam = new QNetworkAccessManager(this);
// Never communicate over HTTP
m_Nam.setStrictTransportSecurityEnabled(true);
m_Nam->setStrictTransportSecurityEnabled(true);
// Allow HTTP redirects
m_Nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
m_Nam->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
connect(&m_Nam, &QNetworkAccessManager::finished,
connect(m_Nam, &QNetworkAccessManager::finished,
this, &CompatFetcher::handleCompatInfoFetched);
}
void CompatFetcher::start()
{
if (!m_Nam) {
Q_ASSERT(m_Nam);
return;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && QT_VERSION < QT_VERSION_CHECK(5, 15, 1) && !defined(QT_NO_BEARERMANAGEMENT)
// HACK: Set network accessibility to work around QTBUG-80947 (introduced in Qt 5.14.0 and fixed in Qt 5.15.1)
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
m_Nam.setNetworkAccessible(QNetworkAccessManager::Accessible);
m_Nam->setNetworkAccessible(QNetworkAccessManager::Accessible);
QT_WARNING_POP
#endif
@@ -41,7 +47,7 @@ void CompatFetcher::start()
#endif
// We'll get a callback when this is finished
m_Nam.get(request);
m_Nam->get(request);
}
bool CompatFetcher::isGfeVersionSupported(QString gfeVersion)
@@ -119,6 +125,11 @@ void CompatFetcher::handleCompatInfoFetched(QNetworkReply* reply)
{
Q_ASSERT(reply->isFinished());
// Delete the QNetworkAccessManager to free resources and
// prevent the bearer plugin from polling in the background.
m_Nam->deleteLater();
m_Nam = nullptr;
if (reply->error() == QNetworkReply::NoError) {
// Queue the reply for deletion
reply->deleteLater();
+1 -1
View File
@@ -18,5 +18,5 @@ private slots:
void handleCompatInfoFetched(QNetworkReply* reply);
private:
QNetworkAccessManager m_Nam;
QNetworkAccessManager* m_Nam;
};

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