mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-08-10 00:52:16 +00:00
@@ -52,7 +52,7 @@ NamespaceIndentation: All
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PointerAlignment: Right
|
||||
ReflowComments: false
|
||||
ReflowComments: true
|
||||
SpaceAfterCStyleCast: true
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -102,7 +102,6 @@ body:
|
||||
- Linux - solus (Third Party)
|
||||
- macOS - dmg
|
||||
- macOS - Portfile
|
||||
- macOS - pkg
|
||||
- Windows - Chocolatey (Third Party)
|
||||
- Windows - installer
|
||||
- Windows - portable
|
||||
@@ -111,6 +110,7 @@ body:
|
||||
- other (not listed)
|
||||
- other (self built)
|
||||
- other (fork of this repo)
|
||||
- n/a
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
@@ -123,6 +123,7 @@ body:
|
||||
- Intel
|
||||
- Nvidia
|
||||
- none (software encoding)
|
||||
- n/a
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
|
||||
81
.github/workflows/CI.yml
vendored
81
.github/workflows/CI.yml
vendored
@@ -474,7 +474,7 @@ jobs:
|
||||
build_mac:
|
||||
name: MacOS
|
||||
runs-on: macos-11
|
||||
needs: setup_release
|
||||
needs: [check_changelog, setup_release]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -514,13 +514,10 @@ jobs:
|
||||
|
||||
# package
|
||||
cpack -G DragNDrop
|
||||
mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-dragndrop.dmg
|
||||
mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine.dmg
|
||||
|
||||
cpack -G Bundle
|
||||
mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-bundle.dmg
|
||||
|
||||
cpack -G ZIP
|
||||
mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-macos-experimental-archive.zip
|
||||
# cpack -G Bundle
|
||||
# mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-bundle.dmg
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -528,33 +525,23 @@ jobs:
|
||||
name: sunshine-macos
|
||||
path: artifacts/
|
||||
|
||||
# this step can be removed after packages are fixed
|
||||
- name: Delete experimental packages
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
working-directory: artifacts
|
||||
run: |
|
||||
rm -f ./sunshine-macos-experimental-dragndrop.dmg
|
||||
rm -f ./sunshine-macos-experimental-bundle.dmg
|
||||
rm -f ./sunshine-macos-experimental-archive.zip
|
||||
|
||||
# # no artifacts to release currently
|
||||
# - name: Create/Update GitHub Release
|
||||
# if: ${{ needs.setup_release.outputs.create_release == 'true' }}
|
||||
# uses: ncipollo/release-action@v1
|
||||
# with:
|
||||
# name: ${{ needs.setup_release.outputs.release_name }}
|
||||
# tag: ${{ needs.setup_release.outputs.release_tag }}
|
||||
# commit: ${{ needs.setup_release.outputs.release_commit }}
|
||||
# artifacts: "*artifacts/*"
|
||||
# token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
# allowUpdates: true
|
||||
# body: ${{ needs.setup_release.outputs.release_body }}
|
||||
# discussionCategory: announcements
|
||||
# prerelease: ${{ needs.setup_release.outputs.pre_release }}
|
||||
- name: Create/Update GitHub Release
|
||||
if: ${{ needs.setup_release.outputs.create_release == 'true' }}
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: ${{ needs.setup_release.outputs.release_name }}
|
||||
tag: ${{ needs.setup_release.outputs.release_tag }}
|
||||
commit: ${{ needs.setup_release.outputs.release_commit }}
|
||||
artifacts: "*artifacts/*"
|
||||
token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
allowUpdates: true
|
||||
body: ${{ needs.setup_release.outputs.release_body }}
|
||||
discussionCategory: announcements
|
||||
prerelease: ${{ needs.setup_release.outputs.pre_release }}
|
||||
|
||||
build_mac_port:
|
||||
name: Macports
|
||||
needs: setup_release
|
||||
needs: [check_changelog, setup_release]
|
||||
runs-on: macos-11
|
||||
|
||||
steps:
|
||||
@@ -728,25 +715,6 @@ jobs:
|
||||
done
|
||||
exit "$fail"
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
# create packages
|
||||
sudo port pkg sunshine
|
||||
sudo port dmg sunshine
|
||||
|
||||
work=$(port work sunshine)
|
||||
echo "Sunshine port work directory: ${work}"
|
||||
|
||||
# move components out of port work directory
|
||||
sudo mv ${work}/Sunshine*component.pkg /tmp/
|
||||
|
||||
# copy artifacts
|
||||
sudo mv ${work}/Sunshine*.pkg ./artifacts/sunshine.pkg
|
||||
sudo mv ${work}/Sunshine*.dmg ./artifacts/sunshine.dmg
|
||||
|
||||
# move components back
|
||||
# sudo mv /tmp/Sunshine*component.pkg ${work}/
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -770,7 +738,7 @@ jobs:
|
||||
build_win:
|
||||
name: Windows
|
||||
runs-on: windows-2019
|
||||
needs: setup_release
|
||||
needs: [check_changelog, setup_release]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -813,7 +781,7 @@ jobs:
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release \
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DSUNSHINE_ASSETS_DIR=assets \
|
||||
-G "MinGW Makefiles" \
|
||||
..
|
||||
@@ -833,6 +801,15 @@ jobs:
|
||||
mv ./cpack_artifacts/Sunshine.exe ../artifacts/sunshine-windows-installer.exe
|
||||
mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-windows-portable.zip
|
||||
|
||||
- name: Package Windows Debug Info
|
||||
working-directory: build
|
||||
run: |
|
||||
# save the original binaries with debug info
|
||||
7z -r `
|
||||
"-xr!CMakeFiles" `
|
||||
"-xr!cpack_artifacts" `
|
||||
a "../artifacts/sunshine-debuginfo-win32.zip" "*.exe"
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
2
.github/workflows/localize.yml
vendored
2
.github/workflows/localize.yml
vendored
@@ -77,7 +77,7 @@ jobs:
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create/Update Pull Request
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
add-paths: |
|
||||
locale/*.po
|
||||
|
||||
@@ -8,26 +8,16 @@ version: 2
|
||||
|
||||
# Set the version of Python
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.10"
|
||||
python: "3.11"
|
||||
apt_packages:
|
||||
- graphviz
|
||||
|
||||
## apt packages required packages to run cmake on sunshine, note that additional packages are required
|
||||
# apt_packages:
|
||||
# - cmake
|
||||
# - libboost-filesystem-dev
|
||||
# - libboost-log-dev
|
||||
# - libboost-thread-dev
|
||||
|
||||
## run cmake
|
||||
# jobs:
|
||||
# pre_build:
|
||||
# - cmake .
|
||||
|
||||
## Include the submodules, required for cmake
|
||||
# submodules:
|
||||
# include: all
|
||||
# recursive: true
|
||||
# submodules required for include statements
|
||||
submodules:
|
||||
include: all
|
||||
recursive: true
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
|
||||
81
CHANGELOG.md
81
CHANGELOG.md
@@ -1,5 +1,85 @@
|
||||
# Changelog
|
||||
|
||||
## [0.20.0] - 2023-05-28
|
||||
**Breaking**
|
||||
- (Windows) The Windows installer version of Sunshine is now always launched by the Sunshine Service. Manually launching Sunshine.exe from Program Files is no longer supported. This was necessary to address security issues caused by non-admin users having access to Sunshine's config data. If you have set up Task Scheduler or other mechanisms to launch Sunshine automatically, remove those from your system before updating.
|
||||
- (Windows) The Start Menu shortcut has been redesigned for use with the Sunshine Service. It now launches Sunshine in the background (if not already running) and opens the Web UI, avoiding the persistent Command Prompt window present in prior versions. The Start Menu shortcut is now the recommended method for opening the Web UI and launching Sunshine.
|
||||
- (Network/UPnP) If the Moonlight Internet Hosting Tool is installed alongside Sunshine, you must remove it or upgrade to v5.6 or later to prevent conflicts with Sunshine's UPnP support. As a reminder, the Moonlight Internet Hosting Tool is not required to stream over the Internet with Sunshine. Instead, simply enable UPnP in the Sunshine Web UI.
|
||||
- (Windows) If Steam is installed, the Steam Streaming Speakers driver will be automatically installed when starting a stream for the first time. This behavior can be disabled in the Audio/Video tab of the Web UI. This Steam driver enables support for surround sound and muting host audio without requiring any manual configuration.
|
||||
- (Input) The Back Button Timeout option has been renamed to Guide Button Emulation Timeout and has been disabled by default to ensure long presses on the Back button work by default. The previous behavior can be restored by setting the Guide Button Emulation Timeout to 2000.
|
||||
- (Windows) The service name of SunshineSvc has been changed to SunshineService to address a false positive in MalwareBytes. If you have any scripts or other logic on your system that is using the service name, you will need to update that for the new name.
|
||||
- (Windows) To support new installer features, install-service.bat no longer sets the service to auto-start by default. Users executing install-service.bat manually on the Sunshine portable build must also execute autostart-service.bat to start Sunshine on boot. However, installing the service manually like this is not recommended. Instead, use the Sunshine installer which handles service installation and configuration automatically.
|
||||
- (Linux/Fedora) Fedora 36 builds are removed due to upstream end of support
|
||||
|
||||
**Added**
|
||||
- (Windows) Added an option to launch apps and prep/undo commands as administrator
|
||||
- (Installer/Windows) Added an option to choose whether Sunshine launches on boot. If not configured to launch on boot, use the Start Menu shortcut to start Sunshine when desired.
|
||||
- (Input/Windows) Added option to send VK codes instead of scancodes for compatibility with iOS/Android devices using non-English keyboard layouts
|
||||
- (UI) The Apply/Restart option is now available in the Web UI for all platforms
|
||||
- (Systray) Added a Restart option to the system tray context menu
|
||||
- (Video/Windows) Added host latency data to video frames. This requires future updates to Moonlight to display in the on-screen overlay.
|
||||
- (Audio) Added support for matching Audio Sink and Virtual Sink values on device names
|
||||
- (Client) Added friendly error messages for clients when streaming fails to start
|
||||
- (Video/Windows) Added warning log messages when Windows is hiding DRM-protected content from display capture
|
||||
- (Interop/Windows) Added warning log messages when GeForce Experience is currently using the same ports as Sunshine
|
||||
- (Linux/Fedora) Fedora 38 builds are now available
|
||||
|
||||
**Changed**
|
||||
- (Video) Encoder selection now happens at each stream start for more reliable GPU detection
|
||||
- (Video/Windows) The host display now stays on while clients are actively streaming
|
||||
- (Audio) Streaming will no longer fail if audio capture is unavailable
|
||||
- (Audio/Windows) Sunshine will automatically switch back to the Virtual Sink if the default audio device is changed while streaming
|
||||
- (Audio) Changes to the host audio playback option will now take effect when resuming a session from Moonlight
|
||||
- (Audio/Windows) Sunshine will switch to a different default audio device if Steam Streaming Speakers are the default when Sunshine starts. This handles cases where Sunshine terminates unexpectedly without restoring the default audio device.
|
||||
- (Apps) The Connection Terminated dialog will no longer appear in Moonlight when the app on the host exits normally
|
||||
- (Systray/Windows) Quitting Sunshine via the system tray will now stop the Sunshine Service rather than leaving it running and allowing it to restart Sunshine
|
||||
- (UI) The 100.64.0.0/10 CGN IP address range is now treated as a LAN address range
|
||||
- (Video) Removed a workaround for some versions of Moonlight prior to mid-2022
|
||||
- (UI) The PIN field is now cleared after successfully pairing
|
||||
- (UI) User names are now treated as case-insensitive
|
||||
- (Linux) Changed udev rule to automatically grant access to virtual input devices
|
||||
- (UI) Several item descriptions were adjusted to reflect newer configuration recommendations
|
||||
- (UI) Placeholder text opacity has been reduced to improve contrast with non-placeholder text
|
||||
- (Video/Windows) Minor capture performance improvements
|
||||
|
||||
**Fixed**
|
||||
- (Video) VRAM usage while streaming is significantly reduced, particularly with higher display resolutions and HDR
|
||||
- (Network/UPnP) UPnP support was rewritten to fix several major issues handling router restarts, IP address changes, and port forwarding expiration
|
||||
- (Input) Fixed modifier keys from the software keyboard on Android clients
|
||||
- (Audio) Fixed handling of default audio device changes while streaming
|
||||
- (Windows) Fixed streaming after Microsoft Remote Desktop or Fast User Switching has been used
|
||||
- (Input) Fixed some games not recognizing emulated Guide button presses
|
||||
- (Video/Windows) Fixed incorrect gamma when using an Advanced Color SDR display on the host
|
||||
- (Installer/Windows) The installer is no longer blurry on High DPI systems
|
||||
- (Systray/Windows) Fixed multiple system tray icons appearing if Sunshine exits unexpectedly
|
||||
- (Systray/Windows) Fixed the system tray icon not appearing in several situations
|
||||
- (Windows) Fixed hang on shutdown/restart if mDNS registration fails
|
||||
- (UI) Fixed missing response headers
|
||||
- (Stability) Fixed several possible crashes in cases where the client did not successfully connect
|
||||
- (Stability) Fixed several memory leaks
|
||||
- (Input/Windows) Back/Select input now correctly generates the Share button when emulating DS4 controllers
|
||||
- (Audio/Windows) Fixed various bugs in audio-info.exe that led to inaccurate output on some systems
|
||||
- (Video/Windows) Fixed incorrect resolution values from dxgi-info.exe on High DPI systems
|
||||
- (Video/Linux) Fixed poor quality encoding from H.264 on Intel encoders
|
||||
- (Config) Fixed a couple of typos in predefined resolutions
|
||||
|
||||
**Dependencies**
|
||||
- Bump sphinx-copybutton from 0.5.1 to 0.5.2
|
||||
- Bump sphinx from 6.13 to 7.0.1
|
||||
- Bump third-party/nv-codec-headers from 2055784 to 2cd175b
|
||||
- Bump furo from 2023.3.27 to 2023.5.20
|
||||
|
||||
**Misc**
|
||||
- (Build/Linux) Add X11 to PLATFORM_LIBARIES when found
|
||||
- (Build/macOS) Fix compilation with Clang 14
|
||||
- (Docs) Fix nvlax URL
|
||||
- (Docs) Add diagrams using graphviz
|
||||
- (Docs) Improvements to source code documentation
|
||||
- (Build) Unpin docker dependencies
|
||||
- (Build/Linux) Honor install prefix for tray icon
|
||||
- (Build/Windows) Unstripped binaries are now provided as a debuginfo package to support crash dump debugging
|
||||
- (Config) Config directories are now created recursively
|
||||
|
||||
## [0.19.1] - 2023-03-30
|
||||
**Fixed**
|
||||
- (Audio) Fixed no audio issue introduced in v0.19.0
|
||||
@@ -409,3 +489,4 @@ settings. In v0.17.0, games now run under your user account without elevated pri
|
||||
[0.18.4]: https://github.com/LizardByte/Sunshine/releases/tag/v0.18.4
|
||||
[0.19.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.19.0
|
||||
[0.19.1]: https://github.com/LizardByte/Sunshine/releases/tag/v0.19.1
|
||||
[0.20.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.20.0
|
||||
|
||||
114
CMakeLists.txt
114
CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.18)
|
||||
# `CMAKE_CUDA_ARCHITECTURES` requires 3.18
|
||||
|
||||
# todo - set version to 0.0.0 once confident in automated versioning
|
||||
project(Sunshine VERSION 0.19.1
|
||||
project(Sunshine VERSION 0.20.0
|
||||
DESCRIPTION "Sunshine is a self-hosted game stream host for Moonlight."
|
||||
HOMEPAGE_URL "https://app.lizardbyte.dev")
|
||||
|
||||
@@ -125,7 +125,7 @@ set(UPNPC_BUILD_TESTS OFF CACHE BOOL "Don't build tests for miniupnpc")
|
||||
set(UPNPC_BUILD_SAMPLE OFF CACHE BOOL "Don't build samples for miniupnpc")
|
||||
set(UPNPC_NO_INSTALL ON CACHE BOOL "Don't install any libraries build for miniupnpc")
|
||||
add_subdirectory(third-party/miniupnp/miniupnpc)
|
||||
include_directories(third-party/miniupnp/miniupnpc/include)
|
||||
include_directories(SYSTEM third-party/miniupnp/miniupnpc/include)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
@@ -134,13 +134,11 @@ pkg_check_modules(CURL REQUIRED libcurl)
|
||||
|
||||
if(WIN32)
|
||||
set(Boost_USE_STATIC_LIBS ON) # cmake-lint: disable=C0103
|
||||
# workaround to prevent link errors against icudata, icui18n
|
||||
set(Boost_NO_BOOST_CMAKE ON) # cmake-lint: disable=C0103
|
||||
endif()
|
||||
|
||||
find_package(Boost COMPONENTS locale log filesystem program_options REQUIRED)
|
||||
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare)
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -Wall -Wno-sign-compare)
|
||||
|
||||
# enable system tray, we will disable this later if we cannot find the required package config on linux
|
||||
set(SUNSHINE_TRAY 1)
|
||||
@@ -151,13 +149,13 @@ if(WIN32)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
|
||||
|
||||
add_definitions(-DCURL_STATICLIB)
|
||||
include_directories(${CURL_STATIC_INCLUDE_DIRS})
|
||||
include_directories(SYSTEM ${CURL_STATIC_INCLUDE_DIRS})
|
||||
link_directories(${CURL_STATIC_LIBRARY_DIRS})
|
||||
|
||||
add_compile_definitions(SUNSHINE_PLATFORM="windows")
|
||||
add_subdirectory(tools) # This is temporary, only tools for Windows are needed, for now
|
||||
|
||||
include_directories(third-party/ViGEmClient/include)
|
||||
include_directories(SYSTEM third-party/ViGEmClient/include)
|
||||
|
||||
if(NOT DEFINED SUNSHINE_ICON_PATH)
|
||||
set(SUNSHINE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/sunshine.ico")
|
||||
@@ -353,12 +351,13 @@ else()
|
||||
|
||||
if(X11_FOUND)
|
||||
add_compile_definitions(SUNSHINE_BUILD_X11)
|
||||
include_directories(${X11_INCLUDE_DIR})
|
||||
include_directories(SYSTEM ${X11_INCLUDE_DIR})
|
||||
list(APPEND PLATFORM_LIBRARIES ${X11_LIBRARIES})
|
||||
list(APPEND PLATFORM_TARGET_FILES src/platform/linux/x11grab.cpp)
|
||||
endif()
|
||||
|
||||
if(CUDA_FOUND)
|
||||
include_directories(third-party/nvfbc)
|
||||
include_directories(SYSTEM third-party/nvfbc)
|
||||
list(APPEND PLATFORM_TARGET_FILES
|
||||
src/platform/linux/cuda.cu
|
||||
src/platform/linux/cuda.cpp
|
||||
@@ -369,7 +368,7 @@ else()
|
||||
|
||||
if(LIBDRM_FOUND AND LIBCAP_FOUND)
|
||||
add_compile_definitions(SUNSHINE_BUILD_DRM)
|
||||
include_directories(${LIBDRM_INCLUDE_DIRS} ${LIBCAP_INCLUDE_DIRS})
|
||||
include_directories(SYSTEM ${LIBDRM_INCLUDE_DIRS} ${LIBCAP_INCLUDE_DIRS})
|
||||
list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES} ${LIBCAP_LIBRARIES})
|
||||
list(APPEND PLATFORM_TARGET_FILES src/platform/linux/kmsgrab.cpp)
|
||||
list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1)
|
||||
@@ -415,6 +414,7 @@ ${CMAKE_BINARY_DIR}/generated-src/${filename}.h")
|
||||
GEN_WAYLAND(wlr-export-dmabuf-unstable-v1)
|
||||
|
||||
include_directories(
|
||||
SYSTEM
|
||||
${WAYLAND_INCLUDE_DIRS}
|
||||
${CMAKE_BINARY_DIR}/generated-src
|
||||
)
|
||||
@@ -435,7 +435,7 @@ ${CMAKE_BINARY_DIR}/generated-src/${filename}.h")
|
||||
message(WARNING "Couldn't find appindicator, disabling tray icon")
|
||||
set(SUNSHINE_TRAY 0)
|
||||
else()
|
||||
include_directories(${APPINDICATOR_INCLUDE_DIRS})
|
||||
include_directories(SYSTEM ${APPINDICATOR_INCLUDE_DIRS})
|
||||
link_directories(${APPINDICATOR_LIBRARY_DIRS})
|
||||
|
||||
list(APPEND PLATFORM_TARGET_FILES third-party/tray/tray_linux.c)
|
||||
@@ -474,6 +474,7 @@ ${CMAKE_BINARY_DIR}/generated-src/${filename}.h")
|
||||
pulse-simple)
|
||||
|
||||
include_directories(
|
||||
SYSTEM
|
||||
/usr/include/libevdev-1.0
|
||||
third-party/nv-codec-headers/include
|
||||
third-party/glad/include)
|
||||
@@ -535,6 +536,8 @@ set(SUNSHINE_TARGET_FILES
|
||||
src/thread_safe.h
|
||||
src/sync.h
|
||||
src/round_robin.h
|
||||
src/stat_trackers.h
|
||||
src/stat_trackers.cpp
|
||||
${PLATFORM_TARGET_FILES})
|
||||
|
||||
set_source_files_properties(src/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic)
|
||||
@@ -582,8 +585,10 @@ set(FFMPEG_LIBRARIES
|
||||
${HDR10_PLUS_LIBRARY}
|
||||
${FFMPEG_PLATFORM_LIBRARIES})
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
SYSTEM
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third-party
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third-party/moonlight-common-c/enet/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third-party/nanors
|
||||
@@ -638,6 +643,8 @@ if(WIN32)
|
||||
set_target_properties(sunshine PROPERTIES LINK_SEARCH_START_STATIC 1)
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
|
||||
find_library(ZLIB ZLIB1)
|
||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
||||
Wtsapi32.lib)
|
||||
endif()
|
||||
|
||||
target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS})
|
||||
@@ -692,25 +699,31 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
|
||||
# Adding tools
|
||||
install(TARGETS dxgi-info RUNTIME DESTINATION "tools" COMPONENT dxgi)
|
||||
install(TARGETS audio-info RUNTIME DESTINATION "tools" COMPONENT audio)
|
||||
install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT sunshinesvc)
|
||||
install(TARGETS elevator RUNTIME DESTINATION "tools" COMPONENT elevator)
|
||||
|
||||
# Mandatory tools
|
||||
install(TARGETS ddprobe RUNTIME DESTINATION "tools" COMPONENT application)
|
||||
install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT application)
|
||||
|
||||
# Mandatory scripts
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/service/"
|
||||
DESTINATION "scripts"
|
||||
COMPONENT assets)
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/migration/"
|
||||
DESTINATION "scripts"
|
||||
COMPONENT assets)
|
||||
|
||||
# Configurable options for the service
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/autostart/"
|
||||
DESTINATION "scripts"
|
||||
COMPONENT autostart)
|
||||
|
||||
# scripts
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/firewall/"
|
||||
DESTINATION "scripts"
|
||||
COMPONENT firewall)
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/service/"
|
||||
DESTINATION "scripts"
|
||||
COMPONENT service)
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/vigembus/"
|
||||
DESTINATION "scripts"
|
||||
COMPONENT vigembus)
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/migration/"
|
||||
DESTINATION "scripts"
|
||||
COMPONENT assets)
|
||||
|
||||
# Sunshine assets
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/"
|
||||
@@ -729,7 +742,6 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
|
||||
# Extra install commands
|
||||
# Restores permissions on the install directory
|
||||
# Migrates config files from the root into the new config folder
|
||||
# Sets permissions on the config folder so that we can write in it
|
||||
# Install service
|
||||
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS
|
||||
"${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}
|
||||
@@ -737,10 +749,10 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
|
||||
ExecShell 'open' 'https://sunshinestream.readthedocs.io/'
|
||||
nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\" /reset'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\migrate-config.bat\\\"'
|
||||
nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\\config\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-vigembus.bat\\\"'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\autostart-service.bat\\\"'
|
||||
NoController:
|
||||
")
|
||||
|
||||
@@ -762,20 +774,19 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
|
||||
NoDelete:
|
||||
")
|
||||
|
||||
# Adding an option for the start menu and PATH
|
||||
# TODO: it asks to add it to the PATH but is not working https://gitlab.kitware.com/cmake/cmake/-/issues/15635
|
||||
# Adding an option for the start menu
|
||||
set(CPACK_NSIS_MODIFY_PATH "OFF")
|
||||
set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
|
||||
# This will be shown on the installed apps Windows settings
|
||||
set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe")
|
||||
set(CPACK_NSIS_CREATE_ICONS_EXTRA
|
||||
"${CPACK_NSIS_CREATE_ICONS_EXTRA}
|
||||
CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME} (Foreground Mode).lnk' \
|
||||
'\$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe'
|
||||
CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME}.lnk' \
|
||||
'\$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe' '--shortcut'
|
||||
")
|
||||
set(CPACK_NSIS_DELETE_ICONS_EXTRA
|
||||
"${CPACK_NSIS_DELETE_ICONS_EXTRA}
|
||||
Delete '\$SMPROGRAMS\\\\$MUI_TEMP\\\\${CMAKE_PROJECT_NAME} (Foreground Mode).lnk'
|
||||
Delete '\$SMPROGRAMS\\\\$MUI_TEMP\\\\${CMAKE_PROJECT_NAME}.lnk'
|
||||
")
|
||||
|
||||
# Checking for previous installed versions
|
||||
@@ -789,57 +800,48 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
|
||||
"https://sunshinestream.readthedocs.io" "Sunshine documentation"
|
||||
"https://app.lizardbyte.dev" "LizardByte Web Site"
|
||||
"https://app.lizardbyte.dev/support" "LizardByte Support")
|
||||
set(CPACK_NSIS_MANIFEST_DPI_AWARE true)
|
||||
|
||||
# Setting components groups and dependencies
|
||||
set(CPACK_COMPONENT_GROUP_CORE_EXPANDED true)
|
||||
|
||||
# sunshine binary
|
||||
set(CPACK_COMPONENT_APPLICATION_DISPLAY_NAME "${CMAKE_PROJECT_NAME}")
|
||||
set(CPACK_COMPONENT_APPLICATION_DESCRIPTION "${CMAKE_PROJECT_NAME} main application.")
|
||||
set(CPACK_COMPONENT_APPLICATION_GROUP "core")
|
||||
set(CPACK_COMPONENT_APPLICATION_DESCRIPTION "${CMAKE_PROJECT_NAME} main application and required components.")
|
||||
set(CPACK_COMPONENT_APPLICATION_GROUP "Core")
|
||||
set(CPACK_COMPONENT_APPLICATION_REQUIRED true)
|
||||
set(CPACK_COMPONENT_APPLICATION_DEPENDS assets)
|
||||
|
||||
# service auto-start script
|
||||
set(CPACK_COMPONENT_AUTOSTART_DISPLAY_NAME "Launch on Startup")
|
||||
set(CPACK_COMPONENT_AUTOSTART_DESCRIPTION "If enabled, launches Sunshine automatically on system startup.")
|
||||
set(CPACK_COMPONENT_AUTOSTART_GROUP "Core")
|
||||
|
||||
# assets
|
||||
set(CPACK_COMPONENT_ASSETS_DISPLAY_NAME "assets")
|
||||
set(CPACK_COMPONENT_ASSETS_DESCRIPTION "Shaders, default box art, and web ui.")
|
||||
set(CPACK_COMPONENT_ASSETS_GROUP "core")
|
||||
set(CPACK_COMPONENT_ASSETS_DISPLAY_NAME "Required Assets")
|
||||
set(CPACK_COMPONENT_ASSETS_DESCRIPTION "Shaders, default box art, and web UI.")
|
||||
set(CPACK_COMPONENT_ASSETS_GROUP "Core")
|
||||
set(CPACK_COMPONENT_ASSETS_REQUIRED true)
|
||||
|
||||
# audio tool
|
||||
set(CPACK_COMPONENT_AUDIO_DISPLAY_NAME "audio-info")
|
||||
set(CPACK_COMPONENT_AUDIO_DESCRIPTION "CLI tool providing information about sound devices.")
|
||||
set(CPACK_COMPONENT_AUDIO_GROUP "tools")
|
||||
|
||||
# elevation tool
|
||||
set(CPACK_COMPONENT_ELEVATOR_DISPLAY_NAME "elevator")
|
||||
set(CPACK_COMPONENT_ELEVATOR_DESCRIPTION "CLI tool that assists with elevating \
|
||||
commands when permissions have been denied.")
|
||||
set(CPACK_COMPONENT_ELEVATOR_GROUP "tools")
|
||||
set(CPACK_COMPONENT_AUDIO_GROUP "Tools")
|
||||
|
||||
# display tool
|
||||
set(CPACK_COMPONENT_DXGI_DISPLAY_NAME "dxgi-info")
|
||||
set(CPACK_COMPONENT_DXGI_DESCRIPTION "CLI tool providing information about graphics cards and displays.")
|
||||
set(CPACK_COMPONENT_DXGI_GROUP "tools")
|
||||
|
||||
# service
|
||||
set(CPACK_COMPONENT_SUNSHINESVC_DISPLAY_NAME "sunshinesvc")
|
||||
set(CPACK_COMPONENT_SUNSHINESVC_DESCRIPTION "CLI tool providing ability to enable/disable the Sunshine service.")
|
||||
set(CPACK_COMPONENT_SUNSHINESVC_GROUP "tools")
|
||||
|
||||
# service scripts
|
||||
set(CPACK_COMPONENT_SERVICE_DISPLAY_NAME "service-scripts")
|
||||
set(CPACK_COMPONENT_SERVICE_DESCRIPTION "Scripts to enable/disable the service.")
|
||||
set(CPACK_COMPONENT_SERVICE_GROUP "scripts")
|
||||
set(CPACK_COMPONENT_SERVICE_DEPENDS sunshinesvc)
|
||||
set(CPACK_COMPONENT_DXGI_GROUP "Tools")
|
||||
|
||||
# firewall scripts
|
||||
set(CPACK_COMPONENT_FIREWALL_DISPLAY_NAME "firewall-scripts")
|
||||
set(CPACK_COMPONENT_FIREWALL_DISPLAY_NAME "Add Firewall Exclusions")
|
||||
set(CPACK_COMPONENT_FIREWALL_DESCRIPTION "Scripts to enable or disable firewall rules.")
|
||||
set(CPACK_COMPONENT_FIREWALL_GROUP "scripts")
|
||||
set(CPACK_COMPONENT_FIREWALL_GROUP "Scripts")
|
||||
|
||||
# vigembus scripts
|
||||
set(CPACK_COMPONENT_VIGEMBUS_DISPLAY_NAME "vigembus-scripts")
|
||||
set(CPACK_COMPONENT_VIGEMBUS_DISPLAY_NAME "Virtual Gamepad Support")
|
||||
set(CPACK_COMPONENT_VIGEMBUS_DESCRIPTION "Scripts to install and uninstall ViGEmBus for virtual gamepad support.")
|
||||
set(CPACK_COMPONENT_VIGEMBUS_GROUP "scripts")
|
||||
set(CPACK_COMPONENT_VIGEMBUS_GROUP "Scripts")
|
||||
endif()
|
||||
if(APPLE)
|
||||
# TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop
|
||||
@@ -942,7 +944,7 @@ elseif(UNIX)
|
||||
|
||||
if(${SUNSHINE_TRAY} STREQUAL 1)
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
|
||||
DESTINATION "/usr/share/icons")
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons")
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "\
|
||||
${CPACK_DEBIAN_PACKAGE_DEPENDS}, \
|
||||
|
||||
@@ -131,7 +131,7 @@ userdel -r builder
|
||||
|
||||
# then create the lizard user
|
||||
groupadd -f -g "${PGID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -u "${PUID}" "${UNAME}"
|
||||
mkdir -p ${HOME}/.config/sunshine
|
||||
ln -s ${HOME}/.config/sunshine /config
|
||||
chown -R ${UNAME} ${HOME}
|
||||
|
||||
@@ -30,39 +30,39 @@ RUN <<_DEPS
|
||||
set -e
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential=12.9* \
|
||||
cmake=3.18.4* \
|
||||
git=1:2.30.2* \
|
||||
libavdevice-dev=7:4.3.* \
|
||||
libboost-filesystem-dev=1.74.0* \
|
||||
libboost-locale-dev=1.74.0* \
|
||||
libboost-log-dev=1.74.0* \
|
||||
libboost-program-options-dev=1.74.0* \
|
||||
libboost-thread-dev=1.74.0* \
|
||||
libcap-dev=1:2.44* \
|
||||
libcurl4-openssl-dev=7.74.0* \
|
||||
libdrm-dev=2.4.104* \
|
||||
libevdev-dev=1.11.0* \
|
||||
libnuma-dev=2.0.12* \
|
||||
libopus-dev=1.3.1* \
|
||||
libpulse-dev=14.2* \
|
||||
libssl-dev=1.1.1* \
|
||||
libva-dev=2.10.0* \
|
||||
libvdpau-dev=1.4* \
|
||||
libwayland-dev=1.18.0* \
|
||||
libx11-dev=2:1.7.2* \
|
||||
libxcb-shm0-dev=1.14* \
|
||||
libxcb-xfixes0-dev=1.14* \
|
||||
libxcb1-dev=1.14* \
|
||||
libxfixes-dev=1:5.0.3* \
|
||||
libxrandr-dev=2:1.5.1* \
|
||||
libxtst-dev=2:1.2.3* \
|
||||
nodejs=12.22* \
|
||||
npm=7.5.2* \
|
||||
wget=1.21*
|
||||
build-essential \
|
||||
cmake=3.18.* \
|
||||
git \
|
||||
libavdevice-dev \
|
||||
libboost-filesystem-dev=1.74.* \
|
||||
libboost-locale-dev=1.74.* \
|
||||
libboost-log-dev=1.74.* \
|
||||
libboost-program-options-dev=1.74.* \
|
||||
libboost-thread-dev=1.74.* \
|
||||
libcap-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libdrm-dev \
|
||||
libevdev-dev \
|
||||
libnuma-dev \
|
||||
libopus-dev \
|
||||
libpulse-dev \
|
||||
libssl-dev \
|
||||
libva-dev \
|
||||
libvdpau-dev \
|
||||
libwayland-dev \
|
||||
libx11-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libxcb1-dev \
|
||||
libxfixes-dev \
|
||||
libxrandr-dev \
|
||||
libxtst-dev \
|
||||
nodejs \
|
||||
npm \
|
||||
wget
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
apt-get install -y --no-install-recommends \
|
||||
libmfx-dev=21.1.0*
|
||||
libmfx-dev
|
||||
fi
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
@@ -161,7 +161,7 @@ RUN <<_SETUP_USER
|
||||
#!/bin/bash
|
||||
set -e
|
||||
groupadd -f -g "${PGID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -u "${PUID}" "${UNAME}"
|
||||
mkdir -p ${HOME}/.config/sunshine
|
||||
ln -s ${HOME}/.config/sunshine /config
|
||||
chown -R ${UNAME} ${HOME}
|
||||
|
||||
@@ -30,37 +30,37 @@ set -e
|
||||
dnf -y update
|
||||
dnf -y group install "Development Tools"
|
||||
dnf -y install \
|
||||
boost-devel-1.78.0* \
|
||||
cmake-3.24.1* \
|
||||
gcc-12.2.1* \
|
||||
gcc-c++-12.2.1* \
|
||||
git-2.39.2* \
|
||||
libappindicator-gtk3-devel-12.10.1* \
|
||||
libcap-devel-2.48* \
|
||||
libcurl-devel-7.85.0* \
|
||||
libdrm-devel-2.4.112* \
|
||||
libevdev-devel-1.13.0* \
|
||||
libva-devel-2.15.0* \
|
||||
libvdpau-devel-1.5* \
|
||||
libX11-devel-1.8.1* \
|
||||
libxcb-devel-1.13.1* \
|
||||
libXcursor-devel-1.2.1* \
|
||||
libXfixes-devel-6.0.0* \
|
||||
libXi-devel-1.8* \
|
||||
libXinerama-devel-1.1.4* \
|
||||
libXrandr-devel-1.5.2* \
|
||||
libXtst-devel-1.2.3* \
|
||||
mesa-libGL-devel-22.2.2* \
|
||||
npm-8.15.0* \
|
||||
numactl-devel-2.0.14* \
|
||||
openssl-devel-3.0.5* \
|
||||
opus-devel-1.3.1* \
|
||||
pulseaudio-libs-devel-16.1* \
|
||||
rpm-build-4.18.0* \
|
||||
wget-1.21.3* \
|
||||
which-2.21*
|
||||
boost-devel-1.78.* \
|
||||
cmake-3.26.* \
|
||||
gcc-12.2.* \
|
||||
gcc-c++-12.2.* \
|
||||
git \
|
||||
libappindicator-gtk3-devel \
|
||||
libcap-devel \
|
||||
libcurl-devel \
|
||||
libdrm-devel \
|
||||
libevdev-devel \
|
||||
libva-devel \
|
||||
libvdpau-devel \
|
||||
libX11-devel \
|
||||
libxcb-devel \
|
||||
libXcursor-devel \
|
||||
libXfixes-devel \
|
||||
libXi-devel \
|
||||
libXinerama-devel \
|
||||
libXrandr-devel \
|
||||
libXtst-devel \
|
||||
mesa-libGL-devel \
|
||||
nodejs-npm \
|
||||
numactl-devel \
|
||||
openssl-devel \
|
||||
opus-devel \
|
||||
pulseaudio-libs-devel \
|
||||
rpm-build \
|
||||
wget \
|
||||
which
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
dnf -y install intel-mediasdk-devel-22.4.4*
|
||||
dnf -y install intel-mediasdk-devel
|
||||
fi
|
||||
dnf clean all
|
||||
rm -rf /var/cache/yum
|
||||
@@ -159,7 +159,7 @@ RUN <<_SETUP_USER
|
||||
#!/bin/bash
|
||||
set -e
|
||||
groupadd -f -g "${PGID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -u "${PUID}" "${UNAME}"
|
||||
mkdir -p ${HOME}/.config/sunshine
|
||||
ln -s ${HOME}/.config/sunshine /config
|
||||
chown -R ${UNAME} ${HOME}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# platforms_pr: linux/amd64
|
||||
# no-cache-filters: sunshine-base,artifacts,sunshine
|
||||
ARG BASE=fedora
|
||||
ARG TAG=36
|
||||
ARG TAG=38
|
||||
FROM ${BASE}:${TAG} AS sunshine-base
|
||||
|
||||
FROM sunshine-base as sunshine-build
|
||||
@@ -30,63 +30,64 @@ set -e
|
||||
dnf -y update
|
||||
dnf -y group install "Development Tools"
|
||||
dnf -y install \
|
||||
boost-devel-1.76.0* \
|
||||
cmake-3.22.2* \
|
||||
gcc-12.0.1* \
|
||||
gcc-c++-12.0.1* \
|
||||
git-2.39.2* \
|
||||
libappindicator-gtk3-devel-12.10.0* \
|
||||
libcap-devel-2.48* \
|
||||
libcurl-devel-7.82.0* \
|
||||
libdrm-devel-2.4.110* \
|
||||
libevdev-devel-1.12.0* \
|
||||
libva-devel-2.14.0* \
|
||||
libvdpau-devel-1.5* \
|
||||
libX11-devel-1.7.3* \
|
||||
libxcb-devel-1.13.1* \
|
||||
libXcursor-devel-1.2.0* \
|
||||
libXfixes-devel-6.0.0* \
|
||||
libXi-devel-1.8* \
|
||||
libXinerama-devel-1.1.4* \
|
||||
libXrandr-devel-1.5.2* \
|
||||
libXtst-devel-1.2.3* \
|
||||
mesa-libGL-devel-22.0.1* \
|
||||
npm-8.3.1* \
|
||||
numactl-devel-2.0.14* \
|
||||
openssl-devel-3.0.2* \
|
||||
opus-devel-1.3.1* \
|
||||
pulseaudio-libs-devel-15.0* \
|
||||
rpm-build-4.17.0* \
|
||||
wget-1.21.3* \
|
||||
which-2.21*
|
||||
boost-devel-1.78.0* \
|
||||
cmake-3.26.* \
|
||||
gcc-13.0.* \
|
||||
gcc-c++-13.0.* \
|
||||
git \
|
||||
libappindicator-gtk3-devel \
|
||||
libcap-devel \
|
||||
libcurl-devel \
|
||||
libdrm-devel \
|
||||
libevdev-devel \
|
||||
libva-devel \
|
||||
libvdpau-devel \
|
||||
libX11-devel \
|
||||
libxcb-devel \
|
||||
libXcursor-devel \
|
||||
libXfixes-devel \
|
||||
libXi-devel \
|
||||
libXinerama-devel \
|
||||
libXrandr-devel \
|
||||
libXtst-devel \
|
||||
mesa-libGL-devel \
|
||||
nodejs-npm \
|
||||
numactl-devel \
|
||||
openssl-devel \
|
||||
opus-devel \
|
||||
pulseaudio-libs-devel \
|
||||
rpm-build \
|
||||
wget \
|
||||
which
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
dnf -y install intel-mediasdk-devel-22.3.0*
|
||||
dnf -y install intel-mediasdk-devel
|
||||
fi
|
||||
dnf clean all
|
||||
rm -rf /var/cache/yum
|
||||
_DEPS
|
||||
|
||||
# install cuda
|
||||
WORKDIR /build/cuda
|
||||
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
||||
ENV CUDA_VERSION="12.0.0"
|
||||
ENV CUDA_BUILD="525.60.13"
|
||||
# hadolint ignore=SC3010
|
||||
RUN <<_INSTALL_CUDA
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
|
||||
cuda_suffix=""
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
|
||||
cuda_suffix="_sbsa"
|
||||
fi
|
||||
url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run"
|
||||
echo "cuda url: ${url}"
|
||||
wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run
|
||||
chmod a+x ./cuda.run
|
||||
./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm
|
||||
rm ./cuda.run
|
||||
_INSTALL_CUDA
|
||||
# todo - enable cuda once it's supported for gcc 13 and fedora 38
|
||||
## install cuda
|
||||
#WORKDIR /build/cuda
|
||||
## versions: https://developer.nvidia.com/cuda-toolkit-archive
|
||||
#ENV CUDA_VERSION="12.0.0"
|
||||
#ENV CUDA_BUILD="525.60.13"
|
||||
## hadolint ignore=SC3010
|
||||
#RUN <<_INSTALL_CUDA
|
||||
##!/bin/bash
|
||||
#set -e
|
||||
#cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
|
||||
#cuda_suffix=""
|
||||
#if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
|
||||
# cuda_suffix="_sbsa"
|
||||
#fi
|
||||
#url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run"
|
||||
#echo "cuda url: ${url}"
|
||||
#wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run
|
||||
#chmod a+x ./cuda.run
|
||||
#./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm
|
||||
#rm ./cuda.run
|
||||
#_INSTALL_CUDA
|
||||
|
||||
# copy repository
|
||||
WORKDIR /build/sunshine/
|
||||
@@ -99,11 +100,12 @@ RUN npm install
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
# cmake and cpack
|
||||
# todo - add cmake argument back in for cuda support "-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \"
|
||||
# todo - re-enable "DSUNSHINE_ENABLE_CUDA"
|
||||
RUN <<_MAKE
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cmake \
|
||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DSUNSHINE_ASSETS_DIR=share/sunshine \
|
||||
@@ -111,7 +113,7 @@ cmake \
|
||||
-DSUNSHINE_ENABLE_WAYLAND=ON \
|
||||
-DSUNSHINE_ENABLE_X11=ON \
|
||||
-DSUNSHINE_ENABLE_DRM=ON \
|
||||
-DSUNSHINE_ENABLE_CUDA=ON \
|
||||
-DSUNSHINE_ENABLE_CUDA=OFF \
|
||||
/build/sunshine
|
||||
make -j "$(nproc)"
|
||||
cpack -G RPM
|
||||
@@ -159,7 +161,7 @@ RUN <<_SETUP_USER
|
||||
#!/bin/bash
|
||||
set -e
|
||||
groupadd -f -g "${PGID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -u "${PUID}" "${UNAME}"
|
||||
mkdir -p ${HOME}/.config/sunshine
|
||||
ln -s ${HOME}/.config/sunshine /config
|
||||
chown -R ${UNAME} ${HOME}
|
||||
@@ -30,41 +30,41 @@ RUN <<_DEPS
|
||||
set -e
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential=12.8* \
|
||||
gcc-10=10.3.0* \
|
||||
g++-10=10.3.0* \
|
||||
git=1:2.25.1* \
|
||||
libappindicator3-dev=12.10.1* \
|
||||
libavdevice-dev=7:4.2.* \
|
||||
libboost-filesystem-dev=1.71.0* \
|
||||
libboost-locale-dev=1.71.0* \
|
||||
libboost-log-dev=1.71.0* \
|
||||
libboost-program-options-dev=1.71.0* \
|
||||
libboost-thread-dev=1.71.0* \
|
||||
libcap-dev=1:2.32* \
|
||||
libcurl4-openssl-dev=7.68.0* \
|
||||
libdrm-dev=2.4.107* \
|
||||
libevdev-dev=1.9.0* \
|
||||
libnuma-dev=2.0.12* \
|
||||
libopus-dev=1.3.1* \
|
||||
libpulse-dev=1:13.99.1* \
|
||||
libssl-dev=1.1.1* \
|
||||
libva-dev=2.7.0* \
|
||||
libvdpau-dev=1.3* \
|
||||
libwayland-dev=1.18.0* \
|
||||
libx11-dev=2:1.6.9* \
|
||||
libxcb-shm0-dev=1.14* \
|
||||
libxcb-xfixes0-dev=1.14* \
|
||||
libxcb1-dev=1.14* \
|
||||
libxfixes-dev=1:5.0.3* \
|
||||
libxrandr-dev=2:1.5.2* \
|
||||
libxtst-dev=2:1.2.3* \
|
||||
nodejs=10.19.0* \
|
||||
npm=6.14.4* \
|
||||
wget=1.20.3*
|
||||
build-essential \
|
||||
gcc-10=10.3.* \
|
||||
g++-10=10.3.* \
|
||||
git \
|
||||
libappindicator3-dev \
|
||||
libavdevice-dev \
|
||||
libboost-filesystem-dev=1.71.* \
|
||||
libboost-locale-dev=1.71.* \
|
||||
libboost-log-dev=1.71.* \
|
||||
libboost-program-options-dev=1.71.* \
|
||||
libboost-thread-dev=1.71.* \
|
||||
libcap-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libdrm-dev \
|
||||
libevdev-dev \
|
||||
libnuma-dev \
|
||||
libopus-dev \
|
||||
libpulse-dev \
|
||||
libssl-dev \
|
||||
libva-dev \
|
||||
libvdpau-dev \
|
||||
libwayland-dev \
|
||||
libx11-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libxcb1-dev \
|
||||
libxfixes-dev \
|
||||
libxrandr-dev \
|
||||
libxtst-dev \
|
||||
nodejs \
|
||||
npm \
|
||||
wget
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
apt-get install -y --no-install-recommends \
|
||||
libmfx-dev=20.1.0*
|
||||
libmfx-dev
|
||||
fi
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
@@ -198,7 +198,7 @@ RUN <<_SETUP_USER
|
||||
#!/bin/bash
|
||||
set -e
|
||||
groupadd -f -g "${PGID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -u "${PUID}" "${UNAME}"
|
||||
mkdir -p ${HOME}/.config/sunshine
|
||||
ln -s ${HOME}/.config/sunshine /config
|
||||
chown -R ${UNAME} ${HOME}
|
||||
|
||||
@@ -30,40 +30,40 @@ RUN <<_DEPS
|
||||
set -e
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential=12.9* \
|
||||
cmake=3.22.1* \
|
||||
git=1:2.34.1* \
|
||||
libappindicator3-dev=12.10.1* \
|
||||
libavdevice-dev=7:4.4.* \
|
||||
libboost-filesystem-dev=1.74.0* \
|
||||
libboost-locale-dev=1.74.0* \
|
||||
libboost-log-dev=1.74.0* \
|
||||
libboost-program-options-dev=1.74.0* \
|
||||
libboost-thread-dev=1.74.0* \
|
||||
libcap-dev=1:2.44* \
|
||||
libcurl4-openssl-dev=7.81.0* \
|
||||
libdrm-dev=2.4.113* \
|
||||
libevdev-dev=1.12.1* \
|
||||
libnuma-dev=2.0.14* \
|
||||
libopus-dev=1.3.1* \
|
||||
libpulse-dev=1:15.99.1* \
|
||||
libssl-dev=3.0.2* \
|
||||
libva-dev=2.14.0* \
|
||||
libvdpau-dev=1.4* \
|
||||
libwayland-dev=1.20.0* \
|
||||
libx11-dev=2:1.7.5* \
|
||||
libxcb-shm0-dev=1.14* \
|
||||
libxcb-xfixes0-dev=1.14* \
|
||||
libxcb1-dev=1.14* \
|
||||
libxfixes-dev=1:6.0.0* \
|
||||
libxrandr-dev=2:1.5.2* \
|
||||
libxtst-dev=2:1.2.3* \
|
||||
nodejs=12.22.9* \
|
||||
npm=8.5.1* \
|
||||
wget=1.21.2*
|
||||
build-essential \
|
||||
cmake=3.22.* \
|
||||
git \
|
||||
libappindicator3-dev \
|
||||
libavdevice-dev \
|
||||
libboost-filesystem-dev=1.74.* \
|
||||
libboost-locale-dev=1.74.* \
|
||||
libboost-log-dev=1.74.* \
|
||||
libboost-program-options-dev=1.74.* \
|
||||
libboost-thread-dev=1.74.* \
|
||||
libcap-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libdrm-dev \
|
||||
libevdev-dev \
|
||||
libnuma-dev \
|
||||
libopus-dev \
|
||||
libpulse-dev \
|
||||
libssl-dev \
|
||||
libva-dev \
|
||||
libvdpau-dev \
|
||||
libwayland-dev \
|
||||
libx11-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libxcb1-dev \
|
||||
libxfixes-dev \
|
||||
libxrandr-dev \
|
||||
libxtst-dev \
|
||||
nodejs \
|
||||
npm \
|
||||
wget
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
apt-get install -y --no-install-recommends \
|
||||
libmfx-dev=22.3.0*
|
||||
libmfx-dev
|
||||
fi
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
@@ -162,7 +162,7 @@ RUN <<_SETUP_USER
|
||||
#!/bin/bash
|
||||
set -e
|
||||
groupadd -f -g "${PGID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -u "${PUID}" "${UNAME}"
|
||||
mkdir -p ${HOME}/.config/sunshine
|
||||
ln -s ${HOME}/.config/sunshine /config
|
||||
chown -R ${UNAME} ${HOME}
|
||||
|
||||
@@ -184,7 +184,7 @@ FULL_PATH_NAMES = YES
|
||||
# will be relative from the directory where doxygen is started.
|
||||
# This tag requires that the tag FULL_PATH_NAMES is set to YES.
|
||||
|
||||
STRIP_FROM_PATH =
|
||||
STRIP_FROM_PATH = ../
|
||||
|
||||
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
|
||||
# path mentioned in the documentation of a class, which tells the reader which
|
||||
@@ -193,7 +193,7 @@ STRIP_FROM_PATH =
|
||||
# specify the list of include paths that are normally passed to the compiler
|
||||
# using the -I flag.
|
||||
|
||||
STRIP_FROM_INC_PATH =
|
||||
STRIP_FROM_INC_PATH = ../
|
||||
|
||||
# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
|
||||
# less readable) file names. This can be useful is your file systems doesn't
|
||||
@@ -878,6 +878,7 @@ WARN_IF_UNDOC_ENUM_VAL = NO
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_AS_ERROR = NO
|
||||
# todo - ideally this will eventually become FAIL_ON_WARNINGS
|
||||
|
||||
# The WARN_FORMAT tag determines the format of the warning messages that doxygen
|
||||
# can produce. The string should contain the $file, $line, and $text tags, which
|
||||
@@ -2331,7 +2332,7 @@ ENABLE_PREPROCESSING = YES
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
MACRO_EXPANSION = NO
|
||||
MACRO_EXPANSION = YES
|
||||
|
||||
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
|
||||
# the macro expansion is limited to the macros specified with the PREDEFINED and
|
||||
@@ -2354,7 +2355,7 @@ SEARCH_INCLUDES = YES
|
||||
# RECURSIVE has no effect here.
|
||||
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
|
||||
|
||||
INCLUDE_PATH =
|
||||
INCLUDE_PATH = ../third-party/ffmpeg-linux-x86_64/include/
|
||||
|
||||
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
|
||||
# patterns (like *.h and *.hpp) to filter out the header-files in the
|
||||
@@ -2463,7 +2464,7 @@ HIDE_UNDOC_RELATIONS = YES
|
||||
# set to NO
|
||||
# The default value is: NO.
|
||||
|
||||
HAVE_DOT = NO
|
||||
HAVE_DOT = YES
|
||||
|
||||
# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
|
||||
# to run in parallel. When set to 0 doxygen will base this on the number of
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
breathe==4.35.0
|
||||
furo==2023.3.27
|
||||
furo==2023.5.20
|
||||
m2r2==0.3.3.post2
|
||||
Sphinx==6.1.3
|
||||
sphinx-copybutton==0.5.1
|
||||
Sphinx==7.0.1
|
||||
sphinx-copybutton==0.5.2
|
||||
|
||||
@@ -156,14 +156,12 @@ back_button_timeout
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
**Description**
|
||||
If, after the timeout, the back/select button is still pressed down, Home/Guide button press is emulated.
|
||||
|
||||
On Nvidia Shield, the home and power button are not passed to Moonlight.
|
||||
If the Back/Select button is held down for the specified number of milliseconds, a Home/Guide button press is emulated.
|
||||
|
||||
.. Tip:: If back_button_timeout < 0, then the Home/Guide button will not be emulated.
|
||||
|
||||
**Default**
|
||||
``2000``
|
||||
``-1``
|
||||
|
||||
**Example**
|
||||
.. code-block:: text
|
||||
@@ -200,6 +198,27 @@ key_repeat_frequency
|
||||
|
||||
key_repeat_frequency = 24.9
|
||||
|
||||
always_send_scancodes
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
**Description**
|
||||
Sending scancodes enhances compatibility with games and apps but may result in incorrect keyboard input
|
||||
from certain clients that aren't using a US English keyboard layout.
|
||||
|
||||
Enable if keyboard input is not working at all in certain applications.
|
||||
|
||||
Disable if keys on the client are generating the wrong input on the host.
|
||||
|
||||
.. Caution:: Applies to Windows only.
|
||||
|
||||
**Default**
|
||||
``enabled``
|
||||
|
||||
**Example**
|
||||
.. code-block:: text
|
||||
|
||||
always_send_scancodes = enabled
|
||||
|
||||
keybindings
|
||||
^^^^^^^^^^^
|
||||
|
||||
@@ -373,7 +392,7 @@ resolutions
|
||||
2560x1080,
|
||||
3440x1440,
|
||||
1920x1200,
|
||||
3860x2160,
|
||||
3840x2160,
|
||||
3840x1600,
|
||||
]
|
||||
|
||||
@@ -389,7 +408,7 @@ resolutions
|
||||
2560x1080,
|
||||
3440x1440,
|
||||
1920x1200,
|
||||
3860x2160,
|
||||
3840x2160,
|
||||
3840x1600,
|
||||
]
|
||||
|
||||
@@ -447,6 +466,8 @@ audio_sink
|
||||
|
||||
tools\audio-info.exe
|
||||
|
||||
.. Tip:: If you have multiple audio devices with identical names, use the Device ID instead.
|
||||
|
||||
.. Tip:: If you want to mute the host speakers, use `virtual_sink`_ instead.
|
||||
|
||||
**Default**
|
||||
@@ -466,7 +487,7 @@ audio_sink
|
||||
**Windows**
|
||||
.. code-block:: text
|
||||
|
||||
audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}
|
||||
audio_sink = Speakers (High Definition Audio Device)
|
||||
|
||||
virtual_sink
|
||||
^^^^^^^^^^^^
|
||||
@@ -481,14 +502,31 @@ virtual_sink
|
||||
|
||||
- Stream Streaming Speakers (Linux, macOS, Windows)
|
||||
|
||||
- To use this option, you must have Steam installed and have used Stream remote play at least once.
|
||||
- Steam must be installed.
|
||||
- Enable `install_steam_audio_drivers`_ or use Steam Remote Play at least once to install the drivers.
|
||||
|
||||
- `Virtual Audio Cable <https://vb-audio.com/Cable/>`_ (macOS, Windows)
|
||||
|
||||
**Example**
|
||||
.. code-block:: text
|
||||
|
||||
virtual_sink = {0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}
|
||||
virtual_sink = Steam Streaming Speakers
|
||||
|
||||
install_steam_audio_drivers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
**Description**
|
||||
Installs the Steam Streaming Speakers driver (if Steam is installed) to support surround sound and muting host audio.
|
||||
|
||||
.. Tip:: This option is only supported on Windows.
|
||||
|
||||
**Default**
|
||||
``enabled``
|
||||
|
||||
**Example**
|
||||
.. code-block:: text
|
||||
|
||||
install_steam_audio_drivers = enabled
|
||||
|
||||
Network
|
||||
-------
|
||||
@@ -790,7 +828,7 @@ capture
|
||||
nvfbc Use NVIDIA Frame Buffer Capture to capture direct to GPU memory. This is usually the fastest method for
|
||||
NVIDIA cards. For GeForce cards it will only work with drivers patched with
|
||||
`nvidia-patch <https://github.com/keylase/nvidia-patch/>`_
|
||||
or `nvlax <https://github.com/keylase/nvidia-patch/>`_.
|
||||
or `nvlax <https://github.com/illnyang/nvlax/>`_.
|
||||
wlr Capture for wlroots based Wayland compositors via DMA-BUF.
|
||||
kms DRM/KMS screen capture from the kernel. This requires that sunshine has cap_sys_admin capability.
|
||||
See :ref:`Linux Setup <about/usage:setup>`.
|
||||
|
||||
@@ -187,3 +187,32 @@ Changing Resolution and Refresh Rate (Windows)
|
||||
|
||||
.. Tip:: You can change your host resolution to match the client resolution automatically using the
|
||||
`Nonary/ResolutionAutomation <https://github.com/Nonary/ResolutionAutomation/>`_ project.
|
||||
|
||||
|
||||
Elevating Commands (Windows)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you've installed Sunshine as a service (default), you can now specify if a command should be elevated with adminsitrative privileges.
|
||||
Simply enable the elevated option in the WEB UI, or add it to the JSON configuration.
|
||||
This is an option for both prep-cmd and regular commands and will launch the process with the current user without a UAC prompt.
|
||||
|
||||
.. Note:: It's important to write the values "true" and "false" as string values, not as the typical true/false values in most JSON.
|
||||
|
||||
**Example**
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name": "Game With AntiCheat that Requires Admin",
|
||||
"output": "",
|
||||
"cmd": "ping 127.0.0.1",
|
||||
"exclude-global-prep-cmd": "false",
|
||||
"elevated": "true",
|
||||
"prep-cmd": [
|
||||
{
|
||||
"do": "powershell.exe -command \"Start-Streaming\"",
|
||||
"undo": "powershell.exe -command \"Stop-Streaming\"",
|
||||
"elevated": "false"
|
||||
}
|
||||
],
|
||||
"image-path": ""
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ CUDA is used for NVFBC capture.
|
||||
sunshine.pkg.tar.zst 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
|
||||
sunshine_{arch}.flatpak 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90
|
||||
sunshine-debian-bullseye-{arch}.deb 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
|
||||
sunshine-fedora-36-{arch}.rpm 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90
|
||||
sunshine-fedora-37-{arch}.rpm 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90
|
||||
sunshine-fedora-38-{arch}.rpm unavailable unavailable none
|
||||
sunshine-ubuntu-20.04-{arch}.deb 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
|
||||
sunshine-ubuntu-22.04-{arch}.deb 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
|
||||
=========================================== ============== ============== ================================
|
||||
@@ -190,11 +190,11 @@ macOS
|
||||
-----
|
||||
Sunshine on macOS is experimental. Gamepads do not work. Other features may not work as expected.
|
||||
|
||||
pkg
|
||||
dmg
|
||||
^^^
|
||||
.. Warning:: The `pkg` does not include runtime dependencies.
|
||||
.. Warning:: The `dmg` does not include runtime dependencies.
|
||||
|
||||
#. Download the ``sunshine.pkg`` file and install it as normal.
|
||||
#. Download the ``sunshine.dmg`` file and install it.
|
||||
|
||||
Uninstall:
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -68,16 +68,11 @@ The `deb`, `rpm`, `Flatpak` and `AppImage` packages handle these steps automatic
|
||||
|
||||
Sunshine needs access to `uinput` to create mouse and gamepad events.
|
||||
|
||||
#. Add user to group `input`, if this is the first time installing.
|
||||
#. Create `udev` rules.
|
||||
.. code-block:: bash
|
||||
|
||||
sudo usermod -a -G input $USER
|
||||
|
||||
#. Create `udev` rules.
|
||||
.. code-block::
|
||||
|
||||
echo 'KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"' | \
|
||||
sudo tee /etc/udev/rules.d/85-sunshine-input.rules
|
||||
echo 'KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", TAG+="uaccess"' | \
|
||||
sudo tee /etc/udev/rules.d/85-sunshine.rules
|
||||
|
||||
#. Optionally, configure autostart service
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ root_dir = os.path.dirname(source_dir) # the root folder directory
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
project = 'Sunshine'
|
||||
copyright = f'{datetime.now ().year}, {project}'
|
||||
project_copyright = f'{datetime.now ().year}, {project}'
|
||||
author = 'ReenigneArcher'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
@@ -95,8 +95,16 @@ breathe_projects = dict(
|
||||
)
|
||||
todo_include_todos = True
|
||||
|
||||
subprocess.run('doxygen', shell=True, cwd=source_dir)
|
||||
|
||||
# disable epub mimetype warnings
|
||||
# https://github.com/readthedocs/readthedocs.org/blob/eadf6ac6dc6abc760a91e1cb147cc3c5f37d1ea8/docs/conf.py#L235-L236
|
||||
suppress_warnings = ["epub.unknown_project_files"]
|
||||
|
||||
# get doxygen version
|
||||
doxy_proc = subprocess.run('doxygen --version', shell=True, cwd=source_dir, capture_output=True)
|
||||
doxy_version = doxy_proc.stdout.decode('utf-8').strip()
|
||||
print('doxygen version: ' + doxy_version)
|
||||
|
||||
# run doxygen
|
||||
doxy_proc = subprocess.run('doxygen Doxyfile', shell=True, cwd=source_dir)
|
||||
if doxy_proc.returncode != 0:
|
||||
raise RuntimeError('doxygen failed with return code ' + str(doxy_proc.returncode))
|
||||
|
||||
@@ -12,6 +12,14 @@ migration option. At the time of writing this GSMS offers the ability to migrate
|
||||
working directory, command, and image are all set in Sunshine's ``apps.json`` file. The box-art image is also copied
|
||||
to a specified directory.
|
||||
|
||||
Internet Streaming
|
||||
------------------
|
||||
If you are using the Moonlight Internet Hosting Tool, you can remove it from your system when you migrate to Sunshine.
|
||||
To stream over the Internet with Sunshine and a UPnP-capable router, enable the UPnP option in the Sunshine Web UI.
|
||||
|
||||
.. note:: Running Sunshine together with versions of the Moonlight Internet Hosting Tool prior to v5.6 will cause UPnP
|
||||
port forwarding to become unreliable. Either uninstall the tool entirely or update it to v5.6 or later.
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
Sunshine does have some limitations, as compared to Nvidia GameStream.
|
||||
|
||||
@@ -2,3 +2,4 @@ audio
|
||||
=====
|
||||
|
||||
.. doxygenfile:: audio.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ cbs
|
||||
===
|
||||
|
||||
.. doxygenfile:: cbs.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ config
|
||||
======
|
||||
|
||||
.. doxygenfile:: config.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ confighttp
|
||||
==========
|
||||
|
||||
.. doxygenfile:: confighttp.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ crypto
|
||||
======
|
||||
|
||||
.. doxygenfile:: crypto.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ httpcommon
|
||||
==========
|
||||
|
||||
.. doxygenfile:: httpcommon.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ input
|
||||
=====
|
||||
|
||||
.. doxygenfile:: input.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ main
|
||||
====
|
||||
|
||||
.. doxygenfile:: main.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ move_by_copy
|
||||
============
|
||||
|
||||
.. doxygenfile:: move_by_copy.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ network
|
||||
=======
|
||||
|
||||
.. doxygenfile:: network.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ nvhttp
|
||||
======
|
||||
|
||||
.. doxygenfile:: nvhttp.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ cuda
|
||||
====
|
||||
|
||||
.. doxygenfile:: platform/linux/cuda.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ vaapi
|
||||
=====
|
||||
|
||||
.. doxygenfile:: platform/linux/vaapi.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ wayland
|
||||
=======
|
||||
|
||||
.. doxygenfile:: platform/linux/wayland.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ misc
|
||||
====
|
||||
|
||||
.. doxygenfile:: platform/macos/misc.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ misc
|
||||
====
|
||||
|
||||
.. doxygenfile:: platform/windows/misc.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ process
|
||||
=======
|
||||
|
||||
.. doxygenfile:: process.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ round_robin
|
||||
===========
|
||||
|
||||
.. doxygenfile:: round_robin.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ rtsp
|
||||
====
|
||||
|
||||
.. doxygenfile:: rtsp.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ stream
|
||||
======
|
||||
|
||||
.. doxygenfile:: stream.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ sync
|
||||
====
|
||||
|
||||
.. doxygenfile:: sync.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ system_tray
|
||||
===========
|
||||
|
||||
.. doxygenfile:: system_tray.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
tasl_pool
|
||||
task_pool
|
||||
=========
|
||||
|
||||
.. doxygenfile:: task_pool.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ thread_pool
|
||||
===========
|
||||
|
||||
.. doxygenfile:: thread_pool.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ thread_safe
|
||||
===========
|
||||
|
||||
.. doxygenfile:: thread_safe.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ upnp
|
||||
====
|
||||
|
||||
.. doxygenfile:: upnp.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ uuid
|
||||
====
|
||||
|
||||
.. doxygenfile:: uuid.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -2,3 +2,4 @@ video
|
||||
=====
|
||||
|
||||
.. doxygenfile:: video.h
|
||||
:allow-dot-graphs:
|
||||
|
||||
@@ -45,9 +45,8 @@ echo "
|
||||
|
||||
function install() {
|
||||
# user input rules
|
||||
sudo usermod -a -G input $USER
|
||||
# shellcheck disable=SC2002
|
||||
cat "$SUNSHINE_SHARE_HERE/udev/rules.d/85-sunshine.rules" | sudo tee /etc/udev/85-sunshine.rules
|
||||
cat "$SUNSHINE_SHARE_HERE/udev/rules.d/85-sunshine.rules" | sudo tee /etc/udev/rules.d/85-sunshine.rules
|
||||
|
||||
# sunshine service
|
||||
mkdir -p ~/.config/systemd/user
|
||||
|
||||
@@ -6,8 +6,8 @@ cp /app/share/sunshine/systemd/user/sunshine.service $HOME/.config/systemd/user/
|
||||
echo Sunshine User Service has been installed.
|
||||
echo Use [systemctl --user enable sunshine] once to autostart Sunshine on login.
|
||||
|
||||
# Udev rule and input group
|
||||
# Udev rule
|
||||
UDEV=$(cat /app/share/sunshine/udev/rules.d/85-sunshine.rules)
|
||||
echo Configuring mouse permission.
|
||||
flatpak-spawn --host pkexec sh -c "usermod -a -G input $USER && echo '$UDEV' > /etc/udev/rules.d/85-sunshine.rules"
|
||||
flatpak-spawn --host pkexec sh -c "echo '$UDEV' > /etc/udev/rules.d/85-sunshine.rules"
|
||||
echo Restart computer for mouse permission to take effect.
|
||||
|
||||
@@ -6,6 +6,6 @@ rm $HOME/.config/systemd/user/sunshine.service
|
||||
systemctl --user daemon-reload
|
||||
echo Sunshine User Service has been removed.
|
||||
|
||||
# Udev rule and input group
|
||||
flatpak-spawn --host pkexec sh -c "gpasswd -d $USER input && rm /etc/udev/rules.d/85-sunshine.rules"
|
||||
# Udev rule
|
||||
flatpak-spawn --host pkexec sh -c "rm /etc/udev/rules.d/85-sunshine.rules"
|
||||
echo Mouse permission removed. Restart computer to take effect.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/audio.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include <thread>
|
||||
|
||||
#include <opus/opus_multistream.h>
|
||||
@@ -135,22 +139,30 @@ namespace audio {
|
||||
return;
|
||||
}
|
||||
|
||||
auto init_failure_fg = util::fail_guard([&shutdown_event]() {
|
||||
BOOST_LOG(error) << "Unable to initialize audio capture. The stream will not have audio."sv;
|
||||
|
||||
// Wait for shutdown to be signalled if we fail init.
|
||||
// This allows streaming to continue without audio.
|
||||
shutdown_event->view();
|
||||
});
|
||||
|
||||
auto &control = ref->control;
|
||||
if (!control) {
|
||||
shutdown_event->view();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Order of priority:
|
||||
// 1. Config
|
||||
// 2. Virtual if available
|
||||
// 1. Virtual sink
|
||||
// 2. Audio sink
|
||||
// 3. Host
|
||||
std::string *sink = &ref->sink.host;
|
||||
if (!config::audio.sink.empty()) {
|
||||
sink = &config::audio.sink;
|
||||
}
|
||||
else if (ref->sink.null) {
|
||||
|
||||
// Prefer the virtual sink if host playback is disabled or there's no other sink
|
||||
if (ref->sink.null && (!config.flags[config_t::HOST_AUDIO] || sink->empty())) {
|
||||
auto &null = *ref->sink.null;
|
||||
switch (stream->channelCount) {
|
||||
case 2:
|
||||
@@ -167,20 +179,24 @@ namespace audio {
|
||||
|
||||
// Only the first to start a session may change the default sink
|
||||
if (!ref->sink_flag->exchange(true, std::memory_order_acquire)) {
|
||||
ref->restore_sink = !config.flags[config_t::HOST_AUDIO];
|
||||
|
||||
// If the sink is empty (Host has no sink!), definately switch to the virtual.
|
||||
if (ref->sink.host.empty()) {
|
||||
// If the selected sink is different than the current one, change sinks.
|
||||
ref->restore_sink = ref->sink.host != *sink;
|
||||
if (ref->restore_sink) {
|
||||
if (control->set_sink(*sink)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If the client requests audio on the host, don't change the default sink
|
||||
else if (!config.flags[config_t::HOST_AUDIO] && control->set_sink(*sink)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
|
||||
auto mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
|
||||
if (!mic) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Audio is initialized, so we don't want to print the failure message
|
||||
init_failure_fg.disable();
|
||||
|
||||
// Capture takes place on this thread
|
||||
platf::adjust_thread_priority(platf::thread_priority_e::critical);
|
||||
|
||||
@@ -194,16 +210,8 @@ namespace audio {
|
||||
shutdown_event->view();
|
||||
});
|
||||
|
||||
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
|
||||
int samples_per_frame = frame_size * stream->channelCount;
|
||||
|
||||
auto mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
|
||||
if (!mic) {
|
||||
BOOST_LOG(error) << "Couldn't create audio input"sv;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
while (!shutdown_event->peek()) {
|
||||
std::vector<std::int16_t> sample_buffer;
|
||||
sample_buffer.resize(samples_per_frame);
|
||||
@@ -215,14 +223,15 @@ namespace audio {
|
||||
case platf::capture_e::timeout:
|
||||
continue;
|
||||
case platf::capture_e::reinit:
|
||||
BOOST_LOG(info) << "Reinitializing audio capture"sv;
|
||||
mic.reset();
|
||||
mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
|
||||
if (!mic) {
|
||||
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
do {
|
||||
mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
|
||||
if (!mic) {
|
||||
BOOST_LOG(warning) << "Couldn't re-initialize audio input"sv;
|
||||
}
|
||||
} while (!mic && !shutdown_event->view(5s));
|
||||
continue;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
@@ -280,7 +289,8 @@ namespace audio {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string &sink = config::audio.sink.empty() ? ctx.sink.host : config::audio.sink;
|
||||
// Change back to the host sink, unless there was none
|
||||
const std::string &sink = ctx.sink.host.empty() ? config::audio.sink : ctx.sink.host;
|
||||
if (!sink.empty()) {
|
||||
// Best effort, it's allowed to fail
|
||||
ctx.control->set_sink(sink);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#ifndef SUNSHINE_AUDIO_H
|
||||
#define SUNSHINE_AUDIO_H
|
||||
/**
|
||||
* @file src/audio.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "thread_safe.h"
|
||||
#include "utility.h"
|
||||
@@ -44,5 +47,3 @@ namespace audio {
|
||||
void
|
||||
capture(safe::mail_t mail, config_t config, void *channel_data);
|
||||
} // namespace audio
|
||||
|
||||
#endif
|
||||
|
||||
12
src/cbs.cpp
12
src/cbs.cpp
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/cbs.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
extern "C" {
|
||||
#include <cbs/cbs_h264.h>
|
||||
#include <cbs/cbs_h265.h>
|
||||
@@ -50,7 +54,7 @@ namespace cbs {
|
||||
};
|
||||
|
||||
util::buffer_t<std::uint8_t>
|
||||
write(const cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
|
||||
write(cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
|
||||
cbs::frag_t frag;
|
||||
auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr);
|
||||
if (err < 0) {
|
||||
@@ -87,9 +91,9 @@ namespace cbs {
|
||||
make_sps_h264(const AVCodecContext *ctx) {
|
||||
H264RawSPS sps {};
|
||||
|
||||
/* b_per_p == ctx->max_b_frames for h264 */
|
||||
/* desired_b_depth == avoption("b_depth") == 1 */
|
||||
/* max_b_depth == std::min(av_log2(ctx->b_per_p) + 1, desired_b_depth) ==> 1 */
|
||||
// b_per_p == ctx->max_b_frames for h264
|
||||
// desired_b_depth == avoption("b_depth") == 1
|
||||
// max_b_depth == std::min(av_log2(ctx->b_per_p) + 1, desired_b_depth) ==> 1
|
||||
auto max_b_depth = 1;
|
||||
auto dpb_frame = ctx->gop_size == 1 ? 0 : 1 + max_b_depth;
|
||||
auto mb_width = (FFALIGN(ctx->width, 16) / 16) * 16;
|
||||
|
||||
13
src/cbs.h
13
src/cbs.h
@@ -1,5 +1,8 @@
|
||||
#ifndef SUNSHINE_CBS_H
|
||||
#define SUNSHINE_CBS_H
|
||||
/**
|
||||
* @file src/cbs.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
@@ -28,10 +31,8 @@ namespace cbs {
|
||||
make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet);
|
||||
|
||||
/**
|
||||
* Check if SPS->VUI is present
|
||||
*/
|
||||
* Check if SPS->VUI is present
|
||||
*/
|
||||
bool
|
||||
validate_sps(const AVPacket *packet, int codec_id);
|
||||
} // namespace cbs
|
||||
|
||||
#endif
|
||||
|
||||
132
src/config.cpp
132
src/config.cpp
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/config.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@@ -16,6 +20,10 @@
|
||||
|
||||
#include "platform/common.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace std::literals;
|
||||
|
||||
@@ -374,7 +382,11 @@ namespace config {
|
||||
true // dwmflush
|
||||
};
|
||||
|
||||
audio_t audio {};
|
||||
audio_t audio {
|
||||
{}, // audio_sink
|
||||
{}, // virtual_sink
|
||||
true, // install_steam_drivers
|
||||
};
|
||||
|
||||
stream_t stream {
|
||||
10s, // ping_timeout
|
||||
@@ -402,9 +414,9 @@ namespace config {
|
||||
"1280x720"s,
|
||||
"1920x1080"s,
|
||||
"2560x1080"s,
|
||||
"3440x1440"s
|
||||
"3440x1440"s,
|
||||
"1920x1200"s,
|
||||
"3860x2160"s,
|
||||
"3840x2160"s,
|
||||
"3840x1600"s,
|
||||
}, // supported resolutions
|
||||
|
||||
@@ -417,7 +429,7 @@ namespace config {
|
||||
{ 0x11, 0xA2 },
|
||||
{ 0x12, 0xA4 },
|
||||
},
|
||||
2s, // back_button_timeout
|
||||
-1ms, // back_button_timeout
|
||||
500ms, // key_repeat_delay
|
||||
std::chrono::duration<double> { 1 / 24.9 }, // key_repeat_period
|
||||
|
||||
@@ -429,6 +441,7 @@ namespace config {
|
||||
true, // keyboard enabled
|
||||
true, // mouse enabled
|
||||
true, // controller enabled
|
||||
true, // always send scancodes
|
||||
};
|
||||
|
||||
sunshine_t sunshine {
|
||||
@@ -816,12 +829,11 @@ namespace config {
|
||||
boost::property_tree::read_json(jsonStream, jsonTree);
|
||||
|
||||
for (auto &[_, prep_cmd] : jsonTree.get_child("prep_cmd"s)) {
|
||||
auto do_cmd = prep_cmd.get<std::string>("do"s);
|
||||
auto undo_cmd = prep_cmd.get<std::string>("undo"s);
|
||||
auto do_cmd = prep_cmd.get_optional<std::string>("do"s);
|
||||
auto undo_cmd = prep_cmd.get_optional<std::string>("undo"s);
|
||||
auto elevated = prep_cmd.get_optional<bool>("elevated"s);
|
||||
|
||||
input.emplace_back(
|
||||
std::move(do_cmd),
|
||||
std::move(undo_cmd));
|
||||
input.emplace_back(do_cmd.value_or(""), undo_cmd.value_or(""), elevated.value_or(false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -975,6 +987,7 @@ namespace config {
|
||||
|
||||
string_f(vars, "audio_sink", audio.sink);
|
||||
string_f(vars, "virtual_sink", audio.virtual_sink);
|
||||
bool_f(vars, "install_steam_audio_drivers", audio.install_steam_drivers);
|
||||
|
||||
string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, { "pc"sv, "lan"sv, "wan"sv });
|
||||
string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, { "pc"sv, "lan"sv, "wan"sv });
|
||||
@@ -1027,6 +1040,8 @@ namespace config {
|
||||
bool_f(vars, "keyboard", input.keyboard);
|
||||
bool_f(vars, "controller", input.controller);
|
||||
|
||||
bool_f(vars, "always_send_scancodes", input.always_send_scancodes);
|
||||
|
||||
int port = sunshine.port;
|
||||
int_f(vars, "port"s, port);
|
||||
sunshine.port = (std::uint16_t) port;
|
||||
@@ -1089,6 +1104,10 @@ namespace config {
|
||||
int
|
||||
parse(int argc, char *argv[]) {
|
||||
std::unordered_map<std::string, std::string> cmd_vars;
|
||||
#ifdef _WIN32
|
||||
bool shortcut_launch = false;
|
||||
bool service_admin_launch = false;
|
||||
#endif
|
||||
|
||||
for (auto x = 1; x < argc; ++x) {
|
||||
auto line = argv[x];
|
||||
@@ -1097,6 +1116,14 @@ namespace config {
|
||||
print_help(*argv);
|
||||
return 1;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
else if (line == "--shortcut"sv) {
|
||||
shortcut_launch = true;
|
||||
}
|
||||
else if (line == "--shortcut-admin"sv) {
|
||||
service_admin_launch = true;
|
||||
}
|
||||
#endif
|
||||
else if (*line == '-') {
|
||||
if (*(line + 1) == '-') {
|
||||
sunshine.cmd.name = line + 2;
|
||||
@@ -1136,23 +1163,90 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
// create appdata folder if it does not exist
|
||||
if (!boost::filesystem::exists(platf::appdata().string())) {
|
||||
boost::filesystem::create_directory(platf::appdata().string());
|
||||
bool config_loaded = false;
|
||||
try {
|
||||
// Create appdata folder if it does not exist
|
||||
if (!boost::filesystem::exists(platf::appdata().string())) {
|
||||
boost::filesystem::create_directories(platf::appdata().string());
|
||||
}
|
||||
|
||||
// Create empty config file if it does not exist
|
||||
if (!fs::exists(sunshine.config_file)) {
|
||||
std::ofstream { sunshine.config_file };
|
||||
}
|
||||
|
||||
// Read config file
|
||||
auto vars = parse_config(read_file(sunshine.config_file.c_str()));
|
||||
|
||||
for (auto &[name, value] : cmd_vars) {
|
||||
vars.insert_or_assign(std::move(name), std::move(value));
|
||||
}
|
||||
|
||||
// Apply the config. Note: This will try to create any paths
|
||||
// referenced in the config, so we may receive exceptions if
|
||||
// the path is incorrect or inaccessible.
|
||||
apply_config(std::move(vars));
|
||||
config_loaded = true;
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &err) {
|
||||
BOOST_LOG(fatal) << "Failed to apply config: "sv << err.what();
|
||||
}
|
||||
catch (const boost::filesystem::filesystem_error &err) {
|
||||
BOOST_LOG(fatal) << "Failed to apply config: "sv << err.what();
|
||||
}
|
||||
|
||||
// create config file if it does not exist
|
||||
if (!fs::exists(sunshine.config_file)) {
|
||||
std::ofstream { sunshine.config_file }; // create empty config file
|
||||
if (!config_loaded) {
|
||||
#ifdef _WIN32
|
||||
BOOST_LOG(fatal) << "To relaunch Sunshine successfully, use the shortcut in the Start Menu. Do not run Sunshine.exe manually."sv;
|
||||
std::this_thread::sleep_for(10s);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto vars = parse_config(read_file(sunshine.config_file.c_str()));
|
||||
#ifdef _WIN32
|
||||
// We have to wait until the config is loaded to handle these launches,
|
||||
// because we need to have the correct base port loaded in our config.
|
||||
if (service_admin_launch) {
|
||||
// This is a relaunch as admin to start the service
|
||||
service_ctrl::start_service();
|
||||
|
||||
for (auto &[name, value] : cmd_vars) {
|
||||
vars.insert_or_assign(std::move(name), std::move(value));
|
||||
// Always return 1 to ensure Sunshine doesn't start normally
|
||||
return 1;
|
||||
}
|
||||
else if (shortcut_launch) {
|
||||
if (!service_ctrl::is_service_running()) {
|
||||
// If the service isn't running, relaunch ourselves as admin to start it
|
||||
WCHAR executable[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, executable, ARRAYSIZE(executable));
|
||||
|
||||
apply_config(std::move(vars));
|
||||
SHELLEXECUTEINFOW shell_exec_info {};
|
||||
shell_exec_info.cbSize = sizeof(shell_exec_info);
|
||||
shell_exec_info.fMask = SEE_MASK_NOASYNC | SEE_MASK_NO_CONSOLE | SEE_MASK_NOCLOSEPROCESS;
|
||||
shell_exec_info.lpVerb = L"runas";
|
||||
shell_exec_info.lpFile = executable;
|
||||
shell_exec_info.lpParameters = L"--shortcut-admin";
|
||||
shell_exec_info.nShow = SW_NORMAL;
|
||||
if (!ShellExecuteExW(&shell_exec_info)) {
|
||||
auto winerr = GetLastError();
|
||||
std::cout << "Error: ShellExecuteEx() failed:"sv << winerr << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Wait for the elevated process to finish starting the service
|
||||
WaitForSingleObject(shell_exec_info.hProcess, INFINITE);
|
||||
CloseHandle(shell_exec_info.hProcess);
|
||||
|
||||
// Wait for the UI to be ready for connections
|
||||
service_ctrl::wait_for_ui_ready();
|
||||
}
|
||||
|
||||
// Launch the web UI
|
||||
launch_ui();
|
||||
|
||||
// Always return 1 to ensure Sunshine doesn't start normally
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
21
src/config.h
21
src/config.h
@@ -1,5 +1,8 @@
|
||||
#ifndef SUNSHINE_CONFIG_H
|
||||
#define SUNSHINE_CONFIG_H
|
||||
/**
|
||||
* @file src/config.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
@@ -62,6 +65,7 @@ namespace config {
|
||||
struct audio_t {
|
||||
std::string sink;
|
||||
std::string virtual_sink;
|
||||
bool install_steam_drivers;
|
||||
};
|
||||
|
||||
struct stream_t {
|
||||
@@ -105,6 +109,8 @@ namespace config {
|
||||
bool keyboard;
|
||||
bool mouse;
|
||||
bool controller;
|
||||
|
||||
bool always_send_scancodes;
|
||||
};
|
||||
|
||||
namespace flag {
|
||||
@@ -119,14 +125,14 @@ namespace config {
|
||||
}
|
||||
|
||||
struct prep_cmd_t {
|
||||
prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd):
|
||||
do_cmd(std::move(do_cmd)), undo_cmd(std::move(undo_cmd)) {}
|
||||
explicit prep_cmd_t(std::string &&do_cmd):
|
||||
do_cmd(std::move(do_cmd)) {}
|
||||
prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd, bool &&elevated):
|
||||
do_cmd(std::move(do_cmd)), undo_cmd(std::move(undo_cmd)), elevated(std::move(elevated)) {}
|
||||
explicit prep_cmd_t(std::string &&do_cmd, bool &&elevated):
|
||||
do_cmd(std::move(do_cmd)), elevated(std::move(elevated)) {}
|
||||
std::string do_cmd;
|
||||
std::string undo_cmd;
|
||||
bool elevated;
|
||||
};
|
||||
|
||||
struct sunshine_t {
|
||||
int min_log_level;
|
||||
std::bitset<flag::FLAG_SIZE> flags;
|
||||
@@ -162,4 +168,3 @@ namespace config {
|
||||
std::unordered_map<std::string, std::string>
|
||||
parse_config(const std::string_view &file_content);
|
||||
} // namespace config
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// Created by TheElixZammuto on 2021-05-09.
|
||||
// TODO: Authentication, better handling of routes common to nvhttp, cleanup
|
||||
/**
|
||||
* @file src/confighttp.cpp
|
||||
* @brief todo
|
||||
*
|
||||
* @todo Authentication, better handling of routes common to nvhttp, cleanup
|
||||
*/
|
||||
|
||||
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
|
||||
|
||||
@@ -128,7 +132,7 @@ namespace confighttp {
|
||||
auto password = authData.substr(index + 1);
|
||||
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
|
||||
|
||||
if (username != config::sunshine.username || hash != config::sunshine.password) {
|
||||
if (!boost::iequals(username, config::sunshine.username) || hash != config::sunshine.password) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -159,7 +163,9 @@ namespace confighttp {
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "index.html");
|
||||
response->write(header + content);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -170,7 +176,9 @@ namespace confighttp {
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "pin.html");
|
||||
response->write(header + content);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -179,11 +187,11 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/");
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "apps.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/");
|
||||
response->write(header + content, headers);
|
||||
}
|
||||
|
||||
@@ -195,7 +203,9 @@ namespace confighttp {
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "clients.html");
|
||||
response->write(header + content);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -206,7 +216,9 @@ namespace confighttp {
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "config.html");
|
||||
response->write(header + content);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -217,7 +229,9 @@ namespace confighttp {
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "password.html");
|
||||
response->write(header + content);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -229,7 +243,9 @@ namespace confighttp {
|
||||
}
|
||||
std::string header = read_file(WEB_DIR "header-no-nav.html");
|
||||
std::string content = read_file(WEB_DIR "welcome.html");
|
||||
response->write(header + content);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -240,7 +256,9 @@ namespace confighttp {
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "troubleshooting.html");
|
||||
response->write(header + content);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -314,7 +332,9 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
std::string content = read_file(config::stream.file_apps.c_str());
|
||||
response->write(content);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "application/json");
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -488,7 +508,7 @@ namespace confighttp {
|
||||
|
||||
const std::string coverdir = platf::appdata().string() + "/covers/";
|
||||
if (!boost::filesystem::exists(coverdir)) {
|
||||
boost::filesystem::create_directory(coverdir);
|
||||
boost::filesystem::create_directories(coverdir);
|
||||
}
|
||||
|
||||
std::basic_string path = coverdir + http::url_escape(key) + ".png";
|
||||
@@ -528,7 +548,6 @@ namespace confighttp {
|
||||
outputTree.put("status", "true");
|
||||
outputTree.put("platform", SUNSHINE_PLATFORM);
|
||||
outputTree.put("version", PROJECT_VER);
|
||||
outputTree.put("restart_supported", platf::restart_supported());
|
||||
|
||||
auto vars = config::parse_config(read_file(config::sunshine.config_file.c_str()));
|
||||
|
||||
@@ -579,30 +598,8 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::stringstream ss;
|
||||
std::stringstream configStream;
|
||||
ss << request->content.rdbuf();
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
if (!platf::restart_supported()) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Restart is not currently supported on this platform");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!platf::restart()) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Restart failed");
|
||||
return;
|
||||
}
|
||||
|
||||
outputTree.put("status", true);
|
||||
// We may not return from this call
|
||||
platf::restart();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -638,7 +635,7 @@ namespace confighttp {
|
||||
}
|
||||
else {
|
||||
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
|
||||
if (config::sunshine.username.empty() || (username == config::sunshine.username && hash == config::sunshine.password)) {
|
||||
if (config::sunshine.username.empty() || (boost::iequals(username, config::sunshine.username) && hash == config::sunshine.password)) {
|
||||
if (newPassword.empty() || newPassword != confirmPassword) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Password Mismatch");
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Created by loki on 6/3/19.
|
||||
|
||||
#ifndef SUNSHINE_CONFIGHTTP_H
|
||||
#define SUNSHINE_CONFIGHTTP_H
|
||||
/**
|
||||
* @file src/confighttp.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@@ -34,5 +35,3 @@ const std::map<std::string, std::string> mime_types = {
|
||||
{ "woff2", "font/woff2" },
|
||||
{ "xml", "text/xml" },
|
||||
};
|
||||
|
||||
#endif // SUNSHINE_CONFIGHTTP_H
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Created by loki on 5/31/19.
|
||||
|
||||
/**
|
||||
* @file src/crypto.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include "crypto.h"
|
||||
#include <openssl/pem.h>
|
||||
|
||||
@@ -35,13 +37,13 @@ namespace crypto {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* When certificates from two or more instances of Moonlight have been added to x509_store_t,
|
||||
* only one of them will be verified by X509_verify_cert, resulting in only a single instance of
|
||||
* Moonlight to be able to use Sunshine
|
||||
*
|
||||
* To circumvent this, x509_store_t instance will be created for each instance of the certificates.
|
||||
*/
|
||||
/**
|
||||
* When certificates from two or more instances of Moonlight have been added to x509_store_t,
|
||||
* only one of them will be verified by X509_verify_cert, resulting in only a single instance of
|
||||
* Moonlight to be able to use Sunshine
|
||||
*
|
||||
* To circumvent this, x509_store_t instance will be created for each instance of the certificates.
|
||||
*/
|
||||
const char *
|
||||
cert_chain_t::verify(x509_t::element_type *cert) {
|
||||
int err_code = 0;
|
||||
@@ -399,7 +401,7 @@ namespace crypto {
|
||||
sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
|
||||
if (EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, pkey.get()) != 1) {
|
||||
if (EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, (EVP_PKEY *) pkey.get()) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -472,7 +474,7 @@ namespace crypto {
|
||||
|
||||
bool
|
||||
verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
|
||||
auto pkey = X509_get_pubkey(x509.get());
|
||||
auto pkey = X509_get0_pubkey(x509.get());
|
||||
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
|
||||
|
||||
31
src/crypto.h
31
src/crypto.h
@@ -1,7 +1,8 @@
|
||||
// Created by loki on 6/1/19.
|
||||
|
||||
#ifndef SUNSHINE_CRYPTO_H
|
||||
#define SUNSHINE_CRYPTO_H
|
||||
/**
|
||||
* @file src/crypto.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <openssl/evp.h>
|
||||
@@ -123,11 +124,11 @@ namespace crypto {
|
||||
gcm_t(const crypto::aes_t &key, bool padding = true);
|
||||
|
||||
/**
|
||||
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size()) + crypto::cipher::tag_size
|
||||
*
|
||||
* return -1 on error
|
||||
* return bytes written on success
|
||||
*/
|
||||
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size()) + crypto::cipher::tag_size
|
||||
*
|
||||
* return -1 on error
|
||||
* return bytes written on success
|
||||
*/
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
|
||||
|
||||
@@ -145,15 +146,13 @@ namespace crypto {
|
||||
cbc_t(const crypto::aes_t &key, bool padding = true);
|
||||
|
||||
/**
|
||||
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size())
|
||||
*
|
||||
* return -1 on error
|
||||
* return bytes written on success
|
||||
*/
|
||||
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size())
|
||||
*
|
||||
* return -1 on error
|
||||
* return bytes written on success
|
||||
*/
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
|
||||
};
|
||||
} // namespace cipher
|
||||
} // namespace crypto
|
||||
|
||||
#endif //SUNSHINE_CRYPTO_H
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/httpcommon.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
|
||||
|
||||
#include "process.h"
|
||||
@@ -89,7 +93,7 @@ namespace http {
|
||||
pt::write_json(file, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "generating user credentials: "sv << e.what();
|
||||
BOOST_LOG(error) << "error writing to the credentials file, perhaps try this again as an administrator? Details: "sv << e.what();
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @file src/httpcommon.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "network.h"
|
||||
#include "thread_safe.h"
|
||||
|
||||
|
||||
157
src/input.cpp
157
src/input.cpp
@@ -1,5 +1,7 @@
|
||||
// Created by loki on 6/20/19.
|
||||
|
||||
/**
|
||||
* @file src/input.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
// define uint32_t for <moonlight-common-c/src/Input.h>
|
||||
#include <cstdint>
|
||||
extern "C" {
|
||||
@@ -17,6 +19,9 @@ extern "C" {
|
||||
#include "thread_pool.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include <boost/chrono.hpp>
|
||||
#include <boost/thread/thread.hpp>
|
||||
|
||||
using namespace std::literals;
|
||||
namespace input {
|
||||
|
||||
@@ -59,8 +64,22 @@ namespace input {
|
||||
gamepad_mask[id] = false;
|
||||
}
|
||||
|
||||
typedef uint32_t key_press_id_t;
|
||||
key_press_id_t
|
||||
make_kpid(uint16_t vk, uint8_t flags) {
|
||||
return (key_press_id_t) vk << 8 | flags;
|
||||
}
|
||||
uint16_t
|
||||
vk_from_kpid(key_press_id_t kpid) {
|
||||
return kpid >> 8;
|
||||
}
|
||||
uint8_t
|
||||
flags_from_kpid(key_press_id_t kpid) {
|
||||
return kpid & 0xFF;
|
||||
}
|
||||
|
||||
static task_pool_util::TaskPool::task_id_t key_press_repeat_id {};
|
||||
static std::unordered_map<short, bool> key_press {};
|
||||
static std::unordered_map<key_press_id_t, bool> key_press {};
|
||||
static std::array<std::uint8_t, 5> mouse_press {};
|
||||
|
||||
static platf::input_t platf_input;
|
||||
@@ -116,7 +135,7 @@ namespace input {
|
||||
touch_port_event { std::move(touch_port_event) },
|
||||
rumble_queue { std::move(rumble_queue) },
|
||||
mouse_left_button_timeout {},
|
||||
touch_port { 0, 0, 0, 0, 0, 0, 1.0f } {}
|
||||
touch_port { { 0, 0, 0, 0 }, 0, 0, 1.0f } {}
|
||||
|
||||
// Keep track of alt+ctrl+shift key combo
|
||||
int shortcutFlags;
|
||||
@@ -133,12 +152,12 @@ namespace input {
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply shortcut based on VKEY
|
||||
* On success
|
||||
* return > 0
|
||||
* On nothing
|
||||
* return 0
|
||||
*/
|
||||
* Apply shortcut based on VKEY
|
||||
* On success
|
||||
* return > 0
|
||||
* On nothing
|
||||
* return 0
|
||||
*/
|
||||
inline int
|
||||
apply_shortcut(short keyCode) {
|
||||
constexpr auto VK_F1 = 0x70;
|
||||
@@ -352,21 +371,20 @@ namespace input {
|
||||
|
||||
mouse_press[button] = !release;
|
||||
}
|
||||
///////////////////////////////////
|
||||
/*/
|
||||
* When Moonlight sends mouse input through absolute coordinates,
|
||||
* it's possible that BUTTON_RIGHT is pressed down immediately after releasing BUTTON_LEFT.
|
||||
* As a result, Sunshine will left-click on hyperlinks in the browser before right-clicking
|
||||
*
|
||||
* This can be solved by delaying BUTTON_LEFT, however, any delay on input is undesirable during gaming
|
||||
* As a compromise, Sunshine will only put delays on BUTTON_LEFT when
|
||||
* absolute mouse coordinates have been sent.
|
||||
*
|
||||
* Try to make sure BUTTON_RIGHT gets called before BUTTON_LEFT is released.
|
||||
*
|
||||
* input->mouse_left_button_timeout can only be nullptr
|
||||
* when the last mouse coordinates were absolute
|
||||
/*/
|
||||
/**
|
||||
* When Moonlight sends mouse input through absolute coordinates,
|
||||
* it's possible that BUTTON_RIGHT is pressed down immediately after releasing BUTTON_LEFT.
|
||||
* As a result, Sunshine will left-click on hyperlinks in the browser before right-clicking
|
||||
*
|
||||
* This can be solved by delaying BUTTON_LEFT, however, any delay on input is undesirable during gaming
|
||||
* As a compromise, Sunshine will only put delays on BUTTON_LEFT when
|
||||
* absolute mouse coordinates have been sent.
|
||||
*
|
||||
* Try to make sure BUTTON_RIGHT gets called before BUTTON_LEFT is released.
|
||||
*
|
||||
* input->mouse_left_button_timeout can only be nullptr
|
||||
* when the last mouse coordinates were absolute
|
||||
*/
|
||||
if (button == BUTTON_LEFT && release && !input->mouse_left_button_timeout) {
|
||||
auto f = [=]() {
|
||||
auto left_released = mouse_press[BUTTON_LEFT];
|
||||
@@ -394,7 +412,6 @@ namespace input {
|
||||
|
||||
return;
|
||||
}
|
||||
///////////////////////////////////
|
||||
|
||||
platf::button_mouse(platf_input, button, release);
|
||||
}
|
||||
@@ -410,8 +427,8 @@ namespace input {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update flags for keyboard shortcut combo's
|
||||
*/
|
||||
* Update flags for keyboard shortcut combo's
|
||||
*/
|
||||
inline void
|
||||
update_shortcutFlags(int *flags, short keyCode, bool release) {
|
||||
switch (keyCode) {
|
||||
@@ -448,17 +465,66 @@ namespace input {
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
is_modifier(uint16_t keyCode) {
|
||||
switch (keyCode) {
|
||||
case VKEY_SHIFT:
|
||||
case VKEY_LSHIFT:
|
||||
case VKEY_RSHIFT:
|
||||
case VKEY_CONTROL:
|
||||
case VKEY_LCONTROL:
|
||||
case VKEY_RCONTROL:
|
||||
case VKEY_MENU:
|
||||
case VKEY_LMENU:
|
||||
case VKEY_RMENU:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
repeat_key(short key_code) {
|
||||
send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
|
||||
if (!release) {
|
||||
// Press any synthetic modifiers required for this key
|
||||
if (synthetic_modifiers & MODIFIER_SHIFT) {
|
||||
platf::keyboard(platf_input, VKEY_SHIFT, false, flags);
|
||||
}
|
||||
if (synthetic_modifiers & MODIFIER_CTRL) {
|
||||
platf::keyboard(platf_input, VKEY_CONTROL, false, flags);
|
||||
}
|
||||
if (synthetic_modifiers & MODIFIER_ALT) {
|
||||
platf::keyboard(platf_input, VKEY_MENU, false, flags);
|
||||
}
|
||||
}
|
||||
|
||||
platf::keyboard(platf_input, map_keycode(key_code), release, flags);
|
||||
|
||||
if (!release) {
|
||||
// Raise any synthetic modifier keys we pressed
|
||||
if (synthetic_modifiers & MODIFIER_SHIFT) {
|
||||
platf::keyboard(platf_input, VKEY_SHIFT, true, flags);
|
||||
}
|
||||
if (synthetic_modifiers & MODIFIER_CTRL) {
|
||||
platf::keyboard(platf_input, VKEY_CONTROL, true, flags);
|
||||
}
|
||||
if (synthetic_modifiers & MODIFIER_ALT) {
|
||||
platf::keyboard(platf_input, VKEY_MENU, true, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
|
||||
// If key no longer pressed, stop repeating
|
||||
if (!key_press[key_code]) {
|
||||
if (!key_press[make_kpid(key_code, flags)]) {
|
||||
key_press_repeat_id = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
platf::keyboard(platf_input, map_keycode(key_code), false);
|
||||
send_key_and_modifiers(key_code, false, flags, synthetic_modifiers);
|
||||
|
||||
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code).task_id;
|
||||
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags, synthetic_modifiers).task_id;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -470,7 +536,22 @@ namespace input {
|
||||
auto release = util::endian::little(packet->header.magic) == KEY_UP_EVENT_MAGIC;
|
||||
auto keyCode = packet->keyCode & 0x00FF;
|
||||
|
||||
auto &pressed = key_press[keyCode];
|
||||
// Set synthetic modifier flags if the keyboard packet is requesting modifier
|
||||
// keys that are not current pressed.
|
||||
uint8_t synthetic_modifiers = 0;
|
||||
if (!release && !is_modifier(keyCode)) {
|
||||
if (!(input->shortcutFlags & input_t::SHIFT) && (packet->modifiers & MODIFIER_SHIFT)) {
|
||||
synthetic_modifiers |= MODIFIER_SHIFT;
|
||||
}
|
||||
if (!(input->shortcutFlags & input_t::CTRL) && (packet->modifiers & MODIFIER_CTRL)) {
|
||||
synthetic_modifiers |= MODIFIER_CTRL;
|
||||
}
|
||||
if (!(input->shortcutFlags & input_t::ALT) && (packet->modifiers & MODIFIER_ALT)) {
|
||||
synthetic_modifiers |= MODIFIER_ALT;
|
||||
}
|
||||
}
|
||||
|
||||
auto &pressed = key_press[make_kpid(keyCode, packet->flags)];
|
||||
if (!pressed) {
|
||||
if (!release) {
|
||||
// A new key has been pressed down, we need to check for key combo's
|
||||
@@ -484,7 +565,7 @@ namespace input {
|
||||
}
|
||||
|
||||
if (config::input.key_repeat_delay.count() > 0) {
|
||||
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, keyCode).task_id;
|
||||
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, keyCode, packet->flags, synthetic_modifiers).task_id;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -499,8 +580,9 @@ namespace input {
|
||||
|
||||
pressed = !release;
|
||||
|
||||
send_key_and_modifiers(keyCode, release, packet->flags, synthetic_modifiers);
|
||||
|
||||
update_shortcutFlags(&input->shortcutFlags, map_keycode(keyCode), release);
|
||||
platf::keyboard(platf_input, map_keycode(keyCode), release);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -655,6 +737,9 @@ namespace input {
|
||||
state.buttonFlags |= platf::HOME;
|
||||
platf::gamepad(platf_input, gamepad.id, state);
|
||||
|
||||
// Sleep for a short time to allow the input to be detected
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
|
||||
|
||||
// Release Home button
|
||||
state.buttonFlags &= ~platf::HOME;
|
||||
platf::gamepad(platf_input, gamepad.id, state);
|
||||
@@ -731,7 +816,7 @@ namespace input {
|
||||
}
|
||||
|
||||
for (auto &kp : key_press) {
|
||||
platf::keyboard(platf_input, kp.first & 0x00FF, true);
|
||||
platf::keyboard(platf_input, vk_from_kpid(kp.first) & 0x00FF, true, flags_from_kpid(kp.first));
|
||||
key_press[kp.first] = false;
|
||||
}
|
||||
});
|
||||
|
||||
11
src/input.h
11
src/input.h
@@ -1,7 +1,8 @@
|
||||
// Created by loki on 6/20/19.
|
||||
|
||||
#ifndef SUNSHINE_INPUT_H
|
||||
#define SUNSHINE_INPUT_H
|
||||
/**
|
||||
* @file src/input.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -33,5 +34,3 @@ namespace input {
|
||||
float scalar_inv;
|
||||
};
|
||||
} // namespace input
|
||||
|
||||
#endif // SUNSHINE_INPUT_H
|
||||
|
||||
304
src/main.cpp
304
src/main.cpp
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @file src/main.cpp
|
||||
* @brief Main entry point for Sunshine.
|
||||
*/
|
||||
|
||||
// standard includes
|
||||
@@ -34,6 +35,10 @@
|
||||
extern "C" {
|
||||
#include <libavutil/log.h>
|
||||
#include <rs.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <iphlpapi.h>
|
||||
#endif
|
||||
}
|
||||
|
||||
safe::mail_t mail::man;
|
||||
@@ -107,6 +112,263 @@ namespace version {
|
||||
}
|
||||
} // namespace version
|
||||
|
||||
namespace lifetime {
|
||||
static char **argv;
|
||||
static std::atomic_int desired_exit_code;
|
||||
|
||||
/**
|
||||
* @brief Terminates Sunshine gracefully with the provided exit code.
|
||||
* @param exit_code The exit code to return from main().
|
||||
* @param async Specifies whether our termination will be non-blocking.
|
||||
*/
|
||||
void
|
||||
exit_sunshine(int exit_code, bool async) {
|
||||
// Store the exit code of the first exit_sunshine() call
|
||||
int zero = 0;
|
||||
desired_exit_code.compare_exchange_strong(zero, exit_code);
|
||||
|
||||
// Raise SIGINT to start termination
|
||||
std::raise(SIGINT);
|
||||
|
||||
// Termination will happen asynchronously, but the caller may
|
||||
// have wanted synchronous behavior.
|
||||
while (!async) {
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the argv array passed to main().
|
||||
*/
|
||||
char **
|
||||
get_argv() {
|
||||
return argv;
|
||||
}
|
||||
} // namespace lifetime
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace service_ctrl {
|
||||
class service_controller {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for service_controller class.
|
||||
* @param service_desired_access SERVICE_* desired access flags.
|
||||
*/
|
||||
service_controller(DWORD service_desired_access) {
|
||||
scm_handle = OpenSCManagerA(nullptr, nullptr, SC_MANAGER_CONNECT);
|
||||
if (!scm_handle) {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(error) << "OpenSCManager() failed: "sv << winerr;
|
||||
return;
|
||||
}
|
||||
|
||||
service_handle = OpenServiceA(scm_handle, "SunshineService", service_desired_access);
|
||||
if (!service_handle) {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(error) << "OpenService() failed: "sv << winerr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
~service_controller() {
|
||||
if (service_handle) {
|
||||
CloseServiceHandle(service_handle);
|
||||
}
|
||||
|
||||
if (scm_handle) {
|
||||
CloseServiceHandle(scm_handle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Asynchronously starts the Sunshine service.
|
||||
*/
|
||||
bool
|
||||
start_service() {
|
||||
if (!service_handle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StartServiceA(service_handle, 0, nullptr)) {
|
||||
auto winerr = GetLastError();
|
||||
if (winerr != ERROR_SERVICE_ALREADY_RUNNING) {
|
||||
BOOST_LOG(error) << "StartService() failed: "sv << winerr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Query the service status.
|
||||
* @param status The SERVICE_STATUS struct to populate.
|
||||
*/
|
||||
bool
|
||||
query_service_status(SERVICE_STATUS &status) {
|
||||
if (!service_handle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!QueryServiceStatus(service_handle, &status)) {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(error) << "QueryServiceStatus() failed: "sv << winerr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
SC_HANDLE scm_handle = NULL;
|
||||
SC_HANDLE service_handle = NULL;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Check if the service is running.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* is_service_running();
|
||||
* ```
|
||||
*/
|
||||
bool
|
||||
is_service_running() {
|
||||
service_controller sc { SERVICE_QUERY_STATUS };
|
||||
|
||||
SERVICE_STATUS status;
|
||||
if (!sc.query_service_status(status)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return status.dwCurrentState == SERVICE_RUNNING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start the service and wait for startup to complete.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* start_service();
|
||||
* ```
|
||||
*/
|
||||
bool
|
||||
start_service() {
|
||||
service_controller sc { SERVICE_QUERY_STATUS | SERVICE_START };
|
||||
|
||||
std::cout << "Starting Sunshine..."sv;
|
||||
|
||||
// This operation is asynchronous, so we must wait for it to complete
|
||||
if (!sc.start_service()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SERVICE_STATUS status;
|
||||
do {
|
||||
Sleep(1000);
|
||||
std::cout << '.';
|
||||
} while (sc.query_service_status(status) && status.dwCurrentState == SERVICE_START_PENDING);
|
||||
|
||||
if (status.dwCurrentState != SERVICE_RUNNING) {
|
||||
BOOST_LOG(error) << SERVICE_NAME " failed to start: "sv << status.dwWin32ExitCode;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wait for the UI to be ready after Sunshine startup.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* wait_for_ui_ready();
|
||||
* ```
|
||||
*/
|
||||
bool
|
||||
wait_for_ui_ready() {
|
||||
std::cout << "Waiting for Web UI to be ready...";
|
||||
|
||||
// Wait up to 30 seconds for the web UI to start
|
||||
for (int i = 0; i < 30; i++) {
|
||||
PMIB_TCPTABLE tcp_table = nullptr;
|
||||
ULONG table_size = 0;
|
||||
ULONG err;
|
||||
|
||||
auto fg = util::fail_guard([&tcp_table]() {
|
||||
free(tcp_table);
|
||||
});
|
||||
|
||||
do {
|
||||
// Query all open TCP sockets to look for our web UI port
|
||||
err = GetTcpTable(tcp_table, &table_size, false);
|
||||
if (err == ERROR_INSUFFICIENT_BUFFER) {
|
||||
free(tcp_table);
|
||||
tcp_table = (PMIB_TCPTABLE) malloc(table_size);
|
||||
}
|
||||
} while (err == ERROR_INSUFFICIENT_BUFFER);
|
||||
|
||||
if (err != NO_ERROR) {
|
||||
BOOST_LOG(error) << "Failed to query TCP table: "sv << err;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t port_nbo = htons(map_port(confighttp::PORT_HTTPS));
|
||||
for (DWORD i = 0; i < tcp_table->dwNumEntries; i++) {
|
||||
auto &entry = tcp_table->table[i];
|
||||
|
||||
// Look for our port in the listening state
|
||||
if (entry.dwLocalPort == port_nbo && entry.dwState == MIB_TCP_STATE_LISTEN) {
|
||||
std::cout << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Sleep(1000);
|
||||
std::cout << '.';
|
||||
}
|
||||
|
||||
std::cout << "timed out"sv << std::endl;
|
||||
return false;
|
||||
}
|
||||
} // namespace service_ctrl
|
||||
|
||||
/**
|
||||
* @brief Checks if NVIDIA's GameStream software is running.
|
||||
* @return `true` if GameStream is enabled.
|
||||
*/
|
||||
bool
|
||||
is_gamestream_enabled() {
|
||||
DWORD enabled;
|
||||
DWORD size = sizeof(enabled);
|
||||
return RegGetValueW(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
L"SOFTWARE\\NVIDIA Corporation\\NvStream",
|
||||
L"EnableStreaming",
|
||||
RRF_RT_REG_DWORD,
|
||||
nullptr,
|
||||
&enabled,
|
||||
&size) == ERROR_SUCCESS &&
|
||||
enabled != 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Launch the Web UI.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* launch_ui();
|
||||
* ```
|
||||
*/
|
||||
void
|
||||
launch_ui() {
|
||||
std::string url = "https://localhost:" + std::to_string(map_port(confighttp::PORT_HTTPS));
|
||||
platf::open_url(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Flush the log.
|
||||
*
|
||||
@@ -159,13 +421,9 @@ LRESULT CALLBACK
|
||||
SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
switch (uMsg) {
|
||||
case WM_ENDSESSION: {
|
||||
// Raise a SIGINT to trigger our cleanup logic and terminate ourselves
|
||||
// Terminate ourselves with a blocking exit call
|
||||
std::cout << "Received WM_ENDSESSION"sv << std::endl;
|
||||
std::raise(SIGINT);
|
||||
|
||||
// The signal handling is asynchronous, so we will wait here to be terminated.
|
||||
// If for some reason we don't terminate in a few seconds, Windows will kill us.
|
||||
SuspendThread(GetCurrentThread());
|
||||
lifetime::exit_sunshine(0, false);
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
@@ -186,6 +444,8 @@ SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
*/
|
||||
int
|
||||
main(int argc, char *argv[]) {
|
||||
lifetime::argv = argv;
|
||||
|
||||
task_pool_util::TaskPool::task_id_t force_shutdown = nullptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -354,14 +614,26 @@ main(int argc, char *argv[]) {
|
||||
BOOST_LOG(error) << "Platform failed to initialize"sv;
|
||||
}
|
||||
|
||||
auto proc_deinit_guard = proc::init();
|
||||
if (!proc_deinit_guard) {
|
||||
BOOST_LOG(error) << "Proc failed to initialize"sv;
|
||||
}
|
||||
|
||||
reed_solomon_init();
|
||||
auto input_deinit_guard = input::init();
|
||||
if (video::init()) {
|
||||
BOOST_LOG(error) << "Video failed to initialize"sv;
|
||||
if (video::probe_encoders()) {
|
||||
BOOST_LOG(error) << "Video failed to find working encoder"sv;
|
||||
}
|
||||
|
||||
if (http::init()) {
|
||||
BOOST_LOG(error) << "http failed to initialize"sv;
|
||||
BOOST_LOG(fatal) << "HTTP interface failed to initialize"sv;
|
||||
|
||||
#ifdef _WIN32
|
||||
BOOST_LOG(fatal) << "To relaunch Sunshine successfully, use the shortcut in the Start Menu. Do not run Sunshine.exe manually."sv;
|
||||
std::this_thread::sleep_for(10s);
|
||||
#endif
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::unique_ptr<platf::deinit_t> mDNS;
|
||||
@@ -376,12 +648,20 @@ main(int argc, char *argv[]) {
|
||||
|
||||
// FIXME: Temporary workaround: Simple-Web_server needs to be updated or replaced
|
||||
if (shutdown_event->peek()) {
|
||||
return 0;
|
||||
return lifetime::desired_exit_code;
|
||||
}
|
||||
|
||||
std::thread httpThread { nvhttp::start };
|
||||
std::thread configThread { confighttp::start };
|
||||
|
||||
#ifdef _WIN32
|
||||
// If we're using the default port and GameStream is enabled, warn the user
|
||||
if (config::sunshine.port == 47989 && is_gamestream_enabled()) {
|
||||
BOOST_LOG(fatal) << "GameStream is still enabled in GeForce Experience! This *will* cause streaming problems with Sunshine!"sv;
|
||||
BOOST_LOG(fatal) << "Disable GameStream on the SHIELD tab in GeForce Experience or change the Port setting on the Advanced tab in the Sunshine Web UI."sv;
|
||||
}
|
||||
#endif
|
||||
|
||||
rtsp_stream::rtpThread();
|
||||
|
||||
httpThread.join();
|
||||
@@ -395,7 +675,7 @@ main(int argc, char *argv[]) {
|
||||
system_tray::end_tray();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
return lifetime::desired_exit_code;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
43
src/main.h
43
src/main.h
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* @file main.h
|
||||
* @file src/main.h
|
||||
* @brief Main header file for the Sunshine application.
|
||||
*/
|
||||
|
||||
// macros
|
||||
#ifndef SUNSHINE_MAIN_H
|
||||
#define SUNSHINE_MAIN_H
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <filesystem>
|
||||
@@ -33,20 +33,6 @@ main(int argc, char *argv[]);
|
||||
void
|
||||
log_flush();
|
||||
void
|
||||
open_url(const std::string &url);
|
||||
void
|
||||
tray_open_ui_cb(struct tray_menu *item);
|
||||
void
|
||||
tray_donate_github_cb(struct tray_menu *item);
|
||||
void
|
||||
tray_donate_mee6_cb(struct tray_menu *item);
|
||||
void
|
||||
tray_donate_patreon_cb(struct tray_menu *item);
|
||||
void
|
||||
tray_donate_paypal_cb(struct tray_menu *item);
|
||||
void
|
||||
tray_quit_cb(struct tray_menu *item);
|
||||
void
|
||||
print_help(const char *name);
|
||||
std::string
|
||||
read_file(const char *path);
|
||||
@@ -54,6 +40,8 @@ int
|
||||
write_file(const char *path, const std::string_view &contents);
|
||||
std::uint16_t
|
||||
map_port(int port);
|
||||
void
|
||||
launch_ui();
|
||||
|
||||
// namespaces
|
||||
namespace mail {
|
||||
@@ -79,4 +67,23 @@ namespace mail {
|
||||
#undef MAIL
|
||||
|
||||
} // namespace mail
|
||||
#endif // SUNSHINE_MAIN_H
|
||||
|
||||
namespace lifetime {
|
||||
void
|
||||
exit_sunshine(int exit_code, bool async);
|
||||
char **
|
||||
get_argv();
|
||||
} // namespace lifetime
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace service_ctrl {
|
||||
bool
|
||||
is_service_running();
|
||||
|
||||
bool
|
||||
start_service();
|
||||
|
||||
bool
|
||||
wait_for_ui_ready();
|
||||
} // namespace service_ctrl
|
||||
#endif
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
#ifndef DOSSIER_MOVE_BY_COPY_H
|
||||
#define DOSSIER_MOVE_BY_COPY_H
|
||||
/**
|
||||
* @file src/move_by_copy.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
namespace move_by_copy_util {
|
||||
/*
|
||||
* When a copy is made, it moves the object
|
||||
* This allows you to move an object when a move can't be done.
|
||||
*/
|
||||
/**
|
||||
* When a copy is made, it moves the object
|
||||
* This allows you to move an object when a move can't be done.
|
||||
*/
|
||||
template <class T>
|
||||
class MoveByCopy {
|
||||
public:
|
||||
@@ -53,4 +56,3 @@ namespace move_by_copy_util {
|
||||
return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
|
||||
}
|
||||
} // namespace move_by_copy_util
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Created by loki on 12/27/19.
|
||||
|
||||
/**
|
||||
* @file src/network.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include "network.h"
|
||||
#include "utility.h"
|
||||
#include <algorithm>
|
||||
@@ -16,7 +18,8 @@ namespace net {
|
||||
std::vector<std::tuple<std::uint32_t, std::uint32_t>> lan_ips {
|
||||
ip_block("192.168.0.0/16"sv),
|
||||
ip_block("172.16.0.0/12"sv),
|
||||
ip_block("10.0.0.0/8"sv)
|
||||
ip_block("10.0.0.0/8"sv),
|
||||
ip_block("100.64.0.0/10"sv)
|
||||
};
|
||||
|
||||
std::uint32_t
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Created by loki on 12/27/19.
|
||||
|
||||
#ifndef SUNSHINE_NETWORK_H
|
||||
#define SUNSHINE_NETWORK_H
|
||||
/**
|
||||
* @file src/network.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
|
||||
@@ -34,5 +35,3 @@ namespace net {
|
||||
host_t
|
||||
host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port);
|
||||
} // namespace net
|
||||
|
||||
#endif // SUNSHINE_NETWORK_H
|
||||
|
||||
145
src/nvhttp.cpp
145
src/nvhttp.cpp
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* @file nvhttp.h
|
||||
*/
|
||||
* @file src/nvhttp.h
|
||||
* @brief todo
|
||||
*/
|
||||
|
||||
// macros
|
||||
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
|
||||
@@ -29,6 +30,7 @@
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
#include "uuid.h"
|
||||
#include "video.h"
|
||||
|
||||
using namespace std::literals;
|
||||
namespace nvhttp {
|
||||
@@ -279,6 +281,7 @@ namespace nvhttp {
|
||||
if (sess.async_insert_pin.salt.size() < 32) {
|
||||
tree.put("root.paired", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 400);
|
||||
tree.put("root.<xmlattr>.status_message", "Salt too short");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -374,14 +377,7 @@ namespace nvhttp {
|
||||
auto hash = crypto::hash(data);
|
||||
|
||||
// if hash not correct, probably MITM
|
||||
if (std::memcmp(hash.data(), sess.clienthash.data(), hash.size())) {
|
||||
// TODO: log
|
||||
|
||||
map_id_sess.erase(client.uniqueID);
|
||||
tree.put("root.paired", 0);
|
||||
}
|
||||
|
||||
if (crypto::verify256(crypto::x509(client.cert), secret, sign)) {
|
||||
if (!std::memcmp(hash.data(), sess.clienthash.data(), hash.size()) && crypto::verify256(crypto::x509(client.cert), secret, sign)) {
|
||||
tree.put("root.paired", 1);
|
||||
add_cert->raise(crypto::x509(client.cert));
|
||||
|
||||
@@ -470,11 +466,12 @@ namespace nvhttp {
|
||||
auto args = request->parse_query_string();
|
||||
if (args.find("uniqueid"s) == std::end(args)) {
|
||||
tree.put("root.<xmlattr>.status_code", 400);
|
||||
tree.put("root.<xmlattr>.status_message", "Missing uniqueid parameter");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto uniqID { std::move(get_arg(args, "uniqueid")) };
|
||||
auto uniqID { get_arg(args, "uniqueid") };
|
||||
auto sess_it = map_id_sess.find(uniqID);
|
||||
|
||||
args_t::const_iterator it;
|
||||
@@ -521,19 +518,20 @@ namespace nvhttp {
|
||||
}
|
||||
else {
|
||||
tree.put("root.<xmlattr>.status_code", 404);
|
||||
tree.put("root.<xmlattr>.status_message", "Invalid pairing request");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compare the user supplied pin to the Moonlight pin.
|
||||
* @param pin The user supplied pin.
|
||||
* @return `true` if the pin is correct, `false` otherwise.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* bool pin_status = nvhttp::pin("1234");
|
||||
* ```
|
||||
*/
|
||||
* @brief Compare the user supplied pin to the Moonlight pin.
|
||||
* @param pin The user supplied pin.
|
||||
* @return `true` if the pin is correct, `false` otherwise.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* bool pin_status = nvhttp::pin("1234");
|
||||
* ```
|
||||
*/
|
||||
bool
|
||||
pin(std::string pin) {
|
||||
pt::ptree tree;
|
||||
@@ -621,23 +619,19 @@ namespace nvhttp {
|
||||
tree.put("root.HttpsPort", map_port(PORT_HTTPS));
|
||||
tree.put("root.ExternalPort", map_port(PORT_HTTP));
|
||||
tree.put("root.mac", platf::get_mac_address(local_endpoint.address().to_string()));
|
||||
tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 1 ? "1869449984" : "0");
|
||||
tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0");
|
||||
tree.put("root.LocalIP", local_endpoint.address().to_string());
|
||||
|
||||
if (config::video.hevc_mode == 3) {
|
||||
if (video::active_hevc_mode == 3) {
|
||||
tree.put("root.ServerCodecModeSupport", "3843");
|
||||
}
|
||||
else if (config::video.hevc_mode == 2) {
|
||||
else if (video::active_hevc_mode == 2) {
|
||||
tree.put("root.ServerCodecModeSupport", "259");
|
||||
}
|
||||
else {
|
||||
tree.put("root.ServerCodecModeSupport", "3");
|
||||
}
|
||||
|
||||
if (!config::nvhttp.external_ip.empty()) {
|
||||
tree.put("root.ExternalIP", config::nvhttp.external_ip);
|
||||
}
|
||||
|
||||
pt::ptree display_nodes;
|
||||
for (auto &resolution : config::nvhttp.resolutions) {
|
||||
auto pred = [](auto ch) { return ch == ' ' || ch == '\t' || ch == 'x'; };
|
||||
@@ -689,22 +683,6 @@ namespace nvhttp {
|
||||
response->close_connection_after_response = true;
|
||||
});
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
if (args.find("uniqueid"s) == std::end(args)) {
|
||||
tree.put("root.<xmlattr>.status_code", 400);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto clientID = get_arg(args, "uniqueid");
|
||||
|
||||
auto client = map_id_client.find(clientID);
|
||||
if (client == std::end(map_id_client)) {
|
||||
tree.put("root.<xmlattr>.status_code", 501);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto &apps = tree.add_child("root", pt::ptree {});
|
||||
|
||||
apps.put("<xmlattr>.status_code", 200);
|
||||
@@ -712,7 +690,7 @@ namespace nvhttp {
|
||||
for (auto &proc : proc::proc.get_apps()) {
|
||||
pt::ptree app;
|
||||
|
||||
app.put("IsHdrSupported"s, config::video.hevc_mode == 3 ? 1 : 0);
|
||||
app.put("IsHdrSupported"s, video::active_hevc_mode == 3 ? 1 : 0);
|
||||
app.put("AppTitle"s, proc.name);
|
||||
app.put("ID", proc.id);
|
||||
|
||||
@@ -736,6 +714,7 @@ namespace nvhttp {
|
||||
if (rtsp_stream::session_count() == config::stream.channels) {
|
||||
tree.put("root.resume", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 503);
|
||||
tree.put("root.<xmlattr>.status_message", "The host's concurrent stream limit has been reached. Stop an existing stream or increase the 'Channels' value in the Sunshine Web UI.");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -748,6 +727,7 @@ namespace nvhttp {
|
||||
args.find("appid"s) == std::end(args)) {
|
||||
tree.put("root.resume", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 400);
|
||||
tree.put("root.<xmlattr>.status_message", "Missing a required launch parameter");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -758,14 +738,30 @@ namespace nvhttp {
|
||||
if (current_appid > 0) {
|
||||
tree.put("root.resume", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 400);
|
||||
tree.put("root.<xmlattr>.status_message", "An app is already running on this host");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Probe encoders again before streaming to ensure our chosen
|
||||
// encoder matches the active GPU (which could have changed
|
||||
// due to hotplugging, driver crash, primary monitor change,
|
||||
// or any number of other factors).
|
||||
if (rtsp_stream::session_count() == 0) {
|
||||
if (video::probe_encoders()) {
|
||||
tree.put("root.<xmlattr>.status_code", 503);
|
||||
tree.put("root.<xmlattr>.status_message", "Failed to initialize video capture/encoding. Is a display connected and turned on?");
|
||||
tree.put("root.gamesession", 0);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (appid > 0) {
|
||||
auto err = proc::proc.execute(appid);
|
||||
if (err) {
|
||||
tree.put("root.<xmlattr>.status_code", err);
|
||||
tree.put("root.<xmlattr>.status_message", "Failed to start the specified application");
|
||||
tree.put("root.gamesession", 0);
|
||||
|
||||
return;
|
||||
@@ -798,6 +794,7 @@ namespace nvhttp {
|
||||
if (rtsp_stream::session_count() == config::stream.channels) {
|
||||
tree.put("root.resume", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 503);
|
||||
tree.put("root.<xmlattr>.status_message", "The host's concurrent stream limit has been reached. Stop an existing stream or increase the 'Channels' value in the Sunshine Web UI.");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -806,6 +803,7 @@ namespace nvhttp {
|
||||
if (current_appid == 0) {
|
||||
tree.put("root.resume", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 503);
|
||||
tree.put("root.<xmlattr>.status_message", "No running app to resume");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -816,10 +814,32 @@ namespace nvhttp {
|
||||
args.find("rikeyid"s) == std::end(args)) {
|
||||
tree.put("root.resume", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 400);
|
||||
tree.put("root.<xmlattr>.status_message", "Missing a required resume parameter");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (rtsp_stream::session_count() == 0) {
|
||||
// Probe encoders again before streaming to ensure our chosen
|
||||
// encoder matches the active GPU (which could have changed
|
||||
// due to hotplugging, driver crash, primary monitor change,
|
||||
// or any number of other factors).
|
||||
if (video::probe_encoders()) {
|
||||
tree.put("root.resume", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 503);
|
||||
tree.put("root.<xmlattr>.status_message", "Failed to initialize video capture/encoding. Is a display connected and turned on?");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Newer Moonlight clients send localAudioPlayMode on /resume too,
|
||||
// so we should use it if it's present in the args and there are
|
||||
// no active sessions we could be interfering with.
|
||||
if (args.find("localAudioPlayMode"s) != std::end(args)) {
|
||||
host_audio = util::from_view(get_arg(args, "localAudioPlayMode"));
|
||||
}
|
||||
}
|
||||
|
||||
rtsp_stream::launch_session_raise(make_launch_session(host_audio, args));
|
||||
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
@@ -845,6 +865,7 @@ namespace nvhttp {
|
||||
if (rtsp_stream::session_count() != 0) {
|
||||
tree.put("root.resume", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 503);
|
||||
tree.put("root.<xmlattr>.status_message", "All sessions must be disconnected before quitting");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -872,13 +893,13 @@ namespace nvhttp {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start the nvhttp server.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* nvhttp::start();
|
||||
* ```
|
||||
*/
|
||||
* @brief Start the nvhttp server.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* nvhttp::start();
|
||||
* ```
|
||||
*/
|
||||
void
|
||||
start() {
|
||||
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
||||
@@ -904,7 +925,7 @@ namespace nvhttp {
|
||||
|
||||
auto add_cert = std::make_shared<safe::queue_t<crypto::x509_t>>(30);
|
||||
|
||||
// /resume doesn't get the parameter "localAudioPlayMode"
|
||||
// /resume doesn't always get the parameter "localAudioPlayMode"
|
||||
// /launch will store it in host_audio
|
||||
bool host_audio {};
|
||||
|
||||
@@ -913,7 +934,7 @@ namespace nvhttp {
|
||||
|
||||
// Verify certificates after establishing connection
|
||||
https_server.verify = [&cert_chain, add_cert](SSL *ssl) {
|
||||
auto x509 = SSL_get_peer_certificate(ssl);
|
||||
crypto::x509_t x509 { SSL_get_peer_certificate(ssl) };
|
||||
if (!x509) {
|
||||
BOOST_LOG(info) << "unknown -- denied"sv;
|
||||
return 0;
|
||||
@@ -924,7 +945,7 @@ namespace nvhttp {
|
||||
auto fg = util::fail_guard([&]() {
|
||||
char subject_name[256];
|
||||
|
||||
X509_NAME_oneline(X509_get_subject_name(x509), subject_name, sizeof(subject_name));
|
||||
X509_NAME_oneline(X509_get_subject_name(x509.get()), subject_name, sizeof(subject_name));
|
||||
|
||||
BOOST_LOG(debug) << subject_name << " -- "sv << (verified ? "verified"sv : "denied"sv);
|
||||
});
|
||||
@@ -939,7 +960,7 @@ namespace nvhttp {
|
||||
cert_chain.add(std::move(cert));
|
||||
}
|
||||
|
||||
auto err_str = cert_chain.verify(x509);
|
||||
auto err_str = cert_chain.verify(x509.get());
|
||||
if (err_str) {
|
||||
BOOST_LOG(warning) << "SSL Verification error :: "sv << err_str;
|
||||
|
||||
@@ -1018,13 +1039,13 @@ namespace nvhttp {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove all paired clients.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* nvhttp::erase_all_clients();
|
||||
* ```
|
||||
*/
|
||||
* @brief Remove all paired clients.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* nvhttp::erase_all_clients();
|
||||
* ```
|
||||
*/
|
||||
void
|
||||
erase_all_clients() {
|
||||
map_id_client.clear();
|
||||
|
||||
29
src/nvhttp.h
29
src/nvhttp.h
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* @file nvhttp.h
|
||||
*/
|
||||
* @file src/nvhttp.h
|
||||
* @brief todo
|
||||
*/
|
||||
|
||||
// macros
|
||||
#ifndef SUNSHINE_NVHTTP_H
|
||||
#define SUNSHINE_NVHTTP_H
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <string>
|
||||
@@ -18,24 +18,25 @@
|
||||
namespace nvhttp {
|
||||
|
||||
/**
|
||||
* @brief The protocol version.
|
||||
*/
|
||||
* @brief The protocol version.
|
||||
* @details The version of the GameStream protocol we are mocking.
|
||||
* @note The negative 4th number indicates to Moonlight that this is Sunshine.
|
||||
*/
|
||||
constexpr auto VERSION = "7.1.431.-1";
|
||||
// The negative 4th version number tells Moonlight that this is Sunshine
|
||||
|
||||
/**
|
||||
* @brief The GFE version we are replicating.
|
||||
*/
|
||||
* @brief The GFE version we are replicating.
|
||||
*/
|
||||
constexpr auto GFE_VERSION = "3.23.0.74";
|
||||
|
||||
/**
|
||||
* @brief The HTTP port, as a difference from the config port.
|
||||
*/
|
||||
* @brief The HTTP port, as a difference from the config port.
|
||||
*/
|
||||
constexpr auto PORT_HTTP = 0;
|
||||
|
||||
/**
|
||||
* @brief The HTTPS port, as a difference from the config port.
|
||||
*/
|
||||
* @brief The HTTPS port, as a difference from the config port.
|
||||
*/
|
||||
constexpr auto PORT_HTTPS = -5;
|
||||
|
||||
// functions
|
||||
@@ -46,5 +47,3 @@ namespace nvhttp {
|
||||
void
|
||||
erase_all_clients();
|
||||
} // namespace nvhttp
|
||||
|
||||
#endif // SUNSHINE_NVHTTP_H
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
//
|
||||
// Created by loki on 6/21/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_COMMON_H
|
||||
#define SUNSHINE_COMMON_H
|
||||
/**
|
||||
* @file src/platform/common.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
#include <filesystem>
|
||||
@@ -169,7 +168,7 @@ namespace platf {
|
||||
virtual ~deinit_t() = default;
|
||||
};
|
||||
|
||||
struct img_t {
|
||||
struct img_t: std::enable_shared_from_this<img_t> {
|
||||
public:
|
||||
img_t() = default;
|
||||
|
||||
@@ -186,6 +185,8 @@ namespace platf {
|
||||
std::int32_t pixel_pitch {};
|
||||
std::int32_t row_pitch {};
|
||||
|
||||
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
|
||||
|
||||
virtual ~img_t() = default;
|
||||
};
|
||||
|
||||
@@ -213,8 +214,8 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* implementations must take ownership of 'frame'
|
||||
*/
|
||||
* implementations must take ownership of 'frame'
|
||||
*/
|
||||
virtual int
|
||||
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
|
||||
BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?";
|
||||
@@ -225,14 +226,14 @@ namespace platf {
|
||||
set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) {};
|
||||
|
||||
/**
|
||||
* Implementations may set parameters during initialization of the hwframes context
|
||||
*/
|
||||
* Implementations may set parameters during initialization of the hwframes context
|
||||
*/
|
||||
virtual void
|
||||
init_hwframes(AVHWFramesContext *frames) {};
|
||||
|
||||
/**
|
||||
* Implementations may make modifications required before context derivation
|
||||
*/
|
||||
* Implementations may make modifications required before context derivation
|
||||
*/
|
||||
virtual int
|
||||
prepare_to_derive_context(int hw_device_type) {
|
||||
return 0;
|
||||
@@ -245,39 +246,53 @@ namespace platf {
|
||||
ok,
|
||||
reinit,
|
||||
timeout,
|
||||
interrupted,
|
||||
error
|
||||
};
|
||||
|
||||
class display_t {
|
||||
public:
|
||||
/**
|
||||
* When display has a new image ready or a timeout occurs, this callback will be called with the image.
|
||||
* If a frame was captured, frame_captured will be true. If a timeout occurred, it will be false.
|
||||
*
|
||||
* On Break Request -->
|
||||
* Returns nullptr
|
||||
*
|
||||
* On Success -->
|
||||
* Returns the image object that should be filled next.
|
||||
* This may or may not be the image send with the callback
|
||||
*/
|
||||
using snapshot_cb_t = std::function<std::shared_ptr<img_t>(std::shared_ptr<img_t> &img, bool frame_captured)>;
|
||||
* When display has a new image ready or a timeout occurs, this callback will be called with the image.
|
||||
* If a frame was captured, frame_captured will be true. If a timeout occurred, it will be false.
|
||||
*
|
||||
* On Break Request -->
|
||||
* Returns false
|
||||
*
|
||||
* On Success -->
|
||||
* Returns true
|
||||
*/
|
||||
using push_captured_image_cb_t = std::function<bool(std::shared_ptr<img_t> &&img, bool frame_captured)>;
|
||||
|
||||
/**
|
||||
* Use to get free image from the pool. Calls must be synchronized.
|
||||
* Blocks until there is free image in the pool or capture is interrupted.
|
||||
*
|
||||
* Returns:
|
||||
* 'true' on success, img_out contains free image
|
||||
* 'false' when capture has been interrupted, img_out contains nullptr
|
||||
*/
|
||||
using pull_free_image_cb_t = std::function<bool(std::shared_ptr<img_t> &img_out)>;
|
||||
|
||||
display_t() noexcept:
|
||||
offset_x { 0 }, offset_y { 0 } {}
|
||||
|
||||
/**
|
||||
* snapshot_cb --> the callback
|
||||
* std::shared_ptr<img_t> img --> The first image to use
|
||||
* bool *cursor --> A pointer to the flag that indicates wether the cursor should be captured as well
|
||||
*
|
||||
* Returns either:
|
||||
* capture_e::ok when stopping
|
||||
* capture_e::error on error
|
||||
* capture_e::reinit when need of reinitialization
|
||||
*/
|
||||
* push_captured_image_cb --> The callback that is called with captured image,
|
||||
* must be called from the same thread as capture()
|
||||
* pull_free_image_cb --> Capture backends call this callback to get empty image
|
||||
* from the pool. If backend uses multiple threads, calls to this
|
||||
* callback must be synchronized. Calls to this callback and
|
||||
* push_captured_image_cb must be synchronized as well.
|
||||
* bool *cursor --> A pointer to the flag that indicates wether the cursor should be captured as well
|
||||
*
|
||||
* Returns either:
|
||||
* capture_e::ok when stopping
|
||||
* capture_e::error on error
|
||||
* capture_e::reinit when need of reinitialization
|
||||
*/
|
||||
virtual capture_e
|
||||
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) = 0;
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) = 0;
|
||||
|
||||
virtual std::shared_ptr<img_t>
|
||||
alloc_img() = 0;
|
||||
@@ -352,14 +367,14 @@ namespace platf {
|
||||
audio_control();
|
||||
|
||||
/**
|
||||
* display_name --> The name of the monitor that SHOULD be displayed
|
||||
* If display_name is empty --> Use the first monitor that's compatible you can find
|
||||
* If you require to use this parameter in a seperate thread --> make a copy of it.
|
||||
*
|
||||
* config --> Stream configuration
|
||||
*
|
||||
* Returns display_t based on hwdevice_type
|
||||
*/
|
||||
* display_name --> The name of the monitor that SHOULD be displayed
|
||||
* If display_name is empty --> Use the first monitor that's compatible you can find
|
||||
* If you require to use this parameter in a seperate thread --> make a copy of it.
|
||||
*
|
||||
* config --> Stream configuration
|
||||
*
|
||||
* Returns display_t based on hwdevice_type
|
||||
*/
|
||||
std::shared_ptr<display_t>
|
||||
display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
|
||||
@@ -368,7 +383,7 @@ namespace platf {
|
||||
display_names(mem_type_e hwdevice_type);
|
||||
|
||||
boost::process::child
|
||||
run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, boost::process::environment &env, FILE *file, std::error_code &ec, boost::process::group *group);
|
||||
run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, boost::process::environment &env, FILE *file, std::error_code &ec, boost::process::group *group);
|
||||
|
||||
enum class thread_priority_e : int {
|
||||
low,
|
||||
@@ -385,9 +400,7 @@ namespace platf {
|
||||
void
|
||||
streaming_will_stop();
|
||||
|
||||
bool
|
||||
restart_supported();
|
||||
bool
|
||||
void
|
||||
restart();
|
||||
|
||||
struct batched_send_info_t {
|
||||
@@ -409,6 +422,13 @@ namespace platf {
|
||||
std::unique_ptr<deinit_t>
|
||||
enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type);
|
||||
|
||||
/**
|
||||
* @brief Open a url in the default web browser.
|
||||
* @param url The url to open.
|
||||
*/
|
||||
void
|
||||
open_url(const std::string &url);
|
||||
|
||||
input_t
|
||||
input();
|
||||
void
|
||||
@@ -422,7 +442,7 @@ namespace platf {
|
||||
void
|
||||
hscroll(input_t &input, int distance);
|
||||
void
|
||||
keyboard(input_t &input, uint16_t modcode, bool release);
|
||||
keyboard(input_t &input, uint16_t modcode, bool release, uint8_t flags);
|
||||
void
|
||||
gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state);
|
||||
void
|
||||
@@ -447,5 +467,3 @@ namespace platf {
|
||||
std::vector<std::string_view> &
|
||||
supported_gamepads();
|
||||
} // namespace platf
|
||||
|
||||
#endif //SUNSHINE_COMMON_H
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//
|
||||
// Created by loki on 5/16/21.
|
||||
//
|
||||
/**
|
||||
* @file src/platform/linux/audio.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include <bitset>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/platform/linux/cuda.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include <bitset>
|
||||
|
||||
#include <NvFBC.h>
|
||||
@@ -346,7 +350,7 @@ namespace cuda {
|
||||
|
||||
handle.handle_flags[SESSION_HANDLE] = true;
|
||||
|
||||
return std::move(handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
const char *
|
||||
@@ -504,9 +508,16 @@ namespace cuda {
|
||||
}
|
||||
|
||||
platf::capture_e
|
||||
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<platf::img_t> img, bool *cursor) override {
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
{
|
||||
// We must create at least one texture on this thread before calling NvFBCToCudaSetUp()
|
||||
// Otherwise it fails with "Unable to register an OpenGL buffer to a CUDA resource (result: 201)" message
|
||||
std::shared_ptr<platf::img_t> img_dummy;
|
||||
pull_free_image_cb(img_dummy);
|
||||
}
|
||||
|
||||
// Force display_t::capture to initialize handle_t::capture
|
||||
cursor_visible = !*cursor;
|
||||
|
||||
@@ -515,7 +526,7 @@ namespace cuda {
|
||||
handle.reset();
|
||||
});
|
||||
|
||||
while (img) {
|
||||
while (true) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (next_frame > now) {
|
||||
std::this_thread::sleep_for((next_frame - now) / 3 * 2);
|
||||
@@ -526,16 +537,22 @@ namespace cuda {
|
||||
}
|
||||
next_frame = now + delay;
|
||||
|
||||
auto status = snapshot(img.get(), 150ms, *cursor);
|
||||
std::shared_ptr<platf::img_t> img_out;
|
||||
auto status = snapshot(pull_free_image_cb, img_out, 150ms, *cursor);
|
||||
switch (status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
case platf::capture_e::interrupted:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
img = snapshot_cb(img, false);
|
||||
if (!push_captured_image_cb(std::move(img_out), false)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img, true);
|
||||
if (!push_captured_image_cb(std::move(img_out), true)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
|
||||
@@ -618,7 +635,7 @@ namespace cuda {
|
||||
}
|
||||
|
||||
platf::capture_e
|
||||
snapshot(platf::img_t *img, std::chrono::milliseconds timeout, bool cursor) {
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
if (cursor != cursor_visible) {
|
||||
auto status = reinit(cursor);
|
||||
if (status != platf::capture_e::ok) {
|
||||
@@ -646,7 +663,12 @@ namespace cuda {
|
||||
return platf::capture_e::error;
|
||||
}
|
||||
|
||||
if (((img_t *) img)->tex.copy((std::uint8_t *) device_ptr, img->height, img->row_pitch)) {
|
||||
if (!pull_free_image_cb(img_out)) {
|
||||
return platf::capture_e::interrupted;
|
||||
}
|
||||
auto img = (img_t *) img_out.get();
|
||||
|
||||
if (img->tex.copy((std::uint8_t *) device_ptr, img->height, img->row_pitch)) {
|
||||
return platf::capture_e::error;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
/**
|
||||
* @file src/platform/linux/cuda.cu
|
||||
* @brief todo
|
||||
*/
|
||||
// #include <algorithm>
|
||||
#include <helper_math.h>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -29,13 +34,15 @@ using namespace std::literals;
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
//////////////////// Special desclarations
|
||||
// Special declarations
|
||||
/**
|
||||
* NVCC segfaults when including <chrono>
|
||||
* Therefore, some declarations need to be added explicitely
|
||||
* NVCC tends to have problems with standard headers.
|
||||
* Don't include common.h, instead use bare minimum
|
||||
* of standard headers and duplicate declarations of necessary classes.
|
||||
* Not pretty and extremely error-prone, fix at earliest convenience.
|
||||
*/
|
||||
namespace platf {
|
||||
struct img_t {
|
||||
struct img_t: std::enable_shared_from_this<img_t> {
|
||||
public:
|
||||
std::uint8_t *data {};
|
||||
std::int32_t width {};
|
||||
@@ -43,6 +50,8 @@ public:
|
||||
std::int32_t pixel_pitch {};
|
||||
std::int32_t row_pitch {};
|
||||
|
||||
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
|
||||
|
||||
virtual ~img_t() = default;
|
||||
};
|
||||
} // namespace platf
|
||||
@@ -70,10 +79,10 @@ struct alignas(16) color_extern_t {
|
||||
|
||||
static_assert(sizeof(video::color_t) == sizeof(video::color_extern_t), "color matrix struct mismatch");
|
||||
|
||||
extern color_t colors[4];
|
||||
extern color_t colors[6];
|
||||
} // namespace video
|
||||
|
||||
//////////////////// End special declarations
|
||||
// End special declarations
|
||||
|
||||
namespace cuda {
|
||||
auto constexpr INVALID_TEXTURE = std::numeric_limits<cudaTextureObject_t>::max();
|
||||
@@ -225,7 +234,7 @@ std::optional<tex_t> tex_t::make(int height, int pitch) {
|
||||
|
||||
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.linear, &res, &desc, nullptr), "Couldn't create cuda texture that uses linear interpolation");
|
||||
|
||||
return std::move(tex);
|
||||
return tex;
|
||||
}
|
||||
|
||||
tex_t::tex_t() : array {}, texture { INVALID_TEXTURE } {}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
#if !defined(SUNSHINE_PLATFORM_CUDA_H) && defined(SUNSHINE_BUILD_CUDA)
|
||||
#define SUNSHINE_PLATFORM_CUDA_H
|
||||
/**
|
||||
* @file src/platform/linux/cuda.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#if defined(SUNSHINE_BUILD_CUDA)
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -88,11 +94,11 @@ namespace cuda {
|
||||
sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix);
|
||||
|
||||
/**
|
||||
* in_width, in_height -- The width and height of the captured image in pixels
|
||||
* out_width, out_height -- the width and height of the NV12 image in pixels
|
||||
*
|
||||
* pitch -- The size of a single row of pixels in bytes
|
||||
*/
|
||||
* in_width, in_height -- The width and height of the captured image in pixels
|
||||
* out_width, out_height -- the width and height of the NV12 image in pixels
|
||||
*
|
||||
* pitch -- The size of a single row of pixels in bytes
|
||||
*/
|
||||
static std::optional<sws_t>
|
||||
make(int in_width, int in_height, int out_width, int out_height, int pitch);
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/platform/linux/graphics.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include "graphics.h"
|
||||
#include "src/video.h"
|
||||
|
||||
@@ -765,7 +769,7 @@ namespace egl {
|
||||
|
||||
gl_drain_errors;
|
||||
|
||||
return std::move(sws);
|
||||
return sws;
|
||||
}
|
||||
|
||||
int
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#ifndef SUNSHINE_PLATFORM_LINUX_OPENGL_H
|
||||
#define SUNSHINE_PLATFORM_LINUX_OPENGL_H
|
||||
/**
|
||||
* @file src/platform/linux/graphics.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
@@ -94,8 +97,8 @@ namespace gl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a part of the framebuffer to texture
|
||||
*/
|
||||
* Copies a part of the framebuffer to texture
|
||||
*/
|
||||
void
|
||||
copy(int id, int texture, int offset_x, int offset_y, int width, int height);
|
||||
};
|
||||
@@ -352,5 +355,3 @@ namespace egl {
|
||||
bool
|
||||
fail();
|
||||
} // namespace egl
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/platform/linux/input.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include <fcntl.h>
|
||||
#include <linux/uinput.h>
|
||||
#include <poll.h>
|
||||
@@ -143,9 +147,9 @@ namespace platf {
|
||||
constexpr auto UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* @brief Initializes the keycode constants for translating
|
||||
* moonlight keycodes to linux/X11 keycodes
|
||||
*/
|
||||
* @brief Initializes the keycode constants for translating
|
||||
* moonlight keycodes to linux/X11 keycodes.
|
||||
*/
|
||||
static constexpr std::array<keycode_t, 0xE3>
|
||||
init_keycodes() {
|
||||
std::array<keycode_t, 0xE3> keycodes {};
|
||||
@@ -1047,16 +1051,16 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief XTest absolute mouse move.
|
||||
* @param input The input_t instance to use.
|
||||
* @param x Absolute x position.
|
||||
* @param y Absolute y position.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* x_abs_mouse(input, 0, 0);
|
||||
* ```
|
||||
*/
|
||||
* @brief XTest absolute mouse move.
|
||||
* @param input The input_t instance to use.
|
||||
* @param x Absolute x position.
|
||||
* @param y Absolute y position.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* x_abs_mouse(input, 0, 0);
|
||||
* ```
|
||||
*/
|
||||
static void
|
||||
x_abs_mouse(input_t &input, float x, float y) {
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
@@ -1070,17 +1074,17 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Absolute mouse move.
|
||||
* @param input The input_t instance to use.
|
||||
* @param touch_port The touch_port instance to use.
|
||||
* @param x Absolute x position.
|
||||
* @param y Absolute y position.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* abs_mouse(input, touch_port, 0, 0);
|
||||
* ```
|
||||
*/
|
||||
* @brief Absolute mouse move.
|
||||
* @param input The input_t instance to use.
|
||||
* @param touch_port The touch_port instance to use.
|
||||
* @param x Absolute x position.
|
||||
* @param y Absolute y position.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* abs_mouse(input, touch_port, 0, 0);
|
||||
* ```
|
||||
*/
|
||||
void
|
||||
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
|
||||
auto touchscreen = ((input_raw_t *) input.get())->touch_input.get();
|
||||
@@ -1101,16 +1105,16 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief XTest relative mouse move.
|
||||
* @param input The input_t instance to use.
|
||||
* @param deltaX Relative x position.
|
||||
* @param deltaY Relative y position.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* x_move_mouse(input, 10, 10); // Move mouse 10 pixels down and right
|
||||
* ```
|
||||
*/
|
||||
* @brief XTest relative mouse move.
|
||||
* @param input The input_t instance to use.
|
||||
* @param deltaX Relative x position.
|
||||
* @param deltaY Relative y position.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* x_move_mouse(input, 10, 10); // Move mouse 10 pixels down and right
|
||||
* ```
|
||||
*/
|
||||
static void
|
||||
x_move_mouse(input_t &input, int deltaX, int deltaY) {
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
@@ -1124,16 +1128,16 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Relative mouse move.
|
||||
* @param input The input_t instance to use.
|
||||
* @param deltaX Relative x position.
|
||||
* @param deltaY Relative y position.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* move_mouse(input, 10, 10); // Move mouse 10 pixels down and right
|
||||
* ```
|
||||
*/
|
||||
* @brief Relative mouse move.
|
||||
* @param input The input_t instance to use.
|
||||
* @param deltaX Relative x position.
|
||||
* @param deltaY Relative y position.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* move_mouse(input, 10, 10); // Move mouse 10 pixels down and right
|
||||
* ```
|
||||
*/
|
||||
void
|
||||
move_mouse(input_t &input, int deltaX, int deltaY) {
|
||||
auto mouse = ((input_raw_t *) input.get())->mouse_input.get();
|
||||
@@ -1154,16 +1158,16 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief XTest mouse button press/release.
|
||||
* @param input The input_t instance to use.
|
||||
* @param button Which mouse button to emulate.
|
||||
* @param release Whether the event was a press (false) or a release (true)
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* x_button_mouse(input, 1, false); // Press left mouse button
|
||||
* ```
|
||||
*/
|
||||
* @brief XTest mouse button press/release.
|
||||
* @param input The input_t instance to use.
|
||||
* @param button Which mouse button to emulate.
|
||||
* @param release Whether the event was a press (false) or a release (true)
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* x_button_mouse(input, 1, false); // Press left mouse button
|
||||
* ```
|
||||
*/
|
||||
static void
|
||||
x_button_mouse(input_t &input, int button, bool release) {
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
@@ -1197,16 +1201,16 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mouse button press/release.
|
||||
* @param input The input_t instance to use.
|
||||
* @param button Which mouse button to emulate.
|
||||
* @param release Whether the event was a press (false) or a release (true)
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* button_mouse(input, 1, false); // Press left mouse button
|
||||
* ```
|
||||
*/
|
||||
* @brief Mouse button press/release.
|
||||
* @param input The input_t instance to use.
|
||||
* @param button Which mouse button to emulate.
|
||||
* @param release Whether the event was a press (false) or a release (true)
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* button_mouse(input, 1, false); // Press left mouse button
|
||||
* ```
|
||||
*/
|
||||
void
|
||||
button_mouse(input_t &input, int button, bool release) {
|
||||
auto mouse = ((input_raw_t *) input.get())->mouse_input.get();
|
||||
@@ -1245,17 +1249,17 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief XTest mouse scroll.
|
||||
* @param input The input_t instance to use.
|
||||
* @param distance How far to scroll
|
||||
* @param button_pos Which mouse button to emulate for positive scroll.
|
||||
* @param button_neg Which mouse button to emulate for negative scroll.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* x_scroll(input, 10, 4, 5);
|
||||
* ```
|
||||
*/
|
||||
* @brief XTest mouse scroll.
|
||||
* @param input The input_t instance to use.
|
||||
* @param distance How far to scroll.
|
||||
* @param button_pos Which mouse button to emulate for positive scroll.
|
||||
* @param button_neg Which mouse button to emulate for negative scroll.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* x_scroll(input, 10, 4, 5);
|
||||
* ```
|
||||
*/
|
||||
static void
|
||||
x_scroll(input_t &input, int distance, int button_pos, int button_neg) {
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
@@ -1274,15 +1278,15 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Vertical mouse scroll.
|
||||
* @param input The input_t instance to use.
|
||||
* @param high_res_distance How far to scroll
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* scroll(input, 1200);
|
||||
* ```
|
||||
*/
|
||||
* @brief Vertical mouse scroll.
|
||||
* @param input The input_t instance to use.
|
||||
* @param high_res_distance How far to scroll.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* scroll(input, 1200);
|
||||
* ```
|
||||
*/
|
||||
void
|
||||
scroll(input_t &input, int high_res_distance) {
|
||||
int distance = high_res_distance / 120;
|
||||
@@ -1299,15 +1303,15 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Horizontal mouse scroll.
|
||||
* @param input The input_t instance to use.
|
||||
* @param high_res_distance How far to scroll
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* hscroll(input, 1200);
|
||||
* ```
|
||||
*/
|
||||
* @brief Horizontal mouse scroll.
|
||||
* @param input The input_t instance to use.
|
||||
* @param high_res_distance How far to scroll.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* hscroll(input, 1200);
|
||||
* ```
|
||||
*/
|
||||
void
|
||||
hscroll(input_t &input, int high_res_distance) {
|
||||
int distance = high_res_distance / 120;
|
||||
@@ -1333,18 +1337,19 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief XTest keyboard emulation.
|
||||
* @param input The input_t instance to use.
|
||||
* @param modcode The moonlight key code.
|
||||
* @param release Whether the event was a press (false) or a release (true)
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* x_keyboard(input, 0x5A, false); // Press Z
|
||||
* ```
|
||||
*/
|
||||
* @brief XTest keyboard emulation.
|
||||
* @param input The input_t instance to use.
|
||||
* @param modcode The moonlight key code.
|
||||
* @param release Whether the event was a press (false) or a release (true).
|
||||
* @param flags SS_KBE_FLAG_* values.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* x_keyboard(input, 0x5A, false, 0); // Press Z
|
||||
* ```
|
||||
*/
|
||||
static void
|
||||
x_keyboard(input_t &input, uint16_t modcode, bool release) {
|
||||
x_keyboard(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
auto keycode = keysym(modcode);
|
||||
if (keycode.keysym == UNKNOWN) {
|
||||
@@ -1367,21 +1372,22 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Keyboard emulation.
|
||||
* @param input The input_t instance to use.
|
||||
* @param modcode The moonlight key code.
|
||||
* @param release Whether the event was a press (false) or a release (true)
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* keyboard(input, 0x5A, false); // Press Z
|
||||
* ```
|
||||
*/
|
||||
* @brief Keyboard emulation.
|
||||
* @param input The input_t instance to use.
|
||||
* @param modcode The moonlight key code.
|
||||
* @param release Whether the event was a press (false) or a release (true).
|
||||
* @param flags SS_KBE_FLAG_* values.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* keyboard(input, 0x5A, false, 0); // Press Z
|
||||
* ```
|
||||
*/
|
||||
void
|
||||
keyboard(input_t &input, uint16_t modcode, bool release) {
|
||||
keyboard(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
|
||||
auto keyboard = ((input_raw_t *) input.get())->keyboard_input.get();
|
||||
if (!keyboard) {
|
||||
x_keyboard(input, modcode, release);
|
||||
x_keyboard(input, modcode, release, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1405,12 +1411,12 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an UTF-32 encoded string and returns a hex string representation of the bytes (uppercase)
|
||||
*
|
||||
* ex: ['👱'] = "1F471" // see UTF encoding at https://www.compart.com/en/unicode/U+1F471
|
||||
*
|
||||
* adapted from: https://stackoverflow.com/a/7639754
|
||||
*/
|
||||
* Takes an UTF-32 encoded string and returns a hex string representation of the bytes (uppercase)
|
||||
*
|
||||
* ex: ['👱'] = "1F471" // see UTF encoding at https://www.compart.com/en/unicode/U+1F471
|
||||
*
|
||||
* adapted from: https://stackoverflow.com/a/7639754
|
||||
*/
|
||||
std::string
|
||||
to_hex(const std::basic_string<char32_t> &str) {
|
||||
std::stringstream ss;
|
||||
@@ -1425,16 +1431,16 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* Here we receive a single UTF-8 encoded char at a time,
|
||||
* the trick is to convert it to UTF-32 then send CTRL+SHIFT+U+<HEXCODE> in order to produce any
|
||||
* unicode character, see: https://en.wikipedia.org/wiki/Unicode_input
|
||||
*
|
||||
* ex:
|
||||
* - when receiving UTF-8 [0xF0 0x9F 0x91 0xB1] (which is '👱')
|
||||
* - we'll convert it to UTF-32 [0x1F471]
|
||||
* - then type: CTRL+SHIFT+U+1F471
|
||||
* see the conversion at: https://www.compart.com/en/unicode/U+1F471
|
||||
*/
|
||||
* Here we receive a single UTF-8 encoded char at a time,
|
||||
* the trick is to convert it to UTF-32 then send CTRL+SHIFT+U+{HEXCODE} in order to produce any
|
||||
* unicode character, see: https://en.wikipedia.org/wiki/Unicode_input
|
||||
*
|
||||
* ex:
|
||||
* - when receiving UTF-8 [0xF0 0x9F 0x91 0xB1] (which is '👱')
|
||||
* - we'll convert it to UTF-32 [0x1F471]
|
||||
* - then type: CTRL+SHIFT+U+1F471
|
||||
* see the conversion at: https://www.compart.com/en/unicode/U+1F471
|
||||
*/
|
||||
void
|
||||
unicode(input_t &input, char *utf8, int size) {
|
||||
auto kb = ((input_raw_t *) input.get())->keyboard_input.get();
|
||||
@@ -1547,13 +1553,13 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initalize a new keyboard and return it.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* auto my_keyboard = keyboard();
|
||||
* ```
|
||||
*/
|
||||
* @brief Initialize a new keyboard and return it.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* auto my_keyboard = keyboard();
|
||||
* ```
|
||||
*/
|
||||
evdev_t
|
||||
keyboard() {
|
||||
evdev_t dev { libevdev_new() };
|
||||
@@ -1576,13 +1582,13 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initalize a new uinput virtual mouse and return it.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* auto my_mouse = mouse();
|
||||
* ```
|
||||
*/
|
||||
* @brief Initialize a new `uinput` virtual mouse and return it.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* auto my_mouse = mouse();
|
||||
* ```
|
||||
*/
|
||||
evdev_t
|
||||
mouse() {
|
||||
evdev_t dev { libevdev_new() };
|
||||
@@ -1627,13 +1633,13 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initalize a new uinput virtual touchscreen and return it.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* auto my_touchscreen = touchscreen();
|
||||
* ```
|
||||
*/
|
||||
* @brief Initialize a new `uinput` virtual touchscreen and return it.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* auto my_touchscreen = touchscreen();
|
||||
* ```
|
||||
*/
|
||||
evdev_t
|
||||
touchscreen() {
|
||||
evdev_t dev { libevdev_new() };
|
||||
@@ -1677,13 +1683,13 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initalize a new uinput virtual X360 gamepad and return it.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* auto my_x360 = x360();
|
||||
* ```
|
||||
*/
|
||||
* @brief Initialize a new `uinput` virtual X360 gamepad and return it.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* auto my_x360 = x360();
|
||||
* ```
|
||||
*/
|
||||
evdev_t
|
||||
x360() {
|
||||
evdev_t dev { libevdev_new() };
|
||||
@@ -1754,13 +1760,13 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initalize the input system and return it.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* auto my_input = input();
|
||||
* ```
|
||||
*/
|
||||
* @brief Initialize the input system and return it.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* auto my_input = input();
|
||||
* ```
|
||||
*/
|
||||
input_t
|
||||
input() {
|
||||
input_t result { new input_raw_t() };
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/platform/linux/kmsgrab.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include <drm_fourcc.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@@ -552,7 +556,7 @@ namespace platf {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//TODO: surf_sd = fb->to_sd();
|
||||
// TODO: surf_sd = fb->to_sd();
|
||||
|
||||
auto crct = card.crtc(plane->crtc_id);
|
||||
kms::print(plane.get(), fb.get(), crct.get());
|
||||
@@ -723,10 +727,10 @@ namespace platf {
|
||||
}
|
||||
|
||||
capture_e
|
||||
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override {
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
while (img) {
|
||||
while (true) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_frame > now) {
|
||||
@@ -738,16 +742,22 @@ namespace platf {
|
||||
}
|
||||
next_frame = now + delay;
|
||||
|
||||
auto status = snapshot(img.get(), 1000ms, *cursor);
|
||||
std::shared_ptr<platf::img_t> img_out;
|
||||
auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
|
||||
switch (status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
case platf::capture_e::interrupted:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
img = snapshot_cb(img, false);
|
||||
if (!push_captured_image_cb(std::move(img_out), false)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img, true);
|
||||
if (!push_captured_image_cb(std::move(img_out), true)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
|
||||
@@ -768,7 +778,7 @@ namespace platf {
|
||||
}
|
||||
|
||||
capture_e
|
||||
snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
file_t fb_fd[4];
|
||||
|
||||
egl::surface_descriptor_t sd;
|
||||
@@ -793,10 +803,14 @@ namespace platf {
|
||||
gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
|
||||
BOOST_LOG(debug) << "width and height: w "sv << w << " h "sv << h;
|
||||
|
||||
gl::ctx.GetTextureSubImage(rgb->tex[0], 0, img_offset_x, img_offset_y, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->height * img_out_base->row_pitch, img_out_base->data);
|
||||
if (!pull_free_image_cb(img_out)) {
|
||||
return platf::capture_e::interrupted;
|
||||
}
|
||||
|
||||
gl::ctx.GetTextureSubImage(rgb->tex[0], 0, img_offset_x, img_offset_y, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out->height * img_out->row_pitch, img_out->data);
|
||||
|
||||
if (cursor_opt && cursor) {
|
||||
cursor_opt->blend(*img_out_base, img_offset_x, img_offset_y);
|
||||
cursor_opt->blend(*img_out, img_offset_x, img_offset_y);
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
@@ -855,14 +869,23 @@ namespace platf {
|
||||
|
||||
int
|
||||
dummy_img(platf::img_t *img) override {
|
||||
return snapshot(img, 1s, false) != capture_e::ok;
|
||||
// TODO: stop cheating and give black image
|
||||
if (!img) {
|
||||
return -1;
|
||||
};
|
||||
auto pull_dummy_img_callback = [&img](std::shared_ptr<platf::img_t> &img_out) -> bool {
|
||||
img_out = img->shared_from_this();
|
||||
return true;
|
||||
};
|
||||
std::shared_ptr<platf::img_t> img_out;
|
||||
return snapshot(pull_dummy_img_callback, img_out, 1s, false) != platf::capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) {
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
while (img) {
|
||||
while (true) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_frame > now) {
|
||||
@@ -874,16 +897,22 @@ namespace platf {
|
||||
}
|
||||
next_frame = now + delay;
|
||||
|
||||
auto status = snapshot(img.get(), 1000ms, *cursor);
|
||||
std::shared_ptr<platf::img_t> img_out;
|
||||
auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
|
||||
switch (status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
case platf::capture_e::interrupted:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
img = snapshot_cb(img, false);
|
||||
if (!push_captured_image_cb(std::move(img_out), false)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img, true);
|
||||
if (!push_captured_image_cb(std::move(img_out), true)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
|
||||
@@ -895,10 +924,13 @@ namespace platf {
|
||||
}
|
||||
|
||||
capture_e
|
||||
snapshot(img_t *img_out_base, std::chrono::milliseconds /* timeout */, bool cursor) {
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds /* timeout */, bool cursor) {
|
||||
file_t fb_fd[4];
|
||||
|
||||
auto img = (egl::img_descriptor_t *) img_out_base;
|
||||
if (!pull_free_image_cb(img_out)) {
|
||||
return platf::capture_e::interrupted;
|
||||
}
|
||||
auto img = (egl::img_descriptor_t *) img_out.get();
|
||||
img->reset();
|
||||
|
||||
auto status = refresh(fb_fd, &img->sd);
|
||||
@@ -909,7 +941,7 @@ namespace platf {
|
||||
img->sequence = ++sequence;
|
||||
|
||||
if (!cursor || !cursor_opt) {
|
||||
img_out_base->data = nullptr;
|
||||
img->data = nullptr;
|
||||
|
||||
for (auto x = 0; x < 4; ++x) {
|
||||
fb_fd[x].release();
|
||||
@@ -971,15 +1003,15 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* On Wayland, it's not possible to determine the position of the monitor on the desktop with KMS.
|
||||
* Wayland does allow applications to query attached monitors on the desktop,
|
||||
* however, the naming scheme is not standardized across implementations.
|
||||
*
|
||||
* As a result, correlating the KMS output to the wayland outputs is guess work at best.
|
||||
* But, it's necessary for absolute mouse coordinates to work.
|
||||
*
|
||||
* This is an ugly hack :(
|
||||
*/
|
||||
* On Wayland, it's not possible to determine the position of the monitor on the desktop with KMS.
|
||||
* Wayland does allow applications to query attached monitors on the desktop,
|
||||
* however, the naming scheme is not standardized across implementations.
|
||||
*
|
||||
* As a result, correlating the KMS output to the wayland outputs is guess work at best.
|
||||
* But, it's necessary for absolute mouse coordinates to work.
|
||||
*
|
||||
* This is an ugly hack :(
|
||||
*/
|
||||
void
|
||||
correlate_to_wayland(std::vector<kms::card_descriptor_t> &cds) {
|
||||
auto monitors = wl::monitors();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file misc.cpp
|
||||
* @file src/misc.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
|
||||
// standard includes
|
||||
#include <fstream>
|
||||
|
||||
@@ -103,15 +103,14 @@ namespace platf {
|
||||
|
||||
std::string
|
||||
from_sockaddr(const sockaddr *const ip_addr) {
|
||||
char data[INET6_ADDRSTRLEN];
|
||||
char data[INET6_ADDRSTRLEN] = {};
|
||||
|
||||
auto family = ip_addr->sa_family;
|
||||
if (family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data,
|
||||
INET6_ADDRSTRLEN);
|
||||
}
|
||||
|
||||
if (family == AF_INET) {
|
||||
else if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data,
|
||||
INET_ADDRSTRLEN);
|
||||
}
|
||||
@@ -121,17 +120,16 @@ namespace platf {
|
||||
|
||||
std::pair<std::uint16_t, std::string>
|
||||
from_sockaddr_ex(const sockaddr *const ip_addr) {
|
||||
char data[INET6_ADDRSTRLEN];
|
||||
char data[INET6_ADDRSTRLEN] = {};
|
||||
|
||||
auto family = ip_addr->sa_family;
|
||||
std::uint16_t port;
|
||||
std::uint16_t port = 0;
|
||||
if (family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data,
|
||||
INET6_ADDRSTRLEN);
|
||||
port = ((sockaddr_in6 *) ip_addr)->sin6_port;
|
||||
}
|
||||
|
||||
if (family == AF_INET) {
|
||||
else if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data,
|
||||
INET_ADDRSTRLEN);
|
||||
port = ((sockaddr_in *) ip_addr)->sin_port;
|
||||
@@ -159,8 +157,7 @@ namespace platf {
|
||||
}
|
||||
|
||||
bp::child
|
||||
run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
|
||||
BOOST_LOG(warning) << "run_unprivileged() is not yet implemented for this platform. The new process will run with Sunshine's permissions."sv;
|
||||
run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
|
||||
if (!group) {
|
||||
if (!file) {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
|
||||
@@ -179,6 +176,28 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open a url in the default web browser.
|
||||
* @param url The url to open.
|
||||
*/
|
||||
void
|
||||
open_url(const std::string &url) {
|
||||
// set working dir to user home directory
|
||||
auto working_dir = boost::filesystem::path(std::getenv("HOME"));
|
||||
std::string cmd = R"(xdg-open ")" + url + R"(")";
|
||||
|
||||
boost::process::environment _env = boost::this_process::environment();
|
||||
std::error_code ec;
|
||||
auto child = run_command(false, false, cmd, working_dir, _env, nullptr, ec, nullptr);
|
||||
if (ec) {
|
||||
BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message();
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(info) << "Opened url ["sv << url << "]"sv;
|
||||
child.detach();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
adjust_thread_priority(thread_priority_e priority) {
|
||||
// Unimplemented
|
||||
@@ -194,16 +213,34 @@ namespace platf {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
bool
|
||||
restart_supported() {
|
||||
// Restart not supported yet
|
||||
return false;
|
||||
void
|
||||
restart_on_exit() {
|
||||
char executable[PATH_MAX];
|
||||
ssize_t len = readlink("/proc/self/exe", executable, PATH_MAX - 1);
|
||||
if (len == -1) {
|
||||
BOOST_LOG(fatal) << "readlink() failed: "sv << errno;
|
||||
return;
|
||||
}
|
||||
executable[len] = '\0';
|
||||
|
||||
// ASIO doesn't use O_CLOEXEC, so we have to close all fds ourselves
|
||||
int openmax = (int) sysconf(_SC_OPEN_MAX);
|
||||
for (int fd = STDERR_FILENO + 1; fd < openmax; fd++) {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// Re-exec ourselves with the same arguments
|
||||
if (execv(executable, lifetime::get_argv()) < 0) {
|
||||
BOOST_LOG(fatal) << "execv() failed: "sv << errno;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
void
|
||||
restart() {
|
||||
// Restart not supported yet
|
||||
return false;
|
||||
// Gracefully clean up and restart ourselves instead of exiting
|
||||
atexit(restart_on_exit);
|
||||
lifetime::exit_sunshine(0, true);
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#ifndef SUNSHINE_PLATFORM_MISC_H
|
||||
#define SUNSHINE_PLATFORM_MISC_H
|
||||
/**
|
||||
* @file src/platform/linux/misc.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
@@ -29,5 +32,3 @@ namespace dyn {
|
||||
handle(const std::vector<const char *> &libs);
|
||||
|
||||
} // namespace dyn
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,9 @@
|
||||
|
||||
// adapted from https://www.avahi.org/doxygen/html/client-publish-service_8c-example.html
|
||||
/**
|
||||
* @file src/platform/linux/publish.cpp
|
||||
* @brief todo
|
||||
* @note Adapted from https://www.avahi.org/doxygen/html/client-publish-service_8c-example.html
|
||||
* @todo Use a common file for this and src/platform/macos/publish.cpp
|
||||
*/
|
||||
#include <thread>
|
||||
|
||||
#include "misc.h"
|
||||
@@ -12,7 +16,9 @@ using namespace std::literals;
|
||||
|
||||
namespace avahi {
|
||||
|
||||
/** Error codes used by avahi */
|
||||
/**
|
||||
* @brief Error codes used by avahi.
|
||||
*/
|
||||
enum err_e {
|
||||
OK = 0, /**< OK */
|
||||
ERR_FAILURE = -1, /**< Generic error code */
|
||||
@@ -113,7 +119,9 @@ namespace avahi {
|
||||
CLIENT_NO_FAIL = 2 /**< Don't fail if the daemon is not available when avahi_client_new() is called, instead enter CLIENT_CONNECTING state and wait for the daemon to appear */
|
||||
};
|
||||
|
||||
/** Some flags for publishing functions */
|
||||
/**
|
||||
* @brief Flags for publishing functions.
|
||||
*/
|
||||
enum PublishFlags {
|
||||
PUBLISH_UNIQUE = 1, /**< For raw records: The RRset is intended to be unique */
|
||||
PUBLISH_NO_PROBE = 2, /**< For raw records: Though the RRset is intended to be unique no probes shall be sent */
|
||||
@@ -434,4 +442,4 @@ namespace platf::publish {
|
||||
|
||||
return std::make_unique<deinit_t>(std::thread { avahi::simple_poll_loop, poll.get() });
|
||||
}
|
||||
} // namespace platf::publish
|
||||
} // namespace platf::publish
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/platform/linux/vaapi.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
@@ -7,8 +11,7 @@ extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <va/va.h>
|
||||
#if !VA_CHECK_VERSION(1, 9, 0)
|
||||
/* vaSyncBuffer stub allows Sunshine built against libva <2.9.0
|
||||
to link against ffmpeg on libva 2.9.0 or later */
|
||||
// vaSyncBuffer stub allows Sunshine built against libva <2.9.0 to link against ffmpeg on libva 2.9.0 or later
|
||||
VAStatus
|
||||
vaSyncBuffer(
|
||||
VADisplay dpy,
|
||||
@@ -25,6 +28,7 @@ vaSyncBuffer(
|
||||
#include "src/main.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
#include "src/video.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
@@ -55,10 +59,7 @@ namespace va {
|
||||
// Needs to be closed manually
|
||||
int fd;
|
||||
|
||||
/*
|
||||
* Total size of this object (may include regions which are
|
||||
* not part of the surface).
|
||||
*/
|
||||
// Total size of this object (may include regions which are not part of the surface)
|
||||
uint32_t size;
|
||||
// Format modifier applied to this object, not sure what that means
|
||||
uint64_t drm_format_modifier;
|
||||
@@ -84,7 +85,9 @@ namespace va {
|
||||
} layers[4];
|
||||
};
|
||||
|
||||
/** Currently defined profiles */
|
||||
/**
|
||||
* @brief Defined profiles
|
||||
*/
|
||||
enum class profile_e {
|
||||
// Profile ID used for video processing.
|
||||
ProfileNone = -1,
|
||||
@@ -134,9 +137,9 @@ namespace va {
|
||||
IDCT = 3,
|
||||
MoComp = 4,
|
||||
Deblocking = 5,
|
||||
EncSlice = 6, /* slice level encode */
|
||||
EncPicture = 7, /* pictuer encode, JPEG, etc */
|
||||
/*
|
||||
EncSlice = 6, /** slice level encode */
|
||||
EncPicture = 7, /** picture encode, JPEG, etc */
|
||||
/**
|
||||
* For an implementation that supports a low power/high performance variant
|
||||
* for slice level encode, it can choose to expose the
|
||||
* VAEntrypointEncSliceLP entrypoint. Certain encoding tools may not be
|
||||
@@ -147,7 +150,7 @@ namespace va {
|
||||
EncSliceLP = 8,
|
||||
VideoProc = 10, /**< Video pre/post-processing. */
|
||||
/**
|
||||
* \brief FEI
|
||||
* @brief FEI
|
||||
*
|
||||
* The purpose of FEI (Flexible Encoding Infrastructure) is to allow applications to
|
||||
* have more controls and trade off quality for speed with their own IPs.
|
||||
@@ -161,10 +164,10 @@ namespace va {
|
||||
* and VAEncFEIDistortionBufferType) for FEI entry function.
|
||||
* If separate PAK is set, two extra input buffers
|
||||
* (VAEncFEIMVBufferType, VAEncFEIMBModeBufferType) are needed for PAK input.
|
||||
**/
|
||||
*/
|
||||
FEI = 11,
|
||||
/**
|
||||
* \brief Stats
|
||||
* @brief Stats
|
||||
*
|
||||
* A pre-processing function for getting some statistics and motion vectors is added,
|
||||
* and some extra controls for Encode pipeline are provided. The application can
|
||||
@@ -178,19 +181,19 @@ namespace va {
|
||||
* (VAStatsStatisticsParameterBufferType) and one or two output buffers
|
||||
* (VAStatsStatisticsBufferType, VAStatsStatisticsBottomFieldBufferType (for interlace only)
|
||||
* and VAStatsMVBufferType) are needed for this entry point.
|
||||
**/
|
||||
*/
|
||||
Stats = 12,
|
||||
/**
|
||||
* \brief ProtectedTEEComm
|
||||
* @brief ProtectedTEEComm
|
||||
*
|
||||
* A function for communicating with TEE (Trusted Execution Environment).
|
||||
**/
|
||||
*/
|
||||
ProtectedTEEComm = 13,
|
||||
/**
|
||||
* \brief ProtectedContent
|
||||
* @brief ProtectedContent
|
||||
*
|
||||
* A function for protected content to decrypt encrypted content.
|
||||
**/
|
||||
*/
|
||||
ProtectedContent = 14,
|
||||
};
|
||||
|
||||
@@ -474,11 +477,11 @@ namespace va {
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a private structure of FFmpeg, I need this to manually create
|
||||
* a VAAPI hardware context
|
||||
*
|
||||
* xdisplay will not be used internally by FFmpeg
|
||||
*/
|
||||
* This is a private structure of FFmpeg, I need this to manually create
|
||||
* a VAAPI hardware context
|
||||
*
|
||||
* xdisplay will not be used internally by FFmpeg
|
||||
*/
|
||||
typedef struct VAAPIDevicePriv {
|
||||
union {
|
||||
void *xdisplay;
|
||||
@@ -488,22 +491,22 @@ namespace va {
|
||||
} VAAPIDevicePriv;
|
||||
|
||||
/**
|
||||
* VAAPI connection details.
|
||||
*
|
||||
* Allocated as AVHWDeviceContext.hwctx
|
||||
*/
|
||||
* VAAPI connection details.
|
||||
*
|
||||
* Allocated as AVHWDeviceContext.hwctx
|
||||
*/
|
||||
typedef struct AVVAAPIDeviceContext {
|
||||
/**
|
||||
* The VADisplay handle, to be filled by the user.
|
||||
*/
|
||||
* The VADisplay handle, to be filled by the user.
|
||||
*/
|
||||
va::VADisplay display;
|
||||
/**
|
||||
* Driver quirks to apply - this is filled by av_hwdevice_ctx_init(),
|
||||
* with reference to a table of known drivers, unless the
|
||||
* AV_VAAPI_DRIVER_QUIRK_USER_SET bit is already present. The user
|
||||
* may need to refer to this field when performing any later
|
||||
* operations using VAAPI with the same VADisplay.
|
||||
*/
|
||||
* Driver quirks to apply - this is filled by av_hwdevice_ctx_init(),
|
||||
* with reference to a table of known drivers, unless the
|
||||
* AV_VAAPI_DRIVER_QUIRK_USER_SET bit is already present. The user
|
||||
* may need to refer to this field when performing any later
|
||||
* operations using VAAPI with the same VADisplay.
|
||||
*/
|
||||
unsigned int driver_quirks;
|
||||
} AVVAAPIDeviceContext;
|
||||
|
||||
@@ -512,6 +515,16 @@ namespace va {
|
||||
BOOST_LOG(*(boost::log::sources::severity_logger<int> *) level) << msg;
|
||||
}
|
||||
|
||||
static void
|
||||
vaapi_hwdevice_ctx_free(AVHWDeviceContext *ctx) {
|
||||
auto hwctx = (AVVAAPIDeviceContext *) ctx->hwctx;
|
||||
auto priv = (VAAPIDevicePriv *) ctx->user_opaque;
|
||||
|
||||
vaTerminate(hwctx->display);
|
||||
close(priv->drm_fd);
|
||||
av_freep(&priv);
|
||||
}
|
||||
|
||||
int
|
||||
vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf) {
|
||||
if (!va::initialize) {
|
||||
@@ -529,7 +542,6 @@ namespace va {
|
||||
|
||||
auto *priv = (VAAPIDevicePriv *) av_mallocz(sizeof(VAAPIDevicePriv));
|
||||
priv->drm_fd = fd;
|
||||
priv->drm.fd = fd;
|
||||
|
||||
auto fg = util::fail_guard([fd, priv]() {
|
||||
close(fd);
|
||||
@@ -559,9 +571,13 @@ namespace va {
|
||||
BOOST_LOG(debug) << "vaapi vendor: "sv << va::queryVendorString(display.get());
|
||||
|
||||
*hw_device_buf = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI);
|
||||
auto ctx = (AVVAAPIDeviceContext *) ((AVHWDeviceContext *) (*hw_device_buf)->data)->hwctx;
|
||||
ctx->display = display.release();
|
||||
auto ctx = (AVHWDeviceContext *) (*hw_device_buf)->data;
|
||||
auto hwctx = (AVVAAPIDeviceContext *) ctx->hwctx;
|
||||
|
||||
// Ownership of the VADisplay and DRM fd is now ours to manage via the free() function
|
||||
hwctx->display = display.release();
|
||||
ctx->user_opaque = priv;
|
||||
ctx->free = vaapi_hwdevice_ctx_free;
|
||||
fg.disable();
|
||||
|
||||
auto err = av_hwdevice_ctx_init(*hw_device_buf);
|
||||
@@ -626,11 +642,11 @@ namespace va {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config::video.hevc_mode > 1 && !query(display.get(), profile_e::HEVCMain)) {
|
||||
if (video::active_hevc_mode > 1 && !query(display.get(), profile_e::HEVCMain)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config::video.hevc_mode > 2 && !query(display.get(), profile_e::HEVCMain10)) {
|
||||
if (video::active_hevc_mode > 2 && !query(display.get(), profile_e::HEVCMain10)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#ifndef SUNSHINE_VAAPI_H
|
||||
#define SUNSHINE_VAAPI_H
|
||||
/**
|
||||
* @file src/platform/linux/vaapi.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "misc.h"
|
||||
#include "src/platform/common.h"
|
||||
@@ -9,12 +12,12 @@ namespace egl {
|
||||
}
|
||||
namespace va {
|
||||
/**
|
||||
* Width --> Width of the image
|
||||
* Height --> Height of the image
|
||||
* offset_x --> Horizontal offset of the image in the texture
|
||||
* offset_y --> Vertical offset of the image in the texture
|
||||
* file_t card --> The file descriptor of the render device used for encoding
|
||||
*/
|
||||
* Width --> Width of the image
|
||||
* Height --> Height of the image
|
||||
* offset_x --> Horizontal offset of the image in the texture
|
||||
* offset_y --> Vertical offset of the image in the texture
|
||||
* file_t card --> The file descriptor of the render device used for encoding
|
||||
*/
|
||||
std::shared_ptr<platf::hwdevice_t>
|
||||
make_hwdevice(int width, int height, bool vram);
|
||||
std::shared_ptr<platf::hwdevice_t>
|
||||
@@ -29,4 +32,3 @@ namespace va {
|
||||
int
|
||||
init();
|
||||
} // namespace va
|
||||
#endif
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/platform/linux/wayland.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-util.h>
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#ifndef SUNSHINE_WAYLAND_H
|
||||
#define SUNSHINE_WAYLAND_H
|
||||
/**
|
||||
* @file src/platform/linux/wayland.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
|
||||
@@ -180,9 +183,9 @@ namespace wl {
|
||||
class display_t {
|
||||
public:
|
||||
/**
|
||||
* Initialize display with display_name
|
||||
* If display_name == nullptr -> display_name = std::getenv("WAYLAND_DISPLAY")
|
||||
*/
|
||||
* Initialize display with display_name
|
||||
* If display_name == nullptr -> display_name = std::getenv("WAYLAND_DISPLAY")
|
||||
*/
|
||||
int
|
||||
init(const char *display_name = nullptr);
|
||||
|
||||
@@ -246,5 +249,3 @@ namespace wl {
|
||||
init() { return -1; }
|
||||
} // namespace wl
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/platform/linux/wlgrab.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include "src/platform/common.h"
|
||||
|
||||
#include "src/main.h"
|
||||
@@ -80,7 +84,7 @@ namespace wl {
|
||||
}
|
||||
|
||||
inline platf::capture_e
|
||||
snapshot(platf::img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
auto to = std::chrono::steady_clock::now() + timeout;
|
||||
|
||||
dmabuf.listen(interface.dmabuf_manager, output, cursor);
|
||||
@@ -118,10 +122,10 @@ namespace wl {
|
||||
class wlr_ram_t: public wlr_t {
|
||||
public:
|
||||
platf::capture_e
|
||||
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<platf::img_t> img, bool *cursor) override {
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
while (img) {
|
||||
while (true) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_frame > now) {
|
||||
@@ -132,16 +136,22 @@ namespace wl {
|
||||
}
|
||||
next_frame = now + delay;
|
||||
|
||||
auto status = snapshot(img.get(), 1000ms, *cursor);
|
||||
std::shared_ptr<platf::img_t> img_out;
|
||||
auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
|
||||
switch (status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
case platf::capture_e::interrupted:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
img = snapshot_cb(img, false);
|
||||
if (!push_captured_image_cb(std::move(img_out), false)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img, true);
|
||||
if (!push_captured_image_cb(std::move(img_out), true)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
|
||||
@@ -153,8 +163,8 @@ namespace wl {
|
||||
}
|
||||
|
||||
platf::capture_e
|
||||
snapshot(platf::img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
|
||||
auto status = wlr_t::snapshot(img_out_base, timeout, cursor);
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor);
|
||||
if (status != platf::capture_e::ok) {
|
||||
return status;
|
||||
}
|
||||
@@ -167,6 +177,10 @@ namespace wl {
|
||||
return platf::capture_e::reinit;
|
||||
}
|
||||
|
||||
if (!pull_free_image_cb(img_out)) {
|
||||
return platf::capture_e::interrupted;
|
||||
}
|
||||
|
||||
gl::ctx.BindTexture(GL_TEXTURE_2D, (*rgb_opt)->tex[0]);
|
||||
|
||||
int w, h;
|
||||
@@ -174,7 +188,7 @@ namespace wl {
|
||||
gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
|
||||
BOOST_LOG(debug) << "width and height: w "sv << w << " h "sv << h;
|
||||
|
||||
gl::ctx.GetTextureSubImage((*rgb_opt)->tex[0], 0, 0, 0, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->height * img_out_base->row_pitch, img_out_base->data);
|
||||
gl::ctx.GetTextureSubImage((*rgb_opt)->tex[0], 0, 0, 0, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out->height * img_out->row_pitch, img_out->data);
|
||||
gl::ctx.BindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
return platf::capture_e::ok;
|
||||
@@ -229,10 +243,10 @@ namespace wl {
|
||||
class wlr_vram_t: public wlr_t {
|
||||
public:
|
||||
platf::capture_e
|
||||
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<platf::img_t> img, bool *cursor) override {
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
while (img) {
|
||||
while (true) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_frame > now) {
|
||||
@@ -243,16 +257,22 @@ namespace wl {
|
||||
}
|
||||
next_frame = now + delay;
|
||||
|
||||
auto status = snapshot(img.get(), 1000ms, *cursor);
|
||||
std::shared_ptr<platf::img_t> img_out;
|
||||
auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
|
||||
switch (status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
case platf::capture_e::interrupted:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
img = snapshot_cb(img, false);
|
||||
if (!push_captured_image_cb(std::move(img_out), false)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img, true);
|
||||
if (!push_captured_image_cb(std::move(img_out), true)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
|
||||
@@ -264,13 +284,16 @@ namespace wl {
|
||||
}
|
||||
|
||||
platf::capture_e
|
||||
snapshot(platf::img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
|
||||
auto status = wlr_t::snapshot(img_out_base, timeout, cursor);
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor);
|
||||
if (status != platf::capture_e::ok) {
|
||||
return status;
|
||||
}
|
||||
|
||||
auto img = (egl::img_descriptor_t *) img_out_base;
|
||||
if (!pull_free_image_cb(img_out)) {
|
||||
return platf::capture_e::interrupted;
|
||||
}
|
||||
auto img = (egl::img_descriptor_t *) img_out.get();
|
||||
img->reset();
|
||||
|
||||
auto current_frame = dmabuf.current_frame;
|
||||
@@ -311,7 +334,16 @@ namespace wl {
|
||||
|
||||
int
|
||||
dummy_img(platf::img_t *img) override {
|
||||
return snapshot(img, 1000ms, false) != platf::capture_e::ok;
|
||||
// TODO: stop cheating and give black image
|
||||
if (!img) {
|
||||
return -1;
|
||||
};
|
||||
auto pull_dummy_img_callback = [&img](std::shared_ptr<platf::img_t> &img_out) -> bool {
|
||||
img_out = img->shared_from_this();
|
||||
return true;
|
||||
};
|
||||
std::shared_ptr<platf::img_t> img_out;
|
||||
return snapshot(pull_dummy_img_callback, img_out, 1000ms, false) != platf::capture_e::ok;
|
||||
}
|
||||
|
||||
std::uint64_t sequence {};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// Created by loki on 6/21/19.
|
||||
//
|
||||
|
||||
/**
|
||||
* @file src/platform/linux/x11grab.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include "src/platform/common.h"
|
||||
|
||||
#include <fstream>
|
||||
@@ -389,10 +389,10 @@ namespace platf {
|
||||
|
||||
mem_type_e mem_type;
|
||||
|
||||
/*
|
||||
* Last X (NOT the streamed monitor!) size.
|
||||
* This way we can trigger reinitialization if the dimensions changed while streaming
|
||||
*/
|
||||
/**
|
||||
* Last X (NOT the streamed monitor!) size.
|
||||
* This way we can trigger reinitialization if the dimensions changed while streaming
|
||||
*/
|
||||
// int env_width, env_height;
|
||||
|
||||
x11_attr_t(mem_type_e mem_type):
|
||||
@@ -468,18 +468,18 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the display attributes should change.
|
||||
*/
|
||||
* Called when the display attributes should change.
|
||||
*/
|
||||
void
|
||||
refresh() {
|
||||
x11::GetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's
|
||||
x11::GetWindowAttributes(xdisplay.get(), xwindow, &xattr); // Update xattr's
|
||||
}
|
||||
|
||||
capture_e
|
||||
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override {
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
while (img) {
|
||||
while (true) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_frame > now) {
|
||||
@@ -491,16 +491,22 @@ namespace platf {
|
||||
}
|
||||
next_frame = now + delay;
|
||||
|
||||
auto status = snapshot(img.get(), 1000ms, *cursor);
|
||||
std::shared_ptr<platf::img_t> img_out;
|
||||
auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
|
||||
switch (status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
case platf::capture_e::interrupted:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
img = snapshot_cb(img, false);
|
||||
if (!push_captured_image_cb(std::move(img_out), false)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img, true);
|
||||
if (!push_captured_image_cb(std::move(img_out), true)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
|
||||
@@ -512,26 +518,31 @@ namespace platf {
|
||||
}
|
||||
|
||||
capture_e
|
||||
snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
refresh();
|
||||
|
||||
//The whole X server changed, so we must reinit everything
|
||||
// The whole X server changed, so we must reinit everything
|
||||
if (xattr.width != env_width || xattr.height != env_height) {
|
||||
BOOST_LOG(warning) << "X dimensions changed in non-SHM mode, request reinit"sv;
|
||||
return capture_e::reinit;
|
||||
}
|
||||
XImage *img { x11::GetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) };
|
||||
|
||||
auto img_out = (x11_img_t *) img_out_base;
|
||||
img_out->width = img->width;
|
||||
img_out->height = img->height;
|
||||
img_out->data = (uint8_t *) img->data;
|
||||
img_out->row_pitch = img->bytes_per_line;
|
||||
img_out->pixel_pitch = img->bits_per_pixel / 8;
|
||||
img_out->img.reset(img);
|
||||
if (!pull_free_image_cb(img_out)) {
|
||||
return platf::capture_e::interrupted;
|
||||
}
|
||||
auto img = (x11_img_t *) img_out.get();
|
||||
|
||||
XImage *x_img { x11::GetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) };
|
||||
|
||||
img->width = x_img->width;
|
||||
img->height = x_img->height;
|
||||
img->data = (uint8_t *) x_img->data;
|
||||
img->row_pitch = x_img->bytes_per_line;
|
||||
img->pixel_pitch = x_img->bits_per_pixel / 8;
|
||||
img->img.reset(x_img);
|
||||
|
||||
if (cursor) {
|
||||
blend_cursor(xdisplay.get(), *img_out_base, offset_x, offset_y);
|
||||
blend_cursor(xdisplay.get(), *img, offset_x, offset_y);
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
@@ -559,7 +570,16 @@ namespace platf {
|
||||
|
||||
int
|
||||
dummy_img(img_t *img) override {
|
||||
snapshot(img, 0s, true);
|
||||
// TODO: stop cheating and give black image
|
||||
if (!img) {
|
||||
return -1;
|
||||
};
|
||||
auto pull_dummy_img_callback = [&img](std::shared_ptr<platf::img_t> &img_out) -> bool {
|
||||
img_out = img->shared_from_this();
|
||||
return true;
|
||||
};
|
||||
std::shared_ptr<platf::img_t> img_out;
|
||||
snapshot(pull_dummy_img_callback, img_out, 0s, true);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
@@ -594,10 +614,10 @@ namespace platf {
|
||||
}
|
||||
|
||||
capture_e
|
||||
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override {
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
while (img) {
|
||||
while (true) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (next_frame > now) {
|
||||
@@ -609,16 +629,22 @@ namespace platf {
|
||||
}
|
||||
next_frame = now + delay;
|
||||
|
||||
auto status = snapshot(img.get(), 1000ms, *cursor);
|
||||
std::shared_ptr<platf::img_t> img_out;
|
||||
auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
|
||||
switch (status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
case platf::capture_e::interrupted:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
img = snapshot_cb(img, false);
|
||||
if (!push_captured_image_cb(std::move(img_out), false)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img, true);
|
||||
if (!push_captured_image_cb(std::move(img_out), true)) {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
|
||||
@@ -630,8 +656,8 @@ namespace platf {
|
||||
}
|
||||
|
||||
capture_e
|
||||
snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) {
|
||||
//The whole X server changed, so we must reinit everything
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
// The whole X server changed, so we must reinit everything
|
||||
if (xattr.width != env_width || xattr.height != env_height) {
|
||||
BOOST_LOG(warning) << "X dimensions changed in SHM mode, request reinit"sv;
|
||||
return capture_e::reinit;
|
||||
@@ -645,10 +671,14 @@ namespace platf {
|
||||
return capture_e::reinit;
|
||||
}
|
||||
|
||||
std::copy_n((std::uint8_t *) data.data, frame_size(), img->data);
|
||||
if (!pull_free_image_cb(img_out)) {
|
||||
return platf::capture_e::interrupted;
|
||||
}
|
||||
|
||||
std::copy_n((std::uint8_t *) data.data, frame_size(), img_out->data);
|
||||
|
||||
if (cursor) {
|
||||
blend_cursor(shm_xdisplay.get(), *img, offset_x, offset_y);
|
||||
blend_cursor(shm_xdisplay.get(), *img_out, offset_x, offset_y);
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#ifndef SUNSHINE_X11_GRAB
|
||||
#define SUNSHINE_X11_GRAB
|
||||
/**
|
||||
* @file src/platform/linux/x11grab.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
@@ -34,11 +37,11 @@ namespace platf::x11 {
|
||||
capture(egl::cursor_t &img);
|
||||
|
||||
/**
|
||||
* Capture and blend the cursor into the image
|
||||
*
|
||||
* img <-- destination image
|
||||
* offsetX, offsetY <--- Top left corner of the virtual screen
|
||||
*/
|
||||
* Capture and blend the cursor into the image
|
||||
*
|
||||
* img <-- destination image
|
||||
* offsetX, offsetY <--- Top left corner of the virtual screen
|
||||
*/
|
||||
void
|
||||
blend(img_t &img, int offsetX, int offsetY);
|
||||
|
||||
@@ -66,5 +69,3 @@ namespace platf::x11 {
|
||||
make_display() { return nullptr; }
|
||||
#endif
|
||||
} // namespace platf::x11
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,8 @@
|
||||
#ifndef SUNSHINE_PLATFORM_AV_AUDIO_H
|
||||
#define SUNSHINE_PLATFORM_AV_AUDIO_H
|
||||
/**
|
||||
* @file src/platform/macos/av_audio.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@@ -22,5 +25,3 @@
|
||||
- (int)setupMicrophone:(AVCaptureDevice *)device sampleRate:(UInt32)sampleRate frameSize:(UInt32)frameSize channels:(UInt8)channels;
|
||||
|
||||
@end
|
||||
|
||||
#endif //SUNSHINE_PLATFORM_AV_AUDIO_H
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/platform/macos/av_audio.m
|
||||
* @brief todo
|
||||
*/
|
||||
#import "av_audio.h"
|
||||
|
||||
@implementation AVAudio
|
||||
@@ -126,7 +130,7 @@
|
||||
|
||||
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, NULL, &audioBufferList, sizeof(audioBufferList), NULL, NULL, 0, &blockBuffer);
|
||||
|
||||
//NSAssert(audioBufferList.mNumberBuffers == 1, @"Expected interlveaved PCM format but buffer contained %u streams", audioBufferList.mNumberBuffers);
|
||||
// NSAssert(audioBufferList.mNumberBuffers == 1, @"Expected interlveaved PCM format but buffer contained %u streams", audioBufferList.mNumberBuffers);
|
||||
|
||||
// this is safe, because an interleaved PCM stream has exactly one buffer
|
||||
// and we don't want to do sanity checks in a performance critical exec path
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#ifndef av_img_t_h
|
||||
#define av_img_t_h
|
||||
/**
|
||||
* @file src/platform/macos/av_img_t.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "src/platform/common.h"
|
||||
|
||||
@@ -14,5 +17,3 @@ namespace platf {
|
||||
~av_img_t();
|
||||
};
|
||||
} // namespace platf
|
||||
|
||||
#endif /* av_img_t_h */
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#ifndef SUNSHINE_PLATFORM_AV_VIDEO_H
|
||||
#define SUNSHINE_PLATFORM_AV_VIDEO_H
|
||||
/**
|
||||
* @file src/platform/macos/av_video.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@@ -38,5 +41,3 @@ typedef bool (^FrameCallbackBlock)(CMSampleBufferRef);
|
||||
- (dispatch_semaphore_t)capture:(FrameCallbackBlock)frameCallback;
|
||||
|
||||
@end
|
||||
|
||||
#endif //SUNSHINE_PLATFORM_AV_VIDEO_H
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/platform/macos/av_video.m
|
||||
* @brief todo
|
||||
*/
|
||||
#import "av_video.h"
|
||||
|
||||
@implementation AVVideo
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/platform/macos/display.mm
|
||||
* @brief todo
|
||||
*/
|
||||
#include "src/platform/common.h"
|
||||
#include "src/platform/macos/av_img_t.h"
|
||||
#include "src/platform/macos/av_video.h"
|
||||
@@ -37,38 +41,46 @@ namespace platf {
|
||||
}
|
||||
|
||||
capture_e
|
||||
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override {
|
||||
__block auto img_next = std::move(img);
|
||||
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) {
|
||||
auto av_img_next = std::static_pointer_cast<av_img_t>(img_next);
|
||||
|
||||
CFRetain(sampleBuffer);
|
||||
|
||||
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
|
||||
if (av_img_next->pixel_buffer != nullptr)
|
||||
CVPixelBufferUnlockBaseAddress(av_img_next->pixel_buffer, 0);
|
||||
std::shared_ptr<img_t> img_out;
|
||||
if (!pull_free_image_cb(img_out)) {
|
||||
// got interrupt signal
|
||||
// returning false here stops capture backend
|
||||
return false;
|
||||
}
|
||||
auto av_img = std::static_pointer_cast<av_img_t>(img_out);
|
||||
|
||||
if (av_img_next->sample_buffer != nullptr)
|
||||
CFRelease(av_img_next->sample_buffer);
|
||||
if (av_img->pixel_buffer != nullptr)
|
||||
CVPixelBufferUnlockBaseAddress(av_img->pixel_buffer, 0);
|
||||
|
||||
av_img_next->sample_buffer = sampleBuffer;
|
||||
av_img_next->pixel_buffer = pixelBuffer;
|
||||
img_next->data = (uint8_t *) CVPixelBufferGetBaseAddress(pixelBuffer);
|
||||
if (av_img->sample_buffer != nullptr)
|
||||
CFRelease(av_img->sample_buffer);
|
||||
|
||||
av_img->sample_buffer = sampleBuffer;
|
||||
av_img->pixel_buffer = pixelBuffer;
|
||||
img_out->data = (uint8_t *) CVPixelBufferGetBaseAddress(pixelBuffer);
|
||||
|
||||
size_t extraPixels[4];
|
||||
CVPixelBufferGetExtendedPixels(pixelBuffer, &extraPixels[0], &extraPixels[1], &extraPixels[2], &extraPixels[3]);
|
||||
|
||||
img_next->width = CVPixelBufferGetWidth(pixelBuffer) + extraPixels[0] + extraPixels[1];
|
||||
img_next->height = CVPixelBufferGetHeight(pixelBuffer) + extraPixels[2] + extraPixels[3];
|
||||
img_next->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer);
|
||||
img_next->pixel_pitch = img_next->row_pitch / img_next->width;
|
||||
img_out->width = CVPixelBufferGetWidth(pixelBuffer) + extraPixels[0] + extraPixels[1];
|
||||
img_out->height = CVPixelBufferGetHeight(pixelBuffer) + extraPixels[2] + extraPixels[3];
|
||||
img_out->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer);
|
||||
img_out->pixel_pitch = img_out->row_pitch / img_out->width;
|
||||
|
||||
img_next = snapshot_cb(img_next, true);
|
||||
if (!push_captured_image_cb(std::move(img_out), false)) {
|
||||
// got interrupt signal
|
||||
// returning false here stops capture backend
|
||||
return false;
|
||||
}
|
||||
|
||||
return img_next != nullptr;
|
||||
return true;
|
||||
}];
|
||||
|
||||
// FIXME: We should time out if an image isn't returned for a while
|
||||
@@ -132,6 +144,7 @@ namespace platf {
|
||||
img->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer);
|
||||
img->pixel_pitch = img->row_pitch / img->width;
|
||||
|
||||
// returning false here stops capture backend
|
||||
return false;
|
||||
}];
|
||||
|
||||
@@ -141,12 +154,12 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* A bridge from the pure C++ code of the hwdevice_t class to the pure Objective C code.
|
||||
*
|
||||
* display --> an opaque pointer to an object of this class
|
||||
* width --> the intended capture width
|
||||
* height --> the intended capture height
|
||||
*/
|
||||
* A bridge from the pure C++ code of the hwdevice_t class to the pure Objective C code.
|
||||
*
|
||||
* display --> an opaque pointer to an object of this class
|
||||
* width --> the intended capture width
|
||||
* height --> the intended capture height
|
||||
*/
|
||||
static void
|
||||
setResolution(void *display, int width, int height) {
|
||||
[static_cast<AVVideo *>(display) setFrameWidth:width frameHeight:height];
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/platform/macos/input.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#import <Carbon/Carbon.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
@@ -230,7 +234,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
}
|
||||
|
||||
void
|
||||
keyboard(input_t &input, uint16_t modcode, bool release) {
|
||||
keyboard(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
|
||||
auto key = keysym(modcode);
|
||||
|
||||
BOOST_LOG(debug) << "got keycode: 0x"sv << std::hex << modcode << ", translated to: 0x" << std::hex << key << ", release:" << release;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file src/platform/macos/microphone.mm
|
||||
* @brief todo
|
||||
*/
|
||||
#include "src/platform/common.h"
|
||||
#include "src/platform/macos/av_audio.h"
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user