Compare commits

..
325 Commits
Author SHA1 Message Date
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
Cameron Gutman cbeb23f16a Adjust Steam Link icon size to 116x116 2021-06-13 16:53:03 -05: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
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
Cameron Gutman 14a6a69dc0 Version 3.0.0 2021-01-17 10:33:10 -06:00
Cameron Gutman 3191182009 Update Windows SDL to Hg a5b970d28bfd 2021-01-17 10:17:27 -06:00
Cameron Gutman e210dc748f Update macOS SDL to Hg a5b970d28bfd 2021-01-17 10:08:45 -06:00
Cameron Gutman 724dcb557f Hide irrelevant options on embedded devices 2021-01-15 19:46:27 -06:00
Cameron Gutman 11d274ff07 Fix X11 keyboard grab when starting in windowed mode 2021-01-15 19:32:38 -06:00
Cameron Gutman ae246b1005 Trim keyboard shortcut setting text slightly 2021-01-15 19:25:28 -06:00
Cameron Gutman ca0250280a Update moonlight-common-c submodule 2021-01-14 19:32:32 -06:00
Cameron Gutman f49d6e15a4 Fix clearing the wrong flag when activating mouse capture 2021-01-14 19:30:30 -06:00
Cameron Gutman 7172cfd889 Properly handle multiple window event flush barriers in flight at once
Fixes streaming on macOS Mojave without Metal support
2021-01-14 19:28:21 -06:00
Cameron Gutman 89e9652075 Save preferences on destruction in case the user closes Moonlight directly from the settings page 2021-01-13 19:43:52 -06:00
Cameron Gutman 53098edc85 Print which archs were bundled in the installer after the build completes 2021-01-13 19:36:46 -06:00
Cameron Gutman e9968fbab3 Add system key capture to the feature list 2021-01-13 19:36:17 -06:00
Cameron Gutman bc10fd0aba Disable the system keyboard capture option on Wayland 2021-01-12 20:41:20 -06:00
Cameron Gutman 76b4922bbe Add toggle for system key capture 2021-01-12 20:05:18 -06:00
Cameron Gutman 8e790c4745 Use real full-screen on macOS when capturing keys 2021-01-12 19:57:01 -06:00
Cameron Gutman c331b180cb Fix modifier keys being stuck down after key combos when capturing system keys on Windows 2021-01-12 18:40:00 -06:00
Cameron Gutman 30e3b02867 Start implementation of system key capture 2021-01-11 23:43:32 -06:00
Cameron Gutman bedea4a093 Rebuild SDL2_ttf.framework as a Universal binary 2021-01-10 14:32:43 -06:00
Cameron Gutman 5372cfba20 Minor fixes to kiosk mode PR
- UI_FULLSCREEN_WINDOWED -> UI_MAXIMIZED
- Propagate user configuration from old "start windowed" option
- Use FullScreen when a window manager isn't present
- Set disabled control to FullScreen with no window manager
2021-01-10 10:09:31 -06:00
jmt-ghandjmt-gh 19dac306d1 Add support for Fullscreen UI in Moonlight (#492)
* initial UI and window decoration removal toggle

* revert default height change

* add fullscreen support to kiosk mode

* add tool tip for kiosk mode

* change to fullscreen rather than kiosk

* update to Fullscreen instead of Full-screen or Full Screen

* update UI text

* convert to display mode picker

* add UI label for picker

* clean up references to startMaximized and fullScreenUIMode

* remove flags property, as fullscreen visiblity handles this already

* use Maximized in selection text

* account for running moonlight without a window manager

* lock display mode from being changed if there is no window manager running

Co-authored-by: jmt-gh <jmt-gh@users.noreply.github.com>
2021-01-10 09:50:12 -06:00
Cameron Gutman e9a98a3402 Add note about multi-monitor bug to remote desktop tooltip 2021-01-09 18:03:48 -06:00
Cameron Gutman 7020749987 Change mute on minimize to mute on focus loss
Minimization doesn't always happen on focus loss in full-screen mode,
so it could lead to unpredictable behavior.
2021-01-09 17:56:23 -06:00
Cameron Gutman 2d62d090df Use a barrier to ensure we don't flush important window events 2021-01-09 17:51:25 -06:00
Cameron Gutman e6f59062fa Update SDL to Hg d7abc11bb69a
https://github.com/cgutman/moonlight-deps/commit/20ce1c15eddb0ee9b216b0928844412e3e63e1d6
2021-01-08 21:43:37 -06:00
Cameron Gutman 0d3092d62c Update SDL to Hg d7abc11bb69a
https://github.com/cgutman/moonlight-deps/commit/20ce1c15eddb0ee9b216b0928844412e3e63e1d6
2021-01-08 21:35:07 -06:00
Cameron Gutman af0921c5b2 Set new hints present in SDL 2.0.15 development builds 2021-01-08 18:14:49 -06:00
Cameron Gutman 137454beff Use SDL_UpdateNVTexture on SDL 2.0.15+ 2021-01-08 18:05:27 -06:00
Cameron Gutman a0cc93b813 Fix EGL renderer build on SDL 2.0.9 and earlier 2021-01-03 14:24:30 -06:00
Cameron Gutman ff516f63b4 Set QT_QPA_EGLFS_ALWAYS_SET_MODE=1 by default for RetroPie compatibility 2021-01-03 11:16:59 -06:00
Cameron Gutman 68acb78ae6 Use specific resuming text when reconnecting to a game 2020-12-31 21:33:41 -06:00
Cameron Gutman 949aaaff2a Add tooltip for hide games option 2020-12-31 21:31:46 -06:00
Cameron Gutman 390544cd9a Charge time spent in the decode unit queue to the decoder rather than receive time 2020-12-31 16:10:01 -06:00
Cameron Gutman 766a923f86 Reduce size of Windows installers and portable packages 2020-12-31 11:44:59 -06:00
Cameron Gutman 202ce14afa Update SDL_GameControllerDB 2020-12-30 13:34:01 -06:00
Cameron Gutman 8ff9d8123d Switch VT renderer pacing logic to be enabled by the frame pacing checkbox 2020-12-29 20:26:56 -06:00
Cameron Gutman 2715367411 Fix default audio output change detection on macOS 2020-12-28 14:37:17 -06:00
Cameron Gutman 44b61d9252 Fix accidental static import of IsWow64Process2() and tweak message text 2020-12-28 13:32:02 -06:00
Cameron Gutman 565bba6e55 Point users to ARM64 version of Moonlight if running the x86 or x64 versions 2020-12-28 12:27:22 -06:00
Cameron Gutman 2ecfb7af64 Improve description of gamepad mouse mode 2020-12-27 15:41:47 -06:00
Cameron Gutman 998d3b2cc1 Fix tooltip line wrapping 2020-12-27 15:34:49 -06:00
Cameron Gutman c923d643b9 Rebuild FFmpeg 4.3.1 (patched), discord-rpc, Opus, and OpenSSL 1.1.1i as Universal binaries
https://github.com/cgutman/moonlight-deps
4c2815b5f2045ef0e8b098ed2fa8c4f9c1f5cd77

Built locally with Xcode 12.3 with ARM64 targets enabled
2020-12-27 00:14:39 -06:00
Cameron Gutman bc39c721e3 Add a key combo to toggle cursor display in remote desktop mouse mode 2020-12-26 19:47:00 -06:00
Cameron Gutman 5096ff6496 Add options to invert scroll direction and swap gamepad buttons
Fixes #463
Fixes #467
2020-12-25 22:21:20 -06:00
Cameron Gutman a11f623b17 Add option to mute audio on minimize and background gamepad input
Fixes #461
2020-12-25 15:32:11 -06:00
Cameron Gutman f7ffb30bc6 Reorganize settings and improve descriptions 2020-12-25 12:19:22 -06:00
Cameron Gutman c7ddb54e67 Add support for bundling ARM64 binaries into the installer
It is disabled by default because Qt doesn't have official ARM64 builds yet
2020-12-24 17:21:35 -06:00
Cameron Gutman c4108feb15 Add SDL 2.0.14 hints 2020-12-24 14:31:47 -06:00
Cameron Gutman 47cad71fcd Add special error text for an early termination 2020-12-24 11:24:01 -06:00
Cameron Gutman 0b56b1685a Use LiStringifyPortFlags() instead of coding it ourselves 2020-12-23 14:08:20 -06:00
Cameron Gutman c414e1a168 Display the failing ports when the connection fails 2020-12-23 13:56:15 -06:00
Cameron Gutman 22bebb6bbc Maxwell 2 doesn't support 8K HEVC encoding 2020-12-22 18:57:45 -06:00
Cameron Gutman c3f9742bb6 Update to SDL 2.0.14 for Windows
Defines:
#undef SDL_JOYSTICK_RAWINPUT
#undef SDL_JOYSTICK_WGI
#define USE_D3D9EX 1
2020-12-21 21:00:55 -06:00
Cameron Gutman 3defccb6e2 Work around poor video scaling quality on Qualcomm GPUs 2020-12-21 21:00:55 -06:00
Cameron Gutman 1197b17509 Update AppImage to SDL 2.0.14 2020-12-21 20:30:21 -06:00
Cameron Gutman 265e28e7c3 Update to SDL 2.0.14 for macOS
This is a fat binary with both Intel and ARM64 code.
2020-12-21 20:29:55 -06:00
Cameron Gutman 9c758f1628 Create power-of-2 overlay surfaces for better performance
This lets us take the fast copy path in the DXVA2 renderer.
2020-12-20 17:31:02 -06:00
Cameron Gutman 5f4975ca5c Fix DXVA2 overlay rendering quality issus 2020-12-20 17:30:06 -06:00
Cameron Gutman e5bcc793e5 Implement overlay support in EGLRenderer
Fixes #405
2020-12-19 23:01:41 -06:00
Cameron Gutman cd5b0e74e3 Draw background in MMAL renderer on X11
Fixes #412
2020-12-19 21:25:20 -06:00
Cameron Gutman 3162d2c17d Fix aspect ratio of MMAL renderer output 2020-12-19 17:48:36 -06:00
Cameron Gutman 6a54fb649c Fix synchronization issues between render and overlay update threads in DXVA2 2020-12-18 20:23:52 -06:00
Cameron Gutman 8031ed7fe1 Run a GC before and after streaming 2020-12-18 19:54:11 -06:00
Cameron Gutman dcbf5affda Fix use-after-free in VDPAU overlay rendering 2020-12-18 19:39:24 -06:00
Cameron Gutman 027c8dcd41 Add overlay support to VDPAU renderer 2020-12-17 18:35:08 -06:00
Cameron Gutman 94b46a2173 Use a vertex buffer for rendering the DXVA2 overlays 2020-12-16 18:16:35 -06:00
Cameron Gutman b66f7f6c8b Don't persist the server cert until pairing is successful
Fixes #484
2020-12-15 21:24:21 -06:00
Cameron Gutman 355e4a5c67 Handle MSVC mkspecs with version number suffixes
ARM64 build uses win32-arm64-msvc2017
2020-12-13 23:58:13 -06:00
Cameron Gutman 1e7cb7f13e Replace D3DX9 with SDL_ttf for overlay rendering 2020-12-13 23:43:31 -06:00
Cameron Gutman d58837421f Refactor SDL overlay drawing into OverlayManager for sharing with other renderers 2020-12-13 15:51:50 -06:00
Cameron Gutman 19d5358b97 Add a PC menu header to show the current PC status 2020-12-13 12:50:56 -06:00
Cameron Gutman a6551c320d Finish Windows ARM64 bringup
SDL2  for ARM64 is not yet checked-in, but I will do that when SDL 2.0.14 is released.
2020-12-12 16:44:41 -06:00
Cameron Gutman 59a73817c8 Don't use AntiHooking.dll or use D3DX9 on ARM64 2020-12-12 13:59:47 -06:00
Cameron Gutman 1231f3eeb1 Stub out ARM64 build support in build-arch.bat 2020-12-12 13:08:45 -06:00
Cameron Gutman cd40c1dacd Update bundled libraries for AppImage 2020-12-12 12:51:00 -06:00
Cameron Gutman 33286a5bde Add ARM64 VCRedist definitions for later use 2020-12-12 12:43:21 -06:00
Cameron Gutman b61c35aff9 Rebuild SDL_ttf 2.0.15 for x86, x64, and ARM64 using FreeType 2.10.4 2020-12-10 21:16:16 -06:00
Cameron Gutman 2a3e1ba62e Rebuild FFmpeg 4.3.1, discord-rpc, Opus, and OpenSSL 1.1.1i for x86, x64, and ARM64
https://github.com/cgutman/moonlight-deps
382270e6c9a9c42addce06ef5dc6522b1368b5e2
2020-12-10 21:03:34 -06:00
Cameron Gutman d95f3277b2 Merge pull request #479 from TotalCaesar659/patch-1
Update URLs to HTTPS
2020-12-10 09:06:35 -06:00
TotalCaesar659 e0a2c8cb8a Update URLs to HTTPS 2020-12-10 16:37:29 +03:00
Cameron Gutman 0aa46a57fe Update moonlight-common-c submodule 2020-12-07 20:05:25 -06:00
Cameron Gutman 53e3cd143e Refresh display resolutions when opening the settings page 2020-12-07 19:58:42 -06:00
Cameron Gutman 8381039f97 Remove extra includes 2020-12-06 17:15:30 -06:00
Cameron Gutman a91c61e5e2 Remove Raspberry Pi PulseAudio avoidance hack
Not only is it no longer required (PA works great now), it actually breaks audio with Pi OS's new PA config
2020-12-06 16:58:41 -06:00
Cameron Gutman f90efd7e38 Fix infinite polish() loop when quitting a game due to removal of the 'overlay' property on Qt 6.0 2020-12-01 20:46:28 -06:00
Cameron Gutman fab142dd38 Add workaround for problems with Qt 6.0's Metal renderer 2020-12-01 19:13:17 -06:00
Cameron Gutman 125d546cea Fix installer build warning 2020-11-29 21:22:30 -06:00
Cameron Gutman 179f7ecc23 Fix AppVeyor artifact archival 2020-11-29 21:22:16 -06:00
Cameron Gutman 0fc30a70ca Build unified Windows installer bundles 2020-11-29 21:13:27 -06:00
Cameron Gutman f81509a892 Download VCRedist on demand rather than always bundling it in the installer 2020-11-29 20:24:23 -06:00
watchhaandwatchha fc4488f17f French update (#476)
Co-authored-by: watchha <watchha@users.noreply.github.com>
2020-11-29 11:20:25 -06:00
Cameron Gutman 2748f3d22e Update build instructions 2020-11-29 11:10:16 -06:00
Cameron Gutman e0d42cbe9c Fix index out of bounds when an app is hidden 2020-11-28 17:33:15 -06:00
Cameron Gutman 1780d9417f Disable app menu options instead of hiding them 2020-11-28 17:32:39 -06:00
Cameron Gutman 5f182b1e62 Remove "View Apps" and change "View Hidden Apps" to "View All Apps" 2020-11-28 17:29:13 -06:00
Cameron Gutman 80cee8367f Make sure the last modified time for the GCDB is not in the future 2020-11-24 00:18:34 -06:00
Cameron Gutman 7325d5657d Add custom resolution option 2020-11-24 00:13:39 -06:00
Cameron Gutman 72182c7caa Add support for selecting an app to launch directly 2020-11-23 21:38:22 -06:00
Cameron Gutman d7ca3801be Fix build on Qt 5.14 and earlier 2020-11-23 20:39:50 -06:00
Cameron Gutman 2ef714f87b Avoid runtime QT_OPENGL warning on Qt 6.0 2020-11-23 18:36:32 -06:00
Cameron Gutman 46940eb15a Enable HTTP/2 for update checking and mapping updates 2020-11-23 18:35:46 -06:00
Cameron Gutman 3485983553 Delete the cached mappings if they are empty or corrupt 2020-11-23 18:34:53 -06:00
Cameron Gutman 861ebc151a Only update gamepad mappings if they're newer than what we already have 2020-11-23 18:31:21 -06:00
Cameron Gutman ae5df938b6 Update macOS icon to match new Big Sur style 2020-11-21 16:37:56 -06:00
Cameron Gutman e224a7f0c7 Fetch updated gamepad mappings each launch 2020-11-21 14:45:34 -06:00
Cameron Gutman aa4684077d Fix inability to open app menu with gamepad 2020-11-21 13:28:04 -06:00
Cameron Gutman 193e196c46 Update searching message 2020-11-21 13:18:17 -06:00
Cameron Gutman 6e7a42065c Translation updates and refactoring 2020-11-21 13:15:54 -06:00
watchhaandwatchha 2c987d2912 French translation (#451)
* french translation

* Update main.cpp

* Update main.cpp

Co-authored-by: watchha <watchha@users.noreply.github.com>
2020-11-21 11:42:16 -06:00
Cameron Gutman 19f5dc12fb Fix fill color of Discord logo on Qt 5.9 2020-11-21 11:29:55 -06:00
Cameron Gutman 38b59aa1a9 Fix DXVA2 renderer compilation with Qt 6.0 2020-11-21 11:12:40 -06:00
Cameron Gutman 201b59cd4c Compilation fixes for Qt 6.0 Beta 3 2020-11-07 19:23:23 -06:00
Cameron Gutman de3afe27c1 Fix LSMinimumSystemVersion to match Qt 5.15 2020-11-01 09:42:03 -06:00
Cameron Gutman 7bee8e73f0 Update moonlight-common-c with QoS fix 2020-10-28 20:39:37 -05:00
Cameron Gutman 17e0fbbaf8 Update moonlight-common-c with MTU test 2020-10-17 21:53:33 -05:00
Cameron Gutman ec486e89e4 Use char16_t overload of QString::fromUtf16() for Qt 6.0 compatibility 2020-10-16 00:07:58 -05:00
Cameron Gutman ec3b000264 QTextStream::setCodec() is gone in Qt 6.0, so use the replacement setEncoding() function 2020-10-15 18:57:04 -05:00
Cameron Gutman 90a513dc3d Improve source compatibility with Qt 6.0 2020-10-15 18:54:12 -05:00
Cameron Gutman 32a74e3416 Update qmdnsengine to build with Qt 6.0 2020-10-14 22:40:25 -05:00
Cameron Gutman 1d90bf9230 Trim spaces from the provided IP address or hostname 2020-10-06 21:39:01 -05:00
832 changed files with 15747 additions and 215454 deletions

No files matched your search

+11 -2
View File
@@ -1,10 +1,12 @@
--- ---
name: Bug report 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!** **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** **Describe the bug**
A clear and concise description of what the bug is. 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? - If so, which settings have been changed?
- Does the problem still occur after reverting settings back to default? - 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)** **Client PC details (please complete the following information)**
- OS: [e.g. Windows 10 1809] - OS: [e.g. Windows 10 1809]
- Moonlight Version: [e.g. v0.9.0] - Moonlight Version: [e.g. v0.9.0]
- GPU: [e.g. Intel HD Graphics 520]
**Server PC details (please complete the following information)** **Server PC details (please complete the following information)**
- OS: [e.g. Windows 10 1809] - OS: [e.g. Windows 10 1809]
- GeForce Experience version: [e.g. 3.16.0.140] - GeForce Experience version: [e.g. 3.16.0.140]
- Nvidia GPU driver: [e.g. 417.35] - Nvidia GPU driver: [e.g. 417.35]
- Antivirus and firewall software: [e.g. Windows Defender and Windows Firewall]
**Moonlight Logs (please attach)** **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. - On Windows, `Moonlight-###.log` files can be found in `%TEMP%`. Simply type that into the File Explorer path field to navigate there.
+5 -1
View File
@@ -9,7 +9,11 @@
url = https://github.com/gabomdq/SDL_GameControllerDB.git url = https://github.com/gabomdq/SDL_GameControllerDB.git
[submodule "soundio/libsoundio"] [submodule "soundio/libsoundio"]
path = soundio/libsoundio path = soundio/libsoundio
url = https://github.com/andrewrk/libsoundio.git url = https://github.com/cgutman/libsoundio.git
[submodule "h264bitstream/h264bitstream"] [submodule "h264bitstream/h264bitstream"]
path = h264bitstream/h264bitstream path = h264bitstream/h264bitstream
url = https://github.com/aizvorski/h264bitstream.git url = https://github.com/aizvorski/h264bitstream.git
[submodule "libs"]
path = libs
url = https://github.com/cgutman/moonlight-qt-prebuilts.git
shallow = true
+22 -11
View File
@@ -2,12 +2,13 @@
[Moonlight PC](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built to run on Windows, Mac, and Linux. This client is the successor to [Moonlight Chrome](https://github.com/moonlight-stream/moonlight-chrome) for streaming on PC. [Moonlight PC](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built to run on Windows, Mac, and Linux. This client is the successor to [Moonlight Chrome](https://github.com/moonlight-stream/moonlight-chrome) for streaming on PC.
Moonlight also has mobile versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [iOS](https://github.com/moonlight-stream/moonlight-ios). 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) [![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) [![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 ## Features
- Hardware accelerated video decoding on Windows, Mac, and Linux - Hardware accelerated video decoding on Windows, Mac, and Linux
@@ -15,6 +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 - Supports streaming at 720p, 1080p, 1440p, 4K, and the client PC's native screen resolution
- 7.1 surround sound audio support - 7.1 surround sound audio support
- Support for both pointer capture (for games) and direct mouse control (for remote desktop) - Support for both pointer capture (for games) and direct mouse control (for remote desktop)
- Support for passing system-wide keyboard shortcuts like Alt+Tab to the host
- Direct touchscreen mouse control - Direct touchscreen mouse control
- HEVC support for better image quality at reduced bandwidth - HEVC support for better image quality at reduced bandwidth
- Gamepad support with force feedback for up to 4 players - Gamepad support with force feedback for up to 4 players
@@ -27,19 +29,24 @@ 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) - [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) - [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 ## Building
### Windows Build Requirements ### Windows Build Requirements
* Qt 5.12.4 SDK or later * Qt 5.15 SDK or later
* Windows 7 or later * Windows 7 or later
* [Visual Studio](https://visualstudio.microsoft.com/downloads/) 2017 (Community edition is fine) * [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) (Community edition is fine)
* Select MSVC Desktop toolchain during Qt installation * Select **MSVC 2019** option during Qt installation. MinGW is not supported.
* [DirectX SDK](https://www.microsoft.com/en-us/download/details.aspx?id=6812)
* [7-Zip](https://www.7-zip.org/) (only if building installers for non-development PCs) * [7-Zip](https://www.7-zip.org/) (only if building installers for non-development PCs)
* [WiX Toolset](http://wixtoolset.org/releases/) v3.11 or later (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)
### macOS Build Requirements ### macOS Build Requirements
* Qt 5.14 SDK or later * Qt 5.15 SDK or later
* macOS High Sierra (10.13) or later * macOS High Sierra (10.13) or later
* Xcode 11 * Xcode 11
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs) * [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
@@ -47,8 +54,10 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
### Linux/Unix Build Requirements ### Linux/Unix Build Requirements
* Qt 5.9 SDK or later * Qt 5.9 SDK or later
* GCC or Clang * GCC or Clang
* Install your distro equivalents of: `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` * Install the required packages:
* FFmpeg 4.0 is required to build. If your distro doesn't package FFmpeg 4.0, you can build and install it from source on http://ffmpeg.org/ * 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 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 ### Steam Link Build Requirements
* [Steam Link SDK](https://github.com/ValveSoftware/steamlink-sdk) cloned on your build system * [Steam Link SDK](https://github.com/ValveSoftware/steamlink-sdk) cloned on your build system
@@ -62,10 +71,12 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
2. Run `git submodule update --init --recursive` from within `moonlight-qt/` 2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
3. Open the project in Qt Creator or build from qmake on the command line. 3. Open the project in Qt Creator or build from qmake on the command line.
* To build a binary for use on non-development machines, use the scripts in the `scripts` folder. * To build a binary for use on non-development machines, use the scripts in the `scripts` folder.
* For Windows builds, use `scripts\generate-installers.bat`. Execute this script from the root of the repository within a Qt command prompt. Ensure WiX and 7-Zip binary directories are in your `%PATH%`. * For Windows builds, use `scripts\build-arch.bat` and `scripts\generate-bundle.bat`. Execute these scripts from the root of the repository within a Qt command prompt. Ensure WiX and 7-Zip binary directories are in your `%PATH%`.
* For macOS builds, use `scripts/generate-dmg.sh`. Execute this script from the root of the repository and ensure Qt's `bin` folder is in your `$PATH`. * For 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. * For Steam Link builds, run `scripts/build-steamlink-app.sh` from the root of the repository.
* To build from the command line for development use, run `qmake moonlight-qt.pro` then `make debug` or `make release` * To build from the command line for development use, 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 ## Contribute
1. Fork us 1. Fork us
+1 -1
View File
@@ -17,7 +17,7 @@
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>10.11</string> <string>10.13</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
<string>NSApplication</string> <string>NSApplication</string>
<key>NSSupportsAutomaticGraphicsSwitching</key> <key>NSSupportsAutomaticGraphicsSwitching</key>
+41 -12
View File
@@ -33,20 +33,19 @@ DEFINES += QT_DEPRECATED_WARNINGS
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
win32 { win32 {
INCLUDEPATH += \ INCLUDEPATH += $$PWD/../libs/windows/include
$$PWD/../libs/windows/include \
$$(DXSDK_DIR)/Include
contains(QT_ARCH, i386) { contains(QT_ARCH, i386) {
LIBS += -L$$PWD/../libs/windows/lib/x86 LIBS += -L$$PWD/../libs/windows/lib/x86
LIBS += -L$$(DXSDK_DIR)/Lib/x86
} }
contains(QT_ARCH, x86_64) { contains(QT_ARCH, x86_64) {
LIBS += -L$$PWD/../libs/windows/lib/x64 LIBS += -L$$PWD/../libs/windows/lib/x64
LIBS += -L$$(DXSDK_DIR)/Lib/x64 }
contains(QT_ARCH, arm64) {
LIBS += -L$$PWD/../libs/windows/lib/arm64
} }
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib dbghelp.lib qwave.lib LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib dbghelp.lib
} }
macx { macx {
INCLUDEPATH += $$PWD/../libs/mac/include INCLUDEPATH += $$PWD/../libs/mac/include
@@ -102,7 +101,7 @@ unix:!macx {
} }
} }
win32 { win32 {
LIBS += -llibssl -llibcrypto -lSDL2 -lSDL2_ttf -lavcodec -lavutil -lopus -ld3dx9 LIBS += -llibssl -llibcrypto -lSDL2 -lSDL2_ttf -lavcodec -lavutil -lopus
CONFIG += ffmpeg CONFIG += ffmpeg
} }
win32:!winrt { win32:!winrt {
@@ -132,8 +131,11 @@ SOURCES += \
cli/commandlineparser.cpp \ cli/commandlineparser.cpp \
cli/quitstream.cpp \ cli/quitstream.cpp \
cli/startstream.cpp \ cli/startstream.cpp \
settings/compatfetcher.cpp \
settings/mappingfetcher.cpp \
settings/streamingpreferences.cpp \ settings/streamingpreferences.cpp \
streaming/input/abstouch.cpp \ streaming/input/abstouch.cpp \
streaming/input/clipboard.cpp \
streaming/input/gamepad.cpp \ streaming/input/gamepad.cpp \
streaming/input/input.cpp \ streaming/input/input.cpp \
streaming/input/keyboard.cpp \ streaming/input/keyboard.cpp \
@@ -155,6 +157,8 @@ SOURCES += \
HEADERS += \ HEADERS += \
backend/nvapp.h \ backend/nvapp.h \
settings/compatfetcher.h \
settings/mappingfetcher.h \
utils.h \ utils.h \
backend/computerseeker.h \ backend/computerseeker.h \
backend/identitymanager.h \ backend/identitymanager.h \
@@ -249,6 +253,12 @@ libdrm {
DEFINES += HAVE_DRM DEFINES += HAVE_DRM
SOURCES += streaming/video/ffmpeg-renderers/drm.cpp SOURCES += streaming/video/ffmpeg-renderers/drm.cpp
HEADERS += streaming/video/ffmpeg-renderers/drm.h HEADERS += streaming/video/ffmpeg-renderers/drm.h
linux {
message(Master hooks enabled)
SOURCES += masterhook.c
LIBS += -ldl
}
} }
config_EGL { config_EGL {
message(EGL renderer selected) message(EGL renderer selected)
@@ -263,7 +273,7 @@ config_EGL {
config_SL { config_SL {
message(Steam Link build configuration selected) 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 LIBS += -lSLVideo -lSLAudio
SOURCES += \ SOURCES += \
@@ -306,11 +316,28 @@ discord-rpc {
LIBS += -ldiscord-rpc LIBS += -ldiscord-rpc
DEFINES += HAVE_DISCORD DEFINES += HAVE_DISCORD
} }
embedded {
message(Embedded build)
DEFINES += EMBEDDED_BUILD
}
RESOURCES += \ RESOURCES += \
resources.qrc \ resources.qrc \
qml.qrc qml.qrc
TRANSLATIONS += \
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
# Additional import path used to resolve QML modules in Qt Creator's code model # Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH = QML_IMPORT_PATH =
@@ -348,11 +375,13 @@ INCLUDEPATH += $$PWD/../h264bitstream/h264bitstream
DEPENDPATH += $$PWD/../h264bitstream/h264bitstream DEPENDPATH += $$PWD/../h264bitstream/h264bitstream
!winrt { !winrt {
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../AntiHooking/release/ -lAntiHooking contains(QT_ARCH, i386)|contains(QT_ARCH, x86_64) {
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../AntiHooking/debug/ -lAntiHooking win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../AntiHooking/release/ -lAntiHooking
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../AntiHooking/debug/ -lAntiHooking
INCLUDEPATH += $$PWD/../AntiHooking INCLUDEPATH += $$PWD/../AntiHooking
DEPENDPATH += $$PWD/../AntiHooking DEPENDPATH += $$PWD/../AntiHooking
}
} }
unix:!macx: { unix:!macx: {
+78 -36
View File
@@ -15,8 +15,8 @@ AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
// Allow HTTP redirects // Allow HTTP redirects
m_Nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); m_Nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
connect(&m_Nam, SIGNAL(finished(QNetworkReply*)), connect(&m_Nam, &QNetworkAccessManager::finished,
this, SLOT(handleUpdateCheckRequestFinished(QNetworkReply*))); this, &AutoUpdateChecker::handleUpdateCheckRequestFinished);
QString currentVersion(VERSION_STR); QString currentVersion(VERSION_STR);
qDebug() << "Current Moonlight version:" << currentVersion; qDebug() << "Current Moonlight version:" << currentVersion;
@@ -39,7 +39,13 @@ void AutoUpdateChecker::start()
// We'll get a callback when this is finished // We'll get a callback when this is finished
QUrl url("https://moonlight-stream.org/updates/qt.json"); QUrl url("https://moonlight-stream.org/updates/qt.json");
m_Nam.get(QNetworkRequest(url)); QNetworkRequest request(url);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);
#else
request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
#endif
m_Nam.get(request);
#endif #endif
} }
@@ -57,18 +63,53 @@ QString AutoUpdateChecker::getPlatform()
return QStringLiteral("steamlink"); return QStringLiteral("steamlink");
#elif defined(APP_IMAGE) #elif defined(APP_IMAGE)
return QStringLiteral("appimage"); 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 #else
return QSysInfo::productType(); return QSysInfo::productType();
#endif #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) void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
{ {
Q_ASSERT(reply->isFinished()); Q_ASSERT(reply->isFinished());
if (reply->error() == QNetworkReply::NoError) { if (reply->error() == QNetworkReply::NoError) {
QTextStream stream(reply); QTextStream stream(reply);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
stream.setEncoding(QStringConverter::Utf8);
#else
stream.setCodec("UTF-8"); stream.setCodec("UTF-8");
#endif
// Read all data and queue the reply for deletion // Read all data and queue the reply for deletion
QString jsonString = stream.readAll(); QString jsonString = stream.readAll();
@@ -108,46 +149,47 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
if (updateObj["arch"] == QSysInfo::buildCpuArchitecture() && if (updateObj["arch"] == QSysInfo::buildCpuArchitecture() &&
updateObj["platform"] == getPlatform()) { 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"; qDebug() << "Found update manifest match for current platform";
QString latestVersion = updateObj["version"].toString(); QString latestVersion = updateObj["version"].toString();
qDebug() << "Latest version of Moonlight for this platform is:" << latestVersion; qDebug() << "Latest version of Moonlight for this platform is:" << latestVersion;
QVector<int> latestVersionQuad; QVector<int> latestVersionQuad;
parseStringToVersionQuad(latestVersion, latestVersionQuad); parseStringToVersionQuad(latestVersion, latestVersionQuad);
for (int i = 0;; i++) { int res = compareVersion(m_CurrentVersionQuad, latestVersionQuad);
int latestVer = 0; if (res < 0) {
int currentVer = 0; // m_CurrentVersionQuad < latestVersionQuad
qDebug() << "Update available";
// Treat missing decimal places as 0 emit onUpdateAvailable(updateObj["version"].toString(),
if (i < latestVersionQuad.count()) { updateObj["browser_url"].toString());
latestVer = latestVersionQuad[i]; return;
} }
if (i < m_CurrentVersionQuad.count()) { else if (res > 0) {
currentVer = m_CurrentVersionQuad[i]; qDebug() << "Update manifest version lower than current version";
} return;
if (i >= latestVersionQuad.count() && i >= m_CurrentVersionQuad.count()) { }
break; else {
} qDebug() << "Update manifest version equal to current version";
return;
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;
}
} }
qDebug() << "Update manifest version equal to current version";
return;
} }
} }
else { else {
@@ -155,8 +197,8 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
} }
} }
qWarning() << "No entry in update manifest found for current platform: " qWarning() << "No entry in update manifest found for current platform:"
<< QSysInfo::buildCpuArchitecture() << getPlatform(); << QSysInfo::buildCpuArchitecture() << getPlatform() << QSysInfo::kernelVersion();
} }
else { else {
qWarning() << "Update checking failed with error: " << reply->error(); qWarning() << "Update checking failed with error: " << reply->error();
+2
View File
@@ -20,6 +20,8 @@ private slots:
private: private:
void parseStringToVersionQuad(QString& string, QVector<int>& version); void parseStringToVersionQuad(QString& string, QVector<int>& version);
int compareVersion(QVector<int>& version1, QVector<int>& version2);
QString getPlatform(); QString getPlatform();
QVector<int> m_CurrentVersionQuad; QVector<int> m_CurrentVersionQuad;
+2 -2
View File
@@ -46,8 +46,8 @@ public:
m_Computer(computer), m_Computer(computer),
m_App(app) m_App(app)
{ {
connect(this, SIGNAL(boxArtFetchCompleted(NvComputer*,NvApp,QUrl)), connect(this, &NetworkBoxArtLoadTask::boxArtFetchCompleted,
boxArtManager, SLOT(handleBoxArtLoadComplete(NvComputer*,NvApp,QUrl))); boxArtManager, &BoxArtManager::handleBoxArtLoadComplete);
} }
signals: signals:
+11 -8
View File
@@ -146,7 +146,8 @@ private:
ComputerManager::ComputerManager(QObject *parent) ComputerManager::ComputerManager(QObject *parent)
: QObject(parent), : QObject(parent),
m_PollingRef(0), m_PollingRef(0),
m_MdnsBrowser(nullptr) m_MdnsBrowser(nullptr),
m_CompatFetcher(nullptr)
{ {
QSettings settings; QSettings settings;
@@ -159,12 +160,15 @@ ComputerManager::ComputerManager(QObject *parent)
} }
settings.endArray(); settings.endArray();
// Fetch latest compatibility data asynchronously
m_CompatFetcher.start();
// To quit in a timely manner, we must block additional requests // To quit in a timely manner, we must block additional requests
// after we receive the aboutToQuit() signal. This is neccessary // after we receive the aboutToQuit() signal. This is neccessary
// because NvHTTP uses aboutToQuit() to abort requests in progres // because NvHTTP uses aboutToQuit() to abort requests in progres
// while quitting, however this is a one time signal - additional // while quitting, however this is a one time signal - additional
// requests would not be aborted and block termination. // 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() ComputerManager::~ComputerManager()
@@ -302,8 +306,8 @@ void ComputerManager::startPollingComputer(NvComputer* computer)
if (!pollingEntry->isActive()) { if (!pollingEntry->isActive()) {
PcMonitorThread* thread = new PcMonitorThread(computer); PcMonitorThread* thread = new PcMonitorThread(computer);
connect(thread, SIGNAL(computerStateChanged(NvComputer*)), connect(thread, &PcMonitorThread::computerStateChanged,
this, SLOT(handleComputerStateChanged(NvComputer*))); this, &ComputerManager::handleComputerStateChanged);
pollingEntry->setActiveThread(thread); pollingEntry->setActiveThread(thread);
thread->start(); thread->start();
} }
@@ -337,7 +341,6 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
address.isInSubnet(QHostAddress("fec0::"), 10) || address.isInSubnet(QHostAddress("fec0::"), 10) ||
address.isInSubnet(QHostAddress("fc00::"), 7)) { address.isInSubnet(QHostAddress("fc00::"), 7)) {
addNewHost(address.toString(), true, v6Global); addNewHost(address.toString(), true, v6Global);
added = true;
break; break;
} }
} }
@@ -418,7 +421,7 @@ void ComputerManager::deleteHost(NvComputer* computer)
void ComputerManager::renameHost(NvComputer* computer, QString name) void ComputerManager::renameHost(NvComputer* computer, QString name)
{ {
{ {
QWriteLocker(&computer->lock); QWriteLocker lock(&computer->lock);
computer->name = name; computer->name = name;
computer->hasCustomName = true; computer->hasCustomName = true;
@@ -536,8 +539,8 @@ private:
} }
if (e.getStatusCode() == 599) { if (e.getStatusCode() == 599) {
// 599 is a special code we make a custom message for // 599 is a special code we make a custom message for
emit quitAppFailed("The running game wasn't started by this PC. " emit quitAppFailed(tr("The running game wasn't started by this PC. "
"You must quit the game on the host PC manually or use the device that originally started the game."); "You must quit the game on the host PC manually or use the device that originally started the game."));
} }
else { else {
emit quitAppFailed(e.toQString()); emit quitAppFailed(e.toQString());
+3 -1
View File
@@ -2,6 +2,7 @@
#include "nvcomputer.h" #include "nvcomputer.h"
#include "nvpairingmanager.h" #include "nvpairingmanager.h"
#include "settings/compatfetcher.h"
#include <qmdnsengine/server.h> #include <qmdnsengine/server.h>
#include <qmdnsengine/cache.h> #include <qmdnsengine/cache.h>
@@ -70,7 +71,7 @@ private:
m_Resolver = new QMdnsEngine::Resolver(m_Server, m_Hostname); m_Resolver = new QMdnsEngine::Resolver(m_Server, m_Hostname);
connect(m_Resolver, &QMdnsEngine::Resolver::resolved, connect(m_Resolver, &QMdnsEngine::Resolver::resolved,
this, &MdnsPendingComputer::handleResolvedAddress); this, &MdnsPendingComputer::handleResolvedAddress);
QTimer::singleShot(2000, this, SLOT(handleResolvedTimeout())); QTimer::singleShot(2000, this, &MdnsPendingComputer::handleResolvedTimeout);
} }
QByteArray m_Hostname; QByteArray m_Hostname;
@@ -214,4 +215,5 @@ private:
QMdnsEngine::Browser* m_MdnsBrowser; QMdnsEngine::Browser* m_MdnsBrowser;
QMdnsEngine::Cache m_MdnsCache; QMdnsEngine::Cache m_MdnsCache;
QVector<MdnsPendingComputer*> m_PendingResolution; QVector<MdnsPendingComputer*> m_PendingResolution;
CompatFetcher m_CompatFetcher;
}; };
+4 -1
View File
@@ -143,7 +143,10 @@ QSslKey
IdentityManager::getSslKey() IdentityManager::getSslKey()
{ {
if (m_CachedSslKey.isNull()) { 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); THROW_BAD_ALLOC_IF_NULL(bio);
EVP_PKEY* pk = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr); EVP_PKEY* pk = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr);
+3
View File
@@ -5,6 +5,7 @@
#define SER_APPHDR "hdr" #define SER_APPHDR "hdr"
#define SER_APPCOLLECTOR "appcollector" #define SER_APPCOLLECTOR "appcollector"
#define SER_HIDDEN "hidden" #define SER_HIDDEN "hidden"
#define SER_DIRECTLAUNCH "directlaunch"
NvApp::NvApp(QSettings& settings) NvApp::NvApp(QSettings& settings)
{ {
@@ -13,6 +14,7 @@ NvApp::NvApp(QSettings& settings)
hdrSupported = settings.value(SER_APPHDR).toBool(); hdrSupported = settings.value(SER_APPHDR).toBool();
isAppCollectorGame = settings.value(SER_APPCOLLECTOR).toBool(); isAppCollectorGame = settings.value(SER_APPCOLLECTOR).toBool();
hidden = settings.value(SER_HIDDEN).toBool(); hidden = settings.value(SER_HIDDEN).toBool();
directLaunch = settings.value(SER_DIRECTLAUNCH).toBool();
} }
void NvApp::serialize(QSettings& settings) const void NvApp::serialize(QSettings& settings) const
@@ -22,4 +24,5 @@ void NvApp::serialize(QSettings& settings) const
settings.setValue(SER_APPHDR, hdrSupported); settings.setValue(SER_APPHDR, hdrSupported);
settings.setValue(SER_APPCOLLECTOR, isAppCollectorGame); settings.setValue(SER_APPCOLLECTOR, isAppCollectorGame);
settings.setValue(SER_HIDDEN, hidden); settings.setValue(SER_HIDDEN, hidden);
settings.setValue(SER_DIRECTLAUNCH, directLaunch);
} }
+3 -1
View File
@@ -14,7 +14,8 @@ public:
name == other.name && name == other.name &&
hdrSupported == other.hdrSupported && hdrSupported == other.hdrSupported &&
isAppCollectorGame == other.isAppCollectorGame && isAppCollectorGame == other.isAppCollectorGame &&
hidden == other.hidden; hidden == other.hidden &&
directLaunch == other.directLaunch;
} }
bool operator!=(const NvApp& other) const bool operator!=(const NvApp& other) const
@@ -35,6 +36,7 @@ public:
bool hdrSupported = false; bool hdrSupported = false;
bool isAppCollectorGame = false; bool isAppCollectorGame = false;
bool hidden = false; bool hidden = false;
bool directLaunch = false;
}; };
Q_DECLARE_METATYPE(NvApp) Q_DECLARE_METATYPE(NvApp)
+7 -1
View File
@@ -1,5 +1,6 @@
#include "nvcomputer.h" #include "nvcomputer.h"
#include "nvapp.h" #include "nvapp.h"
#include "settings/compatfetcher.h"
#include <QUdpSocket> #include <QUdpSocket>
#include <QHostInfo> #include <QHostInfo>
@@ -30,6 +31,7 @@ NvComputer::NvComputer(QSettings& settings)
this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray()); this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray());
int appCount = settings.beginReadArray(SER_APPLIST); int appCount = settings.beginReadArray(SER_APPLIST);
this->appList.reserve(appCount);
for (int i = 0; i < appCount; i++) { for (int i = 0; i < appCount; i++) {
settings.setArrayIndex(i); settings.setArrayIndex(i);
@@ -49,6 +51,7 @@ NvComputer::NvComputer(QSettings& settings)
this->serverCodecModeSupport = 0; this->serverCodecModeSupport = 0;
this->pendingQuit = false; this->pendingQuit = false;
this->gpuModel = nullptr; this->gpuModel = nullptr;
this->isSupportedServerVersion = true;
} }
void NvComputer::serialize(QSettings& settings) const void NvComputer::serialize(QSettings& settings) const
@@ -98,7 +101,7 @@ NvComputer::NvComputer(QString address, QString serverInfo, QSslCertificate serv
QString newMacString = NvHTTP::getXmlString(serverInfo, "mac"); QString newMacString = NvHTTP::getXmlString(serverInfo, "mac");
if (newMacString != "00:00:00:00:00:00") { if (newMacString != "00:00:00:00:00:00") {
QStringList macOctets = newMacString.split(':'); QStringList macOctets = newMacString.split(':');
for (QString macOctet : macOctets) { for (const QString& macOctet : macOctets) {
this->macAddress.append((char) macOctet.toInt(nullptr, 16)); this->macAddress.append((char) macOctet.toInt(nullptr, 16));
} }
} }
@@ -142,6 +145,7 @@ NvComputer::NvComputer(QString address, QString serverInfo, QSslCertificate serv
this->activeAddress = address; this->activeAddress = address;
this->state = NvComputer::CS_ONLINE; this->state = NvComputer::CS_ONLINE;
this->pendingQuit = false; this->pendingQuit = false;
this->isSupportedServerVersion = CompatFetcher::isGfeVersionSupported(this->gfeVersion);
} }
bool NvComputer::wake() bool NvComputer::wake()
@@ -317,6 +321,7 @@ bool NvComputer::updateAppList(QVector<NvApp> newAppList) {
for (NvApp& newApp : newAppList) { for (NvApp& newApp : newAppList) {
if (existingApp.id == newApp.id) { if (existingApp.id == newApp.id) {
newApp.hidden = existingApp.hidden; newApp.hidden = existingApp.hidden;
newApp.directLaunch = existingApp.directLaunch;
} }
} }
} }
@@ -406,6 +411,7 @@ bool NvComputer::update(NvComputer& that)
ASSIGN_IF_CHANGED(state); ASSIGN_IF_CHANGED(state);
ASSIGN_IF_CHANGED(gfeVersion); ASSIGN_IF_CHANGED(gfeVersion);
ASSIGN_IF_CHANGED(appVersion); ASSIGN_IF_CHANGED(appVersion);
ASSIGN_IF_CHANGED(isSupportedServerVersion);
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC); ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
ASSIGN_IF_CHANGED(gpuModel); ASSIGN_IF_CHANGED(gpuModel);
ASSIGN_IF_CHANGED_AND_NONNULL(serverCert); ASSIGN_IF_CHANGED_AND_NONNULL(serverCert);
+1
View File
@@ -65,6 +65,7 @@ public:
int maxLumaPixelsHEVC; int maxLumaPixelsHEVC;
int serverCodecModeSupport; int serverCodecModeSupport;
QString gpuModel; QString gpuModel;
bool isSupportedServerVersion;
// Persisted traits // Persisted traits
QString localAddress; QString localAddress;
+30 -18
View File
@@ -67,7 +67,8 @@ NvHTTP::parseQuad(QString quad)
} }
QStringList parts = quad.split("."); 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()); ret.append(parts.at(i).toInt());
} }
@@ -233,17 +234,17 @@ NvHTTP::getDisplayModeList(QString serverInfo)
while (!xmlReader.atEnd()) { while (!xmlReader.atEnd()) {
while (xmlReader.readNextStartElement()) { while (xmlReader.readNextStartElement()) {
QStringRef name = xmlReader.name(); auto name = xmlReader.name();
if (name == "DisplayMode") { if (name == QString("DisplayMode")) {
modes.append(NvDisplayMode()); modes.append(NvDisplayMode());
} }
else if (name == "Width") { else if (name == QString("Width")) {
modes.last().width = xmlReader.readElementText().toInt(); modes.last().width = xmlReader.readElementText().toInt();
} }
else if (name == "Height") { else if (name == QString("Height")) {
modes.last().height = xmlReader.readElementText().toInt(); modes.last().height = xmlReader.readElementText().toInt();
} }
else if (name == "RefreshRate") { else if (name == QString("RefreshRate")) {
modes.last().refreshRate = xmlReader.readElementText().toInt(); modes.last().refreshRate = xmlReader.readElementText().toInt();
} }
} }
@@ -266,8 +267,8 @@ NvHTTP::getAppList()
QVector<NvApp> apps; QVector<NvApp> apps;
while (!xmlReader.atEnd()) { while (!xmlReader.atEnd()) {
while (xmlReader.readNextStartElement()) { while (xmlReader.readNextStartElement()) {
QStringRef name = xmlReader.name(); auto name = xmlReader.name();
if (name == "App") { if (name == QString("App")) {
// We must have a valid app before advancing to the next one // We must have a valid app before advancing to the next one
if (!apps.isEmpty() && !apps.last().isInitialized()) { if (!apps.isEmpty() && !apps.last().isInitialized()) {
qWarning() << "Invalid applist XML"; qWarning() << "Invalid applist XML";
@@ -276,16 +277,16 @@ NvHTTP::getAppList()
} }
apps.append(NvApp()); apps.append(NvApp());
} }
else if (name == "AppTitle") { else if (name == QString("AppTitle")) {
apps.last().name = xmlReader.readElementText(); apps.last().name = xmlReader.readElementText();
} }
else if (name == "ID") { else if (name == QString("ID")) {
apps.last().id = xmlReader.readElementText().toInt(); apps.last().id = xmlReader.readElementText().toInt();
} }
else if (name == "IsHdrSupported") { else if (name == QString("IsHdrSupported")) {
apps.last().hdrSupported = xmlReader.readElementText() == "1"; apps.last().hdrSupported = xmlReader.readElementText() == "1";
} }
else if (name == "IsAppCollectorGame") { else if (name == QString("IsAppCollectorGame")) {
apps.last().isAppCollectorGame = xmlReader.readElementText() == "1"; apps.last().isAppCollectorGame = xmlReader.readElementText() == "1";
} }
} }
@@ -301,7 +302,7 @@ NvHTTP::verifyResponseStatus(QString xml)
while (xmlReader.readNextStartElement()) while (xmlReader.readNextStartElement())
{ {
if (xmlReader.name() == "root") if (xmlReader.name() == QString("root"))
{ {
// Status code can be 0xFFFFFFFF in some rare cases on GFE 3.20.3, and // Status code can be 0xFFFFFFFF in some rare cases on GFE 3.20.3, and
// QString::toInt() will fail in that case, so use QString::toUInt() // QString::toInt() will fail in that case, so use QString::toUInt()
@@ -323,7 +324,7 @@ NvHTTP::verifyResponseStatus(QString xml)
// Special case handling an audio capture error which GFE doesn't // Special case handling an audio capture error which GFE doesn't
// provide any useful status message for. // provide any useful status message for.
statusCode = 418; statusCode = 418;
statusMessage = "Missing audio capture device. Reinstalling GeForce Experience should resolve this error."; statusMessage = tr("Missing audio capture device. Reinstalling GeForce Experience should resolve this error.");
} }
throw GfeHttpResponseException(statusCode, statusMessage); throw GfeHttpResponseException(statusCode, statusMessage);
} }
@@ -391,7 +392,7 @@ void NvHTTP::handleSslErrors(QNetworkReply* reply, const QList<QSslError>& error
return; return;
} }
for (auto error : errors) { for (const QSslError& error : errors) {
if (m_ServerCert != error.certificate()) { if (m_ServerCert != error.certificate()) {
ignoreErrors = false; ignoreErrors = false;
break; break;
@@ -414,7 +415,13 @@ NvHTTP::openConnectionToString(QUrl baseUrl,
QString ret; QString ret;
QTextStream stream(reply); QTextStream stream(reply);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
stream.setEncoding(QStringConverter::Utf8);
#else
stream.setCodec("UTF-8"); stream.setCodec("UTF-8");
#endif
ret = stream.readAll(); ret = stream.readAll();
delete reply; delete reply;
@@ -444,6 +451,11 @@ NvHTTP::openConnection(QUrl baseUrl,
// Add our client certificate // Add our client certificate
request.setSslConfiguration(IdentityManager::get()->getSslConfig()); 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) #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) // 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_PUSH
@@ -456,10 +468,10 @@ NvHTTP::openConnection(QUrl baseUrl,
// Run the request with a timeout if requested // Run the request with a timeout if requested
QEventLoop loop; QEventLoop loop;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), &loop, SLOT(quit())); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, &loop, &QEventLoop::quit);
if (timeoutMs) { if (timeoutMs) {
QTimer::singleShot(timeoutMs, &loop, SLOT(quit())); QTimer::singleShot(timeoutMs, &loop, &QEventLoop::quit);
} }
if (logLevel >= NvLogLevel::NVLL_VERBOSE) { if (logLevel >= NvLogLevel::NVLL_VERBOSE) {
qInfo() << "Executing request:" << url.toString(); qInfo() << "Executing request:" << url.toString();
+1
View File
@@ -23,6 +23,7 @@ public:
int height; int height;
int refreshRate; int refreshRate;
}; };
Q_DECLARE_TYPEINFO(NvDisplayMode, Q_PRIMITIVE_TYPE);
class GfeHttpResponseException : public std::exception class GfeHttpResponseException : public std::exception
{ {
+8 -6
View File
@@ -215,7 +215,7 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
QByteArray salt = generateRandomBytes(16); QByteArray salt = generateRandomBytes(16);
QByteArray saltedPin = saltPin(salt, pin); QByteArray saltedPin = saltPin(salt, pin);
QByteArray aesKey = QCryptographicHash::hash(saltedPin, hashAlgo).data(); QByteArray aesKey = QCryptographicHash::hash(saltedPin, hashAlgo).constData();
aesKey.truncate(16); aesKey.truncate(16);
QString getCert = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, QString getCert = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
@@ -238,17 +238,18 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
return PairState::ALREADY_IN_PROGRESS; return PairState::ALREADY_IN_PROGRESS;
} }
serverCert = QSslCertificate(serverCertStr); QSslCertificate unverifiedServerCert = QSslCertificate(serverCertStr);
if (serverCert.isNull()) { if (unverifiedServerCert.isNull()) {
Q_ASSERT(!serverCert.isNull()); Q_ASSERT(!unverifiedServerCert.isNull());
qCritical() << "Failed to parse plaincert"; qCritical() << "Failed to parse plaincert";
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS); m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
return PairState::FAILED; return PairState::FAILED;
} }
// Pin this cert for TLS // Pin this cert for TLS until pairing is complete. If successful, we will propagate
m_Http.setServerCert(serverCert); // the cert into the NvComputer object and persist it.
m_Http.setServerCert(unverifiedServerCert);
QByteArray randomChallenge = generateRandomBytes(16); QByteArray randomChallenge = generateRandomBytes(16);
QByteArray encryptedChallenge = encrypt(randomChallenge, aesKey); QByteArray encryptedChallenge = encrypt(randomChallenge, aesKey);
@@ -353,5 +354,6 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
return PairState::FAILED; return PairState::FAILED;
} }
serverCert = std::move(unverifiedServerCert);
return PairState::PAIRED; return PairState::PAIRED;
} }
+140 -33
View File
@@ -6,25 +6,57 @@
#include "streaming/session.h" #include "streaming/session.h"
#include "streaming/streamutils.h" #include "streaming/streamutils.h"
#ifdef Q_OS_WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif
SystemProperties::SystemProperties() SystemProperties::SystemProperties()
{ {
versionString = QString(VERSION_STR); versionString = QString(VERSION_STR);
hasWindowManager = WMUtils::isRunningWindowManager(); hasDesktopEnvironment = WMUtils::isRunningDesktopEnvironment();
isRunningWayland = WMUtils::isRunningWayland(); isRunningWayland = WMUtils::isRunningWayland();
isRunningXWayland = isRunningWayland && QGuiApplication::platformName() == "xcb"; isRunningXWayland = isRunningWayland && QGuiApplication::platformName() == "xcb";
QString nativeArch = QSysInfo::currentCpuArchitecture();
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
isWow64 = QSysInfo::currentCpuArchitecture() != QSysInfo::buildCpuArchitecture(); {
USHORT processArch, machineArch;
// Use IsWow64Process2 on TH2 and later, because it supports ARM64
auto fnIsWow64Process2 = (decltype(IsWow64Process2)*)GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsWow64Process2");
if (fnIsWow64Process2 != nullptr && fnIsWow64Process2(GetCurrentProcess(), &processArch, &machineArch)) {
switch (machineArch) {
case IMAGE_FILE_MACHINE_I386:
nativeArch = "i386";
break;
case IMAGE_FILE_MACHINE_AMD64:
nativeArch = "x86_64";
break;
case IMAGE_FILE_MACHINE_ARM64:
nativeArch = "arm64";
break;
}
}
isWow64 = nativeArch != QSysInfo::buildCpuArchitecture();
}
#else #else
isWow64 = false; isWow64 = false;
#endif #endif
#ifndef STEAM_LINK if (nativeArch == "i386") {
friendlyNativeArchName = "x86";
}
else if (nativeArch == "x86_64") {
friendlyNativeArchName = "x64";
}
else {
friendlyNativeArchName = nativeArch.toUpper();
}
// Assume we can probably launch a browser if we're in a GUI environment // Assume we can probably launch a browser if we're in a GUI environment
hasBrowser = hasWindowManager; hasBrowser = hasDesktopEnvironment;
#else
hasBrowser = false;
#endif
#ifdef HAVE_DISCORD #ifdef HAVE_DISCORD
hasDiscordIntegration = true; hasDiscordIntegration = true;
@@ -55,14 +87,38 @@ QRect SystemProperties::getNativeResolution(int displayIndex)
return monitorNativeResolutions.value(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() void SystemProperties::querySdlVideoInfo()
{ {
monitorDesktopResolutions.clear(); if (WMUtils::isRunningX11() || WMUtils::isRunningWayland()) {
monitorNativeResolutions.clear(); // Use a separate thread to temporarily initialize SDL
hasHardwareAcceleration = false; // video to avoid stomping on Qt's X11 and OGL state.
QuerySdlVideoThread thread(this);
thread.start();
thread.wait();
}
else {
querySdlVideoInfoInternal();
}
}
// Never let the maximum drop below 60 FPS void SystemProperties::querySdlVideoInfoInternal()
maximumStreamingFrameRate = 60; {
hasHardwareAcceleration = false;
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@@ -71,6 +127,78 @@ void SystemProperties::querySdlVideoInfo()
return; return;
} }
// Update display related attributes (max FPS, native resolution, etc).
// 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());
if (!testWindow) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Failed to create test window with platform flags: %s",
SDL_GetError());
testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN);
if (!testWindow) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to create window for hardware decode test: %s",
SDL_GetError());
SDL_QuitSubSystem(SDL_INIT_VIDEO);
return;
}
}
Session::getDecoderInfo(testWindow, hasHardwareAcceleration, rendererAlwaysFullScreen, maximumResolution);
SDL_DestroyWindow(testWindow);
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,
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
SDL_GetError());
return;
}
monitorDesktopResolutions.clear();
monitorNativeResolutions.clear();
// Never let the maximum drop below 60 FPS
maximumStreamingFrameRate = 60;
SDL_DisplayMode bestMode; SDL_DisplayMode bestMode;
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) { for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
SDL_DisplayMode desktopMode; SDL_DisplayMode desktopMode;
@@ -120,26 +248,5 @@ void SystemProperties::querySdlVideoInfo()
} }
} }
SDL_Window* testWindow = SDL_CreateWindow("", 0, 0, 1280, 720,
SDL_WINDOW_HIDDEN | StreamUtils::getPlatformWindowFlags());
if (!testWindow) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Failed to create test window with platform flags: %s",
SDL_GetError());
testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN);
if (!testWindow) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to create window for hardware decode test: %s",
SDL_GetError());
SDL_QuitSubSystem(SDL_INIT_VIDEO);
return;
}
}
Session::getDecoderInfo(testWindow, hasHardwareAcceleration, rendererAlwaysFullScreen, maximumResolution);
SDL_DestroyWindow(testWindow);
SDL_QuitSubSystem(SDL_INIT_VIDEO); SDL_QuitSubSystem(SDL_INIT_VIDEO);
} }
+10 -2
View File
@@ -7,6 +7,9 @@ class SystemProperties : public QObject
{ {
Q_OBJECT Q_OBJECT
friend class QuerySdlVideoThread;
friend class RefreshDisplaysThread;
public: public:
SystemProperties(); SystemProperties();
@@ -15,7 +18,8 @@ public:
Q_PROPERTY(bool isRunningWayland MEMBER isRunningWayland CONSTANT) Q_PROPERTY(bool isRunningWayland MEMBER isRunningWayland CONSTANT)
Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT) Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT)
Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT) Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT)
Q_PROPERTY(bool hasWindowManager MEMBER hasWindowManager CONSTANT) Q_PROPERTY(QString friendlyNativeArchName MEMBER friendlyNativeArchName CONSTANT)
Q_PROPERTY(bool hasDesktopEnvironment MEMBER hasDesktopEnvironment CONSTANT)
Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT) Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT)
Q_PROPERTY(bool hasDiscordIntegration MEMBER hasDiscordIntegration CONSTANT) Q_PROPERTY(bool hasDiscordIntegration MEMBER hasDiscordIntegration CONSTANT)
Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged) Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged)
@@ -23,6 +27,7 @@ public:
Q_PROPERTY(QSize maximumResolution MEMBER maximumResolution CONSTANT) Q_PROPERTY(QSize maximumResolution MEMBER maximumResolution CONSTANT)
Q_PROPERTY(QString versionString MEMBER versionString CONSTANT) Q_PROPERTY(QString versionString MEMBER versionString CONSTANT)
Q_INVOKABLE void refreshDisplays();
Q_INVOKABLE QRect getDesktopResolution(int displayIndex); Q_INVOKABLE QRect getDesktopResolution(int displayIndex);
Q_INVOKABLE QRect getNativeResolution(int displayIndex); Q_INVOKABLE QRect getNativeResolution(int displayIndex);
@@ -31,13 +36,16 @@ signals:
private: private:
void querySdlVideoInfo(); void querySdlVideoInfo();
void querySdlVideoInfoInternal();
void refreshDisplaysInternal();
bool hasHardwareAcceleration; bool hasHardwareAcceleration;
bool rendererAlwaysFullScreen; bool rendererAlwaysFullScreen;
bool isRunningWayland; bool isRunningWayland;
bool isRunningXWayland; bool isRunningXWayland;
bool isWow64; bool isWow64;
bool hasWindowManager; QString friendlyNativeArchName;
bool hasDesktopEnvironment;
bool hasBrowser; bool hasBrowser;
bool hasDiscordIntegration; bool hasDiscordIntegration;
QString unmappedGamepads; QString unmappedGamepads;
+31 -8
View File
@@ -52,7 +52,7 @@ public:
void handleUnknownOptions() void handleUnknownOptions()
{ {
if (unknownOptionNames().length()) { if (!unknownOptionNames().isEmpty()) {
showError(QString("Unknown options: %1").arg(unknownOptionNames().join(", "))); showError(QString("Unknown options: %1").arg(unknownOptionNames().join(", ")));
} }
} }
@@ -264,6 +264,11 @@ StreamCommandLineParser::StreamCommandLineParser()
{"software", StreamingPreferences::VDS_FORCE_SOFTWARE}, {"software", StreamingPreferences::VDS_FORCE_SOFTWARE},
{"hardware", StreamingPreferences::VDS_FORCE_HARDWARE}, {"hardware", StreamingPreferences::VDS_FORCE_HARDWARE},
}; };
m_CaptureSysKeysModeMap = {
{"never", StreamingPreferences::CSK_OFF},
{"fullscreen", StreamingPreferences::CSK_FULLSCREEN},
{"always", StreamingPreferences::CSK_ALWAYS},
};
} }
StreamCommandLineParser::~StreamCommandLineParser() StreamCommandLineParser::~StreamCommandLineParser()
@@ -303,6 +308,11 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
parser.addToggleOption("game-optimization", "game optimizations"); parser.addToggleOption("game-optimization", "game optimizations");
parser.addToggleOption("audio-on-host", "audio on host PC"); parser.addToggleOption("audio-on-host", "audio on host PC");
parser.addToggleOption("frame-pacing", "frame pacing"); parser.addToggleOption("frame-pacing", "frame pacing");
parser.addToggleOption("mute-on-focus-loss", "mute audio when Moonlight window loses focus");
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.addChoiceOption("capture-system-keys", "capture system key combos", m_CaptureSysKeysModeMap.keys());
parser.addChoiceOption("video-codec", "video codec", m_VideoCodecMap.keys()); parser.addChoiceOption("video-codec", "video codec", m_VideoCodecMap.keys());
parser.addChoiceOption("video-decoder", "video decoder", m_VideoDecoderMap.keys()); parser.addChoiceOption("video-decoder", "video decoder", m_VideoDecoderMap.keys());
@@ -315,25 +325,21 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
// Resolve display's width and height // Resolve display's width and height
QRegularExpression resolutionRexExp("^(720|1080|1440|4K|resolution)$"); QRegularExpression resolutionRexExp("^(720|1080|1440|4K|resolution)$");
QStringList resoOptions = parser.optionNames().filter(resolutionRexExp); QStringList resoOptions = parser.optionNames().filter(resolutionRexExp);
bool displaySet = resoOptions.length(); bool displaySet = !resoOptions.isEmpty();
if (displaySet) { if (displaySet) {
QString name = resoOptions.last(); QString name = resoOptions.last();
if (name == "720") { if (name == "720") {
preferences->width = 1280; preferences->width = 1280;
preferences->height = 720; preferences->height = 720;
displaySet = true;
} else if (name == "1080") { } else if (name == "1080") {
preferences->width = 1920; preferences->width = 1920;
preferences->height = 1080; preferences->height = 1080;
displaySet = true;
} else if (name == "1440") { } else if (name == "1440") {
preferences->width = 2560; preferences->width = 2560;
preferences->height = 1440; preferences->height = 1440;
displaySet = true;
} else if (name == "4K") { } else if (name == "4K") {
preferences->width = 3840; preferences->width = 3840;
preferences->height = 2160; preferences->height = 2160;
displaySet = true;
} else if (name == "resolution") { } else if (name == "resolution") {
auto resolution = parser.getResolutionOptionValue(name); auto resolution = parser.getResolutionOptionValue(name);
preferences->width = resolution.first; preferences->width = resolution.first;
@@ -344,8 +350,8 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
// Resolve --fps option // Resolve --fps option
if (parser.isSet("fps")) { if (parser.isSet("fps")) {
preferences->fps = parser.getIntOption("fps"); preferences->fps = parser.getIntOption("fps");
if (!inRange(preferences->fps, 30, 120)) { if (!inRange(preferences->fps, 30, 240)) {
parser.showError("FPS must be in range: 30 - 120"); parser.showError("FPS must be in range: 30 - 240");
} }
} }
@@ -405,6 +411,23 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
// Resolve --frame-pacing and --no-frame-pacing options // Resolve --frame-pacing and --no-frame-pacing options
preferences->framePacing = parser.getToggleOptionValue("frame-pacing", preferences->framePacing); preferences->framePacing = parser.getToggleOptionValue("frame-pacing", preferences->framePacing);
// Resolve --mute-on-focus-loss and --no-mute-on-focus-loss options
preferences->muteOnFocusLoss = parser.getToggleOptionValue("mute-on-focus-loss", preferences->muteOnFocusLoss);
// Resolve --background-gamepad and --no-background-gamepad options
preferences->backgroundGamepad = parser.getToggleOptionValue("background-gamepad", preferences->backgroundGamepad);
// Resolve --reverse-scroll-direction and --no-reverse-scroll-direction options
preferences->reverseScrollDirection = parser.getToggleOptionValue("reverse-scroll-direction", preferences->reverseScrollDirection);
// Resolve --swap-gamepad-buttons and --no-swap-gamepad-buttons options
preferences->swapFaceButtons = parser.getToggleOptionValue("swap-gamepad-buttons", preferences->swapFaceButtons);
// 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 // Resolve --video-codec option
if (parser.isSet("video-codec")) { if (parser.isSet("video-codec")) {
preferences->videoCodecConfig = mapValue(m_VideoCodecMap, parser.getChoiceOptionValue("video-codec")); preferences->videoCodecConfig = mapValue(m_VideoCodecMap, parser.getChoiceOptionValue("video-codec"));
+1
View File
@@ -53,4 +53,5 @@ private:
QMap<QString, StreamingPreferences::AudioConfig> m_AudioConfigMap; QMap<QString, StreamingPreferences::AudioConfig> m_AudioConfigMap;
QMap<QString, StreamingPreferences::VideoCodecConfig> m_VideoCodecMap; QMap<QString, StreamingPreferences::VideoCodecConfig> m_VideoCodecMap;
QMap<QString, StreamingPreferences::VideoDecoderSelection> m_VideoDecoderMap; QMap<QString, StreamingPreferences::VideoDecoderSelection> m_VideoDecoderMap;
QMap<QString, StreamingPreferences::CaptureSysKeysMode> m_CaptureSysKeysModeMap;
}; };
+4 -4
View File
@@ -73,7 +73,7 @@ public:
case Event::ComputerSeekTimedout: case Event::ComputerSeekTimedout:
if (m_State == StateSeekComputer) { if (m_State == StateSeekComputer) {
m_State = StateFailure; m_State = StateFailure;
emit q->failed(QString("Failed to connect to %1").arg(m_ComputerName)); emit q->failed(QObject::tr("Failed to connect to %1").arg(m_ComputerName));
} }
break; break;
// Occurs when searched computer is found // Occurs when searched computer is found
@@ -85,8 +85,8 @@ public:
m_ComputerManager->quitRunningApp(event.computer); m_ComputerManager->quitRunningApp(event.computer);
} else { } else {
m_State = StateFailure; m_State = StateFailure;
QString msg = QString("Computer %1 has not been paired. " QString msg = QObject::tr("Computer %1 has not been paired. "
"Please open Moonlight to pair before streaming.") "Please open Moonlight to pair before streaming.")
.arg(event.computer->name); .arg(event.computer->name);
emit q->failed(msg); emit q->failed(msg);
} }
@@ -99,7 +99,7 @@ public:
QCoreApplication::exit(0); QCoreApplication::exit(0);
} else { } else {
m_State = StateFailure; m_State = StateFailure;
emit q->failed(QString("Quitting app failed, reason: %1").arg(event.errorMessage)); emit q->failed(QObject::tr("Quitting app failed, reason: %1").arg(event.errorMessage));
} }
} }
break; break;
+5 -5
View File
@@ -86,8 +86,8 @@ public:
emit q->searchingApp(); emit q->searchingApp();
} else { } else {
m_State = StateFailure; m_State = StateFailure;
QString msg = QString("Computer %1 has not been paired. " QString msg = QObject::tr("Computer %1 has not been paired. "
"Please open Moonlight to pair before streaming.") "Please open Moonlight to pair before streaming.")
.arg(event.computer->name); .arg(event.computer->name);
emit q->failed(msg); emit q->failed(msg);
} }
@@ -123,18 +123,18 @@ public:
case Event::AppQuitCompleted: case Event::AppQuitCompleted:
if (m_State == StateSeekApp && !event.errorMessage.isEmpty()) { if (m_State == StateSeekApp && !event.errorMessage.isEmpty()) {
m_State = StateFailure; m_State = StateFailure;
emit q->failed(QString("Quitting app failed, reason: %1").arg(event.errorMessage)); emit q->failed(QObject::tr("Quitting app failed, reason: %1").arg(event.errorMessage));
} }
break; break;
// Occurs when computer or app search timed out // Occurs when computer or app search timed out
case Event::Timedout: case Event::Timedout:
if (m_State == StateSeekComputer) { if (m_State == StateSeekComputer) {
m_State = StateFailure; m_State = StateFailure;
emit q->failed(QString("Failed to connect to %1").arg(m_ComputerName)); emit q->failed(QObject::tr("Failed to connect to %1").arg(m_ComputerName));
} }
if (m_State == StateSeekApp) { if (m_State == StateSeekApp) {
m_State = StateFailure; m_State = StateFailure;
emit q->failed(QString("Failed to find application %1").arg(m_AppName)); emit q->failed(QObject::tr("Failed to find application %1").arg(m_AppName));
} }
break; break;
} }
@@ -15,6 +15,7 @@
<li>Hardware accelerated video decoding with VAAPI, VDPAU, and NVDEC support</li> <li>Hardware accelerated video decoding with VAAPI, VDPAU, and NVDEC support</li>
<li>7.1 surround sound audio 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 both pointer capture (for games) and direct mouse control (for remote desktop)</li>
<li>Support for passing system-wide keyboard shortcuts like Alt+Tab to the host</li>
<li>Direct touchscreen mouse control</li> <li>Direct touchscreen mouse control</li>
<li>HEVC support for better video compression efficiency</li> <li>HEVC support for better video compression efficiency</li>
<li>Gamepad support with force feedback for up to 4 players</li> <li>Gamepad support with force feedback for up to 4 players</li>
@@ -35,6 +36,98 @@
</screenshots> </screenshots>
<releases> <releases>
<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>
<ul>
<li>Added the ability to define a custom streaming resolution using the UI</li>
<li>Added the option to capture system key combinations (Alt+Tab, Ctrl+Shift+Esc, Win+Tab, etc) in fullscreen mode</li>
<li>Updated gamepad mappings are automatically downloaded each time Moonlight launches</li>
<li>The performance overlay is now supported for Linux clients using VAAPI and VDPAU hardware acceleration</li>
<li>Added the option to enable "Direct Launch" which immediately launches the specified game when clicking on a PC</li>
<li>Added the option to mute audio when the Moonlight window loses input focus</li>
<li>Added the option to toggle background gamepad input</li>
<li>Added the option to invert scroll direction</li>
<li>Added the option to swap gamepad button layout to Nintendo-style</li>
<li>Added the option to start the Moonlight UI in fullscreen mode</li>
<li>The failing TCP or UDP ports are now displayed when the connection fails</li>
</ul>
<p>Bugfixes:</p>
<ul>
<li>Fixed PS4 controllers not reading inputs during haptics in some games</li>
<li>Fixed some slow decoders causing the receive time value to increase rather than the decode time</li>
<li>Native resolution options now refresh each time the settings page is opened, rather than once per launch of Moonlight</li>
<li>Fixed no video or audio while streaming over Internet connections that drop packets with QoS tagging enabled</li>
<li>Improved connection testing accuracy for connections with low MTUs</li>
<li>Fixed inability to open an app's context menu with a gamepad</li>
</ul>
</description>
</release>
<release version="2.2.0" date="2020-09-10"> <release version="2.2.0" date="2020-09-10">
<description> <description>
<p>New features:</p> <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. # pairing data will be lost each reboot.
HOME=/usr/local/moonlight 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 PE_Single_CPU which seems to host A/V stuff
renice -10 -p $(pidof PE_Single_CPU) renice -10 -p $(pidof PE_Single_CPU)
# Renice Moonlight itself to avoid preemption by background tasks # Renice Moonlight itself to avoid preemption by background tasks
# Write output to a logfile in /tmp # Write output to a logfile in /tmp
echo Launching $BIN exec nice -n -10 ./bin/moonlight > /tmp/moonlight.log
exec nice -n -10 ./bin/$BIN > /tmp/moonlight.log
+166 -94
View File
@@ -10,6 +10,7 @@ CenteredGridView {
property AppModel appModel : createModel() property AppModel appModel : createModel()
property bool activated property bool activated
property bool showHiddenGames property bool showHiddenGames
property bool showGames
id: appGrid id: appGrid
focus: true focus: true
@@ -39,6 +40,19 @@ CenteredGridView {
if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) { if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
currentIndex = 0 currentIndex = 0
} }
if (!showGames && !showHiddenGames) {
// Check if there's a direct launch app
var directLaunchAppIndex = model.getDirectLaunchAppIndex();
if (directLaunchAppIndex >= 0) {
// Start the direct launch app if nothing else is running
currentIndex = directLaunchAppIndex
currentItem.launchOrResumeSelectedApp(false)
// Set showGames so we will not loop when the stream ends
showGames = true
}
}
} }
StackView.onDeactivating: { StackView.onDeactivating: {
@@ -59,6 +73,9 @@ CenteredGridView {
width: 220; height: 287; width: 220; height: 287;
grid: appGrid grid: appGrid
property alias appContextMenu: appContextMenuLoader.item
property alias appNameText: appNameTextLoader.item
// Dim the app if it's hidden // Dim the app if it's hidden
opacity: model.hidden ? 0.4 : 1.0 opacity: model.hidden ? 0.4 : 1.0
@@ -71,9 +88,14 @@ CenteredGridView {
source: model.boxart source: model.boxart
onSourceSizeChanged: { onSourceSizeChanged: {
if ((sourceSize.width == 130 && sourceSize.height == 180) || // GFE 2.0 placeholder image // Nearly all of Nvidia's official box art does not match the dimensions of placeholder
(sourceSize.width == 628 && sourceSize.height == 888) || // GFE 3.0 placeholder image // images, however the one known exeception is Overcooked. Therefore, we only execute
(sourceSize.width == 200 && sourceSize.height == 266)) // Our no_app_image.png // 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 isPlaceholder = true
} }
@@ -90,97 +112,117 @@ CenteredGridView {
ToolTip.text: model.name ToolTip.text: model.name
ToolTip.delay: 1000 ToolTip.delay: 1000
ToolTip.timeout: 5000 ToolTip.timeout: 5000
ToolTip.visible: (parent.hovered || parent.highlighted) && (!appNameText.visible || appNameText.truncated) ToolTip.visible: (parent.hovered || parent.highlighted) && (!appNameText || appNameText.truncated)
} }
ToolButton { Loader {
id: resumeButton active: model.running
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? -47 : 0 asynchronous: true
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : -60 anchors.fill: appIcon
anchors.centerIn: appIcon
visible: model.running
implicitWidth: 125
implicitHeight: 125
Image { sourceComponent: Item {
source: "qrc:/res/baseline-play_circle_filled_white-48px.svg" ToolButton {
anchors.centerIn: parent anchors.horizontalCenterOffset: appIcon.isPlaceholder ? -47 : 0
sourceSize { anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : -60
width: 75 anchors.centerIn: parent
height: 75 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()
}
ToolTip.text: "Resume Game"
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
} }
ToolButton { Loader {
id: quitButton id: appNameTextLoader
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? 47 : 0 active: appIcon.isPlaceholder
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : 60
anchors.centerIn: appIcon
visible: model.running
implicitWidth: 125
implicitHeight: 125
Image { // This loader is not asynchronous to avoid noticeable differences
source: "qrc:/res/baseline-cancel-24px.svg" // in the time in which the text loads for each game.
anchors.centerIn: parent
sourceSize {
width: 75
height: 75
}
}
onClicked: {
doQuitGame()
}
ToolTip.text: "Quit Game"
ToolTip.delay: 1000
ToolTip.timeout: 3000
ToolTip.visible: hovered
}
Label {
id: appNameText
visible: appIcon.isPlaceholder
text: model.name
width: appIcon.width width: appIcon.width
height: model.running ? 175 : appIcon.height height: model.running ? 175 : appIcon.height
leftPadding: 20
rightPadding: 20
anchors.left: appIcon.left anchors.left: appIcon.left
anchors.right: appIcon.right anchors.right: appIcon.right
anchors.bottom: appIcon.bottom anchors.bottom: appIcon.bottom
font.pointSize: 22
verticalAlignment: Text.AlignVCenter sourceComponent: Label {
horizontalAlignment: Text.AlignHCenter id: appNameText
wrapMode: Text.Wrap text: model.name
elide: Text.ElideRight font.pointSize: 22
leftPadding: 20
rightPadding: 20
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
elide: Text.ElideRight
}
} }
function launchOrResumeSelectedApp() function launchOrResumeSelectedApp(quitExistingApp)
{ {
var runningId = appModel.getRunningAppId() var runningId = appModel.getRunningAppId()
if (runningId !== 0 && runningId !== model.appid) { if (runningId !== 0 && runningId !== model.appid) {
quitAppDialog.appName = appModel.getRunningAppName() if (quitExistingApp) {
quitAppDialog.segueToStream = true quitAppDialog.appName = appModel.getRunningAppName()
quitAppDialog.nextAppName = model.name quitAppDialog.segueToStream = true
quitAppDialog.nextAppIndex = index quitAppDialog.nextAppName = model.name
quitAppDialog.open() quitAppDialog.nextAppIndex = index
quitAppDialog.open()
}
return return
} }
var component = Qt.createComponent("StreamSegue.qml") var component = Qt.createComponent("StreamSegue.qml")
var segue = component.createObject(stackView, {"appName": model.name, "session": appModel.createSessionForApp(index)}) var segue = component.createObject(stackView, {
"appName": model.name,
"session": appModel.createSessionForApp(index),
"isResume": runningId === model.appid
})
stackView.push(segue) stackView.push(segue)
} }
@@ -190,7 +232,7 @@ CenteredGridView {
// will handle starting the game and clicks on the box art will // will handle starting the game and clicks on the box art will
// be ignored. // be ignored.
if (!model.running) { if (!model.running) {
launchOrResumeSelectedApp() launchOrResumeSelectedApp(true)
} }
} }
@@ -224,7 +266,10 @@ CenteredGridView {
} }
} }
Keys.onMenuPressed: { 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) { if (model.running) {
// This will be keyboard/gamepad driven so use // This will be keyboard/gamepad driven so use
// open() instead of popup() // open() instead of popup()
@@ -232,32 +277,59 @@ CenteredGridView {
} }
} }
Keys.onMenuPressed: {
// This will be keyboard/gamepad driven so use open() instead of popup()
appContextMenu.open()
}
function doQuitGame() { function doQuitGame() {
quitAppDialog.appName = appModel.getRunningAppName() quitAppDialog.appName = appModel.getRunningAppName()
quitAppDialog.segueToStream = false quitAppDialog.segueToStream = false
quitAppDialog.open() quitAppDialog.open()
} }
NavigableMenu { Loader {
id: appContextMenu id: appContextMenuLoader
NavigableMenuItem { asynchronous: true
parentMenu: appContextMenu sourceComponent: NavigableMenu {
text: model.running ? "Resume Game" : "Launch Game" id: appContextMenu
onTriggered: launchOrResumeSelectedApp() NavigableMenuItem {
} parentMenu: appContextMenu
NavigableMenuItem { text: model.running ? qsTr("Resume Game") : qsTr("Launch Game")
parentMenu: appContextMenu onTriggered: launchOrResumeSelectedApp(true)
text: "Quit Game" }
onTriggered: doQuitGame() NavigableMenuItem {
visible: model.running parentMenu: appContextMenu
} text: qsTr("Quit Game")
NavigableMenuItem { onTriggered: doQuitGame()
parentMenu: appContextMenu visible: model.running
checkable: true }
checked: model.hidden NavigableMenuItem {
text: "Hide Game" parentMenu: appContextMenu
onTriggered: appModel.setAppHidden(model.index, !model.hidden) checkable: true
visible: !model.running || model.hidden 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("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
}
} }
} }
} }
@@ -268,7 +340,7 @@ CenteredGridView {
property bool segueToStream : false property bool segueToStream : false
property string nextAppName: "" property string nextAppName: ""
property int nextAppIndex: 0 property int nextAppIndex: 0
text:"Are you sure you want to quit " + appName +"? Any unsaved progress will be lost." text:qsTr("Are you sure you want to quit %1? Any unsaved progress will be lost.").arg(appName)
standardButtons: Dialog.Yes | Dialog.No standardButtons: Dialog.Yes | Dialog.No
function quitApp() { function quitApp() {
+5 -3
View File
@@ -19,9 +19,7 @@ ComboBox {
id: textMetrics id: textMetrics
} }
// We call this every time the options change (and init) function recalculateWidth() {
// so we can adjust the combo box width here too
onActivated: {
textMetrics.font = font textMetrics.font = font
popupMetrics.font = popup.font popupMetrics.font = popup.font
textWidth = 0 textWidth = 0
@@ -33,6 +31,10 @@ ComboBox {
} }
} }
// We call this every time the options change (and init)
// so we can adjust the combo box width here too
onActivated: recalculateWidth()
popup.onAboutToShow: { popup.onAboutToShow: {
// Switch to normal navigation for combo boxes // Switch to normal navigation for combo boxes
SdlGamepadKeyNavigation.setUiNavMode(false) SdlGamepadKeyNavigation.setUiNavMode(false)
+2 -2
View File
@@ -6,11 +6,11 @@ import Session 1.0
Item { Item {
function onSearchingComputer() { function onSearchingComputer() {
stageLabel.text = "Establishing connection to PC..." stageLabel.text = qsTr("Establishing connection to PC...")
} }
function onQuittingApp() { function onQuittingApp() {
stageLabel.text = "Quitting app..." stageLabel.text = qsTr("Quitting app...")
} }
function onFailure(message) { function onFailure(message) {
+3 -3
View File
@@ -6,11 +6,11 @@ import SdlGamepadKeyNavigation 1.0
Item { Item {
function onSearchingComputer() { function onSearchingComputer() {
stageLabel.text = "Establishing connection to PC..." stageLabel.text = qsTr("Establishing connection to PC...")
} }
function onSearchingApp() { function onSearchingApp() {
stageLabel.text = "Loading app list..." stageLabel.text = qsTr("Loading app list...")
} }
function onSessionCreated(appName, session) { function onSessionCreated(appName, session) {
@@ -78,7 +78,7 @@ Item {
NavigableMessageDialog { NavigableMessageDialog {
id: quitAppDialog id: quitAppDialog
text:"Are you sure you want to quit " + appName +"? Any unsaved progress will be lost." text:qsTr("Are you sure you want to quit %1? Any unsaved progress will be lost.").arg(appName)
standardButtons: Dialog.Yes | Dialog.No standardButtons: Dialog.Yes | Dialog.No
property string appName : "" property string appName : ""
+1 -1
View File
@@ -1,5 +1,5 @@
import QtQuick 2.0 import QtQuick 2.0
Item { Item {
objectName: "Gamepad Mapping" objectName: qsTr("Gamepad Mapping")
} }
+2 -1
View File
@@ -2,7 +2,8 @@ import QtQuick 2.0
import QtQuick.Controls 2.2 import QtQuick.Controls 2.2
Dialog { Dialog {
parent: overlay // We should use Overlay.overlay here but that's not available in Qt 5.9 :(
parent: ApplicationWindow.contentItem
x: Math.round((parent.width - width) / 2) x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2) y: Math.round((parent.height - height) / 2)
+3
View File
@@ -21,4 +21,7 @@ ItemDelegate {
Keys.onReturnPressed: { Keys.onReturnPressed: {
clicked() clicked()
} }
Keys.onEnterPressed: {
clicked()
}
} }
+8 -1
View File
@@ -3,6 +3,13 @@ import QtQuick.Controls 2.2
Menu { Menu {
onOpened: { onOpened: {
itemAt(0).forceActiveFocus(Qt.TabFocusReason) // Give focus to the first visible and enabled menu item
for (var i = 0; i < count; i++) {
var item = itemAt(i)
if (item.visible && item.enabled) {
item.forceActiveFocus(Qt.TabFocusReason)
break
}
}
} }
} }
+4
View File
@@ -22,6 +22,10 @@ MenuItem {
triggered() triggered()
} }
Keys.onEnterPressed: {
triggered()
}
Keys.onEscapePressed: { Keys.onEscapePressed: {
parentMenu.close() parentMenu.close()
} }
+4
View File
@@ -56,6 +56,10 @@ NavigableDialog {
accept() accept()
} }
Keys.onEnterPressed: {
accept()
}
Keys.onEscapePressed: { Keys.onEscapePressed: {
reject() reject()
} }
+4
View File
@@ -27,6 +27,10 @@ ToolButton {
clicked() clicked()
} }
Keys.onEnterPressed: {
clicked()
}
Keys.onRightPressed: { Keys.onRightPressed: {
nextItemInFocusChain(true).forceActiveFocus(Qt.TabFocus) nextItemInFocusChain(true).forceActiveFocus(Qt.TabFocus)
} }
+73 -63
View File
@@ -17,7 +17,7 @@ CenteredGridView {
topMargin: 20 topMargin: 20
bottomMargin: 5 bottomMargin: 5
cellWidth: 310; cellHeight: 330; cellWidth: 310; cellHeight: 330;
objectName: "Computers" objectName: qsTr("Computers")
Component.onCompleted: { Component.onCompleted: {
// Don't show any highlighted item until interacting with them. // Don't show any highlighted item until interacting with them.
@@ -64,13 +64,13 @@ CenteredGridView {
function addComplete(success, detectedPortBlocking) function addComplete(success, detectedPortBlocking)
{ {
if (!success) { if (!success) {
errorDialog.text = "Unable to connect to the specified PC." errorDialog.text = qsTr("Unable to connect to the specified PC.")
if (detectedPortBlocking) { if (detectedPortBlocking) {
errorDialog.text += "\n\nThis PC's Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network." errorDialog.text += "\n\n" + qsTr("This PC's Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network.")
} }
else { else {
errorDialog.helpText = "Click the Help button for possible solutions." errorDialog.helpText = qsTr("Click the Help button for possible solutions.")
} }
errorDialog.open() errorDialog.open()
@@ -99,8 +99,8 @@ CenteredGridView {
Label { Label {
height: searchSpinner.height height: searchSpinner.height
elide: Label.ElideRight elide: Label.ElideRight
text: StreamingPreferences.enableMdns ? "Searching for PCs with NVIDIA GameStream enabled..." text: StreamingPreferences.enableMdns ? qsTr("Searching for PCs on your local network with NVIDIA GameStream enabled...")
: "Automatic PC discovery is disabled. Add your PC manually." : qsTr("Automatic PC discovery is disabled. Add your PC manually.")
font.pointSize: 20 font.pointSize: 20
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap wrapMode: Text.Wrap
@@ -113,6 +113,8 @@ CenteredGridView {
width: 300; height: 320; width: 300; height: 320;
grid: pcGrid grid: pcGrid
property alias pcContextMenu : pcContextMenuLoader.item
Image { Image {
id: pcIcon id: pcIcon
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -160,66 +162,70 @@ CenteredGridView {
elide: Text.ElideRight elide: Text.ElideRight
} }
NavigableMenu { Loader {
id: pcContextMenu id: pcContextMenuLoader
NavigableMenuItem { asynchronous: true
parentMenu: pcContextMenu sourceComponent: NavigableMenu {
text: "View Apps" id: pcContextMenu
onTriggered: { MenuItem {
var component = Qt.createComponent("AppView.qml") text: qsTr("PC Status: %1").arg(model.online ? qsTr("Online") : qsTr("Offline"))
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name}) font.bold: true
stackView.push(appView) enabled: false
} }
visible: model.online && model.paired NavigableMenuItem {
} parentMenu: pcContextMenu
NavigableMenuItem { text: qsTr("View All Apps")
parentMenu: pcContextMenu onTriggered: {
text: "View Hidden Apps" var component = Qt.createComponent("AppView.qml")
onTriggered: { var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name, "showHiddenGames": true})
var component = Qt.createComponent("AppView.qml") stackView.push(appView)
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name, "showHiddenGames": true}) }
stackView.push(appView) visible: model.online && model.paired
} }
visible: model.online && model.paired NavigableMenuItem {
} parentMenu: pcContextMenu
NavigableMenuItem { text: qsTr("Wake PC")
parentMenu: pcContextMenu onTriggered: computerModel.wakeComputer(index)
text: "Wake PC" visible: !model.online && model.wakeable
onTriggered: computerModel.wakeComputer(index) }
visible: !model.online && model.wakeable NavigableMenuItem {
} parentMenu: pcContextMenu
NavigableMenuItem { text: qsTr("Test Network")
parentMenu: pcContextMenu onTriggered: {
text: "Test Network" computerModel.testConnectionForComputer(index)
onTriggered: { testConnectionDialog.open()
computerModel.testConnectionForComputer(index) }
testConnectionDialog.open()
} }
}
NavigableMenuItem { NavigableMenuItem {
parentMenu: pcContextMenu parentMenu: pcContextMenu
text: "Rename PC" text: qsTr("Rename PC")
onTriggered: { onTriggered: {
renamePcDialog.pcIndex = index renamePcDialog.pcIndex = index
renamePcDialog.originalName = model.name renamePcDialog.originalName = model.name
renamePcDialog.open() renamePcDialog.open()
}
} }
} NavigableMenuItem {
NavigableMenuItem { parentMenu: pcContextMenu
parentMenu: pcContextMenu text: qsTr("Delete PC")
text: "Delete PC" onTriggered: {
onTriggered: { deletePcDialog.pcIndex = index
deletePcDialog.pcIndex = index // get confirmation first, actual closing is called from the dialog
// get confirmation first, actual closing is called from the dialog deletePcDialog.open()
deletePcDialog.open() }
} }
} }
} }
onClicked: { onClicked: {
if (model.online) { 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 // go to game view
var component = Qt.createComponent("AppView.qml") var component = Qt.createComponent("AppView.qml")
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name}) var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name})
@@ -238,7 +244,7 @@ CenteredGridView {
} }
else { else {
// cannot pair while something is streaming or attempting to pair // cannot pair while something is streaming or attempting to pair
errorDialog.text = "You cannot pair while a previous session is still running on the host PC. Quit any running games or reboot the host PC, then try pairing again." errorDialog.text = qsTr("You cannot pair while a previous session is still running on the host PC. Quit any running games or reboot the host PC, then try pairing again.")
errorDialog.helpText = "" errorDialog.helpText = ""
errorDialog.open() errorDialog.open()
} }
@@ -293,7 +299,7 @@ CenteredGridView {
// don't allow edits to the rest of the window while open // don't allow edits to the rest of the window while open
property string pin : "0000" property string pin : "0000"
text:"Please enter " + pin + " on your GameStream PC. This dialog will close when pairing is completed." text:qsTr("Please enter %1 on your GameStream PC. This dialog will close when pairing is completed.").arg(pin)
standardButtons: Dialog.Cancel standardButtons: Dialog.Cancel
onRejected: { onRejected: {
// FIXME: We should interrupt pairing here // FIXME: We should interrupt pairing here
@@ -304,7 +310,7 @@ CenteredGridView {
id: deletePcDialog id: deletePcDialog
// don't allow edits to the rest of the window while open // don't allow edits to the rest of the window while open
property int pcIndex : -1; property int pcIndex : -1;
text:"Are you sure you want to remove this PC?" text:qsTr("Are you sure you want to remove this PC?")
standardButtons: Dialog.Yes | Dialog.No standardButtons: Dialog.Yes | Dialog.No
function deletePc() { function deletePc() {
@@ -321,22 +327,22 @@ CenteredGridView {
standardButtons: Dialog.Ok standardButtons: Dialog.Ok
onAboutToShow: { onAboutToShow: {
testConnectionDialog.text = "Moonlight is testing your network connection to determine if NVIDIA GameStream is blocked.\n\nThis may take a few seconds…" testConnectionDialog.text = qsTr("Moonlight is testing your network connection to determine if NVIDIA GameStream is blocked.") + "\n\n" + qsTr("This may take a few seconds…")
showSpinner = true showSpinner = true
} }
function connectionTestComplete(result, blockedPorts) function connectionTestComplete(result, blockedPorts)
{ {
if (result === -1) { if (result === -1) {
text = "The network test could not be performed because none of Moonlight's connection testing servers were reachable from this PC. Check your Internet connection or try again later." text = qsTr("The network test could not be performed because none of Moonlight's connection testing servers were reachable from this PC. Check your Internet connection or try again later.")
imageSrc = "qrc:/res/baseline-warning-24px.svg" imageSrc = "qrc:/res/baseline-warning-24px.svg"
} }
else if (result === 0) { else if (result === 0) {
text = "This network does not appear to be blocking Moonlight. If you still have trouble connecting, check your PC's firewall settings.\n\nIf you are trying to stream over the Internet, install the Moonlight Internet Hosting Tool on your gaming PC and run the included Internet Streaming Tester to check your gaming PC's Internet connection." text = qsTr("This network does not appear to be blocking Moonlight. If you still have trouble connecting, check your PC's firewall settings.") + "\n\n" + qsTr("If you are trying to stream over the Internet, install the Moonlight Internet Hosting Tool on your gaming PC and run the included Internet Streaming Tester to check your gaming PC's Internet connection.")
imageSrc = "qrc:/res/baseline-check_circle_outline-24px.svg" imageSrc = "qrc:/res/baseline-check_circle_outline-24px.svg"
} }
else { else {
text = "Your PC's current network connection seems to be blocking Moonlight. Streaming over the Internet may not work while connected to this network.\n\nThe following network ports were blocked:\n" text = qsTr("Your PC's current network connection seems to be blocking Moonlight. Streaming over the Internet may not work while connected to this network.") + "\n\n" + qsTr("The following network ports were blocked:") + "\n"
text += blockedPorts text += blockedPorts
imageSrc = "qrc:/res/baseline-error_outline-24px.svg" imageSrc = "qrc:/res/baseline-error_outline-24px.svg"
} }
@@ -348,7 +354,7 @@ CenteredGridView {
NavigableDialog { NavigableDialog {
id: renamePcDialog id: renamePcDialog
property string label: "Enter the new name for this PC:" property string label: qsTr("Enter the new name for this PC:")
property string originalName property string originalName
property int pcIndex : -1; property int pcIndex : -1;
@@ -384,6 +390,10 @@ CenteredGridView {
Keys.onReturnPressed: { Keys.onReturnPressed: {
renamePcDialog.accept() renamePcDialog.accept()
} }
Keys.onEnterPressed: {
renamePcDialog.accept()
}
} }
} }
} }
+1 -1
View File
@@ -9,7 +9,7 @@ Item {
property Session nextSession : null property Session nextSession : null
property string nextAppName : "" property string nextAppName : ""
property string stageText : "Quitting " + appName + "..." property string stageText : qsTr("Quitting %1...").arg(appName)
function quitAppCompleted(error) function quitAppCompleted(error)
{ {
+616 -132
View File
File diff suppressed because it is too large. Load diff
+23 -8
View File
@@ -8,19 +8,25 @@ import Session 1.0
Item { Item {
property Session session property Session session
property string appName property string appName
property string stageText : "Starting " + appName + "..." property string stageText : isResume ? qsTr("Resuming %1...").arg(appName) :
qsTr("Starting %1...").arg(appName)
property bool isResume : false
property bool quitAfter : false property bool quitAfter : false
function stageStarting(stage) function stageStarting(stage)
{ {
// Update the spinner text // Update the spinner text
stageText = "Starting " + stage + "..." stageText = qsTr("Starting %1...").arg(stage)
} }
function stageFailed(stage, errorCode) function stageFailed(stage, errorCode, failingPorts)
{ {
// Display the error dialog after Session::exec() returns // Display the error dialog after Session::exec() returns
streamSegueErrorDialog.text = "Starting " + stage + " failed: Error " + errorCode streamSegueErrorDialog.text = qsTr("Starting %1 failed: Error %2").arg(stage).arg(errorCode)
if (failingPorts) {
streamSegueErrorDialog.text += "\n\n" + qsTr("Check your firewall and port forwarding rules for port(s): %1").arg(failingPorts)
}
} }
function connectionStarted() function connectionStarted()
@@ -65,7 +71,7 @@ Item {
function sessionFinished(portTestResult) function sessionFinished(portTestResult)
{ {
if (portTestResult !== 0 && portTestResult !== -1 && streamSegueErrorDialog.text) { if (portTestResult !== 0 && portTestResult !== -1 && streamSegueErrorDialog.text) {
streamSegueErrorDialog.text += "\n\nThis PC's Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network." streamSegueErrorDialog.text += "\n\n" + qsTr("This PC's Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network.")
} }
// Enable GUI gamepad usage now // Enable GUI gamepad usage now
@@ -97,6 +103,11 @@ Item {
streamSegueErrorDialog.open() streamSegueErrorDialog.open()
} }
} }
// Garbage collect the Session object since it's pretty heavyweight
// and keeps other libraries (like SDL_TTF) around until it is deleted.
session = null
gc()
} }
StackView.onDeactivating: { StackView.onDeactivating: {
@@ -147,13 +158,17 @@ Item {
// in the hintText control itself to synchronize // in the hintText control itself to synchronize
// with Session.exec() which requires no concurrent // with Session.exec() which requires no concurrent
// gamepad usage. // gamepad usage.
hintText.text = SdlGamepadKeyNavigation.getConnectedGamepads() > 0 ? hintText.text = qsTr("Tip:") + " " + qsTr("Press %1 to disconnect your session").arg(SdlGamepadKeyNavigation.getConnectedGamepads() > 0 ?
"Tip: Press Start+Select+L1+R1 to disconnect your session" : qsTr("Start+Select+L1+R1") : qsTr("Ctrl+Alt+Shift+Q"))
"Tip: Press Ctrl+Alt+Shift+Q to disconnect your session"
// Stop GUI gamepad usage now // Stop GUI gamepad usage now
SdlGamepadKeyNavigation.disable() SdlGamepadKeyNavigation.disable()
// Garbage collect QML stuff before we start streaming,
// since we'll probably be streaming for a while and we
// won't be able to GC during the stream.
gc()
// Run the streaming session to completion // Run the streaming session to completion
session.exec(Screen.virtualX, Screen.virtualY) session.exec(Screen.virtualX, Screen.virtualY)
} }
+43
View File
@@ -47,6 +47,17 @@ Session* AppModel::createSessionForApp(int appIndex)
return new Session(m_Computer, app); return new Session(m_Computer, app);
} }
int AppModel::getDirectLaunchAppIndex()
{
for (int i = 0; i < m_VisibleApps.count(); i++) {
if (m_VisibleApps[i].directLaunch) {
return i;
}
}
return -1;
}
int AppModel::rowCount(const QModelIndex &parent) const int AppModel::rowCount(const QModelIndex &parent) const
{ {
// For list models only the root node (an invalid parent) should return the list's size. For all // For list models only the root node (an invalid parent) should return the list's size. For all
@@ -78,6 +89,10 @@ QVariant AppModel::data(const QModelIndex &index, int role) const
return app.hidden; return app.hidden;
case AppIdRole: case AppIdRole:
return app.id; return app.id;
case DirectLaunchRole:
return app.directLaunch;
case AppCollectorGameRole:
return app.isAppCollectorGame;
default: default:
return QVariant(); return QVariant();
} }
@@ -92,6 +107,8 @@ QHash<int, QByteArray> AppModel::roleNames() const
names[BoxArtRole] = "boxart"; names[BoxArtRole] = "boxart";
names[HiddenRole] = "hidden"; names[HiddenRole] = "hidden";
names[AppIdRole] = "appid"; names[AppIdRole] = "appid";
names[DirectLaunchRole] = "directLaunch";
names[AppCollectorGameRole] = "appCollectorGame";
return names; return names;
} }
@@ -207,6 +224,32 @@ void AppModel::setAppHidden(int appIndex, bool hidden)
m_ComputerManager->clientSideAttributeUpdated(m_Computer); m_ComputerManager->clientSideAttributeUpdated(m_Computer);
} }
void AppModel::setAppDirectLaunch(int appIndex, bool directLaunch)
{
Q_ASSERT(appIndex < m_VisibleApps.count());
int appId = m_VisibleApps.at(appIndex).id;
{
QWriteLocker lock(&m_Computer->lock);
for (NvApp& app : m_Computer->appList) {
if (directLaunch) {
// We must clear direct launch from all other apps
// to set it on the new app.
app.directLaunch = app.id == appId;
}
else if (app.id == appId) {
// If we're clearing direct launch, we're done once we
// find our matching app ID.
app.directLaunch = false;
break;
}
}
}
m_ComputerManager->clientSideAttributeUpdated(m_Computer);
}
void AppModel::handleComputerStateChanged(NvComputer* computer) void AppModel::handleComputerStateChanged(NvComputer* computer)
{ {
// Ignore updates for computers that aren't ours // Ignore updates for computers that aren't ours
+6
View File
@@ -17,6 +17,8 @@ class AppModel : public QAbstractListModel
BoxArtRole, BoxArtRole,
HiddenRole, HiddenRole,
AppIdRole, AppIdRole,
DirectLaunchRole,
AppCollectorGameRole,
}; };
public: public:
@@ -27,6 +29,8 @@ public:
Q_INVOKABLE Session* createSessionForApp(int appIndex); Q_INVOKABLE Session* createSessionForApp(int appIndex);
Q_INVOKABLE int getDirectLaunchAppIndex();
Q_INVOKABLE int getRunningAppId(); Q_INVOKABLE int getRunningAppId();
Q_INVOKABLE QString getRunningAppName(); Q_INVOKABLE QString getRunningAppName();
@@ -35,6 +39,8 @@ public:
Q_INVOKABLE void setAppHidden(int appIndex, bool hidden); Q_INVOKABLE void setAppHidden(int appIndex, bool hidden);
Q_INVOKABLE void setAppDirectLaunch(int appIndex, bool directLaunch);
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent) const override; int rowCount(const QModelIndex &parent) const override;
+6 -20
View File
@@ -42,6 +42,8 @@ QVariant ComputerModel::data(const QModelIndex& index, int role) const
return !computer->macAddress.isEmpty(); return !computer->macAddress.isEmpty();
case StatusUnknownRole: case StatusUnknownRole:
return computer->state == NvComputer::CS_UNKNOWN; return computer->state == NvComputer::CS_UNKNOWN;
case ServerSupportedRole:
return computer->isSupportedServerVersion;
default: default:
return QVariant(); return QVariant();
} }
@@ -68,6 +70,7 @@ QHash<int, QByteArray> ComputerModel::roleNames() const
names[BusyRole] = "busy"; names[BusyRole] = "busy";
names[WakeableRole] = "wakeable"; names[WakeableRole] = "wakeable";
names[StatusUnknownRole] = "statusUnknown"; names[StatusUnknownRole] = "statusUnknown";
names[ServerSupportedRole] = "serverSupported";
return names; return names;
} }
@@ -158,26 +161,9 @@ public:
emit connectionTestCompleted(-1, QString()); emit connectionTestCompleted(-1, QString());
} }
else { else {
QString blockedPorts; char blockedPorts[512];
LiStringifyPortFlags(portTestResult, "\n", blockedPorts, sizeof(blockedPorts));
for (int i = 0; i < 32; i++) { emit connectionTestCompleted(portTestResult, QString(blockedPorts));
if (portTestResult & (1 << i)) {
if (!blockedPorts.isEmpty()) {
blockedPorts += "\n";
}
if (LiGetProtocolFromPortFlagIndex(i) == 17 /* IPPROTO_UDP */) {
blockedPorts += "UDP ";
}
else {
blockedPorts += "TCP ";
}
blockedPorts += QString::number(LiGetPortFromPortFlagIndex(i));
}
}
emit connectionTestCompleted(portTestResult, blockedPorts);
} }
} }
+2 -1
View File
@@ -14,7 +14,8 @@ class ComputerModel : public QAbstractListModel
PairedRole, PairedRole,
BusyRole, BusyRole,
WakeableRole, WakeableRole,
StatusUnknownRole StatusUnknownRole,
ServerSupportedRole
}; };
public: public:
+60 -28
View File
@@ -12,12 +12,40 @@ import SdlGamepadKeyNavigation 1.0
ApplicationWindow { ApplicationWindow {
property bool pollingActive: false 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 id: window
visible: true visible: true
width: 1280 width: 1280
height: 600 height: 600
visibility: (SystemProperties.hasWindowManager && StreamingPreferences.startWindowed) ? "Windowed" : "Maximized" visibility: {
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"
} else {
return "FullScreen"
}
}
// This configures the maximum width of the singleton attached QML ToolTip. If left unconstrained,
// it will never insert a line break and just extend on forever.
ToolTip.toolTip.contentWidth: ToolTip.toolTip.implicitContentWidth < 400 ? ToolTip.toolTip.implicitContentWidth : 400
function goBack() {
if (clearOnBack) {
// Pop all items except the first one
stackView.pop(null)
clearOnBack = false
}
else {
stackView.pop()
}
}
StackView { StackView {
id: stackView id: stackView
@@ -34,7 +62,7 @@ ApplicationWindow {
Keys.onEscapePressed: { Keys.onEscapePressed: {
if (depth > 1) { if (depth > 1) {
stackView.pop() goBack()
} }
else { else {
quitConfirmationDialog.open() quitConfirmationDialog.open()
@@ -43,7 +71,7 @@ ApplicationWindow {
Keys.onBackPressed: { Keys.onBackPressed: {
if (depth > 1) { if (depth > 1) {
stackView.pop() goBack()
} }
else { else {
quitConfirmationDialog.open() quitConfirmationDialog.open()
@@ -198,7 +226,7 @@ ApplicationWindow {
iconSource: "qrc:/res/arrow_left.svg" iconSource: "qrc:/res/arrow_left.svg"
onClicked: stackView.pop() onClicked: goBack()
Keys.onDownPressed: { Keys.onDownPressed: {
stackView.currentItem.forceActiveFocus(Qt.TabFocus) stackView.currentItem.forceActiveFocus(Qt.TabFocus)
@@ -223,8 +251,8 @@ ApplicationWindow {
Label { Label {
id: versionLabel id: versionLabel
visible: stackView.currentItem.objectName === "Settings" visible: stackView.currentItem.objectName === qsTr("Settings")
text: "Version " + SystemProperties.versionString text: qsTr("Version %1").arg(SystemProperties.versionString)
font.pointSize: 12 font.pointSize: 12
horizontalAlignment: Qt.AlignRight horizontalAlignment: Qt.AlignRight
verticalAlignment: Qt.AlignVCenter verticalAlignment: Qt.AlignVCenter
@@ -233,14 +261,14 @@ ApplicationWindow {
NavigableToolButton { NavigableToolButton {
id: discordButton id: discordButton
visible: SystemProperties.hasBrowser && visible: SystemProperties.hasBrowser &&
stackView.currentItem.objectName === "Settings" stackView.currentItem.objectName === qsTr("Settings")
iconSource: "qrc:/res/Discord-Logo-White.svg" iconSource: "qrc:/res/Discord-Logo-White.svg"
ToolTip.delay: 1000 ToolTip.delay: 1000
ToolTip.timeout: 3000 ToolTip.timeout: 3000
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: "Join our community on Discord" ToolTip.text: qsTr("Join our community on Discord")
// TODO need to make sure browser is brought to foreground. // TODO need to make sure browser is brought to foreground.
onClicked: Qt.openUrlExternally("https://moonlight-stream.org/discord"); onClicked: Qt.openUrlExternally("https://moonlight-stream.org/discord");
@@ -252,14 +280,14 @@ ApplicationWindow {
NavigableToolButton { NavigableToolButton {
id: addPcButton id: addPcButton
visible: stackView.currentItem.objectName === "Computers" visible: stackView.currentItem.objectName === qsTr("Computers")
iconSource: "qrc:/res/ic_add_to_queue_white_48px.svg" iconSource: "qrc:/res/ic_add_to_queue_white_48px.svg"
ToolTip.delay: 1000 ToolTip.delay: 1000
ToolTip.timeout: 3000 ToolTip.timeout: 3000
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: "Add PC manually" + (newPcShortcut.nativeText ? (" ("+newPcShortcut.nativeText+")") : "") ToolTip.text: qsTr("Add PC manually") + (newPcShortcut.nativeText ? (" ("+newPcShortcut.nativeText+")") : "")
Shortcut { Shortcut {
id: newPcShortcut id: newPcShortcut
@@ -299,7 +327,7 @@ ApplicationWindow {
function updateAvailable(version, url) function updateAvailable(version, url)
{ {
ToolTip.text = "Update available for Moonlight: Version " + version ToolTip.text = qsTr("Update available for Moonlight: Version %1").arg(version)
updateButton.browserUrl = url updateButton.browserUrl = url
updateButton.visible = true updateButton.visible = true
} }
@@ -323,7 +351,7 @@ ApplicationWindow {
ToolTip.delay: 1000 ToolTip.delay: 1000
ToolTip.timeout: 3000 ToolTip.timeout: 3000
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: "Help" + (helpShortcut.nativeText ? (" ("+helpShortcut.nativeText+")") : "") ToolTip.text: qsTr("Help") + (helpShortcut.nativeText ? (" ("+helpShortcut.nativeText+")") : "")
Shortcut { Shortcut {
id: helpShortcut id: helpShortcut
@@ -346,11 +374,11 @@ ApplicationWindow {
ToolTip.delay: 1000 ToolTip.delay: 1000
ToolTip.timeout: 3000 ToolTip.timeout: 3000
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: "Gamepad Mapper" ToolTip.text: qsTr("Gamepad Mapper")
iconSource: "qrc:/res/ic_videogame_asset_white_48px.svg" iconSource: "qrc:/res/ic_videogame_asset_white_48px.svg"
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "Gamepad Mapping") onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", qsTr("Gamepad Mapping"))
Keys.onDownPressed: { Keys.onDownPressed: {
stackView.currentItem.forceActiveFocus(Qt.TabFocus) stackView.currentItem.forceActiveFocus(Qt.TabFocus)
@@ -362,7 +390,7 @@ ApplicationWindow {
iconSource: "qrc:/res/settings.svg" iconSource: "qrc:/res/settings.svg"
onClicked: navigateTo("qrc:/gui/SettingsView.qml", "Settings") onClicked: navigateTo("qrc:/gui/SettingsView.qml", qsTr("Settings"))
Keys.onDownPressed: { Keys.onDownPressed: {
stackView.currentItem.forceActiveFocus(Qt.TabFocus) stackView.currentItem.forceActiveFocus(Qt.TabFocus)
@@ -377,31 +405,31 @@ ApplicationWindow {
ToolTip.delay: 1000 ToolTip.delay: 1000
ToolTip.timeout: 3000 ToolTip.timeout: 3000
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: "Settings" + (settingsShortcut.nativeText ? (" ("+settingsShortcut.nativeText+")") : "") ToolTip.text: qsTr("Settings") + (settingsShortcut.nativeText ? (" ("+settingsShortcut.nativeText+")") : "")
} }
} }
} }
ErrorMessageDialog { ErrorMessageDialog {
id: noHwDecoderDialog id: noHwDecoderDialog
text: "No functioning hardware accelerated H.264 video decoder was detected by Moonlight. " + text: qsTr("No functioning hardware accelerated H.264 video decoder was detected by Moonlight. " +
"Your streaming performance may be severely degraded in this configuration." "Your streaming performance may be severely degraded in this configuration.")
helpText: "Click the Help button for more information on solving this problem." 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" helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems"
} }
ErrorMessageDialog { ErrorMessageDialog {
id: xWaylandDialog id: xWaylandDialog
text: "Hardware acceleration doesn't work on XWayland. Continuing on XWayland may result in poor streaming performance. " + text: qsTr("Hardware acceleration doesn't work on XWayland. Continuing on XWayland may result in poor streaming performance. " +
"Try running with QT_QPA_PLATFORM=wayland or switch to X11." "Try running with QT_QPA_PLATFORM=wayland or switch to X11.")
helpText: "Click the Help button for more information." helpText: qsTr("Click the Help button for more information.")
helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems" helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Fixing-Hardware-Decoding-Problems"
} }
NavigableMessageDialog { NavigableMessageDialog {
id: wow64Dialog id: wow64Dialog
standardButtons: Dialog.Ok | Dialog.Cancel standardButtons: Dialog.Ok | Dialog.Cancel
text: "This PC is running a 64-bit version of Windows. Please download the x64 version of Moonlight for the best streaming performance." text: qsTr("This version of Moonlight isn't optimized for your PC. Please download the '%1' version of Moonlight for the best streaming performance.").arg(SystemProperties.friendlyNativeArchName)
onAccepted: { onAccepted: {
Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-qt/releases"); Qt.openUrlExternally("https://github.com/moonlight-stream/moonlight-qt/releases");
} }
@@ -410,9 +438,9 @@ ApplicationWindow {
ErrorMessageDialog { ErrorMessageDialog {
id: unmappedGamepadDialog id: unmappedGamepadDialog
property string unmappedGamepads : "" property string unmappedGamepads : ""
text: "Moonlight detected gamepads without a mapping:\n" + unmappedGamepads text: qsTr("Moonlight detected gamepads without a mapping:") + "\n" + unmappedGamepads
helpTextSeparator: "\n\n" helpTextSeparator: "\n\n"
helpText: "Click the Help button for information on how to map your gamepads." helpText: qsTr("Click the Help button for information on how to map your gamepads.")
helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Gamepad-Mapping" helpUrl: "https://github.com/moonlight-stream/moonlight-docs/wiki/Gamepad-Mapping"
} }
@@ -420,7 +448,7 @@ ApplicationWindow {
NavigableMessageDialog { NavigableMessageDialog {
id: quitConfirmationDialog id: quitConfirmationDialog
standardButtons: Dialog.Yes | Dialog.No standardButtons: Dialog.Yes | Dialog.No
text: "Are you sure you want to quit?" text: qsTr("Are you sure you want to quit?")
// For keyboard/gamepad navigation // For keyboard/gamepad navigation
onAccepted: Qt.quit() onAccepted: Qt.quit()
} }
@@ -450,7 +478,7 @@ ApplicationWindow {
NavigableDialog { NavigableDialog {
id: addPcDialog id: addPcDialog
property string label: "Enter the IP address of your GameStream PC:" property string label: qsTr("Enter the IP address of your GameStream PC:")
standardButtons: Dialog.Ok | Dialog.Cancel standardButtons: Dialog.Ok | Dialog.Cancel
@@ -465,7 +493,7 @@ ApplicationWindow {
onAccepted: { onAccepted: {
if (editText.text) { if (editText.text) {
ComputerManager.addNewHost(editText.text, false) ComputerManager.addNewHost(editText.text.trim(), false)
} }
} }
@@ -483,6 +511,10 @@ ApplicationWindow {
Keys.onReturnPressed: { Keys.onReturnPressed: {
addPcDialog.accept() addPcDialog.accept()
} }
Keys.onEnterPressed: {
addPcDialog.accept()
}
} }
} }
} }
+21 -1
View File
@@ -4,6 +4,7 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QWindow> #include <QWindow>
#include "settings/streamingpreferences.h"
#include "settings/mappingmanager.h" #include "settings/mappingmanager.h"
#define AXIS_NAVIGATION_REPEAT_DELAY 150 #define AXIS_NAVIGATION_REPEAT_DELAY 150
@@ -14,7 +15,7 @@ SdlGamepadKeyNavigation::SdlGamepadKeyNavigation()
m_LastAxisNavigationEventTime(0) m_LastAxisNavigationEventTime(0)
{ {
m_PollingTimer = new QTimer(this); m_PollingTimer = new QTimer(this);
connect(m_PollingTimer, SIGNAL(timeout()), this, SLOT(onPollingTimerFired())); connect(m_PollingTimer, &QTimer::timeout, this, &SdlGamepadKeyNavigation::onPollingTimerFired);
} }
SdlGamepadKeyNavigation::~SdlGamepadKeyNavigation() SdlGamepadKeyNavigation::~SdlGamepadKeyNavigation()
@@ -88,6 +89,7 @@ void SdlGamepadKeyNavigation::disable()
void SdlGamepadKeyNavigation::onPollingTimerFired() void SdlGamepadKeyNavigation::onPollingTimerFired()
{ {
SDL_Event event; SDL_Event event;
StreamingPreferences prefs;
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&event)) {
switch (event.type) { switch (event.type) {
@@ -104,6 +106,24 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
event.type == SDL_CONTROLLERBUTTONDOWN ? event.type == SDL_CONTROLLERBUTTONDOWN ?
QEvent::Type::KeyPress : QEvent::Type::KeyRelease; QEvent::Type::KeyPress : QEvent::Type::KeyRelease;
// Swap face buttons if needed
if (prefs.swapFaceButtons) {
switch (event.cbutton.button) {
case SDL_CONTROLLER_BUTTON_A:
event.cbutton.button = SDL_CONTROLLER_BUTTON_B;
break;
case SDL_CONTROLLER_BUTTON_B:
event.cbutton.button = SDL_CONTROLLER_BUTTON_A;
break;
case SDL_CONTROLLER_BUTTON_X:
event.cbutton.button = SDL_CONTROLLER_BUTTON_Y;
break;
case SDL_CONTROLLER_BUTTON_Y:
event.cbutton.button = SDL_CONTROLLER_BUTTON_X;
break;
}
}
switch (event.cbutton.button) { switch (event.cbutton.button) {
case SDL_CONTROLLER_BUTTON_DPAD_UP: case SDL_CONTROLLER_BUTTON_DPAD_UP:
if (m_UiNavMode) { if (m_UiNavMode) {
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.
+1000
View File
@@ -0,0 +1,1000 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>AppView</name>
<message>
<location filename="../gui/AppView.qml" line="144"/>
<location filename="../gui/AppView.qml" line="298"/>
<source>Resume Game</source>
<translation>继续游戏</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="170"/>
<location filename="../gui/AppView.qml" line="303"/>
<source>Quit Game</source>
<translation>退出游戏</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="298"/>
<source>Launch Game</source>
<translation>启动游戏</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="311"/>
<source>Direct Launch</source>
<translation>直接启动</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="315"/>
<source>Launch this app immediately when the host is selected, bypassing the app selection grid.</source>
<translation>选择目标计算机后立即启动此应用程序,绕过应用程序选择网格。</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="324"/>
<source>Hide Game</source>
<translation>隐藏游戏</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="328"/>
<source>Hide this game from the app grid. To access hidden games, right-click on the host and choose %1.</source>
<translation>从应用程序网格中隐藏此游戏。要访问隐藏的游戏,请右键单击目标计算机并选择 %1。</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="328"/>
<source>View All Apps</source>
<translation>查看所有应用程序</translation>
</message>
<message>
<location filename="../gui/AppView.qml" line="343"/>
<source>Are you sure you want to quit %1? Any unsaved progress will be lost.</source>
<translation>你确定要退出 %1 吗?所有未保存的游戏进度都将丢失。</translation>
</message>
</context>
<context>
<name>CliQuitStreamSegue</name>
<message>
<location filename="../gui/CliQuitStreamSegue.qml" line="9"/>
<source>Establishing connection to PC...</source>
<translation>正在建立与目标计算机的连接...</translation>
</message>
<message>
<location filename="../gui/CliQuitStreamSegue.qml" line="13"/>
<source>Quitting app...</source>
<translation>应用程序退出中...</translation>
</message>
</context>
<context>
<name>CliStartStreamSegue</name>
<message>
<location filename="../gui/CliStartStreamSegue.qml" line="9"/>
<source>Establishing connection to PC...</source>
<translation>正在建立与目标计算机的连接...</translation>
</message>
<message>
<location filename="../gui/CliStartStreamSegue.qml" line="13"/>
<source>Loading app list...</source>
<translation>应用程序列表加载中...</translation>
</message>
<message>
<location filename="../gui/CliStartStreamSegue.qml" line="81"/>
<source>Are you sure you want to quit %1? Any unsaved progress will be lost.</source>
<translation>你确定要退出 %1 吗?任何未保存的进度都将丢失。</translation>
</message>
</context>
<context>
<name>GamepadMapper</name>
<message>
<location filename="../gui/GamepadMapper.qml" line="4"/>
<source>Gamepad Mapping</source>
<translation>手柄按键映射</translation>
</message>
</context>
<context>
<name>NvHTTP</name>
<message>
<location filename="../backend/nvhttp.cpp" line="327"/>
<source>Missing audio capture device. Reinstalling GeForce Experience should resolve this error.</source>
<translation>缺少音频捕获设备。重新安装 GeForce Experience 可以解决此错误。</translation>
</message>
</context>
<context>
<name>PcView</name>
<message>
<location filename="../gui/PcView.qml" line="20"/>
<source>Computers</source>
<translation>计算机列表</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="67"/>
<source>Unable to connect to the specified PC.</source>
<translation>无法连接到目标计算机。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="70"/>
<source>This PC&apos;s Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network.</source>
<translation>此计算机的互联网连接被阻塞。当连接到此网络时,互联网上的流式传输可能无法工作。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="73"/>
<source>Click the Help button for possible solutions.</source>
<translation>单击帮助按钮以获取可能的解决方案。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="102"/>
<source>Searching for PCs on your local network with NVIDIA GameStream enabled...</source>
<translation>正在本地网络上搜索启用 NVIDIA GameStrea 的计算机...</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="103"/>
<source>Automatic PC discovery is disabled. Add your PC manually.</source>
<translation>已禁用计算机自动发现。需要手动添加计算机。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="171"/>
<source>PC Status: %1</source>
<translation>计算机状态: %1</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="171"/>
<source>Online</source>
<translation>在线</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="171"/>
<source>Offline</source>
<translation>离线</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="177"/>
<source>View All Apps</source>
<translation>查看所有应用程序</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="187"/>
<source>Wake PC</source>
<translation>唤醒目标计算机</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="193"/>
<source>Test Network</source>
<translation>网络测试</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="202"/>
<source>Rename PC</source>
<translation>重命名计算机</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="211"/>
<source>Delete PC</source>
<translation>删除计算机</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="224"/>
<source>The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="247"/>
<source>You cannot pair while a previous session is still running on the host PC. Quit any running games or reboot the host PC, then try pairing again.</source>
<translation>当上一个会话仍在运行时,无法进行配对。请退出所有正在运行的游戏或重新启动目标计算机,然后重试配对。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="302"/>
<source>Please enter %1 on your GameStream PC. This dialog will close when pairing is completed.</source>
<translation>请在启用 GameStream 的计算机上输入 %1。配对完成后,此对话框将关闭。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="313"/>
<source>Are you sure you want to remove this PC?</source>
<translation>确定要移除这个计算机吗?</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="330"/>
<source>Moonlight is testing your network connection to determine if NVIDIA GameStream is blocked.</source>
<translation>Moonlight 正在测试你的网络连接,以检测 NVIDIA GameStream 是否被阻止。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="330"/>
<source>This may take a few seconds…</source>
<translation>这可能需要几秒钟……</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="337"/>
<source>The network test could not be performed because none of Moonlight&apos;s connection testing servers were reachable from this PC. Check your Internet connection or try again later.</source>
<translation>无法执行网络测试,因为此计算机无法访问 Moonlight 的连接测试服务器。请检查你的互联网连接或稍后再试。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="341"/>
<source>This network does not appear to be blocking Moonlight. If you still have trouble connecting, check your PC&apos;s firewall settings.</source>
<translation>此网络似乎没有阻止 Moonlight。如果仍然无法连接,请检查计算机的防火墙设置。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="341"/>
<source>If you are trying to stream over the Internet, install the Moonlight Internet Hosting Tool on your gaming PC and run the included Internet Streaming Tester to check your gaming PC&apos;s Internet connection.</source>
<translation>如果你想通过互联网进行流式传输,请在你的目标计算机上安装 Moonlight Internet Hosting 工具,并运行包含的互联网流式传输测试程序来检查你目标计算机的互联网连接。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="345"/>
<source>Your PC&apos;s current network connection seems to be blocking Moonlight. Streaming over the Internet may not work while connected to this network.</source>
<translation>此计算机当前的网络连接似乎被阻塞了。当连接到此网络时,互联网上的流式传输可能无法工作。</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="345"/>
<source>The following network ports were blocked:</source>
<translation>下列网络端口被阻止:</translation>
</message>
<message>
<location filename="../gui/PcView.qml" line="357"/>
<source>Enter the new name for this PC:</source>
<translation>输入这台计算机的新名称:</translation>
</message>
</context>
<context>
<name>PendingQuitTask</name>
<message>
<location filename="../backend/computermanager.cpp" line="542"/>
<source>The running game wasn&apos;t started by this PC. You must quit the game on the host PC manually or use the device that originally started the game.</source>
<translation>正在运行的游戏不是由这台计算机启动的。你必须在目标计算机上手动退出游戏或使用最初启动游戏的设备。</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<location filename="../cli/quitstream.cpp" line="76"/>
<location filename="../cli/startstream.cpp" line="133"/>
<source>Failed to connect to %1</source>
<translation>无法连接到 %1</translation>
</message>
<message>
<location filename="../cli/quitstream.cpp" line="88"/>
<location filename="../cli/startstream.cpp" line="89"/>
<source>Computer %1 has not been paired. Please open Moonlight to pair before streaming.</source>
<translation>计算机 %1 尚未配对。</translation>
</message>
<message>
<location filename="../cli/quitstream.cpp" line="102"/>
<location filename="../cli/startstream.cpp" line="126"/>
<source>Quitting app failed, reason: %1</source>
<translation>退出应用程序失败,原因: %1</translation>
</message>
<message>
<location filename="../cli/startstream.cpp" line="137"/>
<source>Failed to find application %1</source>
<translation>找不到应用程序 %1</translation>
</message>
</context>
<context>
<name>QuitSegue</name>
<message>
<location filename="../gui/QuitSegue.qml" line="12"/>
<source>Quitting %1...</source>
<translation>正在退出 %1...</translation>
</message>
</context>
<context>
<name>Session</name>
<message>
<location filename="../streaming/session.cpp" line="89"/>
<source>No video received from host.</source>
<translation>没有从目标计算机接收到视频画面。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="90"/>
<source>Check your firewall and port forwarding rules for port(s): %1</source>
<translation>检查防火墙和端口转发规则: %1</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="95"/>
<source>Your network connection isn&apos;t performing well. Reduce your video bitrate setting or try a faster connection.</source>
<translation>网络连接性能不佳。请降低视频比特率或尝试更快的网络连接。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="100"/>
<source>Something went wrong on your host PC when starting the stream.</source>
<translation>目标计算机启动流式传输时出现问题。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="101"/>
<source>Make sure you don&apos;t have any DRM-protected content open on your host PC. You can also try restarting your host PC.</source>
<translation>确保目标计算机上没有打开任何受 DRM 保护的内容。你也可以尝试重新启动目标计算机。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="102"/>
<source>If the issue persists, try reinstalling your GPU drivers and GeForce Experience.</source>
<translation>如果问题仍然存在,请尝试重新安装显卡驱动程序和 GeForce Experience。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="107"/>
<source>Connection terminated</source>
<translation>会话已终止</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="592"/>
<source>The version of GeForce Experience on %1 is not supported by this build of Moonlight. You must update Moonlight to stream from %1.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="597"/>
<source>Your selection to enable remote desktop mouse mode may cause problems in games.</source>
<translation>启用远程桌面鼠标模式可能会导致游戏出现问题。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="602"/>
<source>HDR is not supported with software decoding.</source>
<translation>软件解码不支持 HDR。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="606"/>
<source>Your settings selection to force software decoding may cause poor streaming performance.</source>
<translation>你选择的用于强制软件解码的设置可能会导致流式传输性能不佳。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="611"/>
<source>Using unsupported FPS options may cause stuttering or lag.</source>
<translation>使用不受支持的 FPS 选项可能会导致卡顿或延迟。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="614"/>
<source>V-sync will be disabled when streaming at a higher frame rate than the display.</source>
<translation>当流式传输的帧率高于显示时,垂直同步将被禁用。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="631"/>
<source>Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.</source>
<translation>根据你的选择使用软件解码,以在没有显卡支持的情况下强制 HEVC。这可能会导致流式传输性能不佳。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="634"/>
<source>This PC&apos;s GPU doesn&apos;t support HEVC decoding.</source>
<translation>此计算机的显卡不支持 HEVC 解码。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="641"/>
<source>Your host PC GPU doesn&apos;t support HEVC. A GeForce GTX 900-series (Maxwell) or later GPU is required for HEVC streaming.</source>
<translation>目标计算机的显卡不支持 HEVC。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="658"/>
<source>%1 doesn&apos;t support HDR10.</source>
<translation>%1 不支持 HDR10。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="662"/>
<source>Your host PC GPU doesn&apos;t support HDR streaming. A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.</source>
<translation>目标计算机的显卡不支持 HDR 流。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="671"/>
<source>This PC&apos;s GPU doesn&apos;t support HEVC Main10 decoding for HDR streaming.</source>
<translation>此计算机的显卡不支持 HEVC Main10 解码 HDR 流。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="684"/>
<source>GeForce Experience 3.0 or higher is required for 4K streaming.</source>
<translation>4K 流式传输需要 GeForce Experience 3.0 或更高版本。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="699"/>
<source>Your selected surround sound setting is not supported by the current audio device.</source>
<translation>当前音频设备不支持你选择的环绕声设置。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="706"/>
<source>Failed to open audio device. Audio will be unavailable during this session.</source>
<translation>无法打开音频设备。音频在此会话期间将不可用。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="711"/>
<source>An attached gamepad has no mapping and won&apos;t be usable. Visit the Moonlight help to resolve this.</source>
<translation>附加的手柄没有映射,因此无法使用。请访问 Moonlight 帮助解决此问题。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="719"/>
<source>Your host PC&apos;s GPU doesn&apos;t support streaming video resolutions over 4K.</source>
<translation>目标计算机的显卡不支持 4K 以上的流式传输视频分辨率。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="723"/>
<source>Video resolutions over 4K are only supported by the HEVC codec.</source>
<translation>只有 HEVC 编解码器才支持 4K 以上的视频分辨率。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="737"/>
<source>Your selection to force hardware decoding cannot be satisfied due to missing hardware decoding support on this PC&apos;s GPU.</source>
<translation>由于此计算机的显卡上缺少硬件解码支持,无法满足强制硬件解码的选择。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="740"/>
<source>Your codec selection and force hardware decoding setting are not compatible. This PC&apos;s GPU lacks support for decoding your chosen codec.</source>
<translation>你选择的编解码器和强制硬件解码设置不兼容。此计算机的显卡不支持解码你选择的编解码器。</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="1041"/>
<source>GeForce Experience returned error: %1</source>
<translation>GeForce Experience 返回错误: %1</translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="1519"/>
<source>Unable to initialize video decoder. Please check your streaming settings and try again.</source>
<translation>无法初始化视频解码器。请检查你的流式传输设置,然后重试。</translation>
</message>
</context>
<context>
<name>SettingsView</name>
<message>
<location filename="../gui/SettingsView.qml" line="12"/>
<source>Settings</source>
<translation>设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="60"/>
<source>Basic Settings</source>
<translation>基本设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="70"/>
<source>Resolution and FPS</source>
<translation>分辨率和 FPS</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="78"/>
<source>Setting values too high for your PC or network connection may cause lag, stuttering, or errors.</source>
<translation>如果你的计算机或网络连接设置的值过高,可能会导致延迟,卡顿或错误。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="209"/>
<source>720p</source>
<translation>720p</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="215"/>
<source>1080p</source>
<translation>1080p</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="221"/>
<source>1440p</source>
<translation>1440p</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="227"/>
<source>4K</source>
<translation>4K</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="331"/>
<source>Custom resolutions are not officially supported by GeForce Experience, so it will not set your host display resolution. You will need to set it manually while in game.</source>
<translation>自定义分辨率不受 GeForce Experience 的正式支持,因此不会设置目标计算机的分辨率。你需要在游戏中手动设置。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="332"/>
<source>Resolutions that are not supported by your client or host PC may cause streaming errors.</source>
<translation>使用当前计算机或目标计算机不支持的分辨率可能会导致流式传输错误。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="338"/>
<source>Enter a custom resolution:</source>
<translation>输入自定义分辨率:</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="406"/>
<location filename="../gui/SettingsView.qml" line="407"/>
<location filename="../gui/SettingsView.qml" line="427"/>
<location filename="../gui/SettingsView.qml" line="430"/>
<source>%1 FPS</source>
<translation>%1 FPS</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="412"/>
<location filename="../gui/SettingsView.qml" line="415"/>
<location filename="../gui/SettingsView.qml" line="424"/>
<location filename="../gui/SettingsView.qml" line="437"/>
<location filename="../gui/SettingsView.qml" line="440"/>
<source>%1 FPS (Unsupported)</source>
<translation>%1 FPS (不支持)</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="493"/>
<source>Video bitrate:</source>
<translation>视频比特率:</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="501"/>
<source>Lower the bitrate on slower connections. Raise the bitrate to increase image quality.</source>
<translation>在较慢的连接上降低比特率。提高比特率以提高图像质量。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="519"/>
<source>Video bitrate: %1 Mbps</source>
<translation>视频比特率: %1 Mbps</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="527"/>
<source>Display mode</source>
<translation>显示模式</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="545"/>
<source> (Recommended)</source>
<translation> (推荐)</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="573"/>
<location filename="../gui/SettingsView.qml" line="868"/>
<source>Fullscreen</source>
<translation>全屏</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="577"/>
<source>Borderless windowed</source>
<translation>无边框窗口</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="581"/>
<location filename="../gui/SettingsView.qml" line="860"/>
<source>Windowed</source>
<translation>窗口化</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="592"/>
<source>Fullscreen generally provides the best performance, but borderless windowed may work better with features like macOS Spaces, Alt+Tab, screenshot tools, on-screen overlays, etc.</source>
<translation>全屏通常提供最好的性能,但是无边框窗口可以更好地与 macOS Spaces、Alt+Tab、屏幕截图工具、屏幕覆盖等功能配合使用。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="599"/>
<source>V-Sync</source>
<translation>垂直同步</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="609"/>
<source>Disabling V-Sync allows sub-frame rendering latency, but it can display visible tearing</source>
<translation>禁用垂直同步可允许子帧渲染延迟,但会显示可见的撕裂</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="616"/>
<source>Frame pacing</source>
<translation>帧速调节</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="626"/>
<source>Frame pacing reduces micro-stutter by delaying frames that come in too early</source>
<translation>通过延迟过早进入的帧来减少卡顿</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="636"/>
<source>Audio Settings</source>
<translation>音频设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="646"/>
<source>Audio configuration</source>
<translation>音频配置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="671"/>
<source>Stereo</source>
<translation>立体声</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="675"/>
<source>5.1 surround sound</source>
<translation>5.1 环绕声</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="679"/>
<source>7.1 surround sound</source>
<translation>7.1 环绕声</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="693"/>
<source>Mute host PC speakers while streaming</source>
<translation>流式传输启动时将目标计算机的扬声器静音</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="703"/>
<source>You must restart any game currently in progress for this setting to take effect</source>
<translation>要使此设置生效,必须重新启动当前正在进行的所有游戏</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="709"/>
<source>Mute audio stream when Moonlight is not the active window</source>
<translation>当 Moonlight 失去焦点时静音</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="720"/>
<source>Mutes Moonlight&apos;s audio when you Alt+Tab out of the stream or click on a different window.</source>
<translation>当你用 Alt+Tab 键离开流式传输或者点击其他窗口时,Moonlight 将静音。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="729"/>
<source>UI Settings</source>
<translation>界面设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="739"/>
<source>Language</source>
<translation>语言</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="765"/>
<source>Automatic</source>
<translation>自动化</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="813"/>
<source>You must restart Moonlight for this change to take effect</source>
<translation>你必须重新启动 Moonlight 才能使更改生效</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="827"/>
<source>GUI display mode</source>
<translation>窗口显示模式</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="864"/>
<source>Maximized</source>
<translation>最大化</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="881"/>
<source>Show connection quality warnings</source>
<translation>显示连接质量警告</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="893"/>
<source>Discord Rich Presence integration</source>
<translation>Discord Rich Presence 集成</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="903"/>
<source>Updates your Discord status to display the name of the game you&apos;re streaming.</source>
<translation>更新你的 Discord 状态,以显示正在运行的游戏名称。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="921"/>
<source>Input Settings</source>
<translation>输入设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="932"/>
<source>Optimize mouse for remote desktop instead of games</source>
<translation>为远程桌面而不是游戏优化鼠标</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="942"/>
<source>This enables seamless mouse control without capturing the client&apos;s mouse cursor. It is ideal for remote desktop usage but will not work in most games.</source>
<translation>这可以实现无缝鼠标控制,而无需捕获客户端的鼠标光标。这是理想的远程桌面使用方案,但不会在大多数游戏中工作。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="943"/>
<source>You can toggle this while streaming using Ctrl+Alt+Shift+M.</source>
<translation>你可以使用 Ctrl+Alt+Shift+M 切换鼠标控制模式。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="944"/>
<source>NOTE: Due to a bug in GeForce Experience, this option may not work properly if your host PC has multiple monitors.</source>
<translation>注意:由于 GeForce Experience 的 Bug,如果你的目标计算机有多个显示器,则此选项可能无法正常工作。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="954"/>
<source>Capture system keyboard shortcuts</source>
<translation>捕获系统快捷键</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="962"/>
<source>This enables the capture of system-wide keyboard shortcuts like Alt+Tab that would normally be handled by the client OS while streaming.</source>
<translation>这样可以捕获系统范围的键盘快捷键,例如 Alt + Tab,这些快捷键通常在流式传输时由客户端系统处理。</translation>
</message>
<message>
<source>Capture system keyboard shortcuts while streaming in fullscreen</source>
<translation type="vanished">在全屏流式传输时捕获系统键盘快捷键</translation>
</message>
<message>
<source>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.</source>
<translation type="vanished">这允许捕获系统范围内的键盘快捷键(如Alt+Tab),这些快捷键通常在全屏流式传输时由客户端操作系统处理。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="963"/>
<source>NOTE: Certain keyboard shortcuts like Ctrl+Alt+Del on Windows cannot be intercepted by any application, including Moonlight.</source>
<translation>注意:Windows 上的某些键盘快捷键(如 Ctrl+Alt+Del)不能被任何应用程序截获,包括 Moonlight。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="992"/>
<source>in fullscreen</source>
<translation>全屏</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="996"/>
<source>always</source>
<translation>总是</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1026"/>
<source>Use touchscreen as a virtual trackpad</source>
<translation>将触摸屏用作虚拟触控板</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1036"/>
<source>When checked, the touchscreen acts like a trackpad. When unchecked, the touchscreen will directly control the mouse pointer.</source>
<translation>选中时,触摸屏就像一个触控板。取消选中时,触摸屏将直接控制鼠标指针。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1043"/>
<source>Swap left and right mouse buttons</source>
<translation>交换鼠标左键和右键</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1055"/>
<source>Reverse mouse scrolling direction</source>
<translation>反转鼠标滚动方向</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1069"/>
<source>Gamepad Settings</source>
<translation>手柄设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1079"/>
<source>Swap A/B and X/Y gamepad buttons</source>
<translation>交换手柄的 A/B 和 X/Y 按钮</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1097"/>
<source>This switches gamepads into a Nintendo-style button layout</source>
<translation>这会将手柄切换为任天堂风格的按钮布局</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1103"/>
<source>Force gamepad #1 always connected</source>
<translation>强制手柄 #1 始终连接</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1113"/>
<source>Forces a single gamepad to always stay connected to the host, even if no gamepads are actually connected to this PC.</source>
<translation>强制单个手柄始终与目标计算机保持连接,即使没有任何手柄实际连接到此计算机。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1114"/>
<source>Only enable this option when streaming a game that doesn&apos;t support gamepads being connected after startup.</source>
<translation>仅在游戏启动后不支持手柄连接时才启用此选项。</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1121"/>
<source>Enable mouse control with gamepads by holding the &apos;Start&apos; button</source>
<translation>按住手柄的“Start”按钮启用鼠标控制</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1132"/>
<source>Process gamepad input when Moonlight is in the background</source>
<translation>当 Moonlight 在后台时处理手柄输入</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1143"/>
<source>Allows Moonlight to capture gamepad inputs even if it&apos;s not the current window in focus</source>
<translation>即使不是焦点窗口,也允许 Moonlight 捕获手柄的输入</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1152"/>
<source>Host Settings</source>
<translation>目标计算机设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1162"/>
<source>Optimize game settings for streaming</source>
<translation>优化游戏设置以进行流式传输</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1173"/>
<source>Quit app on host PC after ending stream</source>
<translation>流式传输结束后退出目标计算机上的应用程序</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1183"/>
<source>This will close the app or game you are streaming when you end your stream. You will lose any unsaved progress!</source>
<translation>当你结束流式传输时,这将关闭正在运行的应用程序或游戏。这会导致你失去所有未保存的游戏进度!</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1192"/>
<source>Advanced Settings</source>
<translation>高级设置</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1202"/>
<source>Video decoder</source>
<translation>视频解码器</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1227"/>
<location filename="../gui/SettingsView.qml" line="1273"/>
<source>Automatic (Recommended)</source>
<translation>自动 (推荐)</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1231"/>
<source>Force software decoding</source>
<translation>强制软件解码</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1235"/>
<source>Force hardware decoding</source>
<translation>强制硬件解码</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1248"/>
<source>Video codec</source>
<translation>视频编解码器</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1277"/>
<source>H.264</source>
<translation>H.264</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1281"/>
<source>HEVC (H.265)</source>
<translation>HEVC (H.265)</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1298"/>
<source>Unlock unsupported FPS options</source>
<translation>解锁不受支持的 FPS 选项</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1317"/>
<source>Automatically find PCs on the local network (Recommended)</source>
<translation>自动在本地网络上查找计算机 (推荐)</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1342"/>
<source>Automatically detect blocked connections (Recommended)</source>
<translation>自动检测被阻止的连接 (推荐)</translation>
</message>
</context>
<context>
<name>StreamSegue</name>
<message>
<location filename="../gui/StreamSegue.qml" line="11"/>
<source>Resuming %1...</source>
<translation>正在恢复 %1...</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="12"/>
<location filename="../gui/StreamSegue.qml" line="19"/>
<source>Starting %1...</source>
<translation>正在启动 %1...</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="25"/>
<source>Starting %1 failed: Error %2</source>
<translation>启动 %1 失败: 错误 %2</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="28"/>
<source>Check your firewall and port forwarding rules for port(s): %1</source>
<translation>检查防火墙和端口转发规则: %1</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="74"/>
<source>This PC&apos;s Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network.</source>
<translation>此计算机的互联网连接被阻塞。当连接到此网络时,互联网上的流式传输可能无法工作。</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="161"/>
<source>Tip:</source>
<translation>提示:</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="161"/>
<source>Press %1 to disconnect your session</source>
<translation>按 %1 来断开会话</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="162"/>
<source>Start+Select+L1+R1</source>
<translation>Start+Select+L1+R1</translation>
</message>
<message>
<location filename="../gui/StreamSegue.qml" line="162"/>
<source>Ctrl+Alt+Shift+Q</source>
<translation>Ctrl+Alt+Shift+Q</translation>
</message>
</context>
<context>
<name>main</name>
<message>
<location filename="../gui/main.qml" line="254"/>
<location filename="../gui/main.qml" line="264"/>
<location filename="../gui/main.qml" line="393"/>
<location filename="../gui/main.qml" line="408"/>
<source>Settings</source>
<translation>设置</translation>
</message>
<message>
<location filename="../gui/main.qml" line="255"/>
<source>Version %1</source>
<translation>版本 %1</translation>
</message>
<message>
<location filename="../gui/main.qml" line="271"/>
<source>Join our community on Discord</source>
<translation>加入我们的社区</translation>
</message>
<message>
<location filename="../gui/main.qml" line="283"/>
<source>Computers</source>
<translation>计算机列表</translation>
</message>
<message>
<location filename="../gui/main.qml" line="290"/>
<source>Add PC manually</source>
<translation>手动添加计算机</translation>
</message>
<message>
<location filename="../gui/main.qml" line="330"/>
<source>Update available for Moonlight: Version %1</source>
<translation>Moonlight 可用更新:版本 %1</translation>
</message>
<message>
<location filename="../gui/main.qml" line="354"/>
<source>Help</source>
<translation>帮助</translation>
</message>
<message>
<location filename="../gui/main.qml" line="377"/>
<source>Gamepad Mapper</source>
<translation>手柄按键映射</translation>
</message>
<message>
<location filename="../gui/main.qml" line="381"/>
<source>Gamepad Mapping</source>
<translation>手柄按键映射</translation>
</message>
<message>
<location filename="../gui/main.qml" line="415"/>
<source>No functioning hardware accelerated H.264 video decoder was detected by Moonlight. Your streaming performance may be severely degraded in this configuration.</source>
<translation>Moonlight 未检测到功能正常的硬件加速 H.264 视频解码器。</translation>
</message>
<message>
<location filename="../gui/main.qml" line="417"/>
<source>Click the Help button for more information on solving this problem.</source>
<translation>单击帮助按钮以获取有关解决此问题的更多信息。</translation>
</message>
<message>
<location filename="../gui/main.qml" line="423"/>
<source>Hardware acceleration doesn&apos;t work on XWayland. Continuing on XWayland may result in poor streaming performance. Try running with QT_QPA_PLATFORM=wayland or switch to X11.</source>
<translation>硬件加速在 XWayland 上不起作用。在 XWayland 上继续操作可能会导致流式传输性能下降。</translation>
</message>
<message>
<location filename="../gui/main.qml" line="425"/>
<source>Click the Help button for more information.</source>
<translation>单击帮助按钮以获取更多信息。</translation>
</message>
<message>
<location filename="../gui/main.qml" line="432"/>
<source>This version of Moonlight isn&apos;t optimized for your PC. Please download the &apos;%1&apos; version of Moonlight for the best streaming performance.</source>
<translation>此版本的 Moonlight 尚未针对你的计算机优化。请下载“%1”版本的 Moonlight,以获得最佳的流式传输性能。</translation>
</message>
<message>
<location filename="../gui/main.qml" line="441"/>
<source>Moonlight detected gamepads without a mapping:</source>
<translation>Moonlight 检测到没有映射的手柄:</translation>
</message>
<message>
<location filename="../gui/main.qml" line="443"/>
<source>Click the Help button for information on how to map your gamepads.</source>
<translation>单击帮助按钮以获取有关如何映射手柄的信息。</translation>
</message>
<message>
<location filename="../gui/main.qml" line="451"/>
<source>Are you sure you want to quit?</source>
<translation>确定要退出吗?</translation>
</message>
<message>
<location filename="../gui/main.qml" line="481"/>
<source>Enter the IP address of your GameStream PC:</source>
<translation>输入目标计算机的 IP 地址:</translation>
</message>
</context>
</TS>
+59 -9
View File
@@ -22,7 +22,7 @@
#include "streaming/video/ffmpeg.h" #include "streaming/video/ffmpeg.h"
#endif #endif
#if defined(Q_OS_WIN32) #if defined(Q_OS_WIN32) && defined(Q_PROCESSOR_X86)
#include "antihookingprotection.h" #include "antihookingprotection.h"
#elif defined(Q_OS_LINUX) #elif defined(Q_OS_LINUX)
#include <openssl/ssl.h> #include <openssl/ssl.h>
@@ -196,7 +196,7 @@ LONG WINAPI UnhandledExceptionHandler(struct _EXCEPTION_POINTERS *ExceptionInfo)
WCHAR dmpFileName[MAX_PATH]; WCHAR dmpFileName[MAX_PATH];
swprintf_s(dmpFileName, L"%ls\\Moonlight-%I64u.dmp", swprintf_s(dmpFileName, L"%ls\\Moonlight-%I64u.dmp",
(PWCHAR)QDir::toNativeSeparators(Path::getLogDir()).utf16(), QDateTime::currentSecsSinceEpoch()); (PWCHAR)QDir::toNativeSeparators(Path::getLogDir()).utf16(), QDateTime::currentSecsSinceEpoch());
QString qDmpFileName = QString::fromUtf16((unsigned short*)dmpFileName); QString qDmpFileName = QString::fromUtf16((const char16_t*)dmpFileName);
HANDLE dumpHandle = CreateFileW(dmpFileName, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); HANDLE dumpHandle = CreateFileW(dmpFileName, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (dumpHandle != INVALID_HANDLE_VALUE) { if (dumpHandle != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION info; MINIDUMP_EXCEPTION_INFORMATION info;
@@ -288,9 +288,10 @@ int main(int argc, char *argv[])
SetUnhandledExceptionFilter(UnhandledExceptionHandler); SetUnhandledExceptionFilter(UnhandledExceptionHandler);
#endif #endif
#if defined(Q_OS_WIN32) #if defined(Q_OS_WIN32) && defined(Q_PROCESSOR_X86)
// Force AntiHooking.dll to be statically imported and loaded // Force AntiHooking.dll to be statically imported and loaded
// by ntdll by calling a dummy function. // by ntdll on x86/x64 platforms by calling a dummy function.
// AntiHooking.dll is not currently built for ARM64.
AntiHookingDummyImport(); AntiHookingDummyImport();
#elif defined(Q_OS_LINUX) #elif defined(Q_OS_LINUX)
// Force libssl.so to be directly linked to our binary, so // Force libssl.so to be directly linked to our binary, so
@@ -305,8 +306,10 @@ int main(int argc, char *argv[])
// NB: We can't use QGuiApplication::platformName() here because it is only // NB: We can't use QGuiApplication::platformName() here because it is only
// set once the QGuiApplication is created, which is too late to enable High DPI :( // set once the QGuiApplication is created, which is too late to enable High DPI :(
if (WMUtils::isRunningWindowManager()) { if (WMUtils::isRunningWindowManager()) {
// Enable High DPI support #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
// Enable High DPI support on Qt 5.x. It is always enabled on Qt 6.0
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
// Enable fractional High DPI scaling on Qt 5.14 and later // Enable fractional High DPI scaling on Qt 5.14 and later
@@ -319,6 +322,13 @@ int main(int argc, char *argv[])
qInfo() << "Unable to detect Wayland or X11, so EGLFS will be used by default. Set QT_QPA_PLATFORM to override this."; qInfo() << "Unable to detect Wayland or X11, so EGLFS will be used by default. Set QT_QPA_PLATFORM to override this.";
qputenv("QT_QPA_PLATFORM", "eglfs"); qputenv("QT_QPA_PLATFORM", "eglfs");
if (!qEnvironmentVariableIsSet("QT_QPA_EGLFS_ALWAYS_SET_MODE")) {
qInfo() << "Setting display mode by default. Set QT_QPA_EGLFS_ALWAYS_SET_MODE=0 to override this.";
// The UI doesn't appear on RetroPie without this option.
qputenv("QT_QPA_EGLFS_ALWAYS_SET_MODE", "1");
}
if (!QFile("/dev/dri").exists()) { if (!QFile("/dev/dri").exists()) {
qWarning() << "Unable to find a KMSDRM display device!"; qWarning() << "Unable to find a KMSDRM display device!";
qWarning() << "On the Raspberry Pi, you must enable the 'fake KMS' driver in raspi-config to use Moonlight outside of the GUI environment."; qWarning() << "On the Raspberry Pi, you must enable the 'fake KMS' driver in raspi-config to use Moonlight outside of the GUI environment.";
@@ -331,7 +341,7 @@ int main(int argc, char *argv[])
// password prompts on macOS. // password prompts on macOS.
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1"); qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
#ifdef Q_OS_WIN32 #if defined(Q_OS_WIN32) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
if (!qEnvironmentVariableIsSet("QT_OPENGL")) { if (!qEnvironmentVariableIsSet("QT_OPENGL")) {
// On Windows, use ANGLE so we don't have to load OpenGL // On Windows, use ANGLE so we don't have to load OpenGL
// user-mode drivers into our app. OGL drivers (especially Intel) // user-mode drivers into our app. OGL drivers (especially Intel)
@@ -340,6 +350,18 @@ int main(int argc, char *argv[])
} }
#endif #endif
#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 // We don't want system proxies to apply to us
QNetworkProxyFactory::setUseSystemConfiguration(false); QNetworkProxyFactory::setUseSystemConfiguration(false);
@@ -373,6 +395,14 @@ int main(int argc, char *argv[])
// future KMSDRM platforms that use SDL for rendering. // future KMSDRM platforms that use SDL for rendering.
SDL_SetHint(SDL_HINT_VIDEO_DOUBLE_BUFFER, "1"); 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.
SDL_SetHint("SDL_WINDOWS_USE_D3D9EX", "1");
if (SDL_InitSubSystem(SDL_INIT_TIMER) != 0) { if (SDL_InitSubSystem(SDL_INIT_TIMER) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_InitSubSystem(SDL_INIT_TIMER) failed: %s", "SDL_InitSubSystem(SDL_INIT_TIMER) failed: %s",
@@ -408,6 +438,14 @@ int main(int argc, char *argv[])
// releases of Moonlight. // releases of Moonlight.
SDL_SetHint("SDL_GAMECONTROLLER_USE_BUTTON_LABELS", "0"); SDL_SetHint("SDL_GAMECONTROLLER_USE_BUTTON_LABELS", "0");
// Disable relative mouse scaling to renderer size or logical DPI. We want to send
// the mouse motion exactly how it was given to us.
SDL_SetHint("SDL_MOUSE_RELATIVE_SCALING", "0");
// Set our app name for SDL to use with PulseAudio. This matches what we provide
// as our app name to libsoundio too.
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "Moonlight");
#ifdef QT_DEBUG #ifdef QT_DEBUG
// Allow thread naming using exceptions on debug builds. SDL doesn't use SEH // Allow thread naming using exceptions on debug builds. SDL doesn't use SEH
// when throwing the exceptions, so we don't enable it for release builds out // when throwing the exceptions, so we don't enable it for release builds out
@@ -417,6 +455,10 @@ int main(int argc, char *argv[])
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
// Apply the initial translation based on user preference
StreamingPreferences prefs;
prefs.retranslate();
// After the QGuiApplication is created, the platform stuff will be initialized // After the QGuiApplication is created, the platform stuff will be initialized
// and we can set the SDL video driver to match Qt. // and we can set the SDL video driver to match Qt.
if (WMUtils::isRunningWayland() && QGuiApplication::platformName() == "xcb") { if (WMUtils::isRunningWayland() && QGuiApplication::platformName() == "xcb") {
@@ -455,7 +497,16 @@ int main(int argc, char *argv[])
} }
#endif #endif
#ifndef Q_OS_DARWIN
// Set the window icon except on macOS where we want to keep the
// modified macOS 11 style rounded corner icon.
app.setWindowIcon(QIcon(":/res/moonlight.svg")); 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 // Register our C++ types for QML
qmlRegisterType<ComputerModel>("ComputerModel", 1, 0, "ComputerModel"); qmlRegisterType<ComputerModel>("ComputerModel", 1, 0, "ComputerModel");
@@ -483,8 +534,8 @@ int main(int argc, char *argv[])
}); });
qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0, qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0,
"StreamingPreferences", "StreamingPreferences",
[](QQmlEngine*, QJSEngine*) -> QObject* { [](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
return new StreamingPreferences(); return new StreamingPreferences(qmlEngine);
}); });
// Create the identity manager on the main thread // Create the identity manager on the main thread
@@ -551,7 +602,6 @@ int main(int argc, char *argv[])
engine.load(QUrl(QStringLiteral("qrc:/gui/main.qml"))); engine.load(QUrl(QStringLiteral("qrc:/gui/main.qml")));
if (engine.rootObjects().isEmpty()) if (engine.rootObjects().isEmpty())
return -1; return -1;
int err = app.exec(); int err = app.exec();
// Give worker tasks time to properly exit. Fixes PendingQuitTask // Give worker tasks time to properly exit. Fixes PendingQuitTask
+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
Binary file not shown.
+40 -2
View File
@@ -6,6 +6,7 @@
#include <QSettings> #include <QSettings>
#include <QCoreApplication> #include <QCoreApplication>
QString Path::s_CacheDir;
QString Path::s_LogDir; QString Path::s_LogDir;
QString Path::s_BoxArtCacheDir; QString Path::s_BoxArtCacheDir;
@@ -28,11 +29,43 @@ QByteArray Path::readDataFile(QString fileName)
return dataFile.readAll(); return dataFile.readAll();
} }
void Path::writeCacheFile(QString fileName, QByteArray data)
{
QDir cacheDir(s_CacheDir);
// Create the cache path if it does not exist
if (!cacheDir.exists()) {
cacheDir.mkpath(".");
}
QFile dataFile(cacheDir.absoluteFilePath(fileName));
dataFile.open(QIODevice::WriteOnly);
dataFile.write(data);
}
void Path::deleteCacheFile(QString fileName)
{
QFile dataFile(QDir(s_CacheDir).absoluteFilePath(fileName));
dataFile.remove();
}
QFileInfo Path::getCacheFileInfo(QString fileName)
{
return QFileInfo(QDir(s_CacheDir), fileName);
}
QString Path::getDataFilePath(QString fileName) QString Path::getDataFilePath(QString fileName)
{ {
QString candidatePath; QString candidatePath;
// Check the current directory first // Check the cache location first (used by Path::writeDataFile())
candidatePath = QDir(s_CacheDir).absoluteFilePath(fileName);
if (QFile::exists(candidatePath)) {
qInfo() << "Found" << fileName << "at" << candidatePath;
return candidatePath;
}
// Check the current directory
candidatePath = QDir(QDir::currentPath()).absoluteFilePath(fileName); candidatePath = QDir(QDir::currentPath()).absoluteFilePath(fileName);
if (QFile::exists(candidatePath)) { if (QFile::exists(candidatePath)) {
qInfo() << "Found" << fileName << "at" << candidatePath; qInfo() << "Found" << fileName << "at" << candidatePath;
@@ -40,7 +73,7 @@ QString Path::getDataFilePath(QString fileName)
} }
// Now check the data directories (for Linux, in particular) // Now check the data directories (for Linux, in particular)
candidatePath = QStandardPaths::locate(QStandardPaths::DataLocation, fileName); candidatePath = QStandardPaths::locate(QStandardPaths::AppDataLocation, fileName);
if (!candidatePath.isEmpty() && QFile::exists(candidatePath)) { if (!candidatePath.isEmpty() && QFile::exists(candidatePath)) {
qInfo() << "Found" << fileName << "at" << candidatePath; qInfo() << "Found" << fileName << "at" << candidatePath;
return candidatePath; return candidatePath;
@@ -64,6 +97,10 @@ void Path::initialize(bool portable)
if (portable) { if (portable) {
s_LogDir = QDir::currentPath(); s_LogDir = QDir::currentPath();
s_BoxArtCacheDir = QDir::currentPath() + "/boxart"; s_BoxArtCacheDir = QDir::currentPath() + "/boxart";
// In order for the If-Modified-Since logic to work in MappingFetcher,
// the cache directory must be different than the current directory.
s_CacheDir = QDir::currentPath() + "/cache";
} }
else { else {
#ifdef Q_OS_DARWIN #ifdef Q_OS_DARWIN
@@ -73,6 +110,7 @@ void Path::initialize(bool portable)
#else #else
s_LogDir = QDir::tempPath(); s_LogDir = QDir::tempPath();
#endif #endif
s_CacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
s_BoxArtCacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/boxart"; s_BoxArtCacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/boxart";
} }
} }
+5
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <QString> #include <QString>
#include <QFileInfo>
class Path class Path
{ {
@@ -10,6 +11,9 @@ public:
static QString getBoxArtCacheDir(); static QString getBoxArtCacheDir();
static QByteArray readDataFile(QString fileName); static QByteArray readDataFile(QString fileName);
static void writeCacheFile(QString fileName, QByteArray data);
static void deleteCacheFile(QString fileName);
static QFileInfo getCacheFileInfo(QString fileName);
// Only safe to use directly for Qt classes // Only safe to use directly for Qt classes
static QString getDataFilePath(QString fileName); static QString getDataFilePath(QString fileName);
@@ -17,6 +21,7 @@ public:
static void initialize(bool portable); static void initialize(bool portable);
private: private:
static QString s_CacheDir;
static QString s_LogDir; static QString s_LogDir;
static QString s_BoxArtCacheDir; static QString s_BoxArtCacheDir;
}; };
+1 -1
View File
@@ -1 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240"><style>.st0{fill:#FFFFFF;}</style><path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg> <svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240" fill="white"><path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

+26 -2
View File
@@ -17,11 +17,35 @@
<file>res/baseline-error_outline-24px.svg</file> <file>res/baseline-error_outline-24px.svg</file>
<file>res/baseline-check_circle_outline-24px.svg</file> <file>res/baseline-check_circle_outline-24px.svg</file>
<file>res/Discord-Logo-White.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>
</qresource> </qresource>
<qresource prefix="/data"> <qresource prefix="/data">
<file alias="gamecontrollerdb.txt">SDL_GameControllerDB/gamecontrollerdb.txt</file> <file alias="gamecontrollerdb.txt">SDL_GameControllerDB/gamecontrollerdb.txt</file>
<file alias="ModeSeven.ttf">ModeSeven.ttf</file> <file alias="ModeSeven.ttf">ModeSeven.ttf</file>
<file alias="egl.frag">shaders/egl.frag</file> <file alias="egl_nv12.frag">shaders/egl_nv12.frag</file>
<file alias="egl.vert">shaders/egl.vert</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> </qresource>
</RCC> </RCC>
+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;
};
+91
View File
@@ -0,0 +1,91 @@
#include "mappingfetcher.h"
#include "path.h"
#include <QNetworkReply>
MappingFetcher::MappingFetcher(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, &MappingFetcher::handleMappingListFetched);
}
void MappingFetcher::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/SDL_GameControllerDB/gamecontrollerdb.txt");
QNetworkRequest request(url);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);
#else
request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
#endif
// Qt 5.12 introduced QNetworkRequest::IfModifiedSinceHeader. We _could_ implement it
// by hand (including QDateTime conversion to the correct string form) for earlier Qt
// versions, but that's a pain and this is just an optimization anyway. We'll do a bit
// of extra work on those legacy Qt versions by fetching the GCDB each time we launch.
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
// Only download the file if it's newer than what we have
QFileInfo existingFileInfo = Path::getCacheFileInfo("gamecontrollerdb.txt");
if (existingFileInfo.exists()) {
// Make sure the cached file looks reasonable. It should have some data and
// the last modified time should not be in the future.
QDateTime lastModifiedTime = existingFileInfo.lastModified().toUTC();
if (existingFileInfo.size() > 0 && lastModifiedTime <= QDateTime::currentDateTimeUtc()) {
request.setHeader(QNetworkRequest::IfModifiedSinceHeader, existingFileInfo.lastModified().toUTC());
}
else {
Path::deleteCacheFile("gamecontrollerdb.txt");
}
}
#endif
// We'll get a callback when this is finished
m_Nam.get(request);
}
void MappingFetcher::handleMappingListFetched(QNetworkReply* reply)
{
Q_ASSERT(reply->isFinished());
if (reply->error() == QNetworkReply::NoError) {
// Queue the reply for deletion
reply->deleteLater();
// If we get a 304 back, Qt will happily just tell us our request was
// successful and let us try to write our empty response to disk. Check
// the status code directly to prevent this.
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 304) {
qInfo() << "Gamepad mappings are up to date";
}
else {
// Update the cached data on disk for next call to applyMappings()
QByteArray data = reply->readAll();
if (!data.isEmpty()) {
Path::writeCacheFile("gamecontrollerdb.txt", data);
}
qInfo() << "Downloaded updated gamepad mappings";
}
}
else {
qWarning() << "Failed to download updated gamepad mappings:" << reply->error();
reply->deleteLater();
}
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <QObject>
#include <QNetworkAccessManager>
class MappingFetcher : public QObject
{
Q_OBJECT
public:
explicit MappingFetcher(QObject *parent = nullptr);
void start();
private slots:
void handleMappingListFetched(QNetworkReply* reply);
private:
QNetworkAccessManager m_Nam;
};
+25 -7
View File
@@ -10,10 +10,18 @@
#define SER_GUID "guid" #define SER_GUID "guid"
#define SER_MAPPING "mapping" #define SER_MAPPING "mapping"
MappingFetcher* MappingManager::s_MappingFetcher;
MappingManager::MappingManager() MappingManager::MappingManager()
{ {
QSettings settings; QSettings settings;
// Load updated mappings from the Internet once per Moonlight launch
if (s_MappingFetcher == nullptr) {
s_MappingFetcher = new MappingFetcher();
s_MappingFetcher->start();
}
// First load existing saved mappings. This ensures the user's // First load existing saved mappings. This ensures the user's
// hints can always override the old data. // hints can always override the old data.
int mappingCount = settings.beginReadArray(SER_GAMEPADMAPPING); int mappingCount = settings.beginReadArray(SER_GAMEPADMAPPING);
@@ -33,7 +41,7 @@ MappingManager::MappingManager()
#else #else
.split('\n', QString::SkipEmptyParts); .split('\n', QString::SkipEmptyParts);
#endif #endif
for (QString sdlMapping : sdlMappings) { for (const QString& sdlMapping : sdlMappings) {
SdlGamepadMapping mapping(sdlMapping); SdlGamepadMapping mapping(sdlMapping);
addMapping(mapping); addMapping(mapping);
} }
@@ -64,16 +72,26 @@ void MappingManager::applyMappings()
if (!mappingData.isEmpty()) { if (!mappingData.isEmpty()) {
int newMappings = SDL_GameControllerAddMappingsFromRW( int newMappings = SDL_GameControllerAddMappingsFromRW(
SDL_RWFromConstMem(mappingData.constData(), mappingData.size()), 1); SDL_RWFromConstMem(mappingData.constData(), mappingData.size()), 1);
if (newMappings < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, if (newMappings > 0) {
"Error loading gamepad mappings: %s",
SDL_GetError());
}
else {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Loaded %d new gamepad mappings", "Loaded %d new gamepad mappings",
newMappings); newMappings);
} }
else {
if (newMappings < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error loading gamepad mappings: %s",
SDL_GetError());
}
else if (newMappings == 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"0 new mappings found in gamecontrollerdb.txt. Is it corrupt?");
}
// Try deleting the cached mapping list just in case it's corrupt
Path::deleteCacheFile("gamecontrollerdb.txt");
}
} }
else { else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+4
View File
@@ -1,5 +1,7 @@
#pragma once #pragma once
#include "mappingfetcher.h"
#include <QSettings> #include <QSettings>
class SdlGamepadMapping class SdlGamepadMapping
@@ -70,5 +72,7 @@ public:
private: private:
QMap<QString, SdlGamepadMapping> m_Mappings; QMap<QString, SdlGamepadMapping> m_Mappings;
static MappingFetcher* s_MappingFetcher;
}; };
+131 -4
View File
@@ -1,6 +1,11 @@
#include "streamingpreferences.h" #include "streamingpreferences.h"
#include <QSettings> #include <QSettings>
#include <QTranslator>
#include <QCoreApplication>
#include <QLocale>
#include <QtDebug>
#define SER_STREAMSETTINGS "streamsettings" #define SER_STREAMSETTINGS "streamsettings"
#define SER_WIDTH "width" #define SER_WIDTH "width"
@@ -24,17 +29,32 @@
#define SER_STARTWINDOWED "startwindowed" #define SER_STARTWINDOWED "startwindowed"
#define SER_FRAMEPACING "framepacing" #define SER_FRAMEPACING "framepacing"
#define SER_CONNWARNINGS "connwarnings" #define SER_CONNWARNINGS "connwarnings"
#define SER_UIDISPLAYMODE "uidisplaymode"
#define SER_RICHPRESENCE "richpresence" #define SER_RICHPRESENCE "richpresence"
#define SER_GAMEPADMOUSE "gamepadmouse" #define SER_GAMEPADMOUSE "gamepadmouse"
#define SER_DEFAULTVER "defaultver" #define SER_DEFAULTVER "defaultver"
#define SER_PACKETSIZE "packetsize" #define SER_PACKETSIZE "packetsize"
#define SER_DETECTNETBLOCKING "detectnetblocking" #define SER_DETECTNETBLOCKING "detectnetblocking"
#define SER_SWAPMOUSEBUTTONS "swapmousebuttons" #define SER_SWAPMOUSEBUTTONS "swapmousebuttons"
#define SER_MUTEONFOCUSLOSS "muteonfocusloss"
#define SER_BACKGROUNDGAMEPAD "backgroundgamepad"
#define SER_REVERSESCROLL "reversescroll"
#define SER_SWAPFACEBUTTONS "swapfacebuttons"
#define SER_CAPTURESYSKEYS "capturesyskeys"
#define SER_LANGUAGE "language"
#define CURRENT_DEFAULT_VER 1 #define CURRENT_DEFAULT_VER 1
StreamingPreferences::StreamingPreferences(QObject *parent) StreamingPreferences::StreamingPreferences(QObject *parent)
: QObject(parent) : QObject(parent),
m_QmlEngine(nullptr)
{
reload();
}
StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent)
: QObject(parent),
m_QmlEngine(qmlEngine)
{ {
reload(); reload();
} }
@@ -64,7 +84,6 @@ void StreamingPreferences::reload()
quitAppAfter = settings.value(SER_QUITAPPAFTER, false).toBool(); quitAppAfter = settings.value(SER_QUITAPPAFTER, false).toBool();
absoluteMouseMode = settings.value(SER_ABSMOUSEMODE, false).toBool(); absoluteMouseMode = settings.value(SER_ABSMOUSEMODE, false).toBool();
absoluteTouchMode = settings.value(SER_ABSTOUCHMODE, true).toBool(); absoluteTouchMode = settings.value(SER_ABSTOUCHMODE, true).toBool();
startWindowed = settings.value(SER_STARTWINDOWED, true).toBool();
framePacing = settings.value(SER_FRAMEPACING, false).toBool(); framePacing = settings.value(SER_FRAMEPACING, false).toBool();
connectionWarnings = settings.value(SER_CONNWARNINGS, true).toBool(); connectionWarnings = settings.value(SER_CONNWARNINGS, true).toBool();
richPresence = settings.value(SER_RICHPRESENCE, true).toBool(); richPresence = settings.value(SER_RICHPRESENCE, true).toBool();
@@ -72,6 +91,12 @@ void StreamingPreferences::reload()
detectNetworkBlocking = settings.value(SER_DETECTNETBLOCKING, true).toBool(); detectNetworkBlocking = settings.value(SER_DETECTNETBLOCKING, true).toBool();
packetSize = settings.value(SER_PACKETSIZE, 0).toInt(); packetSize = settings.value(SER_PACKETSIZE, 0).toInt();
swapMouseButtons = settings.value(SER_SWAPMOUSEBUTTONS, false).toBool(); swapMouseButtons = settings.value(SER_SWAPMOUSEBUTTONS, false).toBool();
muteOnFocusLoss = settings.value(SER_MUTEONFOCUSLOSS, false).toBool();
backgroundGamepad = settings.value(SER_BACKGROUNDGAMEPAD, false).toBool();
reverseScrollDirection = settings.value(SER_REVERSESCROLL, false).toBool();
swapFaceButtons = settings.value(SER_SWAPFACEBUTTONS, 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, audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
static_cast<int>(AudioConfig::AC_STEREO)).toInt()); static_cast<int>(AudioConfig::AC_STEREO)).toInt());
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG, videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
@@ -82,6 +107,12 @@ void StreamingPreferences::reload()
// Try to load from the old preference value too // Try to load from the old preference value too
static_cast<int>(settings.value(SER_FULLSCREEN, true).toBool() ? static_cast<int>(settings.value(SER_FULLSCREEN, true).toBool() ?
recommendedFullScreenMode : WindowMode::WM_WINDOWED)).toInt()); recommendedFullScreenMode : WindowMode::WM_WINDOWED)).toInt());
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 // Perform default settings updates as required based on last default version
if (defaultVer == 0) { if (defaultVer == 0) {
@@ -94,6 +125,90 @@ 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_AUTO:
default:
return QLocale::system().name();
}
}
void StreamingPreferences::save() void StreamingPreferences::save()
{ {
QSettings settings; QSettings settings;
@@ -111,7 +226,6 @@ void StreamingPreferences::save()
settings.setValue(SER_QUITAPPAFTER, quitAppAfter); settings.setValue(SER_QUITAPPAFTER, quitAppAfter);
settings.setValue(SER_ABSMOUSEMODE, absoluteMouseMode); settings.setValue(SER_ABSMOUSEMODE, absoluteMouseMode);
settings.setValue(SER_ABSTOUCHMODE, absoluteTouchMode); settings.setValue(SER_ABSTOUCHMODE, absoluteTouchMode);
settings.setValue(SER_STARTWINDOWED, startWindowed);
settings.setValue(SER_FRAMEPACING, framePacing); settings.setValue(SER_FRAMEPACING, framePacing);
settings.setValue(SER_CONNWARNINGS, connectionWarnings); settings.setValue(SER_CONNWARNINGS, connectionWarnings);
settings.setValue(SER_RICHPRESENCE, richPresence); settings.setValue(SER_RICHPRESENCE, richPresence);
@@ -122,8 +236,15 @@ void StreamingPreferences::save()
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig)); settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection)); settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode)); 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_DEFAULTVER, CURRENT_DEFAULT_VER);
settings.setValue(SER_SWAPMOUSEBUTTONS, swapMouseButtons); 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, captureSysKeysMode);
} }
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps) int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
@@ -133,8 +254,14 @@ int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
// we don't want to bump those 16:10 resolutions up to the // we don't want to bump those 16:10 resolutions up to the
// next 16:9 slot. // next 16:9 slot.
if (width * height <= 640 * 360) {
return static_cast<int>(1000 * (fps / 30.0));
}
else if (width * height <= 854 * 480) {
return static_cast<int>(1500 * (fps / 30.0));
}
// This covers 1280x720 and 1280x800 too // This covers 1280x720 and 1280x800 too
if (width * height <= 1366 * 768) { else if (width * height <= 1366 * 768) {
return static_cast<int>(5000 * (fps / 30.0)); return static_cast<int>(5000 * (fps / 30.0));
} }
else if (width * height <= 1920 * 1200) { else if (width * height <= 1920 * 1200) {
+66 -3
View File
@@ -2,6 +2,7 @@
#include <QObject> #include <QObject>
#include <QRect> #include <QRect>
#include <QQmlEngine>
class StreamingPreferences : public QObject class StreamingPreferences : public QObject
{ {
@@ -9,6 +10,7 @@ class StreamingPreferences : public QObject
public: public:
StreamingPreferences(QObject *parent = nullptr); StreamingPreferences(QObject *parent = nullptr);
StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent = nullptr);
Q_INVOKABLE static int Q_INVOKABLE static int
getDefaultBitrate(int width, int height, int fps); getDefaultBitrate(int width, int height, int fps);
@@ -50,6 +52,42 @@ public:
}; };
Q_ENUM(WindowMode) Q_ENUM(WindowMode)
enum UIDisplayMode
{
UI_WINDOWED,
UI_MAXIMIZED,
UI_FULLSCREEN
};
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
};
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 width MEMBER width NOTIFY displayModeChanged)
Q_PROPERTY(int height MEMBER height NOTIFY displayModeChanged) Q_PROPERTY(int height MEMBER height NOTIFY displayModeChanged)
Q_PROPERTY(int fps MEMBER fps NOTIFY displayModeChanged) Q_PROPERTY(int fps MEMBER fps NOTIFY displayModeChanged)
@@ -63,7 +101,6 @@ public:
Q_PROPERTY(bool quitAppAfter MEMBER quitAppAfter NOTIFY quitAppAfterChanged) Q_PROPERTY(bool quitAppAfter MEMBER quitAppAfter NOTIFY quitAppAfterChanged)
Q_PROPERTY(bool absoluteMouseMode MEMBER absoluteMouseMode NOTIFY absoluteMouseModeChanged) Q_PROPERTY(bool absoluteMouseMode MEMBER absoluteMouseMode NOTIFY absoluteMouseModeChanged)
Q_PROPERTY(bool absoluteTouchMode MEMBER absoluteTouchMode NOTIFY absoluteTouchModeChanged) Q_PROPERTY(bool absoluteTouchMode MEMBER absoluteTouchMode NOTIFY absoluteTouchModeChanged)
Q_PROPERTY(bool startWindowed MEMBER startWindowed NOTIFY startWindowedChanged)
Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged) Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged)
Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged) Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged)
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged) Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged)
@@ -74,7 +111,16 @@ public:
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged) Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
Q_PROPERTY(WindowMode windowMode MEMBER windowMode NOTIFY windowModeChanged) Q_PROPERTY(WindowMode windowMode MEMBER windowMode NOTIFY windowModeChanged)
Q_PROPERTY(WindowMode recommendedFullScreenMode MEMBER recommendedFullScreenMode CONSTANT) Q_PROPERTY(WindowMode recommendedFullScreenMode MEMBER recommendedFullScreenMode CONSTANT)
Q_PROPERTY(UIDisplayMode uiDisplayMode MEMBER uiDisplayMode NOTIFY uiDisplayModeChanged)
Q_PROPERTY(bool swapMouseButtons MEMBER swapMouseButtons NOTIFY mouseButtonsChanged) Q_PROPERTY(bool swapMouseButtons MEMBER swapMouseButtons NOTIFY mouseButtonsChanged)
Q_PROPERTY(bool muteOnFocusLoss MEMBER muteOnFocusLoss NOTIFY muteOnFocusLossChanged)
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(CaptureSysKeysMode captureSysKeysMode MEMBER captureSysKeysMode NOTIFY captureSysKeysModeChanged)
Q_PROPERTY(Language language MEMBER language NOTIFY languageChanged);
Q_INVOKABLE bool retranslate();
// Directly accessible members for preferences // Directly accessible members for preferences
int width; int width;
@@ -90,19 +136,25 @@ public:
bool quitAppAfter; bool quitAppAfter;
bool absoluteMouseMode; bool absoluteMouseMode;
bool absoluteTouchMode; bool absoluteTouchMode;
bool startWindowed;
bool framePacing; bool framePacing;
bool connectionWarnings; bool connectionWarnings;
bool richPresence; bool richPresence;
bool gamepadMouse; bool gamepadMouse;
bool detectNetworkBlocking; bool detectNetworkBlocking;
bool swapMouseButtons; bool swapMouseButtons;
bool muteOnFocusLoss;
bool backgroundGamepad;
bool reverseScrollDirection;
bool swapFaceButtons;
int packetSize; int packetSize;
AudioConfig audioConfig; AudioConfig audioConfig;
VideoCodecConfig videoCodecConfig; VideoCodecConfig videoCodecConfig;
VideoDecoderSelection videoDecoderSelection; VideoDecoderSelection videoDecoderSelection;
WindowMode windowMode; WindowMode windowMode;
WindowMode recommendedFullScreenMode; WindowMode recommendedFullScreenMode;
UIDisplayMode uiDisplayMode;
Language language;
CaptureSysKeysMode captureSysKeysMode;
signals: signals:
void displayModeChanged(); void displayModeChanged();
@@ -119,13 +171,24 @@ signals:
void audioConfigChanged(); void audioConfigChanged();
void videoCodecConfigChanged(); void videoCodecConfigChanged();
void videoDecoderSelectionChanged(); void videoDecoderSelectionChanged();
void uiDisplayModeChanged();
void windowModeChanged(); void windowModeChanged();
void startWindowedChanged();
void framePacingChanged(); void framePacingChanged();
void connectionWarningsChanged(); void connectionWarningsChanged();
void richPresenceChanged(); void richPresenceChanged();
void gamepadMouseChanged(); void gamepadMouseChanged();
void detectNetworkBlockingChanged(); void detectNetworkBlockingChanged();
void mouseButtonsChanged(); void mouseButtonsChanged();
void muteOnFocusLossChanged();
void backgroundGamepadChanged();
void reverseScrollDirectionChanged();
void swapFaceButtonsChanged();
void captureSysKeysModeChanged();
void languageChanged();
private:
QString getSuffixFromLanguage(Language lang);
QQmlEngine* m_QmlEngine;
}; };
File renamed without changes.
File renamed without changes.
+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);
}
+11
View File
@@ -0,0 +1,11 @@
precision mediump float;
uniform sampler2D uTexture;
varying vec2 vTexCoord;
void main() {
vec4 abgr = texture2D(uTexture, vTexCoord);
gl_FragColor = abgr;
gl_FragColor.r = abgr.b;
gl_FragColor.b = abgr.r;
}
+8
View File
@@ -0,0 +1,8 @@
attribute vec2 aPosition; // 2D: X,Y
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main() {
vTexCoord = aTexCoord;
gl_Position = vec4(aPosition, 0, 1);
}
+5
View File
@@ -197,6 +197,11 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
s_ActiveSession->m_AudioSampleCount++; s_ActiveSession->m_AudioSampleCount++;
// If audio is muted, don't decode or play the audio
if (s_ActiveSession->m_AudioMuted) {
return;
}
if (s_ActiveSession->m_AudioRenderer != nullptr) { if (s_ActiveSession->m_AudioRenderer != nullptr) {
int desiredSize = sizeof(short) * s_ActiveSession->m_AudioConfig.samplesPerFrame * s_ActiveSession->m_AudioConfig.channelCount; int desiredSize = sizeof(short) * s_ActiveSession->m_AudioConfig.samplesPerFrame * s_ActiveSession->m_AudioConfig.channelCount;
void* buffer = s_ActiveSession->m_AudioRenderer->getAudioBuffer(&desiredSize); void* buffer = s_ActiveSession->m_AudioRenderer->getAudioBuffer(&desiredSize);
-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;
};
+2 -33
View File
@@ -3,43 +3,12 @@
#include <Limelight.h> #include <Limelight.h>
#include <SDL.h> #include <SDL.h>
#include <QtGlobal>
#include <QFile>
#include <QTextStream>
SdlAudioRenderer::SdlAudioRenderer() SdlAudioRenderer::SdlAudioRenderer()
: m_AudioDevice(0), : m_AudioDevice(0),
m_AudioBuffer(nullptr) m_AudioBuffer(nullptr)
{ {
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO)); SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
#ifdef HAVE_MMAL
// HACK: PulseAudio on Raspberry Pi suffers from horrible underruns,
// so switch to ALSA if we detect that we're on a Pi. We need to
// actually set a bogus PULSE_SERVER so it doesn't try to get smart on us
// and find PA anyway. SDL_AUDIODRIVER=pulseaudio can override this logic.
if (qgetenv("SDL_AUDIODRIVER").toLower() != "pulseaudio") {
QFile file("/proc/cpuinfo");
if (file.open(QIODevice::ReadOnly | QFile::Text)) {
QTextStream textStream(&file);
if (textStream.readAll().contains("Raspberry Pi")) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Avoiding PulseAudio on Raspberry Pi");
qputenv("PULSE_SERVER", "");
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"ALSA failed; falling back to default");
qunsetenv("PULSE_SERVER");
}
else {
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
}
}
}
#endif
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s", "SDL_InitSubSystem(SDL_INIT_AUDIO) failed: %s",
@@ -73,7 +42,7 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION*
return false; return false;
} }
m_AudioBuffer = malloc(m_FrameSize); m_AudioBuffer = SDL_malloc(m_FrameSize);
if (m_AudioBuffer == nullptr) { if (m_AudioBuffer == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to allocate audio buffer"); "Failed to allocate audio buffer");
@@ -105,7 +74,7 @@ SdlAudioRenderer::~SdlAudioRenderer()
} }
if (m_AudioBuffer != nullptr) { if (m_AudioBuffer != nullptr) {
free(m_AudioBuffer); SDL_free(m_AudioBuffer);
} }
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_AUDIO);
+190
View File
@@ -0,0 +1,190 @@
#include "input.h"
#include <QString>
int SdlInputHandler::clipboardThreadProc(void *ptr)
{
auto me = (SdlInputHandler*)ptr;
while (!SDL_AtomicGet(&me->m_ShutdownClipboardThread)) {
QString clipboardData;
SDL_LockMutex(me->m_ClipboardLock);
for (;;) {
clipboardData = me->m_ClipboardData;
me->m_ClipboardData.clear();
if (!clipboardData.isEmpty() || SDL_AtomicGet(&me->m_ShutdownClipboardThread)) {
break;
}
SDL_CondWait(me->m_ClipboardHasData, me->m_ClipboardLock);
}
SDL_UnlockMutex(me->m_ClipboardLock);
// We might get here on shutdown, so don't send text if there's
// nothing to send.
if (!clipboardData.isEmpty()) {
me->sendText(clipboardData);
}
}
return 0;
}
#define MAP_KEY(c, sc) \
case c: \
event.key.keysym.scancode = sc; \
break
#define MAP_KEY_SHIFT(c, sc) \
case c: \
event.key.keysym.scancode = sc; \
event.key.keysym.mod = KMOD_SHIFT; \
break
void SdlInputHandler::sendText(QString& string)
{
for (int i = 0; i < string.size(); i++) {
char16_t c = string[i].unicode();
SDL_Event event = {};
// If we're sending a very long string, we might get a termination request
// while we're in the middle of sending a string. In that case, just bail.
if (SDL_AtomicGet(&m_ShutdownClipboardThread)) {
return;
}
if (c >= 'A' && c <= 'Z') {
event.key.keysym.scancode = (SDL_Scancode)((c - 'A') + SDL_SCANCODE_A);
event.key.keysym.mod = KMOD_SHIFT;
}
else if (c >= 'a' && c <= 'z') {
event.key.keysym.scancode = (SDL_Scancode)((c - 'a') + SDL_SCANCODE_A);
}
else if (c >= '1' && c <= '9') {
event.key.keysym.scancode = (SDL_Scancode)((c - '1') + SDL_SCANCODE_1);
}
else {
// Fixup some Unicode characters to be expressible in ASCII
switch (c) {
case u'\U0000201C': // Left "
case u'\U0000201D': // Right "
c = '"';
break;
case u'\U00002018': // Left '
case u'\U00002019': // Right '
c = '\'';
break;
case u'\U00002013': // Smart -
c = '-';
break;
case u'\U00002026': // Ellipsis (...)
// Convert this character into 3 periods
string.insert(i+1, '.');
string.insert(i+1, '.');
c = '.';
break;
case u'\U000000A0': // Non-breaking space
c = ' ';
break;
default:
// Nothing
break;
}
switch (c) {
// Handle CRLF separately to avoid duplicate newlines
case '\r':
if (string[i + 1] == '\n') {
i++;
}
event.key.keysym.scancode = SDL_SCANCODE_RETURN;
break;
MAP_KEY('\b', SDL_SCANCODE_BACKSPACE);
MAP_KEY('\n', SDL_SCANCODE_RETURN);
MAP_KEY('\t', SDL_SCANCODE_TAB);
MAP_KEY(' ', SDL_SCANCODE_SPACE);
MAP_KEY_SHIFT('!', SDL_SCANCODE_1);
MAP_KEY_SHIFT('"', SDL_SCANCODE_APOSTROPHE);
MAP_KEY_SHIFT('#', SDL_SCANCODE_3);
MAP_KEY_SHIFT('$', SDL_SCANCODE_4);
MAP_KEY_SHIFT('%', SDL_SCANCODE_5);
MAP_KEY_SHIFT('&', SDL_SCANCODE_7);
MAP_KEY('\'', SDL_SCANCODE_APOSTROPHE);
MAP_KEY_SHIFT('(', SDL_SCANCODE_9);
MAP_KEY_SHIFT(')', SDL_SCANCODE_0);
MAP_KEY_SHIFT('*', SDL_SCANCODE_8);
MAP_KEY_SHIFT('+', SDL_SCANCODE_EQUALS);
MAP_KEY(',', SDL_SCANCODE_COMMA);
MAP_KEY('-', SDL_SCANCODE_MINUS);
MAP_KEY('.', SDL_SCANCODE_PERIOD);
MAP_KEY('/', SDL_SCANCODE_SLASH);
MAP_KEY('0', SDL_SCANCODE_0);
MAP_KEY_SHIFT(':', SDL_SCANCODE_SEMICOLON);
MAP_KEY(';', SDL_SCANCODE_SEMICOLON);
MAP_KEY_SHIFT('<', SDL_SCANCODE_COMMA);
MAP_KEY('=', SDL_SCANCODE_EQUALS);
MAP_KEY_SHIFT('>', SDL_SCANCODE_PERIOD);
MAP_KEY_SHIFT('?', SDL_SCANCODE_SLASH);
MAP_KEY_SHIFT('@', SDL_SCANCODE_2);
MAP_KEY('[', SDL_SCANCODE_LEFTBRACKET);
MAP_KEY('\\', SDL_SCANCODE_BACKSLASH);
MAP_KEY(']', SDL_SCANCODE_RIGHTBRACKET);
MAP_KEY_SHIFT('^', SDL_SCANCODE_6);
MAP_KEY_SHIFT('_', SDL_SCANCODE_MINUS);
MAP_KEY('`', SDL_SCANCODE_GRAVE);
MAP_KEY_SHIFT('{', SDL_SCANCODE_LEFTBRACKET);
MAP_KEY_SHIFT('|', SDL_SCANCODE_BACKSLASH);
MAP_KEY_SHIFT('}', SDL_SCANCODE_RIGHTBRACKET);
MAP_KEY_SHIFT('~', SDL_SCANCODE_GRAVE);
default:
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Pasting text - non-ASCII character value 'U+%x' ignored",
c);
continue;
}
}
if (event.key.keysym.mod & KMOD_SHIFT) {
SDL_Event modifierEvent = {};
modifierEvent.type = SDL_KEYDOWN;
modifierEvent.key.state = SDL_PRESSED;
modifierEvent.key.keysym.scancode = SDL_SCANCODE_LSHIFT;
handleKeyEvent(&modifierEvent.key);
SDL_Delay(10);
}
event.type = SDL_KEYDOWN;
event.key.state = SDL_PRESSED;
handleKeyEvent(&event.key);
SDL_Delay(10);
event.type = SDL_KEYUP;
event.key.state = SDL_RELEASED;
handleKeyEvent(&event.key);
if (event.key.keysym.mod & KMOD_SHIFT) {
SDL_Event modifierEvent = {};
modifierEvent.type = SDL_KEYUP;
modifierEvent.key.state = SDL_RELEASED;
modifierEvent.key.keysym.scancode = SDL_SCANCODE_LSHIFT;
handleKeyEvent(&modifierEvent.key);
SDL_Delay(10);
}
}
}
+33 -1
View File
@@ -167,6 +167,23 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
return; return;
} }
if (m_SwapFaceButtons) {
switch (event->button) {
case SDL_CONTROLLER_BUTTON_A:
event->button = SDL_CONTROLLER_BUTTON_B;
break;
case SDL_CONTROLLER_BUTTON_B:
event->button = SDL_CONTROLLER_BUTTON_A;
break;
case SDL_CONTROLLER_BUTTON_X:
event->button = SDL_CONTROLLER_BUTTON_Y;
break;
case SDL_CONTROLLER_BUTTON_Y:
event->button = SDL_CONTROLLER_BUTTON_X;
break;
}
}
if (event->state == SDL_PRESSED) { if (event->state == SDL_PRESSED) {
state->buttons |= k_ButtonMap[event->button]; state->buttons |= k_ButtonMap[event->button];
@@ -252,7 +269,22 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
event.quit.timestamp = SDL_GetTicks(); event.quit.timestamp = SDL_GetTicks();
SDL_PushEvent(&event); 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, LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0); 0, 0, 0, 0, 0, 0, 0);
return; return;
+250 -31
View File
@@ -3,9 +3,15 @@
#include "streaming/session.h" #include "streaming/session.h"
#include "settings/mappingmanager.h" #include "settings/mappingmanager.h"
#include "path.h" #include "path.h"
#include "utils.h"
#ifdef Q_OS_WIN32
#include <SDL_syswm.h>
#endif
#include <QtGlobal> #include <QtGlobal>
#include <QDir> #include <QDir>
#include <QGuiApplication>
#define MOUSE_POLLING_INTERVAL 5 #define MOUSE_POLLING_INTERVAL 5
@@ -13,25 +19,34 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
: m_MultiController(prefs.multiController), : m_MultiController(prefs.multiController),
m_GamepadMouse(prefs.gamepadMouse), m_GamepadMouse(prefs.gamepadMouse),
m_SwapMouseButtons(prefs.swapMouseButtons), m_SwapMouseButtons(prefs.swapMouseButtons),
m_ReverseScrollDirection(prefs.reverseScrollDirection),
m_SwapFaceButtons(prefs.swapFaceButtons),
m_MouseMoveTimer(0), m_MouseMoveTimer(0),
m_MousePositionLock(0), m_MousePositionLock(0),
m_MouseWasInVideoRegion(false), m_MouseWasInVideoRegion(false),
m_PendingMouseButtonsAllUpOnVideoRegionLeave(false), m_PendingMouseButtonsAllUpOnVideoRegionLeave(false),
m_FakeCaptureActive(false), m_FakeCaptureActive(false),
m_CaptureSystemKeysMode(prefs.captureSysKeysMode),
m_MouseCursorCapturedVisibilityState(SDL_DISABLE),
m_LongPressTimer(0), m_LongPressTimer(0),
m_StreamWidth(streamWidth), m_StreamWidth(streamWidth),
m_StreamHeight(streamHeight), m_StreamHeight(streamHeight),
m_AbsoluteMouseMode(prefs.absoluteMouseMode), m_AbsoluteMouseMode(prefs.absoluteMouseMode),
m_AbsoluteTouchMode(prefs.absoluteTouchMode), m_AbsoluteTouchMode(prefs.absoluteTouchMode),
m_PendingMouseLeaveButtonUp(0),
m_LeftButtonReleaseTimer(0), m_LeftButtonReleaseTimer(0),
m_RightButtonReleaseTimer(0), m_RightButtonReleaseTimer(0),
m_DragTimer(0), m_DragTimer(0),
m_DragButton(0), m_DragButton(0),
m_NumFingersDown(0) m_NumFingersDown(0),
m_ClipboardData()
{ {
// Allow gamepad input when the app doesn't have focus // System keys are always captured when running without a DE
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); 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");
// If absolute mouse mode is enabled, use relative mode warp (which // If absolute mouse mode is enabled, use relative mode warp (which
// is via normal motion events that are influenced by mouse acceleration). // is via normal motion events that are influenced by mouse acceleration).
@@ -41,28 +56,69 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
prefs.absoluteMouseMode ? "1" : "0", prefs.absoluteMouseMode ? "1" : "0",
SDL_HINT_OVERRIDE); SDL_HINT_OVERRIDE);
#if !SDL_VERSION_ATLEAST(2, 0, 15)
// For older versions of SDL (2.0.14 and earlier), use SDL_HINT_GRAB_KEYBOARD
SDL_SetHintWithPriority(SDL_HINT_GRAB_KEYBOARD,
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 // 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 // absolute mouse mode, this will avoid the user having to click twice to
// trigger a click on the host if the Moonlight window is not focused. In // trigger a click on the host if the Moonlight window is not focused. In
// relative mode, the click event will trigger the mouse to be recaptured. // relative mode, the click event will trigger the mouse to be recaptured.
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
#if defined(Q_OS_DARWIN) && !SDL_VERSION_ATLEAST(2, 0, 10) // Enabling extended input reports allows rumble to function on Bluetooth PS4/PS5
// SDL 2.0.9 on macOS has a broken HIDAPI mapping for the older Xbox One S
// firmware, so we have to disable HIDAPI for Xbox gamepads on macOS until
// SDL 2.0.10 where the bug is fixed.
// https://github.com/moonlight-stream/moonlight-qt/issues/133
// https://bugzilla.libsdl.org/show_bug.cgi?id=4395
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
#endif
#if SDL_VERSION_ATLEAST(2, 0, 9)
// Enabling extended input reports allows rumble to function on Bluetooth PS4
// controllers, but breaks DirectInput applications. We will enable it because // controllers, but breaks DirectInput applications. We will enable it because
// it's likely that working rumble is what the user is expecting. If they don't // it's likely that working rumble is what the user is expecting. If they don't
// want this behavior, they can override it with the environment variable. // want this behavior, they can override it with the environment variable.
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); SDL_SetHint("SDL_JOYSTICK_HIDAPI_PS4_RUMBLE", "1");
#endif 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_OldIgnoreDevices = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES);
m_OldIgnoreDevicesExcept = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT); m_OldIgnoreDevicesExcept = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT);
@@ -120,6 +176,10 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
} }
#endif #endif
#ifdef Q_OS_DARWIN
CGSGetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), &m_OldHotKeyMode);
#endif
// Initialize the gamepad mask with currently attached gamepads to avoid // Initialize the gamepad mask with currently attached gamepads to avoid
// causing gamepads to unexpectedly disappear and reappear on the host // causing gamepads to unexpectedly disappear and reappear on the host
// during stream startup as we detect currently attached gamepads one at a time. // during stream startup as we detect currently attached gamepads one at a time.
@@ -146,6 +206,13 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
} }
m_MouseMoveTimer = SDL_AddTimer(pollingInterval, SdlInputHandler::mouseMoveTimerCallback, this); m_MouseMoveTimer = SDL_AddTimer(pollingInterval, SdlInputHandler::mouseMoveTimerCallback, this);
// Initialize state used by the clipboard thread before we start it
SDL_AtomicSet(&m_ShutdownClipboardThread, 0);
m_ClipboardHasData = SDL_CreateCond();
m_ClipboardLock = SDL_CreateMutex();
m_ClipboardThread = SDL_CreateThread(SdlInputHandler::clipboardThreadProc, "Clipboard Sender", this);
} }
SdlInputHandler::~SdlInputHandler() SdlInputHandler::~SdlInputHandler()
@@ -171,6 +238,21 @@ SdlInputHandler::~SdlInputHandler()
SDL_RemoveTimer(m_RightButtonReleaseTimer); SDL_RemoveTimer(m_RightButtonReleaseTimer);
SDL_RemoveTimer(m_DragTimer); SDL_RemoveTimer(m_DragTimer);
// Wake up the clipboard thread to terminate it
SDL_AtomicSet(&m_ShutdownClipboardThread, 1);
SDL_CondBroadcast(m_ClipboardHasData);
// Wait for it to terminate
SDL_WaitThread(m_ClipboardThread, nullptr);
// Now we can safely clean up its resources
SDL_DestroyCond(m_ClipboardHasData);
SDL_DestroyMutex(m_ClipboardLock);
#ifdef Q_OS_DARWIN
CGSSetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), m_OldHotKeyMode);
#endif
#if !SDL_VERSION_ATLEAST(2, 0, 9) #if !SDL_VERSION_ATLEAST(2, 0, 9)
SDL_QuitSubSystem(SDL_INIT_HAPTIC); SDL_QuitSubSystem(SDL_INIT_HAPTIC);
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC)); SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
@@ -211,7 +293,7 @@ void SdlInputHandler::raiseAllKeys()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Raising %d keys", "Raising %d keys",
m_KeysDown.count()); (int)m_KeysDown.count());
for (auto keyDown : m_KeysDown) { for (auto keyDown : m_KeysDown) {
LiSendKeyboardEvent(keyDown, KEY_ACTION_UP, 0); LiSendKeyboardEvent(keyDown, KEY_ACTION_UP, 0);
@@ -222,20 +304,22 @@ void SdlInputHandler::raiseAllKeys()
void SdlInputHandler::notifyMouseLeave() void SdlInputHandler::notifyMouseLeave()
{ {
#ifdef Q_OS_WIN32
// SDL on Windows doesn't send the mouse button up until the mouse re-enters the window // 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()) { if (m_AbsoluteMouseMode && isCaptureActive()) {
// NB: Not using SDL_GetGlobalMouseState() because we want our state not the system's // NB: Not using SDL_GetGlobalMouseState() because we want our state not the system's
Uint32 mouseState = SDL_GetMouseState(nullptr, nullptr); Uint32 mouseState = SDL_GetMouseState(nullptr, nullptr);
for (Uint32 button = SDL_BUTTON_LEFT; button <= SDL_BUTTON_X2; button++) { for (Uint32 button = SDL_BUTTON_LEFT; button <= SDL_BUTTON_X2; button++) {
if (mouseState & SDL_BUTTON(button)) { if (mouseState & SDL_BUTTON(button)) {
m_PendingMouseLeaveButtonUp = button; SDL_CaptureMouse(SDL_TRUE);
break; break;
} }
} }
} }
#endif
} }
void SdlInputHandler::notifyFocusLost() void SdlInputHandler::notifyFocusLost()
@@ -248,11 +332,27 @@ void SdlInputHandler::notifyFocusLost()
setCaptureActive(false); setCaptureActive(false);
} }
#ifdef Q_OS_DARWIN
// Ungrab the keyboard
updateKeyboardGrabState();
#endif
// Raise all keys that are currently pressed. If we don't do this, certain keys // Raise all keys that are currently pressed. If we don't do this, certain keys
// used in shortcuts that cause focus loss (such as Alt+Tab) may get stuck down. // used in shortcuts that cause focus loss (such as Alt+Tab) may get stuck down.
raiseAllKeys(); raiseAllKeys();
} }
void SdlInputHandler::notifyFocusGained()
{
#ifdef Q_OS_DARWIN
// Re-grab the keyboard if it was grabbed before focus loss
// FIXME: We only do this on macOS because we get a spurious
// focus gain when in SDL_WINDOW_FULLSCREEN_DESKTOP on Windows
// immediately after losing focus by clicking on another window.
updateKeyboardGrabState();
#endif
}
bool SdlInputHandler::isCaptureActive() bool SdlInputHandler::isCaptureActive()
{ {
if (SDL_GetRelativeMouseMode()) { if (SDL_GetRelativeMouseMode()) {
@@ -263,26 +363,137 @@ bool SdlInputHandler::isCaptureActive()
return m_FakeCaptureActive; 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;
}
#ifdef Q_OS_DARWIN
else if (!(windowFlags & SDL_WINDOW_INPUT_FOCUS)) {
// Ungrab if we lose input focus on macOS. SDL will handle
// this internally for platforms where we use the SDL
// SDL_SetWindowKeyboardGrab() API exclusively.
//
// NB: On X11, we may not have input focus at the time of
// the initial keyboard grab update, so we must not enable
// this codepath on Linux.
shouldGrab = false;
}
#endif
// 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
#ifdef Q_OS_DARWIN
// SDL doesn't support this private macOS API
CGSSetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), CGSGlobalHotKeyDisable);
#endif
}
else {
#if SDL_VERSION_ATLEAST(2, 0, 15)
// Allow the keyboard to leave the window
SDL_SetWindowKeyboardGrab(m_Window, SDL_FALSE);
#endif
#ifdef Q_OS_DARWIN
// SDL doesn't support this private macOS API
CGSSetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), m_OldHotKeyMode);
#endif
}
}
bool SdlInputHandler::isSystemKeyCaptureActive()
{
if (m_CaptureSystemKeysMode == StreamingPreferences::CSK_OFF) {
return false;
}
if (m_Window == nullptr) {
return false;
}
Uint32 windowFlags = SDL_GetWindowFlags(m_Window);
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) void SdlInputHandler::setCaptureActive(bool active)
{ {
if (active) { if (active) {
#if defined(Q_OS_WIN32)
// 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. This can also cause problems when keyboard
// capture is enabled (modifiers will be eaten by our background window).
// We can't use SDL_RaiseWindow() because it doesn't let us know if
// SetForegroundWindow() failed. In the failure case, we must not
// enable capture to avoid trapping the mouse and/or keyboard.
{
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
SDL_GetWindowWMInfo(m_Window, &info);
SDL_assert(info.subsystem == SDL_SYSWM_WINDOWS);
if (!SetForegroundWindow(info.info.win.window)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Not enabling input capture - window is not foreground");
return;
}
}
#endif
// 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 exclusive mode, grab the cursor so it can't accidentally leave our window.
if ((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); SDL_SetWindowGrab(m_Window, SDL_TRUE);
} #endif
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);
} }
// If we're in relative mode, try to activate SDL's relative mouse mode // If we're in relative mode, try to activate SDL's relative mouse mode
if (m_AbsoluteMouseMode || SDL_SetRelativeMouseMode(SDL_TRUE) < 0) { if (m_AbsoluteMouseMode || SDL_SetRelativeMouseMode(SDL_TRUE) < 0) {
// Relative mouse mode didn't work or was disabled, so we'll just hide the cursor // 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; m_FakeCaptureActive = true;
} }
@@ -315,9 +526,17 @@ void SdlInputHandler::setCaptureActive(bool active)
SDL_SetRelativeMouseMode(SDL_FALSE); 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. // Allow the cursor to leave the bounds of our window again.
SDL_SetWindowGrab(m_Window, SDL_FALSE); SDL_SetWindowGrab(m_Window, SDL_FALSE);
#endif
} }
// Now update the keyboard grab
updateKeyboardGrabState();
} }
void SdlInputHandler::handleTouchFingerEvent(SDL_TouchFingerEvent* event) void SdlInputHandler::handleTouchFingerEvent(SDL_TouchFingerEvent* event)
+66 -1
View File
@@ -7,6 +7,7 @@
#define SDL_CODE_HIDE_CURSOR 1 #define SDL_CODE_HIDE_CURSOR 1
#define SDL_CODE_SHOW_CURSOR 2 #define SDL_CODE_SHOW_CURSOR 2
#define SDL_CODE_UNCAPTURE_MOUSE 3
struct GamepadState { struct GamepadState {
SDL_GameController* controller; SDL_GameController* controller;
@@ -28,6 +29,25 @@ struct GamepadState {
unsigned char lt, rt; unsigned char lt, rt;
}; };
#ifdef Q_OS_DARWIN
#include <CoreGraphics/CGError.h>
extern "C" {
typedef int CGSConnection;
typedef enum {
CGSGlobalHotKeyEnable = 0,
CGSGlobalHotKeyDisable = 1,
} CGSGlobalHotKeyOperatingMode;
extern CGSConnection _CGSDefaultConnection(void);
extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection connection,
CGSGlobalHotKeyOperatingMode* mode);
extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection,
CGSGlobalHotKeyOperatingMode mode);
}
#endif
#define MAX_GAMEPADS 4 #define MAX_GAMEPADS 4
#define MAX_FINGERS 2 #define MAX_FINGERS 2
@@ -64,6 +84,8 @@ public:
void handleJoystickArrivalEvent(SDL_JoyDeviceEvent* event); void handleJoystickArrivalEvent(SDL_JoyDeviceEvent* event);
void sendText(QString& string);
void rumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor); void rumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor);
void handleTouchFingerEvent(SDL_TouchFingerEvent* event); void handleTouchFingerEvent(SDL_TouchFingerEvent* event);
@@ -76,8 +98,12 @@ public:
void notifyFocusLost(); void notifyFocusLost();
void notifyFocusGained();
bool isCaptureActive(); bool isCaptureActive();
bool isSystemKeyCaptureActive();
void setCaptureActive(bool active); void setCaptureActive(bool active);
bool isMouseInVideoRegion(int mouseX, int mouseY, int windowWidth = -1, int windowHeight = -1); bool isMouseInVideoRegion(int mouseX, int mouseY, int windowWidth = -1, int windowHeight = -1);
@@ -86,10 +112,24 @@ public:
void flushMousePositionUpdate(); void flushMousePositionUpdate();
void updateKeyboardGrabState();
static static
QString getUnmappedGamepads(); QString getUnmappedGamepads();
private: private:
enum KeyCombo {
KeyComboQuit,
KeyComboUngrabInput,
KeyComboToggleFullScreen,
KeyComboToggleStatsOverlay,
KeyComboToggleMouseMode,
KeyComboToggleCursorHide,
KeyComboToggleMinimize,
KeyComboPasteText,
KeyComboMax
};
GamepadState* GamepadState*
findStateForGamepad(SDL_JoystickID id); findStateForGamepad(SDL_JoystickID id);
@@ -99,6 +139,8 @@ private:
void handleRelativeFingerEvent(SDL_TouchFingerEvent* event); void handleRelativeFingerEvent(SDL_TouchFingerEvent* event);
void performSpecialKeyCombo(KeyCombo combo);
static static
Uint32 longPressTimerCallback(Uint32 interval, void* param); Uint32 longPressTimerCallback(Uint32 interval, void* param);
@@ -117,10 +159,15 @@ private:
static static
Uint32 dragTimerCallback(Uint32 interval, void* param); Uint32 dragTimerCallback(Uint32 interval, void* param);
static
int clipboardThreadProc(void *ptr);
SDL_Window* m_Window; SDL_Window* m_Window;
bool m_MultiController; bool m_MultiController;
bool m_GamepadMouse; bool m_GamepadMouse;
bool m_SwapMouseButtons; bool m_SwapMouseButtons;
bool m_ReverseScrollDirection;
bool m_SwapFaceButtons;
SDL_TimerID m_MouseMoveTimer; SDL_TimerID m_MouseMoveTimer;
SDL_atomic_t m_MouseDeltaX; SDL_atomic_t m_MouseDeltaX;
SDL_atomic_t m_MouseDeltaY; SDL_atomic_t m_MouseDeltaY;
@@ -140,6 +187,19 @@ private:
bool m_FakeCaptureActive; bool m_FakeCaptureActive;
QString m_OldIgnoreDevices; QString m_OldIgnoreDevices;
QString m_OldIgnoreDevicesExcept; QString m_OldIgnoreDevicesExcept;
StreamingPreferences::CaptureSysKeysMode m_CaptureSystemKeysMode;
int m_MouseCursorCapturedVisibilityState;
#ifdef Q_OS_DARWIN
CGSGlobalHotKeyOperatingMode m_OldHotKeyMode;
#endif
struct {
KeyCombo keyCombo;
SDL_Keycode keyCode;
SDL_Scancode scanCode;
bool enabled;
} m_SpecialKeyCombos[KeyComboMax];
SDL_TouchFingerEvent m_LastTouchDownEvent; SDL_TouchFingerEvent m_LastTouchDownEvent;
SDL_TouchFingerEvent m_LastTouchUpEvent; SDL_TouchFingerEvent m_LastTouchUpEvent;
@@ -148,7 +208,6 @@ private:
int m_StreamHeight; int m_StreamHeight;
bool m_AbsoluteMouseMode; bool m_AbsoluteMouseMode;
bool m_AbsoluteTouchMode; bool m_AbsoluteTouchMode;
Uint32 m_PendingMouseLeaveButtonUp;
SDL_TouchFingerEvent m_TouchDownEvent[MAX_FINGERS]; SDL_TouchFingerEvent m_TouchDownEvent[MAX_FINGERS];
SDL_TimerID m_LeftButtonReleaseTimer; SDL_TimerID m_LeftButtonReleaseTimer;
@@ -157,5 +216,11 @@ private:
char m_DragButton; char m_DragButton;
int m_NumFingersDown; int m_NumFingersDown;
SDL_Thread* m_ClipboardThread;
SDL_atomic_t m_ShutdownClipboardThread;
QString m_ClipboardData;
SDL_cond* m_ClipboardHasData;
SDL_mutex* m_ClipboardLock;
static const int k_ButtonMap[]; static const int k_ButtonMap[];
}; };
+152 -150
View File
@@ -3,12 +3,6 @@
#include <Limelight.h> #include <Limelight.h>
#include <SDL.h> #include <SDL.h>
#include <QGuiApplication>
// Until we can fully capture these on all platforms (without conflicting with
// OS-provided shortcuts), we should avoid passing them through to the host.
//#define ENABLE_META
#define VK_0 0x30 #define VK_0 0x30
#define VK_A 0x41 #define VK_A 0x41
@@ -19,13 +13,134 @@
#define VK_NUMPAD0 0x60 #define VK_NUMPAD0 0x60
#endif #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();
const char* text;
if (SDL_HasClipboardText() && (text = SDL_GetClipboardText()) != nullptr) {
// Append this data to the clipboard data string for the thread to process
SDL_LockMutex(m_ClipboardLock);
m_ClipboardData.append(text);
SDL_CondSignal(m_ClipboardHasData);
SDL_UnlockMutex(m_ClipboardLock);
// 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();
}
}
#define IS_SYNTHETIC_KEY_EVENT(x) ((x)->timestamp == 0)
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event) void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
{ {
short keyCode; short keyCode;
char modifiers; char modifiers;
if (event->repeat) {
// Ignore repeat key down events
SDL_assert(event->state == SDL_PRESSED);
return;
}
// Check for our special key combos // Check for our special key combos
if ((event->state == SDL_PRESSED) && // Ignore timestamp == 0 which are sent from our keyboard code.
if (!IS_SYNTHETIC_KEY_EVENT(event) &&
(event->state == SDL_PRESSED) &&
(event->keysym.mod & KMOD_CTRL) && (event->keysym.mod & KMOD_CTRL) &&
(event->keysym.mod & KMOD_ALT) && (event->keysym.mod & KMOD_ALT) &&
(event->keysym.mod & KMOD_SHIFT)) { (event->keysym.mod & KMOD_SHIFT)) {
@@ -40,140 +155,19 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
// where the SDLK for one shortcut collides with // where the SDLK for one shortcut collides with
// the scancode of another. // the scancode of another.
// Check for quit combo (Ctrl+Alt+Shift+Q) for (int i = 0; i < KeyComboMax; i++) {
if (event->keysym.sym == SDLK_q) { if (m_SpecialKeyCombos[i].enabled && event->keysym.sym == m_SpecialKeyCombos[i].keyCode) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, performSpecialKeyCombo(m_SpecialKeyCombos[i].keyCombo);
"Detected quit key combo (SDLK)"); return;
}
// 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 for (int i = 0; i < KeyComboMax; i++) {
setCaptureActive(!isCaptureActive()); if (m_SpecialKeyCombos[i].enabled && event->keysym.scancode == m_SpecialKeyCombos[i].scanCode) {
performSpecialKeyCombo(m_SpecialKeyCombos[i].keyCombo);
// Force raise all keys to ensure they aren't stuck, return;
// 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 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;
}
}
if (event->repeat) {
// Ignore repeat key down events
SDL_assert(event->state == SDL_PRESSED);
return;
} }
// Set modifier flags // Set modifier flags
@@ -187,11 +181,11 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
if (event->keysym.mod & KMOD_SHIFT) { if (event->keysym.mod & KMOD_SHIFT) {
modifiers |= MODIFIER_SHIFT; modifiers |= MODIFIER_SHIFT;
} }
#ifdef ENABLE_META
if (event->keysym.mod & KMOD_GUI) { if (event->keysym.mod & KMOD_GUI) {
modifiers |= MODIFIER_META; if (isSystemKeyCaptureActive()) {
modifiers |= MODIFIER_META;
}
} }
#endif
// Set keycode. We explicitly use scancode here because GFE will try to correct // Set keycode. We explicitly use scancode here because GFE will try to correct
// for AZERTY layouts on the host but it depends on receiving VK_ values matching // for AZERTY layouts on the host but it depends on receiving VK_ values matching
@@ -332,14 +326,18 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
case SDL_SCANCODE_RALT: case SDL_SCANCODE_RALT:
keyCode = 0xA5; keyCode = 0xA5;
break; break;
#ifdef ENABLE_META
case SDL_SCANCODE_LGUI: case SDL_SCANCODE_LGUI:
if (!isSystemKeyCaptureActive()) {
return;
}
keyCode = 0x5B; keyCode = 0x5B;
break; break;
case SDL_SCANCODE_RGUI: case SDL_SCANCODE_RGUI:
if (!isSystemKeyCaptureActive()) {
return;
}
keyCode = 0x5C; keyCode = 0x5C;
break; break;
#endif
case SDL_SCANCODE_AC_BACK: case SDL_SCANCODE_AC_BACK:
keyCode = 0xA6; keyCode = 0xA6;
break; break;
@@ -405,15 +403,19 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
} }
} }
// Track the key state so we always know which keys are down // If this is a synthetic keypress from the clipboard code,
if (event->state == SDL_PRESSED) { // this will be on a non-main thread, so don't touch m_KeysDown.
m_KeysDown.insert(keyCode); if (!IS_SYNTHETIC_KEY_EVENT(event)) {
} // Track the key state so we always know which keys are down
else { if (event->state == SDL_PRESSED) {
m_KeysDown.remove(keyCode); m_KeysDown.insert(keyCode);
}
else {
m_KeysDown.remove(keyCode);
}
} }
LiSendKeyboardEvent(keyCode, LiSendKeyboardEvent(0x8000 | keyCode,
event->state == SDL_PRESSED ? event->state == SDL_PRESSED ?
KEY_ACTION_DOWN : KEY_ACTION_UP, KEY_ACTION_DOWN : KEY_ACTION_UP,
modifiers); modifiers);
+16 -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) // 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); Uint32 buttonState = SDL_GetMouseState(nullptr, nullptr);
if (buttonState == 0) { 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) { if (mouseInVideoRegion || m_MouseWasInVideoRegion || m_PendingMouseButtonsAllUpOnVideoRegionLeave) {
LiSendMousePositionEvent(x, y, dst.w, dst.h); LiSendMousePositionEvent(x, y, dst.w, dst.h);
@@ -146,7 +154,8 @@ void SdlInputHandler::flushMousePositionUpdate()
// executes. // executes.
SDL_Event event; SDL_Event event;
event.type = SDL_USEREVENT; 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); SDL_PushEvent(&event);
if (!mouseInVideoRegion && buttonState != 0) { if (!mouseInVideoRegion && buttonState != 0) {
@@ -204,6 +213,11 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
} }
if (event->y != 0) { if (event->y != 0) {
// Invert the scroll direction if needed
if (m_ReverseScrollDirection) {
event->y = -event->y;
}
LiSendScrollEvent((signed char)event->y); LiSendScrollEvent((signed char)event->y);
} }
} }
@@ -245,33 +259,5 @@ Uint32 SdlInputHandler::mouseMoveTimerCallback(Uint32 interval, void *param)
// Send mouse position updates if applicable // Send mouse position updates if applicable
me->flushMousePositionUpdate(); 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; return interval;
} }
Loaded 100 of 832 files, more files were not shown because too many files have changed in this diff. Show more