Merge pull request #1115 from LizardByte/nightly

v0.20.0
This commit is contained in:
ReenigneArcher
2023-05-29 01:11:33 -04:00
committed by GitHub
163 changed files with 5690 additions and 3287 deletions

View File

@@ -52,7 +52,7 @@ NamespaceIndentation: All
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
ReflowComments: true
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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}, \

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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

View File

@@ -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

View File

@@ -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>`.

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -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.

View File

@@ -2,3 +2,4 @@ audio
=====
.. doxygenfile:: audio.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ cbs
===
.. doxygenfile:: cbs.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ config
======
.. doxygenfile:: config.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ confighttp
==========
.. doxygenfile:: confighttp.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ crypto
======
.. doxygenfile:: crypto.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ httpcommon
==========
.. doxygenfile:: httpcommon.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ input
=====
.. doxygenfile:: input.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ main
====
.. doxygenfile:: main.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ move_by_copy
============
.. doxygenfile:: move_by_copy.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ network
=======
.. doxygenfile:: network.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ nvhttp
======
.. doxygenfile:: nvhttp.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ cuda
====
.. doxygenfile:: platform/linux/cuda.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ vaapi
=====
.. doxygenfile:: platform/linux/vaapi.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ wayland
=======
.. doxygenfile:: platform/linux/wayland.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ misc
====
.. doxygenfile:: platform/macos/misc.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ misc
====
.. doxygenfile:: platform/windows/misc.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ process
=======
.. doxygenfile:: process.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ round_robin
===========
.. doxygenfile:: round_robin.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ rtsp
====
.. doxygenfile:: rtsp.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ stream
======
.. doxygenfile:: stream.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ sync
====
.. doxygenfile:: sync.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ system_tray
===========
.. doxygenfile:: system_tray.h
:allow-dot-graphs:

View File

@@ -1,4 +1,5 @@
tasl_pool
task_pool
=========
.. doxygenfile:: task_pool.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ thread_pool
===========
.. doxygenfile:: thread_pool.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ thread_safe
===========
.. doxygenfile:: thread_safe.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ upnp
====
.. doxygenfile:: upnp.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ uuid
====
.. doxygenfile:: uuid.h
:allow-dot-graphs:

View File

@@ -2,3 +2,4 @@ video
=====
.. doxygenfile:: video.h
:allow-dot-graphs:

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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");

View File

@@ -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

View File

@@ -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() };

View File

@@ -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

View File

@@ -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;
}

View File

@@ -1,3 +1,9 @@
/**
* @file src/httpcommon.h
* @brief todo
*/
#pragma once
#include "network.h"
#include "thread_safe.h"

View File

@@ -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;
}
});

View File

@@ -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

View File

@@ -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;
}
/**

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -1,6 +1,7 @@
//
// Created by loki on 5/16/21.
//
/**
* @file src/platform/linux/audio.cpp
* @brief todo
*/
#include <bitset>
#include <sstream>

View File

@@ -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;
}

View File

@@ -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 } {}

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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() };

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -1,3 +1,7 @@
/**
* @file src/platform/linux/wayland.cpp
* @brief todo
*/
#include <wayland-client.h>
#include <wayland-util.h>

View File

@@ -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

View File

@@ -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 {};

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 */

View File

@@ -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

View File

@@ -1,3 +1,7 @@
/**
* @file src/platform/macos/av_video.m
* @brief todo
*/
#import "av_video.h"
@implementation AVVideo

View File

@@ -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];

View File

@@ -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;

View File

@@ -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