├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── auto-comment.yml ├── no-response.yml └── stale.yml ├── .gitignore ├── .gitmodules ├── AntiHooking ├── AntiHooking.pro ├── antihookingprotection.cpp └── antihookingprotection.h ├── LICENSE ├── README.md ├── app ├── Info.plist ├── ModeSeven.ttf ├── Moonlight.exe.manifest ├── SDL_compat.h ├── app.pro ├── backend │ ├── autoupdatechecker.cpp │ ├── autoupdatechecker.h │ ├── boxartmanager.cpp │ ├── boxartmanager.h │ ├── computermanager.cpp │ ├── computermanager.h │ ├── computerseeker.cpp │ ├── computerseeker.h │ ├── identitymanager.cpp │ ├── identitymanager.h │ ├── nvaddress.cpp │ ├── nvaddress.h │ ├── nvapp.cpp │ ├── nvapp.h │ ├── nvcomputer.cpp │ ├── nvcomputer.h │ ├── nvhttp.cpp │ ├── nvhttp.h │ ├── nvpairingmanager.cpp │ ├── nvpairingmanager.h │ ├── richpresencemanager.cpp │ ├── richpresencemanager.h │ ├── systemproperties.cpp │ └── systemproperties.h ├── cli │ ├── commandlineparser.cpp │ ├── commandlineparser.h │ ├── listapps.cpp │ ├── listapps.h │ ├── pair.cpp │ ├── pair.h │ ├── quitstream.cpp │ ├── quitstream.h │ ├── startstream.cpp │ └── startstream.h ├── deploy │ ├── linux │ │ ├── appimage │ │ │ └── 0001-Vulkan-Don-t-try-to-reuse-old-swapchain.patch │ │ ├── com.moonlight_stream.Moonlight.appdata.xml │ │ └── com.moonlight_stream.Moonlight.desktop │ └── steamlink │ │ ├── moonlight.png │ │ ├── moonlight.sh │ │ └── toc.txt ├── gui │ ├── AppView.qml │ ├── AutoResizingComboBox.qml │ ├── CenteredGridView.qml │ ├── CliPair.qml │ ├── CliQuitStreamSegue.qml │ ├── CliStartStreamSegue.qml │ ├── ErrorMessageDialog.qml │ ├── GamepadMapper.qml │ ├── NavigableDialog.qml │ ├── NavigableItemDelegate.qml │ ├── NavigableMenu.qml │ ├── NavigableMenuItem.qml │ ├── NavigableMessageDialog.qml │ ├── NavigableToolButton.qml │ ├── PcView.qml │ ├── QuitSegue.qml │ ├── SettingsView.qml │ ├── StreamSegue.qml │ ├── appmodel.cpp │ ├── appmodel.h │ ├── computermodel.cpp │ ├── computermodel.h │ ├── main.qml │ ├── sdlgamepadkeynavigation.cpp │ └── sdlgamepadkeynavigation.h ├── languages │ ├── qml_bg.qm │ ├── qml_bg.ts │ ├── qml_ckb.qm │ ├── qml_ckb.ts │ ├── qml_cs.qm │ ├── qml_cs.ts │ ├── qml_de.qm │ ├── qml_de.ts │ ├── qml_el.qm │ ├── qml_el.ts │ ├── qml_eo.qm │ ├── qml_eo.ts │ ├── qml_es.qm │ ├── qml_es.ts │ ├── qml_et.qm │ ├── qml_et.ts │ ├── qml_fr.qm │ ├── qml_fr.ts │ ├── qml_he.qm │ ├── qml_he.ts │ ├── qml_hi.qm │ ├── qml_hi.ts │ ├── qml_hu.qm │ ├── qml_hu.ts │ ├── qml_it.qm │ ├── qml_it.ts │ ├── qml_ja.qm │ ├── qml_ja.ts │ ├── qml_ko.qm │ ├── qml_ko.ts │ ├── qml_lt.qm │ ├── qml_lt.ts │ ├── qml_nb_NO.qm │ ├── qml_nb_NO.ts │ ├── qml_nl.qm │ ├── qml_nl.ts │ ├── qml_pl.qm │ ├── qml_pl.ts │ ├── qml_pt.qm │ ├── qml_pt.ts │ ├── qml_pt_BR.qm │ ├── qml_pt_BR.ts │ ├── qml_ru.qm │ ├── qml_ru.ts │ ├── qml_sv.qm │ ├── qml_sv.ts │ ├── qml_ta.qm │ ├── qml_ta.ts │ ├── qml_th.qm │ ├── qml_th.ts │ ├── qml_tr.qm │ ├── qml_tr.ts │ ├── qml_uk.qm │ ├── qml_uk.ts │ ├── qml_vi.qm │ ├── qml_vi.ts │ ├── qml_zh_CN.qm │ ├── qml_zh_CN.ts │ ├── qml_zh_TW.qm │ └── qml_zh_TW.ts ├── main.cpp ├── masterhook.c ├── masterhook_internal.c ├── moonlight.icns ├── moonlight.ico ├── moonlight_wix.png ├── path.cpp ├── path.h ├── qml.qrc ├── qt_qt5.conf ├── res │ ├── arrow_left.svg │ ├── baseline-check_circle_outline-24px.svg │ ├── baseline-error_outline-24px.svg │ ├── baseline-help_outline-24px.svg │ ├── baseline-lock-24px.svg │ ├── baseline-warning-24px.svg │ ├── desktop_windows-48px.svg │ ├── discord.svg │ ├── ic_add_to_queue_white_48px.svg │ ├── ic_videogame_asset_white_48px.svg │ ├── moonlight.svg │ ├── no_app_image.png │ ├── play_arrow_FILL1_wght700_GRAD200_opsz48.svg │ ├── question_mark.svg │ ├── settings.svg │ ├── stop_FILL1_wght700_GRAD200_opsz48.svg │ ├── update.svg │ └── warning_FILL1_wght300_GRAD200_opsz24.svg ├── resources.qrc ├── settings │ ├── compatfetcher.cpp │ ├── compatfetcher.h │ ├── mappingfetcher.cpp │ ├── mappingfetcher.h │ ├── mappingmanager.cpp │ ├── mappingmanager.h │ ├── streamingpreferences.cpp │ └── streamingpreferences.h ├── shaders │ ├── build_hlsl.bat │ ├── d3d11_ayuv_pixel.fxc │ ├── d3d11_ayuv_pixel.hlsl │ ├── d3d11_bt2020lim_pixel.fxc │ ├── d3d11_bt2020lim_pixel.hlsl │ ├── d3d11_bt601lim_pixel.fxc │ ├── d3d11_bt601lim_pixel.hlsl │ ├── d3d11_genyuv_pixel.fxc │ ├── d3d11_genyuv_pixel.hlsl │ ├── d3d11_overlay_pixel.fxc │ ├── d3d11_overlay_pixel.hlsl │ ├── d3d11_vertex.fxc │ ├── d3d11_vertex.hlsl │ ├── d3d11_video_pixel_end.hlsli │ ├── d3d11_video_pixel_start.hlsli │ ├── d3d11_y410_pixel.fxc │ ├── d3d11_y410_pixel.hlsl │ ├── d3d11_yuv444_pixel_end.hlsli │ ├── d3d11_yuv444_pixel_start.hlsli │ ├── egl_nv12.frag │ ├── egl_nv12.vert │ ├── egl_opaque.frag │ ├── egl_opaque.vert │ ├── egl_overlay.frag │ ├── egl_overlay.vert │ └── vt_renderer.metal ├── streaming │ ├── audio │ │ ├── audio.cpp │ │ └── renderers │ │ │ ├── renderer.h │ │ │ ├── sdl.h │ │ │ ├── sdlaud.cpp │ │ │ ├── slaud.cpp │ │ │ ├── slaud.h │ │ │ ├── soundioaudiorenderer.cpp │ │ │ └── soundioaudiorenderer.h │ ├── input │ │ ├── abstouch.cpp │ │ ├── gamepad.cpp │ │ ├── input.cpp │ │ ├── input.h │ │ ├── keyboard.cpp │ │ ├── mouse.cpp │ │ └── reltouch.cpp │ ├── session.cpp │ ├── session.h │ ├── streamutils.cpp │ ├── streamutils.h │ └── video │ │ ├── decoder.h │ │ ├── ffmpeg-renderers │ │ ├── cuda.cpp │ │ ├── cuda.h │ │ ├── d3d11va.cpp │ │ ├── d3d11va.h │ │ ├── drm.cpp │ │ ├── drm.h │ │ ├── dxutil.h │ │ ├── dxva2.cpp │ │ ├── dxva2.h │ │ ├── egl_extensions.cpp │ │ ├── eglimagefactory.cpp │ │ ├── eglimagefactory.h │ │ ├── eglvid.cpp │ │ ├── eglvid.h │ │ ├── genhwaccel.cpp │ │ ├── genhwaccel.h │ │ ├── mmal.cpp │ │ ├── mmal.h │ │ ├── pacer │ │ │ ├── dxvsyncsource.cpp │ │ │ ├── dxvsyncsource.h │ │ │ ├── pacer.cpp │ │ │ ├── pacer.h │ │ │ ├── waylandvsyncsource.cpp │ │ │ └── waylandvsyncsource.h │ │ ├── plvk.cpp │ │ ├── plvk.h │ │ ├── plvk_c.c │ │ ├── renderer.h │ │ ├── sdlvid.cpp │ │ ├── sdlvid.h │ │ ├── swframemapper.cpp │ │ ├── swframemapper.h │ │ ├── vaapi.cpp │ │ ├── vaapi.h │ │ ├── vdpau.cpp │ │ ├── vdpau.h │ │ ├── vt.h │ │ ├── vt_avsamplelayer.mm │ │ ├── vt_base.mm │ │ └── vt_metal.mm │ │ ├── ffmpeg.cpp │ │ ├── ffmpeg.h │ │ ├── ffmpeg_videosamples.cpp │ │ ├── overlaymanager.cpp │ │ ├── overlaymanager.h │ │ ├── slvid.cpp │ │ └── slvid.h ├── utils.h ├── version.txt └── wm.cpp ├── appveyor.yml ├── config.tests ├── EGL │ ├── EGL.pro │ └── main.cpp └── SL │ ├── SL.pro │ └── main.cpp ├── globaldefs.pri ├── h264bitstream └── h264bitstream.pro ├── moonlight-common-c └── moonlight-common-c.pro ├── moonlight-qt.pro ├── qmdnsengine ├── qmdnsengine.pro └── qmdnsengine_export.h ├── scripts ├── appveyor │ ├── qmake.bat │ ├── qtpaths.bat │ └── target_qt.conf ├── build-appimage.sh ├── build-arch.bat ├── build-steamlink-app.sh ├── clean-libs.sh ├── create-qt-pdb-zip.bat ├── generate-bundle.bat ├── generate-dmg.sh ├── generate-ico.sh ├── generate-src.sh ├── git-archive-all.sh ├── jom.exe ├── svg2icns ├── update-msvcredist.ps1 └── vswhere.exe ├── soundio ├── config.h └── soundio.pro └── wix ├── Moonlight.sln ├── Moonlight ├── Moonlight.wixproj └── Product.wxs └── MoonlightSetup ├── Bundle.wxs ├── MoonlightSetup.wixproj ├── RtfTheme.xml └── license.rtf /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Follow the troubleshooting guide before reporting a bug 4 | 5 | --- 6 | **READ ME FIRST!** 7 | If you're here because something basic is not working (like gamepad input, video, or similar), it's probably something specific to your setup, so make sure you've gone through the Troubleshooting Guide first: https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting 8 | 9 | If you still have trouble with basic functionality after following the guide, join our Discord server where there are many other volunteers who can help (or direct you back here if it looks like a Moonlight bug after all). https://moonlight-stream.org/discord 10 | 11 | **Describe the bug** 12 | A clear and concise description of what the bug is. 13 | 14 | **Steps to reproduce** 15 | Any special steps that are required for the bug to appear. 16 | 17 | **Screenshots** 18 | If applicable, add screenshots to help explain your problem. If the issue is related to video glitching or poor quality, please include screenshots. 19 | 20 | **Affected games** 21 | List the games you've tried that exhibit the issue. To see if the issue is game-specific, try streaming Steam Big Picture with Moonlight and see if the issue persists there. 22 | 23 | **Other Moonlight clients** 24 | - Does the issue occur when using Moonlight on iOS or Android? 25 | 26 | **Moonlight settings (please complete the following information)** 27 | - Have any settings been adjusted from defaults? 28 | - If so, which settings have been changed? 29 | - Does the problem still occur after reverting settings back to default? 30 | 31 | **Gamepad-related issues (please complete if problem is gamepad-related)** 32 | - Do you have any gamepads connected to your host PC directly? 33 | - Does the problem still remain if you stream the desktop and use https://html5gamepad.com to test your gamepad? 34 | - Instructions for streaming the desktop can be found here: https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide 35 | 36 | **Client PC details (please complete the following information)** 37 | - OS: [e.g. Windows 10 1809] 38 | - Moonlight Version: [e.g. v0.9.0] 39 | - GPU: [e.g. Intel HD Graphics 520] 40 | - Linux package type (if applicable): [e.g. Flatpak] 41 | 42 | **Server PC details (please complete the following information)** 43 | - OS: [e.g. Windows 10 1809] 44 | - Sunshine or GeForce Experience version: [e.g. Sunshine v0.21.0] 45 | - GPU: [e.g. AMD Radeon RX 7900 XT] 46 | - GPU driver: [e.g. 24.1.1] 47 | 48 | **Moonlight Logs (please attach)** 49 | - On Windows, `Moonlight-###.log` files can be found in `%TEMP%`. Simply type that into the File Explorer path field to navigate there. 50 | - On macOS, `Moonlight-###.log` files can be found in `/tmp`. In Finder, press Cmd+Shift+G, then type `/tmp` to navigate there. 51 | - On Linux with the Flatpak, logs print to the terminal when running with the command: `flatpak run com.moonlight_stream.Moonlight` 52 | - On Linux with the Snap, logs print to the terminal when running with the command: `moonlight` 53 | 54 | **Additional context** 55 | Anything else you think may be relevant to the issue 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/auto-comment.yml: -------------------------------------------------------------------------------- 1 | issuesOpened: > 2 | If this is a question about Moonlight or you need help troubleshooting a streaming problem, please use the help channels on our [Discord server](https://moonlight-stream.org/discord) instead of GitHub issues. There are many more people available on Discord to help you and answer your questions.

3 | This issue tracker should only be used for specific bugs or feature requests.

