Compare commits

..
366 Commits
Author SHA1 Message Date
Cameron Gutman 719024ec01 Prepare for v3.2.0 release 2022-01-18 18:33:23 -06:00
Cameron Gutman f0659b4f3c Fix random crash on stream start with renderers that require test frames 2022-01-18 18:19:28 -06:00
Cameron Gutman a649f2736f Rerun lupdate and lrelease 2022-01-18 18:07:17 -06:00
Cameron Gutman 046ccb1dee Merge remote-tracking branch 'origin/weblate' 2022-01-18 18:05:42 -06:00
Tygo T d340556c28 Translated using Weblate (Dutch)
Currently translated at 100.0% (189 of 189 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nl/
2022-01-19 01:04:10 +01:00
Wout Rombouts 32250db126 Translated using Weblate (Dutch)
Currently translated at 100.0% (189 of 189 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nl/
2022-01-19 01:04:10 +01:00
Cameron Gutman cb8c30db2e Add workaround for keyboard and UTF-8 events interfering with each other 2022-01-17 21:56:39 -06:00
Cameron Gutman dacd5a9e3d Discard stale gamepad events when enabling gamepad navigation 2022-01-17 19:49:52 -06:00
Cameron Gutman 859d8b96a7 Don't use duplicate resize workaround on SDL 2.0.21+ 2022-01-17 19:25:43 -06:00
Cameron Gutman 02981e3cee Update SDL to pick up HIDAPI deadzone fix 2022-01-17 19:23:42 -06:00
Cameron Gutman 49892b3ce8 Only use SDL_WaitEventTimeout() on SDL 2.0.18+ 2022-01-17 16:59:45 -06:00
Cameron Gutman b223e7ec36 Update SDL_GameControllerDB 2022-01-17 15:57:36 -06:00
Cameron Gutman 0926337c1d Rerun lupdate and lrelease 2022-01-17 15:56:39 -06:00
Cameron Gutman 346e72a678 Merge remote-tracking branch 'origin/weblate' 2022-01-17 15:55:12 -06:00
Wout Rombouts d612e47b62 Translated using Weblate (Dutch)
Currently translated at 100.0% (189 of 189 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nl/
2022-01-17 22:54:04 +01:00
Cameron Gutman c138cbf994 Fix Windows build 2022-01-17 15:47:36 -06:00
Cameron Gutman dfe275ab67 Avoid consuming stale data during/after decoder reset 2022-01-17 15:26:00 -06:00
Cameron Gutman d6cfbdb273 Rewrite FFmpeg decoder to use pull model
This allows us to keep asynchronous decoders like MMAL and V4L2M2M fed
while we're waiting for output frames. Behavior for synchronous decoders
should be identical.

Continuing to feed new data while waiting for output frames is crucial for
acceptable performance on 1080p video on the Raspberry Pi using V4L2M2M,
since it allows the decode and copy operations to be pipelined.
2022-01-17 15:06:12 -06:00
Cameron Gutman 8a27fa7bb5 Convert NeedsIdr member into an atomic variable with a getter 2022-01-17 14:30:12 -06:00
Cameron Gutman b9a6fb1fe5 Add workaround for AVSampleBufferDisplayLayer colorspace and artifacting issues
Fixes #493
2022-01-15 14:44:08 -06:00
Cameron Gutman 4b6194e15e Add chroma location buffer attachment 2022-01-15 12:17:31 -06:00
Cameron Gutman e1b3f90f8b Add workaround for duplicate size changed events on Wayland 2022-01-14 23:36:21 -06:00
Wen-haur Chiu c5cc0b8c59 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (189 of 189 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2022-01-13 12:56:43 +01:00
bruh 5c849ec666 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (189 of 189 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/vi/
2022-01-13 12:56:42 +01:00
Eric c2a30cbe19 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (189 of 189 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2022-01-13 12:56:42 +01:00
Cameron Gutman 31eddd8da0 Remove superfluous colorspace check 2022-01-13 01:19:17 -06:00
Cameron Gutman abf2a14ee6 Handle colorspace changes in SDL renderer 2022-01-13 01:16:09 -06:00
Cameron Gutman e555874485 Refactor WiX bundle description of VCRedist package 2022-01-12 19:14:59 -06:00
Cameron Gutman a7aef8f147 Use new WiX 3.14 variable instead of BuildLabEx hack for ARM64 detection 2022-01-12 19:14:44 -06:00
Cameron Gutman 5b8c11f1a9 Update and rebuild all dependencies with Xcode 13 and VS 2022
macOS minimum target version is now 10.14 Mojave

Updates:
OpenSSL 1.1.1m
SDL 2.0.20
libopus 12a356

https://github.com/cgutman/moonlight-deps 5cd31601c3362562b52d8124dfc33029506d1744
2022-01-11 20:49:11 -06:00
Cameron Gutman 645040c943 Update AppImage to SDL 2.0.20 2022-01-10 20:03:47 -06:00
Cameron Gutman fcc5ea348c Remove old FFmpeg build scripts
Superseded by https://github.com/cgutman/moonlight-deps
2022-01-10 19:49:41 -06:00
Héctor 3fbb45448f Translated using Weblate (Spanish)
Currently translated at 100.0% (189 of 189 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2022-01-10 20:56:47 +01:00
Cameron Gutman 8a83289709 Switch to VS2022 build image 2022-01-09 21:55:41 -06:00
Cameron Gutman 08175de069 Fix incorrect colorspace in MMAL renderer 2022-01-09 17:04:38 -06:00
Cameron Gutman 808933cd70 Use DT files to get status of FKMS and rpivid 2022-01-09 16:44:20 -06:00
Cameron Gutman 384dd2adab Prefer a render node in indirect rendering mode 2022-01-08 18:39:12 -06:00
Jorys Paulin 4a37952b41 Translated using Weblate (French)
Currently translated at 100.0% (189 of 189 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2022-01-08 09:55:55 +01:00
lyashenko.v 1046d264d8 Fixed not working frame pacing on macOS 2022-01-06 23:23:00 -06:00
Cameron Gutman bb235ab1ae Rerun lupdate and lrelease 2022-01-06 23:19:16 -06:00
Cameron Gutman ef88b83727 Merge remote-tracking branch 'origin/weblate' 2022-01-06 23:15:48 -06:00
Cameron Gutman 5612e9864e Fix handling of some validation scenarios
- HEVC forced with no host or client hw support
- H.264 forced with no client hw support
- Client has hw HEVC only and host only supports H.264
- Client has no hw decode at all
2022-01-06 23:15:19 -06:00
Cameron Gutman 8302187dee Remove superfluous availability checks 2022-01-06 23:05:18 -06:00
Cameron Gutman 77d07c7c4d Fix incorrect error message when only HEVC decode is available but HEVC encode is not 2022-01-06 22:08:43 -06:00
Cameron Gutman b2265080fd Allow HEVC decoders to satisfy hardware decoding check if H.264 is unavailable 2022-01-06 22:07:31 -06:00
Cameron Gutman 0d70bd9ac0 Add (untested) VAAPI support for CCS planes on Tiger Lake
See https://github.com/intel/media-driver/issues/1210
2022-01-05 21:09:08 -06:00
Cameron Gutman 8f63d59b81 Correct comment to avoid confusion 2022-01-04 22:00:46 -06:00
Cameron Gutman 0395c0cc95 Revert "Revert "Don't attempt to use direct rendering without DRM master""
The correct fix was 154b5b6ded.

This reverts commit 8219dbef6f.
2022-01-04 21:59:02 -06:00
Cameron Gutman 72065412a3 Don't allow copyback rendering on RPi by default 2022-01-04 20:35:17 -06:00
Jorys Paulin 7af2cdd0ef Translated using Weblate (French)
Currently translated at 100.0% (187 of 187 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2022-01-03 11:52:50 +01:00
Cameron Gutman 67c801c4e7 Use av_frame_copy_props() to copy metadata 2022-01-02 17:38:58 -06:00
Cameron Gutman f7c8ad4c5a Fail render frame test for unsupported pixel formats 2022-01-02 15:03:49 -06:00
Cameron Gutman 61092b34de Properly handle mismatched pitch between frame and texture 2022-01-02 14:35:14 -06:00
Cameron Gutman 1e9e96fb8c Small code cleanup for hwframe code 2022-01-02 13:58:37 -06:00
Cameron Gutman 17d1ced07c Add support for mapping hwframes 2022-01-02 13:41:36 -06:00
Cameron Gutman a26ced04ff Add support for negotiating non-default transfer formats for hwframes 2022-01-01 22:15:43 -06:00
Cameron Gutman 154b5b6ded Fix fallback to SDL read-back for DRM contexts when !HAVE_EGL 2022-01-01 20:44:29 -06:00
Cameron Gutman 8219dbef6f Revert "Don't attempt to use direct rendering without DRM master"
We support indirect rendering via SDL, even if EGL is unavailable.

This reverts commit 6145913068.
2022-01-01 20:37:04 -06:00
Cameron Gutman 3da724e76a Fix and enhance the Full KMS check 2021-12-28 17:13:54 -06:00
Cameron Gutman 1616d237ae Avoid h264_v4l2m2m by default on RPi builds 2021-12-28 16:28:04 -06:00
Nikita Epifanov 3d21facc9f Translated using Weblate (Russian)
Currently translated at 100.0% (187 of 187 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2021-12-24 19:53:18 +01:00
Héctor fead2a08a3 Translated using Weblate (Spanish)
Currently translated at 96.2% (180 of 187 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2021-12-20 18:52:37 +01:00
Cameron Gutman d8acf24af0 Don't use SdlRenderer with MMAL 2021-12-19 20:58:50 -06:00
Cameron Gutman 97412b3c79 Display warnings about Full KMS mode and HEVC on the Pi 4 2021-12-19 20:33:48 -06:00
Cameron Gutman 6145913068 Don't attempt to use direct rendering without DRM master 2021-12-19 20:32:15 -06:00
Cameron Gutman e56bb6785f Change display mode when using KMSDRM backend 2021-12-18 21:07:35 -06:00
Cameron Gutman 6d63d6c54a Rerun lupdate and lrelease 2021-12-15 20:52:48 -06:00
Cameron Gutman 6e8a103e70 Merge remote-tracking branch 'origin/weblate' 2021-12-15 20:50:15 -06:00
Cameron Gutman 00d3530f64 Don't try VDPAU on XWayland by default 2021-12-15 20:49:12 -06:00
Cameron Gutman 1e6ca18193 Fix error checking in decode test 2021-12-15 17:33:28 -06:00
Cameron Gutman 5a32992497 Try to learn if decoders support retrying avcodec_receive_frame() 2021-12-14 20:41:27 -06:00
李俊緯 79ba00a2f4 Translated using Weblate (Chinese (Traditional))
Currently translated at 84.4% (158 of 187 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2021-12-14 11:53:41 +01:00
bruh fdfaf8ec70 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (187 of 187 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/vi/
2021-12-14 11:53:41 +01:00
Héctor 162c58b697 Translated using Weblate (Spanish)
Currently translated at 60.9% (114 of 187 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2021-12-14 11:53:41 +01:00
Allan Nordhøy 75959ca692 Translated using Weblate (Norwegian Bokmål)
Currently translated at 70.5% (132 of 187 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nb_NO/
2021-12-14 11:53:40 +01:00
Eric 06fbcd19c8 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (187 of 187 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2021-12-14 11:53:40 +01:00
Cameron Gutman 36e0468a99 Optimize CUDA GL interop and provide fallback 2021-12-13 21:41:00 -06:00
Cameron Gutman c4d85cf928 Don't show HDR option if we know it's unsupported 2021-12-12 17:43:20 -06:00
Cameron Gutman daf8495acb Fix excessive high-res scroll speed on newer GFE versions 2021-12-12 16:50:22 -06:00
Cameron Gutman 9999ded933 Quiet down log spam for asynchronous decoders 2021-12-11 18:37:39 -06:00
Cameron Gutman 80128e8293 Properly support asynchronous decoders that return multiple frames at a time 2021-12-11 18:15:49 -06:00
Cameron Gutman 9579b2c85e Enable Spanish and Traditional Chinese language options 2021-12-11 14:32:38 -06:00
Cameron Gutman eb7f184848 Rerun lupdate and lrelease 2021-12-11 14:32:12 -06:00
Cameron Gutman 71b21920e2 Merge remote-tracking branch 'origin/weblate' 2021-12-11 14:24:07 -06:00
Cameron Gutman 91b787a5be Update SDL and switch to arch-specific headers on Windows
SDL -> e2ade2b
2021-12-11 14:06:26 -06:00
Cameron Gutman 11f659aadb Update Visual C++ Runtime installers 2021-12-11 13:28:48 -06:00
Cameron Gutman 0870dd15a8 Fix improper null termination when stripping clipboard text 2021-12-10 02:04:19 -06:00
Héctor 7cd52041d8 Translated using Weblate (Spanish)
Currently translated at 53.2% (99 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2021-12-09 17:52:51 +01:00
Cameron Gutman f2bcb49478 Update dependencies for AppImage
SDL -> 2.0.18
nv-codec-headers -> 8.1.24.13
FFmpeg -> 4.4.1
2021-12-06 23:03:53 -06:00
Cameron Gutman 60a4bda94f Switch macOS builds to Qt 6 2021-12-06 22:54:55 -06:00
Cameron Gutman 54cfc46500 Fix VDPAU on X11 2021-12-06 20:42:51 -06:00
Cameron Gutman c09559ade5 Don't try to initialize VDPAU on Wayland 2021-12-06 19:38:28 -06:00
Cameron Gutman 3e9aea1f7a Optimize CUDA support to avoid roundtrip to CPU memory 2021-12-06 18:23:40 -06:00
Cameron Gutman f0c292f508 Update SDL_GameControllerDB 2021-12-03 12:52:23 +03:00
Cameron Gutman 1444ba2c35 Use attached Layout attached properties to set tool button size 2021-12-03 12:24:31 +03:00
Cameron Gutman fac4c85133 Revert "Fix toolbar button size on Qt 6.2"
Breaks running with Qt 5.9

This reverts commit 84ea483b4c.
2021-12-03 12:17:25 +03:00
Cameron Gutman 97a09e0571 Send UTF-8 clipboard text directly rather than emulating keystrokes 2021-12-03 11:32:58 +03:00
Cameron Gutman c7121516c1 Remove workaround for window occlusion issue fixed in SDL 2.0.18 2021-11-29 23:13:50 +03:00
Cameron Gutman 56a5fbf63b Fix get_buffer2() thread-safety warning with FFmpeg 4.4 2021-11-29 19:45:28 +03:00
Cameron Gutman 80604a752b Set SDL_HINT_APP_NAME for SDL 2.0.18 2021-11-27 17:28:01 +03:00
Cameron Gutman 3fe6e18609 Use SDL_GameControllerHasRumble() on SDL 2.0.18 2021-11-27 17:06:02 +03:00
Cameron Gutman 47f3f30d83 Add high resolution scrolling support with SDL 2.0.18 2021-11-27 06:59:16 +03:00
Cameron Gutman 085a904970 Fix false warning when building without libva-x11 or libva-wayland 2021-11-25 17:52:54 +03:00
Cameron Gutman 0fa7723f51 Fix build without libva-x11 2021-11-25 17:51:08 +03:00
Cameron Gutman 84ea483b4c Fix toolbar button size on Qt 6.2 2021-11-25 12:26:29 +03:00
Cameron Gutman 4de3aa96b6 Add YUV colorspace and color range attributes to EGLImage 2021-11-19 01:15:33 -06:00
Cameron Gutman 1193943228 Re-add comment on color_range comparison 2021-11-19 00:56:46 -06:00
Cameron Gutman 2959faca0c Fix colorspace transitions in EGLRenderer 2021-11-19 00:54:59 -06:00
Cameron Gutman d0df4fa3a0 Fix handling of HDR colorspace transitions in VT renderer 2021-11-19 00:28:15 -06:00
liyu 0a301dd2f9 enables H265 HDR option and adds PQ transforeFunction to render HDR stream on macOS 10.13+ 2021-11-18 23:42:44 -06:00
Cameron Gutman 43e3679297 Revert "Disable the EGL render thread on Wayland"
This reverts commit dcc567ebda.
2021-11-15 21:33:28 -06:00
Cameron Gutman 0455063854 Fix driver version check for Intel's updated version schema in 100 and newer drivers
Fixes #673
2021-11-13 20:44:10 -06:00
Cameron Gutman 12d90d77c7 Fix unintentionally concatenated strings 2021-11-08 19:47:01 -06:00
Cameron Gutman 61d7749aaf Update pre-built dependencies
SDL -> 7f308ab
OpenSSL -> 1.1.1l
FFmpeg -> 4.4.1
2021-11-04 22:49:30 -05:00
曹恩逢 69fb4a0c37 Translated using Weblate (Chinese (Traditional))
Currently translated at 77.9% (145 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2021-11-01 07:34:45 +01:00
Cameron Gutman d2c9f058f8 Add names to session threads 2021-10-24 16:22:49 -05:00
Cameron Gutman acaa3afa0f Rerun lupdate and lrelease 2021-10-06 21:14:21 -05:00
Cameron Gutman 3c58e21cbf Merge remote-tracking branch 'origin/weblate' 2021-10-06 21:12:14 -05:00
曹恩逢 9d3c5b9bc6 Translated using Weblate (Chinese (Traditional))
Currently translated at 76.8% (143 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2021-10-07 04:07:30 +02:00
Eric ce34358d46 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2021-10-07 04:07:29 +02:00
Cameron Gutman 278bcec238 Integrate new languages 2021-10-06 21:03:02 -05:00
Cameron Gutman 128f458848 Merge remote-tracking branch 'origin/weblate' 2021-10-06 20:15:11 -05:00
Cameron Gutman 7ae2bcd6db Update AppImage to libva 2.13 2021-10-06 20:13:05 -05:00
Luna Jernberg 130f252f97 Translated using Weblate (Swedish)
Currently translated at 31.7% (59 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/sv/
2021-10-06 13:05:00 +02:00
SWZ d339e05a3b Added translation using Weblate (Swedish) 2021-10-01 20:57:41 +02:00
Jorys Paulin 933f4813aa Translated using Weblate (French)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2021-09-25 11:38:59 +02:00
BOYI LIN f14e71c1d6 Translated using Weblate (Chinese (Traditional))
Currently translated at 38.1% (71 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2021-08-27 17:33:01 +02:00
Mert d78dbd7b0f Translated using Weblate (Turkish)
Currently translated at 8.0% (15 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/tr/
2021-08-24 02:37:31 +02:00
Cameron Gutman 71a6ef6eba Update SDL to 402b86f in AppImage 2021-08-15 15:23:05 -05:00
Cameron Gutman 4873862d16 Use SDL_WaitEventTimeout() on SDL 2.0.16 2021-08-15 15:16:51 -05:00
Cameron Gutman 425efe3d72 Switch to SDL audio backend by default on Windows and macOS 2021-08-15 14:58:29 -05:00
Cameron Gutman 666dc13c01 Use SDL's new support for keyboard grab on macOS 2021-08-15 14:54:26 -05:00
Cameron Gutman 267f9f5759 Update SDL to 402b86f for Windows and macOS 2021-08-15 14:47:10 -05:00
Cameron Gutman c8d8c00f84 Update Visual C++ Runtime installers 2021-08-15 12:17:09 -05:00
Cameron Gutman dcc567ebda Disable the EGL render thread on Wayland 2021-07-31 17:24:18 -05:00
LiuAnnan a199a3ccc3 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.4% (185 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2021-07-30 08:34:39 +02:00
Cameron Gutman 8159e07427 Ensure our GL context is always current 2021-07-28 20:31:22 -05:00
Nikita Epifanov 32d70758c5 Translated using Weblate (Russian)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2021-07-23 10:32:32 +02:00
Jorys Paulin 3a9e284f29 Translated using Weblate (French)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2021-07-19 17:34:21 +02:00
Cameron Gutman c658daeac6 Remove unneccessary EVP_get_digestbyname() invocation
This causes problems with pre-OpenSSL 1.1.0 clients that don't
call OpenSSL_add_all_algorithms() during initialization. This
is known to affect Steam Link on firmware 815.
2021-07-17 20:32:50 -05:00
Jorys Paulin f605169b15 Translated using Weblate (French)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2021-07-15 15:33:00 +02:00
Cameron Gutman f82b495bcc Switch to the new Sectigo certificate 2021-07-14 21:09:48 -05:00
Tygo T 33b44a87c5 Translated using Weblate (Dutch)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nl/
2021-07-06 14:33:19 +02:00
Gerard Joling a88d994fcb Translated using Weblate (Dutch)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nl/
2021-07-05 13:48:27 +02:00
Gerard Joling dd9224d9a6 Added translation using Weblate (Dutch) 2021-07-05 10:39:15 +02:00
Cameron Gutman 48728efee0 Add support for dynamic HTTPS port 2021-07-03 00:00:27 -05:00
Cameron Gutman 043c55ae66 Add missing changes to fix build 2021-07-02 23:54:17 -05:00
Cameron Gutman fa3f822cf1 Rework handling of addresses to allow alternate HTTP ports 2021-07-02 23:34:54 -05:00
Cameron Gutman 5afab126b3 Refactor NvPairingManager, NvComputer, and NvHTTP to reduce passing of addresses 2021-07-02 17:14:48 -05:00
Cameron Gutman bee8378795 Pass RTSP session URL to moonlight-common-c for dynamic ports 2021-07-02 01:51:08 -05:00
Cameron Gutman cbd42f4a58 Log launch, resume, and quit responses 2021-07-02 01:18:57 -05:00
Furkan 3333fd7fc8 Added translation using Weblate (Turkish) 2021-07-01 17:52:59 +02:00
DankXylese 142258b4b5 Translated using Weblate (Ukrainian)
Currently translated at 1.6% (3 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/uk/
2021-06-30 13:32:33 +02:00
DankXylese 01213b58ba Added translation using Weblate (Ukrainian) 2021-06-29 12:59:43 +02:00
wkopiaspupn 51df7032bb Translated using Weblate (Chinese (Traditional))
Currently translated at 35.4% (66 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2021-06-27 14:32:49 +02:00
wkopiaspupn c033ce5ecf Translated using Weblate (Chinese (Traditional))
Currently translated at 1.6% (3 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2021-06-26 13:33:38 +02:00
wkopiaspupn bfaf57cfd0 Added translation using Weblate (Chinese (Traditional)) 2021-06-26 13:31:04 +02:00
Cameron Gutman 6c523570e5 Prepare for v3.1.4 2021-06-25 20:39:30 -05:00
Cameron Gutman 047e5bdd81 Update AppImage to libva 2.12 2021-06-24 21:47:01 -05:00
Cameron Gutman 6b1559d397 Update moonlight-common-c to fix audio problems with old GFE and Sunshine versions 2021-06-21 19:26:08 -05:00
WALKTHROUGH RAYMAND LEGENDS a3167b8152 Translated using Weblate (Hungarian)
Currently translated at 2.1% (4 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/hu/
2021-06-15 14:32:39 +02:00
Cameron Gutman cbeb23f16a Adjust Steam Link icon size to 116x116 2021-06-13 16:53:03 -05:00
WALKTHROUGH RAYMAND LEGENDS b4a6512784 Added translation using Weblate (Hungarian) 2021-06-13 21:47:33 +02:00
Cameron Gutman 1af2742013 Update moonlight-common-c to fix spurious exit of fast audio recovery mode on startup 2021-06-13 11:20:28 -05:00
Cameron Gutman 8048938158 Fix Linux build 2021-06-12 16:12:46 -05:00
Cameron Gutman 3689c7f982 Don't enable input capture if the window can't be foregrounded 2021-06-12 13:59:13 -05:00
Cameron Gutman 96c7057fee Update moonlight-common-c to avoid qwave.dll dependency 2021-06-12 12:21:55 -05:00
Cameron Gutman df7c4c82a9 Update moonlight-common-c with performance optimizations 2021-06-09 23:23:47 -05:00
Cameron Gutman 18d8db338b Update moonlight-common-c 2021-06-08 20:19:39 -05:00
Cameron Gutman 9637639e31 Update moonlight-common-c with audio FEC support 2021-06-04 18:46:36 -05:00
Héctor 17abe8f164 Translated using Weblate (Spanish)
Currently translated at 35.4% (66 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2021-05-27 21:33:27 +02:00
Cameron Gutman badaaece04 Update moonlight-common-c 2021-05-25 20:39:53 -05:00
Cameron Gutman 56119eebae Adapt to constification of AVCodec in FFmpeg master 2021-05-24 23:27:31 -05:00
Cameron Gutman c17c8e2a2c Remove receive time from performance overlay
It is largely superseded by network latency
2021-05-22 13:59:14 -05:00
Cameron Gutman e45a60f2ed Save RTT in VIDEO_STATS so it can be logged on disconnect 2021-05-22 13:57:12 -05:00
Cameron Gutman ebe9356937 Integrate Thai and Korean translations 2021-05-22 13:44:45 -05:00
Cameron Gutman 0094305299 Merge remote-tracking branch 'origin/weblate' 2021-05-22 13:33:20 -05:00
NicolasS b27050d3bf Translated using Weblate (French)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2021-05-22 18:32:19 +02:00
Jorys Paulin d941e596ad Translated using Weblate (French)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2021-05-22 18:32:19 +02:00
sanhoe ddaec92da5 Translated using Weblate (Korean)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2021-05-21 16:14:30 +02:00
sanhoe 1c0a4c92a8 Translated using Weblate (Korean)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2021-05-20 21:45:31 +02:00
sanhoe 383c00afbd Added translation using Weblate (Korean) 2021-05-20 16:32:05 +02:00
Krit789 d3b2bff8ad Translated using Weblate (Thai)
Currently translated at 41.9% (78 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/th/
2021-05-18 00:32:49 +02:00
Cameron Gutman 77aaa6141d Update moonlight-common-c to avoid excessive ENet retransmissions when RTT variance is 0 2021-05-16 15:40:38 -05:00
Krit789 2bb1af61d0 Added translation using Weblate (Thai) 2021-05-16 21:58:44 +02:00
Cameron Gutman 40b29472b3 Update moonlight-common-c with more accurate RTTs, minRequiredFecPackets, and input queue draining 2021-05-16 14:46:57 -05:00
Cameron Gutman 5d5029de59 Increase limit for the --fps option 2021-05-16 14:45:53 -05:00
Cameron Gutman cc96e6a222 Rebase AppImage on Ubuntu Bionic and Qt 5.15.2 2021-05-16 12:45:34 -05:00
Cameron Gutman 581c9438ca Add network latency to stats overlay 2021-05-15 14:40:31 -05:00
Cameron Gutman 54bd427f26 Integrate Japanese and Vietnamese translations 2021-05-13 00:24:00 -05:00
Cameron Gutman c55d184195 Rerun lupdate-pro and lrelease-pro 2021-05-13 00:13:58 -05:00
Cameron Gutman b279f94789 Merge remote-tracking branch 'origin/weblate' 2021-05-13 00:06:47 -05:00
bruh f3a897e99a Translated using Weblate (Vietnamese)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/vi/
2021-05-13 07:04:50 +02:00
bruh 392b1b5904 Added translation using Weblate (Vietnamese) 2021-05-12 07:36:31 +02:00
grtw2116 4daa8066a6 Translated using Weblate (Japanese)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ja/
2021-05-10 07:33:45 +02:00
NicolasS 8a89951286 Translated using Weblate (French)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2021-05-08 07:28:33 +02:00
grtw2116 c41ed55291 Added translation using Weblate (Japanese) 2021-05-08 07:28:33 +02:00
Cameron Gutman caba0499d7 Prepare for v3.1.3 2021-05-07 19:07:52 -05:00
Cameron Gutman 13deacfc3b Fix Spanish translation args 2021-05-07 18:56:08 -05:00
Cameron Gutman 7231085456 Run lupdate-pro and lrelease-pro 2021-05-06 17:26:26 -05:00
Cameron Gutman 663bb4aa10 Merge remote-tracking branch 'origin/weblate' 2021-05-06 17:24:39 -05:00
Cameron Gutman 18721b4542 Improve sanity checking and logging for unsupported GFE versions 2021-05-06 17:18:47 -05:00
NicolasS 19232722e2 Translated using Weblate (French)
Currently translated at 100.0% (186 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2021-05-05 22:32:11 +02:00
Cameron Gutman d2eca68484 Update moonlight-common-c to attempt to fix audio latency issues 2021-05-04 10:22:29 -05:00
Héctor 091167478e Translated using Weblate (Spanish)
Currently translated at 23.6% (44 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2021-05-04 10:32:11 +02:00
Cameron Gutman 547b8abc56 Prepare for v3.1.2 release 2021-05-03 17:38:14 -05:00
Héctor dbfac45d59 Translated using Weblate (Spanish)
Currently translated at 17.7% (33 of 186 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2021-05-03 09:32:23 +02:00
Cameron Gutman 086f6de867 Work around broken package repositories in AppVeyor 2021-05-02 14:49:07 -05:00
Cameron Gutman 0f93091848 Update SDL_GameControllerDB 2021-05-02 10:56:50 -05:00
Cameron Gutman ac19f62ed9 Never display placeholder text for an app collector game
Fixes #567
2021-05-02 10:39:25 -05:00
Cameron Gutman 8fcef63890 Don't waste time performing extra decoder initializations for EGL when it's not supported 2021-04-30 20:52:09 -05:00
Cameron Gutman 34cd2c1afa Run lupdate-pro and lrelease-pro 2021-04-30 20:09:15 -05:00
Cameron Gutman 5d1a19d49c Merge remote-tracking branch 'origin/weblate' 2021-04-30 20:07:59 -05:00
Cameron Gutman 2fbb320539 Refuse to stream if Moonlight is known incompatible with the host GFE version 2021-04-30 20:05:49 -05:00
Cameron Gutman b50e5ed7e6 Fix keyboard grab on X11
Fixes #584
2021-04-30 18:17:15 -05:00
Cameron Gutman 26c82ca72c Don't enable vsync on test decoders 2021-04-30 18:12:56 -05:00
Cameron Gutman f8a8a431a8 Revert warn_off removal
There are still a bunch of unused parameter warnings with GCC/Clang
2021-04-29 21:22:59 -05:00
Cameron Gutman fd27867195 Update moonlight-common-c and remove warn_off config option 2021-04-29 18:05:40 -05:00
Cameron Gutman 5e2a627795 Update moonlight-common-c with RTSP handshake retry logic 2021-04-28 17:38:59 -05:00
Cameron Gutman e081401c2d Update moonlight-common-c for multi-FEC support and audio latency fix 2021-04-26 22:51:46 -05:00
Cameron Gutman e62a268aac Avoid audio encryption on Steam Link for performance reasons 2021-04-23 19:49:03 -05:00
Cameron Gutman 140a9e5191 Enable audio stream encryption 2021-04-23 18:55:10 -05:00
Cameron Gutman 51b5f8046f Disable HTTP/2 on host PC connections for GFE 3.22 2021-04-19 18:41:01 -05:00
Cameron Gutman 91b965d1aa Update moonlight-common-c 2021-04-18 19:34:12 -05:00
Cameron Gutman f8faccee59 Remove qt5-devel which doesn't exist in Fedora 34 2021-04-17 11:11:37 -05:00
shower 6ac46a36a7 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (184 of 184 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2021-04-17 13:27:02 +02:00
Héctor 27572ba4bc Translated using Weblate (Spanish)
Currently translated at 16.3% (30 of 184 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2021-04-15 22:27:01 +02:00
Cameron Gutman 21be832161 Prepare for v3.1.1 release 2021-04-09 21:07:05 -05:00
Cameron Gutman 5769632793 Update moonlight-common-c for initial GFE 3.22 compatibility 2021-04-09 20:18:47 -05:00
Cameron Gutman 9efd959ed8 Merge branch 'weblate' 2021-04-09 20:17:39 -05:00
Cameron Gutman 5c72afe17e Run lupdate-pro and lrelease-pro 2021-04-04 20:45:57 -05:00
a dc24319333 Translated using Weblate (Russian)
Currently translated at 100.0% (184 of 184 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2021-04-05 03:42:34 +02:00
Jorys Paulin 1acbdf94aa Translated using Weblate (French)
Currently translated at 100.0% (184 of 184 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2021-04-05 03:42:33 +02:00
Cameron Gutman 44229dfe51 Update translations
- Add RU and ES to resources.qrc and app.pro
- Rerun lupdate-pro and lrelease-pro
2021-04-04 10:25:06 -05:00
a 13206227d9 Translated using Weblate (Russian)
Currently translated at 100.0% (184 of 184 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2021-04-04 17:03:35 +02:00
Cameron Gutman e3e8a57df6 Update to OpenSSL 1.1.1k and FFmpeg 4.3.2 2021-03-31 20:27:33 -05:00
Cameron Gutman 7afff80984 Update moonlight-common-c to pick up AMF HEVC parsing fix 2021-03-31 20:07:45 -05:00
Cameron Gutman 500522418e Fix AppImage build 2021-03-28 16:07:20 -05:00
a 5ccac5a5c3 Translated using Weblate (Russian)
Currently translated at 45.1% (83 of 184 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2021-03-28 14:29:40 +02:00
Héctor 69822f4401 Translated using Weblate (Spanish)
Currently translated at 11.4% (21 of 184 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2021-03-28 14:29:39 +02:00
reloxx13 f290cfbd35 Translated using Weblate (German)
Currently translated at 100.0% (184 of 184 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2021-03-28 14:29:39 +02:00
Cameron Gutman fae30f59ce Add attribution for Cloudsmith 2021-03-27 12:40:51 -05:00
a a15f303c0a Added translation using Weblate (Russian) 2021-03-26 05:05:09 +01:00
Cameron Gutman 5e85038d78 Update AppImage to libva 2.11 2021-03-25 22:17:51 -05:00
Héctor 4e7b35c54e Added translation using Weblate (Spanish) 2021-03-25 09:04:18 +01:00
Cameron Gutman 48dde310ae Add Norwegian Bokmål to the build 2021-03-24 20:40:22 -05:00
Cameron Gutman 6b041e7657 Run lrelease-pro 2021-03-24 20:39:52 -05:00
Cameron Gutman 69576ee1e1 Run lupdate-pro 2021-03-24 20:39:39 -05:00
Cameron Gutman bd3105f62e Merge remote-tracking branch 'origin/weblate' 2021-03-24 20:28:24 -05:00
Cameron Gutman 02e3f0e71b Add missing declaration 2021-03-22 23:32:00 -05:00
Cameron Gutman 74bf981f3d Ensure hwframe read-back works in SDL renderer 2021-03-22 23:15:14 -05:00
Cameron Gutman 32f0678692 Only pass modifiers array if DRM_MODE_FB_MODIFIERS is set 2021-03-22 22:52:06 -05:00
Cameron Gutman e74753bec1 Allow fallback from EGL to direct on EGLImage export failure 2021-03-22 22:51:29 -05:00
Cameron Gutman 4a8c9ad17f Add support for 4-plane frames 2021-03-22 00:46:04 -05:00
Cameron Gutman 0c6c882f50 Fix attribute buffer overflow for fully planar YUV formats 2021-03-22 00:14:06 -05:00
Cameron Gutman 579f25c1ba Add missing EGL_DMA_BUF_PLANE2 definitions 2021-03-22 00:02:03 -05:00
Cameron Gutman d5eb5c8816 Fix build with EGL 1.4 headers 2021-03-21 23:59:35 -05:00
Cameron Gutman 26d04b5f9c Render DRM-PRIME frames as opaque images
We can't always assume they are NV12. Even if they _are_ NV12,
they may have DRM format modifiers that are incompatible with
simply assuming linear Y and UV buffers (such as tiling).
2021-03-21 23:23:12 -05:00
Cameron Gutman a0b9684504 Continue refactoring for non-NV12 support in EGLRenderer 2021-03-21 21:43:57 -05:00
Cameron Gutman 402b6d1f0c Begin refactoring for non-NV12 support in EGLRenderer 2021-03-21 19:20:52 -05:00
Cameron Gutman e87a5fa354 Pass DRM format modifiers to FB creation 2021-03-21 17:54:10 -05:00
Allan Nordhøy 63574f643e Translated using Weblate (Norwegian Bokmål)
Currently translated at 70.1% (129 of 184 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nb_NO/
2021-03-21 10:36:54 +01:00
Cameron Gutman 1d6b1020c3 Translated using Weblate (French)
Currently translated at 73.9% (136 of 184 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2021-03-21 06:29:36 +01:00
Allan Nordhøy 96337a3230 Translated using Weblate (Norwegian Bokmål)
Currently translated at 16.3% (30 of 184 strings)

Translation: moonlight/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nb_NO/
2021-03-21 02:33:56 +01:00
reloxx13 950c59d8b4 Translated using Weblate (German)
Currently translated at 100.0% (184 of 184 strings)

Translation: moonlight/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2021-03-21 02:33:56 +01:00
Allan Nordhøy 9cfd095281 Added translation using Weblate (Norwegian Bokmål) 2021-03-21 02:33:56 +01:00
Anonymous 996e049863 Translated using Weblate (Chinese (Simplified))
Currently translated at 96.1% (177 of 184 strings)

Translation: moonlight/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2021-03-21 02:33:56 +01:00
Anonymous 6e42817c3e Translated using Weblate (French)
Currently translated at 74.4% (137 of 184 strings)

Translation: moonlight/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2021-03-21 02:33:56 +01:00
Cameron Gutman 7758344a57 Fix Weblate badge 2021-03-20 18:12:04 -05:00
Cameron Gutman b5df566b6b Add Weblate links to readme
Closes #539
2021-03-20 18:11:15 -05:00
Cameron Gutman e21fd19a70 Prepare for v3.1.0 release 2021-03-14 11:30:21 -05:00
Cameron Gutman 98672c8e44 Allow remote desktop mouse mode in embedded mode
It can be easier to control the mouse on the desktop that way
2021-03-13 16:58:58 -06:00
Cameron Gutman 2e92f0640b Update SDL_GameControllerDB 2021-03-13 15:50:01 -06:00
Cameron Gutman d6b314f573 Add option to build for embedded environments 2021-03-13 15:20:58 -06:00
Cameron Gutman d7a150ffa8 Only enable capture mode list if capture toggle is enabled 2021-03-13 14:59:32 -06:00
Cameron Gutman 955da6be30 Enable Gallium VAAPI driver on VAAPI 1.x 2021-03-13 14:37:13 -06:00
Cameron Gutman b5f8e6738a Move LANG_DE to the end of the Language enum 2021-03-09 20:13:55 -06:00
reloxx13 d7fa8c9795 Add german translation (#536)
* add german translation

* add german to language list

* donttranslate language names. always use the native name.

* add compiled german translation file

* german translation handling

* add german to getSuffixFromLanguage

* update german string and translation

* update all language files with new strings

* typo

* compile

* more typos and fixes for german
2021-03-09 20:10:36 -06:00
Cameron Gutman acb7a5b3c8 Fix XCB out-of-sequence abort after switching to non-threaded Qt renderer 2021-03-07 16:48:10 -06:00
Cameron Gutman 783a57ef89 Use separate thread for streaming on X11 and Wayland 2021-03-07 12:15:42 -06:00
Cameron Gutman d33ccce683 Hold a reference to the underlying DMA-BUFs until the next frame 2021-03-07 12:01:25 -06:00
Cameron Gutman d722bf10b9 Don't use glFinish() with KMSDRM backend 2021-03-07 11:23:31 -06:00
Cameron Gutman 41337475a2 Update SDL_GameControllerDB 2021-03-06 14:29:55 -06:00
Cameron Gutman f504a069d7 Update VCRedist version for VC 16.9 2021-03-05 17:51:26 -06:00
Cameron Gutman 96f79688dd Use av_packet_alloc() instead of av_init_packet()
sizeof(AVPacket) may be deprecated from ABI in the near future
2021-03-05 17:47:04 -06:00
Cameron Gutman c330898aeb Add workaround for AppView breaking after changing language at runtime 2021-03-05 17:44:31 -06:00
Cameron Gutman 94952cf0a4 Update issue template 2021-03-03 01:39:55 -06:00
Cameron Gutman a3c249abe9 Don't load the app label asynchronously 2021-03-02 19:03:09 -06:00
Cameron Gutman c26562fab8 Fix Steam Link build 2021-03-02 18:57:16 -06:00
Cameron Gutman b7cbd99212 Fix tooltip display in app grid 2021-03-02 18:35:50 -06:00
Cameron Gutman b53ef35a85 Improve loading speed of PC and app grids 2021-03-02 18:32:56 -06:00
Cameron Gutman 1ebb5fefb1 Address Clazy warnings 2021-03-02 18:14:15 -06:00
Cameron Gutman 164b3edd41 Fix viewport size on High DPI Wayland systems
Fixes #533
2021-03-01 22:47:14 -06:00
Cameron Gutman 819ea36cca Hack to fix build on AppVeyor 2021-02-28 16:47:39 -06:00
Cameron Gutman 088c9b9e4e Stop single-arch binary build before extracting dSYM 2021-02-28 16:02:50 -06:00
Cameron Gutman ab25a9b8ee Add Universal binary support to build script 2021-02-28 15:49:59 -06:00
Cameron Gutman b2aea9f8f0 Revert change to only fire special combos on key up 2021-02-28 15:06:46 -06:00
Cameron Gutman 5399b95936 Fix update checker on macOS with Qt 6.0 builds 2021-02-28 14:33:20 -06:00
Cameron Gutman 1d5c120748 Add support for specifying minimum kernel versions in the update manifest 2021-02-28 14:27:29 -06:00
jmt-gh 6be7301e69 Stats overlay controller combo (#495)
* Update gamepad.cpp

add gamepad combo to toggle stats overlay

* typo fixes
2021-02-27 17:03:52 -06:00
Maximiliano Bertacchini fff9f2b17d Add numpad Enter handlers (Keys.onEnterPressed) (#516) 2021-02-27 17:01:22 -06:00
Cameron Gutman 87a7d2e45c Add setting to only capture system keys in full-screen 2021-02-27 16:47:38 -06:00
Cameron Gutman 58803ef40d Update SDL to e561ce3 and OpenSSL to 1.1.1j 2021-02-27 13:08:21 -06:00
Cameron Gutman b27ca993aa Add support for system key capture in windowed mode on macOS 2021-02-26 21:43:47 -06:00
Cameron Gutman 28ecc6bcbf Remove old Qt Multimedia audio renderer 2021-02-25 23:23:08 -06:00
Cameron Gutman 30c5e3f237 Fix window icons on Wayland 2021-02-25 23:20:21 -06:00
Cameron Gutman 6a25a3a3d0 Don't require DRM master for SDL KMSDRM backend 2021-02-25 22:27:20 -06:00
Cameron Gutman 9a84c0ae30 Update AppImage to official SDL Git repo 2021-02-25 22:00:35 -06:00
Cameron Gutman 133cc5457d Use SDL_CaptureMouse() on macOS and Linux too 2021-02-08 18:30:22 -06:00
Cameron Gutman 05e4c5b507 Move clipboard processing to a separate thread to avoid blocking the main thread during pastes 2021-02-07 17:18:35 -06:00
Cameron Gutman 8372717a09 Improve Unicode support for some common characters 2021-02-07 16:00:00 -06:00
Cameron Gutman 5f3f2a96fd Improve detection of having no clipboard text 2021-02-07 13:53:03 -06:00
Cameron Gutman b83c6f0c28 Add support for pasting clipboard text 2021-02-07 13:46:38 -06:00
Cameron Gutman 36dc0f3e3c Use mouse capture for Win32 instead of our global mouse state hack 2021-02-07 12:38:57 -06:00
Cameron Gutman b4edde6f90 Disable Qt 6 threaded renderer on Windows
They switched from non-threaded to threaded by default in Qt 6
2021-02-07 11:21:47 -06:00
Cameron Gutman 395eb5581c Cache results of WMUtils::isRunningX11() and WMUtils::isRunningWayland() 2021-02-07 11:03:53 -06:00
Cameron Gutman 6dab251055 Don't overwrite window mode settings when switching between WM and non-WM environments 2021-02-07 10:49:29 -06:00
Cameron Gutman 3bd6a6a614 Fullscreen is no longer required for passing system keyboard shortcuts 2021-02-06 20:15:58 -06:00
Cameron Gutman 93d21a0d1d Fix EGLImage leak when not using a hwaccel backend 2021-02-06 17:59:05 -06:00
Cameron Gutman 2c2a73e320 Fix build and require SDL 2.0.15 to hook 2021-02-06 17:07:21 -06:00
Cameron Gutman a88a3c9ea3 Allow SDL to render with KMSDRM backend while Qt is also using DRM 2021-02-06 16:32:54 -06:00
Cameron Gutman e4df70cd56 Use the async connect thread on all platforms
It turned out that the cause of the random crashes was the threaded renderer
that we now turn off.
2021-02-06 15:14:15 -06:00
Cameron Gutman 90f7d35150 Always use a non-threaded renderer
This ensures we can block Qt's rendering as needed to synchronize with SDL
2021-02-06 14:58:30 -06:00
Cameron Gutman 75b19a60c1 Fix build on macOS and keyboard grab bugs 2021-02-06 11:29:18 -06:00
Cameron Gutman 9fc921f49f Use SDL's keyboard grab support now that it's been upstreamed 2021-02-05 00:13:20 -06:00
Cameron Gutman 4b9e90ac72 Update SDL for Windows and macOS to Hg 60b9ce4ee73e
https://github.com/cgutman/moonlight-deps/commit/ad955430988be24fd379a6152642c7ce4053bc7f
2021-02-04 20:51:33 -06:00
Cameron Gutman 36dd6c83e0 Fix assert failure when using v4l2m2m with EGL 2021-02-04 20:38:18 -06:00
Cameron Gutman d790313221 Use the existing install of Qt on macOS in CI 2021-02-04 19:39:58 -06:00
Cameron Gutman c3895f06c0 Fix handling of preferred/compatible pixel formats with EGLRenderer and VAAPI/DRM backends 2021-02-04 19:39:18 -06:00
Cameron Gutman 76e81fa651 Add windowed support for MMAL 2021-02-03 20:57:39 -06:00
Cameron Gutman d63a1b0eb0 Handle custom hwaccel decoders 2021-02-02 19:45:22 -06:00
Cameron Gutman 4dffac3c78 Ask for NV12 buffers for DRMRenderer 2021-02-02 19:40:06 -06:00
Cameron Gutman 88391b0274 Use the decoder's supported pix_fmts to select a suitable renderer 2021-02-02 19:05:27 -06:00
Cameron Gutman c3cea0238e Fix build with old EGL 1.5 headers without EGL_EGL_PROTOTYPES 2021-01-31 21:42:56 -06:00
Cameron Gutman 5bf4266973 Fix build with old Mesa EGL headers 2021-01-31 18:41:53 -06:00
Cameron Gutman ed57c54d06 Move EGL definitions into renderer.h to try to fix build on Armbian 2021-01-31 17:01:11 -06:00
Cameron Gutman 01936e000e Fix Steam Link bundle build 2021-01-31 16:27:38 -06:00
Cameron Gutman 80137848dc Add DMA-BUF export compatibility with EGL 1.4 2021-01-31 16:24:31 -06:00
Cameron Gutman 149575e0c4 Don't retranslate if the language didn't change 2021-01-31 15:28:00 -06:00
Cameron Gutman 6a66b462ce Allow DRMRenderer to be used for indirect rendering 2021-01-31 15:19:19 -06:00
Cameron Gutman 1e31f6fe77 Fix initialization of DRM backend with X running 2021-01-31 15:03:40 -06:00
Cameron Gutman f173b3ff09 Attempt to fix AppImage and Steam Link build errors 2021-01-31 14:48:42 -06:00
Cameron Gutman baffab62ce Fix uninitialized variable warning 2021-01-31 12:58:19 -06:00
Cameron Gutman d141cec2a8 Fix mangled macOS libraries after transition to submodule 2021-01-31 12:23:52 -06:00
Cameron Gutman df87ac3810 Update SDL in AppImage to Hg d75deb75464a 2021-01-31 12:13:20 -06:00
Cameron Gutman 46960ce0a4 Remove side-by-side support for Qt 5.9 on Steam Link 2021-01-31 12:08:15 -06:00
Cameron Gutman 522206b0bc Use shallow-cloned submodule for prebuilts 2021-01-31 11:50:41 -06:00
Cameron Gutman 24d60dd3ab Remove prebuilt binaries 2021-01-31 11:40:41 -06:00
Cameron Gutman dbfdc2fd14 Share DRM FD and GBM device with SDL
Depends on https://hg.libsdl.org/SDL/rev/d75deb75464a
2021-01-30 21:11:52 -06:00
Cameron Gutman a97912e297 Print the error when querying EGL_EXTENSIONS fails 2021-01-30 20:13:20 -06:00
Cameron Gutman cedba501ee Query the frontend renderer to determine fullscreen-only limitations 2021-01-30 17:57:34 -06:00
Cameron Gutman ac947d3bda Add EGLImage export support to DRMRenderer 2021-01-30 17:52:23 -06:00
Cameron Gutman 243ba371b3 Initialize a DRM hardware context for hwaccel usage 2021-01-30 16:33:58 -06:00
Cameron Gutman 3af24eafbb Build fixes 2021-01-30 12:53:57 -06:00
Cameron Gutman 69b6022afe Add language selection option in settings
Fixes #501
2021-01-30 12:14:31 -06:00
Cameron Gutman 1cd66419d4 Remember desired cursor hide state
Fixes #511
2021-01-30 11:08:01 -06:00
WLongandWLong 5309a02f6d Add simplified Chinese language (#506)
* 添加qml_zh_cn.ts

* 更新部分翻译

* 优化部分文字描述

* 优化部分文字描述

* 恢复README.md

Co-authored-by: WLong <a101084@126.com>
2021-01-30 10:10:36 -06:00
Cameron Gutman 541784382a Add support for AV_HWDEVICE_TYPE_DRM hwaccels 2021-01-30 10:00:01 -06:00
Cameron Gutman 2a7fae2c82 Revert "Add logic to try LibreELEC's out-of-tree stateless V4L2 decoders"
v4l2request is a hwaccel not a decoder

This reverts commit ed3a5448b1.
2021-01-29 20:30:01 -06:00
Cameron Gutman ed3a5448b1 Add logic to try LibreELEC's out-of-tree stateless V4L2 decoders 2021-01-29 20:19:27 -06:00
Cameron Gutman 2eb4769bbc Only trigger special key combos when all keys are up 2021-01-24 13:02:42 -06:00
Cameron Gutman 02019000df Ctrl+Alt+Shift+Down Arrow is a reserved key combo on GNOME, so switch to Ctrl+Alt+Shift+D 2021-01-21 23:05:48 -06:00
Cameron Gutman 6262f51c96 Key capture is supported on Wayland with latest SDL Hg 2021-01-21 21:07:54 -06:00
Cameron Gutman d38ff5759a Add Ctrl+Alt+Shift+Down Arrow shortcut to minimize 2021-01-21 19:39:17 -06:00
Cameron Gutman f6de2e4dd3 Revert "Disable the system keyboard capture option on Wayland"
SDL now supports keyboard grab with https://hg.libsdl.org/SDL/rev/b0e13627c656

This reverts commit bc10fd0aba.
2021-01-20 21:43:16 -06:00
Cameron Gutman 3959f447d4 Fix detection of x64 architecture on some machines 2021-01-17 12:58:08 -06:00
839 changed files with 20591 additions and 198206 deletions
+11 -2
View File
@@ -1,10 +1,12 @@
---
name: Bug report
about: Ask for help on Discord before reporting a bug
about: Follow the troubleshooting guide before reporting a bug
---
**READ ME FIRST!**
This issue tracker is not the right place to ask for troubleshooting help if you're not able to connect or stream at all. For those requests, please use the #pc-help channel on our Discord server where there are many more people who can help (or direct you back here if it looks like a Moonlight bug after all). https://moonlight-stream.org/discord
If you're here because something basic is not working (like gamepad input, video, or similar), it's probably something specific to your setup, so make sure you've gone through the Troubleshooting Guide first: https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting
If you still have trouble with basic functionality after following the guide, join our Discord server where there are many other volunteers who can help (or direct you back here if it looks like a Moonlight bug after all). https://moonlight-stream.org/discord
**Describe the bug**
A clear and concise description of what the bug is.
@@ -26,14 +28,21 @@ List the games you've tried that exhibit the issue. To see if the issue is game-
- If so, which settings have been changed?
- Does the problem still occur after reverting settings back to default?
**Gamepad-related issues (please complete if problem is gamepad-related)**
- Do you have any gamepads connected to your host PC directly?
- Does the problem still remain if you stream the desktop and use https://html5gamepad.com to test your gamepad?
- Instructions for streaming the desktop can be found here: https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide
**Client PC details (please complete the following information)**
- OS: [e.g. Windows 10 1809]
- Moonlight Version: [e.g. v0.9.0]
- GPU: [e.g. Intel HD Graphics 520]
**Server PC details (please complete the following information)**
- OS: [e.g. Windows 10 1809]
- GeForce Experience version: [e.g. 3.16.0.140]
- Nvidia GPU driver: [e.g. 417.35]
- Antivirus and firewall software: [e.g. Windows Defender and Windows Firewall]
**Moonlight Logs (please attach)**
- On Windows, `Moonlight-###.log` files can be found in `%TEMP%`. Simply type that into the File Explorer path field to navigate there.
+4
View File
@@ -13,3 +13,7 @@
[submodule "h264bitstream/h264bitstream"]
path = h264bitstream/h264bitstream
url = https://github.com/aizvorski/h264bitstream.git
[submodule "libs"]
path = libs
url = https://github.com/cgutman/moonlight-qt-prebuilts.git
shallow = true
+1 -1
View File
@@ -139,7 +139,7 @@ private:
L"SS2OSD.dll", // ASUS Sonic Studio 2
L"Nahimic2OSD.dll",
L"NahimicMSIOSD.dll",
L"nhAsusPhoebusOSD.dll" // ASUS Phoebus
L"nhAsusPhoebusOSD.dll", // ASUS Phoebus
// This DLL has been seen in several crash reports. Some Googling
// suggests it's highly unstable and causes issues in many games.
+16 -7
View File
@@ -4,10 +4,11 @@
Moonlight also has mobile versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [iOS](https://github.com/moonlight-stream/moonlight-ios).
You can follow development on our [Discord server](https://moonlight-stream.org/discord).
You can follow development on our [Discord server](https://moonlight-stream.org/discord) and help translate Moonlight into your language on [Weblate](https://hosted.weblate.org/projects/moonlight/moonlight-qt/).
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/glj5cxqwy2w3bglv/branch/master?svg=true)](https://ci.appveyor.com/project/cgutman/moonlight-qt/branch/master)
[![Downloads](https://img.shields.io/github/downloads/moonlight-stream/moonlight-qt/total)](https://github.com/moonlight-stream/moonlight-qt/releases)
[![Translation Status](https://hosted.weblate.org/widgets/moonlight/-/moonlight-qt/svg-badge.svg)](https://hosted.weblate.org/projects/moonlight/moonlight-qt/)
## Features
- Hardware accelerated video decoding on Windows, Mac, and Linux
@@ -15,7 +16,7 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
- Supports streaming at 720p, 1080p, 1440p, 4K, and the client PC's native screen resolution
- 7.1 surround sound audio support
- 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 in fullscreen mode
- 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
@@ -28,6 +29,12 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
- [Raspberry Pi 4](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-Raspberry-Pi-4)
- [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).
## Building
### Windows Build Requirements
@@ -36,12 +43,12 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
* [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) (Community edition is fine)
* Select **MSVC 2019** 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.11 or later (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 5.15 SDK or later
* macOS High Sierra (10.13) or later
* Xcode 11
* Qt 6.2 SDK or later
* macOS Mojave (10.14) or later
* Xcode 13
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
### Linux/Unix Build Requirements
@@ -49,7 +56,7 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
* GCC or Clang
* Install the required packages:
* Debian/Ubuntu: `libegl1-mesa-dev libgl1-mesa-dev libopus-dev libqt5svg5-dev libsdl2-dev libsdl2-ttf-dev libssl-dev libavcodec-dev libva-dev libvdpau-dev libxkbcommon-dev qt5-default qt5-qmake qtbase5-dev qtdeclarative5-dev qtquickcontrols2-5-dev wayland-protocols qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtquick-window2 qml-module-qtquick2`
* RedHat/Fedora: `openssl-devel qt5-devel SDL2-devel ffmpeg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel SDL2_ttf-devel`
* 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/
### Steam Link Build Requirements
@@ -68,6 +75,8 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
* 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 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.
## Contribute
1. Fork us
+1 -1
View File
@@ -17,7 +17,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
<string>10.14</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
+49 -7
View File
@@ -33,19 +33,21 @@ DEFINES += QT_DEPRECATED_WARNINGS
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
win32 {
INCLUDEPATH += $$PWD/../libs/windows/include
contains(QT_ARCH, i386) {
LIBS += -L$$PWD/../libs/windows/lib/x86
INCLUDEPATH += $$PWD/../libs/windows/include/x86
}
contains(QT_ARCH, x86_64) {
LIBS += -L$$PWD/../libs/windows/lib/x64
INCLUDEPATH += $$PWD/../libs/windows/include/x64
}
contains(QT_ARCH, arm64) {
LIBS += -L$$PWD/../libs/windows/lib/arm64
INCLUDEPATH += $$PWD/../libs/windows/include/arm64
}
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib dbghelp.lib qwave.lib
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
}
macx {
INCLUDEPATH += $$PWD/../libs/mac/include
@@ -88,6 +90,11 @@ unix:!macx {
PKGCONFIG += libdrm
CONFIG += libdrm
}
packagesExist(ffnvcodec) {
PKGCONFIG += ffnvcodec
CONFIG += cuda
}
}
packagesExist(wayland-client) {
@@ -118,6 +125,7 @@ macx {
}
SOURCES += \
backend/nvaddress.cpp \
backend/nvapp.cpp \
main.cpp \
backend/computerseeker.cpp \
@@ -131,6 +139,7 @@ SOURCES += \
cli/commandlineparser.cpp \
cli/quitstream.cpp \
cli/startstream.cpp \
settings/compatfetcher.cpp \
settings/mappingfetcher.cpp \
settings/streamingpreferences.cpp \
streaming/input/abstouch.cpp \
@@ -154,7 +163,9 @@ SOURCES += \
wm.cpp
HEADERS += \
backend/nvaddress.h \
backend/nvapp.h \
settings/compatfetcher.h \
settings/mappingfetcher.h \
utils.h \
backend/computerseeker.h \
@@ -192,7 +203,6 @@ ffmpeg {
SOURCES += \
streaming/video/ffmpeg.cpp \
streaming/video/ffmpeg-renderers/sdlvid.cpp \
streaming/video/ffmpeg-renderers/cuda.cpp \
streaming/video/ffmpeg-renderers/pacer/pacer.cpp \
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.cpp
@@ -200,7 +210,6 @@ ffmpeg {
streaming/video/ffmpeg.h \
streaming/video/ffmpeg-renderers/renderer.h \
streaming/video/ffmpeg-renderers/sdlvid.h \
streaming/video/ffmpeg-renderers/cuda.h \
streaming/video/ffmpeg-renderers/pacer/pacer.h \
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.h
}
@@ -250,6 +259,19 @@ libdrm {
DEFINES += HAVE_DRM
SOURCES += streaming/video/ffmpeg-renderers/drm.cpp
HEADERS += streaming/video/ffmpeg-renderers/drm.h
linux {
message(Master hooks enabled)
SOURCES += masterhook.c
LIBS += -ldl
}
}
cuda {
message(CUDA support enabled)
DEFINES += HAVE_CUDA
SOURCES += streaming/video/ffmpeg-renderers/cuda.cpp
HEADERS += streaming/video/ffmpeg-renderers/cuda.h
}
config_EGL {
message(EGL renderer selected)
@@ -264,7 +286,7 @@ config_EGL {
config_SL {
message(Steam Link build configuration selected)
DEFINES += STEAM_LINK HAVE_SLVIDEO HAVE_SLAUDIO
DEFINES += EMBEDDED_BUILD STEAM_LINK HAVE_SLVIDEO HAVE_SLAUDIO
LIBS += -lSLVideo -lSLAudio
SOURCES += \
@@ -307,13 +329,33 @@ discord-rpc {
LIBS += -ldiscord-rpc
DEFINES += HAVE_DISCORD
}
embedded {
message(Embedded build)
DEFINES += EMBEDDED_BUILD
}
RESOURCES += \
resources.qrc \
qml.qrc
TRANSLATIONS += \
languages/qml_fr.ts
languages/qml_zh_cn.ts \
languages/qml_de.ts \
languages/qml_fr.ts \
languages/qml_nb_NO.ts \
languages/qml_ru.ts \
languages/qml_es.ts \
languages/qml_ja.ts \
languages/qml_vi.ts \
languages/qml_th.ts \
languages/qml_ko.ts \
languages/qml_hu.ts \
languages/qml_nl.ts \
languages/qml_sv.ts \
languages/qml_tr.ts \
languages/qml_uk.ts \
languages/qml_zh_Hant.ts
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
+66 -35
View File
@@ -15,8 +15,8 @@ AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
// Allow HTTP redirects
m_Nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
connect(&m_Nam, SIGNAL(finished(QNetworkReply*)),
this, SLOT(handleUpdateCheckRequestFinished(QNetworkReply*)));
connect(&m_Nam, &QNetworkAccessManager::finished,
this, &AutoUpdateChecker::handleUpdateCheckRequestFinished);
QString currentVersion(VERSION_STR);
qDebug() << "Current Moonlight version:" << currentVersion;
@@ -63,11 +63,41 @@ QString AutoUpdateChecker::getPlatform()
return QStringLiteral("steamlink");
#elif defined(APP_IMAGE)
return QStringLiteral("appimage");
#elif defined(Q_OS_DARWIN) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
// Qt 6 changed this from 'osx' to 'macos'. Use the old one
// to be consistent (and not require another entry in the manifest).
return QStringLiteral("osx");
#else
return QSysInfo::productType();
#endif
}
int AutoUpdateChecker::compareVersion(QVector<int>& version1, QVector<int>& version2) {
for (int i = 0;; i++) {
int v1Val = 0;
int v2Val = 0;
// Treat missing decimal places as 0
if (i < version1.count()) {
v1Val = version1[i];
}
if (i < version2.count()) {
v2Val = version2[i];
}
if (i >= version1.count() && i >= version2.count()) {
// Equal versions
return 0;
}
if (v1Val < v2Val) {
return -1;
}
else if (v1Val > v2Val) {
return 1;
}
}
}
void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
{
Q_ASSERT(reply->isFinished());
@@ -119,46 +149,47 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
if (updateObj["arch"] == QSysInfo::buildCpuArchitecture() &&
updateObj["platform"] == getPlatform()) {
// Check the kernel version minimum if one exists
if (updateObj.contains("kernel_version_at_least") && updateObj["kernel_version_at_least"].isString()) {
QVector<int> requiredVersionQuad;
QVector<int> actualVersionQuad;
QString requiredVersion = updateObj["kernel_version_at_least"].toString();
QString actualVersion = QSysInfo::kernelVersion();
parseStringToVersionQuad(requiredVersion, requiredVersionQuad);
parseStringToVersionQuad(actualVersion, actualVersionQuad);
if (compareVersion(actualVersionQuad, requiredVersionQuad) < 0) {
qDebug() << "Skipping manifest entry due to kernel version (" << actualVersion << "<" << requiredVersion << ")";
continue;
}
}
qDebug() << "Found update manifest match for current platform";
QString latestVersion = updateObj["version"].toString();
qDebug() << "Latest version of Moonlight for this platform is:" << latestVersion;
QVector<int> latestVersionQuad;
parseStringToVersionQuad(latestVersion, latestVersionQuad);
for (int i = 0;; i++) {
int latestVer = 0;
int currentVer = 0;
// Treat missing decimal places as 0
if (i < latestVersionQuad.count()) {
latestVer = latestVersionQuad[i];
}
if (i < m_CurrentVersionQuad.count()) {
currentVer = m_CurrentVersionQuad[i];
}
if (i >= latestVersionQuad.count() && i >= m_CurrentVersionQuad.count()) {
break;
}
if (currentVer < latestVer) {
qDebug() << "Update available";
emit onUpdateAvailable(updateObj["version"].toString(),
updateObj["browser_url"].toString());
return;
}
else if (currentVer > latestVer) {
qDebug() << "Update manifest version lower than current version";
return;
}
int res = compareVersion(m_CurrentVersionQuad, latestVersionQuad);
if (res < 0) {
// m_CurrentVersionQuad < latestVersionQuad
qDebug() << "Update available";
emit onUpdateAvailable(updateObj["version"].toString(),
updateObj["browser_url"].toString());
return;
}
else if (res > 0) {
qDebug() << "Update manifest version lower than current version";
return;
}
else {
qDebug() << "Update manifest version equal to current version";
return;
}
qDebug() << "Update manifest version equal to current version";
return;
}
}
else {
@@ -166,8 +197,8 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
}
}
qWarning() << "No entry in update manifest found for current platform: "
<< QSysInfo::buildCpuArchitecture() << getPlatform();
qWarning() << "No entry in update manifest found for current platform:"
<< QSysInfo::buildCpuArchitecture() << getPlatform() << QSysInfo::kernelVersion();
}
else {
qWarning() << "Update checking failed with error: " << reply->error();
+2
View File
@@ -20,6 +20,8 @@ private slots:
private:
void parseStringToVersionQuad(QString& string, QVector<int>& version);
int compareVersion(QVector<int>& version1, QVector<int>& version2);
QString getPlatform();
QVector<int> m_CurrentVersionQuad;
+3 -3
View File
@@ -46,8 +46,8 @@ public:
m_Computer(computer),
m_App(app)
{
connect(this, SIGNAL(boxArtFetchCompleted(NvComputer*,NvApp,QUrl)),
boxArtManager, SLOT(handleBoxArtLoadComplete(NvComputer*,NvApp,QUrl)));
connect(this, &NetworkBoxArtLoadTask::boxArtFetchCompleted,
boxArtManager, &BoxArtManager::handleBoxArtLoadComplete);
}
signals:
@@ -106,7 +106,7 @@ void BoxArtManager::handleBoxArtLoadComplete(NvComputer* computer, NvApp app, QU
QUrl BoxArtManager::loadBoxArtFromNetwork(NvComputer* computer, int appId)
{
NvHTTP http(computer->activeAddress, computer->serverCert);
NvHTTP http(computer);
QString cachePath = getFilePathForBoxArt(computer, appId);
QImage image;
+47 -33
View File
@@ -27,9 +27,9 @@ public:
}
private:
bool tryPollComputer(QString address, bool& changed)
bool tryPollComputer(NvAddress address, bool& changed)
{
NvHTTP http(address, m_Computer->serverCert);
NvHTTP http(address, 0, m_Computer->serverCert);
QString serverInfo;
try {
@@ -38,7 +38,7 @@ private:
return false;
}
NvComputer newState(address, serverInfo, QSslCertificate());
NvComputer newState(http, serverInfo);
// Ensure the machine that responded is the one we intended to contact
if (m_Computer->uuid != newState.uuid) {
@@ -52,9 +52,7 @@ private:
bool updateAppList(bool& changed)
{
Q_ASSERT(m_Computer->activeAddress != nullptr);
NvHTTP http(m_Computer->activeAddress, m_Computer->serverCert);
NvHTTP http(m_Computer);
QVector<NvApp> appList;
@@ -88,7 +86,7 @@ private:
if (tryPollComputer(address, stateChanged)) {
if (!wasOnline) {
qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress;
qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress.toString();
}
online = true;
break;
@@ -146,7 +144,8 @@ private:
ComputerManager::ComputerManager(QObject *parent)
: QObject(parent),
m_PollingRef(0),
m_MdnsBrowser(nullptr)
m_MdnsBrowser(nullptr),
m_CompatFetcher(nullptr)
{
QSettings settings;
@@ -159,12 +158,15 @@ ComputerManager::ComputerManager(QObject *parent)
}
settings.endArray();
// Fetch latest compatibility data asynchronously
m_CompatFetcher.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
// while quitting, however this is a one time signal - additional
// requests would not be aborted and block termination.
connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(handleAboutToQuit()));
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &ComputerManager::handleAboutToQuit);
}
ComputerManager::~ComputerManager()
@@ -302,8 +304,8 @@ void ComputerManager::startPollingComputer(NvComputer* computer)
if (!pollingEntry->isActive()) {
PcMonitorThread* thread = new PcMonitorThread(computer);
connect(thread, SIGNAL(computerStateChanged(NvComputer*)),
this, SLOT(handleComputerStateChanged(NvComputer*)));
connect(thread, &PcMonitorThread::computerStateChanged,
this, &ComputerManager::handleComputerStateChanged);
pollingEntry->setActiveThread(thread);
thread->start();
}
@@ -322,7 +324,7 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
// address may not be reachable (if the user hasn't installed the IPv6 helper yet
// or if this host lacks outbound IPv6 capability). We want to add IPv6 even if
// it's not currently reachable.
addNewHost(address.toString(), true, v6Global);
addNewHost(NvAddress(address, computer->port()), true, NvAddress(v6Global, computer->port()));
added = true;
break;
}
@@ -336,8 +338,7 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
if (address.isInSubnet(QHostAddress("fe80::"), 10) ||
address.isInSubnet(QHostAddress("fec0::"), 10) ||
address.isInSubnet(QHostAddress("fc00::"), 7)) {
addNewHost(address.toString(), true, v6Global);
added = true;
addNewHost(NvAddress(address, computer->port()), true, NvAddress(v6Global, computer->port()));
break;
}
}
@@ -418,7 +419,7 @@ void ComputerManager::deleteHost(NvComputer* computer)
void ComputerManager::renameHost(NvComputer* computer, QString name)
{
{
QWriteLocker(&computer->lock);
QWriteLocker lock(&computer->lock);
computer->name = name;
computer->hasCustomName = true;
@@ -467,7 +468,7 @@ signals:
private:
void run()
{
NvPairingManager pairingManager(m_Computer->activeAddress);
NvPairingManager pairingManager(m_Computer);
try {
NvPairingManager::PairState result = pairingManager.pair(m_Computer->appVersion, m_Pin, m_Computer->serverCert);
@@ -523,7 +524,7 @@ signals:
private:
void run()
{
NvHTTP http(m_Computer->activeAddress, m_Computer->serverCert);
NvHTTP http(m_Computer);
try {
if (m_Computer->currentGameId != 0) {
@@ -589,12 +590,24 @@ void ComputerManager::stopPollingAsync()
}
}
void ComputerManager::addNewHostManually(QString address)
{
QUrl url = QUrl::fromUserInput(address);
if (url.isValid() && !url.host().isEmpty()) {
// If there wasn't a port specified, use the default
addNewHost(NvAddress(url.host(), url.port(DEFAULT_HTTP_PORT)), false);
}
else {
emit computerAddCompleted(false, false);
}
}
class PendingAddTask : public QObject, public QRunnable
{
Q_OBJECT
public:
PendingAddTask(ComputerManager* computerManager, QString address, QHostAddress mdnsIpv6Address, bool mdns)
PendingAddTask(ComputerManager* computerManager, NvAddress address, NvAddress mdnsIpv6Address, bool mdns)
: m_ComputerManager(computerManager),
m_Address(address),
m_MdnsIpv6Address(mdnsIpv6Address),
@@ -658,15 +671,15 @@ private:
void run()
{
NvHTTP http(m_Address, QSslCertificate());
NvHTTP http(m_Address, 0, QSslCertificate());
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user") << m_MdnsIpv6Address;
qInfo() << "Processing new PC at" << m_Address.toString() << "from" << (m_Mdns ? "mDNS" : "user") << "with IPv6 address" << m_MdnsIpv6Address.toString();
// Perform initial serverinfo fetch over HTTP since we don't know which cert to use
QString serverInfo = fetchServerInfo(http);
if (serverInfo.isEmpty() && !m_MdnsIpv6Address.isNull()) {
// Retry using the global IPv6 address if the IPv4 or link-local IPv6 address fails
http.setAddress(m_MdnsIpv6Address.toString());
http.setAddress(m_MdnsIpv6Address);
serverInfo = fetchServerInfo(http);
}
if (serverInfo.isEmpty()) {
@@ -674,7 +687,7 @@ private:
}
// Create initial newComputer using HTTP serverinfo with no pinned cert
NvComputer* newComputer = new NvComputer(http.address(), serverInfo, QSslCertificate());
NvComputer* newComputer = new NvComputer(http, serverInfo);
// Check if we have a record of this host UUID to pull the pinned cert
NvComputer* existingComputer;
@@ -688,13 +701,14 @@ private:
// Fetch serverinfo again over HTTPS with the pinned cert
if (existingComputer != nullptr) {
Q_ASSERT(http.httpsPort() != 0);
serverInfo = fetchServerInfo(http);
if (serverInfo.isEmpty()) {
return;
}
// Update the polled computer with the HTTPS information
NvComputer httpsComputer(http.address(), serverInfo, QSslCertificate());
NvComputer httpsComputer(http, serverInfo);
newComputer->update(httpsComputer);
}
@@ -708,11 +722,11 @@ private:
}
// Get the WAN IP address using STUN if we're on mDNS over IPv4
if (QHostAddress(newComputer->localAddress).protocol() == QAbstractSocket::IPv4Protocol) {
if (QHostAddress(newComputer->localAddress.address()).protocol() == QAbstractSocket::IPv4Protocol) {
quint32 addr;
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
if (err == 0) {
newComputer->remoteAddress = QHostAddress(qFromBigEndian(addr)).toString();
newComputer->setRemoteAddress(QHostAddress(qFromBigEndian(addr)));
}
else {
qWarning() << "STUN failed to get WAN address:" << err;
@@ -720,15 +734,15 @@ private:
}
if (!m_MdnsIpv6Address.isNull()) {
Q_ASSERT(m_MdnsIpv6Address.protocol() == QAbstractSocket::IPv6Protocol);
newComputer->ipv6Address = m_MdnsIpv6Address.toString();
Q_ASSERT(QHostAddress(m_MdnsIpv6Address.address()).protocol() == QAbstractSocket::IPv6Protocol);
newComputer->ipv6Address = m_MdnsIpv6Address;
}
}
else {
newComputer->manualAddress = m_Address;
}
QHostAddress hostAddress(m_Address);
QHostAddress hostAddress(m_Address.address());
bool addressIsSiteLocalV4 =
hostAddress.isInSubnet(QHostAddress("10.0.0.0"), 8) ||
hostAddress.isInSubnet(QHostAddress("172.16.0.0"), 12) ||
@@ -753,7 +767,7 @@ private:
// Tell our client if something changed
if (changed) {
qInfo() << existingComputer->name << "is now at" << existingComputer->activeAddress;
qInfo() << existingComputer->name << "is now at" << existingComputer->activeAddress.toString();
emit computerStateChanged(existingComputer);
}
}
@@ -774,7 +788,7 @@ private:
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
if (err == 0) {
lock.relock();
newComputer->remoteAddress = QHostAddress(qFromBigEndian(addr)).toString();
newComputer->setRemoteAddress(QHostAddress(qFromBigEndian(addr)));
lock.unlock();
}
else {
@@ -794,12 +808,12 @@ private:
}
ComputerManager* m_ComputerManager;
QString m_Address;
QHostAddress m_MdnsIpv6Address;
NvAddress m_Address;
NvAddress m_MdnsIpv6Address;
bool m_Mdns;
};
void ComputerManager::addNewHost(QString address, bool mdns, QHostAddress mdnsIpv6Address)
void ComputerManager::addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address)
{
// Punt to a worker thread to avoid stalling the
// UI while waiting for serverinfo query to complete
+13 -2
View File
@@ -2,6 +2,7 @@
#include "nvcomputer.h"
#include "nvpairingmanager.h"
#include "settings/compatfetcher.h"
#include <qmdnsengine/server.h>
#include <qmdnsengine/cache.h>
@@ -23,6 +24,7 @@ public:
explicit MdnsPendingComputer(QMdnsEngine::Server* server,
const QMdnsEngine::Service& service)
: m_Hostname(service.hostname()),
m_Port(service.port()),
m_Server(server),
m_Resolver(nullptr)
{
@@ -40,6 +42,11 @@ public:
return m_Hostname;
}
uint16_t port()
{
return m_Port;
}
private slots:
void handleResolvedTimeout()
{
@@ -70,10 +77,11 @@ private:
m_Resolver = new QMdnsEngine::Resolver(m_Server, m_Hostname);
connect(m_Resolver, &QMdnsEngine::Resolver::resolved,
this, &MdnsPendingComputer::handleResolvedAddress);
QTimer::singleShot(2000, this, SLOT(handleResolvedTimeout()));
QTimer::singleShot(2000, this, &MdnsPendingComputer::handleResolvedTimeout);
}
QByteArray m_Hostname;
uint16_t m_Port;
QMdnsEngine::Server* m_Server;
QMdnsEngine::Resolver* m_Resolver;
QVector<QHostAddress> m_Addresses;
@@ -168,7 +176,9 @@ public:
Q_INVOKABLE void stopPollingAsync();
Q_INVOKABLE void addNewHost(QString address, bool mdns, QHostAddress mdnsIpv6Address = QHostAddress());
Q_INVOKABLE void addNewHostManually(QString address);
void addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address = NvAddress());
void pairHost(NvComputer* computer, QString pin);
@@ -214,4 +224,5 @@ private:
QMdnsEngine::Browser* m_MdnsBrowser;
QMdnsEngine::Cache m_MdnsCache;
QVector<MdnsPendingComputer*> m_PendingResolution;
CompatFetcher m_CompatFetcher;
};
+3 -3
View File
@@ -20,7 +20,7 @@ void ComputerSeeker::start(int timeout)
// if m_ComputerName is UUID, or the name that doesn't resolve to an IP
// address) and by polling it using mDNS, hopefully one of these methods
// would find the host
m_ComputerManager->addNewHost(m_ComputerName, false);
m_ComputerManager->addNewHostManually(m_ComputerName);
m_ComputerManager->startPolling();
}
@@ -44,8 +44,8 @@ bool ComputerSeeker::matchComputer(NvComputer *computer) const
return true;
}
for (const QString& addr : computer->uniqueAddresses()) {
if (addr.toLower() == value) {
for (const NvAddress& addr : computer->uniqueAddresses()) {
if (addr.address().toLower() == value || addr.toString().toLower() == value) {
return true;
}
}
+4 -1
View File
@@ -143,7 +143,10 @@ QSslKey
IdentityManager::getSslKey()
{
if (m_CachedSslKey.isNull()) {
BIO* bio = BIO_new_mem_buf(m_CachedPrivateKey.data(), -1);
// This seemingly useless const_cast is required for old OpenSSL headers
// where BIO_new_mem_buf's parameter is not declared const like those on
// the Steam Link hardware.
BIO* bio = BIO_new_mem_buf(const_cast<char*>(m_CachedPrivateKey.constData()), -1);
THROW_BAD_ALLOC_IF_NULL(bio);
EVP_PKEY* pk = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr);
+65
View File
@@ -0,0 +1,65 @@
#include "nvaddress.h"
#include <QHostAddress>
NvAddress::NvAddress()
{
setAddress(nullptr);
setPort(0);
}
NvAddress::NvAddress(QString addr, uint16_t port)
{
setAddress(addr);
setPort(port);
}
NvAddress::NvAddress(QHostAddress addr, uint16_t port)
{
setAddress(addr);
setPort(port);
}
uint16_t NvAddress::port() const
{
return m_Port;
}
QString NvAddress::address() const
{
return m_Address;
}
void NvAddress::setPort(uint16_t port)
{
m_Port = port;
}
void NvAddress::setAddress(QString addr)
{
m_Address = addr;
}
void NvAddress::setAddress(QHostAddress addr)
{
m_Address = addr.toString();
}
bool NvAddress::isNull() const
{
return m_Address.isEmpty();
}
QString NvAddress::toString() const
{
if (m_Address.isEmpty()) {
return "<NULL>";
}
if (QHostAddress(m_Address).protocol() == QAbstractSocket::IPv6Protocol) {
return QString("[%1]:%2").arg(m_Address).arg(m_Port);
}
else {
return QString("%1:%2").arg(m_Address).arg(m_Port);
}
}
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <QHostAddress>
#define DEFAULT_HTTP_PORT 47989
#define DEFAULT_HTTPS_PORT 47984
class NvAddress
{
public:
NvAddress();
explicit NvAddress(QString addr, uint16_t port);
explicit NvAddress(QHostAddress addr, uint16_t port);
uint16_t port() const;
void setPort(uint16_t port);
QString address() const;
void setAddress(QString addr);
void setAddress(QHostAddress addr);
bool isNull() const;
QString toString() const;
bool operator==(const NvAddress& other) const
{
return m_Address == other.m_Address &&
m_Port == other.m_Port;
}
bool operator!=(const NvAddress& other) const
{
return !operator==(other);
}
private:
QString m_Address;
uint16_t m_Port;
};
+76 -27
View File
@@ -1,5 +1,6 @@
#include "nvcomputer.h"
#include "nvapp.h"
#include "settings/compatfetcher.h"
#include <QUdpSocket>
#include <QHostInfo>
@@ -10,9 +11,13 @@
#define SER_UUID "uuid"
#define SER_MAC "mac"
#define SER_LOCALADDR "localaddress"
#define SER_LOCALPORT "localport"
#define SER_REMOTEADDR "remoteaddress"
#define SER_REMOTEPORT "remoteport"
#define SER_MANUALADDR "manualaddress"
#define SER_MANUALPORT "manualport"
#define SER_IPV6ADDR "ipv6address"
#define SER_IPV6PORT "ipv6port"
#define SER_APPLIST "apps"
#define SER_SRVCERT "srvcert"
#define SER_CUSTOMNAME "customname"
@@ -23,13 +28,18 @@ NvComputer::NvComputer(QSettings& settings)
this->uuid = settings.value(SER_UUID).toString();
this->hasCustomName = settings.value(SER_CUSTOMNAME).toBool();
this->macAddress = settings.value(SER_MAC).toByteArray();
this->localAddress = settings.value(SER_LOCALADDR).toString();
this->remoteAddress = settings.value(SER_REMOTEADDR).toString();
this->ipv6Address = settings.value(SER_IPV6ADDR).toString();
this->manualAddress = settings.value(SER_MANUALADDR).toString();
this->localAddress = NvAddress(settings.value(SER_LOCALADDR).toString(),
settings.value(SER_LOCALPORT, QVariant(DEFAULT_HTTP_PORT)).toUInt());
this->remoteAddress = NvAddress(settings.value(SER_REMOTEADDR).toString(),
settings.value(SER_REMOTEPORT, QVariant(DEFAULT_HTTP_PORT)).toUInt());
this->ipv6Address = NvAddress(settings.value(SER_IPV6ADDR).toString(),
settings.value(SER_IPV6PORT, QVariant(DEFAULT_HTTP_PORT)).toUInt());
this->manualAddress = NvAddress(settings.value(SER_MANUALADDR).toString(),
settings.value(SER_MANUALPORT, QVariant(DEFAULT_HTTP_PORT)).toUInt());
this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray());
int appCount = settings.beginReadArray(SER_APPLIST);
this->appList.reserve(appCount);
for (int i = 0; i < appCount; i++) {
settings.setArrayIndex(i);
@@ -39,7 +49,6 @@ NvComputer::NvComputer(QSettings& settings)
settings.endArray();
sortAppList();
this->activeAddress = nullptr;
this->currentGameId = 0;
this->pairState = PS_UNKNOWN;
this->state = CS_UNKNOWN;
@@ -49,6 +58,16 @@ NvComputer::NvComputer(QSettings& settings)
this->serverCodecModeSupport = 0;
this->pendingQuit = false;
this->gpuModel = nullptr;
this->isSupportedServerVersion = true;
this->externalPort = this->remoteAddress.port();
this->activeHttpsPort = 0;
}
void NvComputer::setRemoteAddress(QHostAddress address)
{
Q_ASSERT(this->externalPort != 0);
this->remoteAddress = NvAddress(address, this->externalPort);
}
void NvComputer::serialize(QSettings& settings) const
@@ -59,10 +78,14 @@ void NvComputer::serialize(QSettings& settings) const
settings.setValue(SER_CUSTOMNAME, hasCustomName);
settings.setValue(SER_UUID, uuid);
settings.setValue(SER_MAC, macAddress);
settings.setValue(SER_LOCALADDR, localAddress);
settings.setValue(SER_REMOTEADDR, remoteAddress);
settings.setValue(SER_IPV6ADDR, ipv6Address);
settings.setValue(SER_MANUALADDR, manualAddress);
settings.setValue(SER_LOCALADDR, localAddress.address());
settings.setValue(SER_LOCALPORT, localAddress.port());
settings.setValue(SER_REMOTEADDR, remoteAddress.address());
settings.setValue(SER_REMOTEPORT, remoteAddress.port());
settings.setValue(SER_IPV6ADDR, ipv6Address.address());
settings.setValue(SER_IPV6PORT, ipv6Address.port());
settings.setValue(SER_MANUALADDR, manualAddress.address());
settings.setValue(SER_MANUALPORT, manualAddress.port());
settings.setValue(SER_SRVCERT, serverCert.toPem());
// Avoid deleting an existing applist if we couldn't get one
@@ -84,9 +107,9 @@ void NvComputer::sortAppList()
});
}
NvComputer::NvComputer(QString address, QString serverInfo, QSslCertificate serverCert)
NvComputer::NvComputer(NvHTTP& http, QString serverInfo)
{
this->serverCert = serverCert;
this->serverCert = http.serverCert();
this->hasCustomName = false;
this->name = NvHTTP::getXmlString(serverInfo, "hostname");
@@ -98,7 +121,7 @@ NvComputer::NvComputer(QString address, QString serverInfo, QSslCertificate serv
QString newMacString = NvHTTP::getXmlString(serverInfo, "mac");
if (newMacString != "00:00:00:00:00:00") {
QStringList macOctets = newMacString.split(':');
for (QString macOctet : macOctets) {
for (const QString& macOctet : macOctets) {
this->macAddress.append((char) macOctet.toInt(nullptr, 16));
}
}
@@ -127,21 +150,41 @@ NvComputer::NvComputer(QString address, QString serverInfo, QSslCertificate serv
});
// We can get an IPv4 loopback address if we're using the GS IPv6 Forwarder
this->localAddress = NvHTTP::getXmlString(serverInfo, "LocalIP");
if (this->localAddress.startsWith("127.")) {
this->localAddress = QString();
this->localAddress = NvAddress(NvHTTP::getXmlString(serverInfo, "LocalIP"), http.httpPort());
if (this->localAddress.address().startsWith("127.")) {
this->localAddress = NvAddress();
}
QString httpsPort = NvHTTP::getXmlString(serverInfo, "HttpsPort");
if (httpsPort.isEmpty() || (this->activeHttpsPort = httpsPort.toUShort()) == 0) {
this->activeHttpsPort = DEFAULT_HTTPS_PORT;
}
// This is an extension which is not present in GFE. It is present for Sunshine to be able
// 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;
}
QString remoteAddress = NvHTTP::getXmlString(serverInfo, "ExternalIP");
if (!remoteAddress.isEmpty()) {
this->remoteAddress = NvAddress(remoteAddress, this->externalPort);
}
else {
this->remoteAddress = NvAddress();
}
this->remoteAddress = NvHTTP::getXmlString(serverInfo, "ExternalIP");
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
PS_PAIRED : PS_NOT_PAIRED;
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
this->appVersion = NvHTTP::getXmlString(serverInfo, "appversion");
this->gfeVersion = NvHTTP::getXmlString(serverInfo, "GfeVersion");
this->gpuModel = NvHTTP::getXmlString(serverInfo, "gputype");
this->activeAddress = address;
this->activeAddress = http.address();
this->state = NvComputer::CS_ONLINE;
this->pendingQuit = false;
this->isSupportedServerVersion = CompatFetcher::isGfeVersionSupported(this->gfeVersion);
}
bool NvComputer::wake()
@@ -173,7 +216,10 @@ bool NvComputer::wake()
// 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 = uniqueAddresses();
QVector<QString> addressList;
for (const NvAddress& addr : uniqueAddresses()) {
addressList.append(addr.address());
}
addressList.append("255.255.255.255");
// Try to broadcast on all available NICs
@@ -234,14 +280,14 @@ bool NvComputer::wake()
bool NvComputer::isReachableOverVpn()
{
if (activeAddress.isEmpty()) {
if (activeAddress.isNull()) {
return false;
}
QTcpSocket s;
s.setProxy(QNetworkProxy::NoProxy);
s.connectToHost(activeAddress, 47984);
s.connectToHost(activeAddress.address(), activeAddress.port());
if (s.waitForConnected(3000)) {
Q_ASSERT(!s.localAddress().isNull());
@@ -327,9 +373,9 @@ bool NvComputer::updateAppList(QVector<NvApp> newAppList) {
return true;
}
QVector<QString> NvComputer::uniqueAddresses() const
QVector<NvAddress> NvComputer::uniqueAddresses() const
{
QVector<QString> uniqueAddressList;
QVector<NvAddress> uniqueAddressList;
// Start with addresses correctly ordered
uniqueAddressList.append(activeAddress);
@@ -340,7 +386,7 @@ QVector<QString> NvComputer::uniqueAddresses() const
// Prune duplicates (always giving precedence to the first)
for (int i = 0; i < uniqueAddressList.count(); i++) {
if (uniqueAddressList[i].isEmpty()) {
if (uniqueAddressList[i].isNull()) {
uniqueAddressList.remove(i);
i--;
continue;
@@ -396,10 +442,12 @@ bool NvComputer::update(NvComputer& that)
ASSIGN_IF_CHANGED(name);
}
ASSIGN_IF_CHANGED_AND_NONEMPTY(macAddress);
ASSIGN_IF_CHANGED_AND_NONEMPTY(localAddress);
ASSIGN_IF_CHANGED_AND_NONEMPTY(remoteAddress);
ASSIGN_IF_CHANGED_AND_NONEMPTY(ipv6Address);
ASSIGN_IF_CHANGED_AND_NONEMPTY(manualAddress);
ASSIGN_IF_CHANGED_AND_NONNULL(localAddress);
ASSIGN_IF_CHANGED_AND_NONNULL(remoteAddress);
ASSIGN_IF_CHANGED_AND_NONNULL(ipv6Address);
ASSIGN_IF_CHANGED_AND_NONNULL(manualAddress);
ASSIGN_IF_CHANGED(activeHttpsPort);
ASSIGN_IF_CHANGED(externalPort);
ASSIGN_IF_CHANGED(pairState);
ASSIGN_IF_CHANGED(serverCodecModeSupport);
ASSIGN_IF_CHANGED(currentGameId);
@@ -407,6 +455,7 @@ bool NvComputer::update(NvComputer& that)
ASSIGN_IF_CHANGED(state);
ASSIGN_IF_CHANGED(gfeVersion);
ASSIGN_IF_CHANGED(appVersion);
ASSIGN_IF_CHANGED(isSupportedServerVersion);
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
ASSIGN_IF_CHANGED(gpuModel);
ASSIGN_IF_CHANGED_AND_NONNULL(serverCert);
+16 -7
View File
@@ -1,6 +1,7 @@
#pragma once
#include "nvhttp.h"
#include "nvaddress.h"
#include <QThread>
#include <QReadWriteLock>
@@ -21,10 +22,13 @@ private:
bool pendingQuit;
public:
explicit NvComputer(QString address, QString serverInfo, QSslCertificate serverCert);
explicit NvComputer(NvHTTP& http, QString serverInfo);
explicit NvComputer(QSettings& settings);
void
setRemoteAddress(QHostAddress);
bool
update(NvComputer& that);
@@ -34,7 +38,7 @@ public:
bool
isReachableOverVpn();
QVector<QString>
QVector<NvAddress>
uniqueAddresses() const;
void
@@ -57,7 +61,8 @@ public:
// Ephemeral traits
ComputerState state;
PairState pairState;
QString activeAddress;
NvAddress activeAddress;
uint16_t activeHttpsPort;
int currentGameId;
QString gfeVersion;
QString appVersion;
@@ -65,12 +70,13 @@ public:
int maxLumaPixelsHEVC;
int serverCodecModeSupport;
QString gpuModel;
bool isSupportedServerVersion;
// Persisted traits
QString localAddress;
QString remoteAddress;
QString ipv6Address;
QString manualAddress;
NvAddress localAddress;
NvAddress remoteAddress;
NvAddress ipv6Address;
NvAddress manualAddress;
QByteArray macAddress;
QString name;
bool hasCustomName;
@@ -80,4 +86,7 @@ public:
// Synchronization
mutable QReadWriteLock lock;
private:
uint16_t externalPort;
};
+81 -19
View File
@@ -1,4 +1,4 @@
#include "nvhttp.h"
#include "nvcomputer.h"
#include <Limelight.h>
#include <QDebug>
@@ -18,15 +18,14 @@
#define RESUME_TIMEOUT_MS 30000
#define QUIT_TIMEOUT_MS 30000
NvHTTP::NvHTTP(QString address, QSslCertificate serverCert) :
NvHTTP::NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert) :
m_ServerCert(serverCert)
{
m_BaseUrlHttp.setScheme("http");
m_BaseUrlHttps.setScheme("https");
m_BaseUrlHttp.setPort(47989);
m_BaseUrlHttps.setPort(47984);
setAddress(address);
setHttpsPort(httpsPort);
// Never use a proxy server
QNetworkProxy noProxy(QNetworkProxy::NoProxy);
@@ -35,26 +34,54 @@ NvHTTP::NvHTTP(QString address, QSslCertificate serverCert) :
connect(&m_Nam, &QNetworkAccessManager::sslErrors, this, &NvHTTP::handleSslErrors);
}
NvHTTP::NvHTTP(NvComputer* computer) :
NvHTTP(computer->activeAddress, computer->activeHttpsPort, computer->serverCert)
{
}
void NvHTTP::setServerCert(QSslCertificate serverCert)
{
m_ServerCert = serverCert;
}
void NvHTTP::setAddress(QString address)
void NvHTTP::setAddress(NvAddress address)
{
Q_ASSERT(!address.isEmpty());
Q_ASSERT(!address.isNull());
m_Address = address;
m_BaseUrlHttp.setHost(address);
m_BaseUrlHttps.setHost(address);
m_BaseUrlHttp.setHost(address.address());
m_BaseUrlHttps.setHost(address.address());
m_BaseUrlHttp.setPort(address.port());
}
QString NvHTTP::address()
void NvHTTP::setHttpsPort(uint16_t port)
{
m_BaseUrlHttps.setPort(port);
}
NvAddress NvHTTP::address()
{
return m_Address;
}
QSslCertificate NvHTTP::serverCert()
{
return m_ServerCert;
}
uint16_t NvHTTP::httpPort()
{
return m_BaseUrlHttp.port();
}
uint16_t NvHTTP::httpsPort()
{
return m_BaseUrlHttps.port();
}
QVector<int>
NvHTTP::parseQuad(QString quad)
{
@@ -67,7 +94,8 @@ NvHTTP::parseQuad(QString quad)
}
QStringList parts = quad.split(".");
for (int i = 0; i < 4; i++)
ret.reserve(parts.length());
for (int i = 0; i < parts.length(); i++)
{
ret.append(parts.at(i).toInt());
}
@@ -97,8 +125,8 @@ NvHTTP::getServerInfo(NvLogLevel logLevel, bool fastFail)
{
QString serverInfo;
// Check if we have a pinned cert for this host yet
if (!m_ServerCert.isNull())
// Check if we have a pinned cert and HTTPS port for this host yet
if (!m_ServerCert.isNull() && httpsPort() != 0)
{
try
{
@@ -133,13 +161,26 @@ NvHTTP::getServerInfo(NvLogLevel logLevel, bool fastFail)
}
else
{
// Only use HTTP prior to pairing
// Only use HTTP prior to pairing or fetching HTTPS port
serverInfo = openConnectionToString(m_BaseUrlHttp,
"serverinfo",
nullptr,
fastFail ? FAST_FAIL_TIMEOUT_MS : REQUEST_TIMEOUT_MS,
logLevel);
verifyResponseStatus(serverInfo);
// Populate the HTTPS port
uint16_t httpsPort = getXmlString(serverInfo, "HttpsPort").toUShort();
if (httpsPort == 0) {
httpsPort = DEFAULT_HTTPS_PORT;
}
setHttpsPort(httpsPort);
// If we just needed to determine the HTTPS port, we'll try again over
// HTTPS now that we have the port number
if (!m_ServerCert.isNull()) {
return getServerInfo(logLevel, fastFail);
}
}
return serverInfo;
@@ -150,7 +191,8 @@ NvHTTP::launchApp(int appId,
PSTREAM_CONFIGURATION streamConfig,
bool sops,
bool localAudio,
int gamepadMask)
int gamepadMask,
QString& rtspSessionUrl)
{
int riKeyId;
@@ -180,12 +222,16 @@ NvHTTP::launchApp(int appId,
"&gcmap="+QString::number(gamepadMask),
LAUNCH_TIMEOUT_MS);
qInfo() << "Launch response:" << response;
// Throws if the request failed
verifyResponseStatus(response);
rtspSessionUrl = getXmlString(response, "sessionUrl0");
}
void
NvHTTP::resumeApp(PSTREAM_CONFIGURATION streamConfig)
NvHTTP::resumeApp(PSTREAM_CONFIGURATION streamConfig, QString& rtspSessionUrl)
{
int riKeyId;
@@ -200,8 +246,12 @@ NvHTTP::resumeApp(PSTREAM_CONFIGURATION streamConfig)
"&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
@@ -213,6 +263,8 @@ NvHTTP::quitApp()
nullptr,
QUIT_TIMEOUT_MS);
qInfo() << "Quit response:" << response;
// Throws if the request failed
verifyResponseStatus(response);
@@ -329,6 +381,8 @@ NvHTTP::verifyResponseStatus(QString xml)
}
}
}
throw GfeHttpResponseException(-1, "Malformed GFE XML (missing root element)");
}
QImage
@@ -391,7 +445,7 @@ void NvHTTP::handleSslErrors(QNetworkReply* reply, const QList<QSslError>& error
return;
}
for (auto error : errors) {
for (const QSslError& error : errors) {
if (m_ServerCert != error.certificate()) {
ignoreErrors = false;
break;
@@ -434,6 +488,9 @@ NvHTTP::openConnection(QUrl baseUrl,
int timeoutMs,
NvLogLevel logLevel)
{
// Port must be set
Q_ASSERT(baseUrl.port(0) != 0);
// Build a URL for the request
QUrl url(baseUrl);
url.setPath("/" + command);
@@ -450,6 +507,11 @@ NvHTTP::openConnection(QUrl baseUrl,
// Add our client certificate
request.setSslConfiguration(IdentityManager::get()->getSslConfig());
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
// Disable HTTP/2 (GFE 3.22 doesn't like it) and Qt 6 enables it by default
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
#endif
#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
@@ -462,10 +524,10 @@ NvHTTP::openConnection(QUrl baseUrl,
// Run the request with a timeout if requested
QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), &loop, SLOT(quit()));
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, &loop, &QEventLoop::quit);
if (timeoutMs) {
QTimer::singleShot(timeoutMs, &loop, SLOT(quit()));
QTimer::singleShot(timeoutMs, &loop, &QEventLoop::quit);
}
if (logLevel >= NvLogLevel::NVLL_VERBOSE) {
qInfo() << "Executing request:" << url.toString();
+21 -6
View File
@@ -2,6 +2,7 @@
#include "identitymanager.h"
#include "nvapp.h"
#include "nvaddress.h"
#include <Limelight.h>
@@ -9,6 +10,8 @@
#include <QNetworkAccessManager>
#include <QNetworkReply>
class NvComputer;
class NvDisplayMode
{
public:
@@ -23,6 +26,7 @@ public:
int height;
int refreshRate;
};
Q_DECLARE_TYPEINFO(NvDisplayMode, Q_PRIMITIVE_TYPE);
class GfeHttpResponseException : public std::exception
{
@@ -105,7 +109,9 @@ public:
NVLL_VERBOSE
};
explicit NvHTTP(QString address, QSslCertificate serverCert);
explicit NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert);
explicit NvHTTP(NvComputer* computer);
static
int
@@ -137,9 +143,16 @@ public:
void setServerCert(QSslCertificate serverCert);
void setAddress(QString address);
void setAddress(NvAddress address);
void setHttpsPort(uint16_t port);
QString address();
NvAddress address();
QSslCertificate serverCert();
uint16_t httpPort();
uint16_t httpsPort();
static
QVector<int>
@@ -149,14 +162,16 @@ public:
quitApp();
void
resumeApp(PSTREAM_CONFIGURATION streamConfig);
resumeApp(PSTREAM_CONFIGURATION streamConfig,
QString& rtspSessionUrl);
void
launchApp(int appId,
PSTREAM_CONFIGURATION streamConfig,
bool sops,
bool localAudio,
int gamepadMask);
int gamepadMask,
QString& rtspSessionUrl);
QVector<NvApp>
getAppList();
@@ -181,7 +196,7 @@ private:
int timeoutMs,
NvLogLevel logLevel);
QString m_Address;
NvAddress m_Address;
QNetworkAccessManager m_Nam;
QSslCertificate m_ServerCert;
};
+4 -8
View File
@@ -11,8 +11,8 @@
#define REQUEST_TIMEOUT_MS 5000
NvPairingManager::NvPairingManager(QString address) :
m_Http(address, QSslCertificate())
NvPairingManager::NvPairingManager(NvComputer* computer) :
m_Http(computer)
{
QByteArray cert = IdentityManager::get()->getCertificate();
BIO *bio = BIO_new_mem_buf(cert.data(), -1);
@@ -167,11 +167,7 @@ NvPairingManager::signMessage(const QByteArray& message)
EVP_MD_CTX *ctx = EVP_MD_CTX_create();
THROW_BAD_ALLOC_IF_NULL(ctx);
const EVP_MD *md = EVP_get_digestbyname("SHA256");
THROW_BAD_ALLOC_IF_NULL(md);
EVP_DigestInit_ex(ctx, md, NULL);
EVP_DigestSignInit(ctx, NULL, md, NULL, m_PrivateKey);
EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, m_PrivateKey);
EVP_DigestSignUpdate(ctx, reinterpret_cast<unsigned char*>(const_cast<char*>(message.data())), message.length());
size_t signatureLength = 0;
@@ -215,7 +211,7 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
QByteArray salt = generateRandomBytes(16);
QByteArray saltedPin = saltPin(salt, pin);
QByteArray aesKey = QCryptographicHash::hash(saltedPin, hashAlgo).data();
QByteArray aesKey = QCryptographicHash::hash(saltedPin, hashAlgo).constData();
aesKey.truncate(16);
QString getCert = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
+1 -1
View File
@@ -17,7 +17,7 @@ public:
ALREADY_IN_PROGRESS
};
explicit NvPairingManager(QString address);
explicit NvPairingManager(NvComputer* computer);
~NvPairingManager();
+72 -7
View File
@@ -14,7 +14,7 @@
SystemProperties::SystemProperties()
{
versionString = QString(VERSION_STR);
hasWindowManager = WMUtils::isRunningWindowManager();
hasDesktopEnvironment = WMUtils::isRunningDesktopEnvironment();
isRunningWayland = WMUtils::isRunningWayland();
isRunningXWayland = isRunningWayland && QGuiApplication::platformName() == "xcb";
QString nativeArch = QSysInfo::currentCpuArchitecture();
@@ -55,12 +55,8 @@ SystemProperties::SystemProperties()
friendlyNativeArchName = nativeArch.toUpper();
}
#ifndef STEAM_LINK
// Assume we can probably launch a browser if we're in a GUI environment
hasBrowser = hasWindowManager;
#else
hasBrowser = false;
#endif
hasBrowser = hasDesktopEnvironment;
#ifdef HAVE_DISCORD
hasDiscordIntegration = true;
@@ -68,6 +64,16 @@ SystemProperties::SystemProperties()
hasDiscordIntegration = false;
#endif
// TODO: Do something smarter than this. We should be able to query
// this from the decoder (or just try VIDEO_FORMAT_H265_MAIN10 and
// fail if we don't get a hardware accelerated decoder).
#ifdef Q_OS_DARWIN
// HDR is supported by the VideoToolbox renderer
supportsHdr = true;
#else
supportsHdr = false;
#endif
unmappedGamepads = SdlInputHandler::getUnmappedGamepads();
// Populate data that requires talking to SDL. We do it all in one shot
@@ -91,7 +97,36 @@ QRect SystemProperties::getNativeResolution(int displayIndex)
return monitorNativeResolutions.value(displayIndex);
}
class QuerySdlVideoThread : public QThread
{
public:
QuerySdlVideoThread(SystemProperties* me) :
QThread(nullptr),
m_Me(me) {}
void run() override
{
m_Me->querySdlVideoInfoInternal();
}
SystemProperties* m_Me;
};
void SystemProperties::querySdlVideoInfo()
{
if (WMUtils::isRunningX11() || WMUtils::isRunningWayland()) {
// Use a separate thread to temporarily initialize SDL
// video to avoid stomping on Qt's X11 and OGL state.
QuerySdlVideoThread thread(this);
thread.start();
thread.wait();
}
else {
querySdlVideoInfoInternal();
}
}
void SystemProperties::querySdlVideoInfoInternal()
{
hasHardwareAcceleration = false;
@@ -103,7 +138,8 @@ void SystemProperties::querySdlVideoInfo()
}
// Update display related attributes (max FPS, native resolution, etc).
refreshDisplays();
// We call the internal variant because we're already in a safe thread context.
refreshDisplaysInternal();
SDL_Window* testWindow = SDL_CreateWindow("", 0, 0, 1280, 720,
SDL_WINDOW_HIDDEN | StreamUtils::getPlatformWindowFlags());
@@ -129,7 +165,36 @@ void SystemProperties::querySdlVideoInfo()
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
class RefreshDisplaysThread : public QThread
{
public:
RefreshDisplaysThread(SystemProperties* me) :
QThread(nullptr),
m_Me(me) {}
void run() override
{
m_Me->refreshDisplaysInternal();
}
SystemProperties* m_Me;
};
void SystemProperties::refreshDisplays()
{
if (WMUtils::isRunningX11() || WMUtils::isRunningWayland()) {
// Use a separate thread to temporarily initialize SDL
// video to avoid stomping on Qt's X11 and OGL state.
RefreshDisplaysThread thread(this);
thread.start();
thread.wait();
}
else {
refreshDisplaysInternal();
}
}
void SystemProperties::refreshDisplaysInternal()
{
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+9 -2
View File
@@ -7,6 +7,9 @@ class SystemProperties : public QObject
{
Q_OBJECT
friend class QuerySdlVideoThread;
friend class RefreshDisplaysThread;
public:
SystemProperties();
@@ -16,13 +19,14 @@ public:
Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT)
Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT)
Q_PROPERTY(QString friendlyNativeArchName MEMBER friendlyNativeArchName CONSTANT)
Q_PROPERTY(bool hasWindowManager MEMBER hasWindowManager CONSTANT)
Q_PROPERTY(bool hasDesktopEnvironment MEMBER hasDesktopEnvironment CONSTANT)
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_INVOKABLE void refreshDisplays();
Q_INVOKABLE QRect getDesktopResolution(int displayIndex);
@@ -33,6 +37,8 @@ signals:
private:
void querySdlVideoInfo();
void querySdlVideoInfoInternal();
void refreshDisplaysInternal();
bool hasHardwareAcceleration;
bool rendererAlwaysFullScreen;
@@ -40,7 +46,7 @@ private:
bool isRunningXWayland;
bool isWow64;
QString friendlyNativeArchName;
bool hasWindowManager;
bool hasDesktopEnvironment;
bool hasBrowser;
bool hasDiscordIntegration;
QString unmappedGamepads;
@@ -49,5 +55,6 @@ private:
QList<QRect> monitorDesktopResolutions;
QList<QRect> monitorNativeResolutions;
QString versionString;
bool supportsHdr;
};
+14 -11
View File
@@ -52,7 +52,7 @@ public:
void handleUnknownOptions()
{
if (unknownOptionNames().length()) {
if (!unknownOptionNames().isEmpty()) {
showError(QString("Unknown options: %1").arg(unknownOptionNames().join(", ")));
}
}
@@ -264,6 +264,11 @@ StreamCommandLineParser::StreamCommandLineParser()
{"software", StreamingPreferences::VDS_FORCE_SOFTWARE},
{"hardware", StreamingPreferences::VDS_FORCE_HARDWARE},
};
m_CaptureSysKeysModeMap = {
{"never", StreamingPreferences::CSK_OFF},
{"fullscreen", StreamingPreferences::CSK_FULLSCREEN},
{"always", StreamingPreferences::CSK_ALWAYS},
};
}
StreamCommandLineParser::~StreamCommandLineParser()
@@ -307,7 +312,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
parser.addToggleOption("background-gamepad", "background gamepad input");
parser.addToggleOption("reverse-scroll-direction", "inverted scroll direction");
parser.addToggleOption("swap-gamepad-buttons", "swap A/B and X/Y gamepad buttons (Nintendo-style)");
parser.addToggleOption("capture-system-keys", "capture system key combos in fullscreen mode");
parser.addChoiceOption("capture-system-keys", "capture system key combos", m_CaptureSysKeysModeMap.keys());
parser.addChoiceOption("video-codec", "video codec", m_VideoCodecMap.keys());
parser.addChoiceOption("video-decoder", "video decoder", m_VideoDecoderMap.keys());
@@ -320,25 +325,21 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
// Resolve display's width and height
QRegularExpression resolutionRexExp("^(720|1080|1440|4K|resolution)$");
QStringList resoOptions = parser.optionNames().filter(resolutionRexExp);
bool displaySet = resoOptions.length();
bool displaySet = !resoOptions.isEmpty();
if (displaySet) {
QString name = resoOptions.last();
if (name == "720") {
preferences->width = 1280;
preferences->height = 720;
displaySet = true;
} else if (name == "1080") {
preferences->width = 1920;
preferences->height = 1080;
displaySet = true;
} else if (name == "1440") {
preferences->width = 2560;
preferences->height = 1440;
displaySet = true;
} else if (name == "4K") {
preferences->width = 3840;
preferences->height = 2160;
displaySet = true;
} else if (name == "resolution") {
auto resolution = parser.getResolutionOptionValue(name);
preferences->width = resolution.first;
@@ -349,8 +350,8 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
// Resolve --fps option
if (parser.isSet("fps")) {
preferences->fps = parser.getIntOption("fps");
if (!inRange(preferences->fps, 30, 120)) {
parser.showError("FPS must be in range: 30 - 120");
if (!inRange(preferences->fps, 30, 240)) {
parser.showError("FPS must be in range: 30 - 240");
}
}
@@ -422,8 +423,10 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
// Resolve --swap-gamepad-buttons and --no-swap-gamepad-buttons options
preferences->swapFaceButtons = parser.getToggleOptionValue("swap-gamepad-buttons", preferences->swapFaceButtons);
// Resolve --capture-system-keys and --no-capture-system-keys options
preferences->captureSysKeys = parser.getToggleOptionValue("capture-system-keys", preferences->captureSysKeys);
// Resolve --capture-system-keys option
if (parser.isSet("capture-system-keys")) {
preferences->captureSysKeysMode = mapValue(m_CaptureSysKeysModeMap, parser.getChoiceOptionValue("capture-system-keys"));
}
// Resolve --video-codec option
if (parser.isSet("video-codec")) {
+1
View File
@@ -53,4 +53,5 @@ private:
QMap<QString, StreamingPreferences::AudioConfig> m_AudioConfigMap;
QMap<QString, StreamingPreferences::VideoCodecConfig> m_VideoCodecMap;
QMap<QString, StreamingPreferences::VideoDecoderSelection> m_VideoDecoderMap;
QMap<QString, StreamingPreferences::CaptureSysKeysMode> m_CaptureSysKeysModeMap;
};
@@ -15,7 +15,7 @@
<li>Hardware accelerated video decoding with VAAPI, VDPAU, and NVDEC support</li>
<li>7.1 surround sound audio support</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 in fullscreen mode on X11</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>
@@ -36,6 +36,90 @@
</screenshots>
<releases>
<release version="3.2.0" date="2022-01-18">
<description>
<p>New features:</p>
<ul>
<li>Reduced CPU usage while streaming</li>
<li>Non-ASCII characters are now supported when pasting clipboard data to the host</li>
<li>Added support for zero-copy NVDEC rendering for Nvidia Wayland systems</li>
<li>Significantly improved performance with asynchronous decoders like V4L2M2M on Linux</li>
<li>Added support for color compression planes on newer Intel systems when using VAAPI decoding</li>
<li>Added support for copy-back rendering in non-default pixel formats (like the Raspberry Pi SAND format)</li>
</ul>
<p>Bugfixes:</p>
<ul>
<li>Fixed unexpected GUI input after using the gamepad quit combo</li>
<li>Updated included gamepad mappings</li>
<li>Updated community-contributed translations from Weblate</li>
</ul>
</description>
</release>
<release version="3.1.4" date="2021-06-25">
<description>
<p>Changes:</p>
<ul>
<li>Added network latency statistics to the performance overlay</li>
<li>Improved audio quality on unreliable connections (requires GeForce Experience 3.19 or later)</li>
<li>Reduced video frame drops on unreliable connections (requires GeForce Experience 3.22 or later)</li>
<li>Updated community-contributed translations from Weblate</li>
</ul>
</description>
</release>
<release version="3.1.3" date="2021-05-07">
<description>
<p>Changes:</p>
<ul>
<li>Fixed an issue that could cause excessive audio latency on some connections</li>
<li>Updated community-contributed translations from Weblate</li>
</ul>
</description>
</release>
<release version="3.1.2" date="2021-05-03">
<description>
<p>Changes:</p>
<ul>
<li>Improved connection reliability and performance with GeForce Experience 3.22</li>
<li>Added support for audio encryption with GeForce Experience 3.22</li>
<li>Added compatibility update checks for future versions of GeForce Experience</li>
<li>Fixed keyboard capture on X11</li>
<li>Updated community-contributed translations from Weblate</li>
</ul>
</description>
</release>
<release version="3.1.1" date="2021-04-09">
<description>
<p>Changes:</p>
<ul>
<li>Added support for streaming from GeForce Experience 3.22</li>
<li>Added support for AMF-encoded HEVC streams from servers like Sunshine and OpenStream</li>
<li>Added new community-contributed translations from Weblate</li>
<li>Fixed KMSDRM rendering with buffer modifiers on embedded devices</li>
</ul>
</description>
</release>
<release version="3.1.0" date="2021-03-14">
<description>
<p>New features:</p>
<ul>
<li>System keyboard shortcuts (Alt+Tab, Super key, Ctrl+Shift+Esc, etc.) can now be (optionally) captured in windowed mode</li>
<li>System keyboard shortcuts can now be captured under Wayland</li>
<li>Added support for typing the client's clipboard text on the host by pressing Ctrl+Alt+Shift+V</li>
<li>Added Ctrl+Alt+Shift+D key combo to minimize the streaming window</li>
<li>Added Ctrl+Alt+Shift+C key combo to show the local cursor while streaming in remote desktop mouse mode</li>
<li>Added Select+L1+R1+X gamepad button combo to toggle performance overlay</li>
<li>Added translations for Simplified Chinese and German</li>
<li>Added a language selector on the settings page</li>
</ul>
<p>Bugfixes:</p>
<ul>
<li>Improved performance of loading the app grid</li>
<li>Fixed video scaling in High DPI mode on Wayland</li>
<li>Fixed window icons on Wayland</li>
<li>Fixed number pad enter key functionality in the UI</li>
</ul>
</description>
</release>
<release version="3.0.0" date="2021-01-17">
<description>
<p>New features:</p>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 13 KiB

+1 -11
View File
@@ -5,19 +5,9 @@
# pairing data will be lost each reboot.
HOME=/usr/local/moonlight
# For a seamless transition across the Qt 5.9 -> Qt 5.14 boundary,
# we bundle binaries built with both Qt 5.9 and Qt 5.14 and pick
# the correct one at runtime.
if [ -d "/usr/local/Qt-5.14.1" ]; then
BIN="moonlight"
else
BIN="moonlight59"
fi
# Renice PE_Single_CPU which seems to host A/V stuff
renice -10 -p $(pidof PE_Single_CPU)
# Renice Moonlight itself to avoid preemption by background tasks
# Write output to a logfile in /tmp
echo Launching $BIN
exec nice -n -10 ./bin/$BIN > /tmp/moonlight.log
exec nice -n -10 ./bin/moonlight > /tmp/moonlight.log
+136 -100
View File
@@ -73,6 +73,9 @@ CenteredGridView {
width: 220; height: 287;
grid: appGrid
property alias appContextMenu: appContextMenuLoader.item
property alias appNameText: appNameTextLoader.item
// Dim the app if it's hidden
opacity: model.hidden ? 0.4 : 1.0
@@ -85,9 +88,14 @@ CenteredGridView {
source: model.boxart
onSourceSizeChanged: {
if ((sourceSize.width == 130 && sourceSize.height == 180) || // GFE 2.0 placeholder image
(sourceSize.width == 628 && sourceSize.height == 888) || // GFE 3.0 placeholder image
(sourceSize.width == 200 && sourceSize.height == 266)) // Our no_app_image.png
// 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
// 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 &&
((sourceSize.width == 130 && sourceSize.height == 180) || // GFE 2.0 placeholder image
(sourceSize.width == 628 && sourceSize.height == 888) || // GFE 3.0 placeholder image
(sourceSize.width == 200 && sourceSize.height == 266))) // Our no_app_image.png
{
isPlaceholder = true
}
@@ -104,81 +112,94 @@ CenteredGridView {
ToolTip.text: model.name
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: (parent.hovered || parent.highlighted) && (!appNameText.visible || appNameText.truncated)
ToolTip.visible: (parent.hovered || parent.highlighted) && (!appNameText || appNameText.truncated)
}
ToolButton {
id: resumeButton
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? -47 : 0
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : -60
anchors.centerIn: appIcon
visible: model.running
implicitWidth: 125
implicitHeight: 125
Loader {
active: model.running
asynchronous: true
anchors.fill: appIcon
Image {
source: "qrc:/res/baseline-play_circle_filled_white-48px.svg"
anchors.centerIn: parent
sourceSize {
width: 75
height: 75
sourceComponent: Item {
ToolButton {
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? -47 : 0
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : -60
anchors.centerIn: parent
implicitWidth: 125
implicitHeight: 125
Image {
source: "qrc:/res/baseline-play_circle_filled_white-48px.svg"
anchors.centerIn: parent
sourceSize {
width: 75
height: 75
}
}
onClicked: {
launchOrResumeSelectedApp(true)
}
ToolTip.text: qsTr("Resume Game")
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
ToolButton {
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? 47 : 0
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : 60
anchors.centerIn: parent
implicitWidth: 125
implicitHeight: 125
Image {
source: "qrc:/res/baseline-cancel-24px.svg"
anchors.centerIn: parent
sourceSize {
width: 75
height: 75
}
}
onClicked: {
doQuitGame()
}
ToolTip.text: qsTr("Quit Game")
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
}
onClicked: {
launchOrResumeSelectedApp(true)
}
ToolTip.text: qsTr("Resume Game")
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
ToolButton {
id: quitButton
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? 47 : 0
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : 60
anchors.centerIn: appIcon
visible: model.running
implicitWidth: 125
implicitHeight: 125
Loader {
id: appNameTextLoader
active: appIcon.isPlaceholder
Image {
source: "qrc:/res/baseline-cancel-24px.svg"
anchors.centerIn: parent
sourceSize {
width: 75
height: 75
}
}
// This loader is not asynchronous to avoid noticeable differences
// in the time in which the text loads for each game.
onClicked: {
doQuitGame()
}
ToolTip.text: qsTr("Quit Game")
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
Label {
id: appNameText
visible: appIcon.isPlaceholder
text: model.name
width: appIcon.width
height: model.running ? 175 : appIcon.height
leftPadding: 20
rightPadding: 20
anchors.left: appIcon.left
anchors.right: appIcon.right
anchors.bottom: appIcon.bottom
font.pointSize: 22
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
elide: Text.ElideRight
sourceComponent: Label {
id: appNameText
text: model.name
font.pointSize: 22
leftPadding: 20
rightPadding: 20
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
elide: Text.ElideRight
}
}
function launchOrResumeSelectedApp(quitExistingApp)
@@ -245,6 +266,17 @@ CenteredGridView {
}
}
Keys.onEnterPressed: {
// Open the app context menu if activated via the gamepad or keyboard
// for running games. If the game isn't running, the above onClicked
// method will handle the launch.
if (model.running) {
// This will be keyboard/gamepad driven so use
// open() instead of popup()
appContextMenu.open()
}
}
Keys.onMenuPressed: {
// This will be keyboard/gamepad driven so use open() instead of popup()
appContextMenu.open()
@@ -256,44 +288,48 @@ CenteredGridView {
quitAppDialog.open()
}
NavigableMenu {
id: appContextMenu
NavigableMenuItem {
parentMenu: appContextMenu
text: model.running ? qsTr("Resume Game") : qsTr("Launch Game")
onTriggered: launchOrResumeSelectedApp(true)
}
NavigableMenuItem {
parentMenu: appContextMenu
text: qsTr("Quit Game")
onTriggered: doQuitGame()
visible: model.running
}
NavigableMenuItem {
parentMenu: appContextMenu
checkable: true
checked: model.directLaunch
text: qsTr("Direct Launch")
onTriggered: appModel.setAppDirectLaunch(model.index, !model.directLaunch)
enabled: !model.hidden
Loader {
id: appContextMenuLoader
asynchronous: true
sourceComponent: NavigableMenu {
id: appContextMenu
NavigableMenuItem {
parentMenu: appContextMenu
text: model.running ? qsTr("Resume Game") : qsTr("Launch Game")
onTriggered: launchOrResumeSelectedApp(true)
}
NavigableMenuItem {
parentMenu: appContextMenu
text: qsTr("Quit Game")
onTriggered: doQuitGame()
visible: model.running
}
NavigableMenuItem {
parentMenu: appContextMenu
checkable: true
checked: model.directLaunch
text: qsTr("Direct Launch")
onTriggered: appModel.setAppDirectLaunch(model.index, !model.directLaunch)
enabled: !model.hidden
ToolTip.text: qsTr("Launch this app immediately when the host is selected, bypassing the app selection grid.")
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
NavigableMenuItem {
parentMenu: appContextMenu
checkable: true
checked: model.hidden
text: qsTr("Hide Game")
onTriggered: appModel.setAppHidden(model.index, !model.hidden)
enabled: model.hidden || (!model.running && !model.directLaunch)
ToolTip.text: qsTr("Launch this app immediately when the host is selected, bypassing the app selection grid.")
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
NavigableMenuItem {
parentMenu: appContextMenu
checkable: true
checked: model.hidden
text: qsTr("Hide Game")
onTriggered: appModel.setAppHidden(model.index, !model.hidden)
enabled: model.hidden || (!model.running && !model.directLaunch)
ToolTip.text: qsTr("Hide this game from the app grid. To access hidden games, right-click on the host and choose %1.").arg(qsTr("View All Apps"))
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: qsTr("Hide this game from the app grid. To access hidden games, right-click on the host and choose %1.").arg(qsTr("View All Apps"))
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
}
}
}
}
+3
View File
@@ -21,4 +21,7 @@ ItemDelegate {
Keys.onReturnPressed: {
clicked()
}
Keys.onEnterPressed: {
clicked()
}
}
+4
View File
@@ -22,6 +22,10 @@ MenuItem {
triggered()
}
Keys.onEnterPressed: {
triggered()
}
Keys.onEscapePressed: {
parentMenu.close()
}
+4
View File
@@ -56,6 +56,10 @@ NavigableDialog {
accept()
}
Keys.onEnterPressed: {
accept()
}
Keys.onEscapePressed: {
reject()
}
+6 -2
View File
@@ -1,5 +1,6 @@
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
ToolButton {
property string iconSource
@@ -20,13 +21,16 @@ ToolButton {
// This determines the size of the Material highlight. We increase it
// from the default because we use larger than normal icons for TV readability.
background.width: (parent.height - parent.anchors.bottomMargin - parent.anchors.topMargin) * 0.60
background.height: (parent.height - parent.anchors.bottomMargin - parent.anchors.topMargin) * 0.60
Layout.preferredHeight: parent.height
Keys.onReturnPressed: {
clicked()
}
Keys.onEnterPressed: {
clicked()
}
Keys.onRightPressed: {
nextItemInFocusChain(true).forceActiveFocus(Qt.TabFocus)
}
+60 -45
View File
@@ -113,6 +113,8 @@ CenteredGridView {
width: 300; height: 320;
grid: pcGrid
property alias pcContextMenu : pcContextMenuLoader.item
Image {
id: pcIcon
anchors.horizontalCenter: parent.horizontalCenter
@@ -160,61 +162,70 @@ CenteredGridView {
elide: Text.ElideRight
}
NavigableMenu {
id: pcContextMenu
MenuItem {
text: qsTr("PC Status: %1").arg(model.online ? qsTr("Online") : qsTr("Offline"))
font.bold: true
enabled: false
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("View All Apps")
onTriggered: {
var component = Qt.createComponent("AppView.qml")
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name, "showHiddenGames": true})
stackView.push(appView)
Loader {
id: pcContextMenuLoader
asynchronous: true
sourceComponent: NavigableMenu {
id: pcContextMenu
MenuItem {
text: qsTr("PC Status: %1").arg(model.online ? qsTr("Online") : qsTr("Offline"))
font.bold: true
enabled: false
}
visible: model.online && model.paired
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Wake PC")
onTriggered: computerModel.wakeComputer(index)
visible: !model.online && model.wakeable
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Test Network")
onTriggered: {
computerModel.testConnectionForComputer(index)
testConnectionDialog.open()
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("View All Apps")
onTriggered: {
var component = Qt.createComponent("AppView.qml")
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name, "showHiddenGames": true})
stackView.push(appView)
}
visible: model.online && model.paired
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Wake PC")
onTriggered: computerModel.wakeComputer(index)
visible: !model.online && model.wakeable
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Test Network")
onTriggered: {
computerModel.testConnectionForComputer(index)
testConnectionDialog.open()
}
}
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Rename PC")
onTriggered: {
renamePcDialog.pcIndex = index
renamePcDialog.originalName = model.name
renamePcDialog.open()
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Rename PC")
onTriggered: {
renamePcDialog.pcIndex = index
renamePcDialog.originalName = model.name
renamePcDialog.open()
}
}
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Delete PC")
onTriggered: {
deletePcDialog.pcIndex = index
// get confirmation first, actual closing is called from the dialog
deletePcDialog.open()
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Delete PC")
onTriggered: {
deletePcDialog.pcIndex = index
// get confirmation first, actual closing is called from the dialog
deletePcDialog.open()
}
}
}
}
onClicked: {
if (model.online) {
if (model.paired) {
if (!model.serverSupported) {
errorDialog.text = qsTr("The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.").arg(model.name)
errorDialog.helpText = ""
errorDialog.open()
}
else if (model.paired) {
// go to game view
var component = Qt.createComponent("AppView.qml")
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name})
@@ -379,6 +390,10 @@ CenteredGridView {
Keys.onReturnPressed: {
renamePcDialog.accept()
}
Keys.onEnterPressed: {
renamePcDialog.accept()
}
}
}
}
+236 -50
View File
@@ -358,6 +358,10 @@ Flickable {
Keys.onReturnPressed: {
customResolutionDialog.accept()
}
Keys.onEnterPressed: {
customResolutionDialog.accept()
}
}
Label {
@@ -382,6 +386,10 @@ Flickable {
Keys.onReturnPressed: {
customResolutionDialog.accept()
}
Keys.onEnterPressed: {
customResolutionDialog.accept()
}
}
}
}
@@ -519,12 +527,17 @@ Flickable {
text: qsTr("Display mode")
font.pointSize: 12
wrapMode: Text.Wrap
visible: SystemProperties.hasWindowManager
visible: SystemProperties.hasDesktopEnvironment
}
AutoResizingComboBox {
// ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
if (!visible) {
// Do nothing if the control won't even be visible
return
}
// Set the recommended option based on the OS
for (var i = 0; i < windowModeListModel.count; i++) {
var thisWm = windowModeListModel.get(i).val;
@@ -537,22 +550,20 @@ Flickable {
currentIndex = 0
if (SystemProperties.hasWindowManager && !SystemProperties.rendererAlwaysFullScreen) {
var savedWm = StreamingPreferences.windowMode
for (var i = 0; i < windowModeListModel.count; i++) {
var thisWm = windowModeListModel.get(i).val;
if (savedWm === thisWm) {
currentIndex = i
break
}
}
var savedWm = StreamingPreferences.windowMode
for (var i = 0; i < windowModeListModel.count; i++) {
var thisWm = windowModeListModel.get(i).val;
if (savedWm === thisWm) {
currentIndex = i
break
}
}
activated(currentIndex)
}
id: windowModeComboBox
visible: SystemProperties.hasWindowManager
visible: SystemProperties.hasDesktopEnvironment
enabled: !SystemProperties.rendererAlwaysFullScreen
hoverEnabled: true
textRole: "text"
@@ -697,7 +708,7 @@ Flickable {
width: parent.width
text: qsTr("Mute audio stream when Moonlight is not the active window")
font.pointSize: 12
visible: SystemProperties.hasWindowManager
visible: SystemProperties.hasDesktopEnvironment
checked: StreamingPreferences.muteOnFocusLoss
onCheckedChanged: {
StreamingPreferences.muteOnFocusLoss = checked
@@ -724,37 +735,155 @@ Flickable {
Label {
width: parent.width
id: uiDisplayModeTitle
text: qsTr("GUI display mode")
id: languageTitle
text: qsTr("Language")
font.pointSize: 12
wrapMode: Text.Wrap
visible: SystemProperties.hasWindowManager
}
AutoResizingComboBox {
// ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
if (SystemProperties.hasWindowManager) {
var saved_uidisplaymode = StreamingPreferences.uiDisplayMode
currentIndex = 0
for (var i = 0; i < uiDisplayModeListModel.count; i++) {
var el_uidisplaymode = uiDisplayModeListModel.get(i).val;
if (saved_uidisplaymode === el_uidisplaymode) {
currentIndex = i
break
}
var saved_language = StreamingPreferences.language
currentIndex = 0
for (var i = 0; i < languageListModel.count; i++) {
var el_language = languageListModel.get(i).val;
if (saved_language === el_language) {
currentIndex = i
break
}
}
else {
// Full-screen is always selected when there is no window manager
currentIndex = 2
activated(currentIndex)
}
id: languageComboBox
textRole: "text"
model: ListModel {
id: languageListModel
ListElement {
text: qsTr("Automatic")
val: StreamingPreferences.LANG_AUTO
}
ListElement {
text: "Deutsch" // German
val: StreamingPreferences.LANG_DE
}
ListElement {
text: "English"
val: StreamingPreferences.LANG_EN
}
ListElement {
text: "Français" // French
val: StreamingPreferences.LANG_FR
}
ListElement {
text: "简体中文" // Simplified Chinese
val: StreamingPreferences.LANG_ZH_CN
}
ListElement {
text: "Norwegian Bokmål"
val: StreamingPreferences.LANG_NB_NO
}
ListElement {
text: "русский" // Russian
val: StreamingPreferences.LANG_RU
}
ListElement {
text: "Español" // Spanish
val: StreamingPreferences.LANG_ES
}
ListElement {
text: "日本語" // Japanese
val: StreamingPreferences.LANG_JA
}
ListElement {
text: "Tiếng Việt" // Vietnamese
val: StreamingPreferences.LANG_VI
}
ListElement {
text: "ภาษาไทย" // Thai
val: StreamingPreferences.LANG_TH
}
ListElement {
text: "한국어" // Korean
val: StreamingPreferences.LANG_KO
}
/* ListElement {
text: "Magyar" // Hungarian
val: StreamingPreferences.LANG_HU
} */
ListElement {
text: "Nederlands" // Dutch
val: StreamingPreferences.LANG_NL
}
ListElement {
text: "Svenska" // Swedish
val: StreamingPreferences.LANG_SV
}
/* ListElement {
text: "Türkçe" // Turkish
val: StreamingPreferences.LANG_TR
} */
/* ListElement {
text: "Українська" // Ukrainian
val: StreamingPreferences.LANG_UK
} */
ListElement {
text: "繁体字" // Traditional Chinese
val: StreamingPreferences.LANG_ZH_HANT
}
}
// ::onActivated must be used, as it only listens for when the index is changed by a human
onActivated : {
// Retranslating is expensive, so only do it if the language actually changed
var new_language = languageListModel.get(currentIndex).val
if (StreamingPreferences.language !== new_language) {
StreamingPreferences.language = languageListModel.get(currentIndex).val
if (!StreamingPreferences.retranslate()) {
ToolTip.show(qsTr("You must restart Moonlight for this change to take effect"), 5000)
}
else {
// Force the back operation to pop any AppView pages that exist.
// The AppView stops working after retranslate() for some reason.
window.clearOnBack = true
}
}
}
}
Label {
width: parent.width
id: uiDisplayModeTitle
text: qsTr("GUI display mode")
font.pointSize: 12
wrapMode: Text.Wrap
visible: SystemProperties.hasDesktopEnvironment
}
AutoResizingComboBox {
// ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
if (!visible) {
// Do nothing if the control won't even be visible
return
}
var saved_uidisplaymode = StreamingPreferences.uiDisplayMode
currentIndex = 0
for (var i = 0; i < uiDisplayModeListModel.count; i++) {
var el_uidisplaymode = uiDisplayModeListModel.get(i).val;
if (saved_uidisplaymode === el_uidisplaymode) {
currentIndex = i
break
}
}
activated(currentIndex)
}
id: uiDisplayModeComboBox
visible: SystemProperties.hasWindowManager
visible: SystemProperties.hasDesktopEnvironment
textRole: "text"
model: ListModel {
id: uiDisplayModeListModel
@@ -833,7 +962,6 @@ Flickable {
width: parent.width
text: qsTr("Optimize mouse for remote desktop instead of games")
font.pointSize: 12
visible: SystemProperties.hasWindowManager
checked: StreamingPreferences.absoluteMouseMode
onCheckedChanged: {
StreamingPreferences.absoluteMouseMode = checked
@@ -847,25 +975,79 @@ Flickable {
qsTr("NOTE: Due to a bug in GeForce Experience, this option may not work properly if your host PC has multiple monitors.")
}
CheckBox {
id: captureSysKeysCheck
hoverEnabled: true
Row {
spacing: 5
width: parent.width
text: qsTr("Capture system keyboard shortcuts ") +
(!SystemProperties.isRunningWayland ? qsTr("while streaming in fullscreen") :
qsTr("(unsupported on Wayland)"))
font.pointSize: 12
enabled: SystemProperties.hasWindowManager && !SystemProperties.isRunningWayland
checked: (StreamingPreferences.captureSysKeys || !SystemProperties.hasWindowManager) && !SystemProperties.isRunningWayland
onCheckedChanged: {
StreamingPreferences.captureSysKeys = checked
CheckBox {
id: captureSysKeysCheck
hoverEnabled: true
text: qsTr("Capture system keyboard shortcuts")
font.pointSize: 12
enabled: SystemProperties.hasDesktopEnvironment
checked: StreamingPreferences.captureSysKeysMode !== StreamingPreferences.CSK_OFF || !SystemProperties.hasDesktopEnvironment
ToolTip.delay: 1000
ToolTip.timeout: 10000
ToolTip.visible: hovered
ToolTip.text: qsTr("This enables the capture of system-wide keyboard shortcuts like Alt+Tab that would normally be handled by the client OS while streaming.") + "\n\n" +
qsTr("NOTE: Certain keyboard shortcuts like Ctrl+Alt+Del on Windows cannot be intercepted by any application, including Moonlight.")
}
ToolTip.delay: 1000
ToolTip.timeout: 10000
ToolTip.visible: hovered
ToolTip.text: qsTr("This enables the capture of system-wide keyboard shortcuts like Alt+Tab that would normally be handled by the client OS while streaming in fullscreen.") + "\n\n" +
qsTr("NOTE: Certain keyboard shortcuts like Ctrl+Alt+Del on Windows cannot be intercepted by any application, including Moonlight.")
AutoResizingComboBox {
// ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
if (!visible) {
// Do nothing if the control won't even be visible
return
}
var saved_syskeysmode = StreamingPreferences.captureSysKeysMode
currentIndex = 0
for (var i = 0; i < captureSysKeysModeListModel.count; i++) {
var el_syskeysmode = captureSysKeysModeListModel.get(i).val;
if (saved_syskeysmode === el_syskeysmode) {
currentIndex = i
break
}
}
activated(currentIndex)
}
enabled: captureSysKeysCheck.checked && captureSysKeysCheck.enabled
textRole: "text"
model: ListModel {
id: captureSysKeysModeListModel
ListElement {
text: qsTr("in fullscreen")
val: StreamingPreferences.CSK_FULLSCREEN
}
ListElement {
text: qsTr("always")
val: StreamingPreferences.CSK_ALWAYS
}
}
function updatePref() {
if (!enabled) {
StreamingPreferences.captureSysKeysMode = StreamingPreferences.CSK_OFF
}
else {
StreamingPreferences.captureSysKeysMode = captureSysKeysModeListModel.get(currentIndex).val
}
}
// ::onActivated must be used, as it only listens for when the index is changed by a human
onActivated: {
updatePref()
}
// This handles transition of the checkbox state
onEnabledChanged: {
updatePref()
}
}
}
CheckBox {
@@ -980,7 +1162,7 @@ Flickable {
width: parent.width
text: qsTr("Process gamepad input when Moonlight is in the background")
font.pointSize: 12
visible: SystemProperties.hasWindowManager
visible: SystemProperties.hasDesktopEnvironment
checked: StreamingPreferences.backgroundGamepad
onCheckedChanged: {
StreamingPreferences.backgroundGamepad = checked
@@ -1102,6 +1284,13 @@ Flickable {
AutoResizingComboBox {
// ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
if (SystemProperties.supportsHdr) {
codecListModel.append({
"text": qsTr("HEVC HDR (Experimental)"),
"val": StreamingPreferences.VCC_FORCE_HEVC_HDR
});
}
var saved_vcc = StreamingPreferences.videoCodecConfig
currentIndex = 0
for(var i = 0; i < codecListModel.count; i++) {
@@ -1130,10 +1319,7 @@ Flickable {
text: qsTr("HEVC (H.265)")
val: StreamingPreferences.VCC_FORCE_HEVC
}
/*ListElement {
text: qsTr("HEVC HDR (Experimental)")
val: StreamingPreferences.VCC_FORCE_HEVC_HDR
}*/
/* HEVC HDR will be appended here */
}
// ::onActivated must be used, as it only listens for when the index is changed by a human
onActivated : {
+3
View File
@@ -91,6 +91,8 @@ QVariant AppModel::data(const QModelIndex &index, int role) const
return app.id;
case DirectLaunchRole:
return app.directLaunch;
case AppCollectorGameRole:
return app.isAppCollectorGame;
default:
return QVariant();
}
@@ -106,6 +108,7 @@ QHash<int, QByteArray> AppModel::roleNames() const
names[HiddenRole] = "hidden";
names[AppIdRole] = "appid";
names[DirectLaunchRole] = "directLaunch";
names[AppCollectorGameRole] = "appCollectorGame";
return names;
}
+2 -1
View File
@@ -17,7 +17,8 @@ class AppModel : public QAbstractListModel
BoxArtRole,
HiddenRole,
AppIdRole,
DirectLaunchRole
DirectLaunchRole,
AppCollectorGameRole,
};
public:
+3
View File
@@ -42,6 +42,8 @@ QVariant ComputerModel::data(const QModelIndex& index, int role) const
return !computer->macAddress.isEmpty();
case StatusUnknownRole:
return computer->state == NvComputer::CS_UNKNOWN;
case ServerSupportedRole:
return computer->isSupportedServerVersion;
default:
return QVariant();
}
@@ -68,6 +70,7 @@ QHash<int, QByteArray> ComputerModel::roleNames() const
names[BusyRole] = "busy";
names[WakeableRole] = "wakeable";
names[StatusUnknownRole] = "statusUnknown";
names[ServerSupportedRole] = "serverSupported";
return names;
}
+2 -1
View File
@@ -14,7 +14,8 @@ class ComputerModel : public QAbstractListModel
PairedRole,
BusyRole,
WakeableRole,
StatusUnknownRole
StatusUnknownRole,
ServerSupportedRole
};
public:
+27 -7
View File
@@ -12,13 +12,18 @@ import SdlGamepadKeyNavigation 1.0
ApplicationWindow {
property bool pollingActive: false
// Set by SettingsView to force the back operation to pop all
// pages except the initial view. This is required when doing
// a retranslate() because AppView breaks for some reason.
property bool clearOnBack: false
id: window
visible: true
width: 1280
height: 600
visibility: {
if (SystemProperties.hasWindowManager) {
if (SystemProperties.hasDesktopEnvironment) {
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_WINDOWED) return "Windowed"
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) return "Maximized"
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_FULLSCREEN) return "FullScreen"
@@ -31,6 +36,17 @@ ApplicationWindow {
// it will never insert a line break and just extend on forever.
ToolTip.toolTip.contentWidth: ToolTip.toolTip.implicitContentWidth < 400 ? ToolTip.toolTip.implicitContentWidth : 400
function goBack() {
if (clearOnBack) {
// Pop all items except the first one
stackView.pop(null)
clearOnBack = false
}
else {
stackView.pop()
}
}
StackView {
id: stackView
initialItem: initialView
@@ -46,7 +62,7 @@ ApplicationWindow {
Keys.onEscapePressed: {
if (depth > 1) {
stackView.pop()
goBack()
}
else {
quitConfirmationDialog.open()
@@ -55,7 +71,7 @@ ApplicationWindow {
Keys.onBackPressed: {
if (depth > 1) {
stackView.pop()
goBack()
}
else {
quitConfirmationDialog.open()
@@ -199,7 +215,7 @@ ApplicationWindow {
}
RowLayout {
spacing: 20
spacing: 10
anchors.leftMargin: 10
anchors.rightMargin: 10
anchors.fill: parent
@@ -210,7 +226,7 @@ ApplicationWindow {
iconSource: "qrc:/res/arrow_left.svg"
onClicked: stackView.pop()
onClicked: goBack()
Keys.onDownPressed: {
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
@@ -396,7 +412,7 @@ ApplicationWindow {
ErrorMessageDialog {
id: noHwDecoderDialog
text: qsTr("No functioning hardware accelerated H.264 video decoder was detected by Moonlight. " +
text: qsTr("No functioning hardware accelerated video decoder was detected by Moonlight. " +
"Your streaming performance may be severely degraded in this configuration.")
helpText: qsTr("Click the Help button for more information on solving this problem.")
helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems"
@@ -477,7 +493,7 @@ ApplicationWindow {
onAccepted: {
if (editText.text) {
ComputerManager.addNewHost(editText.text.trim(), false)
ComputerManager.addNewHostManually(editText.text.trim())
}
}
@@ -495,6 +511,10 @@ ApplicationWindow {
Keys.onReturnPressed: {
addPcDialog.accept()
}
Keys.onEnterPressed: {
addPcDialog.accept()
}
}
}
}
+15 -1
View File
@@ -12,10 +12,11 @@
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation()
: m_Enabled(false),
m_UiNavMode(false),
m_FirstPoll(false),
m_LastAxisNavigationEventTime(0)
{
m_PollingTimer = new QTimer(this);
connect(m_PollingTimer, SIGNAL(timeout()), this, SLOT(onPollingTimerFired()));
connect(m_PollingTimer, &QTimer::timeout, this, &SdlGamepadKeyNavigation::onPollingTimerFired);
}
SdlGamepadKeyNavigation::~SdlGamepadKeyNavigation()
@@ -62,6 +63,9 @@ void SdlGamepadKeyNavigation::enable()
}
}
// Flush events on the first poll
m_FirstPoll = true;
// Poll every 50 ms for a new joystick event
m_PollingTimer->start(50);
@@ -91,6 +95,16 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
SDL_Event event;
StreamingPreferences prefs;
// Discard any pending button events on the first poll to avoid picking up
// stale input data from the stream session (like the quit combo).
if (m_FirstPoll) {
SDL_PumpEvents();
SDL_FlushEvent(SDL_CONTROLLERBUTTONDOWN);
SDL_FlushEvent(SDL_CONTROLLERBUTTONUP);
m_FirstPoll = false;
}
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
+1
View File
@@ -34,5 +34,6 @@ private:
QList<SDL_GameController*> m_Gamepads;
bool m_Enabled;
bool m_UiNavMode;
bool m_FirstPoll;
Uint32 m_LastAxisNavigationEventTime;
};
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
+475 -220
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.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
+27 -16
View File
@@ -11,7 +11,6 @@
#include <QCursor>
#include <QElapsedTimer>
#include <QFile>
#include<QTranslator>
// Don't let SDL hook our main function, since Qt is already
// doing the same thing. This needs to be before any headers
@@ -351,14 +350,16 @@ int main(int argc, char *argv[])
}
#endif
#if defined(Q_OS_DARWIN) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (!qEnvironmentVariableIsSet("QSG_RHI_BACKEND")) {
// The Metal backend in Qt 6.0.0 causes really strange issues transitioning to
// full-screen in our SDL window (hangs in SDL's Cocoa_SetWindowFullscreenSpace())
// and breaks drawing our status updates in the StreamSegue, so use OpenGL like
// Qt 5 does until we figure out the cause of these issues.
qputenv("QSG_RHI_BACKEND", "opengl");
}
#if !defined(Q_OS_WIN32) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
// Moonlight requires the non-threaded renderer because we depend
// on being able to control the render thread by blocking in the
// main thread (and pumping events from the main thread when needed).
// That doesn't work with the threaded renderer which causes all
// sorts of odd behavior depending on the platform.
//
// NB: Windows defaults to the "windows" non-threaded render loop on
// Qt 5 and the threaded render loop on Qt 6.
qputenv("QSG_RENDER_LOOP", "basic");
#endif
// We don't want system proxies to apply to us
@@ -394,6 +395,9 @@ int main(int argc, char *argv[])
// future KMSDRM platforms that use SDL for rendering.
SDL_SetHint(SDL_HINT_VIDEO_DOUBLE_BUFFER, "1");
// We use MMAL to render on Raspberry Pi, so we do not require DRM master.
SDL_SetHint("SDL_KMSDRM_REQUIRE_DRM_MASTER", "0");
// Use Direct3D 9Ex to avoid a deadlock caused by the D3D device being reset when
// the user triggers a UAC prompt. This option controls the software/SDL renderer.
// The DXVA2 renderer uses Direct3D 9Ex itself directly.
@@ -438,9 +442,11 @@ int main(int argc, char *argv[])
// the mouse motion exactly how it was given to us.
SDL_SetHint("SDL_MOUSE_RELATIVE_SCALING", "0");
// Set our app name for SDL to use with PulseAudio. This matches what we provide
// as our app name to libsoundio too.
// Set our app name for SDL to use with PulseAudio and PipeWire. This matches what we
// provide as our app name to libsoundio too. On SDL 2.0.18+, SDL_APP_NAME is also used
// for screensaver inhibitor reporting.
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "Moonlight");
SDL_SetHint("SDL_APP_NAME", "Moonlight");
#ifdef QT_DEBUG
// Allow thread naming using exceptions on debug builds. SDL doesn't use SEH
@@ -451,9 +457,9 @@ int main(int argc, char *argv[])
QGuiApplication app(argc, argv);
QTranslator translator;
qDebug() << "Translation loaded:" << translator.load(QString(":/languages/qml_") + QLocale::system().name());
app.installTranslator(&translator);
// Apply the initial translation based on user preference
StreamingPreferences prefs;
prefs.retranslate();
// After the QGuiApplication is created, the platform stuff will be initialized
// and we can set the SDL video driver to match Qt.
@@ -499,6 +505,11 @@ int main(int argc, char *argv[])
app.setWindowIcon(QIcon(":/res/moonlight.svg"));
#endif
// This is necessary to show our icon correctly on Wayland
app.setDesktopFileName("com.moonlight_stream.Moonlight.desktop");
qputenv("SDL_VIDEO_WAYLAND_WMCLASS", "com.moonlight_stream.Moonlight");
qputenv("SDL_VIDEO_X11_WMCLASS", "com.moonlight_stream.Moonlight");
// Register our C++ types for QML
qmlRegisterType<ComputerModel>("ComputerModel", 1, 0, "ComputerModel");
qmlRegisterType<AppModel>("AppModel", 1, 0, "AppModel");
@@ -525,8 +536,8 @@ int main(int argc, char *argv[])
});
qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0,
"StreamingPreferences",
[](QQmlEngine*, QJSEngine*) -> QObject* {
return new StreamingPreferences();
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
return new StreamingPreferences(qmlEngine);
});
// Create the identity manager on the main thread
+146
View File
@@ -0,0 +1,146 @@
// This file contains hooks for several functions that allow
// Qt and SDL to more or less share DRM master ownership.
//
// This technique requires Linux v5.8 or later, or for Moonlight
// to run as root (with CAP_SYS_ADMIN). Prior to Linux v5.8,
// DRM_IOCTL_DROP_MASTER required CAP_SYS_ADMIN, which prevents
// our trick from working (without root, that is).
//
// The specific kernel change required to run without root is:
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=45bc3d26c95a8fc63a7d8668ca9e57ef0883351c
#define _GNU_SOURCE
#include <SDL.h>
#include <dlfcn.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
// We require SDL 2.0.15+ to hook because it supports sharing
// the DRM FD with our code. This avoids having multiple DRM FDs
// in flight at the same time which would significantly complicate
// the logic here because we'd need to figure out exactly which FD
// should be the master at any given time. With the position of our
// hooks, that is definitely not trivial.
#if SDL_VERSION_ATLEAST(2, 0, 15)
// Qt's DRM master FD grabbed by our hook
int g_QtDrmMasterFd = -1;
struct stat64 g_DrmMasterStat;
// The DRM master FD created for SDL
int g_SdlDrmMasterFd = -1;
// This hook will handle legacy DRM rendering
int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
uint32_t x, uint32_t y, uint32_t *connectors, int count,
drmModeModeInfoPtr mode)
{
// Grab the first DRM FD that makes it in here. This will be the Qt
// EGLFS backend's DRM FD, which we will need later.
if (g_QtDrmMasterFd == -1) {
g_QtDrmMasterFd = fd;
fstat64(g_QtDrmMasterFd, &g_DrmMasterStat);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Captured Qt EGLFS DRM master fd (legacy): %d",
g_QtDrmMasterFd);
}
// Call into the real thing
return ((typeof(drmModeSetCrtc)*)dlsym(RTLD_NEXT, "drmModeSetCrtc"))(fd, crtcId, bufferId, x, y, connectors, count, mode);
}
// This hook will handle atomic DRM rendering
int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req,
uint32_t flags, void *user_data)
{
// Grab the first DRM FD that makes it in here. This will be the Qt
// EGLFS backend's DRM FD, which we will need later.
if (g_QtDrmMasterFd == -1) {
g_QtDrmMasterFd = fd;
fstat64(g_QtDrmMasterFd, &g_DrmMasterStat);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Captured Qt EGLFS DRM master fd (atomic): %d",
g_QtDrmMasterFd);
}
// Call into the real thing
return ((typeof(drmModeAtomicCommit)*)dlsym(RTLD_NEXT, "drmModeAtomicCommit"))(fd, req, flags, user_data);
}
// This hook will handle SDL's open() on the DRM device. We just need to
// hook this variant of open(), since that's what SDL uses. When we see
// the open a FD for the same card as the Qt DRM master FD, we'll drop
// master on the Qt FD to allow the new FD to have master.
int open(const char *pathname, int flags)
{
// Call the real thing to do the open operation
int fd = ((typeof(open)*)dlsym(RTLD_NEXT, "open"))(pathname, flags);
// If the file was successfully opened and we have a DRM master FD,
// check if the FD we just opened is a DRM device.
if (fd >= 0 && g_QtDrmMasterFd != -1) {
if (strncmp(pathname, "/dev/dri/card", 13) == 0) {
// It's a DRM device, but is it _our_ DRM device?
struct stat64 fdstat;
fstat64(fd, &fdstat);
if (g_DrmMasterStat.st_dev == fdstat.st_dev &&
g_DrmMasterStat.st_ino == fdstat.st_ino) {
// It is our device. Time to do the magic!
// This code assumes SDL only ever opens a single FD
// for a given DRM device.
SDL_assert(g_SdlDrmMasterFd == -1);
// Drop master on Qt's FD so we can pick it up for SDL.
if (drmDropMaster(g_QtDrmMasterFd) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to drop master on Qt DRM FD: %d",
errno);
// Hope for the best
return fd;
}
// We are not allowed to call drmSetMaster() without CAP_SYS_ADMIN,
// but since we just dropped the master, we can become master by
// simply creating a new FD. Let's do it.
close(fd);
fd = ((typeof(open)*)dlsym(RTLD_NEXT, "open"))(pathname, flags);
g_SdlDrmMasterFd = fd;
}
}
}
return fd;
}
// Our close() hook handles restoring DRM master to the Qt FD
// after SDL closes its DRM FD.
int close(int fd)
{
// Call the real thing
int ret = ((typeof(close)*)dlsym(RTLD_NEXT, "close"))(fd);
if (ret == 0) {
// If we just closed the SDL DRM master FD, restore master
// to the Qt DRM FD. This works because the Qt DRM master FD
// was master once before, so we can set it as master again
// using drmSetMaster() without CAP_SYS_ADMIN.
if (g_SdlDrmMasterFd != -1 && fd == g_SdlDrmMasterFd) {
if (drmSetMaster(g_QtDrmMasterFd) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to restore master to Qt DRM FD: %d",
errno);
}
g_SdlDrmMasterFd = -1;
}
}
return ret;
}
#endif
+34 -2
View File
@@ -17,14 +17,46 @@
<file>res/baseline-error_outline-24px.svg</file>
<file>res/baseline-check_circle_outline-24px.svg</file>
<file>res/Discord-Logo-White.svg</file>
<file>languages/qml_de.ts</file>
<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_nb_NO.ts</file>
<file>languages/qml_nb_NO.qm</file>
<file>languages/qml_ru.ts</file>
<file>languages/qml_ru.qm</file>
<file>languages/qml_es.ts</file>
<file>languages/qml_es.qm</file>
<file>languages/qml_ja.ts</file>
<file>languages/qml_ja.qm</file>
<file>languages/qml_vi.ts</file>
<file>languages/qml_vi.qm</file>
<file>languages/qml_th.ts</file>
<file>languages/qml_th.qm</file>
<file>languages/qml_ko.ts</file>
<file>languages/qml_ko.qm</file>
<file>languages/qml_hu.ts</file>
<file>languages/qml_hu.qm</file>
<file>languages/qml_nl.ts</file>
<file>languages/qml_nl.qm</file>
<file>languages/qml_sv.ts</file>
<file>languages/qml_sv.qm</file>
<file>languages/qml_tr.ts</file>
<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>
</qresource>
<qresource prefix="/data">
<file alias="gamecontrollerdb.txt">SDL_GameControllerDB/gamecontrollerdb.txt</file>
<file alias="ModeSeven.ttf">ModeSeven.ttf</file>
<file alias="egl.frag">shaders/egl.frag</file>
<file alias="egl.vert">shaders/egl.vert</file>
<file alias="egl_nv12.frag">shaders/egl_nv12.frag</file>
<file alias="egl_nv12.vert">shaders/egl_nv12.vert</file>
<file alias="egl_opaque.frag">shaders/egl_opaque.frag</file>
<file alias="egl_opaque.vert">shaders/egl_opaque.vert</file>
<file alias="egl_overlay.frag">shaders/egl_overlay.frag</file>
<file alias="egl_overlay.vert">shaders/egl_overlay.vert</file>
</qresource>
+137
View File
@@ -0,0 +1,137 @@
#include "compatfetcher.h"
#include "path.h"
#include <QNetworkReply>
#include <QSettings>
#define COMPAT_VERSION "v1"
#define COMPAT_KEY "latestsupportedversion-"
CompatFetcher::CompatFetcher(QObject *parent) :
QObject(parent),
m_Nam(this)
{
// Never communicate over HTTP
m_Nam.setStrictTransportSecurityEnabled(true);
// Allow HTTP redirects
m_Nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
connect(&m_Nam, &QNetworkAccessManager::finished,
this, &CompatFetcher::handleCompatInfoFetched);
}
void CompatFetcher::start()
{
#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);
QT_WARNING_POP
#endif
QUrl url("https://moonlight-stream.org/compatibility/" COMPAT_VERSION);
QNetworkRequest request(url);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);
#else
request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
#endif
// We'll get a callback when this is finished
m_Nam.get(request);
}
bool CompatFetcher::isGfeVersionSupported(QString gfeVersion)
{
QSettings settings;
if (gfeVersion.trimmed().isEmpty()) {
// If we don't have a GFE version, just allow it
return true;
}
QString latestSupportedVersion = settings.value(COMPAT_KEY COMPAT_VERSION).toString();
if (latestSupportedVersion.isEmpty()) {
// We don't have compat data yet, so just assume it's supported
return true;
}
QStringList latestSupportedVersionQuad = latestSupportedVersion.split('.');
QStringList gfeVersionQuad = gfeVersion.split('.');
if (gfeVersionQuad.count() <= 1) {
qWarning() << "Failed to parse GFE version:" << gfeVersion;
return true;
}
if (latestSupportedVersionQuad.count() <= 1) {
qWarning() << "Failed to parse latest supported version:" << latestSupportedVersion;
return true;
}
for (int i = 0;; i++) {
int actualVerVal = 0;
int latestSupportedVal = 0;
// Treat missing decimal places as 0
if (i < gfeVersionQuad.count()) {
bool ok;
actualVerVal = gfeVersionQuad[i].toInt(&ok);
if (!ok || actualVerVal < 0) {
// Return true to be safe
qWarning() << "Failed to parse GFE version:" << gfeVersion;
return true;
}
}
if (i < latestSupportedVersionQuad.count()) {
bool ok;
latestSupportedVal = latestSupportedVersionQuad[i].toInt(&ok);
if (!ok || latestSupportedVal < 0) {
// Return true to be safe
qWarning() << "Failed to parse latest supported version:" << latestSupportedVersion;
return true;
}
}
if (i >= gfeVersionQuad.count() && i >= latestSupportedVersionQuad.count()) {
// Equal versions - this is fine
return true;
}
if (actualVerVal < latestSupportedVal) {
// Actual version is lower than latest supported - this is fine
return true;
}
else if (actualVerVal > latestSupportedVal) {
// Actual version is greater than latest supported - this is bad
qWarning() << "GFE version" << gfeVersion << "is not supported by this version of Moonlight";
return false;
}
}
}
void CompatFetcher::handleCompatInfoFetched(QNetworkReply* reply)
{
Q_ASSERT(reply->isFinished());
if (reply->error() == QNetworkReply::NoError) {
// Queue the reply for deletion
reply->deleteLater();
QString version = QString(reply->readAll()).trimmed();
QSettings settings;
settings.setValue(COMPAT_KEY COMPAT_VERSION, version);
qInfo() << "Latest supported GFE server:" << version;
}
else {
qWarning() << "Failed to download latest compatibility data:" << reply->error();
reply->deleteLater();
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <QObject>
#include <QNetworkAccessManager>
class CompatFetcher : public QObject
{
Q_OBJECT
public:
explicit CompatFetcher(QObject *parent = nullptr);
void start();
static bool isGfeVersionSupported(QString gfeVersion);
private slots:
void handleCompatInfoFetched(QNetworkReply* reply);
private:
QNetworkAccessManager m_Nam;
};
+2 -2
View File
@@ -13,8 +13,8 @@ MappingFetcher::MappingFetcher(QObject *parent) :
// Allow HTTP redirects
m_Nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
connect(&m_Nam, SIGNAL(finished(QNetworkReply*)),
this, SLOT(handleMappingListFetched(QNetworkReply*)));
connect(&m_Nam, &QNetworkAccessManager::finished,
this, &MappingFetcher::handleMappingListFetched);
}
void MappingFetcher::start()
+1 -1
View File
@@ -41,7 +41,7 @@ MappingManager::MappingManager()
#else
.split('\n', QString::SkipEmptyParts);
#endif
for (QString sdlMapping : sdlMappings) {
for (const QString& sdlMapping : sdlMappings) {
SdlGamepadMapping mapping(sdlMapping);
addMapping(mapping);
}
+117 -3
View File
@@ -1,6 +1,11 @@
#include "streamingpreferences.h"
#include <QSettings>
#include <QTranslator>
#include <QCoreApplication>
#include <QLocale>
#include <QtDebug>
#define SER_STREAMSETTINGS "streamsettings"
#define SER_WIDTH "width"
@@ -36,11 +41,20 @@
#define SER_REVERSESCROLL "reversescroll"
#define SER_SWAPFACEBUTTONS "swapfacebuttons"
#define SER_CAPTURESYSKEYS "capturesyskeys"
#define SER_LANGUAGE "language"
#define CURRENT_DEFAULT_VER 1
StreamingPreferences::StreamingPreferences(QObject *parent)
: QObject(parent)
: QObject(parent),
m_QmlEngine(nullptr)
{
reload();
}
StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent)
: QObject(parent),
m_QmlEngine(qmlEngine)
{
reload();
}
@@ -81,7 +95,8 @@ void StreamingPreferences::reload()
backgroundGamepad = settings.value(SER_BACKGROUNDGAMEPAD, false).toBool();
reverseScrollDirection = settings.value(SER_REVERSESCROLL, false).toBool();
swapFaceButtons = settings.value(SER_SWAPFACEBUTTONS, false).toBool();
captureSysKeys = settings.value(SER_CAPTURESYSKEYS, 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,
static_cast<int>(AudioConfig::AC_STEREO)).toInt());
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
@@ -95,6 +110,8 @@ void StreamingPreferences::reload()
uiDisplayMode = static_cast<UIDisplayMode>(settings.value(SER_UIDISPLAYMODE,
static_cast<int>(settings.value(SER_STARTWINDOWED, true).toBool() ? UIDisplayMode::UI_WINDOWED
: UIDisplayMode::UI_MAXIMIZED)).toInt());
language = static_cast<Language>(settings.value(SER_LANGUAGE,
static_cast<int>(Language::LANG_AUTO)).toInt());
// Perform default settings updates as required based on last default version
@@ -108,6 +125,102 @@ void StreamingPreferences::reload()
}
}
bool StreamingPreferences::retranslate()
{
static QTranslator* translator = nullptr;
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
if (m_QmlEngine != nullptr) {
// Dynamic retranslation is not supported until Qt 5.10
return false;
}
#endif
QTranslator* newTranslator = new QTranslator();
QString languageSuffix = getSuffixFromLanguage(language);
// Remove the old translator, even if we can't load a new one.
// Otherwise we'll be stuck with the old translated values instead
// of defaulting to English.
if (translator != nullptr) {
QCoreApplication::removeTranslator(translator);
delete translator;
translator = nullptr;
}
if (newTranslator->load(QString(":/languages/qml_") + languageSuffix)) {
qInfo() << "Successfully loaded translation for " << languageSuffix;
translator = newTranslator;
QCoreApplication::installTranslator(translator);
}
else {
qInfo() << "No translation available for " << languageSuffix;
delete newTranslator;
}
if (m_QmlEngine != nullptr) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
// This is a dynamic retranslation from the settings page.
// We have to kick the QML engine into reloading our text.
m_QmlEngine->retranslate();
#else
// Unreachable below Qt 5.10 due to the check above
Q_ASSERT(false);
#endif
}
else {
// This is a translation from a non-QML context, which means
// it is probably app startup. There's nothing to refresh.
}
return true;
}
QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Language lang)
{
switch (lang)
{
case LANG_DE:
return "de";
case LANG_EN:
return "en";
case LANG_FR:
return "fr";
case LANG_ZH_CN:
return "zh_cn";
case LANG_NB_NO:
return "nb_NO";
case LANG_RU:
return "ru";
case LANG_ES:
return "es";
case LANG_JA:
return "ja";
case LANG_VI:
return "vi";
case LANG_TH:
return "th";
case LANG_KO:
return "ko";
case LANG_HU:
return "hu";
case LANG_NL:
return "nl";
case LANG_SV:
return "sv";
case LANG_TR:
return "tr";
case LANG_UK:
return "uk";
case LANG_ZH_HANT:
return "zh_Hant";
case LANG_AUTO:
default:
return QLocale::system().name();
}
}
void StreamingPreferences::save()
{
QSettings settings;
@@ -136,13 +249,14 @@ void StreamingPreferences::save()
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
settings.setValue(SER_UIDISPLAYMODE, static_cast<int>(uiDisplayMode));
settings.setValue(SER_LANGUAGE, static_cast<int>(language));
settings.setValue(SER_DEFAULTVER, CURRENT_DEFAULT_VER);
settings.setValue(SER_SWAPMOUSEBUTTONS, swapMouseButtons);
settings.setValue(SER_MUTEONFOCUSLOSS, muteOnFocusLoss);
settings.setValue(SER_BACKGROUNDGAMEPAD, backgroundGamepad);
settings.setValue(SER_REVERSESCROLL, reverseScrollDirection);
settings.setValue(SER_SWAPFACEBUTTONS, swapFaceButtons);
settings.setValue(SER_CAPTURESYSKEYS, captureSysKeys);
settings.setValue(SER_CAPTURESYSKEYS, captureSysKeysMode);
}
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
+49 -3
View File
@@ -2,6 +2,7 @@
#include <QObject>
#include <QRect>
#include <QQmlEngine>
class StreamingPreferences : public QObject
{
@@ -9,6 +10,7 @@ class StreamingPreferences : public QObject
public:
StreamingPreferences(QObject *parent = nullptr);
StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent = nullptr);
Q_INVOKABLE static int
getDefaultBitrate(int width, int height, int fps);
@@ -58,6 +60,40 @@ public:
};
Q_ENUM(UIDisplayMode)
// New entries must go at the end of the enum
// to avoid renumbering existing entries (which
// would affect existing user preferences).
enum Language
{
LANG_AUTO,
LANG_EN,
LANG_FR,
LANG_ZH_CN,
LANG_DE,
LANG_NB_NO,
LANG_RU,
LANG_ES,
LANG_JA,
LANG_VI,
LANG_TH,
LANG_KO,
LANG_HU,
LANG_NL,
LANG_SV,
LANG_TR,
LANG_UK,
LANG_ZH_HANT
};
Q_ENUM(Language);
enum CaptureSysKeysMode
{
CSK_OFF,
CSK_FULLSCREEN,
CSK_ALWAYS,
};
Q_ENUM(CaptureSysKeysMode);
Q_PROPERTY(int width MEMBER width NOTIFY displayModeChanged)
Q_PROPERTY(int height MEMBER height NOTIFY displayModeChanged)
Q_PROPERTY(int fps MEMBER fps NOTIFY displayModeChanged)
@@ -87,7 +123,10 @@ public:
Q_PROPERTY(bool backgroundGamepad MEMBER backgroundGamepad NOTIFY backgroundGamepadChanged)
Q_PROPERTY(bool reverseScrollDirection MEMBER reverseScrollDirection NOTIFY reverseScrollDirectionChanged)
Q_PROPERTY(bool swapFaceButtons MEMBER swapFaceButtons NOTIFY swapFaceButtonsChanged)
Q_PROPERTY(bool captureSysKeys MEMBER captureSysKeys NOTIFY captureSysKeysChanged)
Q_PROPERTY(CaptureSysKeysMode captureSysKeysMode MEMBER captureSysKeysMode NOTIFY captureSysKeysModeChanged)
Q_PROPERTY(Language language MEMBER language NOTIFY languageChanged);
Q_INVOKABLE bool retranslate();
// Directly accessible members for preferences
int width;
@@ -113,7 +152,6 @@ public:
bool backgroundGamepad;
bool reverseScrollDirection;
bool swapFaceButtons;
bool captureSysKeys;
int packetSize;
AudioConfig audioConfig;
VideoCodecConfig videoCodecConfig;
@@ -121,6 +159,8 @@ public:
WindowMode windowMode;
WindowMode recommendedFullScreenMode;
UIDisplayMode uiDisplayMode;
Language language;
CaptureSysKeysMode captureSysKeysMode;
signals:
void displayModeChanged();
@@ -149,6 +189,12 @@ signals:
void backgroundGamepadChanged();
void reverseScrollDirectionChanged();
void swapFaceButtonsChanged();
void captureSysKeysChanged();
void captureSysKeysModeChanged();
void languageChanged();
private:
QString getSuffixFromLanguage(Language lang);
QQmlEngine* m_QmlEngine;
};
+12
View File
@@ -0,0 +1,12 @@
#version 300 es
#extension GL_OES_EGL_image_external : require
precision mediump float;
out vec4 FragColor;
in vec2 vTextCoord;
uniform samplerExternalOES uTexture;
void main() {
FragColor = texture2D(uTexture, vTextCoord);
}
+10
View File
@@ -0,0 +1,10 @@
#version 300 es
layout (location = 0) in vec2 aPosition; // 2D: X,Y
layout (location = 1) in vec2 aTexCoord;
out vec2 vTextCoord;
void main() {
vTextCoord = aTexCoord;
gl_Position = vec4(aPosition, 0, 1);
}
+1 -10
View File
@@ -55,20 +55,11 @@ IAudioRenderer* Session::createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATI
TRY_INIT_RENDERER(SLAudioRenderer, opusConfig)
#endif
#if !defined(Q_OS_WIN32) && !defined(Q_OS_DARWIN)
// Linux defaults to SDL due to persistent glitching issues under libsoundio.
// Platforms that libsoundio doesn't support also default to SDL.
// Default to SDL and use libsoundio as a fallback
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
#ifdef HAVE_SOUNDIO
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
#endif
#else
// Windows and macOS default to libsoundio and fall back to SDL
#ifdef HAVE_SOUNDIO
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
#endif
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
#endif
return nullptr;
}
-96
View File
@@ -1,96 +0,0 @@
#include "qtaud.h"
#include <QDebug>
QtAudioRenderer::QtAudioRenderer()
: m_AudioOutput(nullptr),
m_OutputDevice(nullptr)
{
}
QtAudioRenderer::~QtAudioRenderer()
{
delete m_AudioOutput;
}
bool QtAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig)
{
QAudioFormat format;
// testAudio() assumes this sample rate
Q_ASSERT(opusConfig->sampleRate == 48000);
format.setSampleRate(opusConfig->sampleRate);
format.setChannelCount(opusConfig->channelCount);
format.setSampleSize(16);
format.setSampleType(QAudioFormat::SignedInt);
format.setCodec("audio/pcm");
m_AudioOutput = new QAudioOutput(format);
// Buffer 40 ms of audio data
m_AudioOutput->setBufferSize(format.bytesForDuration(40000));
m_OutputDevice = m_AudioOutput->start();
if (m_OutputDevice == nullptr) {
qWarning() << "Audio start failed:" << m_AudioOutput->error();
return false;
}
// Check what buffer size was really used after starting
qInfo() << "Audio stream buffer:" << m_AudioOutput->bufferSize() << "bytes";
return true;
}
void QtAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
{
m_OutputDevice->write((const char *)audioBuffer, audioSize);
}
bool QtAudioRenderer::testAudio(int audioConfiguration)
{
QAudioFormat format;
format.setSampleRate(48000);
format.setSampleSize(16);
format.setSampleType(QAudioFormat::SignedInt);
format.setCodec("audio/pcm");
switch (audioConfiguration) {
case AUDIO_CONFIGURATION_STEREO:
format.setChannelCount(2);
break;
case AUDIO_CONFIGURATION_51_SURROUND:
format.setChannelCount(6);
break;
default:
Q_ASSERT(false);
return false;
}
if (QAudioDeviceInfo::defaultOutputDevice().isFormatSupported(format)) {
qInfo() << "Audio test successful with" << format.channelCount() << "channels";
return true;
}
else {
qWarning() << "Audio test failed with" << format.channelCount() << "channels";
return false;
}
}
int QtAudioRenderer::detectAudioConfiguration()
{
int preferredChannelCount = QAudioDeviceInfo::defaultOutputDevice().preferredFormat().channelCount();
qInfo() << "Audio output device prefers" << preferredChannelCount << "channel configuration";
// We can better downmix 5.1 to quad than we can upmix stereo
if (preferredChannelCount > 2) {
return AUDIO_CONFIGURATION_51_SURROUND;
}
else {
return AUDIO_CONFIGURATION_STEREO;
}
}
-24
View File
@@ -1,24 +0,0 @@
#pragma once
#include "renderer.h"
#include <QAudioOutput>
class QtAudioRenderer : public IAudioRenderer
{
public:
QtAudioRenderer();
virtual ~QtAudioRenderer();
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
virtual void submitAudio(short* audioBuffer, int audioSize);
virtual bool testAudio(int audioConfiguration);
virtual int detectAudioConfiguration();
private:
QAudioOutput* m_AudioOutput;
QIODevice* m_OutputDevice;
};
+19 -2
View File
@@ -269,7 +269,22 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
event.quit.timestamp = SDL_GetTicks();
SDL_PushEvent(&event);
// Clear buttons down on this gameapd
// Clear buttons down on this gamepad
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
return;
}
// Handle Select+L1+R1+X as a gamepad overlay combo
if (state->buttons == (BACK_FLAG | LB_FLAG | RB_FLAG | X_FLAG)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected stats toggle gamepad combo");
// Toggle the stats overlay
Session::get()->getOverlayManager().setOverlayState(Overlay::OverlayDebug,
!Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebug));
// Clear buttons down on this gamepad
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
return;
@@ -342,7 +357,9 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
state->controller = controller;
state->jsId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(state->controller));
#if SDL_VERSION_ATLEAST(2, 0, 9)
#if SDL_VERSION_ATLEAST(2, 0, 18)
hapticCaps = SDL_GameControllerHasRumble(controller) ? ML_HAPTIC_GC_RUMBLE : 0;
#elif SDL_VERSION_ATLEAST(2, 0, 9)
// Perform a tiny rumble 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.
+134 -207
View File
@@ -7,13 +7,10 @@
#include <QtGlobal>
#include <QDir>
#include <QGuiApplication>
#define MOUSE_POLLING_INTERVAL 5
#ifdef Q_OS_WIN32
HHOOK g_KeyboardHook;
#endif
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int streamWidth, int streamHeight)
: m_MultiController(prefs.multiController),
m_GamepadMouse(prefs.gamepadMouse),
@@ -25,19 +22,24 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
m_MouseWasInVideoRegion(false),
m_PendingMouseButtonsAllUpOnVideoRegionLeave(false),
m_FakeCaptureActive(false),
m_CaptureSystemKeysEnabled(prefs.captureSysKeys || !WMUtils::isRunningWindowManager()),
m_CaptureSystemKeysMode(prefs.captureSysKeysMode),
m_MouseCursorCapturedVisibilityState(SDL_DISABLE),
m_LongPressTimer(0),
m_StreamWidth(streamWidth),
m_StreamHeight(streamHeight),
m_AbsoluteMouseMode(prefs.absoluteMouseMode),
m_AbsoluteTouchMode(prefs.absoluteTouchMode),
m_PendingMouseLeaveButtonUp(0),
m_LeftButtonReleaseTimer(0),
m_RightButtonReleaseTimer(0),
m_DragTimer(0),
m_DragButton(0),
m_NumFingersDown(0)
{
// System keys are always captured when running without a DE
if (!WMUtils::isRunningDesktopEnvironment()) {
m_CaptureSystemKeysMode = StreamingPreferences::CSK_ALWAYS;
}
// Allow gamepad input when the app doesn't have focus if requested
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, prefs.backgroundGamepad ? "1" : "0");
@@ -49,10 +51,15 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
prefs.absoluteMouseMode ? "1" : "0",
SDL_HINT_OVERRIDE);
// If we're grabbing system keys, enable the X11 keyboard grab in SDL
#if !SDL_VERSION_ATLEAST(2, 0, 15)
// For older versions of SDL (2.0.14 and earlier), use SDL_HINT_GRAB_KEYBOARD
SDL_SetHintWithPriority(SDL_HINT_GRAB_KEYBOARD,
m_CaptureSystemKeysEnabled ? "1" : "0",
m_CaptureSystemKeysMode != StreamingPreferences::CSK_OFF ? "1" : "0",
SDL_HINT_OVERRIDE);
#endif
// Opt-out of SDL's built-in Alt+Tab handling while keyboard grab is enabled
SDL_SetHint("SDL_ALLOW_ALT_TAB_WHILE_GRABBED", "0");
// Allow clicks to pass through to us when focusing the window. If we're in
// absolute mouse mode, this will avoid the user having to click twice to
@@ -67,6 +74,47 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
SDL_SetHint("SDL_JOYSTICK_HIDAPI_PS4_RUMBLE", "1");
SDL_SetHint("SDL_JOYSTICK_HIDAPI_PS5_RUMBLE", "1");
// Populate special key combo configuration
m_SpecialKeyCombos[KeyComboQuit].keyCombo = KeyComboQuit;
m_SpecialKeyCombos[KeyComboQuit].keyCode = SDLK_q;
m_SpecialKeyCombos[KeyComboQuit].scanCode = SDL_SCANCODE_Q;
m_SpecialKeyCombos[KeyComboQuit].enabled = true;
m_SpecialKeyCombos[KeyComboUngrabInput].keyCombo = KeyComboUngrabInput;
m_SpecialKeyCombos[KeyComboUngrabInput].keyCode = SDLK_z;
m_SpecialKeyCombos[KeyComboUngrabInput].scanCode = SDL_SCANCODE_Z;
m_SpecialKeyCombos[KeyComboUngrabInput].enabled = QGuiApplication::platformName() != "eglfs";
m_SpecialKeyCombos[KeyComboToggleFullScreen].keyCombo = KeyComboToggleFullScreen;
m_SpecialKeyCombos[KeyComboToggleFullScreen].keyCode = SDLK_x;
m_SpecialKeyCombos[KeyComboToggleFullScreen].scanCode = SDL_SCANCODE_X;
m_SpecialKeyCombos[KeyComboToggleFullScreen].enabled = QGuiApplication::platformName() != "eglfs";
m_SpecialKeyCombos[KeyComboToggleStatsOverlay].keyCombo = KeyComboToggleStatsOverlay;
m_SpecialKeyCombos[KeyComboToggleStatsOverlay].keyCode = SDLK_s;
m_SpecialKeyCombos[KeyComboToggleStatsOverlay].scanCode = SDL_SCANCODE_S;
m_SpecialKeyCombos[KeyComboToggleStatsOverlay].enabled = true;
m_SpecialKeyCombos[KeyComboToggleMouseMode].keyCombo = KeyComboToggleMouseMode;
m_SpecialKeyCombos[KeyComboToggleMouseMode].keyCode = SDLK_m;
m_SpecialKeyCombos[KeyComboToggleMouseMode].scanCode = SDL_SCANCODE_M;
m_SpecialKeyCombos[KeyComboToggleMouseMode].enabled = true;
m_SpecialKeyCombos[KeyComboToggleCursorHide].keyCombo = KeyComboToggleCursorHide;
m_SpecialKeyCombos[KeyComboToggleCursorHide].keyCode = SDLK_c;
m_SpecialKeyCombos[KeyComboToggleCursorHide].scanCode = SDL_SCANCODE_C;
m_SpecialKeyCombos[KeyComboToggleCursorHide].enabled = true;
m_SpecialKeyCombos[KeyComboToggleMinimize].keyCombo = KeyComboToggleMinimize;
m_SpecialKeyCombos[KeyComboToggleMinimize].keyCode = SDLK_d;
m_SpecialKeyCombos[KeyComboToggleMinimize].scanCode = SDL_SCANCODE_D;
m_SpecialKeyCombos[KeyComboToggleMinimize].enabled = QGuiApplication::platformName() != "eglfs";
m_SpecialKeyCombos[KeyComboPasteText].keyCombo = KeyComboPasteText;
m_SpecialKeyCombos[KeyComboPasteText].keyCode = SDLK_v;
m_SpecialKeyCombos[KeyComboPasteText].scanCode = SDL_SCANCODE_V;
m_SpecialKeyCombos[KeyComboPasteText].enabled = true;
m_OldIgnoreDevices = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES);
m_OldIgnoreDevicesExcept = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT);
@@ -149,38 +197,10 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
}
m_MouseMoveTimer = SDL_AddTimer(pollingInterval, SdlInputHandler::mouseMoveTimerCallback, this);
#ifdef Q_OS_WIN32
if (m_CaptureSystemKeysEnabled) {
// If system key capture is enabled, install the window hook required to intercept
// these key presses and block them from the OS itself.
g_KeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, SdlInputHandler::keyboardHookProc, GetModuleHandle(NULL), 0);
}
#endif
}
SdlInputHandler::~SdlInputHandler()
{
#ifdef Q_OS_WIN32
if (g_KeyboardHook != nullptr) {
UnhookWindowsHookEx(g_KeyboardHook);
g_KeyboardHook = nullptr;
// If we're terminating because of the user pressing the quit combo,
// we won't have a chance to inform SDL as the modifier keys raise.
// This will leave stale modifier flags in the SDL_Keyboard global
// inside SDL which will show up on our next key event if the user
// streams again. Avoid this by explicitly zeroing mod state when
// ending the stream.
//
// This is only needed for the case where we're hooking the keyboard
// because we're generating synthetic SDL_KEYDOWN/SDL_KEYUP events
// which don't properly maintain SDL's internal keyboard state,
// so we're forced to invoke SDL_SetModState() ourselves to set mods.
SDL_SetModState(KMOD_NONE);
}
#endif
for (int i = 0; i < MAX_GAMEPADS; i++) {
if (m_GamepadState[i].mouseEmulationTimer != 0) {
Session::get()->notifyMouseEmulationMode(false);
@@ -234,157 +254,6 @@ void SdlInputHandler::setWindow(SDL_Window *window)
m_Window = window;
}
#ifdef Q_OS_WIN32
LRESULT CALLBACK SdlInputHandler::keyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode < 0 || nCode != HC_ACTION) {
return CallNextHookEx(g_KeyboardHook, nCode, wParam, lParam);
}
bool keyCaptureActive;
SDL_Event event = {};
KBDLLHOOKSTRUCT* hookData = (KBDLLHOOKSTRUCT*)lParam;
switch (hookData->vkCode) {
case VK_LWIN:
event.key.keysym.sym = SDLK_LGUI;
event.key.keysym.scancode = SDL_SCANCODE_LGUI;
break;
case VK_RWIN:
event.key.keysym.sym = SDLK_RGUI;
event.key.keysym.scancode = SDL_SCANCODE_RGUI;
break;
case VK_LMENU:
event.key.keysym.sym = SDLK_LALT;
event.key.keysym.scancode = SDL_SCANCODE_LALT;
break;
case VK_RMENU:
event.key.keysym.sym = SDLK_RALT;
event.key.keysym.scancode = SDL_SCANCODE_RALT;
break;
case VK_LCONTROL:
event.key.keysym.sym = SDLK_LCTRL;
event.key.keysym.scancode = SDL_SCANCODE_LCTRL;
break;
case VK_RCONTROL:
event.key.keysym.sym = SDLK_RCTRL;
event.key.keysym.scancode = SDL_SCANCODE_RCTRL;
break;
default:
// Bail quickly if it's not a key we care about
goto NextHook;
}
// Make sure we're in a state where we actually want to steal this event (and it is safe to do so)
Session* session = Session::get();
if (session == nullptr || session->m_InputHandler == nullptr) {
goto NextHook;
}
keyCaptureActive = session->m_InputHandler->isSystemKeyCaptureActive();
// If this is a key we're going to intercept, create the synthetic SDL event.
// This is necessary because we are also hiding this key event from ourselves too.
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
event.type = SDL_KEYDOWN;
event.key.state = SDL_PRESSED;
}
else {
event.type = SDL_KEYUP;
event.key.state = SDL_RELEASED;
}
// Drop the event if it's a key down and capture is not active.
// For key up, we'll need to do a little more work to determine if we need to send this
// event to SDL in order to keep its internal keyboard modifier state consistent.
if (event.type == SDL_KEYDOWN && !keyCaptureActive) {
goto NextHook;
}
event.key.keysym.mod = SDL_GetModState();
switch (hookData->vkCode) {
case VK_LWIN:
if (event.key.state == SDL_PRESSED) {
event.key.keysym.mod |= KMOD_LGUI;
}
else {
event.key.keysym.mod &= ~KMOD_LGUI;
}
break;
case VK_RWIN:
if (event.key.state == SDL_PRESSED) {
event.key.keysym.mod |= KMOD_RGUI;
}
else {
event.key.keysym.mod &= ~KMOD_RGUI;
}
break;
case VK_LMENU:
if (event.key.state == SDL_PRESSED) {
event.key.keysym.mod |= KMOD_LALT;
}
else {
event.key.keysym.mod &= ~KMOD_LALT;
}
break;
case VK_RMENU:
if (event.key.state == SDL_PRESSED) {
event.key.keysym.mod |= KMOD_RALT;
}
else {
event.key.keysym.mod &= ~KMOD_RALT;
}
break;
case VK_LCONTROL:
if (event.key.state == SDL_PRESSED) {
event.key.keysym.mod |= KMOD_LCTRL;
}
else {
event.key.keysym.mod &= ~KMOD_LCTRL;
}
break;
case VK_RCONTROL:
if (event.key.state == SDL_PRESSED) {
event.key.keysym.mod |= KMOD_RCTRL;
}
else {
event.key.keysym.mod &= ~KMOD_RCTRL;
}
break;
}
// If the modifier state is unchanged and we're not capturing system keys,
// drop the event. If the modifier state is changed, we need to send the
// event to update SDL's state even if system key capture is now inactive
// (due to focus loss, mouse capture toggled off, etc.) otherwise SDL won't
// know that the modifier key has been lifted.
if (event.key.keysym.mod == SDL_GetModState() && !keyCaptureActive) {
goto NextHook;
}
event.key.timestamp = SDL_GetTicks();
event.key.windowID = SDL_GetWindowID(session->m_Window);
// NOTE: event.key.repeat is not populated in this path!
SDL_PushEvent(&event);
// Synchronize SDL's modifier state with the current state.
// SDL won't do this on its own because it will never see the
// WM_KEYUP/WM_KEYDOWN events.
SDL_SetModState((SDL_Keymod)event.key.keysym.mod);
// Eat the event only if key capture is active.
// If capture is not active and we're just resyncing SDL's modifier state,
// we need to ensure the key event is still delivered normally to the
// window in focus.
if (keyCaptureActive) {
return 1;
}
NextHook:
return CallNextHookEx(g_KeyboardHook, nCode, wParam, lParam);
}
#endif
void SdlInputHandler::raiseAllKeys()
{
if (m_KeysDown.isEmpty()) {
@@ -404,20 +273,22 @@ void SdlInputHandler::raiseAllKeys()
void SdlInputHandler::notifyMouseLeave()
{
#ifdef Q_OS_WIN32
// SDL on Windows doesn't send the mouse button up until the mouse re-enters the window
// after leaving it. This breaks some of the Aero snap gestures, so we'll fake it here.
// after leaving it. This breaks some of the Aero snap gestures, so we'll capture it to
// allow us to receive the mouse button up events later.
//
// On macOS and X11, capturing the mouse allows us to receive mouse motion outside the
// window (button up already worked without capture).
if (m_AbsoluteMouseMode && isCaptureActive()) {
// NB: Not using SDL_GetGlobalMouseState() because we want our state not the system's
Uint32 mouseState = SDL_GetMouseState(nullptr, nullptr);
for (Uint32 button = SDL_BUTTON_LEFT; button <= SDL_BUTTON_X2; button++) {
if (mouseState & SDL_BUTTON(button)) {
m_PendingMouseLeaveButtonUp = button;
SDL_CaptureMouse(SDL_TRUE);
break;
}
}
}
#endif
}
void SdlInputHandler::notifyFocusLost()
@@ -445,9 +316,49 @@ bool SdlInputHandler::isCaptureActive()
return m_FakeCaptureActive;
}
void SdlInputHandler::updateKeyboardGrabState()
{
if (m_CaptureSystemKeysMode == StreamingPreferences::CSK_OFF) {
return;
}
bool shouldGrab = isCaptureActive();
Uint32 windowFlags = SDL_GetWindowFlags(m_Window);
if (m_CaptureSystemKeysMode == StreamingPreferences::CSK_FULLSCREEN &&
!(windowFlags & SDL_WINDOW_FULLSCREEN)) {
// Ungrab if it's fullscreen only and we left fullscreen
shouldGrab = false;
}
// Don't close the window on Alt+F4 when keyboard grab is enabled
SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, shouldGrab ? "1" : "0");
if (shouldGrab) {
#if SDL_VERSION_ATLEAST(2, 0, 15)
// On SDL 2.0.15, we can get keyboard-only grab on Win32, X11, and Wayland.
// This does nothing on macOS but it sets the SDL_WINDOW_KEYBOARD_GRABBED flag
// that we look for to see if keyboard capture is enabled. We'll handle macOS
// ourselves below using the private CGSSetGlobalHotKeyOperatingMode() API.
SDL_SetWindowKeyboardGrab(m_Window, SDL_TRUE);
#else
// If we're in full-screen desktop mode and SDL doesn't have keyboard grab yet,
// grab the cursor (will grab the keyboard too on X11).
if (SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN) {
SDL_SetWindowGrab(m_Window, SDL_TRUE);
}
#endif
}
else {
#if SDL_VERSION_ATLEAST(2, 0, 15)
// Allow the keyboard to leave the window
SDL_SetWindowKeyboardGrab(m_Window, SDL_FALSE);
#endif
}
}
bool SdlInputHandler::isSystemKeyCaptureActive()
{
if (!m_CaptureSystemKeysEnabled) {
if (m_CaptureSystemKeysMode == StreamingPreferences::CSK_OFF) {
return false;
}
@@ -456,33 +367,41 @@ bool SdlInputHandler::isSystemKeyCaptureActive()
}
Uint32 windowFlags = SDL_GetWindowFlags(m_Window);
return (windowFlags & SDL_WINDOW_INPUT_FOCUS) &&
(windowFlags & SDL_WINDOW_INPUT_GRABBED) &&
(windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP);
if (!(windowFlags & SDL_WINDOW_INPUT_FOCUS)
#if SDL_VERSION_ATLEAST(2, 0, 15)
|| !(windowFlags & SDL_WINDOW_KEYBOARD_GRABBED)
#else
|| !(windowFlags & SDL_WINDOW_INPUT_GRABBED)
#endif
)
{
return false;
}
if (m_CaptureSystemKeysMode == StreamingPreferences::CSK_FULLSCREEN &&
!(windowFlags & SDL_WINDOW_FULLSCREEN)) {
return false;
}
return true;
}
void SdlInputHandler::setCaptureActive(bool active)
{
if (active) {
// If we're in full-screen exclusive mode, grab the cursor so it can't accidentally leave our window.
// If we're in full-screen desktop mode but system key capture is enabled, also grab the cursor (will grab the keyboard too on X11).
if (((SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0 && m_CaptureSystemKeysEnabled) ||
(SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
if ((SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
#if SDL_VERSION_ATLEAST(2, 0, 15)
SDL_SetWindowMouseGrab(m_Window, SDL_TRUE);
#else
SDL_SetWindowGrab(m_Window, SDL_TRUE);
}
if (!m_AbsoluteMouseMode) {
// If our window is occluded when mouse is captured, the mouse may
// get stuck on top of the occluding window and not be properly
// captured. We can avoid this by raising our window before we
// capture the mouse.
SDL_RaiseWindow(m_Window);
#endif
}
// If we're in relative mode, try to activate SDL's relative mouse mode
if (m_AbsoluteMouseMode || SDL_SetRelativeMouseMode(SDL_TRUE) < 0) {
// Relative mouse mode didn't work or was disabled, so we'll just hide the cursor
SDL_ShowCursor(SDL_DISABLE);
SDL_ShowCursor(m_MouseCursorCapturedVisibilityState);
m_FakeCaptureActive = true;
}
@@ -515,9 +434,17 @@ void SdlInputHandler::setCaptureActive(bool active)
SDL_SetRelativeMouseMode(SDL_FALSE);
}
#if SDL_VERSION_ATLEAST(2, 0, 15)
// Allow the cursor to leave the bounds of our window again.
SDL_SetWindowMouseGrab(m_Window, SDL_FALSE);
#else
// Allow the cursor to leave the bounds of our window again.
SDL_SetWindowGrab(m_Window, SDL_FALSE);
#endif
}
// Now update the keyboard grab
updateKeyboardGrabState();
}
void SdlInputHandler::handleTouchFingerEvent(SDL_TouchFingerEvent* event)
+28 -12
View File
@@ -5,13 +5,9 @@
#include <SDL.h>
#ifdef Q_OS_WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif
#define SDL_CODE_HIDE_CURSOR 1
#define SDL_CODE_SHOW_CURSOR 2
#define SDL_CODE_UNCAPTURE_MOUSE 3
struct GamepadState {
SDL_GameController* controller;
@@ -69,6 +65,8 @@ public:
void handleJoystickArrivalEvent(SDL_JoyDeviceEvent* event);
void sendText(QString& string);
void rumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor);
void handleTouchFingerEvent(SDL_TouchFingerEvent* event);
@@ -93,10 +91,24 @@ public:
void flushMousePositionUpdate();
void updateKeyboardGrabState();
static
QString getUnmappedGamepads();
private:
enum KeyCombo {
KeyComboQuit,
KeyComboUngrabInput,
KeyComboToggleFullScreen,
KeyComboToggleStatsOverlay,
KeyComboToggleMouseMode,
KeyComboToggleCursorHide,
KeyComboToggleMinimize,
KeyComboPasteText,
KeyComboMax
};
GamepadState*
findStateForGamepad(SDL_JoystickID id);
@@ -106,6 +118,8 @@ private:
void handleRelativeFingerEvent(SDL_TouchFingerEvent* event);
void performSpecialKeyCombo(KeyCombo combo);
static
Uint32 longPressTimerCallback(Uint32 interval, void* param);
@@ -124,11 +138,6 @@ private:
static
Uint32 dragTimerCallback(Uint32 interval, void* param);
#ifdef Q_OS_WIN32
static
LRESULT CALLBACK keyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam);
#endif
SDL_Window* m_Window;
bool m_MultiController;
bool m_GamepadMouse;
@@ -154,7 +163,15 @@ private:
bool m_FakeCaptureActive;
QString m_OldIgnoreDevices;
QString m_OldIgnoreDevicesExcept;
bool m_CaptureSystemKeysEnabled;
StreamingPreferences::CaptureSysKeysMode m_CaptureSystemKeysMode;
int m_MouseCursorCapturedVisibilityState;
struct {
KeyCombo keyCombo;
SDL_Keycode keyCode;
SDL_Scancode scanCode;
bool enabled;
} m_SpecialKeyCombos[KeyComboMax];
SDL_TouchFingerEvent m_LastTouchDownEvent;
SDL_TouchFingerEvent m_LastTouchUpEvent;
@@ -163,7 +180,6 @@ private:
int m_StreamHeight;
bool m_AbsoluteMouseMode;
bool m_AbsoluteTouchMode;
Uint32 m_PendingMouseLeaveButtonUp;
SDL_TouchFingerEvent m_TouchDownEvent[MAX_FINGERS];
SDL_TimerID m_LeftButtonReleaseTimer;
+134 -161
View File
@@ -3,8 +3,6 @@
#include <Limelight.h>
#include <SDL.h>
#include <QGuiApplication>
#define VK_0 0x30
#define VK_A 0x41
@@ -15,11 +13,135 @@
#define VK_NUMPAD0 0x60
#endif
void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
{
switch (combo) {
case KeyComboQuit:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected quit key combo");
// Push a quit event to the main loop
SDL_Event event;
event.type = SDL_QUIT;
event.quit.timestamp = SDL_GetTicks();
SDL_PushEvent(&event);
break;
case KeyComboUngrabInput:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse capture toggle combo");
// Stop handling future input
setCaptureActive(!isCaptureActive());
// Force raise all keys to ensure they aren't stuck,
// since we won't get their key up events.
raiseAllKeys();
break;
case KeyComboToggleFullScreen:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected full-screen toggle combo");
Session::s_ActiveSession->toggleFullscreen();
// Force raise all keys just be safe across this full-screen/windowed
// transition just in case key events get lost.
raiseAllKeys();
break;
case KeyComboToggleStatsOverlay:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected stats toggle combo");
// Toggle the stats overlay
Session::get()->getOverlayManager().setOverlayState(Overlay::OverlayDebug,
!Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebug));
break;
case KeyComboToggleMouseMode:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse mode toggle combo");
// Uncapture input
setCaptureActive(false);
// Toggle mouse mode
m_AbsoluteMouseMode = !m_AbsoluteMouseMode;
// Recapture input
setCaptureActive(true);
break;
case KeyComboToggleCursorHide:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected show mouse combo");
if (!SDL_GetRelativeMouseMode()) {
m_MouseCursorCapturedVisibilityState = !m_MouseCursorCapturedVisibilityState;
SDL_ShowCursor(m_MouseCursorCapturedVisibilityState);
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Cursor can only be shown in remote desktop mouse mode");
}
break;
case KeyComboToggleMinimize:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected minimize combo");
SDL_MinimizeWindow(m_Window);
break;
case KeyComboPasteText:
{
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected type clipboard text combo");
// Force raise all keys to ensure that none of them interfere
// with the text we're going to type.
raiseAllKeys();
char* text;
if (SDL_HasClipboardText() && (text = SDL_GetClipboardText()) != nullptr) {
// Sending both CR and LF will lead to two newlines in the destination for
// each newline in the source, so we fix up any CRLFs into just a single LF.
for (char* c = text; *c != 0; c++) {
if (*c == '\r' && *(c + 1) == '\n') {
// We're using strlen() rather than strlen() - 1 since we need to add 1
// to copy the null terminator which is not included in strlen()'s count.
memmove(c, c + 1, strlen(c));
}
}
// Send this text to the PC
LiSendUtf8TextEvent(text, strlen(text));
// SDL_GetClipboardText() allocates, so we must free
SDL_free((void*)text);
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"No text in clipboard to paste!");
}
break;
}
default:
Q_UNREACHABLE();
}
}
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
{
short keyCode;
char modifiers;
if (event->repeat) {
// Ignore repeat key down events
SDL_assert(event->state == SDL_PRESSED);
return;
}
// Check for our special key combos
if ((event->state == SDL_PRESSED) &&
(event->keysym.mod & KMOD_CTRL) &&
@@ -36,168 +158,19 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
// where the SDLK for one shortcut collides with
// the scancode of another.
// Check for quit combo (Ctrl+Alt+Shift+Q)
if (event->keysym.sym == SDLK_q) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected quit key combo (SDLK)");
// Push a quit event to the main loop
SDL_Event event;
event.type = SDL_QUIT;
event.quit.timestamp = SDL_GetTicks();
SDL_PushEvent(&event);
return;
}
// Check for the unbind combo (Ctrl+Alt+Shift+Z) unless on EGLFS which has no window manager
else if (event->keysym.sym == SDLK_z && QGuiApplication::platformName() != "eglfs") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse capture toggle combo (SDLK)");
// Stop handling future input
setCaptureActive(!isCaptureActive());
// Force raise all keys to ensure they aren't stuck,
// since we won't get their key up events.
raiseAllKeys();
return;
}
// Check for the mouse mode combo (Ctrl+Alt+Shift+M) unless on EGLFS which has no window manager
else if (event->keysym.sym == SDLK_m && QGuiApplication::platformName() != "eglfs") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse mode toggle combo (SDLK)");
// Uncapture input
setCaptureActive(false);
// Toggle mouse mode
m_AbsoluteMouseMode = !m_AbsoluteMouseMode;
// Recapture input
setCaptureActive(true);
return;
}
else if (event->keysym.sym == SDLK_x && QGuiApplication::platformName() != "eglfs") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected full-screen toggle combo (SDLK)");
Session::s_ActiveSession->toggleFullscreen();
// Force raise all keys just be safe across this full-screen/windowed
// transition just in case key events get lost.
raiseAllKeys();
return;
}
// Check for overlay combo (Ctrl+Alt+Shift+S)
else if (event->keysym.sym == SDLK_s) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected stats toggle combo (SDLK)");
// Toggle the stats overlay
Session::get()->getOverlayManager().setOverlayState(Overlay::OverlayDebug,
!Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebug));
// Force raise all keys just be safe across this full-screen/windowed
// transition just in case key events get lost.
raiseAllKeys();
return;
}
// Check for mouse show combo (Ctrl+Alt+Shift+C)
else if (event->keysym.sym == SDLK_c) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected show mouse combo (SDLK)");
if (!SDL_GetRelativeMouseMode()) {
SDL_ShowCursor(!SDL_ShowCursor(SDL_QUERY));
for (int i = 0; i < KeyComboMax; i++) {
if (m_SpecialKeyCombos[i].enabled && event->keysym.sym == m_SpecialKeyCombos[i].keyCode) {
performSpecialKeyCombo(m_SpecialKeyCombos[i].keyCombo);
return;
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Cursor can only be shown in remote desktop mouse mode");
}
for (int i = 0; i < KeyComboMax; i++) {
if (m_SpecialKeyCombos[i].enabled && event->keysym.scancode == m_SpecialKeyCombos[i].scanCode) {
performSpecialKeyCombo(m_SpecialKeyCombos[i].keyCombo);
return;
}
return;
}
// Check for quit combo (Ctrl+Alt+Shift+Q)
else if (event->keysym.scancode == SDL_SCANCODE_Q) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected quit key combo (scancode)");
// Push a quit event to the main loop
SDL_Event event;
event.type = SDL_QUIT;
event.quit.timestamp = SDL_GetTicks();
SDL_PushEvent(&event);
return;
}
// Check for the unbind combo (Ctrl+Alt+Shift+Z)
else if (event->keysym.scancode == SDL_SCANCODE_Z && QGuiApplication::platformName() != "eglfs") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse capture toggle combo (scancode)");
// Stop handling future input
setCaptureActive(!isCaptureActive());
// Force raise all keys to ensure they aren't stuck,
// since we won't get their key up events.
raiseAllKeys();
return;
}
// Check for the full-screen combo (Ctrl+Alt+Shift+X) unless on EGLFS which has no window manager
else if (event->keysym.scancode == SDL_SCANCODE_X && QGuiApplication::platformName() != "eglfs") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected full-screen toggle combo (scancode)");
Session::s_ActiveSession->toggleFullscreen();
// Force raise all keys just be safe across this full-screen/windowed
// transition just in case key events get lost.
raiseAllKeys();
return;
}
// Check for the mouse mode toggle combo (Ctrl+Alt+Shift+M) unless on EGLFS which has no window manager
else if (event->keysym.scancode == SDL_SCANCODE_M && QGuiApplication::platformName() != "eglfs") {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse mode toggle combo (scancode)");
// Uncapture input
setCaptureActive(false);
// Toggle mouse mode
m_AbsoluteMouseMode = !m_AbsoluteMouseMode;
// Recapture input
setCaptureActive(true);
return;
}
else if (event->keysym.scancode == SDL_SCANCODE_S) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected stats toggle combo (scancode)");
// Toggle the stats overlay
Session::get()->getOverlayManager().setOverlayState(Overlay::OverlayDebug,
!Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebug));
// Force raise all keys just be safe across this full-screen/windowed
// transition just in case key events get lost.
raiseAllKeys();
return;
}
// Check for mouse show combo (Ctrl+Alt+Shift+C)
else if (event->keysym.sym == SDL_SCANCODE_C) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected show mouse combo (scancode)");
if (!SDL_GetRelativeMouseMode()) {
SDL_ShowCursor(!SDL_ShowCursor(SDL_QUERY));
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Cursor can only be shown in remote desktop mouse mode");
}
return;
}
}
if (event->repeat) {
// Ignore repeat key down events
SDL_assert(event->state == SDL_PRESSED);
return;
}
// Set modifier flags
+22 -30
View File
@@ -133,7 +133,15 @@ void SdlInputHandler::flushMousePositionUpdate()
// c) a mouse button is still down from before the cursor left the video region (to allow smooth dragging)
Uint32 buttonState = SDL_GetMouseState(nullptr, nullptr);
if (buttonState == 0) {
m_PendingMouseButtonsAllUpOnVideoRegionLeave = false;
if (m_PendingMouseButtonsAllUpOnVideoRegionLeave) {
// Tell the main thread to stop capturing the mouse now
SDL_Event event;
event.type = SDL_USEREVENT;
event.user.code = SDL_CODE_UNCAPTURE_MOUSE;
SDL_PushEvent(&event);
m_PendingMouseButtonsAllUpOnVideoRegionLeave = false;
}
}
if (mouseInVideoRegion || m_MouseWasInVideoRegion || m_PendingMouseButtonsAllUpOnVideoRegionLeave) {
LiSendMousePositionEvent(x, y, dst.w, dst.h);
@@ -146,7 +154,8 @@ void SdlInputHandler::flushMousePositionUpdate()
// executes.
SDL_Event event;
event.type = SDL_USEREVENT;
event.user.code = mouseInVideoRegion ? SDL_CODE_HIDE_CURSOR : SDL_CODE_SHOW_CURSOR;
event.user.code = (mouseInVideoRegion && m_MouseCursorCapturedVisibilityState == SDL_DISABLE) ?
SDL_CODE_HIDE_CURSOR : SDL_CODE_SHOW_CURSOR;
SDL_PushEvent(&event);
if (!mouseInVideoRegion && buttonState != 0) {
@@ -203,6 +212,16 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
}
}
#if SDL_VERSION_ATLEAST(2, 0, 18)
if (event->preciseY != 0.0f) {
// Invert the scroll direction if needed
if (m_ReverseScrollDirection) {
event->preciseY = -event->preciseY;
}
LiSendHighResScrollEvent((short)(event->preciseY * 120)); // WHEEL_DELTA
}
#else
if (event->y != 0) {
// Invert the scroll direction if needed
if (m_ReverseScrollDirection) {
@@ -211,6 +230,7 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
LiSendScrollEvent((signed char)event->y);
}
#endif
}
bool SdlInputHandler::isMouseInVideoRegion(int mouseX, int mouseY, int windowWidth, int windowHeight)
@@ -250,33 +270,5 @@ Uint32 SdlInputHandler::mouseMoveTimerCallback(Uint32 interval, void *param)
// Send mouse position updates if applicable
me->flushMousePositionUpdate();
#ifdef Q_OS_WIN32
// See comment in SdlInputHandler::notifyMouseLeave()
if (me->m_AbsoluteMouseMode && me->m_PendingMouseLeaveButtonUp != 0 && me->isCaptureActive()) {
int mouseX, mouseY;
int windowX, windowY;
Uint32 mouseState = SDL_GetGlobalMouseState(&mouseX, &mouseY);
SDL_GetWindowPosition(me->m_Window, &windowX, &windowY);
// If the button is now up, send the synthetic mouse up event
if ((mouseState & SDL_BUTTON(me->m_PendingMouseLeaveButtonUp)) == 0) {
SDL_Event event;
event.button.type = SDL_MOUSEBUTTONUP;
event.button.timestamp = SDL_GetTicks();
event.button.windowID = SDL_GetWindowID(me->m_Window);
event.button.which = 0;
event.button.button = me->m_PendingMouseLeaveButtonUp;
event.button.state = SDL_RELEASED;
event.button.clicks = 1;
event.button.x = mouseX - windowX;
event.button.y = mouseY - windowY;
SDL_PushEvent(&event);
me->m_PendingMouseLeaveButtonUp = 0;
}
}
#endif
return interval;
}
+273 -96
View File
@@ -37,14 +37,6 @@
#define CONN_TEST_SERVER "qt.conntest.moonlight-stream.org"
// Running the connection process asynchronously seems to reliably
// cause a crash in QSGRenderThread on Wayland and strange crashes
// elsewhere. Until these are figured out, avoid the async connect
// thread on Linux and BSDs.
#if !defined(Q_OS_UNIX) || defined(Q_OS_DARWIN)
#define USE_ASYNC_CONNECT_THREAD 1
#endif
CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
Session::clStageStarting,
nullptr,
@@ -65,11 +57,6 @@ void Session::clStageStarting(int stage)
// which happens to be the main thread, so it's cool to interact
// with the GUI in these callbacks.
emit s_ActiveSession->stageStarting(QString::fromLocal8Bit(LiGetStageName(stage)));
#ifndef USE_ASYNC_CONNECT_THREAD
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
#endif
}
void Session::clStageFailed(int stage, int errorCode)
@@ -81,11 +68,6 @@ void Session::clStageFailed(int stage, int errorCode)
char failingPorts[128];
LiStringifyPortFlags(portFlags, ", ", failingPorts, sizeof(failingPorts));
emit s_ActiveSession->stageFailed(QString::fromLocal8Bit(LiGetStageName(stage)), errorCode, QString(failingPorts));
#ifndef USE_ASYNC_CONNECT_THREAD
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
#endif
}
void Session::clConnectionTerminated(int errorCode)
@@ -199,6 +181,11 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
{
DECODER_PARAMETERS params;
// We should never have vsync enabled for test-mode.
// It introduces unnecessary delay for renderers that may
// block while waiting for a backbuffer swap.
SDL_assert(!enableVsync || !testOnly);
params.width = width;
params.height = height;
params.frameRate = frameRate;
@@ -277,14 +264,12 @@ int Session::drSubmitDecodeUnit(PDECODE_UNIT du)
// safely return DR_OK and wait for m_NeedsIdr to be set by
// the decoder reinitialization code.
if (SDL_AtomicTryLock(&s_ActiveSession->m_DecoderLock)) {
if (s_ActiveSession->m_NeedsIdr) {
// If we reset our decoder, we'll need to request an IDR frame
s_ActiveSession->m_NeedsIdr = false;
SDL_AtomicUnlock(&s_ActiveSession->m_DecoderLock);
return DR_NEED_IDR;
}
if (s_ActiveSession->getAndClearPendingIdrFrameStatus()) {
// If we reset our decoder, we'll need to request an IDR frame
return DR_NEED_IDR;
}
if (SDL_AtomicTryLock(&s_ActiveSession->m_DecoderLock)) {
IVideoDecoder* decoder = s_ActiveSession->m_VideoDecoder;
if (decoder != nullptr) {
int ret = decoder->submitDecodeUnit(du);
@@ -310,7 +295,7 @@ void Session::getDecoderInfo(SDL_Window* window,
if (!chooseDecoder(StreamingPreferences::VDS_AUTO,
window, VIDEO_FORMAT_H264, 1920, 1080, 60,
true, false, true, decoder)) {
false, false, true, decoder)) {
isHardwareAccelerated = isFullScreenOnly = false;
return;
}
@@ -320,6 +305,23 @@ void Session::getDecoderInfo(SDL_Window* window,
maxResolution = decoder->getDecoderMaxResolution();
delete decoder;
// If we don't get back a hardware H.264 decoder, see if we have a hardware
// HEVC decoder. This can be the case on the Raspberry Pi with Full KMS
// when not running in X11. Everything since Maxwell in 2014 can encode HEVC,
// so we probably don't need to be concerned about a lack of fast H.264
// decoding enough to bug the user about it every launch.
if (!isHardwareAccelerated) {
if (chooseDecoder(StreamingPreferences::VDS_FORCE_HARDWARE,
window, VIDEO_FORMAT_H265, 1920, 1080, 60,
false, false, true, decoder)) {
isHardwareAccelerated = decoder->isHardwareAccelerated();
isFullScreenOnly = decoder->isAlwaysFullScreen();
maxResolution = decoder->getDecoderMaxResolution();
delete decoder;
}
}
}
bool Session::isHardwareDecodeAvailable(SDL_Window* window,
@@ -328,7 +330,7 @@ bool Session::isHardwareDecodeAvailable(SDL_Window* window,
{
IVideoDecoder* decoder;
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, false, true, decoder)) {
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, false, false, true, decoder)) {
return false;
}
@@ -350,14 +352,25 @@ bool Session::populateDecoderProperties(SDL_Window* window)
m_StreamConfig.width,
m_StreamConfig.height,
m_StreamConfig.fps,
true, false, true, decoder)) {
false, false, true, decoder)) {
return false;
}
m_VideoCallbacks.capabilities = decoder->getDecoderCapabilities();
if (m_VideoCallbacks.capabilities & CAPABILITY_PULL_RENDERER) {
// It is an error to pass a push callback when in pull mode
m_VideoCallbacks.submitDecodeUnit = nullptr;
}
else {
m_VideoCallbacks.submitDecodeUnit = drSubmitDecodeUnit;
}
m_StreamConfig.colorSpace = decoder->getDecoderColorspace();
if (decoder->isAlwaysFullScreen()) {
m_IsFullScreen = true;
}
delete decoder;
return true;
@@ -365,12 +378,12 @@ bool Session::populateDecoderProperties(SDL_Window* window)
Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences)
: m_Preferences(preferences ? preferences : new StreamingPreferences(this)),
m_IsFullScreen(m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED || !WMUtils::isRunningDesktopEnvironment()),
m_Computer(computer),
m_App(app),
m_Window(nullptr),
m_VideoDecoder(nullptr),
m_DecoderLock(0),
m_NeedsIdr(false),
m_AudioDisabled(false),
m_AudioMuted(false),
m_DisplayOriginX(0),
@@ -388,6 +401,7 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
m_AudioSampleCount(0),
m_DropAudioEndTime(0)
{
SDL_AtomicSet(&m_NeedsIdr, 0);
}
// NB: This may not get destroyed for a long time! Don't put any vital cleanup here.
@@ -432,7 +446,6 @@ bool Session::initialize()
LiInitializeVideoCallbacks(&m_VideoCallbacks);
m_VideoCallbacks.setup = drSetup;
m_VideoCallbacks.submitDecodeUnit = drSubmitDecodeUnit;
LiInitializeStreamConfiguration(&m_StreamConfig);
m_StreamConfig.width = m_Preferences->width;
@@ -441,6 +454,12 @@ bool Session::initialize()
m_StreamConfig.bitrate = m_Preferences->bitrateKbps;
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
#ifndef STEAM_LINK
// Enable audio encryption as long as we're not on Steam Link.
// That hardware can hardly handle Opus decoding at all.
m_StreamConfig.encryptionFlags = ENCFLG_AUDIO;
#endif
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Video bitrate: %d kbps",
m_StreamConfig.bitrate);
@@ -524,22 +543,17 @@ bool Session::initialize()
{
default:
case StreamingPreferences::WM_FULLSCREEN_DESKTOP:
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN_DESKTOP;
break;
// Only use full-screen desktop mode if we're running a desktop environment
if (WMUtils::isRunningDesktopEnvironment()) {
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN_DESKTOP;
break;
}
// Fall-through
case StreamingPreferences::WM_FULLSCREEN:
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
break;
}
#ifdef Q_OS_DARWIN
// macOS behaves as if we're capturing system keys when
// we enter "real" full-screen mode, so use standard
// full-screen as our full-screen flag when capturing keys.
if (m_Preferences->captureSysKeys) {
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
}
#endif
#if !SDL_VERSION_ATLEAST(2, 0, 11)
// HACK: Using a full-screen window breaks mouse capture on the Pi's LXDE
// GUI environment. Force the session to use windowed mode (which won't
@@ -581,10 +595,13 @@ void Session::emitLaunchWarning(QString text)
// to allow it to transition off the screen before continuing.
uint32_t start = SDL_GetTicks();
while (!SDL_TICKS_PASSED(SDL_GetTicks(), start + 3500)) {
// Pump the UI loop while we wait
SDL_Delay(5);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
if (!m_ThreadedExec) {
// Pump the UI loop while we wait if we're on the main thread
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
}
}
}
@@ -592,6 +609,11 @@ bool Session::validateLaunch(SDL_Window* testWindow)
{
QStringList warningList;
if (!m_Computer->isSupportedServerVersion) {
emit displayLaunchError(tr("The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.").arg(m_Computer->name));
return false;
}
if (m_Preferences->absoluteMouseMode && !m_App.isAppCollectorGame) {
emitLaunchWarning(tr("Your selection to enable remote desktop mouse mode may cause problems in games."));
}
@@ -618,32 +640,53 @@ bool Session::validateLaunch(SDL_Window* testWindow)
bool hevcForced = m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR;
if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Already checked in initialize()
if (m_Computer->maxLumaPixelsHEVC == 0) {
if (hevcForced) {
emitLaunchWarning(tr("Your host PC GPU doesn't support HEVC. "
"A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming."));
}
// Moonlight-common-c will handle this case already, but we want
// to set this explicitly here so we can do our hardware acceleration
// check below.
m_StreamConfig.supportsHevc = false;
}
else if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
hevcForced && // Auto VCC is already checked in initialize()
!isHardwareDecodeAvailable(testWindow,
m_Preferences->videoDecoderSelection,
VIDEO_FORMAT_H265,
m_StreamConfig.width,
m_StreamConfig.height,
m_StreamConfig.fps)) {
if (hevcForced) {
emitLaunchWarning(tr("Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance."));
emitLaunchWarning(tr("Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance."));
}
}
if (!m_StreamConfig.supportsHevc &&
m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO &&
!isHardwareDecodeAvailable(testWindow,
m_Preferences->videoDecoderSelection,
VIDEO_FORMAT_H264,
m_StreamConfig.width,
m_StreamConfig.height,
m_StreamConfig.fps)) {
if (m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_H264) {
emitLaunchWarning(tr("Using software decoding due to your selection to force H.264 without GPU support. This may cause poor streaming performance."));
}
else {
if (m_Computer->maxLumaPixelsHEVC == 0 &&
isHardwareDecodeAvailable(testWindow,
m_Preferences->videoDecoderSelection,
VIDEO_FORMAT_H265,
m_StreamConfig.width,
m_StreamConfig.height,
m_StreamConfig.fps)) {
emitLaunchWarning(tr("Your host PC and client PC don't support the same video codecs. This may cause poor streaming performance."));
}
else {
emitLaunchWarning(tr("This PC's GPU doesn't support HEVC decoding."));
m_StreamConfig.supportsHevc = false;
}
}
if (hevcForced) {
if (m_Computer->maxLumaPixelsHEVC == 0) {
emitLaunchWarning(tr("Your host PC GPU doesn't support HEVC. "
"A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming."));
// Moonlight-common-c will handle this case already, but we want
// to set this explicitly here so we can do our hardware acceleration
// check below.
m_StreamConfig.supportsHevc = false;
emitLaunchWarning(tr("Your client GPU doesn't support H.264 decoding. This may cause poor streaming performance."));
}
}
}
@@ -776,12 +819,17 @@ private:
emit m_Session->sessionFinished(m_Session->m_PortTestResults);
}
// The video decoder must already be destroyed, since it could
// try to interact with APIs that can only be called between
// LiStartConnection() and LiStopConnection().
SDL_assert(m_Session->m_VideoDecoder == nullptr);
// Finish cleanup of the connection state
LiStopConnection();
// Perform a best-effort app quit
if (shouldQuit) {
NvHTTP http(m_Session->m_Computer->activeAddress, m_Session->m_Computer->serverCert);
NvHTTP http(m_Session->m_Computer);
// Logging is already done inside NvHTTP
try {
@@ -835,7 +883,7 @@ void Session::getWindowDimensions(int& x, int& y,
}
}
fullScreen = (m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED);
fullScreen = m_IsFullScreen;
}
SDL_Rect usableBounds;
@@ -918,6 +966,24 @@ void Session::updateOptimalWindowDisplayMode()
}
}
// For KMSDRM backends where we exclusively own the display, we should opt
// to change the resolution if that allows us to hit the desired frame rate
if (bestMode.refresh_rate == 0 && strcmp(SDL_GetCurrentVideoDriver(), "KMSDRM") == 0) {
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected display mode: %dx%dx%d",
mode.w, mode.h, mode.refresh_rate);
if (mode.w >= m_ActiveVideoWidth && mode.h >= m_ActiveVideoHeight &&
mode.refresh_rate % m_StreamConfig.fps == 0) {
if (mode.refresh_rate > bestMode.refresh_rate) {
bestMode = mode;
}
}
}
}
}
if (bestMode.refresh_rate == 0) {
// We may find no match if the user has moved a 120 FPS
// stream onto a 60 Hz monitor (since no refresh rate can
@@ -944,10 +1010,8 @@ void Session::toggleFullscreen()
bool fullScreen = !(SDL_GetWindowFlags(m_Window) & m_FullScreenFlag);
if (fullScreen) {
if ((m_FullScreenFlag == SDL_WINDOW_FULLSCREEN || m_Preferences->captureSysKeys) && m_InputHandler->isCaptureActive()) {
if (m_FullScreenFlag == SDL_WINDOW_FULLSCREEN && m_InputHandler->isCaptureActive()) {
// Confine the cursor to the window if we're capturing input while transitioning to full screen.
// We also need to grab if we're capturing system keys, because SDL requires window grab to
// capture the keyboard on X11.
SDL_SetWindowGrab(m_Window, SDL_TRUE);
}
@@ -964,6 +1028,9 @@ void Session::toggleFullscreen()
// Reposition the window when the resize is complete
m_PendingWindowedTransition = true;
}
// Input handler might need to start/stop keyboard grab after changing modes
m_InputHandler->updateKeyboardGrabState();
}
void Session::notifyMouseEmulationMode(bool enabled)
@@ -987,7 +1054,10 @@ class AsyncConnectionStartThread : public QThread
public:
AsyncConnectionStartThread(Session* session) :
QThread(nullptr),
m_Session(session) {}
m_Session(session)
{
setObjectName("Async Conn Start");
}
void run() override
{
@@ -1002,17 +1072,7 @@ bool Session::startConnectionAsync()
{
// Wait 1.5 seconds before connecting to let the user
// have time to read any messages present on the segue
#ifdef USE_ASYNC_CONNECT_THREAD
SDL_Delay(1500);
#else
uint32_t start = SDL_GetTicks();
while (!SDL_TICKS_PASSED(SDL_GetTicks(), start + 1500)) {
// Pump the UI loop while we wait since we're not async
SDL_Delay(5);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
}
#endif
// The UI should have ensured the old game was already quit
// if we decide to stream a different game.
@@ -1034,16 +1094,19 @@ bool Session::startConnectionAsync()
}
}
QString rtspSessionUrl;
try {
NvHTTP http(m_Computer->activeAddress, m_Computer->serverCert);
NvHTTP http(m_Computer);
if (m_Computer->currentGameId != 0) {
http.resumeApp(&m_StreamConfig);
http.resumeApp(&m_StreamConfig, rtspSessionUrl);
}
else {
http.launchApp(m_App.id, &m_StreamConfig,
enableGameOptimizations,
m_Preferences->playAudioOnHost,
m_InputHandler->getAttachedGamepadMask());
m_InputHandler->getAttachedGamepadMask(),
rtspSessionUrl);
}
} catch (const GfeHttpResponseException& e) {
emit displayLaunchError(tr("GeForce Experience returned error: %1").arg(e.toQString()));
@@ -1053,7 +1116,7 @@ bool Session::startConnectionAsync()
return false;
}
QByteArray hostnameStr = m_Computer->activeAddress.toLatin1();
QByteArray hostnameStr = m_Computer->activeAddress.address().toLatin1();
QByteArray siAppVersion = m_Computer->appVersion.toLatin1();
SERVER_INFORMATION hostInfo;
@@ -1069,6 +1132,13 @@ bool Session::startConnectionAsync()
hostInfo.serverInfoGfeVersion = siGfeVersion.data();
}
// Older GFE and Sunshine versions didn't have this field
QByteArray rtspSessionUrlStr;
if (!rtspSessionUrl.isEmpty()) {
rtspSessionUrlStr = rtspSessionUrl.toLatin1();
hostInfo.rtspSessionUrl = rtspSessionUrlStr.data();
}
if (m_Preferences->packetSize != 0) {
// Override default packet size and remote streaming detection
// NB: Using STREAM_CFG_AUTO will cap our packet size at 1024 for remote hosts.
@@ -1124,11 +1194,66 @@ void Session::flushWindowEvents()
SDL_PushEvent(&flushEvent);
}
bool Session::getAndClearPendingIdrFrameStatus()
{
return SDL_AtomicSet(&m_NeedsIdr, 0);
}
class ExecThread : public QThread
{
public:
ExecThread(Session* session) :
QThread(nullptr),
m_Session(session)
{
setObjectName("Session Exec");
}
void run() override
{
m_Session->execInternal();
}
Session* m_Session;
};
void Session::exec(int displayOriginX, int displayOriginY)
{
m_DisplayOriginX = displayOriginX;
m_DisplayOriginY = displayOriginY;
// Use a separate thread for the streaming session on X11 or Wayland
// to ensure we don't stomp on Qt's GL context. This breaks when using
// the Qt EGLFS backend, so we will restrict this to X11
m_ThreadedExec = WMUtils::isRunningX11() || WMUtils::isRunningWayland();
if (m_ThreadedExec) {
// Run the streaming session on a separate thread for Linux/BSD
ExecThread execThread(this);
execThread.start();
// Until the SDL streaming window is created, we should continue
// to update the Qt UI to allow warning messages to display and
// make sure that the Qt window can hide itself.
while (!execThread.wait(10) && m_Window == nullptr) {
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
}
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
// SDL is in charge now. Wait until the streaming thread exits
// to further update the Qt window.
execThread.wait();
}
else {
// Run the streaming session on the main thread for Windows and macOS
execInternal();
}
}
void Session::execInternal()
{
// Complete initialization in this deferred context to avoid
// calling expensive functions in the constructor (during the
// process of loading the StreamSegue).
@@ -1152,22 +1277,26 @@ void Session::exec(int displayOriginX, int displayOriginY)
m_StreamConfig.width,
m_StreamConfig.height);
// Kick off the async connection thread while we sit here and pump the event loop
AsyncConnectionStartThread asyncConnThread(this);
#ifdef USE_ASYNC_CONNECT_THREAD
asyncConnThread.start();
while (!asyncConnThread.wait(10)) {
if (!m_ThreadedExec) {
// Kick off the async connection thread while we sit here and pump the event loop
asyncConnThread.start();
while (!asyncConnThread.wait(10)) {
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
}
// Pump the event loop one last time to ensure we pick up any events from
// the thread that happened while it was in the final successful QThread::wait().
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
}
#else
asyncConnThread.run();
#endif
// Pump the event loop one last time to ensure we pick up any events from
// the thread that happened while it was in the final successful QThread::wait().
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::sendPostedEvents();
else {
// We're already in a separate thread so run the connection operations
// synchronously and don't pump the event loop. The main thread is already
// pumping the event loop for us.
asyncConnThread.run();
}
// If the connection failed, clean up and abort the connection.
if (!m_AsyncConnectionSuccess) {
@@ -1244,7 +1373,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
// For non-full screen windows, call getWindowDimensions()
// again after creating a window to allow it to account
// for window chrome size.
if (m_Preferences->windowMode == StreamingPreferences::WM_WINDOWED) {
if (!m_IsFullScreen) {
getWindowDimensions(x, y, width, height);
// We must set the size before the position because centering
@@ -1266,6 +1395,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
bool needsFirstEnterCapture = false;
bool needsPostDecoderCreationCapture = false;
bool ignoreDuplicateResizes = false;
// HACK: For Wayland, we wait until we get the first SDL_WINDOWEVENT_ENTER
// event where it seems to work consistently on GNOME. For other platforms,
@@ -1274,6 +1404,13 @@ void Session::exec(int displayOriginX, int displayOriginY)
if (strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) {
// Native Wayland: Capture on SDL_WINDOWEVENT_ENTER
needsFirstEnterCapture = true;
// As of SDL 2.0.20, we get duplicate SDL_WINDOWEVENT_SIZE_CHANGED events
// when focus is lost and gained under GNOME Wayland. This causes us to
// recreate our renderer needlessly. This is fixed in SDL 2.0.21.
#if !SDL_VERSION_ATLEAST(2, 0, 21)
ignoreDuplicateResizes = true;
#endif
}
else {
// X11/XWayland: Capture after decoder creation
@@ -1311,7 +1448,23 @@ void Session::exec(int displayOriginX, int displayOriginY)
// Hijack this thread to be the SDL main thread. We have to do this
// because we want to suspend all Qt processing until the stream is over.
SDL_Event event;
Sint32 lastResizeW = -1, lastResizeH = -1;
for (;;) {
#if SDL_VERSION_ATLEAST(2, 0, 18) && !defined(STEAM_LINK)
// SDL 2.0.18 has a proper wait event implementation that uses platform
// support to block on events rather than polling on Windows, macOS, X11,
// and Wayland. It will fall back to 1 ms polling if a joystick is
// connected, so we don't use it for STEAM_LINK to ensure we only poll
// every 10 ms.
//
// NB: This behavior was introduced in SDL 2.0.16, but had a few critical
// issues that could cause indefinite timeouts, delayed joystick detection,
// and other problems.
if (!SDL_WaitEventTimeout(&event, 1000)) {
presence.runCallbacks();
continue;
}
#else
// We explicitly use SDL_PollEvent() and SDL_Delay() because
// SDL_WaitEvent() has an internal SDL_Delay(10) inside which
// blocks this thread too long for high polling rate mice and high
@@ -1327,6 +1480,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
presence.runCallbacks();
continue;
}
#endif
switch (event.type) {
case SDL_QUIT:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
@@ -1344,6 +1498,9 @@ void Session::exec(int displayOriginX, int displayOriginY)
case SDL_CODE_SHOW_CURSOR:
SDL_ShowCursor(SDL_ENABLE);
break;
case SDL_CODE_UNCAPTURE_MOUSE:
SDL_CaptureMouse(SDL_FALSE);
break;
case SDL_CODE_FLUSH_WINDOW_EVENT_BARRIER:
m_FlushingWindowEventsRef--;
break;
@@ -1371,6 +1528,8 @@ void Session::exec(int displayOriginX, int displayOriginY)
break;
}
presence.runCallbacks();
// Capture the mouse on SDL_WINDOWEVENT_ENTER if needed
if (needsFirstEnterCapture && event.window.event == SDL_WINDOWEVENT_ENTER) {
m_InputHandler->setCaptureActive(true);
@@ -1413,6 +1572,19 @@ void Session::exec(int displayOriginX, int displayOriginY)
break;
}
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
if (ignoreDuplicateResizes && lastResizeW == event.window.data1 && lastResizeH == event.window.data2) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Dropping duplicate size changed event: %d %d",
event.window.data1,
event.window.data2);
break;
}
lastResizeW = event.window.data1;
lastResizeH = event.window.data2;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Recreating renderer for window event: %d (%d %d)",
event.window.event,
@@ -1483,17 +1655,19 @@ void Session::exec(int displayOriginX, int displayOriginY)
}
// Request an IDR frame to complete the reset
m_NeedsIdr = true;
SDL_AtomicSet(&m_NeedsIdr, 1);
SDL_AtomicUnlock(&m_DecoderLock);
break;
case SDL_KEYUP:
case SDL_KEYDOWN:
presence.runCallbacks();
m_InputHandler->handleKeyEvent(&event.key);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
presence.runCallbacks();
m_InputHandler->handleMouseButtonEvent(&event.button);
break;
case SDL_MOUSEMOTION:
@@ -1507,6 +1681,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
presence.runCallbacks();
m_InputHandler->handleControllerButtonEvent(&event.cbutton);
break;
case SDL_CONTROLLERDEVICEADDED:
@@ -1547,6 +1722,8 @@ DispatchDeferredCleanup:
SDL_AtomicUnlock(&m_InputHandlerLock);
// Destroy the decoder, since this must be done on the main thread
// NB: This must happen before LiStopConnection() for pull-based
// decoders.
SDL_AtomicLock(&m_DecoderLock);
delete m_VideoDecoder;
m_VideoDecoder = nullptr;
+8 -1
View File
@@ -17,6 +17,7 @@ class Session : public QObject
friend class SdlInputHandler;
friend class DeferredSessionCleanupTask;
friend class AsyncConnectionStartThread;
friend class ExecThread;
public:
explicit Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences = nullptr);
@@ -41,6 +42,8 @@ public:
void flushWindowEvents();
bool getAndClearPendingIdrFrameStatus();
signals:
void stageStarting(QString stage);
@@ -57,6 +60,8 @@ signals:
void sessionFinished(int portTestResult);
private:
void execInternal();
bool initialize();
bool startConnectionAsync();
@@ -133,6 +138,7 @@ private:
int drSubmitDecodeUnit(PDECODE_UNIT du);
StreamingPreferences* m_Preferences;
bool m_IsFullScreen;
STREAM_CONFIGURATION m_StreamConfig;
DECODER_RENDERER_CALLBACKS m_VideoCallbacks;
AUDIO_RENDERER_CALLBACKS m_AudioCallbacks;
@@ -141,12 +147,13 @@ private:
SDL_Window* m_Window;
IVideoDecoder* m_VideoDecoder;
SDL_SpinLock m_DecoderLock;
bool m_NeedsIdr;
SDL_atomic_t m_NeedsIdr;
bool m_AudioDisabled;
bool m_AudioMuted;
Uint32 m_FullScreenFlag;
int m_DisplayOriginX;
int m_DisplayOriginY;
bool m_ThreadedExec;
bool m_PendingWindowedTransition;
bool m_UnexpectedTermination;
SdlInputHandler* m_InputHandler;
+2
View File
@@ -19,6 +19,8 @@ typedef struct _VIDEO_STATS {
uint32_t totalDecodeTime;
uint32_t totalPacerTime;
uint32_t totalRenderTime;
uint32_t lastRtt;
uint32_t lastRttVariance;
float totalFps;
float receivedFps;
float decodedFps;
@@ -1,5 +1,7 @@
#include "cuda.h"
#include <SDL_opengl.h>
CUDARenderer::CUDARenderer()
: m_HwContext(nullptr)
{
@@ -55,3 +57,157 @@ bool CUDARenderer::isDirectRenderingSupported()
return false;
}
CUDAGLInteropHelper::CUDAGLInteropHelper(AVHWDeviceContext* context)
: m_Funcs(nullptr),
m_Context((AVCUDADeviceContext*)context->hwctx)
{
memset(m_Resources, 0, sizeof(m_Resources));
// One-time init of CUDA library
cuda_load_functions(&m_Funcs, nullptr);
if (m_Funcs == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize CUDA library");
return;
}
}
CUDAGLInteropHelper::~CUDAGLInteropHelper()
{
unregisterTextures();
if (m_Funcs != nullptr) {
cuda_free_functions(&m_Funcs);
}
}
bool CUDAGLInteropHelper::registerBoundTextures()
{
int err;
if (m_Funcs == nullptr) {
// Already logged in constructor
return false;
}
// Push FFmpeg's CUDA context to use for our CUDA operations
err = m_Funcs->cuCtxPushCurrent(m_Context->cuda_ctx);
if (err != CUDA_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuCtxPushCurrent() failed: %d", err);
return false;
}
// Register each plane as a separate resource
for (int i = 0; i < NV12_PLANES; i++) {
GLint tex;
// Get the ID of this plane's texture
glActiveTexture(GL_TEXTURE0 + i);
glGetIntegerv(GL_TEXTURE_BINDING_2D, &tex);
// Register it with CUDA
err = m_Funcs->cuGraphicsGLRegisterImage(&m_Resources[i], tex, GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD);
if (err != CUDA_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuGraphicsGLRegisterImage() failed: %d", err);
m_Resources[i] = 0;
unregisterTextures();
goto Exit;
}
}
Exit:
{
CUcontext dummy;
m_Funcs->cuCtxPopCurrent(&dummy);
}
return err == CUDA_SUCCESS;
}
void CUDAGLInteropHelper::unregisterTextures()
{
int err;
if (m_Funcs == nullptr) {
// Already logged in constructor
return;
}
// Push FFmpeg's CUDA context to use for our CUDA operations
err = m_Funcs->cuCtxPushCurrent(m_Context->cuda_ctx);
if (err != CUDA_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuCtxPushCurrent() failed: %d", err);
return;
}
for (int i = 0; i < NV12_PLANES; i++) {
if (m_Resources[i] != 0) {
m_Funcs->cuGraphicsUnregisterResource(m_Resources[i]);
m_Resources[i] = 0;
}
}
{
CUcontext dummy;
m_Funcs->cuCtxPopCurrent(&dummy);
}
}
bool CUDAGLInteropHelper::copyCudaFrameToTextures(AVFrame* frame)
{
int err;
if (m_Funcs == nullptr) {
// Already logged in constructor
return false;
}
// Push FFmpeg's CUDA context to use for our CUDA operations
err = m_Funcs->cuCtxPushCurrent(m_Context->cuda_ctx);
if (err != CUDA_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuCtxPushCurrent() failed: %d", err);
return false;
}
// Map our resources
err = m_Funcs->cuGraphicsMapResources(NV12_PLANES, m_Resources, m_Context->stream);
if (err != CUDA_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuGraphicsMapResources() failed: %d", err);
goto PopCtxExit;
}
for (int i = 0; i < NV12_PLANES; i++) {
CUarray cudaArray;
// Get a pointer to the mapped array for this plane
err = m_Funcs->cuGraphicsSubResourceGetMappedArray(&cudaArray, m_Resources[i], 0, 0);
if (err != CUDA_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuGraphicsSubResourceGetMappedArray() failed: %d", err);
goto UnmapExit;
}
// Do the copy
CUDA_MEMCPY2D cu2d = {
.srcMemoryType = CU_MEMORYTYPE_DEVICE,
.srcDevice = (CUdeviceptr)frame->data[i],
.srcPitch = (size_t)frame->linesize[i],
.dstMemoryType = CU_MEMORYTYPE_ARRAY,
.dstArray = cudaArray,
.dstPitch = (size_t)frame->width >> i,
.WidthInBytes = (size_t)frame->width,
.Height = (size_t)frame->height >> i
};
err = m_Funcs->cuMemcpy2D(&cu2d);
if (err != CUDA_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "cuMemcpy2D() failed: %d", err);
goto UnmapExit;
}
}
UnmapExit:
m_Funcs->cuGraphicsUnmapResources(NV12_PLANES, m_Resources, m_Context->stream);
PopCtxExit:
{
CUcontext dummy;
m_Funcs->cuCtxPopCurrent(&dummy);
}
return err == CUDA_SUCCESS;
}

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