Compare commits

..
304 Commits
Author SHA1 Message Date
Cameron Gutman 60fadcbf36 Add Qt EGLFS plugin to AppImage 2023-11-07 00:55:21 -06:00
Cameron Gutman f374e88bac Update README.md
Added new SBC packages and updated build requirements
2023-11-03 00:13:50 -05:00
Cameron Gutman 3cb169ea26 Fix CUDA build without KMSDRM enabled 2023-11-01 22:49:18 -05:00
Cameron Gutman 58f87e39a9 Prepare for v5.0.1 2023-11-01 21:49:03 -05:00
Cameron Gutman b1c8005d1a Disable libdrm in AppImage build 2023-11-01 21:44:44 -05:00
Cathy J. Fitzpatrick f8a2e22a18 Update moonlight-common-c to eb67a2fd (2023-10-26) 2023-11-01 20:31:19 -05:00
Cameron Gutman 7ea118fbae Fix dark mode on Windows with Qt 6.5+ 2023-11-01 20:30:34 -05:00
Cameron Gutman 2149024c35 Rerun lupdate and lrelease 2023-11-01 20:02:40 -05:00
Cameron Gutman f5d71c8c9a Merge remote-tracking branch 'origin/weblate' 2023-11-01 20:01:25 -05:00
Cameron Gutman e108a05895 Bump WiX SDK to 4.0.2 2023-11-01 19:58:29 -05:00
Cameron Gutman ea8bccd508 Fix typo causing libva-drm only to be enabled if libva-wayland is present 2023-11-01 17:39:24 -05:00
maicolbattistini bca919c48c Translated using Weblate (Italian)
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/it/
2023-10-21 04:13:46 +00:00
Jorys Paulin 511ffe03b5 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-21 04:13:45 +00:00
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
162 changed files with 13657 additions and 9660 deletions
+18 -20
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)
@@ -28,37 +26,36 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
- [Flatpak (for other Linux distros)](https://flathub.org/apps/details/com.moonlight_stream.Moonlight)
- [AppImage](https://github.com/moonlight-stream/moonlight-qt/releases)
- [Raspberry Pi 4](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-Raspberry-Pi-4)
- [Generic ARM SBC packages for Debian](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-ARM%E2%80%90based-Single-Board-Computers) (not for Raspberry Pi)
- [Experimental RISC-V SBC packages for Debian](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-RISC%E2%80%90V-Single-Board-Computers)
- [NVIDIA Jetson and Nintendo Switch (Ubuntu L4T)](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-Linux4Tegra-(L4T)-Ubuntu)
#### Special Thanks
[![Hosted By: Cloudsmith](https://img.shields.io/badge/OSS%20hosting%20by-cloudsmith-blue?logo=cloudsmith&style=flat-square)](https://cloudsmith.com)
Hosting for Moonlight's Raspberry Pi and L4T package repositories is graciously provided for free by [Cloudsmith](https://cloudsmith.com).
Hosting for Moonlight's Debian and L4T package repositories is graciously provided for free by [Cloudsmith](https://cloudsmith.com).
## Building
### Windows Build Requirements
* Qt 5.15 SDK or later
* Windows 7 or later
* [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) (Community edition is fine)
* Select **MSVC 2019** option during Qt installation. MinGW is not supported.
* Qt 5.15 SDK or later. Qt 6 is also supported for x64 and ARM64 builds.
* [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) (Community edition is fine)
* Select **MSVC** option during Qt installation. MinGW is not supported.
* [7-Zip](https://www.7-zip.org/) (only if building installers for non-development PCs)
* [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
* Qt 6.4 SDK or later
* Xcode 13 or later
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
### Linux/Unix Build Requirements
* Qt 5.9 SDK or later
* Qt 5.9 SDK or later. Qt 6 is also supported.
* 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 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
@@ -68,16 +65,17 @@ Hosting for Moonlight's Raspberry Pi and L4T package repositories is graciously
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
* You can install Qt via Homebrew on macOS, but you will need to use `brew install qt --with-debug` to be able to create debug builds of Moonlight.
* You may also use your Linux distro's package manager for the Qt SDK as long as the packages are Qt 5.9 or later.
* This step is not required for building on Steam Link, because the Steam Link SDK includes Qt 5.9.
* This step is not required for building on Steam Link, because the Steam Link SDK includes Qt 5.14.
2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
3. Open the project in Qt Creator or build from qmake on the command line.
* To build a binary for use on non-development machines, use the scripts in the `scripts` folder.
* For Windows builds, use `scripts\build-arch.bat` and `scripts\generate-bundle.bat`. Execute these scripts from the root of the repository within a Qt command prompt. Ensure 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>
+103 -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
@@ -212,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)
@@ -242,7 +267,7 @@ libva-wayland {
PKGCONFIG += libva-wayland
DEFINES += HAVE_LIBVA_WAYLAND
}
libva-wayland {
libva-drm {
message(VAAPI DRM support enabled)
PKGCONFIG += libva-drm
@@ -261,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)
@@ -281,6 +318,9 @@ cuda {
DEFINES += HAVE_CUDA
SOURCES += streaming/video/ffmpeg-renderers/cuda.cpp
HEADERS += streaming/video/ffmpeg-renderers/cuda.h
# ffnvcodec uses libdl in cuda_load_functions()/cuda_free_functions()
LIBS += -ldl
}
config_EGL {
message(EGL renderer selected)
@@ -289,8 +329,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)
@@ -348,13 +391,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 \
@@ -369,14 +424,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 =
+18 -7
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);
+1 -1
View File
@@ -25,5 +25,5 @@ private:
QString getPlatform();
QVector<int> m_CurrentVersionQuad;
QNetworkAccessManager m_Nam;
QNetworkAccessManager* m_Nam;
};
+78 -24
View File
@@ -13,6 +13,7 @@
#include <random>
#define SER_HOSTS "hosts"
#define SER_HOSTS_BACKUP "hostsbackup"
class PcMonitorThread : public QThread
{
@@ -152,12 +153,21 @@ ComputerManager::ComputerManager(QObject *parent)
{
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();
@@ -169,8 +179,8 @@ ComputerManager::ComputerManager(QObject *parent)
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);
@@ -241,21 +251,48 @@ void DelayedFlushThread::run() {
// 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
{
QReadLocker lock(&m_ComputerManager->m_Lock);
QSettings settings;
settings.remove(SER_HOSTS);
settings.beginWriteArray(SER_HOSTS);
int i = 0;
for (const NvComputer* computer : m_ComputerManager->m_KnownHosts) {
settings.setArrayIndex(i++);
computer->serialize(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);
}
}
}
@@ -319,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);
@@ -405,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);
@@ -414,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
@@ -446,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.
@@ -487,9 +543,6 @@ 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);
}
@@ -547,7 +600,7 @@ private:
break;
case NvPairingManager::PairState::PAIRED:
// Persist the newly pinned server certificate for this host
m_ComputerManager->saveHosts();
m_ComputerManager->saveHost(m_Computer);
emit pairingCompleted(m_Computer, nullptr);
break;
@@ -646,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) {
@@ -658,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);
}
+23 -7
View File
@@ -42,11 +42,11 @@ 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
@@ -94,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);
@@ -103,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;
};
@@ -237,6 +251,8 @@ private slots:
private:
void saveHosts();
void saveHost(NvComputer* computer);
QHostAddress getBestGlobalAddressV6(QVector<QHostAddress>& addresses);
void startPollingComputer(NvComputer* computer);
@@ -245,13 +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;
QMutex m_DelayedFlushMutex; // Lock ordering: Must never be acquired while holding NvComputer lock
QWaitCondition m_DelayedFlushCondition;
bool m_NeedsDelayedFlush;
};
+81 -22
View File
@@ -74,7 +74,7 @@ void NvComputer::setRemoteAddress(QHostAddress address)
this->remoteAddress = NvAddress(address, this->externalPort);
}
void NvComputer::serialize(QSettings& settings) const
void NvComputer::serialize(QSettings& settings, bool serializeApps) const
{
QReadLocker lock(&this->lock);
@@ -94,7 +94,7 @@ void NvComputer::serialize(QSettings& settings) const
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++) {
@@ -105,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) {
@@ -136,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");
@@ -150,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
@@ -169,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");
@@ -219,23 +235,30 @@ bool NvComputer::wake() const
for (int i = 0; i < 16; i++) {
wolPayload.append(macAddress);
}
Q_ASSERT(wolPayload.count() == 102);
Q_ASSERT(wolPayload.size() == 102);
}
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)
};
// 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()) {
@@ -254,31 +277,42 @@ bool NvComputer::wake() const
// 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;
@@ -287,6 +321,31 @@ bool NvComputer::wake() const
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();
}
}
}
}
}
+25 -2
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);
@@ -49,7 +67,11 @@ public:
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
{
@@ -91,9 +113,10 @@ public:
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;
+17 -37
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
+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();
+20 -5
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,7 +72,7 @@ SystemProperties::SystemProperties()
// and cache the results to speed up future queries on this data.
querySdlVideoInfo();
Q_ASSERT(maximumStreamingFrameRate >= 60);
Q_ASSERT(!monitorRefreshRates.isEmpty());
Q_ASSERT(!monitorNativeResolutions.isEmpty());
}
@@ -80,6 +82,12 @@ QRect SystemProperties::getNativeResolution(int displayIndex)
return monitorNativeResolutions.value(displayIndex);
}
int SystemProperties::getRefreshRate(int displayIndex)
{
// Returns 0 if out of bounds
return monitorRefreshRates.value(displayIndex);
}
class QuerySdlVideoThread : public QThread
{
public:
@@ -188,9 +196,6 @@ void SystemProperties::refreshDisplaysInternal()
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;
@@ -218,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 -2
View File
@@ -23,13 +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 getNativeResolution(int displayIndex);
Q_INVOKABLE int getRefreshRate(int displayIndex);
signals:
void unmappedGamepadsChanged();
@@ -49,10 +50,11 @@ private:
bool hasBrowser;
bool hasDiscordIntegration;
QString unmappedGamepads;
int maximumStreamingFrameRate;
QSize maximumResolution;
QList<QRect> monitorNativeResolutions;
QList<int> monitorRefreshRates;
QString versionString;
bool supportsHdr;
bool usesMaterial3Theme;
};
+1 -1
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()) {
@@ -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,65 @@
</screenshots>
<releases>
<release version="5.0.1" date="2023-11-02">
<description>
<p>Bugfixes:</p>
<ul>
<li>Fixed connection issue streaming from some hosts running GeForce Experience or Sunshine v0.20 and earlier</li>
<li>Updated community-contributed translations from Weblate</li>
</ul>
</description>
</release>
<release version="5.0.0" date="2023-10-18">
<description>
<p>New features:</p>
<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>
@@ -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: {
+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()
+20 -15
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()
}
}
@@ -271,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 {
@@ -291,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
@@ -301,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 {
@@ -319,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: {
+242 -85
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.
@@ -388,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
@@ -459,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()
}
}
}
@@ -511,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 {
@@ -546,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
}
@@ -837,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
@@ -868,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 : {
@@ -1375,7 +1559,6 @@ Flickable {
id: codecComboBox
textRole: "text"
enabled: !enableHdr.checked
model: ListModel {
id: codecListModel
ListElement {
@@ -1390,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 : {
@@ -1397,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 {
@@ -1419,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
@@ -1433,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()
+10 -11
View File
@@ -186,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.
+292 -292
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+290 -290
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+289 -293
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+305 -309
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+313 -352
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.
+290 -286
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+661 -633
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+304 -308
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+296 -296
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+291 -291
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+289 -293
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+290 -294
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+304 -304
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+297 -301
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+296 -288
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+290 -294
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+452 -432
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+290 -286
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+291 -267
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+310 -290
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+290 -294
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.
+33 -17
View File
@@ -301,6 +301,11 @@ 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());
@@ -326,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.
@@ -371,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
}
@@ -531,6 +550,13 @@ int main(int argc, char *argv[])
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") {
@@ -614,29 +640,19 @@ 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;
+3 -3
View File
@@ -34,7 +34,7 @@
// 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;
@@ -55,7 +55,7 @@ int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
// 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);
@@ -73,7 +73,7 @@ int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req,
// 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);
+3 -3
View File
@@ -27,7 +27,7 @@
#if SDL_VERSION_ATLEAST(2, 0, 15)
extern int g_QtDrmMasterFd;
extern struct stat64 g_DrmMasterStat;
extern struct stat g_DrmMasterStat;
extern int g_SdlDrmMasterFd;
int openHook(const char *funcname, const char *pathname, int flags, va_list va)
@@ -50,9 +50,9 @@ int openHook(const char *funcname, const char *pathname, int flags, va_list va)
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;
struct stat fdstat;
fstat64(fd, &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!
+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;
};
View File
-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;
};
+18 -7
View File
@@ -4,26 +4,32 @@
#include <QNetworkReply>
MappingFetcher::MappingFetcher(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, &MappingFetcher::handleMappingListFetched);
}
void MappingFetcher::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
@@ -57,13 +63,18 @@ void MappingFetcher::start()
#endif
// We'll get a callback when this is finished
m_Nam.get(request);
m_Nam->get(request);
}
void MappingFetcher::handleMappingListFetched(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
@@ -16,5 +16,5 @@ private slots:
void handleMappingListFetched(QNetworkReply* reply);
private:
QNetworkAccessManager m_Nam;
QNetworkAccessManager* m_Nam;
};
+60 -28
View File
@@ -5,6 +5,7 @@
#include <QTranslator>
#include <QCoreApplication>
#include <QLocale>
#include <QtMath>
#include <QtDebug>
@@ -20,9 +21,9 @@
#define SER_MULTICONT "multicontroller"
#define SER_AUDIOCFG "audiocfg"
#define SER_VIDEOCFG "videocfg"
#define SER_HDR "hdr"
#define SER_VIDEODEC "videodec"
#define SER_WINDOWMODE "windowmode"
#define SER_UNSUPPORTEDFPS "unsupportedfps"
#define SER_MDNS "mdns"
#define SER_QUITAPPAFTER "quitAppAfter"
#define SER_ABSMOUSEMODE "mouseacceleration"
@@ -87,7 +88,6 @@ void StreamingPreferences::reload()
gameOptimizations = settings.value(SER_GAMEOPTS, true).toBool();
playAudioOnHost = settings.value(SER_HOSTAUDIO, false).toBool();
multiController = settings.value(SER_MULTICONT, true).toBool();
unsupportedFps = settings.value(SER_UNSUPPORTEDFPS, false).toBool();
enableMdns = settings.value(SER_MDNS, true).toBool();
quitAppAfter = settings.value(SER_QUITAPPAFTER, false).toBool();
absoluteMouseMode = settings.value(SER_ABSMOUSEMODE, false).toBool();
@@ -104,6 +104,7 @@ void StreamingPreferences::reload()
reverseScrollDirection = settings.value(SER_REVERSESCROLL, false).toBool();
swapFaceButtons = settings.value(SER_SWAPFACEBUTTONS, false).toBool();
keepAwake = settings.value(SER_KEEPAWAKE, true).toBool();
enableHdr = settings.value(SER_HDR, false).toBool();
captureSysKeysMode = static_cast<CaptureSysKeysMode>(settings.value(SER_CAPTURESYSKEYS,
static_cast<int>(CaptureSysKeysMode::CSK_OFF)).toInt());
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
@@ -137,6 +138,12 @@ void StreamingPreferences::reload()
windowMode = WindowMode::WM_FULLSCREEN_DESKTOP;
}
}
// Fixup VCC value to the new settings format with codec and HDR separate
if (videoCodecConfig == VCC_FORCE_HEVC_HDR_DEPRECATED) {
videoCodecConfig = VCC_AUTO;
enableHdr = true;
}
}
bool StreamingPreferences::retranslate()
@@ -202,7 +209,7 @@ QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Langua
case LANG_FR:
return "fr";
case LANG_ZH_CN:
return "zh_cn";
return "zh_CN";
case LANG_NB_NO:
return "nb_NO";
case LANG_RU:
@@ -227,8 +234,8 @@ QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Langua
return "tr";
case LANG_UK:
return "uk";
case LANG_ZH_HANT:
return "zh_Hant";
case LANG_ZH_TW:
return "zh_TW";
case LANG_PT:
return "pt";
case LANG_PT_BR:
@@ -243,6 +250,8 @@ QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Langua
return "pl";
case LANG_CS:
return "cs";
case LANG_HE:
return "he";
case LANG_AUTO:
default:
return QLocale::system().name();
@@ -261,7 +270,6 @@ void StreamingPreferences::save()
settings.setValue(SER_GAMEOPTS, gameOptimizations);
settings.setValue(SER_HOSTAUDIO, playAudioOnHost);
settings.setValue(SER_MULTICONT, multiController);
settings.setValue(SER_UNSUPPORTEDFPS, unsupportedFps);
settings.setValue(SER_MDNS, enableMdns);
settings.setValue(SER_QUITAPPAFTER, quitAppAfter);
settings.setValue(SER_ABSMOUSEMODE, absoluteMouseMode);
@@ -273,6 +281,7 @@ void StreamingPreferences::save()
settings.setValue(SER_PACKETSIZE, packetSize);
settings.setValue(SER_DETECTNETBLOCKING, detectNetworkBlocking);
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
settings.setValue(SER_HDR, enableHdr);
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
@@ -290,28 +299,51 @@ void StreamingPreferences::save()
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
{
// This table prefers 16:10 resolutions because they are
// only slightly more pixels than the 16:9 equivalents, so
// we don't want to bump those 16:10 resolutions up to the
// next 16:9 slot.
// Don't scale bitrate linearly beyond 60 FPS. It's definitely not a linear
// bitrate increase for frame rate once we get to values that high.
float frameRateFactor = (fps <= 60 ? fps : (qSqrt(fps / 60.f) * 60.f)) / 30.f;
if (width * height <= 640 * 360) {
return static_cast<int>(1000 * (fps / 30.0));
}
else if (width * height <= 854 * 480) {
return static_cast<int>(1500 * (fps / 30.0));
}
// This covers 1280x720 and 1280x800 too
else if (width * height <= 1366 * 768) {
return static_cast<int>(5000 * (fps / 30.0));
}
else if (width * height <= 1920 * 1200) {
return static_cast<int>(10000 * (fps / 30.0));
}
else if (width * height <= 2560 * 1600) {
return static_cast<int>(20000 * (fps / 30.0));
}
else /* if (width * height <= 3840 * 2160) */ {
return static_cast<int>(40000 * (fps / 30.0));
// TODO: Collect some empirical data to see if these defaults make sense.
// We're just using the values that the Shield used, as we have for years.
static const struct resTable {
int pixels;
int factor;
} resTable[] {
{ 640 * 360, 1 },
{ 854 * 480, 2 },
{ 1280 * 720, 5 },
{ 1920 * 1080, 10 },
{ 2560 * 1440, 20 },
{ 3840 * 2160, 40 },
{ -1, -1 },
};
// Calculate the resolution factor by linear interpolation of the resolution table
float resolutionFactor;
int pixels = width * height;
for (int i = 0;; i++) {
if (pixels == resTable[i].pixels) {
// We can bail immediately for exact matches
resolutionFactor = resTable[i].factor;
break;
}
else if (pixels < resTable[i].pixels) {
if (i == 0) {
// Never go below the lowest resolution entry
resolutionFactor = resTable[i].factor;
}
else {
// Interpolate between the entry greater than the chosen resolution (i) and the entry less than the chosen resolution (i-1)
resolutionFactor = ((float)(pixels - resTable[i-1].pixels) / (resTable[i].pixels - resTable[i-1].pixels)) * (resTable[i].factor - resTable[i-1].factor) + resTable[i-1].factor;
}
break;
}
else if (resTable[i].pixels == -1) {
// Never go above the highest resolution entry
resolutionFactor = resTable[i-1].factor;
break;
}
}
return qRound(resolutionFactor * frameRateFactor) * 1000;
}
+8 -5
View File
@@ -32,7 +32,8 @@ public:
VCC_AUTO,
VCC_FORCE_H264,
VCC_FORCE_HEVC,
VCC_FORCE_HEVC_HDR
VCC_FORCE_HEVC_HDR_DEPRECATED, // Kept for backwards compatibility
VCC_FORCE_AV1
};
Q_ENUM(VideoCodecConfig)
@@ -82,7 +83,7 @@ public:
LANG_SV,
LANG_TR,
LANG_UK,
LANG_ZH_HANT,
LANG_ZH_TW,
LANG_PT,
LANG_PT_BR,
LANG_EL,
@@ -90,6 +91,7 @@ public:
LANG_HI,
LANG_PL,
LANG_CS,
LANG_HE,
};
Q_ENUM(Language);
@@ -109,7 +111,6 @@ public:
Q_PROPERTY(bool gameOptimizations MEMBER gameOptimizations NOTIFY gameOptimizationsChanged)
Q_PROPERTY(bool playAudioOnHost MEMBER playAudioOnHost NOTIFY playAudioOnHostChanged)
Q_PROPERTY(bool multiController MEMBER multiController NOTIFY multiControllerChanged)
Q_PROPERTY(bool unsupportedFps MEMBER unsupportedFps NOTIFY unsupportedFpsChanged)
Q_PROPERTY(bool enableMdns MEMBER enableMdns NOTIFY enableMdnsChanged)
Q_PROPERTY(bool quitAppAfter MEMBER quitAppAfter NOTIFY quitAppAfterChanged)
Q_PROPERTY(bool absoluteMouseMode MEMBER absoluteMouseMode NOTIFY absoluteMouseModeChanged)
@@ -118,9 +119,10 @@ public:
Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged)
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged)
Q_PROPERTY(bool gamepadMouse MEMBER gamepadMouse NOTIFY gamepadMouseChanged)
Q_PROPERTY(bool detectNetworkBlocking MEMBER detectNetworkBlocking NOTIFY detectNetworkBlockingChanged);
Q_PROPERTY(bool detectNetworkBlocking MEMBER detectNetworkBlocking NOTIFY detectNetworkBlockingChanged)
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
Q_PROPERTY(bool enableHdr MEMBER enableHdr NOTIFY enableHdrChanged)
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
Q_PROPERTY(WindowMode windowMode MEMBER windowMode NOTIFY windowModeChanged)
Q_PROPERTY(WindowMode recommendedFullScreenMode MEMBER recommendedFullScreenMode CONSTANT)
@@ -145,7 +147,6 @@ public:
bool gameOptimizations;
bool playAudioOnHost;
bool multiController;
bool unsupportedFps;
bool enableMdns;
bool quitAppAfter;
bool absoluteMouseMode;
@@ -164,6 +165,7 @@ public:
int packetSize;
AudioConfig audioConfig;
VideoCodecConfig videoCodecConfig;
bool enableHdr;
VideoDecoderSelection videoDecoderSelection;
WindowMode windowMode;
WindowMode recommendedFullScreenMode;
@@ -185,6 +187,7 @@ signals:
void absoluteTouchModeChanged();
void audioConfigChanged();
void videoCodecConfigChanged();
void enableHdrChanged();
void videoDecoderSelectionChanged();
void uiDisplayModeChanged();
void windowModeChanged();
+14 -1
View File
@@ -28,8 +28,17 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION*
// On PulseAudio systems, setting a value too small can cause underruns for other
// applications sharing this output device. We impose a floor of 480 samples (10 ms)
// to mitigate this issue.
// to mitigate this issue. Otherwise, we will buffer up to 3 frames of audio which
// is 15 ms at regular 5 ms frames and 30 ms at 10 ms frames for slow connections.
// The buffering helps avoid audio underruns due to network jitter.
#ifndef Q_OS_DARWIN
want.samples = SDL_max(480, opusConfig->samplesPerFrame * 3);
#else
// HACK: Changing the buffer size can lead to Bluetooth HFP
// audio issues on macOS, so we're leaving this alone.
// https://github.com/moonlight-stream/moonlight-qt/issues/1071
want.samples = SDL_max(480, opusConfig->samplesPerFrame);
#endif
m_FrameSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
@@ -58,6 +67,10 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION*
have.samples,
have.size);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"SDL audio driver: %s",
SDL_GetCurrentAudioDriver());
// Start playback
SDL_PauseAudioDevice(m_AudioDevice, 0);
+97
View File
@@ -2,6 +2,7 @@
#include <Limelight.h>
#include <SDL.h>
#include <SDL_syswm.h>
#include "streaming/streamutils.h"
#include <QtMath>
@@ -27,7 +28,103 @@ Uint32 SdlInputHandler::longPressTimerCallback(Uint32, void*)
return 0;
}
void SdlInputHandler::disableTouchFeedback()
{
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
SDL_GetWindowWMInfo(m_Window, &info);
#ifdef Q_OS_WIN32
if (info.subsystem == SDL_SYSWM_WINDOWS) {
auto fnSetWindowFeedbackSetting = (decltype(SetWindowFeedbackSetting)*)GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetWindowFeedbackSetting");
if (fnSetWindowFeedbackSetting) {
constexpr FEEDBACK_TYPE feedbackTypes[] = {
FEEDBACK_TOUCH_CONTACTVISUALIZATION,
FEEDBACK_PEN_BARRELVISUALIZATION,
FEEDBACK_PEN_TAP,
FEEDBACK_PEN_DOUBLETAP,
FEEDBACK_PEN_PRESSANDHOLD,
FEEDBACK_PEN_RIGHTTAP,
FEEDBACK_TOUCH_TAP,
FEEDBACK_TOUCH_DOUBLETAP,
FEEDBACK_TOUCH_PRESSANDHOLD,
FEEDBACK_TOUCH_RIGHTTAP,
FEEDBACK_GESTURE_PRESSANDTAP,
};
for (FEEDBACK_TYPE ft : feedbackTypes) {
BOOL val = FALSE;
fnSetWindowFeedbackSetting(info.info.win.window, ft, 0, sizeof(val), &val);
}
}
}
#endif
}
void SdlInputHandler::handleAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
{
SDL_Rect src, dst;
int windowWidth, windowHeight;
SDL_GetWindowSize(m_Window, &windowWidth, &windowHeight);
src.x = src.y = 0;
src.w = m_StreamWidth;
src.h = m_StreamHeight;
dst.x = dst.y = 0;
dst.w = windowWidth;
dst.h = windowHeight;
// Scale window-relative events to be video-relative and clamp to video region
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
float vidrelx = qMin(qMax((int)(event->x * windowWidth), dst.x), dst.x + dst.w) - dst.x;
float vidrely = qMin(qMax((int)(event->y * windowHeight), dst.y), dst.y + dst.h) - dst.y;
uint8_t eventType;
switch (event->type) {
case SDL_FINGERDOWN:
eventType = LI_TOUCH_EVENT_DOWN;
break;
case SDL_FINGERMOTION:
eventType = LI_TOUCH_EVENT_MOVE;
break;
case SDL_FINGERUP:
eventType = LI_TOUCH_EVENT_UP;
break;
default:
return;
}
uint32_t pointerId;
// If the pointer ID is larger than we can fit, just CRC it and use that as the ID.
if ((uint64_t)event->fingerId > UINT32_MAX) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QByteArrayView bav((char*)&event->fingerId, sizeof(event->fingerId));
pointerId = qChecksum(bav);
#else
pointerId = qChecksum((char*)&event->fingerId, sizeof(event->fingerId));
#endif
}
else {
pointerId = (uint32_t)event->fingerId;
}
// Try to send it as a native touch event, otherwise fall back to our touch emulation
if (LiSendTouchEvent(eventType, pointerId, vidrelx / dst.w, vidrely / dst.h, event->pressure,
0.0f, 0.0f, LI_ROT_UNKNOWN) == LI_ERR_UNSUPPORTED) {
emulateAbsoluteFingerEvent(event);
}
else if (!m_DisabledTouchFeedback) {
// Disable touch feedback when passing touch natively
disableTouchFeedback();
m_DisabledTouchFeedback = true;
}
}
void SdlInputHandler::emulateAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
{
// Observations on Windows 10: x and y appear to be relative to 0,0 of the window client area.
// Although SDL documentation states they are 0.0 - 1.0 float values, they can actually be higher
+338 -14
View File
@@ -19,15 +19,19 @@
#define MOUSE_EMULATION_DEADZONE 2
// Haptic capabilities (in addition to those from SDL_HapticQuery())
#define ML_HAPTIC_GC_RUMBLE (1U << 16)
#define ML_HAPTIC_SIMPLE_RUMBLE (1U << 17)
#define ML_HAPTIC_GC_RUMBLE (1U << 16)
#define ML_HAPTIC_SIMPLE_RUMBLE (1U << 17)
#define ML_HAPTIC_GC_TRIGGER_RUMBLE (1U << 18)
const int SdlInputHandler::k_ButtonMap[] = {
A_FLAG, B_FLAG, X_FLAG, Y_FLAG,
BACK_FLAG, SPECIAL_FLAG, PLAY_FLAG,
LS_CLK_FLAG, RS_CLK_FLAG,
LB_FLAG, RB_FLAG,
UP_FLAG, DOWN_FLAG, LEFT_FLAG, RIGHT_FLAG
UP_FLAG, DOWN_FLAG, LEFT_FLAG, RIGHT_FLAG,
MISC_FLAG,
PADDLE1_FLAG, PADDLE2_FLAG, PADDLE3_FLAG, PADDLE4_FLAG,
TOUCHPAD_FLAG,
};
GamepadState*
@@ -42,17 +46,31 @@ SdlInputHandler::findStateForGamepad(SDL_JoystickID id)
}
}
// This should only happen with > 4 gamepads
SDL_assert(SDL_NumJoysticks() > 4);
// We can get a spurious removal event if the device is removed
// before or during SDL_GameControllerOpen(). This is fine to ignore.
return nullptr;
}
void SdlInputHandler::sendGamepadState(GamepadState* state)
{
SDL_assert(m_GamepadMask == 0x1 || m_MultiController);
// Handle Select+PS as the clickpad button on PS4/5 controllers without a clickpad mapping
int buttons = state->buttons;
if (state->clickpadButtonEmulationEnabled) {
if (state->buttons == (BACK_FLAG | SPECIAL_FLAG)) {
buttons = MISC_FLAG;
state->emulatedClickpadButtonDown = true;
}
else if (state->emulatedClickpadButtonDown) {
buttons &= ~MISC_FLAG;
state->emulatedClickpadButtonDown = false;
}
}
LiSendMultiControllerEvent(state->index,
m_GamepadMask,
state->buttons,
buttons,
state->lt,
state->rt,
state->lsX,
@@ -61,6 +79,47 @@ void SdlInputHandler::sendGamepadState(GamepadState* state)
state->rsY);
}
void SdlInputHandler::sendGamepadBatteryState(GamepadState* state, SDL_JoystickPowerLevel level)
{
uint8_t batteryPercentage;
uint8_t batteryState;
// SDL's battery reporting capabilities are quite limited. Notably, we cannot
// tell the battery level while charging (or even if a battery is present).
// We also cannot tell the percentage of charge exactly in any case.
switch (level)
{
case SDL_JOYSTICK_POWER_UNKNOWN:
batteryState = LI_BATTERY_STATE_UNKNOWN;
batteryPercentage = LI_BATTERY_PERCENTAGE_UNKNOWN;
break;
case SDL_JOYSTICK_POWER_WIRED:
batteryState = LI_BATTERY_STATE_CHARGING;
batteryPercentage = LI_BATTERY_PERCENTAGE_UNKNOWN;
break;
case SDL_JOYSTICK_POWER_EMPTY:
batteryState = LI_BATTERY_STATE_DISCHARGING;
batteryPercentage = 5;
break;
case SDL_JOYSTICK_POWER_LOW:
batteryState = LI_BATTERY_STATE_DISCHARGING;
batteryPercentage = 20;
break;
case SDL_JOYSTICK_POWER_MEDIUM:
batteryState = LI_BATTERY_STATE_DISCHARGING;
batteryPercentage = 50;
break;
case SDL_JOYSTICK_POWER_FULL:
batteryState = LI_BATTERY_STATE_DISCHARGING;
batteryPercentage = 90;
break;
default:
return;
}
LiSendControllerBatteryEvent(state->index, batteryState, batteryPercentage);
}
Uint32 SdlInputHandler::mouseEmulationTimerCallback(Uint32 interval, void *param)
{
auto gamepad = reinterpret_cast<GamepadState*>(param);
@@ -162,6 +221,13 @@ void SdlInputHandler::handleControllerAxisEvent(SDL_ControllerAxisEvent* event)
void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* event)
{
if (event->button >= SDL_arraysize(k_ButtonMap)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"No mapping for gamepad button: %u",
event->button);
return;
}
GamepadState* state = findStateForGamepad(event->which);
if (state == NULL) {
return;
@@ -212,6 +278,12 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
else if (event->button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) {
LiSendScrollEvent(-1);
}
else if (event->button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) {
LiSendHScrollEvent(1);
}
else if (event->button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) {
LiSendHScrollEvent(-1);
}
}
}
else {
@@ -296,6 +368,84 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
}
}
#if SDL_VERSION_ATLEAST(2, 0, 14)
void SdlInputHandler::handleControllerSensorEvent(SDL_ControllerSensorEvent* event)
{
GamepadState* state = findStateForGamepad(event->which);
if (state == NULL) {
return;
}
switch (event->sensor) {
case SDL_SENSOR_ACCEL:
if (state->accelReportPeriodMs &&
SDL_TICKS_PASSED(event->timestamp, state->lastAccelEventTime + state->accelReportPeriodMs) &&
memcmp(event->data, state->lastAccelEventData, sizeof(event->data)) != 0) {
memcpy(state->lastAccelEventData, event->data, sizeof(event->data));
state->lastAccelEventTime = event->timestamp;
LiSendControllerMotionEvent((uint8_t)state->index, LI_MOTION_TYPE_ACCEL, event->data[0], event->data[1], event->data[2]);
}
break;
case SDL_SENSOR_GYRO:
if (state->gyroReportPeriodMs &&
SDL_TICKS_PASSED(event->timestamp, state->lastGyroEventTime + state->gyroReportPeriodMs) &&
memcmp(event->data, state->lastGyroEventData, sizeof(event->data)) != 0) {
memcpy(state->lastGyroEventData, event->data, sizeof(event->data));
state->lastGyroEventTime = event->timestamp;
// Convert rad/s to deg/s
LiSendControllerMotionEvent((uint8_t)state->index, LI_MOTION_TYPE_GYRO,
event->data[0] * 57.2957795f,
event->data[1] * 57.2957795f,
event->data[2] * 57.2957795f);
}
break;
}
}
void SdlInputHandler::handleControllerTouchpadEvent(SDL_ControllerTouchpadEvent* event)
{
GamepadState* state = findStateForGamepad(event->which);
if (state == NULL) {
return;
}
uint8_t eventType;
switch (event->type) {
case SDL_CONTROLLERTOUCHPADDOWN:
eventType = LI_TOUCH_EVENT_DOWN;
break;
case SDL_CONTROLLERTOUCHPADUP:
eventType = LI_TOUCH_EVENT_UP;
break;
case SDL_CONTROLLERTOUCHPADMOTION:
eventType = LI_TOUCH_EVENT_MOVE;
break;
default:
return;
}
LiSendControllerTouchEvent((uint8_t)state->index, eventType, event->finger, event->x, event->y, event->pressure);
}
#endif
#if SDL_VERSION_ATLEAST(2, 24, 0)
void SdlInputHandler::handleJoystickBatteryEvent(SDL_JoyBatteryEvent* event)
{
GamepadState* state = findStateForGamepad(event->which);
if (state == NULL) {
return;
}
sendGamepadBatteryState(state, event->level);
}
#endif
void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
{
GamepadState* state;
@@ -316,6 +466,20 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
return;
}
// 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.
for (int i = 0; i < MAX_GAMEPADS; i++) {
if (m_GamepadState[i].controller == controller) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Received duplicate add event for controller index: %d",
event->which);
SDL_GameControllerClose(controller);
return;
}
}
// We used to use SDL_GameControllerGetPlayerIndex() here but that
// can lead to strange issues due to bugs in Windows where an Xbox
// controller will join as player 2, even though no player 1 controller
@@ -338,6 +502,17 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
return;
}
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(controller)),
guidStr, sizeof(guidStr));
if (m_IgnoreDeviceGuids.contains(guidStr, Qt::CaseInsensitive))
{
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Skipping ignored device with GUID: %s",
guidStr);
SDL_GameControllerClose(controller);
return;
}
state = &m_GamepadState[i];
if (m_MultiController) {
state->index = i;
@@ -357,14 +532,18 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
state->controller = controller;
state->jsId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(state->controller));
hapticCaps = 0;
#if SDL_VERSION_ATLEAST(2, 0, 18)
hapticCaps = SDL_GameControllerHasRumble(controller) ? ML_HAPTIC_GC_RUMBLE : 0;
hapticCaps |= SDL_GameControllerHasRumble(controller) ? ML_HAPTIC_GC_RUMBLE : 0;
hapticCaps |= SDL_GameControllerHasRumbleTriggers(controller) ? ML_HAPTIC_GC_TRIGGER_RUMBLE : 0;
#elif SDL_VERSION_ATLEAST(2, 0, 9)
// Perform a tiny rumble to see if haptics are supported.
// Perform a tiny rumbles to see if haptics are supported.
// NB: We cannot use zeros for rumble intensity or SDL will not actually call the JS driver
// and we'll get a (potentially false) success value returned.
hapticCaps = SDL_GameControllerRumble(controller, 1, 1, 1) == 0 ?
ML_HAPTIC_GC_RUMBLE : 0;
hapticCaps |= SDL_GameControllerRumble(controller, 1, 1, 1) == 0 ? ML_HAPTIC_GC_RUMBLE : 0;
#if SDL_VERSION_ATLEAST(2, 0, 14)
hapticCaps |= SDL_GameControllerRumbleTriggers(controller, 1, 1, 1) == 0 ? ML_HAPTIC_GC_TRIGGER_RUMBLE : 0;
#endif
#else
state->haptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(state->controller));
state->hapticEffectId = -1;
@@ -394,15 +573,18 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
}
#endif
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(state->controller)),
guidStr, sizeof(guidStr));
mapping = SDL_GameControllerMapping(state->controller);
name = SDL_GameControllerName(state->controller);
uint16_t vendorId = SDL_GameControllerGetVendor(state->controller);
uint16_t productId = SDL_GameControllerGetProduct(state->controller);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Gamepad %d (player %d) is: %s (haptic capabilities: 0x%x) (mapping: %s -> %s)",
"Gamepad %d (player %d) is: %s (VID/PID: 0x%.4x/0x%.4x) (haptic capabilities: 0x%x) (mapping: %s -> %s)",
i,
state->index,
name != nullptr ? name : "<null>",
vendorId,
productId,
hapticCaps,
guidStr,
mapping != nullptr ? mapping : "<null>");
@@ -421,8 +603,89 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
SDL_assert(m_GamepadMask == 0x1);
}
SDL_JoystickPowerLevel powerLevel = SDL_JoystickCurrentPowerLevel(SDL_GameControllerGetJoystick(state->controller));
#if SDL_VERSION_ATLEAST(2, 0, 14)
// On SDL 2.0.14 and later, we can provide enhanced controller information to the host PC
// for it to use as a hint for the type of controller to emulate.
uint32_t supportedButtonFlags = 0;
for (int i = 0; i < (int)SDL_arraysize(k_ButtonMap); i++) {
if (SDL_GameControllerHasButton(state->controller, (SDL_GameControllerButton)i)) {
supportedButtonFlags |= k_ButtonMap[i];
}
}
uint32_t capabilities = 0;
if (SDL_GameControllerGetBindForAxis(state->controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT).bindType == SDL_CONTROLLER_BINDTYPE_AXIS ||
SDL_GameControllerGetBindForAxis(state->controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT).bindType == SDL_CONTROLLER_BINDTYPE_AXIS) {
// We assume these are analog triggers if the binding is to an axis rather than a button
capabilities |= LI_CCAP_ANALOG_TRIGGERS;
}
if (hapticCaps & ML_HAPTIC_GC_RUMBLE) {
capabilities |= LI_CCAP_RUMBLE;
}
if (hapticCaps & ML_HAPTIC_GC_TRIGGER_RUMBLE) {
capabilities |= LI_CCAP_TRIGGER_RUMBLE;
}
if (SDL_GameControllerGetNumTouchpads(state->controller) > 0) {
capabilities |= LI_CCAP_TOUCHPAD;
}
if (SDL_GameControllerHasSensor(state->controller, SDL_SENSOR_ACCEL)) {
capabilities |= LI_CCAP_ACCEL;
}
if (SDL_GameControllerHasSensor(state->controller, SDL_SENSOR_GYRO)) {
capabilities |= LI_CCAP_GYRO;
}
if (powerLevel != SDL_JOYSTICK_POWER_UNKNOWN || SDL_VERSION_ATLEAST(2, 24, 0)) {
capabilities |= LI_CCAP_BATTERY_STATE;
}
if (SDL_GameControllerHasLED(state->controller)) {
capabilities |= LI_CCAP_RGB_LED;
}
uint8_t type;
switch (SDL_GameControllerGetType(state->controller)) {
case SDL_CONTROLLER_TYPE_XBOX360:
case SDL_CONTROLLER_TYPE_XBOXONE:
type = LI_CTYPE_XBOX;
break;
case SDL_CONTROLLER_TYPE_PS3:
case SDL_CONTROLLER_TYPE_PS4:
case SDL_CONTROLLER_TYPE_PS5:
type = LI_CTYPE_PS;
break;
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO:
#if SDL_VERSION_ATLEAST(2, 24, 0)
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
#endif
type = LI_CTYPE_NINTENDO;
break;
default:
type = LI_CTYPE_UNKNOWN;
break;
}
// If this is a PlayStation controller that doesn't have a touchpad button mapped,
// we'll allow the Select+PS button combo to act as the touchpad.
state->clickpadButtonEmulationEnabled =
#if SDL_VERSION_ATLEAST(2, 0, 14)
SDL_GameControllerGetBindForButton(state->controller, SDL_CONTROLLER_BUTTON_TOUCHPAD).bindType == SDL_CONTROLLER_BINDTYPE_NONE &&
#endif
type == LI_CTYPE_PS;
LiSendControllerArrivalEvent(state->index, m_GamepadMask, type, supportedButtonFlags, capabilities);
#else
// Send an empty event to tell the PC we've arrived
sendGamepadState(state);
#endif
// Send a power level if it's known at this time
if (powerLevel != SDL_JOYSTICK_POWER_UNKNOWN) {
sendGamepadBatteryState(state, powerLevel);
}
}
else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
state = findStateForGamepad(event->which);
@@ -550,6 +813,60 @@ void SdlInputHandler::rumble(unsigned short controllerNumber, unsigned short low
#endif
}
void SdlInputHandler::rumbleTriggers(uint16_t controllerNumber, uint16_t leftTrigger, uint16_t rightTrigger)
{
// Make sure the controller number is within our supported count
if (controllerNumber >= MAX_GAMEPADS) {
return;
}
#if SDL_VERSION_ATLEAST(2, 0, 14)
if (m_GamepadState[controllerNumber].controller != nullptr) {
SDL_GameControllerRumbleTriggers(m_GamepadState[controllerNumber].controller, leftTrigger, rightTrigger, 30000);
}
#endif
}
void SdlInputHandler::setMotionEventState(uint16_t controllerNumber, uint8_t motionType, uint16_t reportRateHz)
{
// Make sure the controller number is within our supported count
if (controllerNumber >= MAX_GAMEPADS) {
return;
}
#if SDL_VERSION_ATLEAST(2, 0, 14)
if (m_GamepadState[controllerNumber].controller != nullptr) {
uint8_t reportPeriodMs = reportRateHz ? (1000 / reportRateHz) : 0;
switch (motionType) {
case LI_MOTION_TYPE_ACCEL:
m_GamepadState[controllerNumber].accelReportPeriodMs = reportPeriodMs;
SDL_GameControllerSetSensorEnabled(m_GamepadState[controllerNumber].controller, SDL_SENSOR_ACCEL, reportRateHz ? SDL_TRUE : SDL_FALSE);
break;
case LI_MOTION_TYPE_GYRO:
m_GamepadState[controllerNumber].gyroReportPeriodMs = reportPeriodMs;
SDL_GameControllerSetSensorEnabled(m_GamepadState[controllerNumber].controller, SDL_SENSOR_GYRO, reportRateHz ? SDL_TRUE : SDL_FALSE);
break;
}
}
#endif
}
void SdlInputHandler::setControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b)
{
// Make sure the controller number is within our supported count
if (controllerNumber >= MAX_GAMEPADS) {
return;
}
#if SDL_VERSION_ATLEAST(2, 0, 14)
if (m_GamepadState[controllerNumber].controller != nullptr) {
SDL_GameControllerSetLED(m_GamepadState[controllerNumber].controller, r, g, b);
}
#endif
}
QString SdlInputHandler::getUnmappedGamepads()
{
QString ret;
@@ -621,7 +938,14 @@ int SdlInputHandler::getAttachedGamepadMask()
count = mask = 0;
for (int i = 0; i < SDL_NumJoysticks(); i++) {
if (SDL_IsGameController(i)) {
mask |= (1 << count++);
char guidStr[33];
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(i),
guidStr, sizeof(guidStr));
if (!m_IgnoreDeviceGuids.contains(guidStr, Qt::CaseInsensitive))
{
mask |= (1 << count++);
}
}
}
+24 -24
View File
@@ -9,16 +9,12 @@
#include <QDir>
#include <QGuiApplication>
#define MOUSE_POLLING_INTERVAL 5
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int streamWidth, int streamHeight)
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, int streamHeight)
: m_MultiController(prefs.multiController),
m_GamepadMouse(prefs.gamepadMouse),
m_SwapMouseButtons(prefs.swapMouseButtons),
m_ReverseScrollDirection(prefs.reverseScrollDirection),
m_SwapFaceButtons(prefs.swapFaceButtons),
m_MouseMoveTimer(0),
m_MousePositionLock(0),
m_MouseWasInVideoRegion(false),
m_PendingMouseButtonsAllUpOnVideoRegionLeave(false),
m_PointerRegionLockActive(false),
@@ -31,6 +27,7 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
m_StreamHeight(streamHeight),
m_AbsoluteMouseMode(prefs.absoluteMouseMode),
m_AbsoluteTouchMode(prefs.absoluteTouchMode),
m_DisabledTouchFeedback(false),
m_LeftButtonReleaseTimer(0),
m_RightButtonReleaseTimer(0),
m_DragTimer(0),
@@ -133,6 +130,20 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
}
streamIgnoreDevices += m_OldIgnoreDevices;
// STREAM_IGNORE_DEVICE_GUIDS allows to specify additional devices to be ignored when starting
// the stream in case the scope of STREAM_GAMECONTROLLER_IGNORE_DEVICES is too broad. One such
// case is "Steam Virtual Gamepad" where everything is under the same VID/PID, but different GUIDs.
// Multiple GUIDs can be provided, but need to be separated by commas:
//
// <GUID>,<GUID>,<GUID>,...
//
QString streamIgnoreDeviceGuids = qgetenv("STREAM_IGNORE_DEVICE_GUIDS");
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
m_IgnoreDeviceGuids = streamIgnoreDeviceGuids.split(',', Qt::SkipEmptyParts);
#else
m_IgnoreDeviceGuids = streamIgnoreDeviceGuids.split(',', QString::SkipEmptyParts);
#endif
// For SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, we use the union of SDL_GAMECONTROLLER_IGNORE_DEVICES
// and STREAM_GAMECONTROLLER_IGNORE_DEVICES while streaming. STREAM_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT
// overrides SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT while streaming.
@@ -187,23 +198,6 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
SDL_zero(m_LastTouchDownEvent);
SDL_zero(m_LastTouchUpEvent);
SDL_zero(m_TouchDownEvent);
SDL_zero(m_MousePositionReport);
SDL_AtomicSet(&m_MouseDeltaX, 0);
SDL_AtomicSet(&m_MouseDeltaY, 0);
SDL_AtomicSet(&m_MousePositionUpdated, 0);
Uint32 pollingInterval = QString(qgetenv("MOUSE_POLLING_INTERVAL")).toUInt();
if (pollingInterval == 0) {
pollingInterval = MOUSE_POLLING_INTERVAL;
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Using custom mouse polling interval: %u ms",
pollingInterval);
}
m_MouseMoveTimer = SDL_AddTimer(pollingInterval, SdlInputHandler::mouseMoveTimerCallback, this);
}
SdlInputHandler::~SdlInputHandler()
@@ -223,7 +217,6 @@ SdlInputHandler::~SdlInputHandler()
}
}
SDL_RemoveTimer(m_MouseMoveTimer);
SDL_RemoveTimer(m_LongPressTimer);
SDL_RemoveTimer(m_LeftButtonReleaseTimer);
SDL_RemoveTimer(m_RightButtonReleaseTimer);
@@ -402,7 +395,14 @@ void SdlInputHandler::setCaptureActive(bool active)
mouseY -= windowY;
if (isMouseInVideoRegion(mouseX, mouseY)) {
updateMousePositionReport(mouseX, mouseY);
// Synthesize a mouse event to synchronize the cursor
SDL_MouseMotionEvent motionEvent = {};
motionEvent.type = SDL_MOUSEMOTION;
motionEvent.timestamp = SDL_GetTicks();
motionEvent.windowID = SDL_GetWindowID(m_Window);
motionEvent.x = mouseX;
motionEvent.y = mouseY;
handleMouseMotionEvent(&motionEvent);
}
}
}

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