Compare commits

...

154 Commits

Author SHA1 Message Date
Cameron Gutman
7d544c1ce4 Move audio capabilities out of the renderer classes
Since the removal of libsoundio, all renderers support arbitrary audio duration, so we can avoid having to start an audio session just to query capabilities.
2025-10-25 19:17:38 -05:00
Cameron Gutman
ac7696ea8f Rebuild FFmpeg for macOS and OpenSSL for Windows
- Fixes unexpected Homebrew library dependency in macOS FFmpeg build
- Adds CFG and EHCont mitigations in Windows OpenSSL build
- Remove unused Windows avformat binaries

d3a3222573
2025-10-25 18:13:13 -05:00
Cameron Gutman
4d303cebee Consolidate all writes to the log stream in LoggerTask
This avoids some thread-safety issues when switching log modes or reaching the log size limit.
2025-10-25 00:33:02 -05:00
Cameron Gutman
bd6235efba Use async logging while streaming only
The benefits of reliable output outweigh performance concerns during non-streaming activities.
2025-10-25 00:18:34 -05:00
Cameron Gutman
b1232e0ed4 Fix memory corruption due to concurrent QString operations 2025-10-24 23:38:07 -05:00
Cameron Gutman
c35f7086a0 Block the Bandicam Vulkan layer from loading into Moonlight
It installs a buggy vkDestroySwapchainKHR() hook that causes Moonlight to crash deterministically on startup.