4 | Thank you, and happy streaming! 5 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # ProBot No Response (https://probot.github.io/apps/no-response/) 2 | 3 | daysUntilClose: 7 4 | responseRequiredLabel: 'need more info' 5 | closeComment: > 6 | This issue has been automatically closed because there was no response to a 7 | request for more information from the issue opener. Please leave a comment or 8 | open a new issue if you have additional information related to this issue. 9 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # ProBot Stale (https://probot.github.io/apps/stale/) 2 | 3 | daysUntilStale: 90 4 | daysUntilClose: 7 5 | exemptLabels: 6 | - accepted 7 | - bug 8 | - enhancement 9 | - meta 10 | staleLabel: stale 11 | markComment: > 12 | This issue has been automatically marked as stale because it has not had 13 | recent activity. It will be closed if no further activity occurs. 14 | closeComment: false 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *pro.user 2 | 3 | 4 | **/.vs/ 5 | build/ 6 | config.tests/*/.qmake.stash 7 | config.tests/*/Makefile 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "moonlight-common-c/moonlight-common-c"] 2 | path = moonlight-common-c/moonlight-common-c 3 | url = https://github.com/moonlight-stream/moonlight-common-c.git 4 | [submodule "qmdnsengine/qmdnsengine"] 5 | path = qmdnsengine/qmdnsengine 6 | url = https://github.com/cgutman/qmdnsengine.git 7 | [submodule "app/SDL_GameControllerDB"] 8 | path = app/SDL_GameControllerDB 9 | url = https://github.com/gabomdq/SDL_GameControllerDB.git 10 | [submodule "soundio/libsoundio"] 11 | path = soundio/libsoundio 12 | url = https://github.com/cgutman/libsoundio.git 13 | [submodule "h264bitstream/h264bitstream"] 14 | path = h264bitstream/h264bitstream 15 | url = https://github.com/aizvorski/h264bitstream.git 16 | [submodule "libs"] 17 | path = libs 18 | url = https://github.com/cgutman/moonlight-qt-prebuilts.git 19 | shallow = true 20 | -------------------------------------------------------------------------------- /AntiHooking/AntiHooking.pro: -------------------------------------------------------------------------------- 1 | QT -= core gui 2 | 3 | TARGET = AntiHooking 4 | TEMPLATE = lib 5 | 6 | include(../globaldefs.pri) 7 | 8 | INCLUDEPATH += $$PWD/../libs/windows/include 9 | contains(QT_ARCH, i386) { 10 | LIBS += -L$$PWD/../libs/windows/lib/x86 11 | } 12 | contains(QT_ARCH, x86_64) { 13 | LIBS += -L$$PWD/../libs/windows/lib/x64 14 | } 15 | contains(QT_ARCH, arm64) { 16 | LIBS += -L$$PWD/../libs/windows/lib/arm64 17 | } 18 | 19 | LIBS += -ldetours 20 | DEFINES += ANTIHOOKING_LIBRARY 21 | SOURCES += antihookingprotection.cpp 22 | HEADERS += antihookingprotection.h 23 | -------------------------------------------------------------------------------- /AntiHooking/antihookingprotection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef ANTIHOOKING_LIBRARY 4 | #define AH_EXPORT extern "C" __declspec(dllexport) 5 | #else 6 | #define AH_EXPORT extern "C" __declspec(dllimport) 7 | #endif 8 | 9 | AH_EXPORT void AntiHookingDummyImport(); 10 | 11 | -------------------------------------------------------------------------------- /app/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleExecutable 6 | Moonlight 7 | CFBundleGetInfoString 8 | Stream games and other applications from another PC 9 | CFBundleIconFile 10 | moonlight 11 | CFBundleIdentifier 12 | com.moonlight-stream.Moonlight 13 | NSHighResolutionCapable 14 | 15 | CFBundlePackageType 16 | APPL 17 | CFBundleSignature 18 | ???? 19 | LSMinimumSystemVersion 20 | 11.0.0 21 | NSPrincipalClass 22 | NSApplication 23 | NSSupportsAutomaticGraphicsSwitching 24 | 25 | GCSupportedGameControllers 26 | 27 | 28 | ProfileName 29 | ExtendedGamepad 30 | 31 | 32 | GCSupportsControllerUserInteraction 33 | 34 | NSAppTransportSecurity 35 | 36 | NSAllowsArbitraryLoads 37 | 38 | 39 | NSLocalNetworkUsageDescription 40 | Moonlight uses the local network to connect to your gaming PC for streaming. 41 | CFBundleVersion 42 | VERSION 43 | CFBundleShortVersionString 44 | VERSION 45 | CFBundleDisplayName 46 | Moonlight 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/ModeSeven.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/ModeSeven.ttf -------------------------------------------------------------------------------- /app/Moonlight.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Moonlight Game Streaming 11 | 12 | 13 | true/pm 14 | permonitor 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/SDL_compat.h: -------------------------------------------------------------------------------- 1 | // 2 | // Compatibility header for older version of SDL. 3 | // Include this instead of SDL.h directly. 4 | // 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | // SDL_FRect wasn't added until 2.0.10 11 | #if !SDL_VERSION_ATLEAST(2, 0, 10) 12 | typedef struct SDL_FRect 13 | { 14 | float x; 15 | float y; 16 | float w; 17 | float h; 18 | } SDL_FRect; 19 | #endif 20 | 21 | #ifndef SDL_HINT_VIDEO_X11_FORCE_EGL 22 | #define SDL_HINT_VIDEO_X11_FORCE_EGL "SDL_VIDEO_X11_FORCE_EGL" 23 | #endif 24 | 25 | #ifndef SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER 26 | #define SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER "SDL_KMSDRM_REQUIRE_DRM_MASTER" 27 | #endif 28 | 29 | #ifndef SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED 30 | #define SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED "SDL_ALLOW_ALT_TAB_WHILE_GRABBED" 31 | #endif 32 | 33 | #ifndef SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE 34 | #define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE "SDL_JOYSTICK_HIDAPI_PS4_RUMBLE" 35 | #endif 36 | 37 | #ifndef SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE 38 | #define SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE "SDL_JOYSTICK_HIDAPI_PS5_RUMBLE" 39 | #endif 40 | 41 | #ifndef SDL_HINT_WINDOWS_USE_D3D9EX 42 | #define SDL_HINT_WINDOWS_USE_D3D9EX "SDL_WINDOWS_USE_D3D9EX" 43 | #endif 44 | 45 | #ifndef SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS 46 | #define SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS "SDL_GAMECONTROLLER_USE_BUTTON_LABELS" 47 | #endif 48 | 49 | #ifndef SDL_HINT_MOUSE_RELATIVE_SCALING 50 | #define SDL_HINT_MOUSE_RELATIVE_SCALING "SDL_MOUSE_RELATIVE_SCALING" 51 | #endif 52 | 53 | #ifndef SDL_HINT_AUDIO_DEVICE_APP_NAME 54 | #define SDL_HINT_AUDIO_DEVICE_APP_NAME "SDL_AUDIO_DEVICE_APP_NAME" 55 | #endif 56 | 57 | #ifndef SDL_HINT_APP_NAME 58 | #define SDL_HINT_APP_NAME "SDL_APP_NAME" 59 | #endif 60 | 61 | #ifndef SDL_HINT_MOUSE_AUTO_CAPTURE 62 | #define SDL_HINT_MOUSE_AUTO_CAPTURE "SDL_MOUSE_AUTO_CAPTURE" 63 | #endif 64 | 65 | #ifndef SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP 66 | #define SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP "SDL_VIDEO_WAYLAND_EMULATE_MOUSE_WARP" 67 | #endif 68 | -------------------------------------------------------------------------------- /app/backend/autoupdatechecker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class AutoUpdateChecker : public QObject 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit AutoUpdateChecker(QObject *parent = nullptr); 11 | 12 | Q_INVOKABLE void start(); 13 | 14 | signals: 15 | void onUpdateAvailable(QString newVersion, QString url); 16 | 17 | private slots: 18 | void handleUpdateCheckRequestFinished(QNetworkReply* reply); 19 | 20 | private: 21 | void parseStringToVersionQuad(QString& string, QVector& version); 22 | 23 | int compareVersion(QVector& version1, QVector& version2); 24 | 25 | QString getPlatform(); 26 | 27 | QVector m_CurrentVersionQuad; 28 | QNetworkAccessManager* m_Nam; 29 | }; 30 | -------------------------------------------------------------------------------- /app/backend/boxartmanager.cpp: -------------------------------------------------------------------------------- 1 | #include "boxartmanager.h" 2 | #include "../path.h" 3 | 4 | #include 5 | #include 6 | 7 | BoxArtManager::BoxArtManager(QObject *parent) : 8 | QObject(parent), 9 | m_BoxArtDir(Path::getBoxArtCacheDir()), 10 | m_ThreadPool(this) 11 | { 12 | // 4 is a good balance between fast loading for large 13 | // app grids and not crushing GFE with tons of requests 14 | // and causing UI jank from constantly stalling to decode 15 | // new images. 16 | m_ThreadPool.setMaxThreadCount(4); 17 | if (!m_BoxArtDir.exists()) { 18 | m_BoxArtDir.mkpath("."); 19 | } 20 | } 21 | 22 | QString 23 | BoxArtManager::getFilePathForBoxArt(NvComputer* computer, int appId) 24 | { 25 | QDir dir = m_BoxArtDir; 26 | 27 | // Create the cache directory if it did not already exist 28 | if (!dir.exists(computer->uuid)) { 29 | dir.mkdir(computer->uuid); 30 | } 31 | 32 | // Change to this computer's box art cache folder 33 | dir.cd(computer->uuid); 34 | 35 | // Try to open the cached file 36 | return dir.filePath(QString::number(appId) + ".png"); 37 | } 38 | 39 | class NetworkBoxArtLoadTask : public QObject, public QRunnable 40 | { 41 | Q_OBJECT 42 | 43 | public: 44 | NetworkBoxArtLoadTask(BoxArtManager* boxArtManager, NvComputer* computer, NvApp& app) 45 | : m_Bam(boxArtManager), 46 | m_Computer(computer), 47 | m_App(app) 48 | { 49 | connect(this, &NetworkBoxArtLoadTask::boxArtFetchCompleted, 50 | boxArtManager, &BoxArtManager::handleBoxArtLoadComplete); 51 | } 52 | 53 | signals: 54 | void boxArtFetchCompleted(NvComputer* computer, NvApp app, QUrl image); 55 | 56 | private: 57 | void run() 58 | { 59 | QUrl image = m_Bam->loadBoxArtFromNetwork(m_Computer, m_App.id); 60 | if (image.isEmpty()) { 61 | // Give it another shot if it fails once 62 | image = m_Bam->loadBoxArtFromNetwork(m_Computer, m_App.id); 63 | } 64 | emit boxArtFetchCompleted(m_Computer, m_App, image); 65 | } 66 | 67 | BoxArtManager* m_Bam; 68 | NvComputer* m_Computer; 69 | NvApp m_App; 70 | }; 71 | 72 | QUrl BoxArtManager::loadBoxArt(NvComputer* computer, NvApp& app) 73 | { 74 | // Try to open the cached file if it exists and contains data 75 | QFile cacheFile(getFilePathForBoxArt(computer, app.id)); 76 | if (cacheFile.exists() && cacheFile.size() > 0) { 77 | return QUrl::fromLocalFile(cacheFile.fileName()); 78 | } 79 | 80 | // If we get here, we need to fetch asynchronously. 81 | // Kick off a worker on our thread pool to do just that. 82 | NetworkBoxArtLoadTask* netLoadTask = new NetworkBoxArtLoadTask(this, computer, app); 83 | m_ThreadPool.start(netLoadTask); 84 | 85 | // Return the placeholder then we can notify the caller 86 | // later when the real image is ready. 87 | return QUrl("qrc:/res/no_app_image.png"); 88 | } 89 | 90 | void BoxArtManager::deleteBoxArt(NvComputer* computer) 91 | { 92 | QDir dir(Path::getBoxArtCacheDir()); 93 | 94 | // Delete everything in this computer's box art directory 95 | if (dir.cd(computer->uuid)) { 96 | dir.removeRecursively(); 97 | } 98 | } 99 | 100 | void BoxArtManager::handleBoxArtLoadComplete(NvComputer* computer, NvApp app, QUrl image) 101 | { 102 | if (!image.isEmpty()) { 103 | emit boxArtLoadComplete(computer, app, image); 104 | } 105 | } 106 | 107 | QUrl BoxArtManager::loadBoxArtFromNetwork(NvComputer* computer, int appId) 108 | { 109 | NvHTTP http(computer); 110 | 111 | QString cachePath = getFilePathForBoxArt(computer, appId); 112 | QImage image; 113 | try { 114 | image = http.getBoxArt(appId); 115 | } catch (...) {} 116 | 117 | // Cache the box art on disk if it loaded 118 | if (!image.isNull()) { 119 | if (image.save(cachePath)) { 120 | return QUrl::fromLocalFile(cachePath); 121 | } 122 | else { 123 | // A failed save() may leave a zero byte file. Make sure that's removed. 124 | QFile(cachePath).remove(); 125 | } 126 | } 127 | 128 | return QUrl(); 129 | } 130 | 131 | #include "boxartmanager.moc" 132 | -------------------------------------------------------------------------------- /app/backend/boxartmanager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "computermanager.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class BoxArtManager : public QObject 10 | { 11 | Q_OBJECT 12 | 13 | friend class NetworkBoxArtLoadTask; 14 | 15 | public: 16 | explicit BoxArtManager(QObject *parent = nullptr); 17 | 18 | QUrl 19 | loadBoxArt(NvComputer* computer, NvApp& app); 20 | 21 | static 22 | void 23 | deleteBoxArt(NvComputer* computer); 24 | 25 | signals: 26 | void 27 | boxArtLoadComplete(NvComputer* computer, NvApp app, QUrl image); 28 | 29 | public slots: 30 | 31 | private slots: 32 | void 33 | handleBoxArtLoadComplete(NvComputer* computer, NvApp app, QUrl image); 34 | 35 | private: 36 | QUrl 37 | loadBoxArtFromNetwork(NvComputer* computer, int appId); 38 | 39 | QString 40 | getFilePathForBoxArt(NvComputer* computer, int appId); 41 | 42 | QDir m_BoxArtDir; 43 | QThreadPool m_ThreadPool; 44 | }; 45 | -------------------------------------------------------------------------------- /app/backend/computerseeker.cpp: -------------------------------------------------------------------------------- 1 | #include "computerseeker.h" 2 | #include "computermanager.h" 3 | #include 4 | 5 | ComputerSeeker::ComputerSeeker(ComputerManager *manager, QString computerName, QObject *parent) 6 | : QObject(parent), m_ComputerManager(manager), m_ComputerName(computerName), 7 | m_TimeoutTimer(new QTimer(this)) 8 | { 9 | // If we know this computer, send a WOL packet to wake it up in case it is asleep. 10 | for (NvComputer * computer: m_ComputerManager->getComputers()) { 11 | if (this->matchComputer(computer)) { 12 | computer->wake(); 13 | } 14 | } 15 | 16 | m_TimeoutTimer->setSingleShot(true); 17 | connect(m_TimeoutTimer, &QTimer::timeout, 18 | this, &ComputerSeeker::onTimeout); 19 | connect(m_ComputerManager, &ComputerManager::computerStateChanged, 20 | this, &ComputerSeeker::onComputerUpdated); 21 | } 22 | 23 | void ComputerSeeker::start(int timeout) 24 | { 25 | m_TimeoutTimer->start(timeout); 26 | // Seek desired computer by both connecting to it directly (this may fail 27 | // if m_ComputerName is UUID, or the name that doesn't resolve to an IP 28 | // address) and by polling it using mDNS, hopefully one of these methods 29 | // would find the host 30 | m_ComputerManager->addNewHostManually(m_ComputerName); 31 | m_ComputerManager->startPolling(); 32 | } 33 | 34 | void ComputerSeeker::onComputerUpdated(NvComputer *computer) 35 | { 36 | if (!m_TimeoutTimer->isActive()) { 37 | return; 38 | } 39 | if (matchComputer(computer) && isOnline(computer)) { 40 | m_ComputerManager->stopPollingAsync(); 41 | m_TimeoutTimer->stop(); 42 | emit computerFound(computer); 43 | } 44 | } 45 | 46 | bool ComputerSeeker::matchComputer(NvComputer *computer) const 47 | { 48 | QString value = m_ComputerName.toLower(); 49 | 50 | if (computer->name.toLower() == value || computer->uuid.toLower() == value) { 51 | return true; 52 | } 53 | 54 | for (const NvAddress& addr : computer->uniqueAddresses()) { 55 | if (addr.address().toLower() == value || addr.toString().toLower() == value) { 56 | return true; 57 | } 58 | } 59 | 60 | return false; 61 | } 62 | 63 | bool ComputerSeeker::isOnline(NvComputer *computer) const 64 | { 65 | return computer->state == NvComputer::CS_ONLINE; 66 | } 67 | 68 | void ComputerSeeker::onTimeout() 69 | { 70 | m_TimeoutTimer->stop(); 71 | m_ComputerManager->stopPollingAsync(); 72 | emit errorTimeout(); 73 | } 74 | -------------------------------------------------------------------------------- /app/backend/computerseeker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class ComputerManager; 6 | class NvComputer; 7 | class QTimer; 8 | 9 | class ComputerSeeker : public QObject 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit ComputerSeeker(ComputerManager *manager, QString computerName, QObject *parent = nullptr); 14 | 15 | void start(int timeout); 16 | 17 | signals: 18 | void computerFound(NvComputer *computer); 19 | void errorTimeout(); 20 | 21 | private slots: 22 | void onComputerUpdated(NvComputer *computer); 23 | void onTimeout(); 24 | 25 | private: 26 | bool matchComputer(NvComputer *computer) const; 27 | bool isOnline(NvComputer *computer) const; 28 | 29 | private: 30 | ComputerManager *m_ComputerManager; 31 | QString m_ComputerName; 32 | QTimer *m_TimeoutTimer; 33 | }; 34 | -------------------------------------------------------------------------------- /app/backend/identitymanager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class IdentityManager 9 | { 10 | public: 11 | QString 12 | getUniqueId(); 13 | 14 | QByteArray 15 | getCertificate(); 16 | 17 | QByteArray 18 | getPrivateKey(); 19 | 20 | QSslConfiguration 21 | getSslConfig(); 22 | 23 | static 24 | IdentityManager* 25 | get(); 26 | 27 | private: 28 | IdentityManager(); 29 | 30 | QSslCertificate 31 | getSslCertificate(); 32 | 33 | QSslKey 34 | getSslKey(); 35 | 36 | void 37 | createCredentials(QSettings& settings); 38 | 39 | // Initialized in constructor 40 | QByteArray m_CachedPrivateKey; 41 | QByteArray m_CachedPemCert; 42 | 43 | // Lazy initialized 44 | QString m_CachedUniqueId; 45 | QSslCertificate m_CachedSslCert; 46 | QSslKey m_CachedSslKey; 47 | 48 | static IdentityManager* s_Im; 49 | }; 50 | -------------------------------------------------------------------------------- /app/backend/nvaddress.cpp: -------------------------------------------------------------------------------- 1 | #include "nvaddress.h" 2 | 3 | #include 4 | 5 | NvAddress::NvAddress() 6 | { 7 | setAddress(nullptr); 8 | setPort(0); 9 | } 10 | 11 | NvAddress::NvAddress(QString addr, uint16_t port) 12 | { 13 | setAddress(addr); 14 | setPort(port); 15 | } 16 | 17 | NvAddress::NvAddress(QHostAddress addr, uint16_t port) 18 | { 19 | setAddress(addr); 20 | setPort(port); 21 | } 22 | 23 | uint16_t NvAddress::port() const 24 | { 25 | return m_Port; 26 | } 27 | 28 | QString NvAddress::address() const 29 | { 30 | return m_Address; 31 | } 32 | 33 | void NvAddress::setPort(uint16_t port) 34 | { 35 | m_Port = port; 36 | } 37 | 38 | void NvAddress::setAddress(QString addr) 39 | { 40 | m_Address = addr; 41 | } 42 | 43 | void NvAddress::setAddress(QHostAddress addr) 44 | { 45 | m_Address = addr.toString(); 46 | } 47 | 48 | bool NvAddress::isNull() const 49 | { 50 | return m_Address.isEmpty(); 51 | } 52 | 53 | QString NvAddress::toString() const 54 | { 55 | if (m_Address.isEmpty()) { 56 | return ""; 57 | } 58 | 59 | if (QHostAddress(m_Address).protocol() == QAbstractSocket::IPv6Protocol) { 60 | return QString("[%1]:%2").arg(m_Address).arg(m_Port); 61 | } 62 | else { 63 | return QString("%1:%2").arg(m_Address).arg(m_Port); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/backend/nvaddress.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define DEFAULT_HTTP_PORT 47989 6 | #define DEFAULT_HTTPS_PORT 47984 7 | 8 | class NvAddress 9 | { 10 | public: 11 | NvAddress(); 12 | explicit NvAddress(QString addr, uint16_t port); 13 | explicit NvAddress(QHostAddress addr, uint16_t port); 14 | 15 | uint16_t port() const; 16 | void setPort(uint16_t port); 17 | 18 | QString address() const; 19 | void setAddress(QString addr); 20 | void setAddress(QHostAddress addr); 21 | 22 | bool isNull() const; 23 | QString toString() const; 24 | 25 | bool operator==(const NvAddress& other) const 26 | { 27 | return m_Address == other.m_Address && 28 | m_Port == other.m_Port; 29 | } 30 | 31 | bool operator!=(const NvAddress& other) const 32 | { 33 | return !operator==(other); 34 | } 35 | 36 | private: 37 | QString m_Address; 38 | uint16_t m_Port; 39 | }; 40 | -------------------------------------------------------------------------------- /app/backend/nvapp.cpp: -------------------------------------------------------------------------------- 1 | #include "nvapp.h" 2 | 3 | #define SER_APPNAME "name" 4 | #define SER_APPID "id" 5 | #define SER_APPHDR "hdr" 6 | #define SER_APPCOLLECTOR "appcollector" 7 | #define SER_HIDDEN "hidden" 8 | #define SER_DIRECTLAUNCH "directlaunch" 9 | 10 | NvApp::NvApp(QSettings& settings) 11 | { 12 | name = settings.value(SER_APPNAME).toString(); 13 | id = settings.value(SER_APPID).toInt(); 14 | hdrSupported = settings.value(SER_APPHDR).toBool(); 15 | isAppCollectorGame = settings.value(SER_APPCOLLECTOR).toBool(); 16 | hidden = settings.value(SER_HIDDEN).toBool(); 17 | directLaunch = settings.value(SER_DIRECTLAUNCH).toBool(); 18 | } 19 | 20 | void NvApp::serialize(QSettings& settings) const 21 | { 22 | settings.setValue(SER_APPNAME, name); 23 | settings.setValue(SER_APPID, id); 24 | settings.setValue(SER_APPHDR, hdrSupported); 25 | settings.setValue(SER_APPCOLLECTOR, isAppCollectorGame); 26 | settings.setValue(SER_HIDDEN, hidden); 27 | settings.setValue(SER_DIRECTLAUNCH, directLaunch); 28 | } 29 | -------------------------------------------------------------------------------- /app/backend/nvapp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class NvApp 6 | { 7 | public: 8 | NvApp() {} 9 | explicit NvApp(QSettings& settings); 10 | 11 | bool operator==(const NvApp& other) const 12 | { 13 | return id == other.id && 14 | name == other.name && 15 | hdrSupported == other.hdrSupported && 16 | isAppCollectorGame == other.isAppCollectorGame && 17 | hidden == other.hidden && 18 | directLaunch == other.directLaunch; 19 | } 20 | 21 | bool operator!=(const NvApp& other) const 22 | { 23 | return !operator==(other); 24 | } 25 | 26 | bool isInitialized() 27 | { 28 | return id != 0 && !name.isEmpty(); 29 | } 30 | 31 | void 32 | serialize(QSettings& settings) const; 33 | 34 | int id = 0; 35 | QString name; 36 | bool hdrSupported = false; 37 | bool isAppCollectorGame = false; 38 | bool hidden = false; 39 | bool directLaunch = false; 40 | }; 41 | 42 | Q_DECLARE_METATYPE(NvApp) 43 | -------------------------------------------------------------------------------- /app/backend/nvcomputer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "nvhttp.h" 4 | #include "nvaddress.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class CopySafeReadWriteLock : public QReadWriteLock 12 | { 13 | public: 14 | CopySafeReadWriteLock() = default; 15 | 16 | // Don't actually copy the QReadWriteLock 17 | CopySafeReadWriteLock(const CopySafeReadWriteLock&) : QReadWriteLock() {} 18 | CopySafeReadWriteLock& operator=(const CopySafeReadWriteLock &) { return *this; } 19 | }; 20 | 21 | class NvComputer 22 | { 23 | friend class PcMonitorThread; 24 | friend class ComputerManager; 25 | friend class PendingQuitTask; 26 | 27 | private: 28 | void sortAppList(); 29 | 30 | bool updateAppList(QVector newAppList); 31 | 32 | bool pendingQuit; 33 | 34 | public: 35 | NvComputer() = default; 36 | 37 | // Caller is responsible for synchronizing read access to the other host 38 | NvComputer(const NvComputer&) = default; 39 | 40 | // Caller is responsible for synchronizing read access to the other host 41 | NvComputer& operator=(const NvComputer &) = default; 42 | 43 | explicit NvComputer(NvHTTP& http, QString serverInfo); 44 | 45 | explicit NvComputer(QSettings& settings); 46 | 47 | void 48 | setRemoteAddress(QHostAddress); 49 | 50 | bool 51 | update(const NvComputer& that); 52 | 53 | bool 54 | wake() const; 55 | 56 | enum ReachabilityType 57 | { 58 | RI_UNKNOWN, 59 | RI_LAN, 60 | RI_VPN, 61 | }; 62 | 63 | ReachabilityType 64 | getActiveAddressReachability() const; 65 | 66 | QVector 67 | uniqueAddresses() const; 68 | 69 | void 70 | serialize(QSettings& settings, bool serializeApps) const; 71 | 72 | // Caller is responsible for synchronizing read access to both hosts 73 | bool 74 | isEqualSerialized(const NvComputer& that) const; 75 | 76 | enum PairState 77 | { 78 | PS_UNKNOWN, 79 | PS_PAIRED, 80 | PS_NOT_PAIRED 81 | }; 82 | 83 | enum ComputerState 84 | { 85 | CS_UNKNOWN, 86 | CS_ONLINE, 87 | CS_OFFLINE 88 | }; 89 | 90 | // Ephemeral traits 91 | ComputerState state; 92 | PairState pairState; 93 | NvAddress activeAddress; 94 | uint16_t activeHttpsPort; 95 | int currentGameId; 96 | QString gfeVersion; 97 | QString appVersion; 98 | QVector displayModes; 99 | int maxLumaPixelsHEVC; 100 | int serverCodecModeSupport; 101 | QString gpuModel; 102 | bool isSupportedServerVersion; 103 | 104 | // Persisted traits 105 | NvAddress localAddress; 106 | NvAddress remoteAddress; 107 | NvAddress ipv6Address; 108 | NvAddress manualAddress; 109 | QByteArray macAddress; 110 | QString name; 111 | bool hasCustomName; 112 | QString uuid; 113 | QSslCertificate serverCert; 114 | QVector appList; 115 | bool isNvidiaServerSoftware; 116 | // Remember to update isEqualSerialized() when adding fields here! 117 | 118 | // Synchronization 119 | mutable CopySafeReadWriteLock lock; 120 | 121 | private: 122 | uint16_t externalPort; 123 | }; 124 | -------------------------------------------------------------------------------- /app/backend/nvpairingmanager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "identitymanager.h" 4 | #include "nvhttp.h" 5 | 6 | #include 7 | #include 8 | 9 | class NvPairingManager 10 | { 11 | public: 12 | enum PairState 13 | { 14 | PAIRED, 15 | PIN_WRONG, 16 | FAILED, 17 | ALREADY_IN_PROGRESS 18 | }; 19 | 20 | explicit NvPairingManager(NvComputer* computer); 21 | 22 | ~NvPairingManager(); 23 | 24 | PairState 25 | pair(QString appVersion, QString pin, QSslCertificate& serverCert); 26 | 27 | private: 28 | QByteArray 29 | generateRandomBytes(int length); 30 | 31 | QByteArray 32 | saltPin(const QByteArray& salt, QString pin); 33 | 34 | QByteArray 35 | encrypt(const QByteArray& plaintext, const QByteArray& key); 36 | 37 | QByteArray 38 | decrypt(const QByteArray& ciphertext, const QByteArray& key); 39 | 40 | QByteArray 41 | getSignatureFromPemCert(const QByteArray& certificate); 42 | 43 | bool 44 | verifySignature(const QByteArray& data, const QByteArray& signature, const QByteArray& serverCertificate); 45 | 46 | QByteArray 47 | signMessage(const QByteArray& message); 48 | 49 | NvHTTP m_Http; 50 | X509* m_Cert; 51 | EVP_PKEY* m_PrivateKey; 52 | }; 53 | -------------------------------------------------------------------------------- /app/backend/richpresencemanager.cpp: -------------------------------------------------------------------------------- 1 | #include "richpresencemanager.h" 2 | 3 | #include 4 | 5 | RichPresenceManager::RichPresenceManager(StreamingPreferences& prefs, QString gameName) 6 | : m_DiscordActive(false) 7 | { 8 | #ifdef HAVE_DISCORD 9 | if (prefs.richPresence) { 10 | DiscordEventHandlers handlers = {}; 11 | handlers.ready = discordReady; 12 | handlers.disconnected = discordDisconnected; 13 | handlers.errored = discordErrored; 14 | Discord_Initialize("594668102021677159", &handlers, 0, nullptr); 15 | m_DiscordActive = true; 16 | } 17 | 18 | if (m_DiscordActive) { 19 | QByteArray stateStr = (QString("Streaming ") + gameName).toUtf8(); 20 | 21 | DiscordRichPresence discordPresence = {}; 22 | discordPresence.state = stateStr.data(); 23 | discordPresence.startTimestamp = time(nullptr); 24 | discordPresence.largeImageKey = "icon"; 25 | Discord_UpdatePresence(&discordPresence); 26 | } 27 | #else 28 | Q_UNUSED(prefs) 29 | Q_UNUSED(gameName) 30 | #endif 31 | } 32 | 33 | RichPresenceManager::~RichPresenceManager() 34 | { 35 | #ifdef HAVE_DISCORD 36 | if (m_DiscordActive) { 37 | Discord_ClearPresence(); 38 | Discord_Shutdown(); 39 | } 40 | #endif 41 | } 42 | 43 | void RichPresenceManager::runCallbacks() 44 | { 45 | #ifdef HAVE_DISCORD 46 | if (m_DiscordActive) { 47 | Discord_RunCallbacks(); 48 | } 49 | #endif 50 | } 51 | 52 | #ifdef HAVE_DISCORD 53 | void RichPresenceManager::discordReady(const DiscordUser* request) 54 | { 55 | qInfo() << "Discord integration ready for user:" << request->username; 56 | } 57 | 58 | void RichPresenceManager::discordDisconnected(int errorCode, const char *message) 59 | { 60 | qInfo() << "Discord integration disconnected:" << errorCode << message; 61 | } 62 | 63 | void RichPresenceManager::discordErrored(int errorCode, const char *message) 64 | { 65 | qWarning() << "Discord integration error:" << errorCode << message; 66 | } 67 | #endif 68 | -------------------------------------------------------------------------------- /app/backend/richpresencemanager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "settings/streamingpreferences.h" 4 | 5 | #ifdef HAVE_DISCORD 6 | #include 7 | #endif 8 | 9 | class RichPresenceManager 10 | { 11 | public: 12 | RichPresenceManager(StreamingPreferences& prefs, QString gameName); 13 | ~RichPresenceManager(); 14 | 15 | void runCallbacks(); 16 | 17 | private: 18 | #ifdef HAVE_DISCORD 19 | static void discordReady(const DiscordUser* request); 20 | static void discordDisconnected(int errorCode, const char* message); 21 | static void discordErrored(int errorCode, const char* message); 22 | #endif 23 | 24 | bool m_DiscordActive; 25 | }; 26 | 27 | -------------------------------------------------------------------------------- /app/backend/systemproperties.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class SystemProperties : public QObject 7 | { 8 | Q_OBJECT 9 | 10 | friend class QuerySdlVideoThread; 11 | friend class RefreshDisplaysThread; 12 | 13 | public: 14 | SystemProperties(); 15 | 16 | Q_PROPERTY(bool hasHardwareAcceleration MEMBER hasHardwareAcceleration CONSTANT) 17 | Q_PROPERTY(bool rendererAlwaysFullScreen MEMBER rendererAlwaysFullScreen CONSTANT) 18 | Q_PROPERTY(bool isRunningWayland MEMBER isRunningWayland CONSTANT) 19 | Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT) 20 | Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT) 21 | Q_PROPERTY(QString friendlyNativeArchName MEMBER friendlyNativeArchName CONSTANT) 22 | Q_PROPERTY(bool hasDesktopEnvironment MEMBER hasDesktopEnvironment CONSTANT) 23 | Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT) 24 | Q_PROPERTY(bool hasDiscordIntegration MEMBER hasDiscordIntegration CONSTANT) 25 | Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged) 26 | Q_PROPERTY(QSize maximumResolution MEMBER maximumResolution CONSTANT) 27 | Q_PROPERTY(QString versionString MEMBER versionString CONSTANT) 28 | Q_PROPERTY(bool supportsHdr MEMBER supportsHdr CONSTANT) 29 | Q_PROPERTY(bool usesMaterial3Theme MEMBER usesMaterial3Theme CONSTANT) 30 | 31 | Q_INVOKABLE void refreshDisplays(); 32 | Q_INVOKABLE QRect getNativeResolution(int displayIndex); 33 | Q_INVOKABLE QRect getSafeAreaResolution(int displayIndex); 34 | Q_INVOKABLE int getRefreshRate(int displayIndex); 35 | 36 | signals: 37 | void unmappedGamepadsChanged(); 38 | 39 | private: 40 | void querySdlVideoInfo(); 41 | void querySdlVideoInfoInternal(); 42 | void refreshDisplaysInternal(); 43 | 44 | bool hasHardwareAcceleration; 45 | bool rendererAlwaysFullScreen; 46 | bool isRunningWayland; 47 | bool isRunningXWayland; 48 | bool isWow64; 49 | QString friendlyNativeArchName; 50 | bool hasDesktopEnvironment; 51 | bool hasBrowser; 52 | bool hasDiscordIntegration; 53 | QString unmappedGamepads; 54 | QSize maximumResolution; 55 | QList monitorNativeResolutions; 56 | QList monitorSafeAreaResolutions; 57 | QList monitorRefreshRates; 58 | QString versionString; 59 | bool supportsHdr; 60 | bool usesMaterial3Theme; 61 | }; 62 | 63 | -------------------------------------------------------------------------------- /app/cli/commandlineparser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "settings/streamingpreferences.h" 4 | 5 | #include 6 | #include 7 | 8 | class GlobalCommandLineParser 9 | { 10 | public: 11 | enum ParseResult { 12 | NormalStartRequested, 13 | StreamRequested, 14 | QuitRequested, 15 | PairRequested, 16 | ListRequested, 17 | }; 18 | 19 | GlobalCommandLineParser(); 20 | virtual ~GlobalCommandLineParser(); 21 | 22 | ParseResult parse(const QStringList &args); 23 | 24 | }; 25 | 26 | class QuitCommandLineParser 27 | { 28 | public: 29 | QuitCommandLineParser(); 30 | virtual ~QuitCommandLineParser(); 31 | 32 | void parse(const QStringList &args); 33 | 34 | QString getHost() const; 35 | 36 | private: 37 | QString m_Host; 38 | }; 39 | 40 | class PairCommandLineParser 41 | { 42 | public: 43 | PairCommandLineParser(); 44 | virtual ~PairCommandLineParser(); 45 | 46 | void parse(const QStringList &args); 47 | 48 | QString getHost() const; 49 | QString getPredefinedPin() const; 50 | 51 | private: 52 | QString m_Host; 53 | QString m_PredefinedPin; 54 | }; 55 | 56 | class StreamCommandLineParser 57 | { 58 | public: 59 | StreamCommandLineParser(); 60 | virtual ~StreamCommandLineParser(); 61 | 62 | void parse(const QStringList &args, StreamingPreferences *preferences); 63 | 64 | QString getHost() const; 65 | QString getAppName() const; 66 | 67 | private: 68 | QString m_Host; 69 | QString m_AppName; 70 | QMap m_WindowModeMap; 71 | QMap m_AudioConfigMap; 72 | QMap m_VideoCodecMap; 73 | QMap m_VideoDecoderMap; 74 | QMap m_CaptureSysKeysModeMap; 75 | }; 76 | 77 | class ListCommandLineParser 78 | { 79 | public: 80 | ListCommandLineParser(); 81 | virtual ~ListCommandLineParser(); 82 | 83 | void parse(const QStringList &args); 84 | 85 | QString getHost() const; 86 | bool isPrintCSV() const; 87 | bool isVerbose() const; 88 | 89 | private: 90 | QString m_Host; 91 | bool m_PrintCSV; 92 | bool m_Verbose; 93 | }; 94 | -------------------------------------------------------------------------------- /app/cli/listapps.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "commandlineparser.h" 4 | 5 | #include 6 | #include 7 | 8 | class ComputerManager; 9 | class NvComputer; 10 | 11 | namespace CliListApps 12 | { 13 | 14 | class LauncherPrivate; 15 | 16 | class Launcher : public QObject 17 | { 18 | Q_OBJECT 19 | Q_DECLARE_PRIVATE_D(m_DPtr, Launcher) 20 | 21 | public: 22 | explicit Launcher(QString computer, ListCommandLineParser arguments, QObject *parent = nullptr); 23 | ~Launcher(); 24 | 25 | Q_INVOKABLE void execute(ComputerManager *manager); 26 | Q_INVOKABLE bool isExecuted() const; 27 | 28 | private slots: 29 | void onComputerFound(NvComputer *computer); 30 | void onComputerUpdated(NvComputer *computer); 31 | void onComputerSeekTimeout(); 32 | 33 | private: 34 | QScopedPointer m_DPtr; 35 | ListCommandLineParser m_Arguments; 36 | }; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/cli/pair.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class ComputerManager; 7 | class NvComputer; 8 | class Session; 9 | class StreamingPreferences; 10 | 11 | namespace CliPair 12 | { 13 | 14 | class Event; 15 | class LauncherPrivate; 16 | 17 | class Launcher : public QObject 18 | { 19 | Q_OBJECT 20 | Q_DECLARE_PRIVATE_D(m_DPtr, Launcher) 21 | 22 | public: 23 | explicit Launcher(QString computer, QString predefinedPin, 24 | QObject *parent = nullptr); 25 | ~Launcher(); 26 | Q_INVOKABLE void execute(ComputerManager *manager); 27 | Q_INVOKABLE bool isExecuted() const; 28 | 29 | signals: 30 | void searchingComputer(); 31 | void pairing(QString pcName, QString pin); 32 | void failed(QString text); 33 | void success(); 34 | 35 | private slots: 36 | void onComputerFound(NvComputer *computer); 37 | void onPairingCompleted(NvComputer *computer, QString error); 38 | void onTimeout(); 39 | 40 | private: 41 | QScopedPointer m_DPtr; 42 | }; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/cli/quitstream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class ComputerManager; 7 | class NvComputer; 8 | 9 | namespace CliQuitStream 10 | { 11 | 12 | class LauncherPrivate; 13 | 14 | class Launcher : public QObject 15 | { 16 | Q_OBJECT 17 | Q_DECLARE_PRIVATE_D(m_DPtr, Launcher) 18 | 19 | public: 20 | explicit Launcher(QString computer, QObject *parent = nullptr); 21 | ~Launcher(); 22 | 23 | Q_INVOKABLE void execute(ComputerManager *manager); 24 | Q_INVOKABLE bool isExecuted() const; 25 | 26 | signals: 27 | void searchingComputer(); 28 | void quittingApp(); 29 | void failed(QString text); 30 | 31 | private slots: 32 | void onComputerFound(NvComputer *computer); 33 | void onComputerSeekTimeout(); 34 | void onQuitAppCompleted(QVariant error); 35 | 36 | private: 37 | QScopedPointer m_DPtr; 38 | }; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/cli/startstream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class ComputerManager; 7 | class NvComputer; 8 | class Session; 9 | class StreamingPreferences; 10 | 11 | namespace CliStartStream 12 | { 13 | 14 | class Event; 15 | class LauncherPrivate; 16 | 17 | class Launcher : public QObject 18 | { 19 | Q_OBJECT 20 | Q_DECLARE_PRIVATE_D(m_DPtr, Launcher) 21 | 22 | public: 23 | explicit Launcher(QString computer, QString app, 24 | StreamingPreferences* preferences, 25 | QObject *parent = nullptr); 26 | ~Launcher(); 27 | Q_INVOKABLE void execute(ComputerManager *manager); 28 | Q_INVOKABLE void quitRunningApp(); 29 | Q_INVOKABLE bool isExecuted() const; 30 | 31 | signals: 32 | void searchingComputer(); 33 | void searchingApp(); 34 | void sessionCreated(QString appName, Session *session); 35 | void failed(QString text); 36 | void appQuitRequired(QString appName); 37 | 38 | private slots: 39 | void onComputerFound(NvComputer *computer); 40 | void onComputerUpdated(NvComputer *computer); 41 | void onTimeout(); 42 | void onQuitAppCompleted(QVariant error); 43 | 44 | private: 45 | QScopedPointer m_DPtr; 46 | }; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/deploy/linux/appimage/0001-Vulkan-Don-t-try-to-reuse-old-swapchain.patch: -------------------------------------------------------------------------------- 1 | From 78261eb6e2fe728fa5d3843b5b8962b1ca559b7e Mon Sep 17 00:00:00 2001 2 | From: David Rosca 3 | Date: Sat, 16 Dec 2023 14:52:01 +0100 4 | Subject: [PATCH] Vulkan: Don't try to reuse old swapchain 5 | 6 | --- 7 | src/vulkan/swapchain.c | 12 ++++++++++-- 8 | 1 file changed, 10 insertions(+), 2 deletions(-) 9 | 10 | diff --git a/src/vulkan/swapchain.c b/src/vulkan/swapchain.c 11 | index 0646e57c..c0b07ce4 100644 12 | --- a/src/vulkan/swapchain.c 13 | +++ b/src/vulkan/swapchain.c 14 | @@ -612,13 +612,21 @@ static bool vk_sw_recreate(pl_swapchain sw, int w, int h) 15 | } 16 | #endif 17 | 18 | + { 19 | + pl_gpu_flush(gpu); 20 | + vk_wait_idle(vk); 21 | + for (int i = 0; i < vk->pool_graphics->num_queues; i++) 22 | + vk->QueueWaitIdle(vk->pool_graphics->queues[i]); 23 | + vk->DestroySwapchainKHR(vk->dev, p->swapchain, PL_VK_ALLOC); 24 | + } 25 | + 26 | // Calling `vkCreateSwapchainKHR` puts sinfo.oldSwapchain into a retired 27 | // state whether the call succeeds or not, so we always need to garbage 28 | // collect it afterwards - asynchronously as it may still be in use 29 | - sinfo.oldSwapchain = p->swapchain; 30 | + /* sinfo.oldSwapchain = p->swapchain; */ 31 | p->swapchain = VK_NULL_HANDLE; 32 | VkResult res = vk->CreateSwapchainKHR(vk->dev, &sinfo, PL_VK_ALLOC, &p->swapchain); 33 | - vk_dev_callback(vk, VK_CB_FUNC(destroy_swapchain), vk, vk_wrap_handle(sinfo.oldSwapchain)); 34 | + /* vk_dev_callback(vk, VK_CB_FUNC(destroy_swapchain), vk, vk_wrap_handle(sinfo.oldSwapchain)); */ 35 | PL_VK_ASSERT(res, "vk->CreateSwapchainKHR(...)"); 36 | 37 | // Get the new swapchain images 38 | -- 39 | 2.43.0 40 | 41 | -------------------------------------------------------------------------------- /app/deploy/linux/com.moonlight_stream.Moonlight.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Moonlight 3 | Comment=Stream games and other applications from another PC running Sunshine or GeForce Experience 4 | Exec=moonlight 5 | Icon=moonlight 6 | Terminal=false 7 | Type=Application 8 | Categories=Qt;Game;Network;RemoteAccess; 9 | Keywords=nvidia;gamestream;stream;sunshine;remote play; 10 | -------------------------------------------------------------------------------- /app/deploy/steamlink/moonlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/deploy/steamlink/moonlight.png -------------------------------------------------------------------------------- /app/deploy/steamlink/moonlight.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The default HOME is not persistent, so override 4 | # it to a path on the onboard flash. Otherwise our 5 | # pairing data will be lost each reboot. 6 | HOME=/usr/local/moonlight 7 | 8 | # Enable Steam Controller detection in SDL 9 | # https://github.com/moonlight-stream/moonlight-qt/issues/697 10 | export SDL_ENABLE_STEAM_CONTROLLERS=1 11 | 12 | # Renice PE_Single_CPU which seems to host A/V stuff 13 | renice -10 -p $(pidof PE_Single_CPU) 14 | 15 | # Renice Moonlight itself to avoid preemption by background tasks 16 | # Write output to a logfile in /tmp 17 | exec nice -n -10 ./bin/moonlight > /tmp/moonlight.log 2>&1 18 | -------------------------------------------------------------------------------- /app/deploy/steamlink/toc.txt: -------------------------------------------------------------------------------- 1 | name=Moonlight 2 | icon=moonlight.png 3 | run=moonlight.sh 4 | -------------------------------------------------------------------------------- /app/gui/AutoResizingComboBox.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.9 2 | import QtQuick.Controls 2.2 3 | 4 | import SdlGamepadKeyNavigation 1.0 5 | import SystemProperties 1.0 6 | 7 | // https://stackoverflow.com/questions/45029968/how-do-i-set-the-combobox-width-to-fit-the-largest-item 8 | ComboBox { 9 | property int textWidth 10 | property int desiredWidth : leftPadding + textWidth + indicator.width + rightPadding 11 | property int maximumWidth : parent.width 12 | 13 | implicitWidth: desiredWidth < maximumWidth ? desiredWidth : maximumWidth 14 | 15 | TextMetrics { 16 | id: popupMetrics 17 | } 18 | 19 | TextMetrics { 20 | id: textMetrics 21 | } 22 | 23 | function recalculateWidth() { 24 | textMetrics.font = font 25 | popupMetrics.font = popup.font 26 | textWidth = 0 27 | for (var i = 0; i < count; i++){ 28 | textMetrics.text = textAt(i) 29 | popupMetrics.text = textAt(i) 30 | textWidth = Math.max(textMetrics.width, textWidth) 31 | textWidth = Math.max(popupMetrics.width, textWidth) 32 | } 33 | } 34 | 35 | // We call this every time the options change (and init) 36 | // so we can adjust the combo box width here too 37 | onActivated: recalculateWidth() 38 | 39 | popup.onAboutToShow: { 40 | // Switch to normal navigation for combo boxes 41 | SdlGamepadKeyNavigation.setUiNavMode(false) 42 | 43 | // Override the popup color to improve contrast with the overridden 44 | // Material 2 background color set in main.qml. 45 | if (SystemProperties.usesMaterial3Theme) { 46 | popup.background.color = "#424242" 47 | } 48 | } 49 | 50 | popup.onAboutToHide: { 51 | SdlGamepadKeyNavigation.setUiNavMode(true) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/gui/CenteredGridView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.9 2 | import QtQuick.Controls 2.2 3 | 4 | GridView { 5 | // Detect Qt 5.11 or earlier using presence of synchronousDrag. 6 | // Prior to 5.12, the leftMargin and rightMargin values did not work. 7 | property bool hasBrokenMargins: this.synchronousDrag === undefined 8 | 9 | property int minMargin: 10 10 | property real availableWidth: (parent.width - 2 * minMargin) 11 | property int itemsPerRow: availableWidth / cellWidth 12 | property real horizontalMargin: itemsPerRow < count && availableWidth >= cellWidth ? 13 | (availableWidth % cellWidth) / 2 : minMargin 14 | 15 | function updateMargins() { 16 | leftMargin = horizontalMargin 17 | rightMargin = horizontalMargin 18 | 19 | if (hasBrokenMargins) { 20 | anchors.leftMargin = leftMargin 21 | anchors.rightMargin = rightMargin 22 | } 23 | } 24 | 25 | onHorizontalMarginChanged: { 26 | updateMargins() 27 | } 28 | 29 | Component.onCompleted: { 30 | if (hasBrokenMargins) { 31 | // This will cause an anchor conflict with the parent StackView 32 | // which breaks animation, but otherwise the grid will not be 33 | // centered in the window. 34 | anchors.fill = parent 35 | anchors.topMargin = topMargin 36 | anchors.bottomMargin = bottomMargin 37 | } 38 | 39 | updateMargins() 40 | } 41 | 42 | boundsBehavior: Flickable.OvershootBounds 43 | } 44 | -------------------------------------------------------------------------------- /app/gui/CliPair.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.2 3 | 4 | import ComputerManager 1.0 5 | 6 | Item { 7 | function onSearchingComputer() { 8 | stageLabel.text = qsTr("Establishing connection to PC...") 9 | } 10 | 11 | function onPairing(pcName, pin) { 12 | stageLabel.text = qsTr("Pairing... Please enter '%1' on %2.").arg(pin).arg(pcName) 13 | } 14 | 15 | function onFailed(message) { 16 | stageIndicator.visible = false 17 | errorDialog.text = message 18 | errorDialog.open() 19 | } 20 | 21 | function onSuccess(appName) { 22 | stageIndicator.visible = false 23 | pairCompleteDialog.open() 24 | } 25 | 26 | // Allow user to back out of pairing 27 | Keys.onEscapePressed: { 28 | Qt.quit() 29 | } 30 | Keys.onBackPressed: { 31 | Qt.quit() 32 | } 33 | Keys.onCancelPressed: { 34 | Qt.quit() 35 | } 36 | 37 | StackView.onActivated: { 38 | if (!launcher.isExecuted()) { 39 | toolBar.visible = false 40 | 41 | launcher.searchingComputer.connect(onSearchingComputer) 42 | launcher.pairing.connect(onPairing) 43 | launcher.failed.connect(onFailed) 44 | launcher.success.connect(onSuccess) 45 | launcher.execute(ComputerManager) 46 | } 47 | } 48 | 49 | Row { 50 | anchors.centerIn: parent 51 | spacing: 5 52 | id: stageIndicator 53 | 54 | BusyIndicator { 55 | id: stageSpinner 56 | } 57 | 58 | Label { 59 | id: stageLabel 60 | height: stageSpinner.height 61 | font.pointSize: 20 62 | verticalAlignment: Text.AlignVCenter 63 | 64 | wrapMode: Text.Wrap 65 | } 66 | } 67 | 68 | ErrorMessageDialog { 69 | id: errorDialog 70 | 71 | onClosed: { 72 | Qt.quit(); 73 | } 74 | } 75 | 76 | NavigableMessageDialog { 77 | id: pairCompleteDialog 78 | closePolicy: Popup.CloseOnEscape 79 | 80 | text:qsTr("Pairing completed successfully") 81 | standardButtons: Dialog.Ok 82 | onClosed: { 83 | Qt.quit() 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/gui/CliQuitStreamSegue.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.2 3 | 4 | import ComputerManager 1.0 5 | import Session 1.0 6 | 7 | Item { 8 | function onSearchingComputer() { 9 | stageLabel.text = qsTr("Establishing connection to PC...") 10 | } 11 | 12 | function onQuittingApp() { 13 | stageLabel.text = qsTr("Quitting app...") 14 | } 15 | 16 | function onFailure(message) { 17 | errorDialog.text = message 18 | errorDialog.open() 19 | } 20 | 21 | StackView.onActivated: { 22 | if (!launcher.isExecuted()) { 23 | toolBar.visible = false 24 | launcher.searchingComputer.connect(onSearchingComputer) 25 | launcher.quittingApp.connect(onQuittingApp) 26 | launcher.failed.connect(onFailure) 27 | launcher.execute(ComputerManager) 28 | } 29 | } 30 | 31 | Row { 32 | anchors.centerIn: parent 33 | spacing: 5 34 | 35 | BusyIndicator { 36 | id: stageSpinner 37 | } 38 | 39 | Label { 40 | id: stageLabel 41 | height: stageSpinner.height 42 | text: stageText 43 | font.pointSize: 20 44 | verticalAlignment: Text.AlignVCenter 45 | 46 | wrapMode: Text.Wrap 47 | } 48 | } 49 | 50 | ErrorMessageDialog { 51 | id: errorDialog 52 | 53 | onClosed: { 54 | Qt.quit() 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/gui/CliStartStreamSegue.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.2 3 | 4 | import ComputerManager 1.0 5 | 6 | Item { 7 | function onSearchingComputer() { 8 | stageLabel.text = qsTr("Establishing connection to PC...") 9 | } 10 | 11 | function onSearchingApp() { 12 | stageLabel.text = qsTr("Loading app list...") 13 | } 14 | 15 | function onSessionCreated(appName, session) { 16 | var component = Qt.createComponent("StreamSegue.qml") 17 | var segue = component.createObject(stackView, { 18 | "appName": appName, 19 | "session": session, 20 | "quitAfter": true 21 | }) 22 | stackView.push(segue) 23 | } 24 | 25 | function onLaunchFailed(message) { 26 | errorDialog.text = message 27 | errorDialog.open() 28 | console.error(message) 29 | } 30 | 31 | function onAppQuitRequired(appName) { 32 | quitAppDialog.appName = appName 33 | quitAppDialog.open() 34 | } 35 | 36 | StackView.onActivated: { 37 | if (!launcher.isExecuted()) { 38 | toolBar.visible = false 39 | 40 | launcher.searchingComputer.connect(onSearchingComputer) 41 | launcher.searchingApp.connect(onSearchingApp) 42 | launcher.sessionCreated.connect(onSessionCreated) 43 | launcher.failed.connect(onLaunchFailed) 44 | launcher.appQuitRequired.connect(onAppQuitRequired) 45 | launcher.execute(ComputerManager) 46 | } 47 | } 48 | 49 | Row { 50 | anchors.centerIn: parent 51 | spacing: 5 52 | 53 | BusyIndicator { 54 | id: stageSpinner 55 | } 56 | 57 | Label { 58 | id: stageLabel 59 | height: stageSpinner.height 60 | font.pointSize: 20 61 | verticalAlignment: Text.AlignVCenter 62 | 63 | wrapMode: Text.Wrap 64 | } 65 | } 66 | 67 | ErrorMessageDialog { 68 | id: errorDialog 69 | 70 | onClosed: { 71 | Qt.quit(); 72 | } 73 | } 74 | 75 | NavigableMessageDialog { 76 | id: quitAppDialog 77 | text:qsTr("Are you sure you want to quit %1? Any unsaved progress will be lost.").arg(appName) 78 | standardButtons: Dialog.Yes | Dialog.No 79 | property string appName : "" 80 | 81 | function quitApp() { 82 | var component = Qt.createComponent("QuitSegue.qml") 83 | var params = {"appName": appName, "quitRunningAppFn": function() { launcher.quitRunningApp() }} 84 | stackView.push(component.createObject(stackView, params)) 85 | } 86 | 87 | onAccepted: quitApp() 88 | onRejected: Qt.quit() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/gui/ErrorMessageDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.2 3 | 4 | import SystemProperties 1.0 5 | 6 | NavigableMessageDialog { 7 | standardButtons: Dialog.Ok | (SystemProperties.hasBrowser ? Dialog.Help : 0) 8 | } 9 | -------------------------------------------------------------------------------- /app/gui/GamepadMapper.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | objectName: qsTr("Gamepad Mapping") 5 | } 6 | -------------------------------------------------------------------------------- /app/gui/NavigableDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.2 3 | 4 | Dialog { 5 | // We should use Overlay.overlay here but that's not available in Qt 5.9 :( 6 | parent: ApplicationWindow.contentItem 7 | 8 | x: Math.round((parent.width - width) / 2) 9 | y: Math.round((parent.height - height) / 2) 10 | 11 | onAboutToHide: { 12 | // We must force focus back to the last item for platforms without 13 | // support for more than one active window like Steam Link. If 14 | // we don't, gamepad and keyboard navigation will break after a 15 | // dialog appears. 16 | stackView.forceActiveFocus() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/gui/NavigableItemDelegate.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.2 3 | 4 | ItemDelegate { 5 | property GridView grid 6 | 7 | highlighted: grid.activeFocus && grid.currentItem === this 8 | 9 | Keys.onLeftPressed: { 10 | grid.moveCurrentIndexLeft() 11 | } 12 | Keys.onRightPressed: { 13 | grid.moveCurrentIndexRight() 14 | } 15 | Keys.onDownPressed: { 16 | grid.moveCurrentIndexDown() 17 | } 18 | Keys.onUpPressed: { 19 | grid.moveCurrentIndexUp() 20 | } 21 | Keys.onReturnPressed: { 22 | clicked() 23 | } 24 | Keys.onEnterPressed: { 25 | clicked() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/gui/NavigableMenu.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.2 3 | 4 | Menu { 5 | onOpened: { 6 | // Give focus to the first visible and enabled menu item 7 | for (var i = 0; i < count; i++) { 8 | var item = itemAt(i) 9 | if (item.visible && item.enabled) { 10 | item.forceActiveFocus(Qt.TabFocusReason) 11 | break 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/gui/NavigableMenuItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.2 3 | 4 | MenuItem { 5 | // Qt 5.10 has a menu property, but we need to support 5.9 6 | // so we must make our own. 7 | property Menu parentMenu 8 | 9 | // Ensure focus can't be given to an invisible item 10 | enabled: visible 11 | height: visible ? implicitHeight : 0 12 | focusPolicy: visible ? Qt.TabFocus : Qt.NoFocus 13 | 14 | onTriggered: { 15 | // We must close the context menu first or 16 | // it can steal focus from any dialogs that 17 | // onTriggered may spawn. 18 | parentMenu.close() 19 | } 20 | 21 | Keys.onReturnPressed: { 22 | triggered() 23 | } 24 | 25 | Keys.onEnterPressed: { 26 | triggered() 27 | } 28 | 29 | Keys.onEscapePressed: { 30 | parentMenu.close() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/gui/NavigableMessageDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.2 3 | import QtQuick.Layouts 1.2 4 | 5 | NavigableDialog { 6 | id: dialog 7 | 8 | property alias text: dialogLabel.dialogText 9 | property alias showSpinner: dialogSpinner.visible 10 | property alias imageSrc: dialogImage.source 11 | 12 | property string helpText 13 | property string helpUrl : "https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting" 14 | property string helpTextSeparator : " " 15 | 16 | onOpened: { 17 | // Force keyboard focus on the label so keyboard navigation works 18 | dialogLabel.forceActiveFocus() 19 | } 20 | 21 | RowLayout { 22 | spacing: 10 23 | 24 | BusyIndicator { 25 | id: dialogSpinner 26 | visible: false 27 | } 28 | 29 | Image { 30 | id: dialogImage 31 | source: (standardButtons & Dialog.Yes) ? 32 | "qrc:/res/baseline-help_outline-24px.svg" : 33 | "qrc:/res/baseline-error_outline-24px.svg" 34 | sourceSize { 35 | // The icon should be square so use the height as the width too 36 | width: 50 37 | height: 50 38 | } 39 | visible: !showSpinner 40 | } 41 | 42 | Label { 43 | property string dialogText 44 | 45 | id: dialogLabel 46 | text: dialogText + ((helpText && (standardButtons & Dialog.Help)) ? (helpTextSeparator + helpText) : "") 47 | wrapMode: Text.Wrap 48 | elide: Label.ElideRight 49 | 50 | // Cap the width so the dialog doesn't grow horizontally forever. This 51 | // will cause word wrap to kick in. 52 | Layout.maximumWidth: 400 53 | Layout.maximumHeight: 400 54 | 55 | Keys.onReturnPressed: { 56 | accept() 57 | } 58 | 59 | Keys.onEnterPressed: { 60 | accept() 61 | } 62 | 63 | Keys.onEscapePressed: { 64 | reject() 65 | } 66 | } 67 | } 68 | 69 | footer: DialogButtonBox { 70 | id: dialogButtonBox 71 | standardButtons: dialog.standardButtons 72 | 73 | onHelpRequested: { 74 | Qt.openUrlExternally(helpUrl) 75 | close() 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/gui/NavigableToolButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.2 3 | import QtQuick.Layouts 1.3 4 | 5 | ToolButton { 6 | property string iconSource 7 | 8 | activeFocusOnTab: true 9 | 10 | // FIXME: We're using an Image here rather than icon.source because 11 | // icons don't work on Qt 5.9 LTS. 12 | Image { 13 | id: image 14 | source: iconSource 15 | anchors.centerIn: parent.background 16 | sourceSize { 17 | width: parent.background.width * 1.10 18 | height: parent.background.height * 1.10 19 | } 20 | } 21 | 22 | // This determines the size of the Material highlight. We increase it 23 | // from the default because we use larger than normal icons for TV readability. 24 | Layout.preferredHeight: parent.height 25 | 26 | Keys.onReturnPressed: { 27 | clicked() 28 | } 29 | 30 | Keys.onEnterPressed: { 31 | clicked() 32 | } 33 | 34 | Keys.onRightPressed: { 35 | nextItemInFocusChain(true).forceActiveFocus(Qt.TabFocus) 36 | } 37 | 38 | Keys.onLeftPressed: { 39 | nextItemInFocusChain(false).forceActiveFocus(Qt.TabFocus) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/gui/QuitSegue.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.2 3 | 4 | import ComputerManager 1.0 5 | import Session 1.0 6 | 7 | Item { 8 | property string appName 9 | property var quitRunningAppFn 10 | property Session nextSession : null 11 | property string nextAppName : "" 12 | 13 | property string stageText : qsTr("Quitting %1...").arg(appName) 14 | 15 | function quitAppCompleted(error) 16 | { 17 | // Display a failed dialog if we got an error 18 | if (error !== undefined) { 19 | errorDialog.text = error 20 | errorDialog.open() 21 | console.error(error) 22 | } 23 | 24 | // If we're supposed to launch another game after this, do so now 25 | if (error === undefined && nextSession !== null) { 26 | var component = Qt.createComponent("StreamSegue.qml") 27 | var segue = component.createObject(stackView, {"appName": nextAppName, "session": nextSession}) 28 | stackView.replace(segue) 29 | } 30 | else { 31 | // Exit this view 32 | stackView.pop() 33 | } 34 | } 35 | 36 | StackView.onActivated: { 37 | // Hide the toolbar before we start loading 38 | toolBar.visible = false 39 | 40 | // Connect the quit completion signal 41 | ComputerManager.quitAppCompleted.connect(quitAppCompleted) 42 | 43 | // Start the quit operation if requested 44 | if (quitRunningAppFn) { 45 | quitRunningAppFn() 46 | } 47 | } 48 | 49 | StackView.onDeactivating: { 50 | // Show the toolbar again 51 | toolBar.visible = true 52 | 53 | // Disconnect the signal 54 | ComputerManager.quitAppCompleted.disconnect(quitAppCompleted) 55 | } 56 | 57 | Row { 58 | anchors.centerIn: parent 59 | spacing: 5 60 | 61 | BusyIndicator { 62 | id: stageSpinner 63 | } 64 | 65 | Label { 66 | id: stageLabel 67 | height: stageSpinner.height 68 | text: stageText 69 | font.pointSize: 20 70 | verticalAlignment: Text.AlignVCenter 71 | 72 | wrapMode: Text.Wrap 73 | } 74 | } 75 | 76 | ErrorMessageDialog { 77 | id: errorDialog 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/gui/appmodel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "backend/boxartmanager.h" 4 | #include "backend/computermanager.h" 5 | #include "streaming/session.h" 6 | 7 | #include 8 | 9 | class AppModel : public QAbstractListModel 10 | { 11 | Q_OBJECT 12 | 13 | enum Roles 14 | { 15 | NameRole = Qt::UserRole, 16 | RunningRole, 17 | BoxArtRole, 18 | HiddenRole, 19 | AppIdRole, 20 | DirectLaunchRole, 21 | AppCollectorGameRole, 22 | }; 23 | 24 | public: 25 | explicit AppModel(QObject *parent = nullptr); 26 | 27 | // Must be called before any QAbstractListModel functions 28 | Q_INVOKABLE void initialize(ComputerManager* computerManager, int computerIndex, bool showHiddenGames); 29 | 30 | Q_INVOKABLE Session* createSessionForApp(int appIndex); 31 | 32 | Q_INVOKABLE int getDirectLaunchAppIndex(); 33 | 34 | Q_INVOKABLE int getRunningAppId(); 35 | 36 | Q_INVOKABLE QString getRunningAppName(); 37 | 38 | Q_INVOKABLE void quitRunningApp(); 39 | 40 | Q_INVOKABLE void setAppHidden(int appIndex, bool hidden); 41 | 42 | Q_INVOKABLE void setAppDirectLaunch(int appIndex, bool directLaunch); 43 | 44 | QVariant data(const QModelIndex &index, int role) const override; 45 | 46 | int rowCount(const QModelIndex &parent) const override; 47 | 48 | virtual QHash roleNames() const override; 49 | 50 | private slots: 51 | void handleComputerStateChanged(NvComputer* computer); 52 | 53 | void handleBoxArtLoaded(NvComputer* computer, NvApp app, QUrl image); 54 | 55 | signals: 56 | void computerLost(); 57 | 58 | private: 59 | void updateAppList(QVector newList); 60 | 61 | QVector getVisibleApps(const QVector& appList); 62 | 63 | bool isAppCurrentlyVisible(const NvApp& app); 64 | 65 | NvComputer* m_Computer; 66 | BoxArtManager m_BoxArtManager; 67 | ComputerManager* m_ComputerManager; 68 | QVector m_VisibleApps, m_AllApps; 69 | int m_CurrentGameId; 70 | bool m_ShowHiddenGames; 71 | }; 72 | -------------------------------------------------------------------------------- /app/gui/computermodel.h: -------------------------------------------------------------------------------- 1 | #include "backend/computermanager.h" 2 | #include "streaming/session.h" 3 | 4 | #include 5 | 6 | class ComputerModel : public QAbstractListModel 7 | { 8 | Q_OBJECT 9 | 10 | enum Roles 11 | { 12 | NameRole = Qt::UserRole, 13 | OnlineRole, 14 | PairedRole, 15 | BusyRole, 16 | WakeableRole, 17 | StatusUnknownRole, 18 | ServerSupportedRole, 19 | DetailsRole 20 | }; 21 | 22 | public: 23 | explicit ComputerModel(QObject* object = nullptr); 24 | 25 | // Must be called before any QAbstractListModel functions 26 | Q_INVOKABLE void initialize(ComputerManager* computerManager); 27 | 28 | QVariant data(const QModelIndex &index, int role) const override; 29 | 30 | int rowCount(const QModelIndex &parent) const override; 31 | 32 | virtual QHash roleNames() const override; 33 | 34 | Q_INVOKABLE void deleteComputer(int computerIndex); 35 | 36 | Q_INVOKABLE QString generatePinString(); 37 | 38 | Q_INVOKABLE void pairComputer(int computerIndex, QString pin); 39 | 40 | Q_INVOKABLE void testConnectionForComputer(int computerIndex); 41 | 42 | Q_INVOKABLE void wakeComputer(int computerIndex); 43 | 44 | Q_INVOKABLE void renameComputer(int computerIndex, QString name); 45 | 46 | Q_INVOKABLE Session* createSessionForCurrentGame(int computerIndex); 47 | 48 | signals: 49 | void pairingCompleted(QVariant error); 50 | void connectionTestCompleted(int result, QString blockedPorts); 51 | 52 | private slots: 53 | void handleComputerStateChanged(NvComputer* computer); 54 | 55 | void handlePairingCompleted(NvComputer* computer, QString error); 56 | 57 | private: 58 | QVector m_Computers; 59 | ComputerManager* m_ComputerManager; 60 | }; 61 | -------------------------------------------------------------------------------- /app/gui/sdlgamepadkeynavigation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "SDL_compat.h" 7 | 8 | #include "settings/streamingpreferences.h" 9 | 10 | class SdlGamepadKeyNavigation : public QObject 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | SdlGamepadKeyNavigation(StreamingPreferences* prefs); 16 | 17 | ~SdlGamepadKeyNavigation(); 18 | 19 | Q_INVOKABLE void enable(); 20 | 21 | Q_INVOKABLE void disable(); 22 | 23 | Q_INVOKABLE void notifyWindowFocus(bool hasFocus); 24 | 25 | Q_INVOKABLE void setUiNavMode(bool settingsMode); 26 | 27 | Q_INVOKABLE int getConnectedGamepads(); 28 | 29 | private: 30 | void sendKey(QEvent::Type type, Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier); 31 | 32 | void updateTimerState(); 33 | 34 | private slots: 35 | void onPollingTimerFired(); 36 | 37 | private: 38 | StreamingPreferences* m_Prefs; 39 | QTimer* m_PollingTimer; 40 | QList m_Gamepads; 41 | bool m_Enabled; 42 | bool m_UiNavMode; 43 | bool m_FirstPoll; 44 | bool m_HasFocus; 45 | Uint32 m_LastAxisNavigationEventTime; 46 | }; 47 | -------------------------------------------------------------------------------- /app/languages/qml_bg.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_bg.qm -------------------------------------------------------------------------------- /app/languages/qml_ckb.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_ckb.qm -------------------------------------------------------------------------------- /app/languages/qml_cs.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_cs.qm -------------------------------------------------------------------------------- /app/languages/qml_de.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_de.qm -------------------------------------------------------------------------------- /app/languages/qml_el.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_el.qm -------------------------------------------------------------------------------- /app/languages/qml_eo.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_eo.qm -------------------------------------------------------------------------------- /app/languages/qml_es.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_es.qm -------------------------------------------------------------------------------- /app/languages/qml_et.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_et.qm -------------------------------------------------------------------------------- /app/languages/qml_fr.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_fr.qm -------------------------------------------------------------------------------- /app/languages/qml_he.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_he.qm -------------------------------------------------------------------------------- /app/languages/qml_hi.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_hi.qm -------------------------------------------------------------------------------- /app/languages/qml_hu.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_hu.qm -------------------------------------------------------------------------------- /app/languages/qml_it.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_it.qm -------------------------------------------------------------------------------- /app/languages/qml_ja.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_ja.qm -------------------------------------------------------------------------------- /app/languages/qml_ko.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_ko.qm -------------------------------------------------------------------------------- /app/languages/qml_lt.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_lt.qm -------------------------------------------------------------------------------- /app/languages/qml_nb_NO.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_nb_NO.qm -------------------------------------------------------------------------------- /app/languages/qml_nl.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_nl.qm -------------------------------------------------------------------------------- /app/languages/qml_pl.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_pl.qm -------------------------------------------------------------------------------- /app/languages/qml_pt.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_pt.qm -------------------------------------------------------------------------------- /app/languages/qml_pt_BR.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_pt_BR.qm -------------------------------------------------------------------------------- /app/languages/qml_ru.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_ru.qm -------------------------------------------------------------------------------- /app/languages/qml_sv.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_sv.qm -------------------------------------------------------------------------------- /app/languages/qml_ta.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_ta.qm -------------------------------------------------------------------------------- /app/languages/qml_th.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_th.qm -------------------------------------------------------------------------------- /app/languages/qml_tr.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_tr.qm -------------------------------------------------------------------------------- /app/languages/qml_uk.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_uk.qm -------------------------------------------------------------------------------- /app/languages/qml_vi.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_vi.qm -------------------------------------------------------------------------------- /app/languages/qml_zh_CN.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_zh_CN.qm -------------------------------------------------------------------------------- /app/languages/qml_zh_TW.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/languages/qml_zh_TW.qm -------------------------------------------------------------------------------- /app/moonlight.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/moonlight.icns -------------------------------------------------------------------------------- /app/moonlight.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/moonlight.ico -------------------------------------------------------------------------------- /app/moonlight_wix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/moonlight_wix.png -------------------------------------------------------------------------------- /app/path.cpp: -------------------------------------------------------------------------------- 1 | #include "path.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | QString Path::s_CacheDir; 10 | QString Path::s_LogDir; 11 | QString Path::s_BoxArtCacheDir; 12 | QString Path::s_QmlCacheDir; 13 | 14 | QString Path::getLogDir() 15 | { 16 | Q_ASSERT(!s_LogDir.isEmpty()); 17 | return s_LogDir; 18 | } 19 | 20 | QString Path::getBoxArtCacheDir() 21 | { 22 | Q_ASSERT(!s_BoxArtCacheDir.isEmpty()); 23 | return s_BoxArtCacheDir; 24 | } 25 | 26 | QString Path::getQmlCacheDir() 27 | { 28 | Q_ASSERT(!s_QmlCacheDir.isEmpty()); 29 | return s_QmlCacheDir; 30 | } 31 | 32 | QByteArray Path::readDataFile(QString fileName) 33 | { 34 | QFile dataFile(getDataFilePath(fileName)); 35 | dataFile.open(QIODevice::ReadOnly); 36 | return dataFile.readAll(); 37 | } 38 | 39 | void Path::writeCacheFile(QString fileName, QByteArray data) 40 | { 41 | QDir cacheDir(s_CacheDir); 42 | 43 | // Create the cache path if it does not exist 44 | if (!cacheDir.exists()) { 45 | cacheDir.mkpath("."); 46 | } 47 | 48 | QFile dataFile(cacheDir.absoluteFilePath(fileName)); 49 | dataFile.open(QIODevice::WriteOnly); 50 | dataFile.write(data); 51 | } 52 | 53 | void Path::deleteCacheFile(QString fileName) 54 | { 55 | QFile dataFile(QDir(s_CacheDir).absoluteFilePath(fileName)); 56 | dataFile.remove(); 57 | } 58 | 59 | QFileInfo Path::getCacheFileInfo(QString fileName) 60 | { 61 | return QFileInfo(QDir(s_CacheDir), fileName); 62 | } 63 | 64 | QString Path::getDataFilePath(QString fileName) 65 | { 66 | QString candidatePath; 67 | 68 | // Check the cache location first (used by Path::writeDataFile()) 69 | candidatePath = QDir(s_CacheDir).absoluteFilePath(fileName); 70 | if (QFile::exists(candidatePath)) { 71 | qInfo() << "Found" << fileName << "at" << candidatePath; 72 | return candidatePath; 73 | } 74 | 75 | // Check the current directory 76 | candidatePath = QDir(QDir::currentPath()).absoluteFilePath(fileName); 77 | if (QFile::exists(candidatePath)) { 78 | qInfo() << "Found" << fileName << "at" << candidatePath; 79 | return candidatePath; 80 | } 81 | 82 | // Now check the data directories (for Linux, in particular) 83 | candidatePath = QStandardPaths::locate(QStandardPaths::AppDataLocation, fileName); 84 | if (!candidatePath.isEmpty() && QFile::exists(candidatePath)) { 85 | qInfo() << "Found" << fileName << "at" << candidatePath; 86 | return candidatePath; 87 | } 88 | 89 | // Now try the directory of our app installation (for Windows, if current dir doesn't find it) 90 | candidatePath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(fileName); 91 | if (QFile::exists(candidatePath)) { 92 | qInfo() << "Found" << fileName << "at" << candidatePath; 93 | return candidatePath; 94 | } 95 | 96 | // Return the QRC embedded copy 97 | candidatePath = ":/data/" + fileName; 98 | qInfo() << "Found" << fileName << "at" << candidatePath; 99 | return QString(candidatePath); 100 | } 101 | 102 | void Path::initialize(bool portable) 103 | { 104 | if (portable) { 105 | s_LogDir = QDir::currentPath(); 106 | s_BoxArtCacheDir = QDir::currentPath() + "/boxart"; 107 | s_QmlCacheDir = QDir::currentPath() + "/qmlcache"; 108 | 109 | // In order for the If-Modified-Since logic to work in MappingFetcher, 110 | // the cache directory must be different than the current directory. 111 | s_CacheDir = QDir::currentPath() + "/cache"; 112 | } 113 | else { 114 | #ifdef Q_OS_DARWIN 115 | // On macOS, $TMPDIR is some random folder under /var/folders/ that nobody can 116 | // easily find, so use the system's global tmp directory instead. 117 | s_LogDir = "/tmp"; 118 | #else 119 | s_LogDir = QDir::tempPath(); 120 | #endif 121 | s_CacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); 122 | s_BoxArtCacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/boxart"; 123 | s_QmlCacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/qmlcache"; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Path 7 | { 8 | public: 9 | static QString getLogDir(); 10 | static QString getBoxArtCacheDir(); 11 | static QString getQmlCacheDir(); 12 | 13 | static QByteArray readDataFile(QString fileName); 14 | static void writeCacheFile(QString fileName, QByteArray data); 15 | static void deleteCacheFile(QString fileName); 16 | static QFileInfo getCacheFileInfo(QString fileName); 17 | 18 | // Only safe to use directly for Qt classes 19 | static QString getDataFilePath(QString fileName); 20 | 21 | static void initialize(bool portable); 22 | 23 | private: 24 | static QString s_CacheDir; 25 | static QString s_LogDir; 26 | static QString s_BoxArtCacheDir; 27 | static QString s_QmlCacheDir; 28 | }; 29 | -------------------------------------------------------------------------------- /app/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | gui/main.qml 4 | gui/PcView.qml 5 | gui/AppView.qml 6 | gui/SettingsView.qml 7 | gui/StreamSegue.qml 8 | gui/GamepadMapper.qml 9 | gui/QuitSegue.qml 10 | gui/NavigableToolButton.qml 11 | gui/NavigableItemDelegate.qml 12 | gui/NavigableMenuItem.qml 13 | gui/CliQuitStreamSegue.qml 14 | gui/CliStartStreamSegue.qml 15 | gui/AutoResizingComboBox.qml 16 | gui/NavigableMenu.qml 17 | gui/ErrorMessageDialog.qml 18 | gui/NavigableMessageDialog.qml 19 | gui/NavigableDialog.qml 20 | gui/CenteredGridView.qml 21 | gui/CliPair.qml 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/qt_qt5.conf: -------------------------------------------------------------------------------- 1 | [Platforms] 2 | WindowsArguments = darkmode=1 -------------------------------------------------------------------------------- /app/res/arrow_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/res/baseline-check_circle_outline-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/res/baseline-error_outline-24px.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/res/baseline-help_outline-24px.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/res/baseline-lock-24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/res/baseline-warning-24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/res/desktop_windows-48px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/res/discord.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/res/ic_add_to_queue_white_48px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/res/ic_videogame_asset_white_48px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/res/moonlight.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/res/no_app_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/res/no_app_image.png -------------------------------------------------------------------------------- /app/res/play_arrow_FILL1_wght700_GRAD200_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/res/question_mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/res/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/res/stop_FILL1_wght700_GRAD200_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/res/update.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/res/warning_FILL1_wght300_GRAD200_opsz24.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/settings/compatfetcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class CompatFetcher : public QObject 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit CompatFetcher(QObject *parent = nullptr); 12 | 13 | void start(); 14 | 15 | static bool isGfeVersionSupported(QString gfeVersion); 16 | 17 | private slots: 18 | void handleCompatInfoFetched(QNetworkReply* reply); 19 | 20 | private: 21 | QNetworkAccessManager* m_Nam; 22 | }; 23 | -------------------------------------------------------------------------------- /app/settings/mappingfetcher.cpp: -------------------------------------------------------------------------------- 1 | #include "mappingfetcher.h" 2 | #include "path.h" 3 | 4 | #include 5 | 6 | MappingFetcher::MappingFetcher(QObject *parent) : 7 | QObject(parent) 8 | { 9 | m_Nam = new QNetworkAccessManager(this); 10 | 11 | // Never communicate over HTTP 12 | m_Nam->setStrictTransportSecurityEnabled(true); 13 | 14 | // Allow HTTP redirects 15 | m_Nam->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); 16 | 17 | connect(m_Nam, &QNetworkAccessManager::finished, 18 | this, &MappingFetcher::handleMappingListFetched); 19 | } 20 | 21 | void MappingFetcher::start() 22 | { 23 | if (!m_Nam) { 24 | Q_ASSERT(m_Nam); 25 | return; 26 | } 27 | 28 | #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && QT_VERSION < QT_VERSION_CHECK(5, 15, 1) && !defined(QT_NO_BEARERMANAGEMENT) 29 | // HACK: Set network accessibility to work around QTBUG-80947 (introduced in Qt 5.14.0 and fixed in Qt 5.15.1) 30 | QT_WARNING_PUSH 31 | QT_WARNING_DISABLE_DEPRECATED 32 | m_Nam->setNetworkAccessible(QNetworkAccessManager::Accessible); 33 | QT_WARNING_POP 34 | #endif 35 | 36 | QUrl url("https://moonlight-stream.org/SDL_GameControllerDB/gamecontrollerdb.txt"); 37 | QNetworkRequest request(url); 38 | 39 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 40 | request.setAttribute(QNetworkRequest::Http2AllowedAttribute, true); 41 | #else 42 | request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true); 43 | #endif 44 | 45 | // Qt 5.12 introduced QNetworkRequest::IfModifiedSinceHeader. We _could_ implement it 46 | // by hand (including QDateTime conversion to the correct string form) for earlier Qt 47 | // versions, but that's a pain and this is just an optimization anyway. We'll do a bit 48 | // of extra work on those legacy Qt versions by fetching the GCDB each time we launch. 49 | #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) 50 | // Only download the file if it's newer than what we have 51 | QFileInfo existingFileInfo = Path::getCacheFileInfo("gamecontrollerdb.txt"); 52 | if (existingFileInfo.exists()) { 53 | // Make sure the cached file looks reasonable. It should have some data and 54 | // the last modified time should not be in the future. 55 | QDateTime lastModifiedTime = existingFileInfo.lastModified().toUTC(); 56 | if (existingFileInfo.size() > 0 && lastModifiedTime <= QDateTime::currentDateTimeUtc()) { 57 | request.setHeader(QNetworkRequest::IfModifiedSinceHeader, existingFileInfo.lastModified().toUTC()); 58 | } 59 | else { 60 | Path::deleteCacheFile("gamecontrollerdb.txt"); 61 | } 62 | } 63 | #endif 64 | 65 | // We'll get a callback when this is finished 66 | m_Nam->get(request); 67 | } 68 | 69 | void MappingFetcher::handleMappingListFetched(QNetworkReply* reply) 70 | { 71 | Q_ASSERT(reply->isFinished()); 72 | 73 | // Delete the QNetworkAccessManager to free resources and 74 | // prevent the bearer plugin from polling in the background. 75 | m_Nam->deleteLater(); 76 | m_Nam = nullptr; 77 | 78 | if (reply->error() == QNetworkReply::NoError) { 79 | // Queue the reply for deletion 80 | reply->deleteLater(); 81 | 82 | // If we get a 304 back, Qt will happily just tell us our request was 83 | // successful and let us try to write our empty response to disk. Check 84 | // the status code directly to prevent this. 85 | if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 304) { 86 | qInfo() << "Gamepad mappings are up to date"; 87 | } 88 | else { 89 | // Update the cached data on disk for next call to applyMappings() 90 | QByteArray data = reply->readAll(); 91 | if (!data.isEmpty()) { 92 | Path::writeCacheFile("gamecontrollerdb.txt", data); 93 | } 94 | 95 | qInfo() << "Downloaded updated gamepad mappings"; 96 | } 97 | } 98 | else { 99 | qWarning() << "Failed to download updated gamepad mappings:" << reply->error(); 100 | reply->deleteLater(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/settings/mappingfetcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class MappingFetcher : public QObject 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit MappingFetcher(QObject *parent = nullptr); 12 | 13 | void start(); 14 | 15 | private slots: 16 | void handleMappingListFetched(QNetworkReply* reply); 17 | 18 | private: 19 | QNetworkAccessManager* m_Nam; 20 | }; 21 | -------------------------------------------------------------------------------- /app/settings/mappingmanager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mappingfetcher.h" 4 | 5 | #include 6 | 7 | class SdlGamepadMapping 8 | { 9 | public: 10 | SdlGamepadMapping() {} 11 | 12 | SdlGamepadMapping(QString string) 13 | { 14 | QStringList mapping = string.split(","); 15 | if (!mapping.isEmpty()) { 16 | m_Guid = mapping[0]; 17 | 18 | string.remove(0, m_Guid.length() + 1); 19 | m_Mapping = string; 20 | } 21 | } 22 | 23 | SdlGamepadMapping(QString guid, QString mapping) 24 | : m_Guid(guid), 25 | m_Mapping(mapping) 26 | { 27 | 28 | } 29 | 30 | bool operator==(const SdlGamepadMapping& other) const 31 | { 32 | return m_Guid == other.m_Guid && m_Mapping == other.m_Mapping; 33 | } 34 | 35 | QString getGuid() const 36 | { 37 | return m_Guid; 38 | } 39 | 40 | QString getMapping() const 41 | { 42 | return m_Mapping; 43 | } 44 | 45 | QString getSdlMappingString() const 46 | { 47 | if (m_Guid.isEmpty() || m_Mapping.isEmpty()) { 48 | return ""; 49 | } 50 | else { 51 | return m_Guid + "," + m_Mapping; 52 | } 53 | } 54 | 55 | private: 56 | QString m_Guid; 57 | QString m_Mapping; 58 | }; 59 | 60 | class MappingManager 61 | { 62 | public: 63 | MappingManager(); 64 | 65 | void addMapping(QString gamepadString); 66 | 67 | void addMapping(SdlGamepadMapping& gamepadMapping); 68 | 69 | void applyMappings(); 70 | 71 | void save(); 72 | 73 | private: 74 | QMap m_Mappings; 75 | 76 | static MappingFetcher* s_MappingFetcher; 77 | }; 78 | 79 | -------------------------------------------------------------------------------- /app/shaders/build_hlsl.bat: -------------------------------------------------------------------------------- 1 | fxc /T vs_4_0_level_9_3 /Fo d3d11_vertex.fxc d3d11_vertex.hlsl 2 | 3 | fxc /T ps_4_0_level_9_3 /Fo d3d11_overlay_pixel.fxc d3d11_overlay_pixel.hlsl 4 | fxc /T ps_4_0_level_9_3 /Fo d3d11_genyuv_pixel.fxc d3d11_genyuv_pixel.hlsl 5 | fxc /T ps_4_0_level_9_3 /Fo d3d11_bt601lim_pixel.fxc d3d11_bt601lim_pixel.hlsl 6 | fxc /T ps_4_0_level_9_3 /Fo d3d11_bt2020lim_pixel.fxc d3d11_bt2020lim_pixel.hlsl 7 | fxc /T ps_4_0_level_9_3 /Fo d3d11_ayuv_pixel.fxc d3d11_ayuv_pixel.hlsl 8 | fxc /T ps_4_0_level_9_3 /Fo d3d11_y410_pixel.fxc d3d11_y410_pixel.hlsl -------------------------------------------------------------------------------- /app/shaders/d3d11_ayuv_pixel.fxc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/shaders/d3d11_ayuv_pixel.fxc -------------------------------------------------------------------------------- /app/shaders/d3d11_ayuv_pixel.hlsl: -------------------------------------------------------------------------------- 1 | #include "d3d11_yuv444_pixel_start.hlsli" 2 | 3 | min16float3 swizzle(min16float3 input) 4 | { 5 | // AYUV SRVs are in VUYA order 6 | return input.bgr; 7 | } 8 | 9 | #include "d3d11_yuv444_pixel_end.hlsli" -------------------------------------------------------------------------------- /app/shaders/d3d11_bt2020lim_pixel.fxc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/shaders/d3d11_bt2020lim_pixel.fxc -------------------------------------------------------------------------------- /app/shaders/d3d11_bt2020lim_pixel.hlsl: -------------------------------------------------------------------------------- 1 | #include "d3d11_video_pixel_start.hlsli" 2 | 3 | static const min16float3x3 cscMatrix = 4 | { 5 | 1.1644, 1.1644, 1.1644, 6 | 0.0, -0.1874, 2.1418, 7 | 1.6781, -0.6505, 0.0, 8 | }; 9 | 10 | static const min16float3 offsets = 11 | { 12 | 16.0 / 255.0, 128.0 / 255.0, 128.0 / 255.0 13 | }; 14 | 15 | #include "d3d11_video_pixel_end.hlsli" -------------------------------------------------------------------------------- /app/shaders/d3d11_bt601lim_pixel.fxc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/shaders/d3d11_bt601lim_pixel.fxc -------------------------------------------------------------------------------- /app/shaders/d3d11_bt601lim_pixel.hlsl: -------------------------------------------------------------------------------- 1 | #include "d3d11_video_pixel_start.hlsli" 2 | 3 | static const min16float3x3 cscMatrix = 4 | { 5 | 1.1644, 1.1644, 1.1644, 6 | 0.0, -0.3917, 2.0172, 7 | 1.5960, -0.8129, 0.0, 8 | }; 9 | 10 | static const min16float3 offsets = 11 | { 12 | 16.0 / 255.0, 128.0 / 255.0, 128.0 / 255.0 13 | }; 14 | 15 | #include "d3d11_video_pixel_end.hlsli" -------------------------------------------------------------------------------- /app/shaders/d3d11_genyuv_pixel.fxc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/shaders/d3d11_genyuv_pixel.fxc -------------------------------------------------------------------------------- /app/shaders/d3d11_genyuv_pixel.hlsl: -------------------------------------------------------------------------------- 1 | #include "d3d11_video_pixel_start.hlsli" 2 | 3 | cbuffer CSC_CONST_BUF : register(b1) 4 | { 5 | min16float3x3 cscMatrix; 6 | min16float3 offsets; 7 | }; 8 | 9 | #include "d3d11_video_pixel_end.hlsli" -------------------------------------------------------------------------------- /app/shaders/d3d11_overlay_pixel.fxc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/shaders/d3d11_overlay_pixel.fxc -------------------------------------------------------------------------------- /app/shaders/d3d11_overlay_pixel.hlsl: -------------------------------------------------------------------------------- 1 | Texture2D theTexture : register(t0); 2 | SamplerState theSampler : register(s0); 3 | 4 | struct ShaderInput 5 | { 6 | float4 pos : SV_POSITION; 7 | float2 tex : TEXCOORD0; 8 | }; 9 | 10 | min16float4 main(ShaderInput input) : SV_TARGET 11 | { 12 | return theTexture.Sample(theSampler, input.tex); 13 | } -------------------------------------------------------------------------------- /app/shaders/d3d11_vertex.fxc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/shaders/d3d11_vertex.fxc -------------------------------------------------------------------------------- /app/shaders/d3d11_vertex.hlsl: -------------------------------------------------------------------------------- 1 | struct ShaderInput 2 | { 3 | float2 pos : POSITION; 4 | float2 tex : TEXCOORD0; 5 | }; 6 | 7 | struct ShaderOutput 8 | { 9 | float4 pos : SV_POSITION; 10 | float2 tex : TEXCOORD0; 11 | }; 12 | 13 | ShaderOutput main(ShaderInput input) 14 | { 15 | ShaderOutput output; 16 | output.pos = float4(input.pos, 0.0, 1.0); 17 | output.tex = input.tex; 18 | return output; 19 | } -------------------------------------------------------------------------------- /app/shaders/d3d11_video_pixel_end.hlsli: -------------------------------------------------------------------------------- 1 | min16float4 main(ShaderInput input) : SV_TARGET 2 | { 3 | // Clamp the chrominance texcoords to avoid sampling the row of texels adjacent to the alignment padding 4 | min16float3 yuv = min16float3(luminancePlane.Sample(theSampler, input.tex), 5 | chrominancePlane.Sample(theSampler, min(input.tex, chromaTexMax.rg))); 6 | 7 | // Subtract the YUV offset for limited vs full range 8 | yuv -= offsets; 9 | 10 | // Multiply by the conversion matrix for this colorspace 11 | yuv = mul(yuv, cscMatrix); 12 | 13 | return min16float4(yuv, 1.0); 14 | } -------------------------------------------------------------------------------- /app/shaders/d3d11_video_pixel_start.hlsli: -------------------------------------------------------------------------------- 1 | Texture2D luminancePlane : register(t0); 2 | Texture2D chrominancePlane : register(t1); 3 | SamplerState theSampler : register(s0); 4 | 5 | struct ShaderInput 6 | { 7 | float4 pos : SV_POSITION; 8 | float2 tex : TEXCOORD0; 9 | }; 10 | 11 | cbuffer ChromaLimitBuf : register(b0) 12 | { 13 | min16float3 chromaTexMax; 14 | }; 15 | -------------------------------------------------------------------------------- /app/shaders/d3d11_y410_pixel.fxc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/app/shaders/d3d11_y410_pixel.fxc -------------------------------------------------------------------------------- /app/shaders/d3d11_y410_pixel.hlsl: -------------------------------------------------------------------------------- 1 | #include "d3d11_yuv444_pixel_start.hlsli" 2 | 3 | min16float3 swizzle(min16float3 input) 4 | { 5 | // Y410 SRVs are in UYVA order 6 | return input.grb; 7 | } 8 | 9 | #include "d3d11_yuv444_pixel_end.hlsli" -------------------------------------------------------------------------------- /app/shaders/d3d11_yuv444_pixel_end.hlsli: -------------------------------------------------------------------------------- 1 | min16float4 main(ShaderInput input) : SV_TARGET 2 | { 3 | // Clamp the texcoords to avoid sampling the row of texels adjacent to the alignment padding 4 | min16float3 yuv = swizzle(videoTex.Sample(theSampler, min(input.tex, chromaTexMax.rg))); 5 | 6 | // Subtract the YUV offset for limited vs full range 7 | yuv -= offsets; 8 | 9 | // Multiply by the conversion matrix for this colorspace 10 | yuv = mul(yuv, cscMatrix); 11 | 12 | return min16float4(yuv, 1.0); 13 | } -------------------------------------------------------------------------------- /app/shaders/d3d11_yuv444_pixel_start.hlsli: -------------------------------------------------------------------------------- 1 | Texture2D videoTex : register(t0); 2 | SamplerState theSampler : register(s0); 3 | 4 | struct ShaderInput 5 | { 6 | float4 pos : SV_POSITION; 7 | float2 tex : TEXCOORD0; 8 | }; 9 | 10 | cbuffer ChromaLimitBuf : register(b0) 11 | { 12 | min16float3 chromaTexMax; 13 | }; 14 | 15 | cbuffer CSC_CONST_BUF : register(b1) 16 | { 17 | min16float3x3 cscMatrix; 18 | min16float3 offsets; 19 | }; -------------------------------------------------------------------------------- /app/shaders/egl_nv12.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | #extension GL_OES_EGL_image_external : require 3 | precision mediump float; 4 | out vec4 FragColor; 5 | 6 | in vec2 vTextCoord; 7 | 8 | uniform mat3 yuvmat; 9 | uniform vec3 offset; 10 | uniform samplerExternalOES plane1; 11 | uniform samplerExternalOES plane2; 12 | 13 | void main() { 14 | vec3 YCbCr = vec3( 15 | texture2D(plane1, vTextCoord)[0], 16 | texture2D(plane2, vTextCoord).xy 17 | ); 18 | 19 | YCbCr -= offset; 20 | FragColor = vec4(clamp(yuvmat * YCbCr, 0.0, 1.0), 1.0f); 21 | } 22 | -------------------------------------------------------------------------------- /app/shaders/egl_nv12.vert: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | layout (location = 0) in vec2 aPosition; // 2D: X,Y 4 | layout (location = 1) in vec2 aTexCoord; 5 | out vec2 vTextCoord; 6 | 7 | void main() { 8 | vTextCoord = aTexCoord; 9 | gl_Position = vec4(aPosition, 0, 1); 10 | } 11 | -------------------------------------------------------------------------------- /app/shaders/egl_opaque.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | #extension GL_OES_EGL_image_external : require 3 | precision mediump float; 4 | out vec4 FragColor; 5 | 6 | in vec2 vTextCoord; 7 | 8 | uniform samplerExternalOES uTexture; 9 | 10 | void main() { 11 | FragColor = texture2D(uTexture, vTextCoord); 12 | } 13 | -------------------------------------------------------------------------------- /app/shaders/egl_opaque.vert: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | layout (location = 0) in vec2 aPosition; // 2D: X,Y 4 | layout (location = 1) in vec2 aTexCoord; 5 | out vec2 vTextCoord; 6 | 7 | void main() { 8 | vTextCoord = aTexCoord; 9 | gl_Position = vec4(aPosition, 0, 1); 10 | } 11 | -------------------------------------------------------------------------------- /app/shaders/egl_overlay.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform sampler2D uTexture; 3 | varying vec2 vTexCoord; 4 | 5 | void main() { 6 | vec4 abgr = texture2D(uTexture, vTexCoord); 7 | 8 | gl_FragColor = abgr; 9 | gl_FragColor.r = abgr.b; 10 | gl_FragColor.b = abgr.r; 11 | } 12 | -------------------------------------------------------------------------------- /app/shaders/egl_overlay.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 aPosition; // 2D: X,Y 2 | attribute vec2 aTexCoord; 3 | varying vec2 vTexCoord; 4 | 5 | void main() { 6 | vTexCoord = aTexCoord; 7 | gl_Position = vec4(aPosition, 0, 1); 8 | } 9 | -------------------------------------------------------------------------------- /app/shaders/vt_renderer.metal: -------------------------------------------------------------------------------- 1 | using namespace metal; 2 | 3 | struct Vertex 4 | { 5 | float4 position [[ position ]]; 6 | float2 texCoords; 7 | }; 8 | 9 | struct CscParams 10 | { 11 | float3 matrix[3]; 12 | float3 offsets; 13 | float bitnessScaleFactor; 14 | }; 15 | 16 | constexpr sampler s(coord::normalized, address::clamp_to_edge, filter::linear); 17 | 18 | vertex Vertex vs_draw(constant Vertex *vertices [[ buffer(0) ]], uint id [[ vertex_id ]]) 19 | { 20 | return vertices[id]; 21 | } 22 | 23 | fragment float4 ps_draw_biplanar(Vertex v [[ stage_in ]], 24 | constant CscParams &cscParams [[ buffer(0) ]], 25 | texture2d luminancePlane [[ texture(0) ]], 26 | texture2d chrominancePlane [[ texture(1) ]]) 27 | { 28 | float3 yuv = float3(luminancePlane.sample(s, v.texCoords).r, 29 | chrominancePlane.sample(s, v.texCoords).rg); 30 | yuv *= cscParams.bitnessScaleFactor; 31 | yuv -= cscParams.offsets; 32 | 33 | float3 rgb; 34 | rgb.r = dot(yuv, cscParams.matrix[0]); 35 | rgb.g = dot(yuv, cscParams.matrix[1]); 36 | rgb.b = dot(yuv, cscParams.matrix[2]); 37 | return float4(rgb, 1.0f); 38 | } 39 | 40 | fragment float4 ps_draw_triplanar(Vertex v [[ stage_in ]], 41 | constant CscParams &cscParams [[ buffer(0) ]], 42 | texture2d luminancePlane [[ texture(0) ]], 43 | texture2d chrominancePlaneU [[ texture(1) ]], 44 | texture2d chrominancePlaneV [[ texture(2) ]]) 45 | { 46 | float3 yuv = float3(luminancePlane.sample(s, v.texCoords).r, 47 | chrominancePlaneU.sample(s, v.texCoords).r, 48 | chrominancePlaneV.sample(s, v.texCoords).r); 49 | yuv *= cscParams.bitnessScaleFactor; 50 | yuv -= cscParams.offsets; 51 | 52 | float3 rgb; 53 | rgb.r = dot(yuv, cscParams.matrix[0]); 54 | rgb.g = dot(yuv, cscParams.matrix[1]); 55 | rgb.b = dot(yuv, cscParams.matrix[2]); 56 | return float4(rgb, 1.0f); 57 | } 58 | 59 | fragment float4 ps_draw_rgb(Vertex v [[ stage_in ]], 60 | texture2d rgbTexture [[ texture(0) ]]) 61 | { 62 | return rgbTexture.sample(s, v.texCoords); 63 | } 64 | -------------------------------------------------------------------------------- /app/streaming/audio/renderers/renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class IAudioRenderer 7 | { 8 | public: 9 | virtual ~IAudioRenderer() {} 10 | 11 | virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig) = 0; 12 | 13 | virtual void* getAudioBuffer(int* size) = 0; 14 | 15 | // Return false if an unrecoverable error has occurred and the renderer must be reinitialized 16 | virtual bool submitAudio(int bytesWritten) = 0; 17 | 18 | virtual int getCapabilities() = 0; 19 | 20 | virtual void remapChannels(POPUS_MULTISTREAM_CONFIGURATION) { 21 | // Use default channel mapping: 22 | // 0 - Front Left 23 | // 1 - Front Right 24 | // 2 - Center 25 | // 3 - LFE 26 | // 4 - Surround Left 27 | // 5 - Surround Right 28 | } 29 | 30 | enum class AudioFormat { 31 | Sint16NE, // 16-bit signed integer (native endian) 32 | Float32NE, // 32-bit floating point (native endian) 33 | }; 34 | virtual AudioFormat getAudioBufferFormat() = 0; 35 | 36 | int getAudioBufferSampleSize() { 37 | switch (getAudioBufferFormat()) { 38 | case IAudioRenderer::AudioFormat::Sint16NE: 39 | return sizeof(short); 40 | case IAudioRenderer::AudioFormat::Float32NE: 41 | return sizeof(float); 42 | default: 43 | Q_UNREACHABLE(); 44 | } 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /app/streaming/audio/renderers/sdl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | #include "SDL_compat.h" 5 | 6 | class SdlAudioRenderer : public IAudioRenderer 7 | { 8 | public: 9 | SdlAudioRenderer(); 10 | 11 | virtual ~SdlAudioRenderer(); 12 | 13 | virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig); 14 | 15 | virtual void* getAudioBuffer(int* size); 16 | 17 | virtual bool submitAudio(int bytesWritten); 18 | 19 | virtual int getCapabilities(); 20 | 21 | virtual AudioFormat getAudioBufferFormat(); 22 | 23 | private: 24 | SDL_AudioDeviceID m_AudioDevice; 25 | void* m_AudioBuffer; 26 | int m_FrameSize; 27 | }; 28 | -------------------------------------------------------------------------------- /app/streaming/audio/renderers/slaud.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | #include 5 | 6 | class SLAudioRenderer : public IAudioRenderer 7 | { 8 | public: 9 | SLAudioRenderer(); 10 | 11 | virtual ~SLAudioRenderer(); 12 | 13 | virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig); 14 | 15 | virtual void* getAudioBuffer(int* size); 16 | 17 | virtual bool submitAudio(int bytesWritten); 18 | 19 | virtual int getCapabilities(); 20 | 21 | virtual AudioFormat getAudioBufferFormat(); 22 | 23 | virtual void remapChannels(POPUS_MULTISTREAM_CONFIGURATION opusConfig); 24 | 25 | private: 26 | static void slLogCallback(void* context, ESLAudioLog logLevel, const char* message); 27 | 28 | CSLAudioContext* m_AudioContext; 29 | CSLAudioStream* m_AudioStream; 30 | 31 | void* m_AudioBuffer; 32 | int m_AudioBufferSize; 33 | int m_MaxQueuedAudioMs; 34 | }; 35 | -------------------------------------------------------------------------------- /app/streaming/audio/renderers/soundioaudiorenderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | 5 | #include 6 | 7 | class SoundIoAudioRenderer : public IAudioRenderer 8 | { 9 | public: 10 | SoundIoAudioRenderer(); 11 | 12 | ~SoundIoAudioRenderer(); 13 | 14 | virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig); 15 | 16 | virtual void* getAudioBuffer(int* size); 17 | 18 | virtual bool submitAudio(int bytesWritten); 19 | 20 | virtual int getCapabilities(); 21 | 22 | virtual AudioFormat getAudioBufferFormat(); 23 | 24 | private: 25 | int scoreChannelLayout(const struct SoundIoChannelLayout* layout, const OPUS_MULTISTREAM_CONFIGURATION* opusConfig); 26 | 27 | static void sioErrorCallback(struct SoundIoOutStream* stream, int err); 28 | 29 | static void sioWriteCallback(struct SoundIoOutStream* stream, int frameCountMin, int frameCountMax); 30 | 31 | static void sioBackendDisconnect(struct SoundIo* soundio, int err); 32 | 33 | static void sioDevicesChanged(SoundIo* soundio); 34 | 35 | int m_OpusChannelCount; 36 | struct SoundIo* m_SoundIo; 37 | struct SoundIoDevice* m_Device; 38 | struct SoundIoOutStream* m_OutputStream; 39 | struct SoundIoRingBuffer* m_RingBuffer; 40 | struct SoundIoChannelLayout m_EffectiveLayout; 41 | double m_AudioPacketDuration; 42 | double m_Latency; 43 | bool m_Errored; 44 | }; 45 | -------------------------------------------------------------------------------- /app/streaming/streamutils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SDL_compat.h" 4 | 5 | class StreamUtils 6 | { 7 | public: 8 | static 9 | Uint32 getPlatformWindowFlags(); 10 | 11 | static 12 | void scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst); 13 | 14 | static 15 | void screenSpaceToNormalizedDeviceCoords(SDL_FRect* rect, int viewportWidth, int viewportHeight); 16 | 17 | static 18 | void screenSpaceToNormalizedDeviceCoords(SDL_Rect* src, SDL_FRect* dst, int viewportWidth, int viewportHeight); 19 | 20 | static 21 | bool getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode, SDL_Rect* safeArea); 22 | 23 | static 24 | int getDisplayRefreshRate(SDL_Window* window); 25 | 26 | static 27 | bool hasFastAes(); 28 | 29 | static 30 | int getDrmFdForWindow(SDL_Window* window, bool* needsClose); 31 | 32 | static 33 | int getDrmFd(bool preferRenderNode); 34 | }; 35 | -------------------------------------------------------------------------------- /app/streaming/video/decoder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "SDL_compat.h" 5 | #include "settings/streamingpreferences.h" 6 | 7 | #define SDL_CODE_FRAME_READY 0 8 | 9 | #define MAX_SLICES 4 10 | 11 | typedef struct _VIDEO_STATS { 12 | uint32_t receivedFrames; 13 | uint32_t decodedFrames; 14 | uint32_t renderedFrames; 15 | uint32_t totalFrames; 16 | uint32_t networkDroppedFrames; 17 | uint32_t pacerDroppedFrames; 18 | uint16_t minHostProcessingLatency; 19 | uint16_t maxHostProcessingLatency; 20 | uint32_t totalHostProcessingLatency; 21 | uint32_t framesWithHostProcessingLatency; 22 | uint32_t totalReassemblyTime; 23 | uint32_t totalDecodeTime; 24 | uint32_t totalPacerTime; 25 | uint32_t totalRenderTime; 26 | uint32_t lastRtt; 27 | uint32_t lastRttVariance; 28 | float totalFps; 29 | float receivedFps; 30 | float decodedFps; 31 | float renderedFps; 32 | uint32_t measurementStartTimestamp; 33 | } VIDEO_STATS, *PVIDEO_STATS; 34 | 35 | typedef struct _DECODER_PARAMETERS { 36 | SDL_Window* window; 37 | StreamingPreferences::VideoDecoderSelection vds; 38 | 39 | int videoFormat; 40 | int width; 41 | int height; 42 | int frameRate; 43 | bool enableVsync; 44 | bool enableFramePacing; 45 | bool testOnly; 46 | } DECODER_PARAMETERS, *PDECODER_PARAMETERS; 47 | 48 | #define WINDOW_STATE_CHANGE_SIZE 0x01 49 | #define WINDOW_STATE_CHANGE_DISPLAY 0x02 50 | 51 | typedef struct _WINDOW_STATE_CHANGE_INFO { 52 | SDL_Window* window; 53 | uint32_t stateChangeFlags; 54 | 55 | // Populated if WINDOW_STATE_CHANGE_SIZE is set 56 | int width; 57 | int height; 58 | 59 | // Populated if WINDOW_STATE_CHANGE_DISPLAY is set 60 | int displayIndex; 61 | } WINDOW_STATE_CHANGE_INFO, *PWINDOW_STATE_CHANGE_INFO; 62 | 63 | class IVideoDecoder { 64 | public: 65 | virtual ~IVideoDecoder() {} 66 | virtual bool initialize(PDECODER_PARAMETERS params) = 0; 67 | virtual bool isHardwareAccelerated() = 0; 68 | virtual bool isAlwaysFullScreen() = 0; 69 | virtual bool isHdrSupported() = 0; 70 | virtual int getDecoderCapabilities() = 0; 71 | virtual int getDecoderColorspace() = 0; 72 | virtual int getDecoderColorRange() = 0; 73 | virtual QSize getDecoderMaxResolution() = 0; 74 | virtual int submitDecodeUnit(PDECODE_UNIT du) = 0; 75 | virtual void renderFrameOnMainThread() = 0; 76 | virtual void setHdrMode(bool enabled) = 0; 77 | virtual bool notifyWindowChanged(PWINDOW_STATE_CHANGE_INFO info) = 0; 78 | }; 79 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/cuda.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | 5 | #include 6 | 7 | extern "C" { 8 | #include 9 | } 10 | 11 | class CUDARenderer : public IFFmpegRenderer { 12 | public: 13 | CUDARenderer(); 14 | virtual ~CUDARenderer() override; 15 | virtual bool initialize(PDECODER_PARAMETERS) override; 16 | virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override; 17 | virtual void renderFrame(AVFrame* frame) override; 18 | virtual bool needsTestFrame() override; 19 | virtual bool isDirectRenderingSupported() override; 20 | virtual int getDecoderCapabilities() override; 21 | 22 | private: 23 | AVBufferRef* m_HwContext; 24 | }; 25 | 26 | #define NV12_PLANES 2 27 | 28 | // Helper class used by SDLRenderer to read our CUDA frame 29 | class CUDAGLInteropHelper { 30 | public: 31 | CUDAGLInteropHelper(AVHWDeviceContext* context); 32 | ~CUDAGLInteropHelper(); 33 | 34 | bool registerBoundTextures(); 35 | void unregisterTextures(); 36 | 37 | bool copyCudaFrameToTextures(AVFrame* frame); 38 | 39 | private: 40 | CudaFunctions* m_Funcs; 41 | AVCUDADeviceContext* m_Context; 42 | CUgraphicsResource m_Resources[NV12_PLANES]; 43 | }; 44 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/d3d11va.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | 5 | #include 6 | #include 7 | 8 | extern "C" { 9 | #include 10 | } 11 | 12 | #include 13 | 14 | class D3D11VARenderer : public IFFmpegRenderer 15 | { 16 | public: 17 | D3D11VARenderer(int decoderSelectionPass); 18 | virtual ~D3D11VARenderer() override; 19 | virtual bool initialize(PDECODER_PARAMETERS params) override; 20 | virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary**) override; 21 | virtual bool prepareDecoderContextInGetFormat(AVCodecContext* context, AVPixelFormat pixelFormat) override; 22 | virtual void renderFrame(AVFrame* frame) override; 23 | virtual void notifyOverlayUpdated(Overlay::OverlayType) override; 24 | virtual int getRendererAttributes() override; 25 | virtual int getDecoderCapabilities() override; 26 | virtual bool needsTestFrame() override; 27 | virtual InitFailureReason getInitFailureReason() override; 28 | 29 | enum PixelShaders { 30 | GENERIC_YUV_420, 31 | BT_601_LIMITED_YUV_420, 32 | BT_2020_LIMITED_YUV_420, 33 | GENERIC_AYUV, 34 | GENERIC_Y410, 35 | _COUNT 36 | }; 37 | 38 | private: 39 | static void lockContext(void* lock_ctx); 40 | static void unlockContext(void* lock_ctx); 41 | 42 | bool setupRenderingResources(); 43 | std::vector getVideoTextureSRVFormats(); 44 | bool setupVideoTexture(); // for !m_BindDecoderOutputTextures 45 | bool setupTexturePoolViews(AVD3D11VAFramesContext* frameContext); // for m_BindDecoderOutputTextures 46 | void renderOverlay(Overlay::OverlayType type); 47 | void bindColorConversion(AVFrame* frame); 48 | void renderVideo(AVFrame* frame); 49 | bool checkDecoderSupport(IDXGIAdapter* adapter); 50 | bool createDeviceByAdapterIndex(int adapterIndex, bool* adapterNotFound = nullptr); 51 | 52 | int m_DecoderSelectionPass; 53 | int m_DevicesWithFL11Support; 54 | int m_DevicesWithCodecSupport; 55 | 56 | enum class SupportedFenceType { 57 | None, 58 | NonMonitored, 59 | Monitored, 60 | }; 61 | 62 | Microsoft::WRL::ComPtr m_Factory; 63 | Microsoft::WRL::ComPtr m_Device; 64 | Microsoft::WRL::ComPtr m_SwapChain; 65 | Microsoft::WRL::ComPtr m_DeviceContext; 66 | Microsoft::WRL::ComPtr m_RenderTargetView; 67 | SupportedFenceType m_FenceType; 68 | SDL_mutex* m_ContextLock; 69 | bool m_BindDecoderOutputTextures; 70 | bool m_UseFenceHack; 71 | 72 | DECODER_PARAMETERS m_DecoderParams; 73 | int m_TextureAlignment; 74 | DXGI_FORMAT m_TextureFormat; 75 | int m_DisplayWidth; 76 | int m_DisplayHeight; 77 | int m_LastColorSpace; 78 | bool m_LastFullRange; 79 | AVColorTransferCharacteristic m_LastColorTrc; 80 | 81 | bool m_AllowTearing; 82 | 83 | std::array, PixelShaders::_COUNT> m_VideoPixelShaders; 84 | Microsoft::WRL::ComPtr m_VideoVertexBuffer; 85 | 86 | // Only valid if !m_BindDecoderOutputTextures 87 | Microsoft::WRL::ComPtr m_VideoTexture; 88 | 89 | // Only index 0 is valid if !m_BindDecoderOutputTextures 90 | #define DECODER_BUFFER_POOL_SIZE 17 91 | std::array, 2>, DECODER_BUFFER_POOL_SIZE> m_VideoTextureResourceViews; 92 | 93 | SDL_SpinLock m_OverlayLock; 94 | std::array, Overlay::OverlayMax> m_OverlayVertexBuffers; 95 | std::array, Overlay::OverlayMax> m_OverlayTextures; 96 | std::array, Overlay::OverlayMax> m_OverlayTextureResourceViews; 97 | Microsoft::WRL::ComPtr m_OverlayPixelShader; 98 | 99 | AVBufferRef* m_HwDeviceContext; 100 | AVBufferRef* m_HwFramesContext; 101 | }; 102 | 103 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/drm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | #include "swframemapper.h" 5 | 6 | #ifdef HAVE_EGL 7 | #include "eglimagefactory.h" 8 | #endif 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | // Newer libdrm headers have these HDR structs, but some older ones don't. 16 | namespace DrmDefs 17 | { 18 | // HDR structs is copied from linux include/linux/hdmi.h 19 | struct hdr_metadata_infoframe 20 | { 21 | uint8_t eotf; 22 | uint8_t metadata_type; 23 | 24 | struct 25 | { 26 | uint16_t x, y; 27 | } display_primaries[3]; 28 | 29 | struct 30 | { 31 | uint16_t x, y; 32 | } white_point; 33 | 34 | uint16_t max_display_mastering_luminance; 35 | uint16_t min_display_mastering_luminance; 36 | 37 | uint16_t max_cll; 38 | uint16_t max_fall; 39 | }; 40 | 41 | struct hdr_output_metadata 42 | { 43 | uint32_t metadata_type; 44 | 45 | union { 46 | struct hdr_metadata_infoframe hdmi_metadata_type1; 47 | }; 48 | }; 49 | } 50 | 51 | class DrmRenderer : public IFFmpegRenderer { 52 | public: 53 | DrmRenderer(AVHWDeviceType hwDeviceType = AV_HWDEVICE_TYPE_NONE, IFFmpegRenderer *backendRenderer = nullptr); 54 | virtual ~DrmRenderer() override; 55 | virtual bool initialize(PDECODER_PARAMETERS params) override; 56 | virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override; 57 | virtual void prepareToRender() override; 58 | virtual void renderFrame(AVFrame* frame) override; 59 | virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) override; 60 | virtual bool isPixelFormatSupported(int videoFormat, AVPixelFormat pixelFormat) override; 61 | virtual int getRendererAttributes() override; 62 | virtual bool needsTestFrame() override; 63 | virtual bool testRenderFrame(AVFrame* frame) override; 64 | virtual bool isDirectRenderingSupported() override; 65 | virtual int getDecoderColorspace() override; 66 | virtual void setHdrMode(bool enabled) override; 67 | #ifdef HAVE_EGL 68 | virtual bool canExportEGL() override; 69 | virtual AVPixelFormat getEGLImagePixelFormat() override; 70 | virtual bool initializeEGL(EGLDisplay dpy, const EGLExtensions &ext) override; 71 | virtual ssize_t exportEGLImages(AVFrame *frame, EGLDisplay dpy, EGLImage images[EGL_MAX_PLANES]) override; 72 | virtual void freeEGLImages(EGLDisplay dpy, EGLImage[EGL_MAX_PLANES]) override; 73 | #endif 74 | 75 | private: 76 | bool getPropertyByName(drmModeObjectPropertiesPtr props, const char* name, uint64_t *value); 77 | const char* getDrmColorEncodingValue(AVFrame* frame); 78 | const char* getDrmColorRangeValue(AVFrame* frame); 79 | bool mapSoftwareFrame(AVFrame* frame, AVDRMFrameDescriptor* mappedFrame); 80 | bool addFbForFrame(AVFrame* frame, uint32_t* newFbId, bool testMode); 81 | static bool drmFormatMatchesVideoFormat(uint32_t drmFormat, int videoFormat); 82 | 83 | IFFmpegRenderer* m_BackendRenderer; 84 | SDL_Window* m_Window; 85 | bool m_DrmPrimeBackend; 86 | AVHWDeviceType m_HwDeviceType; 87 | AVBufferRef* m_HwContext; 88 | int m_DrmFd; 89 | bool m_DrmIsMaster; 90 | bool m_MustCloseDrmFd; 91 | bool m_SupportsDirectRendering; 92 | int m_VideoFormat; 93 | uint32_t m_ConnectorId; 94 | uint32_t m_EncoderId; 95 | uint32_t m_CrtcId; 96 | uint32_t m_PlaneId; 97 | uint32_t m_CurrentFbId; 98 | bool m_LastFullRange; 99 | int m_LastColorSpace; 100 | drmModePlanePtr m_Plane; 101 | drmModePropertyPtr m_ColorEncodingProp; 102 | drmModePropertyPtr m_ColorRangeProp; 103 | drmModePropertyPtr m_HdrOutputMetadataProp; 104 | drmModePropertyPtr m_ColorspaceProp; 105 | drmVersionPtr m_Version; 106 | uint32_t m_HdrOutputMetadataBlobId; 107 | SDL_Rect m_OutputRect; 108 | std::set m_SupportedPlaneFormats; 109 | 110 | static constexpr int k_SwFrameCount = 2; 111 | SwFrameMapper m_SwFrameMapper; 112 | int m_CurrentSwFrameIdx; 113 | struct { 114 | uint32_t handle; 115 | uint32_t pitch; 116 | uint64_t size; 117 | uint8_t* mapping; 118 | int primeFd; 119 | } m_SwFrame[k_SwFrameCount]; 120 | 121 | #ifdef HAVE_EGL 122 | EglImageFactory m_EglImageFactory; 123 | #endif 124 | }; 125 | 126 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/dxutil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class DXUtil 6 | { 7 | public: 8 | static bool isFormatHybridDecodedByHardware(int videoFormat, unsigned int vendorId, unsigned int deviceId) { 9 | if (vendorId == 0x8086) { 10 | // Intel seems to encode the series in the high byte of 11 | // the device ID. We want to avoid the "Partial" acceleration 12 | // support explicitly. Those will claim to have HW acceleration 13 | // but perform badly. 14 | // https://en.wikipedia.org/wiki/Intel_Graphics_Technology#Capabilities_(GPU_video_acceleration) 15 | // https://raw.githubusercontent.com/GameTechDev/gpudetect/master/IntelGfx.cfg 16 | switch (deviceId & 0xFF00) { 17 | case 0x0400: // Haswell 18 | case 0x0A00: // Haswell 19 | case 0x0D00: // Haswell 20 | case 0x1600: // Broadwell 21 | case 0x2200: // Cherry Trail and Braswell 22 | // Block these for HEVC to avoid hybrid decode 23 | return (videoFormat & VIDEO_FORMAT_MASK_H265) != 0; 24 | case 0x1900: // Skylake 25 | // Blacklist these for HEVC Main10 to avoid hybrid decode. 26 | // Regular HEVC Main is fine though. 27 | if (videoFormat == VIDEO_FORMAT_H265_MAIN10) { 28 | return true; 29 | } 30 | default: 31 | break; 32 | } 33 | } 34 | else if (vendorId == 0x10DE) { 35 | // For NVIDIA, we wait to avoid those GPUs with Feature Set E 36 | // for HEVC decoding, since that's hybrid. It appears that Kepler GPUs 37 | // also had some hybrid decode support (per DXVA2 Checker) so we'll 38 | // blacklist those too. 39 | // https://en.wikipedia.org/wiki/Nvidia_PureVideo 40 | // https://bluesky23.yukishigure.com/en/dxvac/deviceInfo/decoder.html 41 | // http://envytools.readthedocs.io/en/latest/hw/pciid.html (missing GM200) 42 | if ((deviceId >= 0x1180 && deviceId <= 0x11BF) || // GK104 43 | (deviceId >= 0x11C0 && deviceId <= 0x11FF) || // GK106 44 | (deviceId >= 0x0FC0 && deviceId <= 0x0FFF) || // GK107 45 | (deviceId >= 0x1000 && deviceId <= 0x103F) || // GK110/GK110B 46 | (deviceId >= 0x1280 && deviceId <= 0x12BF) || // GK208 47 | (deviceId >= 0x1340 && deviceId <= 0x137F) || // GM108 48 | (deviceId >= 0x1380 && deviceId <= 0x13BF) || // GM107 49 | (deviceId >= 0x13C0 && deviceId <= 0x13FF) || // GM204 50 | (deviceId >= 0x1617 && deviceId <= 0x161A) || // GM204 51 | (deviceId == 0x1667) || // GM204 52 | (deviceId >= 0x17C0 && deviceId <= 0x17FF)) { // GM200 53 | // Avoid HEVC on Feature Set E GPUs 54 | return (videoFormat & VIDEO_FORMAT_MASK_H265) != 0; 55 | } 56 | } 57 | 58 | return false; 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/dxva2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | #include "pacer/pacer.h" 5 | 6 | #include 7 | #include 8 | 9 | extern "C" { 10 | #include 11 | } 12 | 13 | #include 14 | 15 | class DXVA2Renderer : public IFFmpegRenderer 16 | { 17 | public: 18 | DXVA2Renderer(int decoderSelectionPass); 19 | virtual ~DXVA2Renderer() override; 20 | virtual bool initialize(PDECODER_PARAMETERS params) override; 21 | virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override; 22 | virtual void renderFrame(AVFrame* frame) override; 23 | virtual void notifyOverlayUpdated(Overlay::OverlayType type) override; 24 | virtual int getDecoderColorspace() override; 25 | virtual int getDecoderCapabilities() override; 26 | 27 | private: 28 | bool initializeDecoder(); 29 | bool initializeRenderer(); 30 | bool initializeDevice(SDL_Window* window, bool enableVsync); 31 | bool isDecoderBlacklisted(); 32 | bool initializeQuirksForAdapter(IDirect3D9Ex* d3d9ex, int adapterIndex); 33 | void renderOverlay(Overlay::OverlayType type); 34 | 35 | #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(56, 68, 0) 36 | #define FF_POOL_SIZE_TYPE size_t 37 | #else 38 | #define FF_POOL_SIZE_TYPE int 39 | #endif 40 | 41 | static 42 | AVBufferRef* ffPoolAlloc(void* opaque, FF_POOL_SIZE_TYPE size); 43 | 44 | static 45 | void ffPoolDummyDelete(void*, uint8_t*); 46 | 47 | static 48 | int ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int flags); 49 | 50 | int m_DecoderSelectionPass; 51 | 52 | int m_VideoFormat; 53 | int m_VideoWidth; 54 | int m_VideoHeight; 55 | 56 | int m_DisplayWidth; 57 | int m_DisplayHeight; 58 | 59 | struct dxva_context m_DXVAContext; 60 | std::array, 19> m_DecSurfaces; 61 | std::array m_DecSurfacesRaw; // Referenced by m_DecSurfaces 62 | DXVA2_ConfigPictureDecode m_Config; 63 | Microsoft::WRL::ComPtr m_DecService; 64 | Microsoft::WRL::ComPtr m_Decoder; 65 | int m_SurfacesUsed; 66 | AVBufferPool* m_Pool; 67 | 68 | SDL_SpinLock m_OverlayLock; 69 | std::array, Overlay::OverlayMax> m_OverlayVertexBuffers; 70 | std::array, Overlay::OverlayMax> m_OverlayTextures; 71 | 72 | Microsoft::WRL::ComPtr m_Device; 73 | Microsoft::WRL::ComPtr m_RenderTarget; 74 | Microsoft::WRL::ComPtr m_ProcService; 75 | Microsoft::WRL::ComPtr m_Processor; 76 | DXVA2_ValueRange m_BrightnessRange; 77 | DXVA2_ValueRange m_ContrastRange; 78 | DXVA2_ValueRange m_HueRange; 79 | DXVA2_ValueRange m_SaturationRange; 80 | DXVA2_VideoDesc m_Desc; 81 | REFERENCE_TIME m_FrameIndex; 82 | bool m_BlockingPresent; 83 | 84 | #define DXVA2_QUIRK_NO_VP 0x01 85 | #define DXVA2_QUIRK_SET_DEST_FORMAT 0x02 86 | #define DXVA2_QUIRK_WDDM_20_PLUS 0x04 // Unused 87 | #define DXVA2_QUIRK_MULTI_GPU 0x08 88 | int m_DeviceQuirks; 89 | }; 90 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/egl_extensions.cpp: -------------------------------------------------------------------------------- 1 | #include "renderer.h" 2 | 3 | static QStringList egl_get_extensions(EGLDisplay dpy) { 4 | const auto EGLExtensionsStr = eglQueryString(dpy, EGL_EXTENSIONS); 5 | if (!EGLExtensionsStr) { 6 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Unable to get EGL extensions: %d", eglGetError()); 7 | return QStringList(); 8 | } 9 | return QString(EGLExtensionsStr).split(" "); 10 | } 11 | 12 | EGLExtensions::EGLExtensions(EGLDisplay dpy) : 13 | m_Extensions(egl_get_extensions(dpy)) 14 | {} 15 | 16 | bool EGLExtensions::isSupported(const QString &extension) const { 17 | return m_Extensions.contains(extension); 18 | } 19 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/eglimagefactory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | 5 | #ifdef HAVE_LIBVA 6 | #include 7 | #endif 8 | 9 | class EglImageFactory 10 | { 11 | public: 12 | EglImageFactory(IFFmpegRenderer* renderer); 13 | bool initializeEGL(EGLDisplay, const EGLExtensions &ext); 14 | 15 | #ifdef HAVE_DRM 16 | ssize_t exportDRMImages(AVFrame* frame, AVDRMFrameDescriptor* drmFrame, EGLDisplay dpy, EGLImage images[EGL_MAX_PLANES]); 17 | #endif 18 | 19 | #ifdef HAVE_LIBVA 20 | ssize_t exportVAImages(AVFrame* frame, VADRMPRIMESurfaceDescriptor* vaFrame, EGLDisplay dpy, EGLImage images[EGL_MAX_PLANES]); 21 | #endif 22 | 23 | bool supportsImportingFormat(EGLDisplay dpy, EGLint format); 24 | bool supportsImportingModifier(EGLDisplay dpy, EGLint format, EGLuint64KHR modifier); 25 | 26 | void freeEGLImages(EGLDisplay dpy, EGLImage images[EGL_MAX_PLANES]); 27 | 28 | private: 29 | IFFmpegRenderer* m_Renderer; 30 | bool m_EGLExtDmaBuf; 31 | PFNEGLCREATEIMAGEPROC m_eglCreateImage; 32 | PFNEGLDESTROYIMAGEPROC m_eglDestroyImage; 33 | PFNEGLCREATEIMAGEKHRPROC m_eglCreateImageKHR; 34 | PFNEGLDESTROYIMAGEKHRPROC m_eglDestroyImageKHR; 35 | PFNEGLQUERYDMABUFFORMATSEXTPROC m_eglQueryDmaBufFormatsEXT; 36 | PFNEGLQUERYDMABUFMODIFIERSEXTPROC m_eglQueryDmaBufModifiersEXT; 37 | }; 38 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/eglvid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | 5 | #define SDL_USE_BUILTIN_OPENGL_DEFINITIONS 1 6 | #include 7 | #include 8 | 9 | class EGLRenderer : public IFFmpegRenderer { 10 | public: 11 | EGLRenderer(IFFmpegRenderer *backendRenderer); 12 | virtual ~EGLRenderer() override; 13 | virtual bool initialize(PDECODER_PARAMETERS params) override; 14 | virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override; 15 | virtual void cleanupRenderContext() override; 16 | virtual void waitToRender() override; 17 | virtual void prepareToRender() override; 18 | virtual void renderFrame(AVFrame* frame) override; 19 | virtual bool testRenderFrame(AVFrame* frame) override; 20 | virtual void notifyOverlayUpdated(Overlay::OverlayType) override; 21 | virtual bool notifyWindowChanged(PWINDOW_STATE_CHANGE_INFO) override; 22 | virtual bool isPixelFormatSupported(int videoFormat, enum AVPixelFormat pixelFormat) override; 23 | virtual AVPixelFormat getPreferredPixelFormat(int videoFormat) override; 24 | 25 | private: 26 | 27 | void renderOverlay(Overlay::OverlayType type, int viewportWidth, int viewportHeight); 28 | unsigned compileShader(const char* vertexShaderSrc, const char* fragmentShaderSrc); 29 | bool compileShaders(); 30 | bool specialize(); 31 | const float *getColorOffsets(const AVFrame* frame); 32 | const float *getColorMatrix(const AVFrame* frame); 33 | static int loadAndBuildShader(int shaderType, const char *filename); 34 | 35 | AVPixelFormat m_EGLImagePixelFormat; 36 | void *m_EGLDisplay; 37 | unsigned m_Textures[EGL_MAX_PLANES]; 38 | unsigned m_OverlayTextures[Overlay::OverlayMax]; 39 | unsigned m_OverlayVbos[Overlay::OverlayMax]; 40 | SDL_atomic_t m_OverlayHasValidData[Overlay::OverlayMax]; 41 | unsigned m_ShaderProgram; 42 | unsigned m_OverlayShaderProgram; 43 | SDL_GLContext m_Context; 44 | SDL_Window *m_Window; 45 | IFFmpegRenderer *m_Backend; 46 | unsigned int m_VAO; 47 | bool m_BlockingSwapBuffers; 48 | EGLSync m_LastRenderSync; 49 | AVFrame* m_LastFrame; 50 | PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_glEGLImageTargetTexture2DOES; 51 | PFNGLGENVERTEXARRAYSOESPROC m_glGenVertexArraysOES; 52 | PFNGLBINDVERTEXARRAYOESPROC m_glBindVertexArrayOES; 53 | PFNGLDELETEVERTEXARRAYSOESPROC m_glDeleteVertexArraysOES; 54 | PFNEGLCREATESYNCPROC m_eglCreateSync; 55 | PFNEGLCREATESYNCKHRPROC m_eglCreateSyncKHR; 56 | PFNEGLDESTROYSYNCPROC m_eglDestroySync; 57 | PFNEGLCLIENTWAITSYNCPROC m_eglClientWaitSync; 58 | int m_GlesMajorVersion; 59 | int m_GlesMinorVersion; 60 | bool m_HasExtUnpackSubimage; 61 | 62 | #define NV12_PARAM_YUVMAT 0 63 | #define NV12_PARAM_OFFSET 1 64 | #define NV12_PARAM_PLANE1 2 65 | #define NV12_PARAM_PLANE2 3 66 | #define OPAQUE_PARAM_TEXTURE 0 67 | int m_ShaderProgramParams[4]; 68 | 69 | #define OVERLAY_PARAM_TEXTURE 0 70 | int m_OverlayShaderProgramParams[1]; 71 | 72 | int m_OldContextProfileMask; 73 | int m_OldContextMajorVersion; 74 | int m_OldContextMinorVersion; 75 | 76 | SDL_Renderer *m_DummyRenderer; 77 | }; 78 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/genhwaccel.cpp: -------------------------------------------------------------------------------- 1 | #include "genhwaccel.h" 2 | 3 | GenericHwAccelRenderer::GenericHwAccelRenderer(AVHWDeviceType hwDeviceType) 4 | : IFFmpegRenderer(RendererType::Unknown), 5 | m_HwDeviceType(hwDeviceType), 6 | m_HwContext(nullptr) 7 | { 8 | 9 | } 10 | 11 | GenericHwAccelRenderer::~GenericHwAccelRenderer() 12 | { 13 | if (m_HwContext != nullptr) { 14 | av_buffer_unref(&m_HwContext); 15 | } 16 | } 17 | 18 | bool GenericHwAccelRenderer::initialize(PDECODER_PARAMETERS) 19 | { 20 | int err; 21 | 22 | err = av_hwdevice_ctx_create(&m_HwContext, m_HwDeviceType, nullptr, nullptr, 0); 23 | if (err != 0) { 24 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, 25 | "av_hwdevice_ctx_create(%u) failed: %d", 26 | m_HwDeviceType, 27 | err); 28 | return false; 29 | } 30 | 31 | return true; 32 | } 33 | 34 | bool GenericHwAccelRenderer::prepareDecoderContext(AVCodecContext* context, AVDictionary**) 35 | { 36 | context->hw_device_ctx = av_buffer_ref(m_HwContext); 37 | 38 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, 39 | "Using generic FFmpeg hwaccel backend (type: %u). Performance may not be optimal!", 40 | m_HwDeviceType); 41 | 42 | return true; 43 | } 44 | 45 | void GenericHwAccelRenderer::renderFrame(AVFrame*) 46 | { 47 | // We only support indirect rendering 48 | SDL_assert(false); 49 | } 50 | 51 | bool GenericHwAccelRenderer::needsTestFrame() 52 | { 53 | return true; 54 | } 55 | 56 | bool GenericHwAccelRenderer::isDirectRenderingSupported() 57 | { 58 | // We only support rendering via read-back 59 | return false; 60 | } 61 | 62 | int GenericHwAccelRenderer::getDecoderCapabilities() 63 | { 64 | bool ok; 65 | int caps = qEnvironmentVariableIntValue("GENHWACCEL_CAPS", &ok); 66 | if (ok) { 67 | SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, 68 | "Using GENHWACCEL_CAPS for decoder capabilities: %x", 69 | caps); 70 | } 71 | else { 72 | SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, 73 | "Assuming default decoder capabilities. Set GENHWACCEL_CAPS to override."); 74 | caps = 0; 75 | } 76 | 77 | return caps; 78 | } 79 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/genhwaccel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | 5 | class GenericHwAccelRenderer : public IFFmpegRenderer 6 | { 7 | public: 8 | GenericHwAccelRenderer(AVHWDeviceType hwDeviceType); 9 | virtual ~GenericHwAccelRenderer() override; 10 | virtual bool initialize(PDECODER_PARAMETERS) override; 11 | virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override; 12 | virtual void renderFrame(AVFrame* frame) override; 13 | virtual bool needsTestFrame() override; 14 | virtual bool isDirectRenderingSupported() override; 15 | virtual int getDecoderCapabilities() override; 16 | 17 | private: 18 | AVHWDeviceType m_HwDeviceType; 19 | AVBufferRef* m_HwContext; 20 | }; 21 | 22 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/mmal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class MmalRenderer : public IFFmpegRenderer { 11 | public: 12 | MmalRenderer(); 13 | virtual ~MmalRenderer() override; 14 | virtual bool initialize(PDECODER_PARAMETERS params) override; 15 | virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override; 16 | virtual void prepareToRender() override; 17 | virtual void renderFrame(AVFrame* frame) override; 18 | virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) override; 19 | virtual bool needsTestFrame() override; 20 | virtual int getRendererAttributes() override; 21 | virtual int getDecoderColorspace() override; 22 | 23 | private: 24 | static void InputPortCallback(MMAL_PORT_T* port, MMAL_BUFFER_HEADER_T* buffer); 25 | bool getDtDeviceStatus(QString name, bool ifUnknown); 26 | bool isMmalOverlaySupported(); 27 | void updateDisplayRegion(); 28 | 29 | MMAL_COMPONENT_T* m_Renderer; 30 | MMAL_PORT_T* m_InputPort; 31 | 32 | SDL_Renderer* m_BackgroundRenderer; 33 | SDL_Window* m_Window; 34 | int m_VideoWidth, m_VideoHeight; 35 | int m_LastWindowPosX, m_LastWindowPosY; 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pacer.h" 4 | 5 | #include 6 | 7 | // from 8 | typedef LONG NTSTATUS; 9 | typedef UINT D3DKMT_HANDLE; 10 | typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID; 11 | #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) 12 | #define STATUS_GRAPHICS_PRESENT_OCCLUDED ((NTSTATUS)0xC01E0006L) 13 | typedef struct _D3DKMT_OPENADAPTERFROMHDC { 14 | HDC hDc; 15 | D3DKMT_HANDLE hAdapter; 16 | LUID AdapterLuid; 17 | D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId; 18 | } D3DKMT_OPENADAPTERFROMHDC; 19 | typedef struct _D3DKMT_CLOSEADAPTER { 20 | D3DKMT_HANDLE hAdapter; 21 | } D3DKMT_CLOSEADAPTER; 22 | typedef struct _D3DKMT_WAITFORVERTICALBLANKEVENT { 23 | D3DKMT_HANDLE hAdapter; 24 | D3DKMT_HANDLE hDevice; 25 | D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId; 26 | } D3DKMT_WAITFORVERTICALBLANKEVENT; 27 | typedef NTSTATUS(APIENTRY* PFND3DKMTOPENADAPTERFROMHDC)(D3DKMT_OPENADAPTERFROMHDC*); 28 | typedef NTSTATUS(APIENTRY* PFND3DKMTCLOSEADAPTER)(D3DKMT_CLOSEADAPTER*); 29 | typedef NTSTATUS(APIENTRY* PFND3DKMTWAITFORVERTICALBLANKEVENT)(D3DKMT_WAITFORVERTICALBLANKEVENT*); 30 | 31 | class DxVsyncSource : public IVsyncSource 32 | { 33 | public: 34 | DxVsyncSource(Pacer* pacer); 35 | 36 | virtual ~DxVsyncSource(); 37 | 38 | virtual bool initialize(SDL_Window* window, int) override; 39 | 40 | virtual bool isAsync() override; 41 | 42 | virtual void waitForVsync() override; 43 | 44 | private: 45 | Pacer* m_Pacer; 46 | HMODULE m_Gdi32Handle; 47 | HWND m_Window; 48 | HMONITOR m_LastMonitor; 49 | D3DKMT_WAITFORVERTICALBLANKEVENT m_WaitForVblankEventParams; 50 | 51 | PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc; 52 | PFND3DKMTCLOSEADAPTER m_D3DKMTCloseAdapter; 53 | PFND3DKMTWAITFORVERTICALBLANKEVENT m_D3DKMTWaitForVerticalBlankEvent; 54 | }; 55 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/pacer/pacer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../decoder.h" 4 | #include "../renderer.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class IVsyncSource { 11 | public: 12 | virtual ~IVsyncSource() {} 13 | virtual bool initialize(SDL_Window* window, int displayFps) = 0; 14 | 15 | // Asynchronous sources produce callbacks on their own, while synchronous 16 | // sources require calls to waitForVsync(). 17 | virtual bool isAsync() = 0; 18 | 19 | virtual void waitForVsync() { 20 | // Synchronous sources must implement waitForVsync()! 21 | SDL_assert(false); 22 | } 23 | }; 24 | 25 | class Pacer 26 | { 27 | public: 28 | Pacer(IFFmpegRenderer* renderer, PVIDEO_STATS videoStats); 29 | 30 | ~Pacer(); 31 | 32 | void submitFrame(AVFrame* frame); 33 | 34 | bool initialize(SDL_Window* window, int maxVideoFps, bool enablePacing); 35 | 36 | void signalVsync(); 37 | 38 | void renderOnMainThread(); 39 | 40 | private: 41 | static int vsyncThread(void* context); 42 | 43 | static int renderThread(void* context); 44 | 45 | void handleVsync(int timeUntilNextVsyncMillis); 46 | 47 | void enqueueFrameForRenderingAndUnlock(AVFrame* frame); 48 | 49 | void renderFrame(AVFrame* frame); 50 | 51 | void dropFrameForEnqueue(QQueue& queue); 52 | 53 | QQueue m_RenderQueue; 54 | QQueue m_PacingQueue; 55 | QQueue m_PacingQueueHistory; 56 | QQueue m_RenderQueueHistory; 57 | QMutex m_FrameQueueLock; 58 | QWaitCondition m_RenderQueueNotEmpty; 59 | QWaitCondition m_PacingQueueNotEmpty; 60 | QWaitCondition m_VsyncSignalled; 61 | SDL_Thread* m_RenderThread; 62 | SDL_Thread* m_VsyncThread; 63 | bool m_Stopping; 64 | 65 | IVsyncSource* m_VsyncSource; 66 | IFFmpegRenderer* m_VsyncRenderer; 67 | int m_MaxVideoFps; 68 | int m_DisplayFps; 69 | PVIDEO_STATS m_VideoStats; 70 | int m_RendererAttributes; 71 | }; 72 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/pacer/waylandvsyncsource.cpp: -------------------------------------------------------------------------------- 1 | #include "waylandvsyncsource.h" 2 | 3 | #include 4 | 5 | #ifndef SDL_VIDEO_DRIVER_WAYLAND 6 | #warning Unable to use WaylandVsyncSource without SDL support 7 | #else 8 | 9 | const struct wl_callback_listener WaylandVsyncSource::s_FrameListener = { 10 | .done = WaylandVsyncSource::frameDone, 11 | }; 12 | 13 | WaylandVsyncSource::WaylandVsyncSource(Pacer* pacer) 14 | : m_Pacer(pacer), 15 | m_Display(nullptr), 16 | m_Surface(nullptr), 17 | m_Callback(nullptr) 18 | { 19 | 20 | } 21 | 22 | WaylandVsyncSource::~WaylandVsyncSource() 23 | { 24 | if (m_Callback != nullptr) { 25 | wl_callback_destroy(m_Callback); 26 | wl_display_roundtrip(m_Display); 27 | } 28 | } 29 | 30 | bool WaylandVsyncSource::initialize(SDL_Window* window, int) 31 | { 32 | SDL_SysWMinfo info; 33 | 34 | SDL_VERSION(&info.version); 35 | 36 | if (!SDL_GetWindowWMInfo(window, &info)) { 37 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, 38 | "SDL_GetWindowWMInfo() failed: %s", 39 | SDL_GetError()); 40 | return false; 41 | } 42 | 43 | // Pacer should not create us for non-Wayland windows 44 | SDL_assert(info.subsystem == SDL_SYSWM_WAYLAND); 45 | 46 | m_Display = info.info.wl.display; 47 | m_Surface = info.info.wl.surface; 48 | 49 | // Enqueue our first frame callback 50 | m_Callback = wl_surface_frame(m_Surface); 51 | wl_callback_add_listener(m_Callback, &s_FrameListener, this); 52 | wl_surface_commit(m_Surface); 53 | 54 | return true; 55 | } 56 | 57 | bool WaylandVsyncSource::isAsync() 58 | { 59 | // Wayland frame callbacks are asynchronous 60 | return true; 61 | } 62 | 63 | void WaylandVsyncSource::frameDone(void* data, struct wl_callback* oldCb, uint32_t) 64 | { 65 | auto me = (WaylandVsyncSource*)data; 66 | 67 | // Free this callback 68 | SDL_assert(oldCb == me->m_Callback); 69 | wl_callback_destroy(oldCb); 70 | 71 | // Wake the Pacer Vsync thread 72 | me->m_Pacer->signalVsync(); 73 | 74 | // Register for another callback 75 | me->m_Callback = wl_surface_frame(me->m_Surface); 76 | wl_callback_add_listener(me->m_Callback, &s_FrameListener, data); 77 | wl_surface_commit(me->m_Surface); 78 | wl_display_flush(me->m_Display); 79 | } 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/pacer/waylandvsyncsource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pacer.h" 4 | 5 | #include 6 | #include 7 | 8 | class WaylandVsyncSource : public IVsyncSource 9 | { 10 | public: 11 | WaylandVsyncSource(Pacer* pacer); 12 | 13 | virtual ~WaylandVsyncSource(); 14 | 15 | virtual bool initialize(SDL_Window* window, int displayFps) override; 16 | 17 | virtual bool isAsync() override; 18 | 19 | private: 20 | static void frameDone(void* data, struct wl_callback* oldCb, uint32_t time); 21 | 22 | static const struct wl_callback_listener s_FrameListener; 23 | 24 | Pacer* m_Pacer; 25 | wl_display* m_Display; 26 | wl_surface* m_Surface; 27 | wl_callback* m_Callback; 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/plvk_c.c: -------------------------------------------------------------------------------- 1 | // This compilation unit contains the implementations of libplacebo header-only libraries. 2 | // These must be compiled as C code, so they cannot be placed inside plvk.cpp. 3 | 4 | #ifdef _MSC_VER 5 | #pragma warning(push) 6 | #pragma warning(disable: 4068) // unknown pragma 7 | #pragma warning(disable: 4244) // double -> float truncation warning 8 | #pragma warning(disable: 4267) // size_t -> int truncation warning 9 | #endif 10 | 11 | #define PL_LIBAV_IMPLEMENTATION 1 12 | #include 13 | 14 | #ifdef _MSC_VER 15 | #pragma warning(pop) 16 | #endif 17 | 18 | // Provide a dummy implementation of av_stream_get_side_data() to avoid having to link with libavformat 19 | uint8_t *av_stream_get_side_data(const AVStream *stream, enum AVPacketSideDataType type, size_t *size) 20 | { 21 | (void)stream; 22 | (void)type; 23 | (void)size; 24 | return NULL; 25 | } 26 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/sdlvid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | #include "swframemapper.h" 5 | 6 | #ifdef HAVE_CUDA 7 | #include "cuda.h" 8 | #endif 9 | 10 | extern "C" { 11 | #include 12 | } 13 | 14 | class SdlRenderer : public IFFmpegRenderer { 15 | public: 16 | SdlRenderer(); 17 | virtual ~SdlRenderer() override; 18 | virtual bool initialize(PDECODER_PARAMETERS params) override; 19 | virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override; 20 | virtual void prepareToRender() override; 21 | virtual void renderFrame(AVFrame* frame) override; 22 | virtual bool isRenderThreadSupported() override; 23 | virtual bool isPixelFormatSupported(int videoFormat, enum AVPixelFormat pixelFormat) override; 24 | virtual bool testRenderFrame(AVFrame* frame) override; 25 | virtual bool notifyWindowChanged(PWINDOW_STATE_CHANGE_INFO) override; 26 | 27 | private: 28 | void renderOverlay(Overlay::OverlayType type); 29 | 30 | static void ffNoopFree(void *opaque, uint8_t *data); 31 | 32 | int m_VideoFormat; 33 | SDL_Renderer* m_Renderer; 34 | SDL_Texture* m_Texture; 35 | int m_ColorSpace; 36 | SDL_Texture* m_OverlayTextures[Overlay::OverlayMax]; 37 | SDL_Rect m_OverlayRects[Overlay::OverlayMax]; 38 | 39 | // Used for CPU conversion of YUV to RGB if needed 40 | bool m_NeedsYuvToRgbConversion; 41 | SwsContext* m_SwsContext; 42 | AVFrame* m_RgbFrame; 43 | 44 | SwFrameMapper m_SwFrameMapper; 45 | 46 | #ifdef HAVE_CUDA 47 | CUDAGLInteropHelper* m_CudaGLHelper; 48 | #endif 49 | }; 50 | 51 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/swframemapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | 5 | class SwFrameMapper 6 | { 7 | public: 8 | explicit SwFrameMapper(IFFmpegRenderer* renderer); 9 | void setVideoFormat(int videoFormat); 10 | AVFrame* getSwFrameFromHwFrame(AVFrame* hwFrame); 11 | 12 | private: 13 | bool initializeReadBackFormat(AVBufferRef* hwFrameCtxRef, AVFrame* testFrame); 14 | 15 | IFFmpegRenderer* m_Renderer; 16 | int m_VideoFormat; 17 | enum AVPixelFormat m_SwPixelFormat; 18 | bool m_MapFrame; 19 | }; 20 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/vdpau.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | 5 | extern "C" { 6 | #include 7 | #include 8 | #include 9 | } 10 | 11 | class VDPAURenderer : public IFFmpegRenderer 12 | { 13 | public: 14 | VDPAURenderer(int decoderSelectionPass); 15 | virtual ~VDPAURenderer() override; 16 | virtual bool initialize(PDECODER_PARAMETERS params) override; 17 | virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override; 18 | virtual void notifyOverlayUpdated(Overlay::OverlayType type) override; 19 | virtual void waitToRender() override; 20 | virtual void renderFrame(AVFrame* frame) override; 21 | virtual bool needsTestFrame() override; 22 | virtual int getDecoderColorspace() override; 23 | virtual int getDecoderCapabilities() override; 24 | 25 | private: 26 | void renderOverlay(VdpOutputSurface destination, Overlay::OverlayType type); 27 | 28 | int m_DecoderSelectionPass; 29 | uint32_t m_VideoWidth, m_VideoHeight; 30 | uint32_t m_DisplayWidth, m_DisplayHeight; 31 | AVBufferRef* m_HwContext; 32 | VdpPresentationQueueTarget m_PresentationQueueTarget; 33 | VdpPresentationQueue m_PresentationQueue; 34 | VdpVideoMixer m_VideoMixer; 35 | VdpRGBAFormat m_OutputSurfaceFormat; 36 | VdpDevice m_Device; 37 | 38 | // We just have a single mutex to protect all overlay slots. 39 | // This is fine because the majority of time spent in the mutex 40 | // is by the render thread, which cannot contend with itself 41 | // because overlays are rendered sequentially. 42 | SDL_mutex* m_OverlayMutex; 43 | VdpBitmapSurface m_OverlaySurface[Overlay::OverlayMax]; 44 | VdpRect m_OverlayRect[Overlay::OverlayMax]; 45 | VdpOutputSurfaceRenderBlendState m_OverlayBlendState; 46 | 47 | #define OUTPUT_SURFACE_COUNT 3 48 | VdpOutputSurface m_OutputSurface[OUTPUT_SURFACE_COUNT]; 49 | int m_NextSurfaceIndex; 50 | 51 | #define OUTPUT_SURFACE_FORMAT_COUNT 2 52 | static const VdpRGBAFormat k_OutputFormats8Bit[OUTPUT_SURFACE_FORMAT_COUNT]; 53 | static const VdpRGBAFormat k_OutputFormats10Bit[OUTPUT_SURFACE_FORMAT_COUNT]; 54 | 55 | VdpGetErrorString* m_VdpGetErrorString; 56 | VdpPresentationQueueTargetDestroy* m_VdpPresentationQueueTargetDestroy; 57 | VdpVideoMixerCreate* m_VdpVideoMixerCreate; 58 | VdpVideoMixerDestroy* m_VdpVideoMixerDestroy; 59 | VdpVideoMixerRender* m_VdpVideoMixerRender; 60 | VdpPresentationQueueCreate* m_VdpPresentationQueueCreate; 61 | VdpPresentationQueueDestroy* m_VdpPresentationQueueDestroy; 62 | VdpPresentationQueueDisplay* m_VdpPresentationQueueDisplay; 63 | VdpPresentationQueueSetBackgroundColor* m_VdpPresentationQueueSetBackgroundColor; 64 | VdpPresentationQueueBlockUntilSurfaceIdle* m_VdpPresentationQueueBlockUntilSurfaceIdle; 65 | VdpOutputSurfaceCreate* m_VdpOutputSurfaceCreate; 66 | VdpOutputSurfaceDestroy* m_VdpOutputSurfaceDestroy; 67 | VdpOutputSurfaceQueryCapabilities* m_VdpOutputSurfaceQueryCapabilities; 68 | VdpBitmapSurfaceCreate* m_VdpBitmapSurfaceCreate; 69 | VdpBitmapSurfaceDestroy* m_VdpBitmapSurfaceDestroy; 70 | VdpBitmapSurfacePutBitsNative* m_VdpBitmapSurfacePutBitsNative; 71 | VdpOutputSurfaceRenderBitmapSurface* m_VdpOutputSurfaceRenderBitmapSurface; 72 | VdpVideoSurfaceGetParameters* m_VdpVideoSurfaceGetParameters; 73 | VdpGetInformationString* m_VdpGetInformationString; 74 | 75 | // X11 stuff 76 | VdpPresentationQueueTargetCreateX11* m_VdpPresentationQueueTargetCreateX11; 77 | }; 78 | 79 | 80 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/vt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "renderer.h" 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | class VTBaseRenderer : public IFFmpegRenderer { 8 | public: 9 | VTBaseRenderer(IFFmpegRenderer::RendererType type) : IFFmpegRenderer(type) {} 10 | bool checkDecoderCapabilities(id device, PDECODER_PARAMETERS params); 11 | }; 12 | #endif 13 | 14 | // A factory is required to avoid pulling in 15 | // incompatible Objective-C headers. 16 | 17 | class VTMetalRendererFactory { 18 | public: 19 | static 20 | IFFmpegRenderer* createRenderer(bool hwAccel); 21 | }; 22 | 23 | class VTRendererFactory { 24 | public: 25 | static 26 | IFFmpegRenderer* createRenderer(); 27 | }; 28 | -------------------------------------------------------------------------------- /app/streaming/video/ffmpeg-renderers/vt_base.mm: -------------------------------------------------------------------------------- 1 | // Nasty hack to avoid conflict between AVFoundation and 2 | // libavutil both defining AVMediaType 3 | #define AVMediaType AVMediaType_FFmpeg 4 | #include "vt.h" 5 | #undef AVMediaType 6 | 7 | #import 8 | #import 9 | #import 10 | #import 11 | 12 | bool VTBaseRenderer::checkDecoderCapabilities(id device, PDECODER_PARAMETERS params) { 13 | if (params->videoFormat & VIDEO_FORMAT_MASK_H264) { 14 | if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_H264)) { 15 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, 16 | "No HW accelerated H.264 decode via VT"); 17 | return false; 18 | } 19 | } 20 | else if (params->videoFormat & VIDEO_FORMAT_MASK_H265) { 21 | if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) { 22 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, 23 | "No HW accelerated HEVC decode via VT"); 24 | return false; 25 | } 26 | 27 | // HEVC Main10 requires more extensive checks because there's no 28 | // simple API to check for Main10 hardware decoding, and if we don't 29 | // have it, we'll silently get software decoding with horrible performance. 30 | if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) { 31 | // Exclude all GPUs earlier than macOSGPUFamily2 32 | // https://developer.apple.com/documentation/metal/mtlfeatureset/mtlfeatureset_macos_gpufamily2_v1 33 | if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1]) { 34 | if ([device.name containsString:@"Intel"]) { 35 | // 500-series Intel GPUs are Skylake and don't support Main10 hardware decoding 36 | if ([device.name containsString:@" 5"]) { 37 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, 38 | "No HEVC Main10 support on Skylake iGPU"); 39 | return false; 40 | } 41 | } 42 | else if ([device.name containsString:@"AMD"]) { 43 | // FirePro D, M200, and M300 series GPUs don't support Main10 hardware decoding 44 | if ([device.name containsString:@"FirePro D"] || 45 | [device.name containsString:@" M2"] || 46 | [device.name containsString:@" M3"]) { 47 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, 48 | "No HEVC Main10 support on AMD GPUs until Polaris"); 49 | return false; 50 | } 51 | } 52 | } 53 | else { 54 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, 55 | "No HEVC Main10 support on macOS GPUFamily1 GPUs"); 56 | return false; 57 | } 58 | } 59 | } 60 | else if (params->videoFormat & VIDEO_FORMAT_MASK_AV1) { 61 | #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130000 62 | if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_AV1)) { 63 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, 64 | "No HW accelerated AV1 decode via VT"); 65 | return false; 66 | } 67 | 68 | // 10-bit is part of the Main profile for AV1, so it will always 69 | // be present on hardware that supports 8-bit. 70 | #else 71 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, 72 | "AV1 requires building with Xcode 14 or later"); 73 | return false; 74 | #endif 75 | } 76 | 77 | return true; 78 | } 79 | -------------------------------------------------------------------------------- /app/streaming/video/overlaymanager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "SDL_compat.h" 6 | #include 7 | 8 | namespace Overlay { 9 | 10 | enum OverlayType { 11 | OverlayDebug, 12 | OverlayStatusUpdate, 13 | OverlayMax 14 | }; 15 | 16 | class IOverlayRenderer 17 | { 18 | public: 19 | virtual ~IOverlayRenderer() = default; 20 | 21 | virtual void notifyOverlayUpdated(OverlayType type) = 0; 22 | }; 23 | 24 | class OverlayManager 25 | { 26 | public: 27 | OverlayManager(); 28 | ~OverlayManager(); 29 | 30 | bool isOverlayEnabled(OverlayType type); 31 | char* getOverlayText(OverlayType type); 32 | void updateOverlayText(OverlayType type, const char* text); 33 | int getOverlayMaxTextLength(); 34 | void setOverlayTextUpdated(OverlayType type); 35 | void setOverlayState(OverlayType type, bool enabled); 36 | SDL_Color getOverlayColor(OverlayType type); 37 | int getOverlayFontSize(OverlayType type); 38 | SDL_Surface* getUpdatedOverlaySurface(OverlayType type); 39 | 40 | void setOverlayRenderer(IOverlayRenderer* renderer); 41 | 42 | private: 43 | void notifyOverlayUpdated(OverlayType type); 44 | 45 | struct { 46 | bool enabled; 47 | int fontSize; 48 | SDL_Color color; 49 | char text[512]; 50 | 51 | TTF_Font* font; 52 | SDL_Surface* surface; 53 | } m_Overlays[OverlayMax]; 54 | IOverlayRenderer* m_Renderer; 55 | QByteArray m_FontData; 56 | }; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /app/streaming/video/slvid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "decoder.h" 4 | #include "overlaymanager.h" 5 | 6 | #include 7 | 8 | class SLVideoDecoder : public IVideoDecoder, public Overlay::IOverlayRenderer 9 | { 10 | public: 11 | SLVideoDecoder(bool testOnly); 12 | virtual ~SLVideoDecoder(); 13 | virtual bool initialize(PDECODER_PARAMETERS params) override; 14 | virtual bool isHardwareAccelerated() override; 15 | virtual bool isAlwaysFullScreen() override; 16 | virtual int getDecoderCapabilities() override; 17 | virtual int getDecoderColorspace() override; 18 | virtual int getDecoderColorRange() override; 19 | virtual QSize getDecoderMaxResolution() override; 20 | virtual int submitDecodeUnit(PDECODE_UNIT du) override; 21 | virtual void notifyOverlayUpdated(Overlay::OverlayType) override; 22 | 23 | // Unused since rendering is done directly from the decode thread 24 | virtual void renderFrameOnMainThread() override {} 25 | 26 | // HDR is not supported by SLVideo 27 | virtual void setHdrMode(bool) override {} 28 | virtual bool isHdrSupported() override { 29 | return false; 30 | } 31 | 32 | // Window state changes are not supported by SLVideo 33 | virtual bool notifyWindowChanged(PWINDOW_STATE_CHANGE_INFO) { 34 | return false; 35 | } 36 | 37 | private: 38 | static void slLogCallback(void* context, ESLVideoLog logLevel, const char* message); 39 | 40 | CSLVideoContext* m_VideoContext; 41 | CSLVideoStream* m_VideoStream; 42 | CSLVideoOverlay* m_Overlay; 43 | 44 | int m_ViewportWidth; 45 | int m_ViewportHeight; 46 | }; 47 | -------------------------------------------------------------------------------- /app/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define THROW_BAD_ALLOC_IF_NULL(x) \ 6 | if ((x) == nullptr) throw std::bad_alloc() 7 | 8 | namespace WMUtils { 9 | bool isRunningX11(); 10 | bool isRunningWayland(); 11 | bool isRunningWindowManager(); 12 | bool isRunningDesktopEnvironment(); 13 | QString getDrmCardOverride(); 14 | } 15 | -------------------------------------------------------------------------------- /app/version.txt: -------------------------------------------------------------------------------- 1 | 6.1.0 -------------------------------------------------------------------------------- /app/wm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "utils.h" 5 | 6 | #include "SDL_compat.h" 7 | 8 | #ifdef HAS_X11 9 | #include 10 | #endif 11 | 12 | #ifdef HAS_WAYLAND 13 | #include 14 | #endif 15 | 16 | #ifdef HAVE_DRM 17 | #include 18 | #include 19 | #endif 20 | 21 | #define VALUE_SET 0x01 22 | #define VALUE_TRUE 0x02 23 | 24 | bool WMUtils::isRunningX11() 25 | { 26 | #ifdef HAS_X11 27 | static SDL_atomic_t isRunningOnX11; 28 | 29 | // If the value is not set yet, populate it now. 30 | int val = SDL_AtomicGet(&isRunningOnX11); 31 | if (!(val & VALUE_SET)) { 32 | Display* display = XOpenDisplay(nullptr); 33 | if (display != nullptr) { 34 | XCloseDisplay(display); 35 | } 36 | 37 | // Populate the value to return and have for next time. 38 | // This can race with another thread populating the same data, 39 | // but that's no big deal. 40 | val = VALUE_SET | ((display != nullptr) ? VALUE_TRUE : 0); 41 | SDL_AtomicSet(&isRunningOnX11, val); 42 | } 43 | 44 | return !!(val & VALUE_TRUE); 45 | #endif 46 | 47 | return false; 48 | } 49 | 50 | bool WMUtils::isRunningWayland() 51 | { 52 | #ifdef HAS_WAYLAND 53 | static SDL_atomic_t isRunningOnWayland; 54 | 55 | // If the value is not set yet, populate it now. 56 | int val = SDL_AtomicGet(&isRunningOnWayland); 57 | if (!(val & VALUE_SET)) { 58 | struct wl_display* display = wl_display_connect(nullptr); 59 | if (display != nullptr) { 60 | wl_display_disconnect(display); 61 | } 62 | 63 | // Populate the value to return and have for next time. 64 | // This can race with another thread populating the same data, 65 | // but that's no big deal. 66 | val = VALUE_SET | ((display != nullptr) ? VALUE_TRUE : 0); 67 | SDL_AtomicSet(&isRunningOnWayland, val); 68 | } 69 | 70 | return !!(val & VALUE_TRUE); 71 | #endif 72 | 73 | return false; 74 | } 75 | 76 | bool WMUtils::isRunningWindowManager() 77 | { 78 | #if defined(Q_OS_WIN) || defined(Q_OS_DARWIN) 79 | // Windows and macOS are always running a window manager 80 | return true; 81 | #else 82 | // On Unix OSes, look for Wayland or X 83 | return WMUtils::isRunningWayland() || WMUtils::isRunningX11(); 84 | #endif 85 | } 86 | 87 | bool WMUtils::isRunningDesktopEnvironment() 88 | { 89 | if (qEnvironmentVariableIsSet("HAS_DESKTOP_ENVIRONMENT")) { 90 | return qEnvironmentVariableIntValue("HAS_DESKTOP_ENVIRONMENT"); 91 | } 92 | 93 | #if defined(Q_OS_WIN) || defined(Q_OS_DARWIN) 94 | // Windows and macOS are always running a desktop environment 95 | return true; 96 | #elif defined(EMBEDDED_BUILD) 97 | // Embedded systems don't run desktop environments 98 | return false; 99 | #else 100 | // On non-embedded systems, assume we have a desktop environment 101 | // if we have a WM running. 102 | return isRunningWindowManager(); 103 | #endif 104 | } 105 | 106 | QString WMUtils::getDrmCardOverride() 107 | { 108 | #ifdef HAVE_DRM 109 | QDir dir("/dev/dri"); 110 | QStringList cardList = dir.entryList(QStringList("card*"), QDir::Files | QDir::System); 111 | if (cardList.length() == 0) { 112 | return QString(); 113 | } 114 | 115 | bool needsOverride = false; 116 | for (const QString& card : cardList) { 117 | QFile cardFd(dir.filePath(card)); 118 | if (!cardFd.open(QFile::ReadOnly)) { 119 | continue; 120 | } 121 | 122 | auto resources = drmModeGetResources(cardFd.handle()); 123 | if (resources == nullptr) { 124 | // If we find a card that doesn't have a display before a card that 125 | // has one, we'll need to override Qt's EGLFS config because they 126 | // don't properly handle cards without displays. 127 | needsOverride = true; 128 | } 129 | else { 130 | // We found a card with a display 131 | drmModeFreeResources(resources); 132 | if (needsOverride) { 133 | // Override the default card with this one 134 | return dir.filePath(card); 135 | } 136 | else { 137 | return QString(); 138 | } 139 | } 140 | } 141 | #endif 142 | 143 | return QString(); 144 | } 145 | -------------------------------------------------------------------------------- /config.tests/EGL/EGL.pro: -------------------------------------------------------------------------------- 1 | SOURCES = main.cpp 2 | 3 | CONFIG += link_pkgconfig 4 | 5 | # This adds /opt/vc/include to the include path which 6 | # pulls in the BRCM GLES and EGL libraries. If we don't 7 | # add this, we'll get the system's headers which expose 8 | # functionality the runtime GL implementation won't have. 9 | packagesExist(mmal) { 10 | PKGCONFIG += mmal 11 | } 12 | 13 | PKGCONFIG += sdl2 egl libavcodec libavutil -------------------------------------------------------------------------------- /config.tests/EGL/main.cpp: -------------------------------------------------------------------------------- 1 | #define SDL_USE_BUILTIN_OPENGL_DEFINITIONS 1 2 | 3 | #include 4 | #include 5 | 6 | #ifndef EGL_VERSION_1_4 7 | #error EGLRenderer requires EGL 1.4 8 | #endif 9 | 10 | #ifndef GL_ES_VERSION_2_0 11 | #error EGLRenderer requires OpenGL ES 2.0 12 | #endif 13 | 14 | int main() { 15 | return 0; 16 | } -------------------------------------------------------------------------------- /config.tests/SL/SL.pro: -------------------------------------------------------------------------------- 1 | SOURCES = main.cpp 2 | LIBS += -lSLVideo -lSLAudio 3 | 4 | -------------------------------------------------------------------------------- /config.tests/SL/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | SLVideo_CreateContext(); 7 | SLAudio_CreateContext(); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /globaldefs.pri: -------------------------------------------------------------------------------- 1 | # Support debug and release builds from command line for CI 2 | CONFIG += debug_and_release 3 | 4 | # Ensure symbols are always generated 5 | CONFIG += force_debug_info 6 | 7 | # Disable asserts on release builds 8 | CONFIG(release, debug|release) { 9 | DEFINES += NDEBUG 10 | } 11 | 12 | # Enable ASan for Linux or macOS 13 | #CONFIG += sanitizer sanitize_address 14 | 15 | # Enable ASan for Windows 16 | #QMAKE_CFLAGS += -fsanitize=address 17 | #QMAKE_CXXFLAGS += -fsanitize=address 18 | #QMAKE_LFLAGS += -incremental:no -wholearchive:clang_rt.asan_dynamic-x86_64.lib -wholearchive:clang_rt.asan_dynamic_runtime_thunk-x86_64.lib 19 | -------------------------------------------------------------------------------- /h264bitstream/h264bitstream.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2018-10-12T15:50:59 4 | # 5 | #------------------------------------------------- 6 | 7 | 8 | QT -= core gui 9 | 10 | TARGET = h264bitstream 11 | TEMPLATE = lib 12 | 13 | # Build a static library 14 | CONFIG += staticlib 15 | 16 | # Disable warnings 17 | CONFIG += warn_off 18 | 19 | # Include global qmake defs 20 | include(../globaldefs.pri) 21 | 22 | # Older GCC versions defaulted to GNU89 23 | *-g++ { 24 | QMAKE_CFLAGS += -std=gnu99 25 | } 26 | 27 | SRC_DIR = $$PWD/h264bitstream 28 | 29 | SOURCES += \ 30 | $$SRC_DIR/h264_nal.c \ 31 | $$SRC_DIR/h264_sei.c \ 32 | $$SRC_DIR/h264_stream.c 33 | 34 | HEADERS += \ 35 | $$SRC_DIR/bs.h \ 36 | $$SRC_DIR/h264_sei.h \ 37 | $$SRC_DIR/h264_stream.h 38 | 39 | INCLUDEPATH += $$INC_DIR 40 | -------------------------------------------------------------------------------- /moonlight-common-c/moonlight-common-c.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2018-05-05T17:41:00 4 | # 5 | #------------------------------------------------- 6 | 7 | QT -= core gui 8 | 9 | TARGET = moonlight-common-c 10 | TEMPLATE = lib 11 | 12 | # Build a static library 13 | CONFIG += staticlib 14 | 15 | # Include global qmake defs 16 | include(../globaldefs.pri) 17 | 18 | win32 { 19 | contains(QT_ARCH, i386) { 20 | INCLUDEPATH += $$PWD/../libs/windows/include/x86 21 | } 22 | contains(QT_ARCH, x86_64) { 23 | INCLUDEPATH += $$PWD/../libs/windows/include/x64 24 | } 25 | contains(QT_ARCH, arm64) { 26 | INCLUDEPATH += $$PWD/../libs/windows/include/arm64 27 | } 28 | 29 | INCLUDEPATH += $$PWD/../libs/windows/include 30 | DEFINES += HAS_QOS_FLOWID=1 HAS_PQOS_FLOWID=1 31 | } 32 | macx { 33 | INCLUDEPATH += $$PWD/../libs/mac/include 34 | } 35 | unix:!macx { 36 | CONFIG += link_pkgconfig 37 | PKGCONFIG += openssl 38 | DEFINES += HAVE_CLOCK_GETTIME=1 39 | } 40 | 41 | COMMON_C_DIR = $$PWD/moonlight-common-c 42 | ENET_DIR = $$COMMON_C_DIR/enet 43 | RS_DIR = $$COMMON_C_DIR/reedsolomon 44 | SOURCES += \ 45 | $$RS_DIR/rs.c \ 46 | $$ENET_DIR/callbacks.c \ 47 | $$ENET_DIR/compress.c \ 48 | $$ENET_DIR/host.c \ 49 | $$ENET_DIR/list.c \ 50 | $$ENET_DIR/packet.c \ 51 | $$ENET_DIR/peer.c \ 52 | $$ENET_DIR/protocol.c \ 53 | $$ENET_DIR/unix.c \ 54 | $$ENET_DIR/win32.c \ 55 | $$COMMON_C_DIR/src/AudioStream.c \ 56 | $$COMMON_C_DIR/src/ByteBuffer.c \ 57 | $$COMMON_C_DIR/src/Connection.c \ 58 | $$COMMON_C_DIR/src/ConnectionTester.c \ 59 | $$COMMON_C_DIR/src/ControlStream.c \ 60 | $$COMMON_C_DIR/src/FakeCallbacks.c \ 61 | $$COMMON_C_DIR/src/InputStream.c \ 62 | $$COMMON_C_DIR/src/LinkedBlockingQueue.c \ 63 | $$COMMON_C_DIR/src/Misc.c \ 64 | $$COMMON_C_DIR/src/Platform.c \ 65 | $$COMMON_C_DIR/src/PlatformCrypto.c \ 66 | $$COMMON_C_DIR/src/PlatformSockets.c \ 67 | $$COMMON_C_DIR/src/RtpAudioQueue.c \ 68 | $$COMMON_C_DIR/src/RtpVideoQueue.c \ 69 | $$COMMON_C_DIR/src/RtspConnection.c \ 70 | $$COMMON_C_DIR/src/RtspParser.c \ 71 | $$COMMON_C_DIR/src/SdpGenerator.c \ 72 | $$COMMON_C_DIR/src/SimpleStun.c \ 73 | $$COMMON_C_DIR/src/VideoDepacketizer.c \ 74 | $$COMMON_C_DIR/src/VideoStream.c 75 | HEADERS += \ 76 | $$COMMON_C_DIR/src/Limelight.h 77 | INCLUDEPATH += \ 78 | $$RS_DIR \ 79 | $$ENET_DIR/include \ 80 | $$COMMON_C_DIR/src 81 | DEFINES += HAS_SOCKLEN_T 82 | 83 | CONFIG(debug, debug|release) { 84 | # Enable asserts on debug builds 85 | DEFINES += LC_DEBUG 86 | } 87 | 88 | # Older GCC versions defaulted to GNU89 89 | *-g++ { 90 | QMAKE_CFLAGS += -std=gnu99 91 | } 92 | 93 | # Disable unused parameter warnings on GCC and Clang 94 | *-g++|*-clang* { 95 | QMAKE_CFLAGS_WARN_ON += -Wno-unused-parameter 96 | } 97 | -------------------------------------------------------------------------------- /moonlight-qt.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | SUBDIRS = \ 3 | moonlight-common-c \ 4 | qmdnsengine \ 5 | app \ 6 | h264bitstream 7 | 8 | # Build the dependencies in parallel before the final app 9 | app.depends = qmdnsengine moonlight-common-c h264bitstream 10 | win32:!winrt { 11 | SUBDIRS += AntiHooking 12 | app.depends += AntiHooking 13 | } 14 | !winrt:win32|macx { 15 | SUBDIRS += soundio 16 | app.depends += soundio 17 | } 18 | 19 | # Support debug and release builds from command line for CI 20 | CONFIG += debug_and_release 21 | 22 | # Run our compile tests 23 | load(configure) 24 | qtCompileTest(SL) 25 | qtCompileTest(EGL) 26 | -------------------------------------------------------------------------------- /qmdnsengine/qmdnsengine.pro: -------------------------------------------------------------------------------- 1 | QT -= gui 2 | QT += network 3 | 4 | TARGET = qmdnsengine 5 | TEMPLATE = lib 6 | 7 | # Build a static library 8 | CONFIG += staticlib 9 | 10 | # Disable warnings 11 | CONFIG += warn_off 12 | 13 | # C++11 is required to build 14 | CONFIG += c++11 15 | 16 | # Include global qmake defs 17 | include(../globaldefs.pri) 18 | 19 | QMDNSE_DIR = $$PWD/qmdnsengine/src 20 | DEFINES += \ 21 | QT_NO_SIGNALS_SLOTS_KEYWORDS 22 | 23 | # These are all required to be defined here not just 24 | # to get them loaded into the IDE nicely, but more 25 | # importantly, to get MOC to run on them properly. 26 | HEADERS += \ 27 | $$QMDNSE_DIR/include/qmdnsengine/abstractserver.h \ 28 | $$QMDNSE_DIR/include/qmdnsengine/bitmap.h \ 29 | $$QMDNSE_DIR/include/qmdnsengine/browser.h \ 30 | $$QMDNSE_DIR/include/qmdnsengine/cache.h \ 31 | $$QMDNSE_DIR/include/qmdnsengine/dns.h \ 32 | $$QMDNSE_DIR/include/qmdnsengine/hostname.h \ 33 | $$QMDNSE_DIR/include/qmdnsengine/mdns.h \ 34 | $$QMDNSE_DIR/include/qmdnsengine/message.h \ 35 | $$QMDNSE_DIR/include/qmdnsengine/prober.h \ 36 | $$QMDNSE_DIR/include/qmdnsengine/provider.h \ 37 | $$QMDNSE_DIR/include/qmdnsengine/query.h \ 38 | $$QMDNSE_DIR/include/qmdnsengine/record.h \ 39 | $$QMDNSE_DIR/include/qmdnsengine/resolver.h \ 40 | $$QMDNSE_DIR/include/qmdnsengine/server.h \ 41 | $$QMDNSE_DIR/include/qmdnsengine/service.h \ 42 | $$QMDNSE_DIR/src/bitmap_p.h \ 43 | $$QMDNSE_DIR/src/browser_p.h \ 44 | $$QMDNSE_DIR/src/cache_p.h \ 45 | $$QMDNSE_DIR/src/hostname_p.h \ 46 | $$QMDNSE_DIR/src/message_p.h \ 47 | $$QMDNSE_DIR/src/prober_p.h \ 48 | $$QMDNSE_DIR/src/provider_p.h \ 49 | $$QMDNSE_DIR/src/query_p.h \ 50 | $$QMDNSE_DIR/src/record_p.h \ 51 | $$QMDNSE_DIR/src/resolver_p.h \ 52 | $$QMDNSE_DIR/src/server_p.h \ 53 | $$QMDNSE_DIR/src/service_p.h 54 | 55 | SOURCES += \ 56 | $$QMDNSE_DIR/src/abstractserver.cpp \ 57 | $$QMDNSE_DIR/src/bitmap.cpp \ 58 | $$QMDNSE_DIR/src/browser.cpp \ 59 | $$QMDNSE_DIR/src/cache.cpp \ 60 | $$QMDNSE_DIR/src/dns.cpp \ 61 | $$QMDNSE_DIR/src/hostname.cpp \ 62 | $$QMDNSE_DIR/src/mdns.cpp \ 63 | $$QMDNSE_DIR/src/message.cpp \ 64 | $$QMDNSE_DIR/src/prober.cpp \ 65 | $$QMDNSE_DIR/src/provider.cpp \ 66 | $$QMDNSE_DIR/src/query.cpp \ 67 | $$QMDNSE_DIR/src/record.cpp \ 68 | $$QMDNSE_DIR/src/resolver.cpp \ 69 | $$QMDNSE_DIR/src/server.cpp \ 70 | $$QMDNSE_DIR/src/service.cpp 71 | 72 | INCLUDEPATH += \ 73 | $$QMDNSE_DIR/include \ 74 | $$PWD/qmdnsengine 75 | -------------------------------------------------------------------------------- /qmdnsengine/qmdnsengine_export.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Nathan Osman 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef QMDNSENGINE_EXPORT_H 26 | #define QMDNSENGINE_EXPORT_H 27 | 28 | #include 29 | 30 | #if defined(BUILD_SHARED_LIBS) 31 | # if defined(QMDNSENGINE_LIBRARY) 32 | # define QMDNSENGINE_EXPORT Q_DECL_EXPORT 33 | # else 34 | # define QMDNSENGINE_EXPORT Q_DECL_IMPORT 35 | # endif 36 | #else 37 | # define QMDNSENGINE_EXPORT 38 | #endif 39 | 40 | #endif // QMDNSENGINE_EXPORT_H 41 | -------------------------------------------------------------------------------- /scripts/appveyor/qmake.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | %QTDIR%\msvc2022_64\bin\qmake.exe -qtconf "%~dp0\target_qt.conf" %* 3 | -------------------------------------------------------------------------------- /scripts/appveyor/qtpaths.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | %QTDIR%\msvc2022_64\bin\qtpaths.exe -qtconf "%~dp0\target_qt.conf" %* 3 | -------------------------------------------------------------------------------- /scripts/appveyor/target_qt.conf: -------------------------------------------------------------------------------- 1 | [DevicePaths] 2 | Prefix=C:/Qt/Qt-6.8 3 | [Paths] 4 | Prefix=../ 5 | Documentation=./doc 6 | Headers=include 7 | Libraries=lib 8 | LibraryExecutables=./bin 9 | Binaries=bin 10 | Plugins=./plugins 11 | QmlImports=./qml 12 | ArchData=. 13 | Data=. 14 | Translations=./translations 15 | Examples=examples 16 | Tests=tests 17 | Settings=etc/xdg 18 | HostPrefix=../../msvc2022_64 19 | HostBinaries=bin 20 | HostLibraries=lib 21 | HostLibraryExecutables=./bin 22 | HostData=../msvc2022_arm64 23 | Sysroot= 24 | SysrootifyPrefix=false 25 | TargetSpec=win32-arm64-msvc 26 | HostSpec= 27 | Documentation=../../Docs/Qt-6.8 28 | Examples=../../Examples/Qt-6.8 -------------------------------------------------------------------------------- /scripts/build-appimage.sh: -------------------------------------------------------------------------------- 1 | BUILD_CONFIG="release" 2 | 3 | fail() 4 | { 5 | echo "$1" 1>&2 6 | exit 1 7 | } 8 | 9 | BUILD_ROOT=$PWD/build 10 | SOURCE_ROOT=$PWD 11 | BUILD_FOLDER=$BUILD_ROOT/build-$BUILD_CONFIG 12 | DEPLOY_FOLDER=$BUILD_ROOT/deploy-$BUILD_CONFIG 13 | INSTALLER_FOLDER=$BUILD_ROOT/installer-$BUILD_CONFIG 14 | VERSION=`cat $SOURCE_ROOT/app/version.txt` 15 | 16 | command -v qmake >/dev/null 2>&1 || fail "Unable to find 'qmake' in your PATH!" 17 | command -v linuxdeployqt >/dev/null 2>&1 || fail "Unable to find 'linuxdeployqt' in your PATH!" 18 | 19 | echo Cleaning output directories 20 | rm -rf $BUILD_FOLDER 21 | rm -rf $DEPLOY_FOLDER 22 | rm -rf $INSTALLER_FOLDER 23 | mkdir $BUILD_ROOT 24 | mkdir $BUILD_FOLDER 25 | mkdir $DEPLOY_FOLDER 26 | mkdir $INSTALLER_FOLDER 27 | 28 | echo Configuring the project 29 | pushd $BUILD_FOLDER 30 | # Building with Wayland support will cause linuxdeployqt to include libwayland-client.so in the AppImage. 31 | # Since we always use the host implementation of EGL, this can cause libEGL_mesa.so to fail to load due 32 | # to missing symbols from the host's version of libwayland-client.so that aren't present in the older 33 | # version of libwayland-client.so from our AppImage build environment. When this happens, EGL fails to 34 | # work even in X11. To avoid this, we will disable Wayland support for the AppImage. 35 | # 36 | # We disable DRM support because linuxdeployqt doesn't bundle the appropriate libraries for Qt EGLFS. 37 | qmake $SOURCE_ROOT/moonlight-qt.pro CONFIG+=disable-wayland CONFIG+=disable-libdrm CONFIG+=disable-cuda PREFIX=$DEPLOY_FOLDER/usr DEFINES+=APP_IMAGE || fail "Qmake failed!" 38 | popd 39 | 40 | echo Compiling Moonlight in $BUILD_CONFIG configuration 41 | pushd $BUILD_FOLDER 42 | make -j$(nproc) $(echo "$BUILD_CONFIG" | tr '[:upper:]' '[:lower:]') || fail "Make failed!" 43 | popd 44 | 45 | echo Deploying to staging directory 46 | pushd $BUILD_FOLDER 47 | make install || fail "Make install failed!" 48 | popd 49 | 50 | echo Creating AppImage 51 | pushd $INSTALLER_FOLDER 52 | VERSION=$VERSION linuxdeployqt $DEPLOY_FOLDER/usr/share/applications/com.moonlight_stream.Moonlight.desktop -qmldir=$SOURCE_ROOT/app/gui -appimage || fail "linuxdeployqt failed!" 53 | popd 54 | 55 | echo Build successful -------------------------------------------------------------------------------- /scripts/build-steamlink-app.sh: -------------------------------------------------------------------------------- 1 | BUILD_CONFIG="release" 2 | QT_514_COMMIT="609d4aaccb503298e7fa9cef45e0ddc4c4afd63c" 3 | 4 | fail() 5 | { 6 | echo "$1" 1>&2 7 | exit 1 8 | } 9 | 10 | if [ "$STEAMLINK_SDK_PATH" == "" ]; then 11 | fail "You must set STEAMLINK_SDK_PATH to build for Steam Link" 12 | fi 13 | 14 | BUILD_ROOT=$PWD/build 15 | SOURCE_ROOT=$PWD 16 | BUILD_FOLDER=$BUILD_ROOT/build-$BUILD_CONFIG 17 | DEPLOY_FOLDER=$BUILD_ROOT/deploy-$BUILD_CONFIG 18 | INSTALLER_FOLDER=$BUILD_ROOT/installer-$BUILD_CONFIG 19 | VERSION=`cat $SOURCE_ROOT/app/version.txt` 20 | 21 | echo Cleaning output directories 22 | rm -rf $BUILD_FOLDER 23 | rm -rf $DEPLOY_FOLDER 24 | rm -rf $INSTALLER_FOLDER 25 | mkdir $BUILD_ROOT 26 | mkdir $BUILD_FOLDER 27 | mkdir $DEPLOY_FOLDER 28 | mkdir $INSTALLER_FOLDER 29 | 30 | echo Initializing Steam Link SDK 31 | source $STEAMLINK_SDK_PATH/setenv.sh || fail "SL SDK initialization failed!" 32 | 33 | echo Configuring the project 34 | pushd $BUILD_FOLDER 35 | qmake $SOURCE_ROOT/moonlight-qt.pro QMAKE_CFLAGS_ISYSTEM= || fail "Qmake failed!" 36 | popd 37 | 38 | echo Compiling Moonlight in $BUILD_CONFIG configuration 39 | pushd $BUILD_FOLDER 40 | make -j$(nproc) $(echo "$BUILD_CONFIG" | tr '[:upper:]' '[:lower:]') || fail "Make failed!" 41 | popd 42 | 43 | echo Creating app bundle 44 | mkdir -p $DEPLOY_FOLDER/steamlink/apps/moonlight/bin 45 | cp $BUILD_FOLDER/app/moonlight $DEPLOY_FOLDER/steamlink/apps/moonlight/bin/ || fail "Binary copy failed!" 46 | cp $SOURCE_ROOT/app/deploy/steamlink/* $DEPLOY_FOLDER/steamlink/apps/moonlight/ || fail "Metadata copy failed!" 47 | pushd $DEPLOY_FOLDER 48 | zip -r $INSTALLER_FOLDER/Moonlight-SteamLink-$VERSION.zip . || fail "Zip failed!" 49 | popd 50 | 51 | echo Build completed -------------------------------------------------------------------------------- /scripts/clean-libs.sh: -------------------------------------------------------------------------------- 1 | LIB_PATH=$(pwd)/libs 2 | 3 | while [[ "$#" -gt 0 ]]; do 4 | echo $1 5 | case "$1" in 6 | --sdl2_win) 7 | rm $LIB_PATH/windows/include/*/begin_code.h $LIB_PATH/windows/include/*/close_code.h $LIB_PATH/windows/include/*/SDL*.h $LIB_PATH/windows/lib/*/SDL2.* $LIB_PATH/windows/lib/*/SDL2main.* 8 | shift 9 | ;; 10 | --sdl2_mac) 11 | rm -r $LIB_PATH/mac/Frameworks/SDL2.framework 12 | shift 13 | ;; 14 | --sdl_ttf_win) 15 | rm $LIB_PATH/windows/include/SDL_ttf.h $LIB_PATH/windows/lib/*/SDL2_ttf.* 16 | shift 17 | ;; 18 | --detours_win) 19 | rm $LIB_PATH/windows/include/detver.h $LIB_PATH/windows/include/detours.h $LIB_PATH/windows/lib/*/detours.* 20 | shift 21 | ;; 22 | --discord-rpc_win) 23 | rm $LIB_PATH/windows/include/discord_*.h $LIB_PATH/windows/lib/*/discord-rpc.* 24 | shift 25 | ;; 26 | --discord-rpc_mac) 27 | rm $LIB_PATH/mac/include/discord_*.h $LIB_PATH/mac/lib/libdiscord-rpc.a 28 | shift 29 | ;; 30 | --opus_win) 31 | rm $LIB_PATH/windows/include/opus*.h $LIB_PATH/windows/lib/*/opus.* 32 | shift 33 | ;; 34 | --opus_mac) 35 | rm $LIB_PATH/mac/include/opus*.h $LIB_PATH/mac/lib/libopus.a 36 | shift 37 | ;; 38 | --openssl_win) 39 | rm -r $LIB_PATH/windows/include/*/openssl 40 | rm $LIB_PATH/windows/lib/*/libcrypto* $LIB_PATH/windows/lib/*/libssl* 41 | shift 42 | ;; 43 | --openssl_mac) 44 | rm -r $LIB_PATH/mac/include/openssl 45 | rm $LIB_PATH/mac/lib/libssl*.dylib $LIB_PATH/mac/lib/libcrypto*.dylib 46 | shift 47 | ;; 48 | --ffmpeg_win) 49 | rm -r $LIB_PATH/windows/include/*/libavcodec $LIB_PATH/windows/include/*/libavutil $LIB_PATH/windows/include/*/libavformat $LIB_PATH/windows/include/*/libswscale 50 | rm $LIB_PATH/windows/lib/*/avcodec* $LIB_PATH/windows/lib/*/avutil* $LIB_PATH/windows/lib/*/avformat* $LIB_PATH/windows/lib/*/swscale* 51 | shift 52 | ;; 53 | --dav1d_win) 54 | rm $LIB_PATH/windows/lib/*/dav1d* 55 | shift 56 | ;; 57 | --ffmpeg_mac) 58 | rm -r $LIB_PATH/mac/include/libavcodec $LIB_PATH/mac/include/libavutil $LIB_PATH/mac/include/libavformat $LIB_PATH/mac/include/libswscale 59 | rm $LIB_PATH/mac/lib/libavcodec* $LIB_PATH/mac/lib/libavutil* $LIB_PATH/mac/lib/libavformat* $LIB_PATH/mac/lib/libswscale* 60 | shift 61 | ;; 62 | --libplacebo_win) 63 | rm -r $LIB_PATH/windows/include/*/libplacebo 64 | rm $LIB_PATH/windows/lib/*/libplacebo* 65 | shift 66 | ;; 67 | --) 68 | shift; 69 | break 70 | ;; 71 | *) 72 | echo "Unexpected option: $1" 73 | exit 74 | ;; 75 | esac 76 | done -------------------------------------------------------------------------------- /scripts/create-qt-pdb-zip.bat: -------------------------------------------------------------------------------- 1 | rem Run from Qt command prompt without changing directories 2 | 3 | del symbols.zip 4 | 7z a symbols.zip *.pdb -r -xr!Qt5WebEngineCore* -------------------------------------------------------------------------------- /scripts/generate-bundle.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal enableDelayedExpansion 3 | 4 | rem Run from Qt command prompt with working directory set to root of repo 5 | 6 | set BUILD_CONFIG=%1 7 | 8 | rem Convert to lower case for windeployqt 9 | if /I "%BUILD_CONFIG%"=="debug" ( 10 | set BUILD_CONFIG=debug 11 | set WIX_MUMS=10 12 | ) else ( 13 | if /I "%BUILD_CONFIG%"=="release" ( 14 | set BUILD_CONFIG=release 15 | set WIX_MUMS=10 16 | ) else ( 17 | if /I "%BUILD_CONFIG%"=="signed-release" ( 18 | set BUILD_CONFIG=release 19 | set SIGN=1 20 | set MUST_DEPLOY_SYMBOLS=1 21 | 22 | rem Fail if there are unstaged changes 23 | git diff-index --quiet HEAD -- 24 | if !ERRORLEVEL! NEQ 0 ( 25 | echo Signed release builds must not have unstaged changes! 26 | exit /b 1 27 | ) 28 | ) else ( 29 | echo Invalid build configuration - expected 'debug' or 'release' 30 | exit /b 1 31 | ) 32 | ) 33 | ) 34 | 35 | set SIGNTOOL_PARAMS=sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sha1 8b9d0d682ad9459e54f05a79694bc10f9876e297 /v 36 | 37 | set BUILD_ROOT=%cd%\build 38 | set SOURCE_ROOT=%cd% 39 | set BUILD_FOLDER=%BUILD_ROOT%\build-%BUILD_CONFIG% 40 | set INSTALLER_FOLDER=%BUILD_ROOT%\installer-%BUILD_CONFIG% 41 | set /p VERSION=<%SOURCE_ROOT%\app\version.txt 42 | 43 | rem Ensure that all architectures have been built before the final bundle 44 | if not exist "%BUILD_ROOT%\build-x64-%BUILD_CONFIG%\Moonlight.msi" ( 45 | echo Unable to build bundle - missing binaries for %BUILD_CONFIG% x64 46 | echo You must run 'build-arch.bat %BUILD_CONFIG% x64' first 47 | exit /b 1 48 | ) 49 | if not exist "%BUILD_ROOT%\build-arm64-%BUILD_CONFIG%\Moonlight.msi" ( 50 | echo Unable to build bundle - missing binaries for %BUILD_CONFIG% arm64 51 | echo You must run 'build-arch.bat %BUILD_CONFIG% arm64' first 52 | exit /b 1 53 | ) 54 | 55 | echo Cleaning output directories 56 | rmdir /s /q %BUILD_FOLDER% 57 | rmdir /s /q %INSTALLER_FOLDER% 58 | mkdir %BUILD_FOLDER% 59 | mkdir %INSTALLER_FOLDER% 60 | 61 | rem Find Visual Studio and run vcvarsall.bat 62 | set VSWHERE="%SOURCE_ROOT%\scripts\vswhere.exe" 63 | for /f "usebackq delims=" %%i in (`%VSWHERE% -latest -property installationPath`) do ( 64 | call "%%i\VC\Auxiliary\Build\vcvarsall.bat" x86 65 | ) 66 | if !ERRORLEVEL! NEQ 0 goto Error 67 | 68 | echo Building bundle 69 | rem Bundles are always x86 binaries 70 | msbuild -Restore %SOURCE_ROOT%\wix\MoonlightSetup\MoonlightSetup.wixproj /p:Configuration=%BUILD_CONFIG% /p:Platform=x86 /p:MSBuildProjectExtensionsPath=%BUILD_FOLDER%\ 71 | if !ERRORLEVEL! NEQ 0 goto Error 72 | 73 | rem Rename the installer to match the publishing convention 74 | ren %INSTALLER_FOLDER%\MoonlightSetup.exe MoonlightSetup-%VERSION%.exe 75 | 76 | echo Build successful for Moonlight v%VERSION% installer! 77 | exit /b 0 78 | 79 | :Error 80 | echo Build failed! 81 | exit /b !ERRORLEVEL! 82 | -------------------------------------------------------------------------------- /scripts/generate-dmg.sh: -------------------------------------------------------------------------------- 1 | # This script requires create-dmg to be installed from https://github.com/sindresorhus/create-dmg 2 | BUILD_CONFIG=$1 3 | 4 | fail() 5 | { 6 | echo "$1" 1>&2 7 | exit 1 8 | } 9 | 10 | if [ "$BUILD_CONFIG" != "Debug" ] && [ "$BUILD_CONFIG" != "Release" ]; then 11 | fail "Invalid build configuration - expected 'Debug' or 'Release'" 12 | fi 13 | 14 | BUILD_ROOT=$PWD/build 15 | SOURCE_ROOT=$PWD 16 | BUILD_FOLDER=$BUILD_ROOT/build-$BUILD_CONFIG 17 | INSTALLER_FOLDER=$BUILD_ROOT/installer-$BUILD_CONFIG 18 | VERSION=`cat $SOURCE_ROOT/app/version.txt` 19 | 20 | if [ "$SIGNING_PROVIDER_SHORTNAME" == "" ]; then 21 | SIGNING_PROVIDER_SHORTNAME=$SIGNING_IDENTITY 22 | fi 23 | if [ "$SIGNING_IDENTITY" == "" ]; then 24 | SIGNING_IDENTITY=$SIGNING_PROVIDER_SHORTNAME 25 | fi 26 | 27 | [ "$SIGNING_IDENTITY" == "" ] || git diff-index --quiet HEAD -- || fail "Signed release builds must not have unstaged changes!" 28 | 29 | echo Cleaning output directories 30 | rm -rf $BUILD_FOLDER 31 | rm -rf $INSTALLER_FOLDER 32 | mkdir $BUILD_ROOT 33 | mkdir $BUILD_FOLDER 34 | mkdir $INSTALLER_FOLDER 35 | 36 | echo Configuring the project 37 | pushd $BUILD_FOLDER 38 | qmake $SOURCE_ROOT/moonlight-qt.pro QMAKE_APPLE_DEVICE_ARCHS="x86_64 arm64" || fail "Qmake failed!" 39 | popd 40 | 41 | echo Compiling Moonlight in $BUILD_CONFIG configuration 42 | pushd $BUILD_FOLDER 43 | make -j$(sysctl -n hw.logicalcpu) $(echo "$BUILD_CONFIG" | tr '[:upper:]' '[:lower:]') || fail "Make failed!" 44 | popd 45 | 46 | echo Saving dSYM file 47 | pushd $BUILD_FOLDER 48 | dsymutil app/Moonlight.app/Contents/MacOS/Moonlight -o Moonlight-$VERSION.dsym || fail "dSYM creation failed!" 49 | cp -R Moonlight-$VERSION.dsym $INSTALLER_FOLDER || fail "dSYM copy failed!" 50 | popd 51 | 52 | echo Creating app bundle 53 | EXTRA_ARGS= 54 | if [ "$BUILD_CONFIG" == "Debug" ]; then EXTRA_ARGS="$EXTRA_ARGS -use-debug-libs"; fi 55 | echo Extra deployment arguments: $EXTRA_ARGS 56 | macdeployqt $BUILD_FOLDER/app/Moonlight.app $EXTRA_ARGS -qmldir=$SOURCE_ROOT/app/gui -appstore-compliant || fail "macdeployqt failed!" 57 | 58 | echo Removing dSYM files from app bundle 59 | find $BUILD_FOLDER/app/Moonlight.app/ -name '*.dSYM' | xargs rm -rf 60 | 61 | if [ "$SIGNING_IDENTITY" != "" ]; then 62 | echo Signing app bundle 63 | codesign --force --deep --options runtime --timestamp --sign "$SIGNING_IDENTITY" $BUILD_FOLDER/app/Moonlight.app || fail "Signing failed!" 64 | fi 65 | 66 | echo Creating DMG 67 | if [ "$SIGNING_IDENTITY" != "" ]; then 68 | create-dmg $BUILD_FOLDER/app/Moonlight.app $INSTALLER_FOLDER --identity="$SIGNING_IDENTITY" || fail "create-dmg failed!" 69 | else 70 | create-dmg $BUILD_FOLDER/app/Moonlight.app $INSTALLER_FOLDER 71 | case $? in 72 | 0) ;; 73 | 2) ;; 74 | *) fail "create-dmg failed!";; 75 | esac 76 | fi 77 | 78 | if [ "$NOTARY_KEYCHAIN_PROFILE" != "" ]; then 79 | echo Uploading to App Notary service 80 | xcrun notarytool submit --keychain-profile "$NOTARY_KEYCHAIN_PROFILE" --wait $INSTALLER_FOLDER/Moonlight\ $VERSION.dmg || fail "Notary submission failed" 81 | 82 | echo Stapling notary ticket to DMG 83 | xcrun stapler staple -v $INSTALLER_FOLDER/Moonlight\ $VERSION.dmg || fail "Notary ticket stapling failed!" 84 | fi 85 | 86 | mv $INSTALLER_FOLDER/Moonlight\ $VERSION.dmg $INSTALLER_FOLDER/Moonlight-$VERSION.dmg 87 | echo Build successful -------------------------------------------------------------------------------- /scripts/generate-ico.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The ImageMagick conversion tool doesn't seem to always generate 4 | # ICO files with background transparency properly. Please validate 5 | # that the output has a transparent background. 6 | 7 | convert -density 256 -background none -define icon:auto-resize ../app/res/moonlight.svg ../app/moonlight.ico 8 | convert -density 256 -background none -size 64x64 ../app/res/moonlight.svg ../app/moonlight_wix.png 9 | 10 | echo IMPORTANT: Validate the icon has a transparent background before committing! 11 | -------------------------------------------------------------------------------- /scripts/generate-src.sh: -------------------------------------------------------------------------------- 1 | # Run from root of the Git repo to archive a source tarball 2 | 3 | fail() 4 | { 5 | echo "$1" 1>&2 6 | exit 1 7 | } 8 | 9 | git diff-index --quiet HEAD -- || fail "Source archives must not have unstaged changes!" 10 | 11 | BUILD_ROOT=$PWD/build 12 | ARCHIVE_FOLDER=$BUILD_ROOT/source 13 | VERSION=`cat app/version.txt` 14 | 15 | echo Cleaning output directories 16 | rm -rf $ARCHIVE_FOLDER 17 | mkdir $BUILD_ROOT 18 | mkdir $ARCHIVE_FOLDER 19 | 20 | scripts/git-archive-all.sh --format tar.gz $ARCHIVE_FOLDER/MoonlightSrc-$VERSION.tar.gz || fail "Archive failed" 21 | 22 | echo Archive successful -------------------------------------------------------------------------------- /scripts/jom.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/scripts/jom.exe -------------------------------------------------------------------------------- /scripts/svg2icns: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # https://gist.github.com/zlbruce/883605a635df8d5964bab11ed75e46ad 3 | echo "*** SVG 2 ICNS ***" 4 | if [ $# -ne 1 ]; then 5 | echo "Usage: svg2icns filename.svg" 6 | exit 100 7 | fi 8 | filename="$1" 9 | name=${filename%.*} 10 | ext=${filename##*.} 11 | echo "processing: $name" 12 | dest="$name".iconset 13 | mkdir "$dest" 14 | 15 | convert -background none -resize '!16x16' "$1" "$dest/icon_16x16.png" 16 | convert -background none -resize '!32x32' "$1" "$dest/icon_16x16@2x.png" 17 | cp "$dest/icon_16x16@2x.png" "$dest/icon_32x32.png" 18 | convert -background none -resize '!64x64' "$1" "$dest/icon_32x32@2x.png" 19 | convert -background none -resize '!128x128' "$1" "$dest/icon_128x128.png" 20 | convert -background none -resize '!256x256' "$1" "$dest/icon_128x128@2x.png" 21 | cp "$dest/icon_128x128@2x.png" "$dest/icon_256x256.png" 22 | convert -background none -resize '!512x512' "$1" "$dest/icon_256x256@2x.png" 23 | cp "$dest/icon_256x256@2x.png" "$dest/icon_512x512.png" 24 | convert -background none -resize '!1024x1024' "$1" "$dest/icon_512x512@2x.png" 25 | 26 | iconutil -c icns "$dest" 27 | rm -R "$dest" 28 | -------------------------------------------------------------------------------- /scripts/update-msvcredist.ps1: -------------------------------------------------------------------------------- 1 | $Urls = [ordered]@{ "X64" = "https://aka.ms/vs/17/release/vc_redist.x64.exe"; 2 | "ARM64" = "https://aka.ms/vs/17/release/vc_redist.arm64.exe"; } 3 | 4 | $UpgradeCodes = @{ "X64" = "36F68A90-239C-34DF-B58C-64B30153CE35"; 5 | "ARM64" = "DC9BAE42-810B-423A-9E25-E4073F1C7B00"; } 6 | 7 | function Get-RedirectTarget([string]$Url) { 8 | return (Invoke-WebRequest -Method Get -Uri $Url -MaximumRedirection 0 -ErrorAction SilentlyContinue).Headers.Location 9 | } 10 | 11 | function Print-WixForArch([string]$Arch) { 12 | $targetUrl = Get-RedirectTarget $Urls[$Arch] 13 | 14 | $file = "$env:TEMP\\vc_redist.tmp" 15 | Invoke-WebRequest -Method Get -Uri $targetUrl -o $file 16 | 17 | $targetSize = (Get-Item $file).Length 18 | $targetVersion = (Get-Command $file).Version 19 | $targetSha512 = (Get-FileHash -Path $file -Algorithm SHA512).Hash 20 | 21 | Write-Output "" 22 | Write-Output "" 23 | Write-Output "" 24 | Write-Output "" 25 | Write-Output "" 26 | 27 | Remove-Item $file 28 | } 29 | 30 | foreach ($arch in $Urls.Keys) { 31 | Print-WixForArch $arch 32 | } 33 | -------------------------------------------------------------------------------- /scripts/vswhere.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/scripts/vswhere.exe -------------------------------------------------------------------------------- /soundio/config.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonlight-stream/moonlight-qt/1dbdcb5279b3c2bce756e6eff3b97d3f12a38092/soundio/config.h -------------------------------------------------------------------------------- /soundio/soundio.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2018-10-02T15:27:07 4 | # 5 | #------------------------------------------------- 6 | 7 | QT -= core gui 8 | 9 | TARGET = soundio 10 | TEMPLATE = lib 11 | 12 | # Build a static library 13 | CONFIG += staticlib 14 | 15 | # Disable warnings 16 | CONFIG += warn_off 17 | 18 | # Include global qmake defs 19 | include(../globaldefs.pri) 20 | 21 | # Force MSVC to compile C as C++ for atomic support 22 | *-msvc* { 23 | QMAKE_CFLAGS += /TP 24 | } 25 | 26 | # Older GCC versions defaulted to GNU89 27 | *-g++ { 28 | QMAKE_CFLAGS += -std=gnu99 29 | } 30 | 31 | unix:!macx { 32 | CONFIG += link_pkgconfig 33 | 34 | packagesExist(libpulse) { 35 | PKGCONFIG += libpulse 36 | CONFIG += pulseaudio 37 | } 38 | packagesExist(alsa) { 39 | PKGCONFIG += alsa 40 | CONFIG += alsa 41 | } 42 | } 43 | 44 | DEFINES += \ 45 | SOUNDIO_STATIC_LIBRARY \ 46 | SOUNDIO_VERSION_MAJOR=1 \ 47 | SOUNDIO_VERSION_MINOR=1 \ 48 | SOUNDIO_VERSION_PATCH=0 \ 49 | SOUNDIO_VERSION_STRING=\\\"1.1.0\\\" 50 | 51 | SRC_DIR = $$PWD/libsoundio/src 52 | INC_DIR = $$PWD/libsoundio 53 | 54 | SOURCES += \ 55 | $$SRC_DIR/channel_layout.c \ 56 | $$SRC_DIR/dummy.c \ 57 | $$SRC_DIR/os.c \ 58 | $$SRC_DIR/ring_buffer.c \ 59 | $$SRC_DIR/soundio.c \ 60 | $$SRC_DIR/util.c 61 | 62 | HEADERS += \ 63 | $$SRC_DIR/atomics.h \ 64 | $$SRC_DIR/dummy.h \ 65 | $$SRC_DIR/list.h \ 66 | $$SRC_DIR/os.h \ 67 | $$SRC_DIR/ring_buffer.h \ 68 | $$SRC_DIR/soundio_internal.h \ 69 | $$SRC_DIR/soundio_private.h \ 70 | $$SRC_DIR/util.h \ 71 | $$INC_DIR/soundio/soundio.h \ 72 | $$INC_DIR/soundio/endian.h 73 | 74 | INCLUDEPATH += $$INC_DIR 75 | 76 | win32 { 77 | message(WASAPI backend selected) 78 | 79 | DEFINES += SOUNDIO_HAVE_WASAPI 80 | SOURCES += $$SRC_DIR/wasapi.c 81 | HEADERS += $$SRC_DIR/wasapi.h 82 | } 83 | macx { 84 | message(CoreAudio backend selected) 85 | 86 | DEFINES += SOUNDIO_HAVE_COREAUDIO 87 | SOURCES += $$SRC_DIR/coreaudio.c 88 | HEADERS += $$SRC_DIR/coreaudio.h 89 | } 90 | pulseaudio { 91 | message(PulseAudio backend selected) 92 | 93 | DEFINES += SOUNDIO_HAVE_PULSEAUDIO 94 | SOURCES += $$SRC_DIR/pulseaudio.c 95 | HEADERS += $$SRC_DIR/pulseaudio.h 96 | } 97 | alsa { 98 | message(ALSA backend selected) 99 | 100 | DEFINES += SOUNDIO_HAVE_ALSA 101 | SOURCES += $$SRC_DIR/alsa.c 102 | HEADERS += $$SRC_DIR/alsa.h 103 | } 104 | -------------------------------------------------------------------------------- /wix/Moonlight.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2035 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "MoonlightSetup", "MoonlightSetup\MoonlightSetup.wixproj", "{466FA35D-4BE4-40EF-9CE5-AFADC3B63BC5}" 7 | EndProject 8 | Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Moonlight", "Moonlight\Moonlight.wixproj", "{8468EF94-3BB8-47A5-AF15-0E314AFE664D}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Debug|x86 = Debug|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {466FA35D-4BE4-40EF-9CE5-AFADC3B63BC5}.Debug|x64.ActiveCfg = Debug|x86 19 | {466FA35D-4BE4-40EF-9CE5-AFADC3B63BC5}.Debug|x64.Build.0 = Debug|x86 20 | {466FA35D-4BE4-40EF-9CE5-AFADC3B63BC5}.Debug|x86.ActiveCfg = Debug|x86 21 | {466FA35D-4BE4-40EF-9CE5-AFADC3B63BC5}.Debug|x86.Build.0 = Debug|x86 22 | {466FA35D-4BE4-40EF-9CE5-AFADC3B63BC5}.Release|x64.ActiveCfg = Release|x86 23 | {466FA35D-4BE4-40EF-9CE5-AFADC3B63BC5}.Release|x64.Build.0 = Release|x86 24 | {466FA35D-4BE4-40EF-9CE5-AFADC3B63BC5}.Release|x86.ActiveCfg = Release|x86 25 | {466FA35D-4BE4-40EF-9CE5-AFADC3B63BC5}.Release|x86.Build.0 = Release|x86 26 | {8468EF94-3BB8-47A5-AF15-0E314AFE664D}.Debug|x64.ActiveCfg = Debug|x64 27 | {8468EF94-3BB8-47A5-AF15-0E314AFE664D}.Debug|x64.Build.0 = Debug|x64 28 | {8468EF94-3BB8-47A5-AF15-0E314AFE664D}.Debug|x86.ActiveCfg = Debug|x86 29 | {8468EF94-3BB8-47A5-AF15-0E314AFE664D}.Debug|x86.Build.0 = Debug|x86 30 | {8468EF94-3BB8-47A5-AF15-0E314AFE664D}.Release|x64.ActiveCfg = Release|x64 31 | {8468EF94-3BB8-47A5-AF15-0E314AFE664D}.Release|x64.Build.0 = Release|x64 32 | {8468EF94-3BB8-47A5-AF15-0E314AFE664D}.Release|x86.ActiveCfg = Release|x86 33 | {8468EF94-3BB8-47A5-AF15-0E314AFE664D}.Release|x86.Build.0 = Release|x86 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {7EEF5F41-49B8-4803-9608-6EA127BE9AE1} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /wix/Moonlight/Moonlight.wixproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Debug 4 | 5 | 6 | $(BUILD_FOLDER)\ 7 | $(BUILD_FOLDER)\ 8 | 9 | 10 | DeployDir=$(DEPLOY_FOLDER);BuildDir=$(BUILD_FOLDER) 11 | 12 | 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /wix/MoonlightSetup/MoonlightSetup.wixproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Bundle 4 | 5 | 6 | Debug 7 | 8 | 9 | $(INSTALLER_FOLDER)\ 10 | $(BUILD_FOLDER)\ 11 | 12 | 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------