Fixes #1425
2025-10-24 23:17:03 -05:00
Cameron Gutman
c7bc76325f Revert macOS audio buffer size workaround
This causes excessive glitching and needs to be fixed by Apple (if it hasn't already in the 2 years since the report).
2025-10-23 21:42:44 -05:00
Cameron Gutman
ccaca68570 Don't use CAMetalDisplayLink on Intel Macs
When in Direct mode, skipping a frame will cause the display to flash black on Tahoe.
2025-10-23 21:16:53 -05:00
Cameron Gutman
57db20016a Rewrite frame pacing mechanism for VTMetalRenderer
This should hopefully improve performance and avoid performance issues on Tahoe.
2025-10-23 19:43:10 -05:00
Cameron Gutman
bdb6d02dac Add (currently harmless) missing break 2025-10-22 23:21:07 -05:00
Cameron Gutman
9bcc6291be Auto-lock the cursor in single display borderless windowed scenarios 2025-10-22 23:13:03 -05:00
Brad Smith
4ec549650d Add fast AES detection for FreeBSD/OpenBSD on ARM/AArch64 using elf_aux_info() 2025-10-22 18:40:20 -05:00
Cameron Gutman
14027f3c74 Limit the IME workaround to Windows only 2025-10-20 23:37:31 -05:00
Qian23333
fb9a164111 feat: disable text input when window gains focus 2025-10-20 23:33:10 -05:00
Cameron Gutman
200cab9d17 Attach EDR metadata to Metal layer
This improves the accuracy of HDR streaming and enables HDR->SDR tonemapping.
2025-10-20 22:28:27 -05:00
Brad Smith
c52a57f0ec build: Fix building on *BSD/riscv64 2025-10-20 21:21:00 -05:00
Cameron Gutman
b4dc7ca7cb Build AppImage with Qt 6 2025-10-20 20:51:51 -05:00
Cameron Gutman
00c1dd0d0d Fix missing svg module in AppImage 2025-10-20 01:01:25 -05:00
Cameron Gutman
e6e91ca88b Disable CUDA by default in favor of Vulkan and VDPAU
All of our official releases (Flatpak and AppImage) already do this, so this just makes it official.

This can be overridden by running qmake with CONFIG+=enable-cuda.
2025-10-19 21:24:29 -05:00
Cameron Gutman
260a0e0ae2 Use Ubuntu Jammy base OS for AppImage 2025-10-19 21:11:34 -05:00
Cameron Gutman
3f8f4744c5 Reduce CPU usage from background PC polling 2025-10-19 20:50:29 -05:00
Cameron Gutman
4bbd02fb2d Reduce the priority of background polling threads 2025-10-19 17:31:33 -05:00
Cameron Gutman
c5ca672865 Don't set pix_fmt for hwaccel decoders
This works around a bug in the AV1 Vulkan decoding code in FFmpeg
that causes it to incorrectly skip hwaccel init.

Fixes #1511
2025-10-19 16:16:06 -05:00
Cameron Gutman
4688f3650c Handle IPv6 literals without URL escaping too
Fixes #1547
2025-10-19 13:31:27 -05:00
celeresx
2550f416f4
Clarify Steam Link hardware limitations in README (fixes #1558) (#1612)
Co-authored-by: Cameron Gutman <aicommander@gmail.com>
2025-10-19 12:58:16 -05:00
Cameron Gutman
579ad25a01 Add CONFIG+=disable-masterhooks QMake option
This allows disabling the DRM master hooks for environments where
getting DRM master is impossible (such as sandboxes like Flatpak),
but leave the DRM renderer itself enabled for usecases like V4L2
which require the DRM renderer to export DMA-BUFs to EGL.
2025-10-12 23:03:53 -05:00
Cameron Gutman
1144dbccb3 Don't call dlsym() in our DRM master hooks
Not only is it faster to cache the function pointers, calling
dlsym() inside open()/close() can lead to deadlocks when using
Vulkan Video decoding on top of the Nvidia driver.
2025-10-12 22:49:29 -05:00
Cameron Gutman
ff81f74391 Enable D3D12VA hwaccels in FFmpeg 2025-10-12 15:56:10 -05:00
Cameron Gutman
c0d38ee78f Switch the decoder lock to a mutex
It can be held for non-trivial amounts of time.
2025-10-12 15:47:09 -05:00
Cameron Gutman
c9cb64f90b Fix StreamSegue BusyIndicator running after streaming
See #1695
2025-10-11 15:50:45 -05:00
Eval EXEC
93e597a93f Stop hidden BusyIndicators from burning CPU 2025-10-11 15:34:55 -05:00
Cameron Gutman
82b33c033a Update and rebuild all prebuilt libraries 2025-10-11 14:41:03 -05:00
Cameron Gutman
be266d3349 Disable toolbar icon scaling
Closes #1192
2025-10-11 12:55:54 -05:00
Cameron Gutman
c618a0b5df Use native QML icon support 2025-10-11 12:52:37 -05:00
Cameron Gutman
a20d429bc1 Remove usage of deprecated Metal API 2025-10-11 11:41:28 -05:00
Cameron Gutman
2b3e0803de Increase Qt requirement to 5.12 and remove pre-5.12 workarounds 2025-10-11 11:03:33 -05:00
Cameron Gutman
4189903233 Remove libsoundio audio backend
SDL has been the default audio backend for years now, and libsoundio has
not been well-maintained upstream.
2025-10-11 10:35:31 -05:00
Cameron Gutman
06b5c4631f Fix warning for unnecessary .desktop suffix passed to setDesktopFileName() 2025-10-11 00:36:54 -05:00
Cameron Gutman
997c4aa0ae Fix unchecked return value warnings using QIODevice::Open() 2025-10-11 00:24:02 -05:00
dependabot[bot]
f4343c5f29 Bump app/SDL_GameControllerDB from 7979e7b to 38fc811
Bumps [app/SDL_GameControllerDB](https://github.com/gabomdq/SDL_GameControllerDB) from `7979e7b` to `38fc811`.
- [Commits](7979e7b292...38fc811c71)

---
updated-dependencies:
- dependency-name: app/SDL_GameControllerDB
  dependency-version: 38fc811c715365e963a6942092cae147eddddc90
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-11 00:13:38 -05:00
Cameron Gutman
3cdea6b039 Update VC redistributable in installer bundle 2025-10-11 00:12:19 -05:00
Cameron Gutman
ac2e10c712 Update to WiX v6.0.2 2025-10-11 00:11:09 -05:00
Simon Pawlowski
61fa45ad21
Fixed translation (#1669)
MBps is used for megabytes, even in German. Mbps is the correct translation.
2025-10-10 00:49:25 -05:00
Cameron Gutman
f362e76127 Sync AppImage dependency versions with moonlight-deps 2025-10-10 00:45:58 -05:00
Cameron Gutman
749c69fc76 Build Windows against Qt 6.9 in CI 2025-10-10 00:41:48 -05:00
Cameron Gutman
5dca30def1 Rerun lupdate and lrelease 2025-10-10 00:35:37 -05:00
Cameron Gutman
502783a86b Merge remote-tracking branch 'origin/weblate' 2025-10-10 00:33:55 -05:00
Cameron Gutman
45989fdd6e Fix incorrect toolbar color on Qt 6.9+
Fixes #1685
2025-10-09 20:47:58 -05:00
Cameron Gutman
490aa5082f Don't use SDL locking functions in our open()/close() hooks
Other shared library constructors can invoke open()/close() before
SDL2-compat's constructor runs to load SDL3 and populate the SDL3
function table. This causes SDL_AtomicLock()/SDL_AtomicUnlock()
to jump to 0.

See #1707
2025-10-09 20:03:19 -05:00
Taylor Lineman
ae1c65805c
Enable Game Mode on Apple Platforms (#1709) 2025-10-09 18:10:44 -05:00
Cameron Gutman
9cb4105aec Fix incorrect getToggleOptionValue behavior 2025-08-31 15:15:24 -05:00
armin-25689
1fd545ae1f build: fix no <linux/dma-buf.h> for BSDs 2025-08-31 15:05:03 -05:00
Daniel Nylander
3194cb09d8
Translated using Weblate (Swedish)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/sv/
2025-08-31 08:05:47 +02:00
Cameron Gutman
f9bb45579b Enable CFG, EHCont, and CET for Windows builds 2025-08-24 20:42:37 -05:00
grgergo
f7b2edc8e3
Translated using Weblate (Hungarian)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/hu/
2025-08-13 20:02:11 +02:00
veldenb
c9a3946d80
Translated using Weblate (Dutch)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nl/
2025-07-29 01:43:50 +02:00
Laszlo Racz
0c8354336b Fix: Improve stream window resizing logic 2025-07-27 14:23:12 -05:00
Cameron Gutman
1bf86f52d3 Deregister logging callbacks before destroying the logger 2025-07-04 15:56:11 -05:00
dependabot[bot]
504d42865d Bump app/SDL_GameControllerDB from e5a5fa2 to 7979e7b
Bumps [app/SDL_GameControllerDB](https://github.com/gabomdq/SDL_GameControllerDB) from `e5a5fa2` to `7979e7b`.
- [Commits](e5a5fa2ac6...7979e7b292)

---
updated-dependencies:
- dependency-name: app/SDL_GameControllerDB
  dependency-version: 7979e7b29261c11ebce2deabc41ed081b6691398
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-04 15:05:05 -05:00
Odizinne
65c04fd560 Added option to disable pre launch warning and delay 2025-07-04 15:04:10 -05:00
Cameron Gutman
11dc244857
Create dependabot.yml for submodule updates 2025-07-04 14:46:44 -05:00
FrogTheFrog
b9cab4cac5 fix outdated app list printing via cli 2025-07-04 14:32:47 -05:00
6690
65647a32f4
Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2025-06-26 22:59:02 +02:00
therealmate
777502b495
Translated using Weblate (Hungarian)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/hu/
2025-06-24 07:01:47 +00:00
தமிழ்நேரம்
ce4309694f
Translated using Weblate (Tamil)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ta/
2025-06-23 05:16:19 +02:00
therealmate
ccc64a5629
Translated using Weblate (Hungarian)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/hu/
2025-06-23 05:16:19 +02:00
Batıkan Ökten
25f6c020b6
Translated using Weblate (Turkish)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/tr/
2025-06-03 01:23:26 +02:00
caviaz
6a95742a92
Translated using Weblate (Dutch)
Currently translated at 84.4% (211 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nl/
2025-05-27 09:01:44 +02:00
FrogTheFrog
1dbdcb5279 Prevent double printing app list via cli 2025-05-07 18:27:00 -05:00
GOGOsu
9ce0537587
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2025-05-07 11:01:44 +02:00
arrhteeime
76d03f015d
Translated using Weblate (Russian)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2025-05-02 15:02:58 +02:00
aga_90
edbc24fa3e
Translated using Weblate (Hungarian)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/hu/
2025-05-01 01:09:49 +02:00
Nicolas Xavier Herrera Medina
d4fd50b973
Translated using Weblate (Spanish)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2025-04-25 10:12:30 +02:00
sanhoe
d5ab795b36
Translated using Weblate (Korean)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2025-04-20 10:06:37 +02:00
Jorys Paulin
c5fc5220f3
Translated using Weblate (French)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2025-04-18 13:00:11 +02:00
Alex
28dea31533
Translated using Weblate (German)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2025-04-16 02:21:21 +02:00
Любомир Василев
8ce6a9585f
Translated using Weblate (Bulgarian)
Currently translated at 100.0% (250 of 250 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/bg/
2025-04-08 05:36:27 +02:00
Cameron Gutman
47452371db Rerun lupdate and lrelease 2025-04-05 20:29:24 -05:00
Cameron Gutman
4001b05fca Add new languages 2025-04-05 20:29:09 -05:00
Cameron Gutman
75e917622e Merge remote-tracking branch 'origin/weblate' 2025-04-05 20:15:30 -05:00
Cameron Gutman
75359bb1c4 Fix tooltip layout with Qt 6.9 2025-04-05 00:27:50 -05:00
Cameron Gutman
c93f7e7385 Output CLI error/info messages to the console on Windows
Fixes #1554
2025-04-04 22:23:17 -05:00
Cameron Gutman
48bcd725c9 Use static QRegularExpressions as recommended by Clazy 2025-04-04 22:13:45 -05:00
MoreOrLessSoftware
e807a52cfa Add bitrate auto-adjustment toggle and reset button
This change adds a 'Use Default' button next to the bitrate slider that resets the bitrate to the default value for the current resolution/FPS. It also implements an autoAdjustBitrate setting that controls whether the bitrate is automatically updated when resolution/FPS/YUV444 settings change.

- When the user manually adjusts the bitrate slider, autoAdjustBitrate is set to false
- When the user clicks the 'Use Default' button, autoAdjustBitrate is set to true
- Bitrate is only updated automatically when settings change if autoAdjustBitrate is true
2025-04-02 22:02:09 -05:00
James Poje
9c9bfd8428 Add quit-app-and-exit shortcut Ctrl+Alt+Shift+E
Add a shortcut to quit the current app and exit Moonlight entirely.
2025-04-02 21:55:38 -05:00
Jorys Paulin
29b1304337 feat: hide hw acceleration warning when using forced software decoding 2025-04-02 21:46:11 -05:00
Jorys Paulin
cc0b574bb1 feat: add message when host doesn't show any apps 2025-04-02 21:45:23 -05:00
Cameron Gutman
7a769172a1 Fix null window on Qt 6.9 2025-04-02 21:14:22 -05:00
ABeltramo
d9c7a245ef
DualSense adaptive trigger support (#1561) 2025-04-01 21:45:25 -05:00
Serdar Sağlam
469a5c78f1
Translated using Weblate (Turkish)
Currently translated at 100.0% (248 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/tr/
2025-03-27 04:15:37 +01:00
rinsuki
fabb4fdadc Fix INTERNATIONAL keys wouldn't work properly
since those keys aren't exist in US keyboard, we shouldn't treat (and convert) as a US keyboard keycodes (on Sunshine).
2025-02-19 22:18:34 -06:00
rinsuki
9491884cb4 Add some key definitions to support Japanese Keyboard Layout
Tested environment:

Moonlight on macOS 15.3 w/ MacBook Pro Internal Keyboard
Sunshine 2025.122.141614 w/ Windows 11 24H2
2025-02-19 22:18:34 -06:00
Cameron Gutman
bf51577787 Fix macOS build 2025-02-18 19:52:33 -06:00
Cameron Gutman
4c5bcee8dc Give up immediately if the backend fails to initialize 2025-02-18 19:46:29 -06:00
Cameron Gutman
fd70865026 Avoid retrying renderer init if we know the error was not transient 2025-02-18 19:02:12 -06:00
Cameron Gutman
351aaa6759 Add an enum type for each renderer 2025-02-18 18:58:04 -06:00
Philip Goto
59bc625cc7
Translated using Weblate (Dutch)
Currently translated at 84.6% (210 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nl/
2025-02-15 23:14:56 +01:00
GOGOsu
b2f765e8ef
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (248 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2025-02-11 07:01:57 +01:00
Kolja
bdd9a3a994 Add Network and RemoteAccess categories 2025-02-07 20:21:56 -06:00
Philip Goto
2ecafabcab
Translated using Weblate (Dutch)
Currently translated at 83.4% (207 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nl/
2025-02-05 14:01:58 +00:00
Cameron Gutman
dd2a99a96b Prepare for SDL3 support 2025-01-25 16:20:20 -06:00
தமிழ்நேரம்
9ff2ac0974
Translated using Weblate (Tamil)
Currently translated at 100.0% (248 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ta/
2025-01-19 10:34:48 +01:00
தமிழ்நேரம்
5760d08c33
Added translation using Weblate (Tamil) 2025-01-18 14:02:22 +01:00
Cameron Gutman
edd7a134d8 Fix MSVC binary path in CI for Qt 6.8 2025-01-13 20:11:06 -06:00
Cameron Gutman
ffa87c5f01 Update Windows AppVeyor builds to Qt 6.8 2025-01-13 19:48:47 -06:00
ZerOriSama
ff7e61c6d9
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (248 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2025-01-06 00:01:23 +00:00
Любомир Василев
6ad96fba42
Translated using Weblate (Bulgarian)
Currently translated at 100.0% (248 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/bg/
2024-12-30 13:18:25 +01:00
Любомир Василев
85856114b2
Translated using Weblate (Bulgarian)
Currently translated at 97.9% (243 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/bg/
2024-12-23 10:43:01 +01:00
Любомир Василев
7decfae792
Translated using Weblate (Bulgarian)
Currently translated at 87.5% (217 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/bg/
2024-12-22 09:00:43 +00:00
Любомир Василев
b6008b15dc
Translated using Weblate (Bulgarian)
Currently translated at 53.6% (133 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/bg/
2024-12-21 09:00:36 +01:00
Любомир Василев
260ec3d80d
Translated using Weblate (Bulgarian)
Currently translated at 38.7% (96 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/bg/
2024-12-20 08:25:55 +01:00
Любомир Василев
7ec2e50334
Translated using Weblate (Bulgarian)
Currently translated at 13.7% (34 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/bg/
2024-12-15 14:06:34 +01:00
Любомир Василев
e532b9167a
Added translation using Weblate (Bulgarian) 2024-12-14 13:07:53 +01:00
Moritz Schirmer
52d5890372
Translated using Weblate (German)
Currently translated at 97.1% (241 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2024-12-06 06:59:09 +01:00
Dark Space
f34d11994f
Translated using Weblate (Italian)
Currently translated at 100.0% (248 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/it/
2024-11-23 13:00:19 +01:00
Rouvr
359c92340d
Translated using Weblate (Czech)
Currently translated at 86.6% (215 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/cs/
2024-11-13 20:00:25 +01:00
Cameron Gutman
15e337fff8 Only call SDL_NumJoysticks() once per loop
It does non-trivial work when using sdl2-compat.
2024-11-12 23:52:31 -06:00
Cameron Gutman
707dd3cb83 Checkout libs submodule for Steam Link too 2024-11-08 21:32:32 -06:00
Cameron Gutman
98f6a09991 Use a Ne10-optimized libopus build for Steam Link
Steam Link is an incredibly CPU-constrained platform, so it needs
all the help it can get to avoid audio underruns.
2024-11-08 21:26:45 -06:00
Jorys Paulin
f1d0e97681
Translated using Weblate (French)
Currently translated at 100.0% (248 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2024-11-03 12:00:23 +01:00
Cameron Gutman
103f988dbf Move logging into a separate thread 2024-10-20 21:52:15 -05:00
Cameron Gutman
2257cb0cef Update Windows, macOS, and AppImage libraries
SDL -> 86fd4ed
FFmpeg -> 7.1 (87ccf995c)
dav1d -> 1.5.0
2024-10-20 13:46:05 -05:00
Cameron Gutman
b6a3369243 Rework window focus tracking for gamepad navigation 2024-10-18 21:20:56 -05:00
Cameron Gutman
4af9623727 Replace C++ VLAs with std::vector 2024-10-15 22:41:21 -05:00
Cameron Gutman
515db03fe5 Add missing stdbool.h include 2024-10-15 22:38:06 -05:00
Matthias Küch
e44d097683 Add stderr log for Steam Link 2024-10-12 00:14:51 -05:00
Jorys Paulin
208d048358
Translated using Weblate (French)
Currently translated at 100.0% (248 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2024-10-08 14:15:38 +02:00
Cameron Gutman
9936085aee Fix incorrect selection if pix_fmt is overridden before ffGetFormat()
The DRM renderer does this for the out-of-tree v4l2m2m patches to ensure
we get NV12 buffers, but it ends up clobbering our own preference for
DRM_PRIME frames in the process.
2024-10-03 20:28:51 -05:00
Cameron Gutman
3279d9c3f6 Fix QML component versioning error on Qt 5 2024-10-03 00:15:53 -05:00
phlostically
e571d5833c
Translated using Weblate (Esperanto)
Currently translated at 9.2% (23 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/eo/
2024-10-02 23:16:22 +02:00
Cameron Gutman
3531fe0a4f Fix typo in version check for new Vulkan queue API 2024-10-02 00:37:22 -05:00
phlostically
286676e5c1
Added translation using Weblate (Esperanto) 2024-10-01 22:36:26 +02:00
Cameron Gutman
6ce02616f0 Use new Vulkan queue API in FFmpeg 7.1 2024-09-29 23:04:23 -05:00
Cameron Gutman
13880353d8 Use new avcodec_get_supported_config() API in FFmpeg 7.1 2024-09-29 22:23:37 -05:00
Cameron Gutman
ec69dad8d7 Fix import incorrectly removed by 2a63ad5 2024-09-28 00:09:21 -05:00
Cameron Gutman
72ae324d71 Fix unused parameter warnings 2024-09-28 00:08:35 -05:00
Cameron Gutman
901cbd255c Disable Vulkan debug layers by default
The vast majority of Vulkan code running in Moonlight
is part of FFmpeg or libplacebo, so the debug layers
really just slow things down without finding any bugs
in our code.

Additionally, there are some overzealous checks firing
constantly on libplacebo and FFmpeg with certain Vulkan
drivers that we can't do anything about.
2024-09-28 00:05:17 -05:00
Cameron Gutman
2a63ad53d7 Don't poll gamepad input when the GUI is not focused/visible 2024-09-26 19:24:29 -05:00
Cameron Gutman
9b3d4c1ad7 Free the old CRTC connectors array 2024-09-23 22:25:51 -05:00
Cameron Gutman
054e334066 Allow Qt to borrow DRM master from SDL to update the UI 2024-09-23 22:15:31 -05:00
Cameron Gutman
6d023c2dfa Defer launch warnings until after launch validation
We need to destroy the SDL window before we can be sure that Qt
can draw to the screen if we used KMSDRM.
2024-09-23 21:48:43 -05:00
Cameron Gutman
0e2d5bf441 Fix EGLFS state restoration after Vulkan rendering 2024-09-23 21:32:09 -05:00
Cameron Gutman
023b6b2772 Fix DRM FD leak with Vulkan windows 2024-09-21 21:41:43 -05:00
Cameron Gutman
6f39d120cb Unify handling of DRM devices between DRM and VAAPI
SDL may not be able to give us a DRM FD for Vulkan windows.
2024-09-21 20:46:11 -05:00
Cameron Gutman
9cf305865b Add support for managing multiple SDL DRM FDs
This is required for Vulkan+KMSDRM rendering.
2024-09-21 20:41:43 -05:00
Patrick Sletvold
6d47287b60
Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.1% (246 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nb_NO/
2024-09-21 11:40:45 +00:00
Cameron Gutman
6b11f43302 Don't set the output rect until after modesetting
It's possible that modesetting will also change the resolution
not just the refresh rate. This can happen in cases where the
CRTC is currently set to 4K 30 Hz and we choose 1080p 60 Hz as
a better mode match for displaying a 1080p 60 FPS stream.
2024-09-19 22:43:34 -05:00
Tomonobu Terakubo
60fb6881b2
Translated using Weblate (Japanese)
Currently translated at 100.0% (248 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ja/
2024-09-20 01:40:49 +02:00
Артём Журин
b79c116bd9
Translated using Weblate (Russian)
Currently translated at 100.0% (248 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2024-09-20 01:40:48 +02:00
Patrick Sletvold
895d0a6bf3
Translated using Weblate (Norwegian Bokmål)
Currently translated at 71.3% (177 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nb_NO/
2024-09-20 01:40:48 +02:00
Cameron Gutman
5a1ef55767
Update README.md
Add note about YUV 4:4:4 and update build requirements
2024-09-18 22:16:33 -05:00
Matt Tannahill
76deafbd7b Fix build for Xcode < 14 2024-09-18 22:12:04 -05:00
Tomonobu Terakubo
ae2693a860
Translated using Weblate (Japanese)
Currently translated at 95.9% (238 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ja/
2024-09-18 13:40:50 +00:00
Артём Журин
0783b28ba6
Translated using Weblate (Russian)
Currently translated at 100.0% (248 of 248 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2024-09-18 13:40:50 +00:00
148 changed files with 12452 additions and 7640 deletions

9
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,9 @@
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "gitsubmodule"
directory: "/"
schedule:
interval: "daily"

3
.gitmodules vendored
View File

@ -7,9 +7,6 @@
[submodule "app/SDL_GameControllerDB"]
path = app/SDL_GameControllerDB
url = https://github.com/gabomdq/SDL_GameControllerDB.git
[submodule "soundio/libsoundio"]
path = soundio/libsoundio
url = https://github.com/cgutman/libsoundio.git
[submodule "h264bitstream/h264bitstream"]
path = h264bitstream/h264bitstream
url = https://github.com/aizvorski/h264bitstream.git

View File

@ -207,6 +207,28 @@ private:
// Since the RTSS OSD doesn't even work with DXVA2, we'll just block the hooks entirely.
L"RTSSHooks.dll",
L"RTSSHooks64.dll",
// Bandicam's Vulkan layer DLL crashes during destruction of the Vulkan swapchain
// bdcamvk64+0x10b73:
// 00007ffa`be3b0b73 498b5630 mov rdx,qword ptr [r14+30h] ds:00000000`00000030=????????????????
//
// bdcamvk64+0x10b73
// libplacebo_357!vk_sw_destroy+0x14f [D:\a\moonlight-deps\moonlight-deps\libplacebo\src\vulkan\swapchain.c @ 460]
// libplacebo_357!pl_swapchain_destroy+0x1a [D:\a\moonlight-deps\moonlight-deps\libplacebo\src\swapchain.c @ 30]
// Moonlight!PlVkRenderer::~PlVkRenderer+0x159 [D:\a\moonlight-qt\app\streaming\video\ffmpeg-renderers\plvk.cpp @ 140]
// Moonlight!PlVkRenderer::`scalar deleting destructor'+0x17
// Moonlight!FFmpegVideoDecoder::reset+0x203 [D:\a\moonlight-qt\app\streaming\video\ffmpeg.cpp @ 301]
// Moonlight!FFmpegVideoDecoder::~FFmpegVideoDecoder+0x22 [D:\a\moonlight-qt\app\streaming\video\ffmpeg.cpp @ 257]
// Moonlight!FFmpegVideoDecoder::`scalar deleting destructor'+0x17
// Moonlight!Session::getDecoderInfo+0x19a [D:\a\moonlight-qt\app\streaming\session.cpp @ 415]
// Moonlight!SystemProperties::querySdlVideoInfoInternal+0x134 [D:\a\moonlight-qt\app\backend\systemproperties.cpp @ 161]
// Moonlight!SystemProperties::querySdlVideoInfo+0x97 [D:\a\moonlight-qt\app\backend\systemproperties.cpp @ 123]
// Moonlight!SystemProperties::SystemProperties+0x4bf [D:\a\moonlight-qt\app\backend\systemproperties.cpp @ 75]
//
// https://github.com/moonlight-stream/moonlight-qt/issues/1425
// https://github.com/moonlight-stream/moonlight-qt/issues/1579
L"bdcamvk32.dll",
L"bdcamvk64.dll",
};
};

View File

@ -13,6 +13,7 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
## Features
- Hardware accelerated video decoding on Windows, Mac, and Linux
- H.264, HEVC, and AV1 codec support (AV1 requires Sunshine and a supported host GPU)
- YUV 4:4:4 support (Sunshine only)
- HDR streaming support
- 7.1 surround sound audio support
- 10-point multitouch support (Sunshine only)
@ -39,7 +40,7 @@ Hosting for Moonlight's Debian and L4T package repositories is graciously provid
## Building
### Windows Build Requirements
* Qt 5.15 SDK or later. Qt 6 is also supported for x64 and ARM64 builds.
* Qt 6.7 SDK or later (earlier versions may work but are not officially supported)
* [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) (Community edition is fine)
* Select **MSVC** option during Qt installation. MinGW is not supported.
* [7-Zip](https://www.7-zip.org/) (only if building installers for non-development PCs)
@ -48,12 +49,12 @@ Hosting for Moonlight's Debian and L4T package repositories is graciously provid
* Alternatively, run `dism /online /add-capability /capabilityname:Tools.Graphics.DirectX~~~~0.0.1.0` and reboot.
### macOS Build Requirements
* Qt 6.4 SDK or later
* Xcode 13 or later
* Qt 6.7 SDK or later (earlier versions may work but are not officially supported)
* Xcode 14 or later (earlier versions may work but are not officially supported)
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
### Linux/Unix Build Requirements
* Qt 6 is recommended, but Qt 5.9 or later is also supported (replace `qmake6` with `qmake` when using Qt 5).
* Qt 6 is recommended, but Qt 5.12 or later is also supported (replace `qmake6` with `qmake` when using Qt 5).
* GCC or Clang
* FFmpeg 4.0 or later
* Install the required packages:
@ -71,10 +72,17 @@ Hosting for Moonlight's Debian and L4T package repositories is graciously provid
* [Steam Link SDK](https://github.com/ValveSoftware/steamlink-sdk) cloned on your build system
* STEAMLINK_SDK_PATH environment variable set to the Steam Link SDK path
**Steam Link Hardware Limitations**
Moonlight builds for Steam Link are subject to hardware limitations of the Steam Link device:
* Maximum resolution: **1080p (1920x1080)**
* Maximum framerate: **60 FPS**
* Maximum video bitrate: **40 Mbps**
* **HDR streaming is not supported** on the original hardware
### Build Setup Steps
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
* You can install Qt via Homebrew on macOS, but you will need to use `brew install qt --with-debug` to be able to create debug builds of Moonlight.
* You may also use your Linux distro's package manager for the Qt SDK as long as the packages are Qt 5.9 or later.
* You may also use your Linux distro's package manager for the Qt SDK as long as the packages are Qt 5.12 or later.
* This step is not required for building on Steam Link, because the Steam Link SDK includes Qt 5.14.
2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
3. Open the project in Qt Creator or build from qmake on the command line.

View File

@ -2,8 +2,12 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>GCSupportsGameMode</key>
<true/>
<key>CFBundleExecutable</key>
<string>Moonlight</string>
<key>LSSupportsGameMode</key>
<true/>
<key>CFBundleGetInfoString</key>
<string>Stream games and other applications from another PC</string>
<key>CFBundleIconFile</key>
@ -44,5 +48,7 @@
<string>VERSION</string>
<key>CFBundleDisplayName</key>
<string>Moonlight</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.games</string>
</dict>
</plist>

@ -1 +1 @@
Subproject commit e5a5fa2ac6e645d72c619ea99520a3a4586ee005
Subproject commit 38fc811c715365e963a6942092cae147eddddc90

67
app/SDL_compat.h Normal file
View File

@ -0,0 +1,67 @@
//
// Compatibility header for older version of SDL.
// Include this instead of SDL.h directly.
//
#pragma once
#include <SDL.h>
// SDL_FRect wasn't added until 2.0.10
#if !SDL_VERSION_ATLEAST(2, 0, 10)
typedef struct SDL_FRect
{
float x;
float y;
float w;
float h;
} SDL_FRect;
#endif
#ifndef SDL_HINT_VIDEO_X11_FORCE_EGL
#define SDL_HINT_VIDEO_X11_FORCE_EGL "SDL_VIDEO_X11_FORCE_EGL"
#endif
#ifndef SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER
#define SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER "SDL_KMSDRM_REQUIRE_DRM_MASTER"
#endif
#ifndef SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED
#define SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED "SDL_ALLOW_ALT_TAB_WHILE_GRABBED"
#endif
#ifndef SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE
#define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE "SDL_JOYSTICK_HIDAPI_PS4_RUMBLE"
#endif
#ifndef SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE
#define SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE "SDL_JOYSTICK_HIDAPI_PS5_RUMBLE"
#endif
#ifndef SDL_HINT_WINDOWS_USE_D3D9EX
#define SDL_HINT_WINDOWS_USE_D3D9EX "SDL_WINDOWS_USE_D3D9EX"
#endif
#ifndef SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS
#define SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS "SDL_GAMECONTROLLER_USE_BUTTON_LABELS"
#endif
#ifndef SDL_HINT_MOUSE_RELATIVE_SCALING
#define SDL_HINT_MOUSE_RELATIVE_SCALING "SDL_MOUSE_RELATIVE_SCALING"
#endif
#ifndef SDL_HINT_AUDIO_DEVICE_APP_NAME
#define SDL_HINT_AUDIO_DEVICE_APP_NAME "SDL_AUDIO_DEVICE_APP_NAME"
#endif
#ifndef SDL_HINT_APP_NAME
#define SDL_HINT_APP_NAME "SDL_APP_NAME"
#endif
#ifndef SDL_HINT_MOUSE_AUTO_CAPTURE
#define SDL_HINT_MOUSE_AUTO_CAPTURE "SDL_MOUSE_AUTO_CAPTURE"
#endif
#ifndef SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP
#define SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP "SDL_VIDEO_WAYLAND_EMULATE_MOUSE_WARP"
#endif

View File

@ -69,7 +69,12 @@ macx:!disable-prebuilts {
unix:if(!macx|disable-prebuilts) {
CONFIG += link_pkgconfig
PKGCONFIG += openssl sdl2 SDL2_ttf opus
PKGCONFIG += openssl sdl2 SDL2_ttf
# We have our own optimized libopus.a for Steam Link
if(!config_SL|disable-prebuilts) {
PKGCONFIG += opus
}
!disable-ffmpeg {
packagesExist(libavcodec) {
@ -117,7 +122,9 @@ unix:if(!macx|disable-prebuilts) {
}
}
!disable-cuda {
# Disabled by default due to reliability issues. See #1314.
# CUDA interop is superseded by VDPAU and Vulkan Video.
enable-cuda {
packagesExist(ffnvcodec) {
PKGCONFIG += ffnvcodec
CONFIG += cuda
@ -152,20 +159,16 @@ win32 {
CONFIG += ffmpeg libplacebo
}
win32:!winrt {
CONFIG += soundio discord-rpc
CONFIG += discord-rpc
}
macx {
!disable-prebuilts {
LIBS += -lssl.3 -lcrypto.3 -lavcodec.61 -lavutil.59 -lswscale.8 -lopus -framework SDL2 -framework SDL2_ttf
LIBS += -lssl.3 -lcrypto.3 -lavcodec.62 -lavutil.60 -lswscale.9 -lopus -framework SDL2 -framework SDL2_ttf
CONFIG += discord-rpc
}
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal -framework QuartzCore
# For libsoundio
LIBS += -framework CoreAudio -framework AudioUnit
CONFIG += ffmpeg soundio
CONFIG += ffmpeg
}
SOURCES += \
@ -209,6 +212,7 @@ SOURCES += \
wm.cpp
HEADERS += \
SDL_compat.h \
backend/nvaddress.h \
backend/nvapp.h \
cli/pair.h \
@ -323,9 +327,11 @@ libdrm {
HEADERS += streaming/video/ffmpeg-renderers/drm.h
linux {
message(Master hooks enabled)
SOURCES += masterhook.c masterhook_internal.c
LIBS += -ldl
!disable-masterhooks {
message(Master hooks enabled)
SOURCES += masterhook.c masterhook_internal.c
LIBS += -ldl -pthread
}
}
}
cuda {
@ -364,6 +370,13 @@ config_EGL {
config_SL {
message(Steam Link build configuration selected)
!disable-prebuilts {
# Link against our NEON-optimized libopus build
LIBS += -L$$PWD/../libs/steamlink/lib
INCLUDEPATH += $$PWD/../libs/steamlink/include
LIBS += -lopus -larmasm -lNE10
}
DEFINES += EMBEDDED_BUILD STEAM_LINK HAVE_SLVIDEO HAVE_SLAUDIO
LIBS += -lSLVideo -lSLAudio
@ -401,13 +414,6 @@ macx {
HEADERS += \
streaming/video/ffmpeg-renderers/vt.h
}
soundio {
message(libsoundio audio renderer selected)
DEFINES += HAVE_SOUNDIO SOUNDIO_STATIC_LIBRARY
SOURCES += streaming/audio/renderers/soundioaudiorenderer.cpp
HEADERS += streaming/audio/renderers/soundioaudiorenderer.h
}
discord-rpc {
message(Discord integration enabled)
@ -473,7 +479,10 @@ TRANSLATIONS += \
languages/qml_he.ts \
languages/qml_ckb.ts \
languages/qml_lt.ts \
languages/qml_et.ts
languages/qml_et.ts \
languages/qml_bg.ts \
languages/qml_eo.ts \
languages/qml_ta.ts
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
@ -495,15 +504,6 @@ else:unix: LIBS += -L$$OUT_PWD/../qmdnsengine/ -lqmdnsengine
INCLUDEPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
DEPENDPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
soundio {
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../soundio/release/ -lsoundio
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../soundio/debug/ -lsoundio
else:unix: LIBS += -L$$OUT_PWD/../soundio/ -lsoundio
INCLUDEPATH += $$PWD/../soundio/libsoundio
DEPENDPATH += $$PWD/../soundio/libsoundio
}
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../h264bitstream/release/ -lh264bitstream
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../h264bitstream/debug/ -lh264bitstream
else:unix: LIBS += -L$$OUT_PWD/../h264bitstream/ -lh264bitstream

View File

@ -9,8 +9,7 @@
#include <QThread>
#include <QThreadPool>
#include <QCoreApplication>
#include <random>
#include <QRandomGenerator>
#define SER_HOSTS "hosts"
#define SER_HOSTS_BACKUP "hostsbackup"
@ -30,9 +29,9 @@ public:
}
private:
bool tryPollComputer(NvAddress address, bool& changed)
bool tryPollComputer(QNetworkAccessManager* nam, NvAddress address, bool& changed)
{
NvHTTP http(address, 0, m_Computer->serverCert);
NvHTTP http(address, 0, m_Computer->serverCert, nam);
QString serverInfo;
try {
@ -53,9 +52,9 @@ private:
return true;
}
bool updateAppList(bool& changed)
bool updateAppList(QNetworkAccessManager* nam, bool& changed)
{
NvHTTP http(m_Computer);
NvHTTP http(m_Computer, nam);
QVector<NvApp> appList;
@ -75,6 +74,21 @@ private:
void run() override
{
// Reduce the power and performance impact of our
// computer status polling while it's running.
setPriority(QThread::LowPriority);
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
setServiceLevel(QThread::QualityOfService::Eco);
#endif
// Share the QNetworkAccessManager to conserve resources when polling.
// Each instance creates a worker thread, so sharing them ensures that
// we are not spamming a new thread for every single polling attempt.
//
// Since QThread inherit the priority of the current thread, this also
// ensures that the NAM's worker thread will inherit our lower priority.
QNetworkAccessManager nam;
// Always fetch the applist the first time
int pollsSinceLastAppListFetch = POLLS_PER_APPLIST_FETCH;
while (!isInterruptionRequested()) {
@ -87,7 +101,7 @@ private:
return;
}
if (tryPollComputer(address, stateChanged)) {
if (tryPollComputer(&nam, address, stateChanged)) {
if (!wasOnline) {
qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress.toString();
}
@ -118,7 +132,7 @@ private:
stateChanged = false;
}
if (updateAppList(stateChanged)) {
if (updateAppList(&nam, stateChanged)) {
pollsSinceLastAppListFetch = 0;
}
}
@ -715,6 +729,10 @@ void ComputerManager::addNewHostManually(QString address)
// If there wasn't a port specified, use the default
addNewHost(NvAddress(url.host(), url.port(DEFAULT_HTTP_PORT)), false);
}
else if (QHostAddress(address).protocol() == QAbstractSocket::IPv6Protocol) {
// The user specified an IPv6 literal without URL escaping, so use the default port
addNewHost(NvAddress(address, DEFAULT_HTTP_PORT), false);
}
else {
emit computerAddCompleted(false, false);
}
@ -966,14 +984,9 @@ void ComputerManager::addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv
QThreadPool::globalInstance()->start(addTask);
}
// TODO: Use QRandomGenerator when we drop Qt 5.9 support
QString ComputerManager::generatePinString()
{
std::uniform_int_distribution<int> dist(0, 9999);
std::random_device rd;
std::mt19937 engine(rd());
return QString::asprintf("%04u", dist(engine));
return QString::asprintf("%04u", QRandomGenerator::system()->bounded(10000));
}
#include "computermanager.moc"

View File

@ -18,7 +18,8 @@
#define RESUME_TIMEOUT_MS 30000
#define QUIT_TIMEOUT_MS 30000
NvHTTP::NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert) :
NvHTTP::NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert, QNetworkAccessManager* nam) :
m_Nam(nam ? nam : new QNetworkAccessManager(this)),
m_ServerCert(serverCert)
{
m_BaseUrlHttp.setScheme("http");
@ -29,13 +30,11 @@ NvHTTP::NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert
// Never use a proxy server
QNetworkProxy noProxy(QNetworkProxy::NoProxy);
m_Nam.setProxy(noProxy);
connect(&m_Nam, &QNetworkAccessManager::sslErrors, this, &NvHTTP::handleSslErrors);
m_Nam->setProxy(noProxy);
}
NvHTTP::NvHTTP(NvComputer* computer) :
NvHTTP(computer->activeAddress, computer->activeHttpsPort, computer->serverCert)
NvHTTP::NvHTTP(NvComputer* computer, QNetworkAccessManager* nam) :
NvHTTP(computer->activeAddress, computer->activeHttpsPort, computer->serverCert, nam)
{
}
@ -304,8 +303,7 @@ NvHTTP::getAppList()
// We must have a valid app before advancing to the next one
if (!apps.isEmpty() && !apps.last().isInitialized()) {
qWarning() << "Invalid applist XML";
Q_ASSERT(false);
return QVector<NvApp>();
throw std::runtime_error("Invalid applist XML");
}
apps.append(NvApp());
}
@ -493,15 +491,15 @@ NvHTTP::openConnection(QUrl baseUrl,
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && QT_VERSION < QT_VERSION_CHECK(5, 15, 1) && !defined(QT_NO_BEARERMANAGEMENT)
// HACK: Set network accessibility to work around QTBUG-80947 (introduced in Qt 5.14.0 and fixed in Qt 5.15.1)
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
m_Nam.setNetworkAccessible(QNetworkAccessManager::Accessible);
QT_WARNING_POP
#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
// Use fine-grained idle timeouts to avoid calling QNetworkAccessManager::clearAccessCache(),
// which tears down the NAM's global thread each time. We must not keep persistent connections
// or GFE will puke.
request.setAttribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute, 0);
#endif
QNetworkReply* reply = m_Nam.get(request);
auto sslErrorsConnection = connect(m_Nam, &QNetworkAccessManager::sslErrors, this, &NvHTTP::handleSslErrors);
QNetworkReply* reply = m_Nam->get(request);
// Run the request with a timeout if requested
QEventLoop loop;
@ -524,9 +522,11 @@ NvHTTP::openConnection(QUrl baseUrl,
reply->abort();
}
// We must clear out cached authentication and connections or
// GFE will puke next time
m_Nam.clearAccessCache();
#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0)
// If we couldn't use fine-grained connection idle timeouts, kill them all now
m_Nam->clearAccessCache();
#endif
disconnect(sslErrorsConnection);
// Handle error
if (reply->error() != QNetworkReply::NoError)

View File

@ -109,9 +109,9 @@ public:
NVLL_VERBOSE
};
explicit NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert);
explicit NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert, QNetworkAccessManager* nam = nullptr);
explicit NvHTTP(NvComputer* computer);
explicit NvHTTP(NvComputer* computer, QNetworkAccessManager* nam = nullptr);
static
int
@ -196,6 +196,6 @@ private:
NvLogLevel logLevel);
NvAddress m_Address;
QNetworkAccessManager m_Nam;
QNetworkAccessManager* m_Nam;
QSslCertificate m_ServerCert;
};

View File

@ -59,13 +59,6 @@ public:
void showMessage(QString message, MessageType type) const
{
#if defined(Q_OS_WIN32)
UINT flags = MB_OK | MB_TOPMOST | MB_SETFOREGROUND;
flags |= (type == Info ? MB_ICONINFORMATION : MB_ICONERROR);
QString title = "Moonlight";
MessageBoxW(nullptr, reinterpret_cast<const wchar_t *>(message.utf16()),
reinterpret_cast<const wchar_t *>(title.utf16()), flags);
#endif
message = message.endsWith('\n') ? message : message + '\n';
fputs(qPrintable(message), type == Info ? stdout : stderr);
}
@ -113,7 +106,7 @@ public:
QPair<int,int> getResolutionOptionValue(QString name) const
{
QRegularExpression re("^(\\d+)x(\\d+)$", QRegularExpression::CaseInsensitiveOption);
static QRegularExpression re("^(\\d+)x(\\d+)$", QRegularExpression::CaseInsensitiveOption);
auto match = re.match(value(name));
if (!match.hasMatch()) {
showError(QString("Invalid %1 format: %2").arg(name, value(name)));
@ -386,7 +379,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
parser.handleUnknownOptions();
// Resolve display's width and height
QRegularExpression resolutionRexExp("^(720|1080|1440|4K|resolution)$");
static QRegularExpression resolutionRexExp("^(720|1080|1440|4K|resolution)$");
QStringList resoOptions = parser.optionNames().filter(resolutionRexExp);
bool displaySet = !resoOptions.isEmpty();
if (displaySet) {

View File

@ -5,10 +5,8 @@
#include "backend/computerseeker.h"
#include <QCoreApplication>
#include <QTimer>
#define COMPUTER_SEEK_TIMEOUT 30000
#define APP_SEEK_TIMEOUT 10000
namespace CliListApps
{
@ -16,8 +14,7 @@ namespace CliListApps
enum State {
StateInit,
StateSeekComputer,
StateListApp,
StateSeekApp,
StateListApps,
StateFailure,
};
@ -26,7 +23,6 @@ class Event
public:
enum Type {
ComputerFound,
ComputerUpdated,
ComputerSeekTimedout,
Executed,
};
@ -66,9 +62,6 @@ public:
q, &Launcher::onComputerSeekTimeout);
m_ComputerSeeker->start(COMPUTER_SEEK_TIMEOUT);
q->connect(m_ComputerManager, &ComputerManager::computerStateChanged,
q, &Launcher::onComputerUpdated);
m_BoxArtManager = new BoxArtManager(q);
if (m_Arguments.isVerbose()) {
@ -88,12 +81,28 @@ public:
case Event::ComputerFound:
if (m_State == StateSeekComputer) {
if (event.computer->pairState == NvComputer::PS_PAIRED) {
m_State = StateSeekApp;
m_State = StateListApps;
m_Computer = event.computer;
m_TimeoutTimer->start(APP_SEEK_TIMEOUT);
if (m_Arguments.isVerbose()) {
fprintf(stdout, "Loading app list...\n");
}
// To avoid race conditions where ComputerSeeker stops async polling, but
// ComputerManager is yet to update the app list, we will explicitly fetch the latest app list.
// Otherwise, it becomes complicated as we would have to guess whether ComputerManager
// would emit 1 signal (the list did not change) or 2 signals (indicating that the list has changed)
try {
NvHTTP http{m_Computer};
const auto appList = http.getAppList();
m_Arguments.isPrintCSV() ? printAppsCSV(appList) : printApps(appList);
QCoreApplication::exit(0);
} catch (std::exception& exception) {
fprintf(stderr, "%s\n", exception.what());
QCoreApplication::exit(1);
}
} else {
m_State = StateFailure;
fprintf(stderr, "%s\n", qPrintable(QObject::tr("Computer %1 has not been paired. "
@ -104,14 +113,6 @@ public:
}
}
break;
// Occurs when a computer is updated
case Event::ComputerUpdated:
if (m_State == StateSeekApp) {
m_Arguments.isPrintCSV() ? printAppsCSV(m_Computer->appList) : printApps(m_Computer->appList);
QCoreApplication::exit(0);
}
break;
}
}
@ -146,7 +147,6 @@ public:
BoxArtManager *m_BoxArtManager;
NvComputer *m_Computer;
State m_State;
QTimer *m_TimeoutTimer;
ListCommandLineParser m_Arguments;
};
@ -157,11 +157,7 @@ Launcher::Launcher(QString computer, ListCommandLineParser arguments, QObject *p
Q_D(Launcher);
d->m_ComputerName = computer;
d->m_State = StateInit;
d->m_TimeoutTimer = new QTimer(this);
d->m_TimeoutTimer->setSingleShot(true);
d->m_Arguments = arguments;
connect(d->m_TimeoutTimer, &QTimer::timeout,
this, &Launcher::onComputerSeekTimeout);
}
Launcher::~Launcher()
@ -197,12 +193,4 @@ void Launcher::onComputerSeekTimeout()
d->handleEvent(event);
}
void Launcher::onComputerUpdated(NvComputer *computer)
{
Q_D(Launcher);
Event event(Event::ComputerUpdated);
event.computer = computer;
d->handleEvent(event);
}
}

View File

@ -27,7 +27,6 @@ public:
private slots:
void onComputerFound(NvComputer *computer);
void onComputerUpdated(NvComputer *computer);
void onComputerSeekTimeout();
private:

View File

@ -5,5 +5,5 @@ Exec=moonlight
Icon=moonlight
Terminal=false
Type=Application
Categories=Qt;Game;
Categories=Qt;Game;Network;RemoteAccess;
Keywords=nvidia;gamestream;stream;sunshine;remote play;

View File

@ -14,4 +14,4 @@ renice -10 -p $(pidof PE_Single_CPU)
# Renice Moonlight itself to avoid preemption by background tasks
# Write output to a logfile in /tmp
exec nice -n -10 ./bin/moonlight > /tmp/moonlight.log
exec nice -n -10 ./bin/moonlight > /tmp/moonlight.log 2>&1

View File

@ -38,7 +38,7 @@ CenteredGridView {
activated = true
// Highlight the first item if a gamepad is connected
if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
if (currentIndex === -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
currentIndex = 0
}
@ -94,9 +94,9 @@ CenteredGridView {
// 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
((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
}
@ -129,14 +129,9 @@ CenteredGridView {
implicitWidth: 85
implicitHeight: 85
Image {
source: "qrc:/res/play_arrow_FILL1_wght700_GRAD200_opsz48.svg"
anchors.centerIn: parent
sourceSize {
width: 75
height: 75
}
}
icon.source: "qrc:/res/play_arrow_FILL1_wght700_GRAD200_opsz48.svg"
icon.width: 75
icon.height: 75
onClicked: {
launchOrResumeSelectedApp(true)
@ -157,14 +152,9 @@ CenteredGridView {
implicitWidth: 85
implicitHeight: 85
Image {
source: "qrc:/res/stop_FILL1_wght700_GRAD200_opsz48.svg"
anchors.centerIn: parent
sourceSize {
width: 75
height: 75
}
}
icon.source: "qrc:/res/stop_FILL1_wght700_GRAD200_opsz48.svg"
icon.width: 75
icon.height: 75
onClicked: {
doQuitGame()
@ -299,18 +289,15 @@ CenteredGridView {
sourceComponent: NavigableMenu {
id: appContextMenu
NavigableMenuItem {
parentMenu: appContextMenu
text: model.running ? qsTr("Resume Game") : qsTr("Launch Game")
onTriggered: launchOrResumeSelectedApp(true)
}
NavigableMenuItem {
parentMenu: appContextMenu
text: qsTr("Quit Game")
onTriggered: doQuitGame()
visible: model.running
}
NavigableMenuItem {
parentMenu: appContextMenu
checkable: true
checked: model.directLaunch
text: qsTr("Direct Launch")
@ -323,7 +310,6 @@ CenteredGridView {
ToolTip.visible: hovered
}
NavigableMenuItem {
parentMenu: appContextMenu
checkable: true
checked: model.hidden
text: qsTr("Hide Game")
@ -339,6 +325,19 @@ CenteredGridView {
}
}
Row {
anchors.centerIn: parent
spacing: 5
visible: appGrid.count === 0
Label {
text: qsTr("This computer doesn't seem to have any applications or some applications are hidden")
font.pointSize: 20
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
}
NavigableMessageDialog {
id: quitAppDialog
property string appName : ""

View File

@ -2,10 +2,6 @@ import QtQuick 2.9
import QtQuick.Controls 2.2
GridView {
// Detect Qt 5.11 or earlier using presence of synchronousDrag.
// Prior to 5.12, the leftMargin and rightMargin values did not work.
property bool hasBrokenMargins: this.synchronousDrag === undefined
property int minMargin: 10
property real availableWidth: (parent.width - 2 * minMargin)
property int itemsPerRow: availableWidth / cellWidth
@ -15,11 +11,6 @@ GridView {
function updateMargins() {
leftMargin = horizontalMargin
rightMargin = horizontalMargin
if (hasBrokenMargins) {
anchors.leftMargin = leftMargin
anchors.rightMargin = rightMargin
}
}
onHorizontalMarginChanged: {
@ -27,15 +18,6 @@ GridView {
}
Component.onCompleted: {
if (hasBrokenMargins) {
// This will cause an anchor conflict with the parent StackView
// which breaks animation, but otherwise the grid will not be
// centered in the window.
anchors.fill = parent
anchors.topMargin = topMargin
anchors.bottomMargin = bottomMargin
}
updateMargins()
}

View File

@ -2,7 +2,6 @@ import QtQuick 2.0
import QtQuick.Controls 2.2
import ComputerManager 1.0
import SdlGamepadKeyNavigation 1.0
Item {
function onSearchingComputer() {
@ -39,10 +38,6 @@ Item {
if (!launcher.isExecuted()) {
toolBar.visible = false
// Normally this is enabled by PcView, but we will won't
// load PcView when streaming from the command-line.
SdlGamepadKeyNavigation.enable()
launcher.searchingComputer.connect(onSearchingComputer)
launcher.pairing.connect(onPairing)
launcher.failed.connect(onFailed)
@ -58,6 +53,7 @@ Item {
BusyIndicator {
id: stageSpinner
running: visible
}
Label {

View File

@ -34,6 +34,7 @@ Item {
BusyIndicator {
id: stageSpinner
running: visible
}
Label {

View File

@ -2,7 +2,6 @@ import QtQuick 2.0
import QtQuick.Controls 2.2
import ComputerManager 1.0
import SdlGamepadKeyNavigation 1.0
Item {
function onSearchingComputer() {
@ -38,10 +37,6 @@ Item {
if (!launcher.isExecuted()) {
toolBar.visible = false
// Normally this is enabled by PcView, but we will won't
// load PcView when streaming from the command-line.
SdlGamepadKeyNavigation.enable()
launcher.searchingComputer.connect(onSearchingComputer)
launcher.searchingApp.connect(onSearchingApp)
launcher.sessionCreated.connect(onSessionCreated)
@ -57,6 +52,7 @@ Item {
BusyIndicator {
id: stageSpinner
running: visible
}
Label {

View File

@ -2,10 +2,6 @@ import QtQuick 2.0
import QtQuick.Controls 2.2
MenuItem {
// Qt 5.10 has a menu property, but we need to support 5.9
// so we must make our own.
property Menu parentMenu
// Ensure focus can't be given to an invisible item
enabled: visible
height: visible ? implicitHeight : 0
@ -15,7 +11,7 @@ MenuItem {
// We must close the context menu first or
// it can steal focus from any dialogs that
// onTriggered may spawn.
parentMenu.close()
menu.close()
}
Keys.onReturnPressed: {
@ -27,6 +23,6 @@ MenuItem {
}
Keys.onEscapePressed: {
parentMenu.close()
menu.close()
}
}

View File

@ -24,6 +24,7 @@ NavigableDialog {
BusyIndicator {
id: dialogSpinner
visible: false
running: visible
}
Image {

View File

@ -7,17 +7,9 @@ ToolButton {
activeFocusOnTab: true
// FIXME: We're using an Image here rather than icon.source because
// icons don't work on Qt 5.9 LTS.
Image {
id: image
source: iconSource
anchors.centerIn: parent.background
sourceSize {
width: parent.background.width * 1.10
height: parent.background.height * 1.10
}
}
icon.source: iconSource
icon.width: background.width
icon.height: background.height
// This determines the size of the Material highlight. We increase it
// from the default because we use larger than normal icons for TV readability.

View File

@ -34,13 +34,8 @@ CenteredGridView {
// Setup signals on CM
ComputerManager.computerAddCompleted.connect(addComplete)
// This is a bit of a hack to do this here as opposed to main.qml, but
// we need it enabled before calling getConnectedGamepads() and PcView
// is never destroyed, so it should be okay.
SdlGamepadKeyNavigation.enable()
// Highlight the first item if a gamepad is connected
if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
if (currentIndex === -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
currentIndex = 0
}
}
@ -95,6 +90,7 @@ CenteredGridView {
BusyIndicator {
id: searchSpinner
visible: StreamingPreferences.enableMdns
running: visible
}
Label {
@ -148,6 +144,7 @@ CenteredGridView {
width: 75
height: 75
visible: model.statusUnknown
running: visible
}
Label {
@ -174,7 +171,6 @@ CenteredGridView {
enabled: false
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("View All Apps")
onTriggered: {
var component = Qt.createComponent("AppView.qml")
@ -184,13 +180,11 @@ CenteredGridView {
visible: model.online && model.paired
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Wake PC")
onTriggered: computerModel.wakeComputer(index)
visible: !model.online && model.wakeable
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Test Network")
onTriggered: {
computerModel.testConnectionForComputer(index)
@ -199,7 +193,6 @@ CenteredGridView {
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Rename PC")
onTriggered: {
renamePcDialog.pcIndex = index
@ -208,7 +201,6 @@ CenteredGridView {
}
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("Delete PC")
onTriggered: {
deletePcDialog.pcIndex = index
@ -217,7 +209,6 @@ CenteredGridView {
}
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("View Details")
onTriggered: {
showPcDetailsDialog.pcDetails = model.details

View File

@ -60,6 +60,7 @@ Item {
BusyIndicator {
id: stageSpinner
running: visible
}
Label {

View File

@ -281,11 +281,13 @@ Flickable {
StreamingPreferences.width = selectedWidth
StreamingPreferences.height = selectedHeight
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
StreamingPreferences.height,
StreamingPreferences.fps,
StreamingPreferences.enableYUV444);
slider.value = StreamingPreferences.bitrateKbps
if (StreamingPreferences.autoAdjustBitrate) {
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
StreamingPreferences.height,
StreamingPreferences.fps,
StreamingPreferences.enableYUV444);
slider.value = StreamingPreferences.bitrateKbps
}
}
lastIndexValue = currentIndex
@ -447,11 +449,13 @@ Flickable {
if (StreamingPreferences.fps !== selectedFps) {
StreamingPreferences.fps = selectedFps
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
StreamingPreferences.height,
StreamingPreferences.fps,
StreamingPreferences.enableYUV444);
slider.value = StreamingPreferences.bitrateKbps
if (StreamingPreferences.autoAdjustBitrate) {
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
StreamingPreferences.height,
StreamingPreferences.fps,
StreamingPreferences.enableYUV444);
slider.value = StreamingPreferences.bitrateKbps
}
}
lastIndexValue = currentIndex
@ -678,26 +682,47 @@ Flickable {
wrapMode: Text.Wrap
}
Slider {
id: slider
Row {
width: parent.width
spacing: 5
value: StreamingPreferences.bitrateKbps
Slider {
id: slider
stepSize: 500
from : 500
to: StreamingPreferences.unlockBitrate ? 500000 : 150000
value: StreamingPreferences.bitrateKbps
snapMode: "SnapOnRelease"
width: Math.min(bitrateDesc.implicitWidth, parent.width)
stepSize: 500
from : 500
to: StreamingPreferences.unlockBitrate ? 500000 : 150000
onValueChanged: {
bitrateTitle.text = qsTr("Video bitrate: %1 Mbps").arg(value / 1000.0)
StreamingPreferences.bitrateKbps = value
snapMode: "SnapOnRelease"
width: Math.min(bitrateDesc.implicitWidth, parent.width - (resetBitrateButton.visible ? resetBitrateButton.width + parent.spacing : 0))
onValueChanged: {
bitrateTitle.text = qsTr("Video bitrate: %1 Mbps").arg(value / 1000.0)
StreamingPreferences.bitrateKbps = value
}
onMoved: {
StreamingPreferences.autoAdjustBitrate = false
}
Component.onCompleted: {
// Refresh the text after translations change
languageChanged.connect(valueChanged)
}
}
Component.onCompleted: {
// Refresh the text after translations change
languageChanged.connect(valueChanged)
Button {
id: resetBitrateButton
text: qsTr("Use Default (%1 Mbps)").arg(StreamingPreferences.getDefaultBitrate(StreamingPreferences.width, StreamingPreferences.height, StreamingPreferences.fps, StreamingPreferences.enableYUV444) / 1000.0)
visible: StreamingPreferences.bitrateKbps !== StreamingPreferences.getDefaultBitrate(StreamingPreferences.width, StreamingPreferences.height, StreamingPreferences.fps, StreamingPreferences.enableYUV444)
onClicked: {
var defaultBitrate = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width, StreamingPreferences.height, StreamingPreferences.fps, StreamingPreferences.enableYUV444)
StreamingPreferences.bitrateKbps = defaultBitrate
StreamingPreferences.autoAdjustBitrate = true
slider.value = defaultBitrate
}
}
}
@ -1112,6 +1137,18 @@ Flickable {
text: "Eesti" // Estonian
val: StreamingPreferences.LANG_ET
} */
ListElement {
text: "Български" // Bulgarian
val: StreamingPreferences.LANG_BG
}
/* ListElement {
text: "Esperanto"
val: StreamingPreferences.LANG_EO
} */
ListElement {
text: "தமிழ்" // Tamil
val: StreamingPreferences.LANG_TA
}
}
// ::onActivated must be used, as it only listens for when the index is changed by a human
onActivated : {
@ -1199,6 +1236,17 @@ Flickable {
}
}
CheckBox {
id: configurationWarningsCheck
width: parent.width
text: qsTr("Show configuration warnings")
font.pointSize: 12
checked: StreamingPreferences.configurationWarnings
onCheckedChanged: {
StreamingPreferences.configurationWarnings = checked
}
}
CheckBox {
visible: SystemProperties.hasDiscordIntegration
id: discordPresenceCheck
@ -1616,11 +1664,13 @@ Flickable {
// This is called on init, so only reset to default bitrate when checked state changes.
if (StreamingPreferences.enableYUV444 != checked) {
StreamingPreferences.enableYUV444 = checked
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
StreamingPreferences.height,
StreamingPreferences.fps,
StreamingPreferences.enableYUV444);
slider.value = StreamingPreferences.bitrateKbps
if (StreamingPreferences.autoAdjustBitrate) {
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
StreamingPreferences.height,
StreamingPreferences.fps,
StreamingPreferences.enableYUV444);
slider.value = StreamingPreferences.bitrateKbps
}
}
}

View File

@ -76,7 +76,7 @@ Item {
streamSegueErrorDialog.text += "\n\n" + qsTr("This PC's Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network.")
}
// Enable GUI gamepad usage now
// Re-enable GUI gamepad usage now
SdlGamepadKeyNavigation.enable()
if (quitAfter) {
@ -119,7 +119,7 @@ Item {
// Show the toolbar again when popped off the stack
toolBar.visible = true
// Enable GUI gamepad usage now
// Re-enable GUI gamepad usage now
SdlGamepadKeyNavigation.enable()
}
@ -151,7 +151,7 @@ Item {
// middle of the animation on Windows, which looks very
// obviously broken.
interval: 100
onTriggered: stageSpinner.running = true
onTriggered: stageSpinner.visible = true
}
Loader {
@ -176,7 +176,7 @@ Item {
gc()
// Run the streaming session to completion
session.exec(Window.window)
session.exec(window)
}
sourceComponent: Item {}
@ -188,7 +188,8 @@ Item {
BusyIndicator {
id: stageSpinner
running: false
running: visible
visible: false
}
Label {

View File

@ -22,7 +22,8 @@ ApplicationWindow {
width: 1280
height: 600
Component.onCompleted: {
// This function runs prior to creation of the initial StackView item
function doEarlyInit() {
// Override the background color to Material 2 colors for Qt 6.5+
// in order to improve contrast between GFE's placeholder box art
// and the background of the app grid.
@ -30,6 +31,10 @@ ApplicationWindow {
Material.background = "#303030"
}
SdlGamepadKeyNavigation.enable()
}
Component.onCompleted: {
// Show the window according to the user's preferences
if (SystemProperties.hasDesktopEnvironment) {
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) {
@ -49,7 +54,7 @@ ApplicationWindow {
if (SystemProperties.isWow64) {
wow64Dialog.open()
}
else if (!SystemProperties.hasHardwareAcceleration) {
else if (!SystemProperties.hasHardwareAcceleration && StreamingPreferences.videoDecoderSelection !== StreamingPreferences.VDS_FORCE_SOFTWARE) {
if (SystemProperties.isRunningXWayland) {
xWaylandDialog.open()
}
@ -64,9 +69,19 @@ ApplicationWindow {
}
}
// It would be better to use TextMetrics here, but it always lays out
// the text slightly more compactly than real Text does in ToolTip,
// causing unexpected line breaks to be inserted
Text {
id: tooltipTextLayoutHelper
visible: false
font: ToolTip.toolTip.font
text: ToolTip.toolTip.text
}
// This configures the maximum width of the singleton attached QML ToolTip. If left unconstrained,
// it will never insert a line break and just extend on forever.
ToolTip.toolTip.contentWidth: ToolTip.toolTip.implicitContentWidth < 400 ? ToolTip.toolTip.implicitContentWidth : 400
ToolTip.toolTip.contentWidth: Math.min(tooltipTextLayoutHelper.width, 400)
function goBack() {
if (clearOnBack) {
@ -81,10 +96,16 @@ ApplicationWindow {
StackView {
id: stackView
initialItem: initialView
anchors.fill: parent
focus: true
Component.onCompleted: {
// Perform our early initialization before constructing
// the initial view and pushing it to the StackView
doEarlyInit()
push(initialView)
}
onCurrentItemChanged: {
// Ensure focus travels to the next view when going back
if (currentItem) {
@ -158,6 +179,9 @@ ApplicationWindow {
pollingActive = true
}
}
// Poll for gamepad input only when the window is in focus
SdlGamepadKeyNavigation.notifyWindowFocus(visible && active)
}
onActiveChanged: {
@ -176,22 +200,15 @@ ApplicationWindow {
// if focus does not return within a few minutes.
inactivityTimer.restart()
}
}
// Workaround for lack of instanceof in Qt 5.9.
//
// Based on https://stackoverflow.com/questions/13923794/how-to-do-a-is-a-typeof-or-instanceof-in-qml
function qmltypeof(obj, className) { // QtObject, string -> bool
// className plus "(" is the class instance without modification
// className plus "_QML" is the class instance with user-defined properties
var str = obj.toString();
return str.startsWith(className + "(") || str.startsWith(className + "_QML");
// Poll for gamepad input only when the window is in focus
SdlGamepadKeyNavigation.notifyWindowFocus(visible && active)
}
function navigateTo(url, objectType)
{
var existingItem = stackView.find(function(item, index) {
return qmltypeof(item, objectType)
return item instanceof objectType
})
if (existingItem !== null) {
@ -258,7 +275,7 @@ ApplicationWindow {
Label {
id: versionLabel
visible: qmltypeof(stackView.currentItem, "SettingsView")
visible: stackView.currentItem instanceof SettingsView
text: qsTr("Version %1").arg(SystemProperties.versionString)
font.pointSize: 12
horizontalAlignment: Qt.AlignRight
@ -268,7 +285,7 @@ ApplicationWindow {
NavigableToolButton {
id: discordButton
visible: SystemProperties.hasBrowser &&
qmltypeof(stackView.currentItem, "SettingsView")
stackView.currentItem instanceof SettingsView
iconSource: "qrc:/res/discord.svg"
@ -287,7 +304,7 @@ ApplicationWindow {
NavigableToolButton {
id: addPcButton
visible: qmltypeof(stackView.currentItem, "PcView")
visible: stackView.currentItem instanceof PcView
iconSource: "qrc:/res/ic_add_to_queue_white_48px.svg"
@ -385,7 +402,7 @@ ApplicationWindow {
iconSource: "qrc:/res/ic_videogame_asset_white_48px.svg"
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "GamepadMapper")
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", GamepadMapper)
Keys.onDownPressed: {
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
@ -397,7 +414,7 @@ ApplicationWindow {
iconSource: "qrc:/res/settings.svg"
onClicked: navigateTo("qrc:/gui/SettingsView.qml", "SettingsView")
onClicked: navigateTo("qrc:/gui/SettingsView.qml", SettingsView)
Keys.onDownPressed: {
stackView.currentItem.forceActiveFocus(Qt.TabFocus)

View File

@ -13,6 +13,7 @@ SdlGamepadKeyNavigation::SdlGamepadKeyNavigation(StreamingPreferences* prefs)
m_Enabled(false),
m_UiNavMode(false),
m_FirstPoll(false),
m_HasFocus(false),
m_LastAxisNavigationEventTime(0)
{
m_PollingTimer = new QTimer(this);
@ -54,7 +55,8 @@ void SdlGamepadKeyNavigation::enable()
SDL_FlushEvent(SDL_CONTROLLERDEVICEADDED);
// Open all currently attached game controllers
for (int i = 0; i < SDL_NumJoysticks(); i++) {
int numJoysticks = SDL_NumJoysticks();
for (int i = 0; i < numJoysticks; i++) {
if (SDL_IsGameController(i)) {
SDL_GameController* gc = SDL_GameControllerOpen(i);
if (gc != nullptr) {
@ -63,13 +65,10 @@ void SdlGamepadKeyNavigation::enable()
}
}
// Flush events on the first poll
m_FirstPoll = true;
// Poll every 50 ms for a new joystick event
m_PollingTimer->start(50);
m_Enabled = true;
// Start the polling timer if the window is focused
updateTimerState();
}
void SdlGamepadKeyNavigation::disable()
@ -78,7 +77,9 @@ void SdlGamepadKeyNavigation::disable()
return;
}
m_PollingTimer->stop();
m_Enabled = false;
updateTimerState();
Q_ASSERT(!m_PollingTimer->isActive());
while (!m_Gamepads.isEmpty()) {
SDL_GameControllerClose(m_Gamepads[0]);
@ -86,8 +87,12 @@ void SdlGamepadKeyNavigation::disable()
}
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
}
m_Enabled = false;
void SdlGamepadKeyNavigation::notifyWindowFocus(bool hasFocus)
{
m_HasFocus = hasFocus;
updateTimerState();
}
void SdlGamepadKeyNavigation::onPollingTimerFired()
@ -261,6 +266,20 @@ void SdlGamepadKeyNavigation::sendKey(QEvent::Type type, Qt::Key key, Qt::Keyboa
}
}
void SdlGamepadKeyNavigation::updateTimerState()
{
if (m_PollingTimer->isActive() && (!m_HasFocus || !m_Enabled)) {
m_PollingTimer->stop();
}
else if (!m_PollingTimer->isActive() && m_HasFocus && m_Enabled) {
// Flush events on the first poll
m_FirstPoll = true;
// Poll every 50 ms for a new joystick event
m_PollingTimer->start(50);
}
}
void SdlGamepadKeyNavigation::setUiNavMode(bool uiNavMode)
{
m_UiNavMode = uiNavMode;
@ -271,7 +290,8 @@ int SdlGamepadKeyNavigation::getConnectedGamepads()
Q_ASSERT(m_Enabled);
int count = 0;
for (int i = 0; i < SDL_NumJoysticks(); i++) {
int numJoysticks = SDL_NumJoysticks();
for (int i = 0; i < numJoysticks; i++) {
if (SDL_IsGameController(i)) {
count++;
}

View File

@ -3,7 +3,7 @@
#include <QTimer>
#include <QEvent>
#include <SDL.h>
#include "SDL_compat.h"
#include "settings/streamingpreferences.h"
@ -20,6 +20,8 @@ public:
Q_INVOKABLE void disable();
Q_INVOKABLE void notifyWindowFocus(bool hasFocus);
Q_INVOKABLE void setUiNavMode(bool settingsMode);
Q_INVOKABLE int getConnectedGamepads();
@ -27,6 +29,8 @@ public:
private:
void sendKey(QEvent::Type type, Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
void updateTimerState();
private slots:
void onPollingTimerFired();
@ -37,5 +41,6 @@ private:
bool m_Enabled;
bool m_UiNavMode;
bool m_FirstPoll;
bool m_HasFocus;
Uint32 m_LastAxisNavigationEventTime;
};

BIN
app/languages/qml_bg.qm Normal file

Binary file not shown.

1336
app/languages/qml_bg.ts Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
app/languages/qml_eo.qm Normal file

Binary file not shown.

1336
app/languages/qml_eo.ts Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

BIN
app/languages/qml_ta.qm Normal file

Binary file not shown.

1336
app/languages/qml_ta.ts Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@
// doing the same thing. This needs to be before any headers
// that might include SDL.h themselves.
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include "SDL_compat.h"
#ifdef HAVE_FFMPEG
#include "streaming/video/ffmpeg.h"
@ -60,28 +60,55 @@
// Log to console for debug Mac builds
#endif
// StreamUtils::setAsyncLogging() exposes control of this to the Session
// class to enable async logging once the stream has started.
//
// FIXME: Clean this up
QAtomicInt g_AsyncLoggingEnabled;
static QElapsedTimer s_LoggerTime;
static QTextStream s_LoggerStream(stderr);
static QMutex s_LoggerLock;
static QThreadPool s_LoggerThread;
static QMutex s_SyncLoggerMutex;
static bool s_SuppressVerboseOutput;
static QRegularExpression k_RikeyRegex("&rikey=\\w+");
static QRegularExpression k_RikeyIdRegex("&rikeyid=[\\d-]+");
#ifdef LOG_TO_FILE
// Max log file size of 10 MB
#define MAX_LOG_SIZE_BYTES (10 * 1024 * 1024)
static int s_LogBytesWritten = 0;
static bool s_LogLimitReached = false;
static const uint64_t k_MaxLogSizeBytes = 10 * 1024 * 1024;
static QAtomicInteger<uint64_t> s_LogBytesWritten = 0;
static QFile* s_LoggerFile;
#endif
class LoggerTask : public QRunnable
{
public:
LoggerTask(const QString& msg) : m_Msg(msg)
{
setAutoDelete(true);
}
void run() override
{
// QTextStream is not thread-safe, so we must lock. This will generally
// only contend in synchronous logging mode or during a transition
// between synchronous and asynchronous. Asynchronous won't contend in
// the common case because we only have a single logging thread.
QMutexLocker locker(&s_SyncLoggerMutex);
s_LoggerStream << m_Msg;
s_LoggerStream.flush();
}
private:
QString m_Msg;
};
void logToLoggerStream(QString& message)
{
QMutexLocker lock(&s_LoggerLock);
#if defined(QT_DEBUG) && defined(Q_OS_WIN32)
// Output log messages to a debugger if attached
if (IsDebuggerPresent()) {
static QString lineBuffer;
thread_local QString lineBuffer;
lineBuffer += message;
if (message.endsWith('\n')) {
OutputDebugStringW(lineBuffer.toStdWString().c_str());
@ -95,26 +122,24 @@ void logToLoggerStream(QString& message)
message.replace(k_RikeyIdRegex, "&rikeyid=REDACTED");
#ifdef LOG_TO_FILE
if (s_LogLimitReached) {
auto oldLogSize = s_LogBytesWritten.fetchAndAddRelaxed(message.size());
if (oldLogSize >= k_MaxLogSizeBytes) {
return;
}
else if (s_LogBytesWritten >= MAX_LOG_SIZE_BYTES) {
s_LoggerStream << "Log size limit reached!";
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
s_LoggerStream << Qt::endl;
#else
s_LoggerStream << endl;
#endif
s_LogLimitReached = true;
return;
}
else {
s_LogBytesWritten += message.size();
else if (oldLogSize >= k_MaxLogSizeBytes - message.size()) {
// Write one final message
message = "Log size limit reached!";
}
#endif
s_LoggerStream << message;
s_LoggerStream.flush();
if (g_AsyncLoggingEnabled) {
// Queue the log message to be written asynchronously
s_LoggerThread.start(new LoggerTask(message));
}
else {
// Log the message immediately
LoggerTask(message).run();
}
}
void sdlLogToDiskHandler(void*, int category, SDL_LogPriority priority, const char* message)
@ -284,6 +309,11 @@ LONG WINAPI UnhandledExceptionHandler(struct _EXCEPTION_POINTERS *ExceptionInfo)
qCritical() << "Unhandled exception! Failed to open dump file:" << qDmpFileName << "with error" << GetLastError();
}
// Sleep for a moment to allow the logging thread to finish up before crashing
if (g_AsyncLoggingEnabled) {
Sleep(500);
}
// Let the program crash and WER collect a dump
return EXCEPTION_CONTINUE_SEARCH;
}
@ -344,10 +374,20 @@ int main(int argc, char *argv[])
}
#endif
// Serialize log messages on a single thread
s_LoggerThread.setMaxThreadCount(1);
s_LoggerTime.start();
qInstallMessageHandler(qtLogToDiskHandler);
SDL_LogSetOutputFunction(sdlLogToDiskHandler, nullptr);
// Register our logger with all libraries
#if SDL_VERSION_ATLEAST(3, 0, 0)
SDL_SetLogOutputFunction(sdlLogToDiskHandler, nullptr);
#else
SDL_LogOutputFunction oldSdlLogFn;
void* oldSdlLogUserdata;
SDL_LogGetOutputFunction(&oldSdlLogFn, &oldSdlLogUserdata);
SDL_LogSetOutputFunction(sdlLogToDiskHandler, nullptr);
#endif
qInstallMessageHandler(qtLogToDiskHandler);
#ifdef HAVE_FFMPEG
av_log_set_callback(ffmpegLogToDiskHandler);
#endif
@ -435,7 +475,7 @@ int main(int argc, char *argv[])
#endif
}
#if !defined(Q_PROCESSOR_X86) && defined(SDL_HINT_VIDEO_X11_FORCE_EGL)
#ifndef Q_PROCESSOR_X86
// Some ARM and RISC-V embedded devices don't have working GLX which can cause
// SDL to fail to find a working OpenGL implementation at all. Let's force EGL
// on non-x86 platforms, since GLX is deprecated anyway.
@ -491,12 +531,12 @@ int main(int argc, char *argv[])
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
// We use MMAL to render on Raspberry Pi, so we do not require DRM master.
SDL_SetHint("SDL_KMSDRM_REQUIRE_DRM_MASTER", "0");
SDL_SetHint(SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER, "0");
// Use Direct3D 9Ex to avoid a deadlock caused by the D3D device being reset when
// the user triggers a UAC prompt. This option controls the software/SDL renderer.
// The DXVA2 renderer uses Direct3D 9Ex itself directly.
SDL_SetHint("SDL_WINDOWS_USE_D3D9EX", "1");
SDL_SetHint(SDL_HINT_WINDOWS_USE_D3D9EX, "1");
if (SDL_InitSubSystem(SDL_INIT_TIMER) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@ -531,28 +571,28 @@ int main(int argc, char *argv[])
// SDL 2.0.12 changes the default behavior to use the button label rather than the button
// position as most other software does. Set this back to 0 to stay consistent with prior
// releases of Moonlight.
SDL_SetHint("SDL_GAMECONTROLLER_USE_BUTTON_LABELS", "0");
SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
// Disable relative mouse scaling to renderer size or logical DPI. We want to send
// the mouse motion exactly how it was given to us.
SDL_SetHint("SDL_MOUSE_RELATIVE_SCALING", "0");
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_SCALING, "0");
// Set our app name for SDL to use with PulseAudio and PipeWire. This matches what we
// provide as our app name to libsoundio too. On SDL 2.0.18+, SDL_APP_NAME is also used
// for screensaver inhibitor reporting.
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "Moonlight");
SDL_SetHint("SDL_APP_NAME", "Moonlight");
SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, "Moonlight");
SDL_SetHint(SDL_HINT_APP_NAME, "Moonlight");
// We handle capturing the mouse ourselves when it leaves the window, so we don't need
// SDL doing it for us behind our backs.
SDL_SetHint("SDL_MOUSE_AUTO_CAPTURE", "0");
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
// SDL will try to lock the mouse cursor on Wayland if it's not visible in order to
// support applications that assume they can warp the cursor (which isn't possible
// on Wayland). We don't want this behavior because it interferes with seamless mouse
// mode when toggling between windowed and fullscreen modes by unexpectedly locking
// the mouse cursor.
SDL_SetHint("SDL_VIDEO_WAYLAND_EMULATE_MOUSE_WARP", "0");
SDL_SetHint(SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP, "0");
#ifdef QT_DEBUG
// Allow thread naming using exceptions on debug builds. SDL doesn't use SEH
@ -571,31 +611,32 @@ int main(int argc, char *argv[])
}
#endif
#ifdef Q_OS_WIN32
// If we don't have stdout or stderr handles (which will normally be the case
// since we're a /SUBSYSTEM:WINDOWS app), attach to our parent console and use
// that for stdout and stderr.
//
// If we do have stdout or stderr handles, that means the user has used standard
// handle redirection. In that case, we don't want to override those handles.
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
// If we didn't have an old stdout/stderr handle, use the new CONOUT$ handle
if (IS_UNSPECIFIED_HANDLE(oldConOut)) {
freopen("CONOUT$", "w", stdout);
setvbuf(stdout, NULL, _IONBF, 0);
}
if (IS_UNSPECIFIED_HANDLE(oldConErr)) {
freopen("CONOUT$", "w", stderr);
setvbuf(stderr, NULL, _IONBF, 0);
}
}
#endif
GlobalCommandLineParser parser;
GlobalCommandLineParser::ParseResult commandLineParserResult = parser.parse(app.arguments());
switch (commandLineParserResult) {
case GlobalCommandLineParser::ListRequested:
// Don't log to the console since it will jumble the command output
s_SuppressVerboseOutput = true;
#ifdef Q_OS_WIN32
// If we don't have stdout or stderr handles (which will normally be the case
// since we're a /SUBSYSTEM:WINDOWS app), attach to our parent console and use
// that for stdout and stderr.
//
// If we do have stdout or stderr handles, that means the user has used standard
// handle redirection. In that case, we don't want to override those handles.
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
// If we didn't have an old stdout/stderr handle, use the new CONOUT$ handle
if (IS_UNSPECIFIED_HANDLE(oldConOut)) {
freopen("CONOUT$", "w", stdout);
setvbuf(stdout, NULL, _IONBF, 0);
}
if (IS_UNSPECIFIED_HANDLE(oldConErr)) {
freopen("CONOUT$", "w", stderr);
setvbuf(stderr, NULL, _IONBF, 0);
}
}
#endif
break;
default:
break;
@ -669,7 +710,7 @@ int main(int argc, char *argv[])
#endif
// This is necessary to show our icon correctly on Wayland
app.setDesktopFileName("com.moonlight_stream.Moonlight.desktop");
app.setDesktopFileName("com.moonlight_stream.Moonlight");
qputenv("SDL_VIDEO_WAYLAND_WMCLASS", "com.moonlight_stream.Moonlight");
qputenv("SDL_VIDEO_X11_WMCLASS", "com.moonlight_stream.Moonlight");
@ -719,6 +760,12 @@ int main(int argc, char *argv[])
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MATERIAL_VARIANT")) {
qputenv("QT_QUICK_CONTROLS_MATERIAL_VARIANT", "Dense");
}
if (!qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MATERIAL_PRIMARY")) {
// Qt 6.9 began to use a different shade of Material.Indigo when we use a dark theme
// (which is all the time). The new color looks washed out, so manually specify the
// old primary color unless the user overrides it themselves.
qputenv("QT_QUICK_CONTROLS_MATERIAL_PRIMARY", "#3F51B5");
}
QQmlApplicationEngine engine;
QString initialView;
@ -784,6 +831,23 @@ int main(int argc, char *argv[])
// sometimes freezing and blocking process exit.
QThreadPool::globalInstance()->waitForDone(30000);
// Restore the default logger for all libraries before shutting down ours
#if SDL_VERSION_ATLEAST(3, 0, 0)
SDL_SetLogOutputFunction(SDL_GetDefaultLogOutputFunction(), nullptr);
#else
SDL_LogSetOutputFunction(oldSdlLogFn, oldSdlLogUserdata);
#endif
qInstallMessageHandler(nullptr);
#ifdef HAVE_FFMPEG
av_log_set_callback(av_log_default_callback);
#endif
// We should not be in async logging mode anymore
Q_ASSERT(g_AsyncLoggingEnabled == 0);
// Wait for pending log messages to be printed
s_LoggerThread.waitForDone();
#ifdef Q_OS_WIN32
// Without an explicit flush, console redirection for the list command
// doesn't work reliably (sometimes the target file contains no text).

View File

@ -9,17 +9,21 @@
// 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
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
// NOTE: This file MUST NOT include fcntl.h due to open() -> open64()
// redirection that happens when _FILE_OFFSET_BITS=64!
// See masterhook_internal.c for details.
#include <SDL.h>
#include "SDL_compat.h"
#include <dlfcn.h>
#include <unistd.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <pthread.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
@ -32,12 +36,44 @@
// hooks, that is definitely not trivial.
#if SDL_VERSION_ATLEAST(2, 0, 15)
// We don't include fcntl.h, so we have to define this ourselves
typedef int (*fn_open_t)(const char *pathname, int flags, ...);
// Pointers to the real implementations of these libdrm functions
static pthread_once_t s_InitDrmFunctions = PTHREAD_ONCE_INIT;
static typeof(drmModeSetCrtc)* fn_drmModeSetCrtc;
static typeof(drmModePageFlip)* fn_drmModePageFlip;
static typeof(drmModeAtomicCommit)* fn_drmModeAtomicCommit;
static void lookupRealDrmFunctions() {
fn_drmModeSetCrtc = dlsym(RTLD_NEXT, "drmModeSetCrtc");
fn_drmModePageFlip = dlsym(RTLD_NEXT, "drmModePageFlip");
fn_drmModeAtomicCommit = dlsym(RTLD_NEXT, "drmModeAtomicCommit");
}
// Pointers to the real implementations of these libc functions
static pthread_once_t s_InitLibCFunctions = PTHREAD_ONCE_INIT;
static fn_open_t *fn_open;
static fn_open_t *fn_open64;
static typeof(close) *fn_close;
static void lookupRealLibCFunctions() {
fn_open = dlsym(RTLD_NEXT, "open");
fn_open64 = dlsym(RTLD_NEXT, "open64");
fn_close = dlsym(RTLD_NEXT, "close");
}
// Qt's DRM master FD grabbed by our hook
int g_QtDrmMasterFd = -1;
struct stat g_DrmMasterStat;
// The DRM master FD created for SDL
int g_SdlDrmMasterFd = -1;
// Last CRTC state for us to restore later
drmModeCrtcPtr g_QtCrtcState;
uint32_t* g_QtCrtcConnectors;
int g_QtCrtcConnectorCount;
bool removeSdlFd(int fd);
int takeMasterFromSdlFd(void);
int drmIsMaster(int fd)
{
@ -51,6 +87,9 @@ int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
uint32_t x, uint32_t y, uint32_t *connectors, int count,
drmModeModeInfoPtr mode)
{
// Lookup the real libdrm function pointers if we haven't yet
pthread_once(&s_InitDrmFunctions, lookupRealDrmFunctions);
// Grab the first DRM Master FD that makes it in here. This will be the Qt
// EGLFS backend's DRM FD, on which we will call drmDropMaster() later.
if (g_QtDrmMasterFd == -1 && drmIsMaster(fd)) {
@ -62,13 +101,53 @@ int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
}
// Call into the real thing
return ((typeof(drmModeSetCrtc)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, crtcId, bufferId, x, y, connectors, count, mode);
int err = fn_drmModeSetCrtc(fd, crtcId, bufferId, x, y, connectors, count, mode);
if (err == 0 && fd == g_QtDrmMasterFd) {
// Free old CRTC state (if any)
if (g_QtCrtcState) {
drmModeFreeCrtc(g_QtCrtcState);
}
if (g_QtCrtcConnectors) {
free(g_QtCrtcConnectors);
}
// Store the CRTC configuration so we can restore it later
g_QtCrtcState = drmModeGetCrtc(fd, crtcId);
g_QtCrtcConnectors = calloc(count, sizeof(*g_QtCrtcConnectors));
memcpy(g_QtCrtcConnectors, connectors, count * sizeof(*connectors));
g_QtCrtcConnectorCount = count;
}
return err;
}
// This hook will temporarily retake DRM master to allow Qt to render while SDL has a DRM FD open
int drmModePageFlip(int fd, uint32_t crtc_id, uint32_t fb_id, uint32_t flags, void *user_data)
{
// Lookup the real libdrm function pointers if we haven't yet
pthread_once(&s_InitDrmFunctions, lookupRealDrmFunctions);
// Call into the real thing
int err = fn_drmModePageFlip(fd, crtc_id, fb_id, flags, user_data);
if (err == -EACCES && fd == g_QtDrmMasterFd) {
// If SDL took master from us, try to grab it back temporarily
int oldMasterFd = takeMasterFromSdlFd();
drmSetMaster(fd);
err = fn_drmModePageFlip(fd, crtc_id, fb_id, flags, user_data);
drmDropMaster(fd);
if (oldMasterFd != -1) {
drmSetMaster(oldMasterFd);
}
}
return err;
}
// This hook will handle atomic DRM rendering
int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req,
uint32_t flags, void *user_data)
{
// Lookup the real libdrm function pointers if we haven't yet
pthread_once(&s_InitDrmFunctions, lookupRealDrmFunctions);
// Grab the first DRM Master FD that makes it in here. This will be the Qt
// EGLFS backend's DRM FD, on which we will call drmDropMaster() later.
if (g_QtDrmMasterFd == -1 && drmIsMaster(fd)) {
@ -80,29 +159,46 @@ int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req,
}
// Call into the real thing
return ((typeof(drmModeAtomicCommit)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, req, flags, user_data);
int err = fn_drmModeAtomicCommit(fd, req, flags, user_data);
if (err == -EACCES && fd == g_QtDrmMasterFd) {
// If SDL took master from us, try to grab it back temporarily
int oldMasterFd = takeMasterFromSdlFd();
drmSetMaster(fd);
err = fn_drmModeAtomicCommit(fd, req, flags, user_data);
drmDropMaster(fd);
if (oldMasterFd != -1) {
drmSetMaster(oldMasterFd);
}
}
return err;
}
// This hook will handle SDL's open() on the DRM device. We just need to
// 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 openHook(const char *funcname, const char *pathname, int flags, va_list va);
int openHook(fn_open_t *real_open, typeof(close) *real_close, const char *pathname, int flags, va_list va);
int open(const char *pathname, int flags, ...)
{
// Lookup the real libc functions if we haven't yet
pthread_once(&s_InitLibCFunctions, lookupRealLibCFunctions);
va_list va;
va_start(va, flags);
int fd = openHook(__FUNCTION__, pathname, flags, va);
int fd = openHook(fn_open, fn_close, pathname, flags, va);
va_end(va);
return fd;
}
int open64(const char *pathname, int flags, ...)
{
// Lookup the real libc functions if we haven't yet
pthread_once(&s_InitLibCFunctions, lookupRealLibCFunctions);
va_list va;
va_start(va, flags);
int fd = openHook(__FUNCTION__, pathname, flags, va);
int fd = openHook(fn_open64, fn_close, pathname, flags, va);
va_end(va);
return fd;
}
@ -111,23 +207,43 @@ int open64(const char *pathname, int flags, ...)
// after SDL closes its DRM FD.
int close(int fd)
{
// Lookup the real libc functions if we haven't yet
pthread_once(&s_InitLibCFunctions, lookupRealLibCFunctions);
// Remove this entry from the SDL FD table
bool lastSdlFd = removeSdlFd(fd);
// Call the real thing
int ret = ((typeof(close)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd);
if (ret == 0) {
// If we just closed the SDL DRM master FD, restore master
// to the Qt DRM FD. This works because the Qt DRM master FD
// was master once before, so we can set it as master again
// using drmSetMaster() without CAP_SYS_ADMIN.
if (g_SdlDrmMasterFd != -1 && fd == g_SdlDrmMasterFd) {
if (drmSetMaster(g_QtDrmMasterFd) < 0) {
int ret = fn_close(fd);
// If we closed the last SDL FD, restore master to the Qt FD
if (ret == 0 && lastSdlFd) {
if (drmSetMaster(g_QtDrmMasterFd) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to restore master to Qt DRM FD: %d",
errno);
}
// Reset the CRTC state to how Qt configured it
if (g_QtCrtcState) {
SDL_assert(fn_drmModeSetCrtc != NULL);
int err = fn_drmModeSetCrtc(g_QtDrmMasterFd,
g_QtCrtcState->crtc_id,
g_QtCrtcState->buffer_id,
g_QtCrtcState->x,
g_QtCrtcState->y,
g_QtCrtcConnectors,
g_QtCrtcConnectorCount,
g_QtCrtcState->mode_valid ?
&g_QtCrtcState->mode : NULL);
if (err < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to restore master to Qt DRM FD: %d",
"Failed to restore CRTC state to Qt DRM FD: %d",
errno);
}
g_SdlDrmMasterFd = -1;
}
}
return ret;
}

View File

@ -2,14 +2,17 @@
// which must be in a separate compilation unit due to fcntl.h doing
// unwanted redirection of open() to open64().
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <SDL.h>
#include <dlfcn.h>
#include "SDL_compat.h"
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <pthread.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
@ -28,9 +31,75 @@
extern int g_QtDrmMasterFd;
extern struct stat g_DrmMasterStat;
extern int g_SdlDrmMasterFd;
int openHook(const char *funcname, const char *pathname, int flags, va_list va)
#define MAX_SDL_FD_COUNT 8
int g_SdlDrmMasterFds[MAX_SDL_FD_COUNT];
int g_SdlDrmMasterFdCount = 0;
pthread_mutex_t g_FdTableLock = PTHREAD_MUTEX_INITIALIZER;
// Caller must hold g_FdTableLock
int getSdlFdEntryIndex(bool unused)
{
for (int i = 0; i < MAX_SDL_FD_COUNT; i++) {
// We slightly bend the FD rules here by treating 0
// as invalid since that's our global default value.
if (unused && g_SdlDrmMasterFds[i] <= 0) {
return i;
}
else if (!unused && g_SdlDrmMasterFds[i] > 0) {
return i;
}
}
return -1;
}
// Returns true if the final SDL FD was removed
bool removeSdlFd(int fd)
{
pthread_mutex_lock(&g_FdTableLock);
if (g_SdlDrmMasterFdCount != 0) {
// Clear the entry for this fd from the table
for (int i = 0; i < MAX_SDL_FD_COUNT; i++) {
if (fd == g_SdlDrmMasterFds[i]) {
g_SdlDrmMasterFds[i] = -1;
g_SdlDrmMasterFdCount--;
break;
}
}
if (g_SdlDrmMasterFdCount == 0) {
pthread_mutex_unlock(&g_FdTableLock);
return true;
}
}
pthread_mutex_unlock(&g_FdTableLock);
return false;
}
// Returns the previous master FD or -1 if none
int takeMasterFromSdlFd()
{
int fd = -1;
// Since all SDL FDs are actually dups of each other
// we can take master from any one of them.
pthread_mutex_lock(&g_FdTableLock);
int fdIndex = getSdlFdEntryIndex(false);
if (fdIndex != -1) {
fd = g_SdlDrmMasterFds[fdIndex];
}
pthread_mutex_unlock(&g_FdTableLock);
if (fd >= 0 && drmDropMaster(fd) == 0) {
return fd;
}
else {
return -1;
}
}
int openHook(typeof(open) *real_open, typeof(close) *real_close, const char *pathname, int flags, va_list va)
{
int fd;
mode_t mode;
@ -38,11 +107,11 @@ int openHook(const char *funcname, const char *pathname, int flags, va_list va)
// Call the real thing to do the open operation
if (__OPEN_NEEDS_MODE(flags)) {
mode = va_arg(va, mode_t);
fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags, mode);
fd = real_open(pathname, flags, mode);
}
else {
mode = 0;
fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags);
fd = real_open(pathname, flags);
}
// If the file was successfully opened and we have a DRM master FD,
@ -55,32 +124,67 @@ int openHook(const char *funcname, const char *pathname, int flags, va_list va)
fstat(fd, &fdstat);
if (g_DrmMasterStat.st_dev == fdstat.st_dev &&
g_DrmMasterStat.st_ino == fdstat.st_ino) {
int freeFdIndex;
int allocatedFdIndex;
// It is our device. Time to do the magic!
pthread_mutex_lock(&g_FdTableLock);
// 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) {
// Get a free index for us to put the new entry
freeFdIndex = getSdlFdEntryIndex(true);
if (freeFdIndex < 0) {
pthread_mutex_unlock(&g_FdTableLock);
SDL_assert(freeFdIndex >= 0);
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to drop master on Qt DRM FD: %d",
errno);
"No unused SDL FD table entries!");
// Hope for the best
return fd;
}
// We are not allowed to call drmSetMaster() without CAP_SYS_ADMIN,
// but since we just dropped the master, we can become master by
// simply creating a new FD. Let's do it.
close(fd);
if (__OPEN_NEEDS_MODE(flags)) {
fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags, mode);
// Check if we have an allocated entry already
allocatedFdIndex = getSdlFdEntryIndex(false);
if (allocatedFdIndex >= 0) {
// Close fd that we opened earlier (skipping our close() hook)
real_close(fd);
// dup() an existing FD into the unused slot
fd = dup(g_SdlDrmMasterFds[allocatedFdIndex]);
}
else {
fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags);
// Drop master on Qt's FD so we can pick it up for SDL.
if (drmDropMaster(g_QtDrmMasterFd) < 0) {
pthread_mutex_unlock(&g_FdTableLock);
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to drop master on Qt DRM FD: %d",
errno);
// Hope for the best
return fd;
}
// Close fd that we opened earlier (skipping our close() hook)
real_close(fd);
// We are not allowed to call drmSetMaster() without CAP_SYS_ADMIN,
// but since we just dropped the master, we can become master by
// simply creating a new FD. Let's do it.
if (__OPEN_NEEDS_MODE(flags)) {
fd = real_open(pathname, flags, mode);
}
else {
fd = real_open(pathname, flags);
}
}
g_SdlDrmMasterFd = fd;
if (fd >= 0) {
// Start with DRM master on the new FD
drmSetMaster(fd);
// Insert the FD into the table
g_SdlDrmMasterFds[freeFdIndex] = fd;
g_SdlDrmMasterFdCount++;
}
pthread_mutex_unlock(&g_FdTableLock);
}
}
}

View File

@ -32,7 +32,9 @@ QString Path::getQmlCacheDir()
QByteArray Path::readDataFile(QString fileName)
{
QFile dataFile(getDataFilePath(fileName));
dataFile.open(QIODevice::ReadOnly);
if (!dataFile.open(QIODevice::ReadOnly)) {
return {};
}
return dataFile.readAll();
}
@ -46,8 +48,9 @@ void Path::writeCacheFile(QString fileName, QByteArray data)
}
QFile dataFile(cacheDir.absoluteFilePath(fileName));
dataFile.open(QIODevice::WriteOnly);
dataFile.write(data);
if (dataFile.open(QIODevice::WriteOnly)) {
dataFile.write(data);
}
}
void Path::deleteCacheFile(QString fileName)

View File

@ -70,6 +70,12 @@
<file>languages/qml_lt.qm</file>
<file>languages/qml_et.ts</file>
<file>languages/qml_et.qm</file>
<file>languages/qml_bg.ts</file>
<file>languages/qml_bg.qm</file>
<file>languages/qml_eo.ts</file>
<file>languages/qml_eo.qm</file>
<file>languages/qml_ta.ts</file>
<file>languages/qml_ta.qm</file>
<!-- Don't include pt_BR until it is more complete -->
<!--file>languages/qml_pt_BR.qm</file-->
<!--file>languages/qml_pt_BR.ts</file-->

View File

@ -3,7 +3,7 @@
#include <QDir>
#include <SDL.h>
#include "SDL_compat.h"
#define SER_GAMEPADMAPPING "gcmapping"

View File

@ -16,6 +16,7 @@
#define SER_FPS "fps"
#define SER_BITRATE "bitrate"
#define SER_UNLOCK_BITRATE "unlockbitrate"
#define SER_AUTOADJUSTBITRATE "autoadjustbitrate"
#define SER_FULLSCREEN "fullscreen"
#define SER_VSYNC "vsync"
#define SER_GAMEOPTS "gameopts"
@ -34,6 +35,7 @@
#define SER_STARTWINDOWED "startwindowed"
#define SER_FRAMEPACING "framepacing"
#define SER_CONNWARNINGS "connwarnings"
#define SER_CONFWARNINGS "confwarnings"
#define SER_UIDISPLAYMODE "uidisplaymode"
#define SER_RICHPRESENCE "richpresence"
#define SER_GAMEPADMOUSE "gamepadmouse"
@ -122,6 +124,7 @@ void StreamingPreferences::reload()
enableYUV444 = settings.value(SER_YUV444, false).toBool();
bitrateKbps = settings.value(SER_BITRATE, getDefaultBitrate(width, height, fps, enableYUV444)).toInt();
unlockBitrate = settings.value(SER_UNLOCK_BITRATE, false).toBool();
autoAdjustBitrate = settings.value(SER_AUTOADJUSTBITRATE, true).toBool();
enableVsync = settings.value(SER_VSYNC, true).toBool();
gameOptimizations = settings.value(SER_GAMEOPTS, true).toBool();
playAudioOnHost = settings.value(SER_HOSTAUDIO, false).toBool();
@ -132,6 +135,7 @@ void StreamingPreferences::reload()
absoluteTouchMode = settings.value(SER_ABSTOUCHMODE, true).toBool();
framePacing = settings.value(SER_FRAMEPACING, false).toBool();
connectionWarnings = settings.value(SER_CONNWARNINGS, true).toBool();
configurationWarnings = settings.value(SER_CONFWARNINGS, true).toBool();
richPresence = settings.value(SER_RICHPRESENCE, true).toBool();
gamepadMouse = settings.value(SER_GAMEPADMOUSE, true).toBool();
detectNetworkBlocking = settings.value(SER_DETECTNETBLOCKING, true).toBool();
@ -297,6 +301,12 @@ QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Langua
return "lt";
case LANG_ET:
return "et";
case LANG_BG:
return "bg";
case LANG_EO:
return "eo";
case LANG_TA:
return "ta";
case LANG_AUTO:
default:
return QLocale::system().name();
@ -312,6 +322,7 @@ void StreamingPreferences::save()
settings.setValue(SER_FPS, fps);
settings.setValue(SER_BITRATE, bitrateKbps);
settings.setValue(SER_UNLOCK_BITRATE, unlockBitrate);
settings.setValue(SER_AUTOADJUSTBITRATE, autoAdjustBitrate);
settings.setValue(SER_VSYNC, enableVsync);
settings.setValue(SER_GAMEOPTS, gameOptimizations);
settings.setValue(SER_HOSTAUDIO, playAudioOnHost);
@ -322,6 +333,7 @@ void StreamingPreferences::save()
settings.setValue(SER_ABSTOUCHMODE, absoluteTouchMode);
settings.setValue(SER_FRAMEPACING, framePacing);
settings.setValue(SER_CONNWARNINGS, connectionWarnings);
settings.setValue(SER_CONFWARNINGS, configurationWarnings);
settings.setValue(SER_RICHPRESENCE, richPresence);
settings.setValue(SER_GAMEPADMOUSE, gamepadMouse);
settings.setValue(SER_PACKETSIZE, packetSize);

View File

@ -94,6 +94,9 @@ public:
LANG_CKB,
LANG_LT,
LANG_ET,
LANG_BG,
LANG_EO,
LANG_TA,
};
Q_ENUM(Language);
@ -110,6 +113,7 @@ public:
Q_PROPERTY(int fps MEMBER fps NOTIFY displayModeChanged)
Q_PROPERTY(int bitrateKbps MEMBER bitrateKbps NOTIFY bitrateChanged)
Q_PROPERTY(bool unlockBitrate MEMBER unlockBitrate NOTIFY unlockBitrateChanged)
Q_PROPERTY(bool autoAdjustBitrate MEMBER autoAdjustBitrate NOTIFY autoAdjustBitrateChanged)
Q_PROPERTY(bool enableVsync MEMBER enableVsync NOTIFY enableVsyncChanged)
Q_PROPERTY(bool gameOptimizations MEMBER gameOptimizations NOTIFY gameOptimizationsChanged)
Q_PROPERTY(bool playAudioOnHost MEMBER playAudioOnHost NOTIFY playAudioOnHostChanged)
@ -120,6 +124,7 @@ public:
Q_PROPERTY(bool absoluteTouchMode MEMBER absoluteTouchMode NOTIFY absoluteTouchModeChanged)
Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged)
Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged)
Q_PROPERTY(bool configurationWarnings MEMBER configurationWarnings NOTIFY configurationWarningsChanged)
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged)
Q_PROPERTY(bool gamepadMouse MEMBER gamepadMouse NOTIFY gamepadMouseChanged)
Q_PROPERTY(bool detectNetworkBlocking MEMBER detectNetworkBlocking NOTIFY detectNetworkBlockingChanged)
@ -149,6 +154,7 @@ public:
int fps;
int bitrateKbps;
bool unlockBitrate;
bool autoAdjustBitrate;
bool enableVsync;
bool gameOptimizations;
bool playAudioOnHost;
@ -159,6 +165,7 @@ public:
bool absoluteTouchMode;
bool framePacing;
bool connectionWarnings;
bool configurationWarnings;
bool richPresence;
bool gamepadMouse;
bool detectNetworkBlocking;
@ -185,6 +192,7 @@ signals:
void displayModeChanged();
void bitrateChanged();
void unlockBitrateChanged();
void autoAdjustBitrateChanged();
void enableVsyncChanged();
void gameOptimizationsChanged();
void playAudioOnHostChanged();
@ -203,6 +211,7 @@ signals:
void windowModeChanged();
void framePacingChanged();
void connectionWarningsChanged();
void configurationWarningsChanged();
void richPresenceChanged();
void gamepadMouseChanged();
void detectNetworkBlockingChanged();

View File

@ -1,10 +1,6 @@
#include "../session.h"
#include "renderers/renderer.h"
#ifdef HAVE_SOUNDIO
#include "renderers/soundioaudiorenderer.h"
#endif
#ifdef HAVE_SLAUDIO
#include "renderers/slaud.h"
#endif
@ -29,12 +25,6 @@ IAudioRenderer* Session::createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATI
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
return nullptr;
}
#ifdef HAVE_SOUNDIO
else if (mlAudio == "libsoundio") {
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
return nullptr;
}
#endif
#if defined(HAVE_SLAUDIO)
else if (mlAudio == "slaudio") {
TRY_INIT_RENDERER(SLAudioRenderer, opusConfig)
@ -55,11 +45,8 @@ IAudioRenderer* Session::createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATI
TRY_INIT_RENDERER(SLAudioRenderer, opusConfig)
#endif
// Default to SDL and use libsoundio as a fallback
// Default to SDL
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
#ifdef HAVE_SOUNDIO
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
#endif
return nullptr;
}
@ -108,21 +95,15 @@ bool Session::initializeAudioRenderer()
int Session::getAudioRendererCapabilities(int audioConfiguration)
{
// Build a fake OPUS_MULTISTREAM_CONFIGURATION to give
// the renderer the channel count and sample rate.
OPUS_MULTISTREAM_CONFIGURATION opusConfig = {};
opusConfig.sampleRate = 48000;
opusConfig.samplesPerFrame = 240;
opusConfig.channelCount = CHANNEL_COUNT_FROM_AUDIO_CONFIGURATION(audioConfiguration);
int caps = 0;
IAudioRenderer* audioRenderer = createAudioRenderer(&opusConfig);
if (audioRenderer == nullptr) {
return 0;
}
// All audio renderers support arbitrary audio duration
caps |= CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION;
int caps = audioRenderer->getCapabilities();
delete audioRenderer;
#ifdef STEAM_LINK
// Steam Link devices have slow Opus decoders
caps |= CAPABILITY_SLOW_OPUS_DECODER;
#endif
return caps;
}

View File

@ -15,8 +15,6 @@ public:
// Return false if an unrecoverable error has occurred and the renderer must be reinitialized
virtual bool submitAudio(int bytesWritten) = 0;
virtual int getCapabilities() = 0;
virtual void remapChannels(POPUS_MULTISTREAM_CONFIGURATION) {
// Use default channel mapping:
// 0 - Front Left

View File

@ -1,7 +1,7 @@
#pragma once
#include "renderer.h"
#include <SDL.h>
#include "SDL_compat.h"
class SdlAudioRenderer : public IAudioRenderer
{
@ -16,8 +16,6 @@ public:
virtual bool submitAudio(int bytesWritten);
virtual int getCapabilities();
virtual AudioFormat getAudioBufferFormat();
private:

View File

@ -1,7 +1,6 @@
#include "sdl.h"
#include <Limelight.h>
#include <SDL.h>
SdlAudioRenderer::SdlAudioRenderer()
: m_AudioDevice(0),
@ -31,14 +30,7 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION*
// to mitigate this issue. Otherwise, we will buffer up to 3 frames of audio which
// is 15 ms at regular 5 ms frames and 30 ms at 10 ms frames for slow connections.
// The buffering helps avoid audio underruns due to network jitter.
#ifndef Q_OS_DARWIN
want.samples = SDL_max(480, opusConfig->samplesPerFrame * 3);
#else
// HACK: Changing the buffer size can lead to Bluetooth HFP
// audio issues on macOS, so we're leaving this alone.
// https://github.com/moonlight-stream/moonlight-qt/issues/1071
want.samples = SDL_max(480, opusConfig->samplesPerFrame);
#endif
m_FrameSize = opusConfig->samplesPerFrame *
opusConfig->channelCount *
@ -140,12 +132,6 @@ bool SdlAudioRenderer::submitAudio(int bytesWritten)
return true;
}
int SdlAudioRenderer::getCapabilities()
{
// Direct submit can't be used because we use LiGetPendingAudioDuration()
return CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION;
}
IAudioRenderer::AudioFormat SdlAudioRenderer::getAudioBufferFormat()
{
return AudioFormat::Float32NE;

View File

@ -1,6 +1,6 @@
#include "slaud.h"
#include <SDL.h>
#include "SDL_compat.h"
SLAudioRenderer::SLAudioRenderer()
: m_AudioContext(nullptr),
@ -114,11 +114,6 @@ bool SLAudioRenderer::submitAudio(int bytesWritten)
return true;
}
int SLAudioRenderer::getCapabilities()
{
return CAPABILITY_SLOW_OPUS_DECODER | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION;
}
IAudioRenderer::AudioFormat SLAudioRenderer::getAudioBufferFormat()
{
return AudioFormat::Sint16NE;

View File

@ -16,8 +16,6 @@ public:
virtual bool submitAudio(int bytesWritten);
virtual int getCapabilities();
virtual AudioFormat getAudioBufferFormat();
virtual void remapChannels(POPUS_MULTISTREAM_CONFIGURATION opusConfig);

View File

@ -1,487 +0,0 @@
#include "soundioaudiorenderer.h"
#include <SDL.h>
#include <QtGlobal>
SoundIoAudioRenderer::SoundIoAudioRenderer()
: m_OpusChannelCount(0),
m_SoundIo(nullptr),
m_Device(nullptr),
m_OutputStream(nullptr),
m_RingBuffer(nullptr),
m_AudioPacketDuration(0),
m_Latency(0),
m_Errored(false)
{
}
SoundIoAudioRenderer::~SoundIoAudioRenderer()
{
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Audio latency: %f",
m_Latency);
if (m_OutputStream != nullptr) {
soundio_outstream_destroy(m_OutputStream);
}
// Must be destroyed after the stream is stopped
// or we could still get sioWriteCallback() calls.
if (m_RingBuffer != nullptr) {
soundio_ring_buffer_destroy(m_RingBuffer);
}
if (m_Device != nullptr) {
soundio_device_unref(m_Device);
}
if (m_SoundIo != nullptr) {
soundio_destroy(m_SoundIo);
}
}
int SoundIoAudioRenderer::scoreChannelLayout(const struct SoundIoChannelLayout* layout, const OPUS_MULTISTREAM_CONFIGURATION* opusConfig)
{
int score = 50;
// Compute a score for this layout based on how many matching channels
// we find (or acceptable alternatives).
for (int i = 0; i < layout->channel_count; i++) {
if (opusConfig->channelCount >= 2) {
switch (layout->channels[i]) {
case SoundIoChannelIdFrontLeft:
case SoundIoChannelIdFrontRight:
score += 2;
break;
default:
break;
}
}
if (opusConfig->channelCount >= 6) {
switch (layout->channels[i]) {
case SoundIoChannelIdFrontCenter:
case SoundIoChannelIdLfe:
score += 2;
break;
case SoundIoChannelIdSideLeft:
case SoundIoChannelIdSideRight:
score++;
break;
case SoundIoChannelIdBackLeft:
case SoundIoChannelIdBackRight:
if (opusConfig->channelCount == 6) {
// Score back channels higher in 5.1 mode to
// discourage selection of side channel layouts.
score += 2;
}
else {
// Score back channels normally for 7.1 mode
score++;
}
break;
default:
break;
}
}
}
// Now subtract the difference between the desired and actual channel count
// to punish layouts that have extra unused speakers.
if (opusConfig->channelCount < layout->channel_count) {
score -= layout->channel_count - opusConfig->channelCount;
}
return score;
}
bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig)
{
m_SoundIo = soundio_create();
if (m_SoundIo == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"soundio_create() failed");
return false;
}
m_SoundIo->app_name = "Moonlight";
m_SoundIo->userdata = this;
m_SoundIo->on_backend_disconnect = sioBackendDisconnect;
m_SoundIo->on_devices_change = sioDevicesChanged;
int err = soundio_connect(m_SoundIo);
if (err != SoundIoErrorNone) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"soundio_connect() failed: %s",
soundio_strerror(err));
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Audio backend: %s",
soundio_backend_name(m_SoundIo->current_backend));
// Don't continue if we could only open the dummy backend
if (m_SoundIo->current_backend == SoundIoBackendDummy) {
return false;
}
// Flush events to update with new device arrivals
soundio_flush_events(m_SoundIo);
// Remember the actual channel count for later
m_OpusChannelCount = opusConfig->channelCount;
int outputDeviceIndex = soundio_default_output_device_index(m_SoundIo);
if (outputDeviceIndex < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"No output device found");
return false;
}
m_Device = soundio_get_output_device(m_SoundIo, outputDeviceIndex);
if (m_Device == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"soundio_get_output_device() failed");
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Selected audio device: %s",
m_Device->name);
m_OutputStream = soundio_outstream_create(m_Device);
if (m_OutputStream == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"soundio_outstream_create() failed");
return false;
}
m_AudioPacketDuration = (opusConfig->samplesPerFrame / (opusConfig->sampleRate / 1000)) / 1000.0;
m_OutputStream->format = SoundIoFormatFloat32NE;
m_OutputStream->sample_rate = opusConfig->sampleRate;
m_OutputStream->software_latency = m_AudioPacketDuration;
m_OutputStream->name = "Moonlight";
m_OutputStream->userdata = this;
m_OutputStream->error_callback = sioErrorCallback;
m_OutputStream->write_callback = sioWriteCallback;
SoundIoChannelLayout bestLayout = m_Device->current_layout;
for (int i = 0; i < m_Device->layout_count; i++) {
if (scoreChannelLayout(&bestLayout, opusConfig) <
scoreChannelLayout(&m_Device->layouts[i], opusConfig)) {
bestLayout = m_Device->layouts[i];
}
}
if (bestLayout.channel_count < opusConfig->channelCount) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"No compatible channel layouts found. Some channels may not be played!");
}
m_OutputStream->layout = bestLayout;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Native layout: %s (%d channels)",
m_OutputStream->layout.name ?
m_OutputStream->layout.name : "<UNKNOWN>",
m_OutputStream->layout.channel_count);
err = soundio_outstream_open(m_OutputStream);
if (err != SoundIoErrorNone) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"soundio_outstream_open() failed: %s",
soundio_strerror(err));
return false;
}
if (m_OutputStream->layout_error) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Channel layout failed: %s",
soundio_strerror(m_OutputStream->layout_error));
// ALSA through PulseAudio appears to fail snd_pcm_set_chmap()
// even after claiming the layout is supported (and even on totally
// standard layouts like Stereo). We'll just ignore this for ALSA
// and only bail if we get an actual failure out of one of these APIs.
if (m_SoundIo->current_backend != SoundIoBackendAlsa) {
return false;
}
}
m_EffectiveLayout = m_OutputStream->layout;
for (int i = 0; i < m_EffectiveLayout.channel_count; i++) {
if (opusConfig->channelCount == 6) {
// For 5.1, replace side L/R with back L/R so our channel position
// logic in sioWriteCallback() works.
if (m_EffectiveLayout.channels[i] == SoundIoChannelIdSideLeft) {
m_EffectiveLayout.channels[i] = SoundIoChannelIdBackLeft;
}
if (m_EffectiveLayout.channels[i] == SoundIoChannelIdSideRight) {
m_EffectiveLayout.channels[i] = SoundIoChannelIdBackRight;
}
}
else if (opusConfig->channelCount == 8) {
// For 5.1, replace side L/R with LOC/ROC so our channel position
// logic in sioWriteCallback() works.
if (m_EffectiveLayout.channels[i] == SoundIoChannelIdSideLeft) {
m_EffectiveLayout.channels[i] = SoundIoChannelIdFrontLeftCenter;
}
if (m_EffectiveLayout.channels[i] == SoundIoChannelIdSideRight) {
m_EffectiveLayout.channels[i] = SoundIoChannelIdFrontRightCenter;
}
}
}
int packetsToBuffer;
if (m_SoundIo->current_backend == SoundIoBackendWasapi) {
// 15 ms buffer seems to be fine for WASAPI
packetsToBuffer = (int)ceil(0.015 / m_AudioPacketDuration);
}
else {
// 30 ms buffer on CoreAudio to avoid glitching on macOS
packetsToBuffer = (int)ceil(0.030 / m_AudioPacketDuration);
}
// Always buffer at least 2 packets
packetsToBuffer = qMax(2, packetsToBuffer);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Audio buffer size: %f seconds",
packetsToBuffer * m_AudioPacketDuration);
m_RingBuffer = soundio_ring_buffer_create(m_SoundIo,
m_OutputStream->bytes_per_sample *
m_OpusChannelCount *
opusConfig->samplesPerFrame *
packetsToBuffer);
if (m_RingBuffer == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"soundio_ring_buffer_create() failed");
return false;
}
err = soundio_outstream_start(m_OutputStream);
if (err != SoundIoErrorNone) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"soundio_outstream_start() failed: %s",
soundio_strerror(err));
return false;
}
// HACK: For some reason, a constant latency hangs around in the audio pipeline
// unless we wait for the audio stream to drain before actually submitting any samples.
// This is a gross hack, but it works remarkably well.
SDL_Delay(500);
return true;
}
void* SoundIoAudioRenderer::getAudioBuffer(int* size)
{
// We must always write a full frame of audio. If we don't,
// the reader will get out of sync with the writer and our
// channels will get all mixed up. To ensure this is always
// the case, round our bytes free down to the next multiple
// of our frame size.
int bytesFree = soundio_ring_buffer_free_count(m_RingBuffer);
int bytesPerFrame = m_OpusChannelCount * m_OutputStream->bytes_per_sample;
*size = qMin(*size, (bytesFree / bytesPerFrame) * bytesPerFrame);
return soundio_ring_buffer_write_ptr(m_RingBuffer);
}
bool SoundIoAudioRenderer::submitAudio(int bytesWritten)
{
if (m_Errored) {
return false;
}
if (bytesWritten == 0) {
// Nothing to do
return true;
}
// Flush events to update with new device arrivals
soundio_flush_events(m_SoundIo);
// Advance the write pointer
soundio_ring_buffer_advance_write_ptr(m_RingBuffer, bytesWritten);
return true;
}
int SoundIoAudioRenderer::getCapabilities()
{
// TODO: Tweak buffer sizes then re-enable arbitrary audio duration
return CAPABILITY_DIRECT_SUBMIT /* | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION */;
}
IAudioRenderer::AudioFormat SoundIoAudioRenderer::getAudioBufferFormat()
{
return AudioFormat::Float32NE;
}
void SoundIoAudioRenderer::sioErrorCallback(SoundIoOutStream* stream, int err)
{
auto me = reinterpret_cast<SoundIoAudioRenderer*>(stream->userdata);
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Audio rendering error: %s",
soundio_strerror(err));
// Trigger reinitialization
me->m_Errored = true;
}
void SoundIoAudioRenderer::sioBackendDisconnect(SoundIo* soundio, int err)
{
auto me = reinterpret_cast<SoundIoAudioRenderer*>(soundio->userdata);
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Audio backend disconnected: %s",
soundio_strerror(err));
// Trigger reinitialization
me->m_Errored = true;
}
void SoundIoAudioRenderer::sioDevicesChanged(SoundIo* soundio)
{
auto me = reinterpret_cast<SoundIoAudioRenderer*>(soundio->userdata);
if (me->m_Device == nullptr) {
// Ignore calls that take place during initialization
return;
}
int outputDeviceIndex = soundio_default_output_device_index(soundio);
if (outputDeviceIndex >= 0) {
struct SoundIoDevice* outputDevice = soundio_get_output_device(soundio, outputDeviceIndex);
if (outputDevice == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"soundio_get_output_device() failed");
return;
}
if (!soundio_device_equal(outputDevice, me->m_Device)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Default audio output device changed");
// Trigger reinitialization
me->m_Errored = true;
}
soundio_device_unref(outputDevice);
}
}
// bytes_per_frame should never be used on the ring buffer! It's not always
// the same number of bytes per frames as the output stream!
void SoundIoAudioRenderer::sioWriteCallback(SoundIoOutStream* stream, int frameCountMin, int frameCountMax)
{
auto me = reinterpret_cast<SoundIoAudioRenderer*>(stream->userdata);
char* readPtr = soundio_ring_buffer_read_ptr(me->m_RingBuffer);
int framesLeft = soundio_ring_buffer_fill_count(me->m_RingBuffer) /
(me->m_OpusChannelCount * stream->bytes_per_sample);
int bytesRead = 0;
// Ensure we always write at least a buffer, even if it's silence, to avoid
// busy looping when no audio data is available while libsoundio tries to keep
// us from starving the output device.
frameCountMin = qMax(frameCountMin, (int)(stream->sample_rate * me->m_AudioPacketDuration));
// Clamp frameCountMax to at least 2 packets or 20 ms to stop our latency from growing if audio packets lag.
// This makes sure that we never increase our latency far beyond what the sink is consuming.
frameCountMax = qMin(frameCountMax, (int)(stream->sample_rate * qMax(me->m_AudioPacketDuration * 2, 0.020)));
frameCountMin = qMin(frameCountMin, frameCountMax);
// Clamp framesLeft to frameCountMax
framesLeft = qMin(framesLeft, frameCountMax);
// Track latency on queueing-based backends
if (me->m_SoundIo->current_backend != SoundIoBackendCoreAudio && me->m_SoundIo->current_backend != SoundIoBackendJack) {
soundio_outstream_get_latency(stream, &me->m_Latency);
}
for (;;) {
int frameCount;
int err;
struct SoundIoChannelArea* areas;
// Always meet the minimum but don't write more than that
// if we'll have to insert silence
frameCount = qMax(framesLeft, frameCountMin);
if (frameCount == 0) {
// Nothing more to write
break;
}
err = soundio_outstream_begin_write(stream, &areas, &frameCount);
if (err != SoundIoErrorNone) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"soundio_outstream_begin_write() failed: %s",
soundio_strerror(err));
break;
}
for (int frame = 0; frame < frameCount; frame++) {
for (int ch = 0; ch < me->m_EffectiveLayout.channel_count; ch++) {
// SoundIoChannelId - 1 happens to match Moonlight's channel layout
// after we've applied our fixups to m_EffectiveLayout for 5.1 and 7.1.
int readPtrChannel = me->m_EffectiveLayout.channels[ch] - 1;
if (frame >= framesLeft || readPtrChannel >= me->m_OpusChannelCount) {
// Write silence if we have no buffered frames left or
// nothing in the audio stream for this channel
memset(areas[ch].ptr, 0, stream->bytes_per_sample);
}
else {
// Write audio data from our ring buffer
memcpy(areas[ch].ptr,
&readPtr[readPtrChannel * stream->bytes_per_sample],
stream->bytes_per_sample);
}
areas[ch].ptr += areas[ch].step;
}
// Move on to the next frame if we aren't inserting silence
if (frame < framesLeft) {
readPtr += stream->bytes_per_sample * me->m_OpusChannelCount;
bytesRead += stream->bytes_per_sample * me->m_OpusChannelCount;
}
}
err = soundio_outstream_end_write(stream);
if (err != SoundIoErrorNone && err != SoundIoErrorUnderflow) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"soundio_outstream_end_write() failed: %s",
soundio_strerror(err));
break;
}
if (framesLeft >= frameCount) {
framesLeft -= frameCount;
}
else {
framesLeft = 0;
}
if (frameCountMin >= frameCount) {
frameCountMin -= frameCount;
}
else {
frameCountMin = 0;
}
}
soundio_ring_buffer_advance_read_ptr(me->m_RingBuffer, bytesRead);
}

View File

@ -1,44 +0,0 @@
#pragma once
#include "renderer.h"
#include <soundio/soundio.h>
class SoundIoAudioRenderer : public IAudioRenderer
{
public:
SoundIoAudioRenderer();
~SoundIoAudioRenderer();
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
virtual void* getAudioBuffer(int* size);
virtual bool submitAudio(int bytesWritten);
virtual int getCapabilities();
virtual AudioFormat getAudioBufferFormat();
private:
int scoreChannelLayout(const struct SoundIoChannelLayout* layout, const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
static void sioErrorCallback(struct SoundIoOutStream* stream, int err);
static void sioWriteCallback(struct SoundIoOutStream* stream, int frameCountMin, int frameCountMax);
static void sioBackendDisconnect(struct SoundIo* soundio, int err);
static void sioDevicesChanged(SoundIo* soundio);
int m_OpusChannelCount;
struct SoundIo* m_SoundIo;
struct SoundIoDevice* m_Device;
struct SoundIoOutStream* m_OutputStream;
struct SoundIoRingBuffer* m_RingBuffer;
struct SoundIoChannelLayout m_EffectiveLayout;
double m_AudioPacketDuration;
double m_Latency;
bool m_Errored;
};

View File

@ -1,7 +1,7 @@
#include "input.h"
#include <Limelight.h>
#include <SDL.h>
#include "SDL_compat.h"
#include <SDL_syswm.h>
#include "streaming/streamutils.h"

View File

@ -1,7 +1,7 @@
#include "streaming/session.h"
#include <Limelight.h>
#include <SDL.h>
#include "SDL_compat.h"
#include "settings/mappingmanager.h"
#include <QtMath>
@ -900,6 +900,22 @@ void SdlInputHandler::setControllerLED(uint16_t controllerNumber, uint8_t r, uin
#endif
}
void SdlInputHandler::setAdaptiveTriggers(uint16_t controllerNumber, DualSenseOutputReport *report){
#if SDL_VERSION_ATLEAST(2, 0, 16)
// Make sure the controller number is within our supported count
if (controllerNumber <= MAX_GAMEPADS &&
// and we have a valid controller
m_GamepadState[controllerNumber].controller != nullptr &&
// and it's a PS5 controller
SDL_GameControllerGetType(m_GamepadState[controllerNumber].controller) == SDL_CONTROLLER_TYPE_PS5) {
SDL_GameControllerSendEffect(m_GamepadState[controllerNumber].controller, report, sizeof(*report));
}
#endif
SDL_free(report);
}
QString SdlInputHandler::getUnmappedGamepads()
{
QString ret;
@ -913,7 +929,8 @@ QString SdlInputHandler::getUnmappedGamepads()
MappingManager mappingManager;
mappingManager.applyMappings();
for (int i = 0; i < SDL_NumJoysticks(); i++) {
int numJoysticks = SDL_NumJoysticks();
for (int i = 0; i < numJoysticks; i++) {
if (!SDL_IsGameController(i)) {
char guidStr[33];
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(i),
@ -973,7 +990,8 @@ int SdlInputHandler::getAttachedGamepadMask()
}
count = mask = 0;
for (int i = 0; i < SDL_NumJoysticks(); i++) {
int numJoysticks = SDL_NumJoysticks();
for (int i = 0; i < numJoysticks; i++) {
if (SDL_IsGameController(i)) {
char guidStr[33];
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(i),

View File

@ -1,5 +1,5 @@
#include <Limelight.h>
#include <SDL.h>
#include "SDL_compat.h"
#include "streaming/session.h"
#include "settings/mappingmanager.h"
#include "path.h"
@ -50,7 +50,7 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i
#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");
SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0");
// Allow clicks to pass through to us when focusing the window. If we're in
// absolute mouse mode, this will avoid the user having to click twice to
@ -62,8 +62,8 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i
// 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
// want this behavior, they can override it with the environment variable.
SDL_SetHint("SDL_JOYSTICK_HIDAPI_PS4_RUMBLE", "1");
SDL_SetHint("SDL_JOYSTICK_HIDAPI_PS5_RUMBLE", "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
// Populate special key combo configuration
m_SpecialKeyCombos[KeyComboQuit].keyCombo = KeyComboQuit;
@ -111,6 +111,11 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i
m_SpecialKeyCombos[KeyComboTogglePointerRegionLock].scanCode = SDL_SCANCODE_L;
m_SpecialKeyCombos[KeyComboTogglePointerRegionLock].enabled = true;
m_SpecialKeyCombos[KeyComboQuitAndExit].keyCombo = KeyComboQuitAndExit;
m_SpecialKeyCombos[KeyComboQuitAndExit].keyCode = SDLK_e;
m_SpecialKeyCombos[KeyComboQuitAndExit].scanCode = SDL_SCANCODE_E;
m_SpecialKeyCombos[KeyComboQuitAndExit].enabled = true;
m_OldIgnoreDevices = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES);
m_OldIgnoreDevicesExcept = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT);
@ -296,6 +301,21 @@ void SdlInputHandler::notifyFocusLost()
// 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.
raiseAllKeys();
#ifdef Q_OS_WIN32
// Re-enable text input when window loses focus as a workaround for an SDL bug.
// See #1617 for details.
SDL_StartTextInput();
#endif
}
void SdlInputHandler::notifyFocusGained()
{
#ifdef Q_OS_WIN32
// Disable text input when window gains focus to prevent IME popup interference.
// See #1617 for details.
SDL_StopTextInput();
#endif
}
bool SdlInputHandler::isCaptureActive()

View File

@ -3,7 +3,7 @@
#include "settings/streamingpreferences.h"
#include "backend/computermanager.h"
#include <SDL.h>
#include "SDL_compat.h"
struct GamepadState {
SDL_GameController* controller;
@ -38,6 +38,37 @@ struct GamepadState {
unsigned char lt, rt;
};
struct DualSenseOutputReport{
uint8_t validFlag0;
uint8_t validFlag1;
/* For DualShock 4 compatibility mode. */
uint8_t motorRight;
uint8_t motorLeft;
/* Audio controls */
uint8_t reserved[4];
uint8_t muteButtonLed;
uint8_t powerSaveControl;
uint8_t rightTriggerEffectType;
uint8_t rightTriggerEffect[DS_EFFECT_PAYLOAD_SIZE];
uint8_t leftTriggerEffectType;
uint8_t leftTriggerEffect[DS_EFFECT_PAYLOAD_SIZE];
uint8_t reserved2[6];
/* LEDs and lightbar */
uint8_t validFlag2;
uint8_t reserved3[2];
uint8_t lightbarSetup;
uint8_t ledBrightness;
uint8_t playerLeds;
uint8_t lightbarRed;
uint8_t lightbarGreen;
uint8_t lightbarBlue;
};
// activeGamepadMask is a short, so we're bounded by the number of mask bits
#define MAX_GAMEPADS 16
@ -95,6 +126,8 @@ public:
void setControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b);
void setAdaptiveTriggers(uint16_t controllerNumber, DualSenseOutputReport *report);
void handleTouchFingerEvent(SDL_TouchFingerEvent* event);
int getAttachedGamepadMask();
@ -105,6 +138,8 @@ public:
void notifyFocusLost();
void notifyFocusGained();
bool isCaptureActive();
bool isSystemKeyCaptureActive();
@ -131,6 +166,7 @@ private:
KeyComboToggleMinimize,
KeyComboPasteText,
KeyComboTogglePointerRegionLock,
KeyComboQuitAndExit,
KeyComboMax
};

View File

@ -1,7 +1,7 @@
#include "streaming/session.h"
#include <Limelight.h>
#include <SDL.h>
#include "SDL_compat.h"
#define VK_0 0x30
#define VK_A 0x41
@ -139,6 +139,20 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
updatePointerRegionLock();
break;
case KeyComboQuitAndExit:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected quitAndExit key combo");
// Indicate that we want to exit afterwards
Session::get()->setShouldExitAfterQuit();
// Push a quit event to the main loop
SDL_Event quitExitEvent;
quitExitEvent.type = SDL_QUIT;
quitExitEvent.quit.timestamp = SDL_GetTicks();
SDL_PushEvent(&quitExitEvent);
break;
default:
Q_UNREACHABLE();
}
@ -148,6 +162,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
{
short keyCode;
char modifiers;
bool shouldNotConvertToScanCodeOnServer = false;
if (event->repeat) {
// Ignore repeat key down events
@ -402,6 +417,8 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
case SDL_SCANCODE_LEFTBRACKET:
keyCode = 0xDB;
break;
case SDL_SCANCODE_INTERNATIONAL3:
shouldNotConvertToScanCodeOnServer = true;
case SDL_SCANCODE_BACKSLASH:
keyCode = 0xDC;
break;
@ -411,9 +428,17 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
case SDL_SCANCODE_APOSTROPHE:
keyCode = 0xDE;
break;
case SDL_SCANCODE_INTERNATIONAL1:
shouldNotConvertToScanCodeOnServer = true;
case SDL_SCANCODE_NONUSBACKSLASH:
keyCode = 0xE2;
break;
case SDL_SCANCODE_LANG1:
keyCode = 0x1C;
break;
case SDL_SCANCODE_LANG2:
keyCode = 0x1D;
break;
default:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Unhandled button event: %d",
@ -430,8 +455,9 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
m_KeysDown.remove(keyCode);
}
LiSendKeyboardEvent(0x8000 | keyCode,
LiSendKeyboardEvent2(0x8000 | keyCode,
event->state == SDL_PRESSED ?
KEY_ACTION_DOWN : KEY_ACTION_UP,
modifiers);
modifiers,
shouldNotConvertToScanCodeOnServer ? SS_KBE_FLAG_NON_NORMALIZED : 0);
}

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