Merge remote-tracking branch 'origin/master'

This commit is contained in:
Yukino Song
2025-02-06 09:14:18 +08:00
195 changed files with 9602 additions and 13677 deletions

View File

@@ -6,27 +6,34 @@
# Generated from CLion C/C++ Code Style settings # Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM BasedOnStyle: LLVM
AccessModifierOffset: -2 AccessModifierOffset: -2
AlignAfterOpenBracket: DontAlign AlignAfterOpenBracket: BlockIndent
AlignConsecutiveAssignments: false AlignConsecutiveAssignments: None
AlignEscapedNewlines: DontAlign
AlignOperands: Align AlignOperands: Align
AllowAllArgumentsOnNextLine: false AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All AllowShortEnumsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortFunctionsOnASingleLine: Empty
AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: None
AllowShortLoopsOnASingleLine: true AllowShortLoopsOnASingleLine: true
AlignTrailingComments: false AlignTrailingComments: false
AlwaysBreakAfterReturnType: All AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: MultiLine AlwaysBreakTemplateDeclarations: MultiLine
BreakBeforeBraces: Custom BinPackArguments: false
BinPackParameters: false
BracedInitializerIndentWidth: 2
BraceWrapping: BraceWrapping:
AfterCaseLabel: false AfterCaseLabel: false
AfterClass: false AfterClass: false
AfterControlStatement: Never AfterControlStatement: Never
AfterEnum: false AfterEnum: false
AfterExternBlock: true
AfterFunction: false AfterFunction: false
AfterNamespace: false AfterNamespace: false
AfterObjCDeclaration: false AfterObjCDeclaration: false
@@ -36,39 +43,75 @@ BraceWrapping:
IndentBraces: false IndentBraces: false
SplitEmptyFunction: false SplitEmptyFunction: false
SplitEmptyRecord: true SplitEmptyRecord: true
BreakArrays: true
BreakBeforeBinaryOperators: None BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: false BreakBeforeTernaryOperators: false
BreakConstructorInitializers: AfterColon BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon BreakInheritanceList: AfterColon
ColumnLimit: 0 ColumnLimit: 0
CompactNamespaces: false CompactNamespaces: false
ContinuationIndentWidth: 2 ContinuationIndentWidth: 2
Cpp11BracedListStyle: true
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: Always
ExperimentalAutoDetectBinPacking: true
FixNamespaceComments: true
IncludeBlocks: Regroup
IndentAccessModifiers: false
IndentCaseBlocks: true
IndentCaseLabels: true IndentCaseLabels: true
IndentExternBlock: Indent
IndentGotoLabels: true
IndentPPDirectives: BeforeHash IndentPPDirectives: BeforeHash
IndentWidth: 2 IndentWidth: 2
IndentWrappedFunctionNames: true
InsertBraces: true
InsertNewlineAtEOF: true
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
LineEnding: LF
MaxEmptyLinesToKeep: 1 MaxEmptyLinesToKeep: 1
NamespaceIndentation: All NamespaceIndentation: All
ObjCBinPackProtocolList: Never
ObjCSpaceAfterProperty: true ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: Never
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 1
PenaltyBreakString: 1
PenaltyBreakFirstLessLess: 0
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 100000000
PointerAlignment: Right PointerAlignment: Right
ReferenceAlignment: Pointer
ReflowComments: true ReflowComments: true
RemoveBracesLLVM: false
RemoveSemicolon: false
SeparateDefinitionBlocks: Always
SortIncludes: CaseInsensitive
SortUsingDeclarations: Lexicographic
SpaceAfterCStyleCast: true SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: true SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: false SpaceBeforeCtorInitializerColon: false
SpaceBeforeInheritanceColon: false SpaceBeforeInheritanceColon: false
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2 SpacesBeforeTrailingComments: 2
SpacesInAngles: Never SpacesInAngles: Never
SpacesInCStyleCastParentheses: false SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false SpacesInContainerLiterals: false
SpacesInLineCommentPrefix:
Maximum: 3
Minimum: 1
SpacesInParentheses: false SpacesInParentheses: false
SpacesInSquareBrackets: false SpacesInSquareBrackets: false
TabWidth: 2 TabWidth: 2
Cpp11BracedListStyle: false
UseTab: Never UseTab: Never

View File

@@ -149,6 +149,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${CMAKE_THREAD_LIBS_INIT} ${CMAKE_THREAD_LIBS_INIT}
enet enet
libdisplaydevice::display_device libdisplaydevice::display_device
nlohmann_json::nlohmann_json
opus opus
${FFMPEG_LIBRARIES} ${FFMPEG_LIBRARIES}
${Boost_LIBRARIES} ${Boost_LIBRARIES}

View File

@@ -198,29 +198,33 @@ if(${SUNSHINE_ENABLE_TRAY})
list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/third-party/tray/src/tray_linux.c") list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/third-party/tray/src/tray_linux.c")
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${APPINDICATOR_LIBRARIES} ${LIBNOTIFY_LIBRARIES}) list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${APPINDICATOR_LIBRARIES} ${LIBNOTIFY_LIBRARIES})
endif() endif()
# flatpak icons must be prefixed with the app id or they will not be included in the flatpak
if(${SUNSHINE_BUILD_FLATPAK})
set(SUNSHINE_TRAY_PREFIX "${PROJECT_FQDN}")
else()
set(SUNSHINE_TRAY_PREFIX "apollo")
endif()
list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_TRAY_PREFIX="${SUNSHINE_TRAY_PREFIX}")
else() else()
set(SUNSHINE_TRAY 0) set(SUNSHINE_TRAY 0)
message(STATUS "Tray icon disabled") message(STATUS "Tray icon disabled")
endif() endif()
if(${SUNSHINE_USE_LEGACY_INPUT}) # TODO: Remove this legacy option after the next stable release # These need to be set before adding the inputtino subdirectory in order for them to be picked up
list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/platform/linux/input/legacy_input.cpp") set(LIBEVDEV_CUSTOM_INCLUDE_DIR "${EVDEV_INCLUDE_DIR}")
else() set(LIBEVDEV_CUSTOM_LIBRARY "${EVDEV_LIBRARY}")
# These need to be set before adding the inputtino subdirectory in order for them to be picked up
set(LIBEVDEV_CUSTOM_INCLUDE_DIR "${EVDEV_INCLUDE_DIR}")
set(LIBEVDEV_CUSTOM_LIBRARY "${EVDEV_LIBRARY}")
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/inputtino") add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/inputtino")
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES inputtino::libinputtino) list(APPEND SUNSHINE_EXTERNAL_LIBRARIES inputtino::libinputtino)
file(GLOB_RECURSE INPUTTINO_SOURCES file(GLOB_RECURSE INPUTTINO_SOURCES
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.h ${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.h
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.cpp) ${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.cpp)
list(APPEND PLATFORM_TARGET_FILES ${INPUTTINO_SOURCES}) list(APPEND PLATFORM_TARGET_FILES ${INPUTTINO_SOURCES})
# build libevdev before the libinputtino target # build libevdev before the libinputtino target
if(EXTERNAL_PROJECT_LIBEVDEV_USED) if(EXTERNAL_PROJECT_LIBEVDEV_USED)
add_dependencies(libinputtino libevdev) add_dependencies(libinputtino libevdev)
endif()
endif() endif()
# AppImage and Flatpak # AppImage and Flatpak

View File

@@ -82,7 +82,6 @@ list(PREPEND PLATFORM_LIBRARIES
libstdc++.a libstdc++.a
libwinpthread.a libwinpthread.a
minhook::minhook minhook::minhook
nlohmann_json::nlohmann_json
ntdll ntdll
setupapi setupapi
shlwapi shlwapi

View File

@@ -16,6 +16,7 @@ add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/Simple-Web-Server")
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/libdisplaydevice") add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/libdisplaydevice")
# common dependencies # common dependencies
include("${CMAKE_MODULE_PATH}/dependencies/nlohmann_json.cmake")
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)

View File

@@ -0,0 +1,18 @@
#
# Loads the nlohmann_json library giving the priority to the system package first, with a fallback to FetchContent.
#
include_guard(GLOBAL)
find_package(nlohmann_json 3.11 QUIET GLOBAL)
if(NOT nlohmann_json_FOUND)
message(STATUS "nlohmann_json v3.11.x package not found in the system. Falling back to FetchContent.")
include(FetchContent)
FetchContent_Declare(
json
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
URL_HASH MD5=c23a33f04786d85c29fda8d16b5f0efd
DOWNLOAD_EXTRACT_TIMESTAMP
)
FetchContent_MakeAvailable(json)
endif()

View File

@@ -1,8 +1,5 @@
# windows specific dependencies # windows specific dependencies
# nlohmann_json
find_package(nlohmann_json CONFIG 3.11 REQUIRED)
# Make sure MinHook is installed # Make sure MinHook is installed
find_library(MINHOOK_LIBRARY libMinHook.a REQUIRED) find_library(MINHOOK_LIBRARY libMinHook.a REQUIRED)
find_path(MINHOOK_INCLUDE_DIR MinHook.h PATH_SUFFIXES include REQUIRED) find_path(MINHOOK_INCLUDE_DIR MinHook.h PATH_SUFFIXES include REQUIRED)

View File

@@ -100,6 +100,7 @@ endif()
# tray icon # tray icon
if(${SUNSHINE_TRAY} STREQUAL 1) if(${SUNSHINE_TRAY} STREQUAL 1)
if(NOT ${SUNSHINE_BUILD_FLATPAK})
install(FILES "${CMAKE_SOURCE_DIR}/apollo.svg" install(FILES "${CMAKE_SOURCE_DIR}/apollo.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "apollo-tray.svg") RENAME "apollo-tray.svg")
@@ -109,6 +110,21 @@ if(${SUNSHINE_TRAY} STREQUAL 1)
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status") DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/apollo-locked.svg" install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/apollo-locked.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status") DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
else()
# flatpak icons must be prefixed with the app id or they will not be included in the flatpak
install(FILES "${CMAKE_SOURCE_DIR}/apollo.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "${PROJECT_FQDN}-tray.svg")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/apollo-playing.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "${PROJECT_FQDN}-playing.svg")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/apollo-pausing.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "${PROJECT_FQDN}-pausing.svg")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/apollo-locked.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "${PROJECT_FQDN}-locked.svg")
endif()
set(CPACK_DEBIAN_PACKAGE_DEPENDS "\ set(CPACK_DEBIAN_PACKAGE_DEPENDS "\
${CPACK_DEBIAN_PACKAGE_DEPENDS}, \ ${CPACK_DEBIAN_PACKAGE_DEPENDS}, \
@@ -128,15 +144,8 @@ else()
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.desktop" install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
RENAME "${PROJECT_FQDN}.desktop") RENAME "${PROJECT_FQDN}.desktop")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_kms.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
RENAME "${PROJECT_FQDN}_kms.desktop")
endif() endif()
if(${SUNSHINE_BUILD_FLATPAK}) if(NOT ${SUNSHINE_BUILD_APPIMAGE} AND NOT ${SUNSHINE_BUILD_FLATPAK})
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_terminal.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
RENAME "${PROJECT_FQDN}_terminal.desktop")
elseif(NOT ${SUNSHINE_BUILD_APPIMAGE})
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_terminal.desktop" install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_terminal.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
endif() endif()

View File

@@ -8,10 +8,10 @@ elseif (UNIX)
endif() endif()
if(SUNSHINE_BUILD_FLATPAK) if(SUNSHINE_BUILD_FLATPAK)
set(SUNSHINE_SERVICE_START_COMMAND "ExecStart=${PROJECT_FQDN}") set(SUNSHINE_SERVICE_START_COMMAND "ExecStart=flatpak run --command=sunshine ${PROJECT_FQDN}")
set(SUNSHINE_SERVICE_STOP_COMMAND "ExecStop=flatpak kill ${PROJECT_FQDN}") set(SUNSHINE_SERVICE_STOP_COMMAND "ExecStop=flatpak kill ${PROJECT_FQDN}")
else() else()
set(SUNSHINE_SERVICE_START_COMMAND "ExecStart=${SUNSHINE_EXECUTABLE_PATH}") set(SUNSHINE_SERVICE_START_COMMAND "ExecStart=${SUNSHINE_EXECUTABLE_PATH}")
set(SUNSHINE_SERVICE_STOP_COMMAND "") set(SUNSHINE_SERVICE_STOP_COMMAND "")
endif() endif()
endif () endif()

View File

@@ -65,6 +65,4 @@ elseif(UNIX) # Linux
"Enable building wayland specific code." ON) "Enable building wayland specific code." ON)
option(SUNSHINE_ENABLE_X11 option(SUNSHINE_ENABLE_X11
"Enable X11 grab if available." ON) "Enable X11 grab if available." ON)
option(SUNSHINE_USE_LEGACY_INPUT # TODO: Remove this legacy option after the next stable release
"Use the legacy virtual input implementation." OFF)
endif() endif()

View File

@@ -14,10 +14,8 @@ elseif(UNIX)
if(${SUNSHINE_BUILD_APPIMAGE}) if(${SUNSHINE_BUILD_APPIMAGE})
configure_file(packaging/linux/AppImage/sunshine.desktop sunshine.desktop @ONLY) configure_file(packaging/linux/AppImage/sunshine.desktop sunshine.desktop @ONLY)
elseif(${SUNSHINE_BUILD_FLATPAK}) elseif(${SUNSHINE_BUILD_FLATPAK})
set(SUNSHINE_DESKTOP_ICON "${PROJECT_FQDN}.svg") set(SUNSHINE_DESKTOP_ICON "${PROJECT_FQDN}")
configure_file(packaging/linux/flatpak/sunshine.desktop sunshine.desktop @ONLY) configure_file(packaging/linux/flatpak/sunshine.desktop sunshine.desktop @ONLY)
configure_file(packaging/linux/flatpak/sunshine_kms.desktop sunshine_kms.desktop @ONLY)
configure_file(packaging/linux/sunshine_terminal.desktop sunshine_terminal.desktop @ONLY)
configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.metainfo.xml configure_file(packaging/linux/flatpak/${PROJECT_FQDN}.metainfo.xml
${PROJECT_FQDN}.metainfo.xml @ONLY) ${PROJECT_FQDN}.metainfo.xml @ONLY)
else() else()

View File

@@ -12,17 +12,23 @@ basic authentication with the admin username and password.
## GET /api/apps ## GET /api/apps
@copydoc confighttp::getApps() @copydoc confighttp::getApps()
## GET /api/logs
@copydoc confighttp::getLogs()
## POST /api/apps ## POST /api/apps
@copydoc confighttp::saveApp() @copydoc confighttp::saveApp()
## POST /api/apps/close
@copydoc confighttp::closeApp()
## DELETE /api/apps/{index} ## DELETE /api/apps/{index}
@copydoc confighttp::deleteApp() @copydoc confighttp::deleteApp()
## POST /api/covers/upload ## GET /api/clients/list
@copydoc confighttp::uploadCover() @copydoc confighttp::getClients()
## POST /api/clients/unpair
@copydoc confighttp::unpair()
## POST /api/clients/unpair-all
@copydoc confighttp::unpairAll()
## GET /api/config ## GET /api/config
@copydoc confighttp::getConfig() @copydoc confighttp::getConfig()
@@ -33,11 +39,11 @@ basic authentication with the admin username and password.
## POST /api/config ## POST /api/config
@copydoc confighttp::saveConfig() @copydoc confighttp::saveConfig()
## POST /api/restart ## POST /api/covers/upload
@copydoc confighttp::restart() @copydoc confighttp::uploadCover()
## POST /api/reset-display-device-persistence ## GET /api/logs
@copydoc confighttp::resetDisplayDevicePersistence() @copydoc confighttp::getLogs()
## POST /api/password ## POST /api/password
@copydoc confighttp::savePassword() @copydoc confighttp::savePassword()
@@ -45,17 +51,11 @@ basic authentication with the admin username and password.
## POST /api/pin ## POST /api/pin
@copydoc confighttp::savePin() @copydoc confighttp::savePin()
## POST /api/clients/unpair-all ## POST /api/reset-display-device-persistence
@copydoc confighttp::unpairAll() @copydoc confighttp::resetDisplayDevicePersistence()
## POST /api/clients/unpair ## POST /api/restart
@copydoc confighttp::unpair() @copydoc confighttp::restart()
## GET /api/clients/list
@copydoc confighttp::listClients()
## POST /api/apps/close
@copydoc confighttp::closeApp()
<div class="section_buttons"> <div class="section_buttons">

View File

@@ -301,22 +301,19 @@ administrative privileges. Simply enable the elevated option in the WEB UI, or a
This is an option for both prep-cmd and regular commands and will launch the process with the current user without a This is an option for both prep-cmd and regular commands and will launch the process with the current user without a
UAC prompt. UAC prompt.
@note{It is important to write the values "true" and "false" as string values, not as the typical true/false
values in most JSON.}
**Example** **Example**
```json ```json
{ {
"name": "Game With AntiCheat that Requires Admin", "name": "Game With AntiCheat that Requires Admin",
"output": "", "output": "",
"cmd": "ping 127.0.0.1", "cmd": "ping 127.0.0.1",
"exclude-global-prep-cmd": "false", "exclude-global-prep-cmd": false,
"elevated": "true", "elevated": true,
"prep-cmd": [ "prep-cmd": [
{ {
"do": "powershell.exe -command \"Start-Streaming\"", "do": "powershell.exe -command \"Start-Streaming\"",
"undo": "powershell.exe -command \"Stop-Streaming\"", "undo": "powershell.exe -command \"Stop-Streaming\"",
"elevated": "false" "elevated": false
} }
], ],
"image-path": "" "image-path": ""

View File

@@ -92,7 +92,6 @@ dependencies=(
"mingw-w64-ucrt-x86_64-graphviz" # Optional, for docs "mingw-w64-ucrt-x86_64-graphviz" # Optional, for docs
"mingw-w64-ucrt-x86_64-MinHook" "mingw-w64-ucrt-x86_64-MinHook"
"mingw-w64-ucrt-x86_64-miniupnpc" "mingw-w64-ucrt-x86_64-miniupnpc"
"mingw-w64-ucrt-x86_64-nlohmann-json"
"mingw-w64-ucrt-x86_64-nodejs" "mingw-w64-ucrt-x86_64-nodejs"
"mingw-w64-ucrt-x86_64-nsis" "mingw-w64-ucrt-x86_64-nsis"
"mingw-w64-ucrt-x86_64-onevpl" "mingw-w64-ucrt-x86_64-onevpl"

View File

@@ -225,7 +225,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
<tr> <tr>
<td>Example</td> <td>Example</td>
<td colspan="2">@code{} <td colspan="2">@code{}
global_prep_cmd = [{"do":"nircmd.exe setdisplay 1280 720 32 144","undo":"nircmd.exe setdisplay 2560 1440 32 144"}] global_prep_cmd = [{"do":"nircmd.exe setdisplay 1280 720 32 144","elevated":true,"undo":"nircmd.exe setdisplay 2560 1440 32 144"}]
@endcode</td> @endcode</td>
</tr> </tr>
</table> </table>
@@ -974,7 +974,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr> </tr>
<tr> <tr>
<td>Default</td> <td>Default</td>
<td colspan="2">@code{}verify_only@endcode</td> <td colspan="2">@code{}
disabled
@endcode</td>
</tr> </tr>
<tr> <tr>
<td>Example</td> <td>Example</td>
@@ -1203,6 +1205,31 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr> </tr>
</table> </table>
### dd_config_revert_on_disconnect
<table>
<tr>
<td>Description</td>
<td colspan="2">
When enabled, display configuration is reverted upon disconnect of all clients instead of app close or last session termination.
This can be useful for returning to physical usage of the host machine without closing the active app.
@warning{Some applications may not function properly when display configuration is changed while active.}
@note{Applies to Windows only.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}disabled@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
dd_config_revert_on_disconnect = enabled
@endcode</td>
</tr>
</table>
### dd_mode_remapping ### dd_mode_remapping
<table> <table>

View File

@@ -1,7 +1,7 @@
# Guides # Guides
@admonition{Community | A collection of guides written by the community is available on our @admonition{Community | A collection of guides written by the community is available on our
[blog](https://lizardbyte.com/blog). [blog](https://app.lizardbyte.dev/blog).
Feel free to contribute your own tips and trips by making a PR to Feel free to contribute your own tips and trips by making a PR to
[LizardByte.github.io](https://github.com/LizardByte/LizardByte.github.io).} [LizardByte.github.io](https://github.com/LizardByte/LizardByte.github.io).}

View File

@@ -29,14 +29,18 @@
</p> </p>
<p>NOTE: Sunshine requires additional installation steps.</p> <p>NOTE: Sunshine requires additional installation steps.</p>
<p>flatpak run --command=additional-install.sh @PROJECT_FQDN@</p> <p>
<code>flatpak run --command=additional-install.sh @PROJECT_FQDN@</code>
</p>
<p>NOTE: Sunshine uses a self-signed certificate. The web browser will report it as not secure, but it is safe.</p> <p>NOTE: Sunshine uses a self-signed certificate. The web browser will report it as not secure, but it is safe.</p>
<p>NOTE: KMS Grab (Optional)</p> <p>NOTE: KMS Grab (Optional)</p>
<p>sudo -i PULSE_SERVER=unix:/run/user/$(id -u $whoami)/pulse/native flatpak run @PROJECT_FQDN@</p> <p>
<code>sudo -i PULSE_SERVER=unix:/run/user/$(id -u $whoami)/pulse/native flatpak run @PROJECT_FQDN@</code>
</p>
</description> </description>
<releases> <releases>
<release version="@PROJECT_VERSION@" date="1970-01-01"></release> <release version="@PROJECT_VERSION@" date="1990-01-01"></release>
</releases> </releases>
<developer_name>LizardByte</developer_name> <developer_name>LizardByte</developer_name>

View File

@@ -0,0 +1,11 @@
#!/bin/sh
PORT=47990
if ! curl -k https://localhost:$PORT > /dev/null 2>&1; then
(sleep 3 && xdg-open https://localhost:$PORT) &
exec sunshine "$@"
else
echo "Sunshine is already running, opening the web interface..."
xdg-open https://localhost:$PORT
fi

View File

@@ -1,20 +1,9 @@
[Desktop Entry] [Desktop Entry]
Type=Application Categories=AudioVideo;Network;RemoteAccess;
Name=@PROJECT_NAME@
Exec=@PROJECT_FQDN@
Version=1.0
Comment=@PROJECT_DESCRIPTION@ Comment=@PROJECT_DESCRIPTION@
Exec=sunshine.sh
Icon=@SUNSHINE_DESKTOP_ICON@ Icon=@SUNSHINE_DESKTOP_ICON@
Keywords=gamestream;stream;moonlight;remote play; Keywords=gamestream;stream;moonlight;remote play;
Categories=AudioVideo;Network;RemoteAccess; Name=@PROJECT_NAME@
Actions=RunInTerminal;KMS; Type=Application
Version=1.0
[Desktop Action RunInTerminal]
Name=Run in Terminal
Icon=application-x-executable
Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/@PROJECT_FQDN@_terminal.desktop
[Desktop Action KMS]
Name=Run in Terminal (KMS)
Icon=application-x-executable
Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/@PROJECT_FQDN@_kms.desktop

View File

@@ -1,6 +0,0 @@
[Desktop Entry]
Name=@PROJECT_NAME@ (KMS)
Exec=sudo -i PULSE_SERVER=unix:$(pactl info | awk '/Server String/{print$3}') flatpak run @PROJECT_FQDN@
Terminal=true
Type=Application
NoDisplay=true

View File

@@ -3,6 +3,7 @@ set -e
# Default value for arguments # Default value for arguments
appimage_build=0 appimage_build=0
num_processors=$(nproc)
publisher_name="Third Party Publisher" publisher_name="Third Party Publisher"
publisher_website="" publisher_website=""
publisher_issue_url="https://app.lizardbyte.dev/support" publisher_issue_url="https://app.lizardbyte.dev/support"
@@ -27,6 +28,7 @@ Options:
-h, --help Display this help message. -h, --help Display this help message.
-s, --sudo-off Disable sudo command. -s, --sudo-off Disable sudo command.
--appimage-build Compile for AppImage, this will not create the AppImage, just the executable. --appimage-build Compile for AppImage, this will not create the AppImage, just the executable.
--num-processors The number of processors to use for compilation. Default is the value of 'nproc'.
--publisher-name The name of the publisher (not developer) of the application. --publisher-name The name of the publisher (not developer) of the application.
--publisher-website The URL of the publisher's website. --publisher-website The URL of the publisher's website.
--publisher-issue-url The URL of the publisher's support site or issue tracker. --publisher-issue-url The URL of the publisher's support site or issue tracker.
@@ -53,6 +55,9 @@ while getopts ":hs-:" opt; do
appimage_build=1 appimage_build=1
skip_libva=1 skip_libva=1
;; ;;
num-processors=*)
num_processors="${OPTARG#*=}"
;;
publisher-name=*) publisher-name=*)
publisher_name="${OPTARG#*=}" publisher_name="${OPTARG#*=}"
;; ;;
@@ -373,7 +378,7 @@ function run_install() {
tar -xzf "${build_dir}/doxygen.tar.gz" tar -xzf "${build_dir}/doxygen.tar.gz"
cd "doxygen-${doxygen_min}" cd "doxygen-${doxygen_min}"
cmake -DCMAKE_BUILD_TYPE=Release -G="Ninja" -B="build" -S="." cmake -DCMAKE_BUILD_TYPE=Release -G="Ninja" -B="build" -S="."
ninja -C "build" ninja -C "build" -j"${num_processors}"
ninja -C "build" install ninja -C "build" install
else else
echo "Doxygen version too low, skipping docs" echo "Doxygen version too low, skipping docs"

View File

@@ -7,12 +7,12 @@ directories = [
'src', 'src',
'tests', 'tests',
'tools', 'tools',
os.path.join('third-party', 'glad'),
os.path.join('third-party', 'nvfbc'),
] ]
file_types = [ file_types = [
'cpp', 'cpp',
'cu',
'h', 'h',
'hpp',
'm', 'm',
'mm' 'mm'
] ]

View File

@@ -2,16 +2,18 @@
* @file src/audio.cpp * @file src/audio.cpp
* @brief Definitions for audio capture and encoding. * @brief Definitions for audio capture and encoding.
*/ */
// standard includes
#include <thread> #include <thread>
// lib includes
#include <opus/opus_multistream.h> #include <opus/opus_multistream.h>
#include "platform/common.h" // local includes
#include "audio.h" #include "audio.h"
#include "config.h" #include "config.h"
#include "globals.h" #include "globals.h"
#include "logging.h" #include "logging.h"
#include "platform/common.h"
#include "thread_safe.h" #include "thread_safe.h"
#include "utility.h" #include "utility.h"
@@ -20,15 +22,11 @@ namespace audio {
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>; using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<float>>>; using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<float>>>;
static int static int start_audio_control(audio_ctx_t &ctx);
start_audio_control(audio_ctx_t &ctx); static void stop_audio_control(audio_ctx_t &);
static void static void apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params);
stop_audio_control(audio_ctx_t &);
static void
apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params);
int int map_stream(int channels, bool quality);
map_stream(int channels, bool quality);
constexpr auto SAMPLE_RATE = 48000; constexpr auto SAMPLE_RATE = 48000;
@@ -85,8 +83,7 @@ namespace audio {
}, },
}; };
void void encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
auto packets = mail::man->queue<packet_t>(mail::audio_packets); auto packets = mail::man->queue<packet_t>(mail::audio_packets);
auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])]; auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
if (config.flags[config_t::CUSTOM_SURROUND_PARAMS]) { if (config.flags[config_t::CUSTOM_SURROUND_PARAMS]) {
@@ -96,14 +93,15 @@ namespace audio {
// Encoding takes place on this thread // Encoding takes place on this thread
platf::adjust_thread_priority(platf::thread_priority_e::high); platf::adjust_thread_priority(platf::thread_priority_e::high);
opus_t opus { opus_multistream_encoder_create( opus_t opus {opus_multistream_encoder_create(
stream.sampleRate, stream.sampleRate,
stream.channelCount, stream.channelCount,
stream.streams, stream.streams,
stream.coupledStreams, stream.coupledStreams,
stream.mapping, stream.mapping,
OPUS_APPLICATION_RESTRICTED_LOWDELAY, OPUS_APPLICATION_RESTRICTED_LOWDELAY,
nullptr) }; nullptr
)};
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_BITRATE(stream.bitrate)); opus_multistream_encoder_ctl(opus.get(), OPUS_SET_BITRATE(stream.bitrate));
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_VBR(0)); opus_multistream_encoder_ctl(opus.get(), OPUS_SET_VBR(0));
@@ -114,7 +112,7 @@ namespace audio {
auto frame_size = config.packetDuration * stream.sampleRate / 1000; auto frame_size = config.packetDuration * stream.sampleRate / 1000;
while (auto sample = samples->pop()) { while (auto sample = samples->pop()) {
buffer_t packet { 1400 }; buffer_t packet {1400};
int bytes = opus_multistream_encode_float(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size()); int bytes = opus_multistream_encode_float(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
if (bytes < 0) { if (bytes < 0) {
@@ -129,8 +127,7 @@ namespace audio {
} }
} }
void void capture(safe::mail_t mail, config_t config, void *channel_data) {
capture(safe::mail_t mail, config_t config, void *channel_data) {
auto shutdown_event = mail->event<bool>(mail::shutdown); auto shutdown_event = mail->event<bool>(mail::shutdown);
if (config.input_only) { if (config.input_only) {
@@ -214,7 +211,7 @@ namespace audio {
platf::adjust_thread_priority(platf::thread_priority_e::critical); platf::adjust_thread_priority(platf::thread_priority_e::critical);
auto samples = std::make_shared<sample_queue_t::element_type>(30); auto samples = std::make_shared<sample_queue_t::element_type>(30);
std::thread thread { encodeThread, samples, config, channel_data }; std::thread thread {encodeThread, samples, config, channel_data};
auto fg = util::fail_guard([&]() { auto fg = util::fail_guard([&]() {
samples->stop(); samples->stop();
@@ -256,14 +253,12 @@ namespace audio {
} }
} }
audio_ctx_ref_t audio_ctx_ref_t get_audio_ctx_ref() {
get_audio_ctx_ref() { static auto control_shared {safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control)};
static auto control_shared { safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control) };
return control_shared.ref(); return control_shared.ref();
} }
bool bool is_audio_ctx_sink_available(const audio_ctx_t &ctx) {
is_audio_ctx_sink_available(const audio_ctx_t &ctx) {
if (!ctx.control) { if (!ctx.control) {
return false; return false;
} }
@@ -276,8 +271,7 @@ namespace audio {
return ctx.control->is_sink_available(sink); return ctx.control->is_sink_available(sink);
} }
int int map_stream(int channels, bool quality) {
map_stream(int channels, bool quality) {
int shift = quality ? 1 : 0; int shift = quality ? 1 : 0;
switch (channels) { switch (channels) {
case 2: case 2:
@@ -290,8 +284,7 @@ namespace audio {
return STEREO; return STEREO;
} }
int int start_audio_control(audio_ctx_t &ctx) {
start_audio_control(audio_ctx_t &ctx) {
auto fg = util::fail_guard([]() { auto fg = util::fail_guard([]() {
BOOST_LOG(warning) << "There will be no audio"sv; BOOST_LOG(warning) << "There will be no audio"sv;
}); });
@@ -318,8 +311,7 @@ namespace audio {
return 0; return 0;
} }
void void stop_audio_control(audio_ctx_t &ctx) {
stop_audio_control(audio_ctx_t &ctx) {
// restore audio-sink if applicable // restore audio-sink if applicable
if (!ctx.restore_sink) { if (!ctx.restore_sink) {
return; return;
@@ -333,8 +325,7 @@ namespace audio {
} }
} }
void void apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params) {
apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params) {
stream.channelCount = params.channelCount; stream.channelCount = params.channelCount;
stream.streams = params.streams; stream.streams = params.streams;
stream.coupledStreams = params.coupledStreams; stream.coupledStreams = params.coupledStreams;

View File

@@ -73,8 +73,7 @@ namespace audio {
using packet_t = std::pair<void *, buffer_t>; using packet_t = std::pair<void *, buffer_t>;
using audio_ctx_ref_t = safe::shared_t<audio_ctx_t>::ptr_t; using audio_ctx_ref_t = safe::shared_t<audio_ctx_t>::ptr_t;
void void capture(safe::mail_t mail, config_t config, void *channel_data);
capture(safe::mail_t mail, config_t config, void *channel_data);
/** /**
* @brief Get the reference to the audio context. * @brief Get the reference to the audio context.
@@ -86,8 +85,7 @@ namespace audio {
* audio_ctx_ref_t audio = get_audio_ctx_ref() * audio_ctx_ref_t audio = get_audio_ctx_ref()
* @examples_end * @examples_end
*/ */
audio_ctx_ref_t audio_ctx_ref_t get_audio_ctx_ref();
get_audio_ctx_ref();
/** /**
* @brief Check if the audio sink held by audio context is available. * @brief Check if the audio sink held by audio context is available.
@@ -103,6 +101,5 @@ namespace audio {
* return false; * return false;
* @examples_end * @examples_end
*/ */
bool bool is_audio_ctx_sink_available(const audio_ctx_t &ctx);
is_audio_ctx_sink_available(const audio_ctx_t &ctx);
} // namespace audio } // namespace audio

View File

@@ -3,6 +3,7 @@
* @brief Definitions for FFmpeg Coded Bitstream API. * @brief Definitions for FFmpeg Coded Bitstream API.
*/ */
extern "C" { extern "C" {
// lib includes
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
#include <cbs/cbs_h264.h> #include <cbs/cbs_h264.h>
#include <cbs/cbs_h265.h> #include <cbs/cbs_h265.h>
@@ -10,14 +11,15 @@ extern "C" {
#include <libavutil/pixdesc.h> #include <libavutil/pixdesc.h>
} }
// local includes
#include "cbs.h" #include "cbs.h"
#include "logging.h" #include "logging.h"
#include "utility.h" #include "utility.h"
using namespace std::literals; using namespace std::literals;
namespace cbs { namespace cbs {
void void close(CodedBitstreamContext *c) {
close(CodedBitstreamContext *c) {
ff_cbs_close(&c); ff_cbs_close(&c);
} }
@@ -36,8 +38,7 @@ namespace cbs {
std::fill_n((std::uint8_t *) this, sizeof(*this), 0); std::fill_n((std::uint8_t *) this, sizeof(*this), 0);
} }
frag_t & frag_t &operator=(frag_t &&o) {
operator=(frag_t &&o) {
std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this); std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this);
o.data = nullptr; o.data = nullptr;
@@ -53,12 +54,11 @@ namespace cbs {
} }
}; };
util::buffer_t<std::uint8_t> util::buffer_t<std::uint8_t> write(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; cbs::frag_t frag;
auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr); auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr);
if (err < 0) { if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {}; return {};
@@ -66,29 +66,27 @@ namespace cbs {
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag); err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag);
if (err < 0) { if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
BOOST_LOG(error) << "Could not write fragment data: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); BOOST_LOG(error) << "Could not write fragment data: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {}; return {};
} }
// frag.data_size * 8 - frag.data_bit_padding == bits in fragment // frag.data_size * 8 - frag.data_bit_padding == bits in fragment
util::buffer_t<std::uint8_t> data { frag.data_size }; util::buffer_t<std::uint8_t> data {frag.data_size};
std::copy_n(frag.data, frag.data_size, std::begin(data)); std::copy_n(frag.data, frag.data_size, std::begin(data));
return data; return data;
} }
util::buffer_t<std::uint8_t> util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::ctx_t cbs_ctx; cbs::ctx_t cbs_ctx;
ff_cbs_init(&cbs_ctx, codec_id, nullptr); ff_cbs_init(&cbs_ctx, codec_id, nullptr);
return write(cbs_ctx, nal, uh, codec_id); return write(cbs_ctx, nal, uh, codec_id);
} }
h264_t h264_t make_sps_h264(const AVCodecContext *avctx, const AVPacket *packet) {
make_sps_h264(const AVCodecContext *avctx, const AVPacket *packet) {
cbs::ctx_t ctx; cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) { if (ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
return {}; return {};
@@ -98,7 +96,7 @@ namespace cbs {
int err = ff_cbs_read_packet(ctx.get(), &frag, packet); int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if (err < 0) { if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {}; return {};
@@ -144,8 +142,7 @@ namespace cbs {
}; };
} }
hevc_t hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
cbs::ctx_t ctx; cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) { if (ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) {
return {}; return {};
@@ -155,7 +152,7 @@ namespace cbs {
int err = ff_cbs_read_packet(ctx.get(), &frag, packet); int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if (err < 0) { if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {}; return {};
@@ -222,8 +219,7 @@ namespace cbs {
* It then checks if the SPS->VUI (Video Usability Information) is present in the active SPS of the packet. * It then checks if the SPS->VUI (Video Usability Information) is present in the active SPS of the packet.
* This is done for both H264 and H265 codecs. * This is done for both H264 and H265 codecs.
*/ */
bool bool validate_sps(const AVPacket *packet, int codec_id) {
validate_sps(const AVPacket *packet, int codec_id) {
cbs::ctx_t ctx; cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, (AVCodecID) codec_id, nullptr)) { if (ff_cbs_init(&ctx, (AVCodecID) codec_id, nullptr)) {
return false; return false;
@@ -233,7 +229,7 @@ namespace cbs {
int err = ff_cbs_read_packet(ctx.get(), &frag, packet); int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if (err < 0) { if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return false; return false;

View File

@@ -4,6 +4,7 @@
*/ */
#pragma once #pragma once
// local includes
#include "utility.h" #include "utility.h"
struct AVPacket; struct AVPacket;
@@ -25,10 +26,8 @@ namespace cbs {
nal_t sps; nal_t sps;
}; };
hevc_t hevc_t make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet);
make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet); h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet);
h264_t
make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet);
/** /**
* @brief Validates the Sequence Parameter Set (SPS) of a given packet. * @brief Validates the Sequence Parameter Set (SPS) of a given packet.
@@ -36,6 +35,5 @@ namespace cbs {
* @param codec_id The ID of the codec used (either AV_CODEC_ID_H264 or AV_CODEC_ID_H265). * @param codec_id The ID of the codec used (either AV_CODEC_ID_H264 or AV_CODEC_ID_H265).
* @return True if the SPS->VUI is present in the active SPS of the packet, false otherwise. * @return True if the SPS->VUI is present in the active SPS of the packet, false otherwise.
*/ */
bool bool validate_sps(const AVPacket *packet, int codec_id);
validate_sps(const AVPacket *packet, int codec_id);
} // namespace cbs } // namespace cbs

View File

@@ -2,6 +2,7 @@
* @file src/config.cpp * @file src/config.cpp
* @brief Definitions for the configuration of Sunshine. * @brief Definitions for the configuration of Sunshine.
*/ */
// standard includes
#include <algorithm> #include <algorithm>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
@@ -11,22 +12,23 @@
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
// lib includes
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
// local includes
#include "config.h" #include "config.h"
#include "entry_handler.h" #include "entry_handler.h"
#include "file_handler.h" #include "file_handler.h"
#include "logging.h" #include "logging.h"
#include "nvhttp.h" #include "nvhttp.h"
#include "platform/common.h"
#include "rtsp.h" #include "rtsp.h"
#include "video.h" #include "video.h"
#include "utility.h" #include "utility.h"
#include "platform/common.h"
#ifdef _WIN32 #ifdef _WIN32
#include <shellapi.h> #include <shellapi.h>
#include "platform/windows/utils.h" #include "platform/windows/utils.h"
@@ -45,15 +47,21 @@ using namespace std::literals;
#define CERTIFICATE_FILE CA_DIR "/cacert.pem" #define CERTIFICATE_FILE CA_DIR "/cacert.pem"
#define APPS_JSON_PATH platf::appdata().string() + "/apps.json" #define APPS_JSON_PATH platf::appdata().string() + "/apps.json"
namespace config { namespace config {
namespace nv { namespace nv {
nvenc::nvenc_two_pass nvenc::nvenc_two_pass twopass_from_view(const std::string_view &preset) {
twopass_from_view(const std::string_view &preset) { if (preset == "disabled") {
if (preset == "disabled") return nvenc::nvenc_two_pass::disabled; return nvenc::nvenc_two_pass::disabled;
if (preset == "quarter_res") return nvenc::nvenc_two_pass::quarter_resolution; }
if (preset == "full_res") return nvenc::nvenc_two_pass::full_resolution; if (preset == "quarter_res") {
return nvenc::nvenc_two_pass::quarter_resolution;
}
if (preset == "full_res") {
return nvenc::nvenc_two_pass::full_resolution;
}
BOOST_LOG(warning) << "config: unknown nvenc_twopass value: " << preset; BOOST_LOG(warning) << "config: unknown nvenc_twopass value: " << preset;
return nvenc::nvenc_two_pass::quarter_resolution; return nvenc::nvenc_two_pass::quarter_resolution;
} }
@@ -180,11 +188,11 @@ namespace config {
cavlc = AMF_VIDEO_ENCODER_CALV ///< CAVLC cavlc = AMF_VIDEO_ENCODER_CALV ///< CAVLC
}; };
template <class T> template<class T>
std::optional<int> std::optional<int> quality_from_view(const std::string_view &quality_type, const std::optional<int>(&original)) {
quality_from_view(const std::string_view &quality_type, const std::optional<int>(&original)) {
#define _CONVERT_(x) \ #define _CONVERT_(x) \
if (quality_type == #x##sv) return (int) T::x if (quality_type == #x##sv) \
return (int) T::x
_CONVERT_(balanced); _CONVERT_(balanced);
_CONVERT_(quality); _CONVERT_(quality);
_CONVERT_(speed); _CONVERT_(speed);
@@ -192,11 +200,11 @@ namespace config {
return original; return original;
} }
template <class T> template<class T>
std::optional<int> std::optional<int> rc_from_view(const std::string_view &rc, const std::optional<int>(&original)) {
rc_from_view(const std::string_view &rc, const std::optional<int>(&original)) {
#define _CONVERT_(x) \ #define _CONVERT_(x) \
if (rc == #x##sv) return (int) T::x if (rc == #x##sv) \
return (int) T::x
_CONVERT_(cbr); _CONVERT_(cbr);
_CONVERT_(cqp); _CONVERT_(cqp);
_CONVERT_(vbr_latency); _CONVERT_(vbr_latency);
@@ -205,11 +213,11 @@ namespace config {
return original; return original;
} }
template <class T> template<class T>
std::optional<int> std::optional<int> usage_from_view(const std::string_view &usage, const std::optional<int>(&original)) {
usage_from_view(const std::string_view &usage, const std::optional<int>(&original)) {
#define _CONVERT_(x) \ #define _CONVERT_(x) \
if (usage == #x##sv) return (int) T::x if (usage == #x##sv) \
return (int) T::x
_CONVERT_(lowlatency); _CONVERT_(lowlatency);
_CONVERT_(lowlatency_high_quality); _CONVERT_(lowlatency_high_quality);
_CONVERT_(transcoding); _CONVERT_(transcoding);
@@ -219,11 +227,16 @@ namespace config {
return original; return original;
} }
int int coder_from_view(const std::string_view &coder) {
coder_from_view(const std::string_view &coder) { if (coder == "auto"sv) {
if (coder == "auto"sv) return _auto; return _auto;
if (coder == "cabac"sv || coder == "ac"sv) return cabac; }
if (coder == "cavlc"sv || coder == "vlc"sv) return cavlc; if (coder == "cabac"sv || coder == "ac"sv) {
return cabac;
}
if (coder == "cavlc"sv || coder == "vlc"sv) {
return cavlc;
}
return _auto; return _auto;
} }
@@ -246,10 +259,10 @@ namespace config {
disabled = false ///< Disabled disabled = false ///< Disabled
}; };
std::optional<int> std::optional<int> preset_from_view(const std::string_view &preset) {
preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x) \ #define _CONVERT_(x) \
if (preset == #x##sv) return x if (preset == #x##sv) \
return x
_CONVERT_(veryslow); _CONVERT_(veryslow);
_CONVERT_(slower); _CONVERT_(slower);
_CONVERT_(slow); _CONVERT_(slow);
@@ -261,11 +274,16 @@ namespace config {
return std::nullopt; return std::nullopt;
} }
std::optional<int> std::optional<int> coder_from_view(const std::string_view &coder) {
coder_from_view(const std::string_view &coder) { if (coder == "auto"sv) {
if (coder == "auto"sv) return _auto; return _auto;
if (coder == "cabac"sv || coder == "ac"sv) return disabled; }
if (coder == "cavlc"sv || coder == "vlc"sv) return enabled; if (coder == "cabac"sv || coder == "ac"sv) {
return disabled;
}
if (coder == "cavlc"sv || coder == "vlc"sv) {
return enabled;
}
return std::nullopt; return std::nullopt;
} }
@@ -279,32 +297,40 @@ namespace config {
cavlc ///< CAVLC cavlc ///< CAVLC
}; };
int int coder_from_view(const std::string_view &coder) {
coder_from_view(const std::string_view &coder) { if (coder == "auto"sv) {
if (coder == "auto"sv) return _auto; return _auto;
if (coder == "cabac"sv || coder == "ac"sv) return cabac; }
if (coder == "cavlc"sv || coder == "vlc"sv) return cavlc; if (coder == "cabac"sv || coder == "ac"sv) {
return cabac;
}
if (coder == "cavlc"sv || coder == "vlc"sv) {
return cavlc;
}
return -1; return -1;
} }
int int allow_software_from_view(const std::string_view &software) {
allow_software_from_view(const std::string_view &software) { if (software == "allowed"sv || software == "forced") {
if (software == "allowed"sv || software == "forced") return 1; return 1;
}
return 0; return 0;
} }
int int force_software_from_view(const std::string_view &software) {
force_software_from_view(const std::string_view &software) { if (software == "forced") {
if (software == "forced") return 1; return 1;
}
return 0; return 0;
} }
int int rt_from_view(const std::string_view &rt) {
rt_from_view(const std::string_view &rt) { if (rt == "disabled" || rt == "off" || rt == "0") {
if (rt == "disabled" || rt == "off" || rt == "0") return 0; return 0;
}
return 1; return 1;
} }
@@ -312,10 +338,10 @@ namespace config {
} // namespace vt } // namespace vt
namespace sw { namespace sw {
int int svtav1_preset_from_view(const std::string_view &preset) {
svtav1_preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x, y) \ #define _CONVERT_(x, y) \
if (preset == #x##sv) return y if (preset == #x##sv) \
return y
_CONVERT_(veryslow, 1); _CONVERT_(veryslow, 1);
_CONVERT_(slower, 2); _CONVERT_(slower, 2);
_CONVERT_(slow, 4); _CONVERT_(slow, 4);
@@ -331,10 +357,10 @@ namespace config {
} // namespace sw } // namespace sw
namespace dd { namespace dd {
video_t::dd_t::config_option_e video_t::dd_t::config_option_e config_option_from_view(const std::string_view value) {
config_option_from_view(const std::string_view value) {
#define _CONVERT_(x) \ #define _CONVERT_(x) \
if (value == #x##sv) return video_t::dd_t::config_option_e::x if (value == #x##sv) \
return video_t::dd_t::config_option_e::x
_CONVERT_(disabled); _CONVERT_(disabled);
_CONVERT_(verify_only); _CONVERT_(verify_only);
_CONVERT_(ensure_active); _CONVERT_(ensure_active);
@@ -344,10 +370,10 @@ namespace config {
return video_t::dd_t::config_option_e::disabled; // Default to this if value is invalid return video_t::dd_t::config_option_e::disabled; // Default to this if value is invalid
} }
video_t::dd_t::resolution_option_e video_t::dd_t::resolution_option_e resolution_option_from_view(const std::string_view value) {
resolution_option_from_view(const std::string_view value) {
#define _CONVERT_2_ARG_(str, val) \ #define _CONVERT_2_ARG_(str, val) \
if (value == #str##sv) return video_t::dd_t::resolution_option_e::val if (value == #str##sv) \
return video_t::dd_t::resolution_option_e::val
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x) #define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
_CONVERT_(disabled); _CONVERT_(disabled);
_CONVERT_2_ARG_(auto, automatic); _CONVERT_2_ARG_(auto, automatic);
@@ -357,10 +383,10 @@ namespace config {
return video_t::dd_t::resolution_option_e::disabled; // Default to this if value is invalid return video_t::dd_t::resolution_option_e::disabled; // Default to this if value is invalid
} }
video_t::dd_t::refresh_rate_option_e video_t::dd_t::refresh_rate_option_e refresh_rate_option_from_view(const std::string_view value) {
refresh_rate_option_from_view(const std::string_view value) {
#define _CONVERT_2_ARG_(str, val) \ #define _CONVERT_2_ARG_(str, val) \
if (value == #str##sv) return video_t::dd_t::refresh_rate_option_e::val if (value == #str##sv) \
return video_t::dd_t::refresh_rate_option_e::val
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x) #define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
_CONVERT_(disabled); _CONVERT_(disabled);
_CONVERT_2_ARG_(auto, automatic); _CONVERT_2_ARG_(auto, automatic);
@@ -370,10 +396,10 @@ namespace config {
return video_t::dd_t::refresh_rate_option_e::disabled; // Default to this if value is invalid return video_t::dd_t::refresh_rate_option_e::disabled; // Default to this if value is invalid
} }
video_t::dd_t::hdr_option_e video_t::dd_t::hdr_option_e hdr_option_from_view(const std::string_view value) {
hdr_option_from_view(const std::string_view value) {
#define _CONVERT_2_ARG_(str, val) \ #define _CONVERT_2_ARG_(str, val) \
if (value == #str##sv) return video_t::dd_t::hdr_option_e::val if (value == #str##sv) \
return video_t::dd_t::hdr_option_e::val
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x) #define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
_CONVERT_(disabled); _CONVERT_(disabled);
_CONVERT_2_ARG_(auto, automatic); _CONVERT_2_ARG_(auto, automatic);
@@ -382,9 +408,8 @@ namespace config {
return video_t::dd_t::hdr_option_e::disabled; // Default to this if value is invalid return video_t::dd_t::hdr_option_e::disabled; // Default to this if value is invalid
} }
video_t::dd_t::mode_remapping_t video_t::dd_t::mode_remapping_t mode_remapping_from_view(const std::string_view value) {
mode_remapping_from_view(const std::string_view value) { const auto parse_entry_list {[](const auto &entry_list, auto &output_field) {
const auto parse_entry_list { [](const auto &entry_list, auto &output_field) {
for (auto &[_, entry] : entry_list) { for (auto &[_, entry] : entry_list) {
auto requested_resolution = entry.template get_optional<std::string>("requested_resolution"s); auto requested_resolution = entry.template get_optional<std::string>("requested_resolution"s);
auto requested_fps = entry.template get_optional<std::string>("requested_fps"s); auto requested_fps = entry.template get_optional<std::string>("requested_fps"s);
@@ -395,9 +420,10 @@ namespace config {
requested_resolution.value_or(""), requested_resolution.value_or(""),
requested_fps.value_or(""), requested_fps.value_or(""),
final_resolution.value_or(""), final_resolution.value_or(""),
final_refresh_rate.value_or("") }); final_refresh_rate.value_or("")
});
} }
} }; }};
// We need to add a wrapping object to make it valid JSON, otherwise ptree cannot parse it. // We need to add a wrapping object to make it valid JSON, otherwise ptree cannot parse it.
std::stringstream json_stream; std::stringstream json_stream;
@@ -483,6 +509,7 @@ namespace config {
{}, // manual_refresh_rate {}, // manual_refresh_rate
video_t::dd_t::hdr_option_e::automatic, // hdr_option video_t::dd_t::hdr_option_e::automatic, // hdr_option
3s, // config_revert_delay 3s, // config_revert_delay
{}, // config_revert_on_disconnect
{}, // mode_remapping {}, // mode_remapping
{} // wa {} // wa
}, // display_device }, // display_device
@@ -522,13 +549,13 @@ namespace config {
input_t input { input_t input {
{ {
{ 0x10, 0xA0 }, {0x10, 0xA0},
{ 0x11, 0xA2 }, {0x11, 0xA2},
{ 0x12, 0xA4 }, {0x12, 0xA4},
}, },
-1ms, // back_button_timeout -1ms, // back_button_timeout
500ms, // key_repeat_delay 500ms, // key_repeat_delay
std::chrono::duration<double> { 1 / 24.9 }, // key_repeat_period std::chrono::duration<double> {1 / 24.9}, // key_repeat_period
{ {
platf::supported_gamepads(nullptr).front().name.data(), platf::supported_gamepads(nullptr).front().name.data(),
@@ -567,23 +594,19 @@ namespace config {
{}, // server commands {}, // server commands
}; };
bool bool endline(char ch) {
endline(char ch) {
return ch == '\r' || ch == '\n'; return ch == '\r' || ch == '\n';
} }
bool bool space_tab(char ch) {
space_tab(char ch) {
return ch == ' ' || ch == '\t'; return ch == ' ' || ch == '\t';
} }
bool bool whitespace(char ch) {
whitespace(char ch) {
return space_tab(ch) || endline(ch); return space_tab(ch) || endline(ch);
} }
std::string std::string to_string(const char *begin, const char *end) {
to_string(const char *begin, const char *end) {
std::string result; std::string result;
KITTY_WHILE_LOOP(auto pos = begin, pos != end, { KITTY_WHILE_LOOP(auto pos = begin, pos != end, {
@@ -598,9 +621,8 @@ namespace config {
return result; return result;
} }
template <class It> template<class It>
It It skip_list(It skipper, It end) {
skip_list(It skipper, It end) {
int stack = 1; int stack = 1;
while (skipper != end && stack) { while (skipper != end && stack) {
if (*skipper == '[') { if (*skipper == '[') {
@@ -649,11 +671,11 @@ namespace config {
return std::make_pair( return std::make_pair(
endl, endl,
std::make_pair(to_string(begin, end_name), to_string(begin_val, endl))); std::make_pair(to_string(begin, end_name), to_string(begin_val, endl))
);
} }
std::unordered_map<std::string, std::string> std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content) {
parse_config(const std::string_view &file_content) {
std::unordered_map<std::string, std::string> vars; std::unordered_map<std::string, std::string> vars;
auto pos = std::begin(file_content); auto pos = std::begin(file_content);
@@ -678,8 +700,7 @@ namespace config {
return vars; return vars;
} }
void void string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
auto it = vars.find(name); auto it = vars.find(name);
if (it == std::end(vars)) { if (it == std::end(vars)) {
return; return;
@@ -690,9 +711,8 @@ namespace config {
vars.erase(it); vars.erase(it);
} }
template <typename T, typename F> template<typename T, typename F>
void void generic_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, T &input, F &&f) {
generic_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, T &input, F &&f) {
std::string tmp; std::string tmp;
string_f(vars, name, tmp); string_f(vars, name, tmp);
if (!tmp.empty()) { if (!tmp.empty()) {
@@ -700,8 +720,7 @@ namespace config {
} }
} }
void void string_restricted_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input, const std::vector<std::string_view> &allowed_vals) {
string_restricted_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input, const std::vector<std::string_view> &allowed_vals) {
std::string temp; std::string temp;
string_f(vars, name, temp); string_f(vars, name, temp);
@@ -713,8 +732,7 @@ namespace config {
} }
} }
void void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
// appdata needs to be retrieved once only // appdata needs to be retrieved once only
static auto appdata = platf::appdata(); static auto appdata = platf::appdata();
@@ -738,8 +756,7 @@ namespace config {
} }
} }
void void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
fs::path temp = input; fs::path temp = input;
path_f(vars, name, temp); path_f(vars, name, temp);
@@ -747,8 +764,7 @@ namespace config {
input = temp.string(); input = temp.string();
} }
void void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
auto it = vars.find(name); auto it = vars.find(name);
if (it == std::end(vars)) { if (it == std::end(vars)) {
@@ -765,16 +781,14 @@ namespace config {
// If that integer is in hexadecimal // If that integer is in hexadecimal
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) { if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
input = util::from_hex<int>(val.substr(2)); input = util::from_hex<int>(val.substr(2));
} } else {
else {
input = util::from_view(val); input = util::from_view(val);
} }
vars.erase(it); vars.erase(it);
} }
void void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
auto it = vars.find(name); auto it = vars.find(name);
if (it == std::end(vars)) { if (it == std::end(vars)) {
@@ -791,17 +805,15 @@ namespace config {
// If that integer is in hexadecimal // If that integer is in hexadecimal
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) { if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
input = util::from_hex<int>(val.substr(2)); input = util::from_hex<int>(val.substr(2));
} } else {
else {
input = util::from_view(val); input = util::from_view(val);
} }
vars.erase(it); vars.erase(it);
} }
template <class F> template<class F>
void void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
std::string tmp; std::string tmp;
string_f(vars, name, tmp); string_f(vars, name, tmp);
if (!tmp.empty()) { if (!tmp.empty()) {
@@ -809,9 +821,8 @@ namespace config {
} }
} }
template <class F> template<class F>
void void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
std::string tmp; std::string tmp;
string_f(vars, name, tmp); string_f(vars, name, tmp);
if (!tmp.empty()) { if (!tmp.empty()) {
@@ -819,8 +830,7 @@ namespace config {
} }
} }
void void int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &range) {
int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &range) {
int temp = input; int temp = input;
int_f(vars, name, temp); int_f(vars, name, temp);
@@ -831,9 +841,10 @@ namespace config {
} }
} }
bool bool to_bool(std::string &boolean) {
to_bool(std::string &boolean) { std::for_each(std::begin(boolean), std::end(boolean), [](char ch) {
std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char) std::tolower(ch); }); return (char) std::tolower(ch);
});
return boolean == "true"sv || return boolean == "true"sv ||
boolean == "yes"sv || boolean == "yes"sv ||
@@ -843,8 +854,7 @@ namespace config {
(std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean)); (std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean));
} }
void void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
std::string tmp; std::string tmp;
string_f(vars, name, tmp); string_f(vars, name, tmp);
@@ -855,8 +865,7 @@ namespace config {
input = to_bool(tmp); input = to_bool(tmp);
} }
void void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
std::string tmp; std::string tmp;
string_f(vars, name, tmp); string_f(vars, name, tmp);
@@ -874,8 +883,7 @@ namespace config {
input = val; input = val;
} }
void void double_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input, const std::pair<double, double> &range) {
double_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input, const std::pair<double, double> &range) {
double temp = input; double temp = input;
double_f(vars, name, temp); double_f(vars, name, temp);
@@ -886,8 +894,7 @@ namespace config {
} }
} }
void void list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
std::string string; std::string string;
string_f(vars, name, string); string_f(vars, name, string);
@@ -911,15 +918,12 @@ namespace config {
while (pos < std::cend(string)) { while (pos < std::cend(string)) {
if (*pos == '[') { if (*pos == '[') {
pos = skip_list(pos + 1, std::cend(string)) + 1; pos = skip_list(pos + 1, std::cend(string)) + 1;
} } else if (*pos == ']') {
else if (*pos == ']') {
break; break;
} } else if (*pos == ',') {
else if (*pos == ',') {
input.emplace_back(begin, pos); input.emplace_back(begin, pos);
pos = begin = std::find_if_not(pos + 1, std::cend(string), whitespace); pos = begin = std::find_if_not(pos + 1, std::cend(string), whitespace);
} } else {
else {
++pos; ++pos;
} }
} }
@@ -929,8 +933,7 @@ namespace config {
} }
} }
void void list_prep_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &input) {
list_prep_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &input) {
std::string string; std::string string;
string_f(vars, name, string); string_f(vars, name, string);
@@ -956,8 +959,7 @@ namespace config {
} }
} }
void void list_server_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<server_cmd_t> &input) {
list_server_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<server_cmd_t> &input) {
std::string string; std::string string;
string_f(vars, name, string); string_f(vars, name, string);
@@ -983,8 +985,7 @@ namespace config {
} }
} }
void void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
std::vector<std::string> list; std::vector<std::string> list;
list_string_f(vars, name, list); list_string_f(vars, name, list);
@@ -1010,16 +1011,14 @@ namespace config {
// If the integer is a hexadecimal // If the integer is a hexadecimal
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) { if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
tmp = util::from_hex<int>(val.substr(2)); tmp = util::from_hex<int>(val.substr(2));
} } else {
else {
tmp = util::from_view(val); tmp = util::from_view(val);
} }
input.emplace_back(tmp); input.emplace_back(tmp);
} }
} }
void void map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
std::vector<int> list; std::vector<int> list;
list_int_f(vars, name, list); list_int_f(vars, name, list);
@@ -1038,8 +1037,7 @@ namespace config {
} }
} }
int int apply_flags(const char *line) {
apply_flags(const char *line) {
int ret = 0; int ret = 0;
while (*line != '\0') { while (*line != '\0') {
switch (*line) { switch (*line) {
@@ -1066,8 +1064,7 @@ namespace config {
return ret; return ret;
} }
std::vector<std::string_view> & std::vector<std::string_view> &get_supported_gamepad_options() {
get_supported_gamepad_options() {
const auto options = platf::supported_gamepads(nullptr); const auto options = platf::supported_gamepads(nullptr);
static std::vector<std::string_view> opts {}; static std::vector<std::string_view> opts {};
opts.reserve(options.size()); opts.reserve(options.size());
@@ -1077,8 +1074,7 @@ namespace config {
return opts; return opts;
} }
void void apply_config(std::unordered_map<std::string, std::string> &&vars) {
apply_config(std::unordered_map<std::string, std::string> &&vars) {
if (!fs::exists(stream.file_apps.c_str())) { if (!fs::exists(stream.file_apps.c_str())) {
fs::copy_file(SUNSHINE_ASSETS_DIR "/apps.json", stream.file_apps); fs::copy_file(SUNSHINE_ASSETS_DIR "/apps.json", stream.file_apps);
} }
@@ -1095,8 +1091,8 @@ namespace config {
bool_f(vars, "limit_framerate", video.limit_framerate); bool_f(vars, "limit_framerate", video.limit_framerate);
bool_f(vars, "double_refreshrate", video.double_refreshrate); bool_f(vars, "double_refreshrate", video.double_refreshrate);
int_f(vars, "qp", video.qp); int_f(vars, "qp", video.qp);
int_between_f(vars, "hevc_mode", video.hevc_mode, { 0, 3 }); int_between_f(vars, "hevc_mode", video.hevc_mode, {0, 3});
int_between_f(vars, "av1_mode", video.av1_mode, { 0, 3 }); int_between_f(vars, "av1_mode", video.av1_mode, {0, 3});
int_f(vars, "min_threads", video.min_threads); int_f(vars, "min_threads", video.min_threads);
string_f(vars, "sw_preset", video.sw.sw_preset); string_f(vars, "sw_preset", video.sw.sw_preset);
if (!video.sw.sw_preset.empty()) { if (!video.sw.sw_preset.empty()) {
@@ -1104,8 +1100,8 @@ namespace config {
} }
string_f(vars, "sw_tune", video.sw.sw_tune); string_f(vars, "sw_tune", video.sw.sw_tune);
int_between_f(vars, "nvenc_preset", video.nv.quality_preset, { 1, 7 }); int_between_f(vars, "nvenc_preset", video.nv.quality_preset, {1, 7});
int_between_f(vars, "nvenc_vbv_increase", video.nv.vbv_percentage_increase, { 0, 400 }); int_between_f(vars, "nvenc_vbv_increase", video.nv.vbv_percentage_increase, {0, 400});
bool_f(vars, "nvenc_spatial_aq", video.nv.adaptive_quantization); bool_f(vars, "nvenc_spatial_aq", video.nv.adaptive_quantization);
generic_f(vars, "nvenc_twopass", video.nv.two_pass, nv::twopass_from_view); generic_f(vars, "nvenc_twopass", video.nv.two_pass, nv::twopass_from_view);
bool_f(vars, "nvenc_h264_cavlc", video.nv.h264_cavlc); bool_f(vars, "nvenc_h264_cavlc", video.nv.h264_cavlc);
@@ -1177,15 +1173,16 @@ namespace config {
generic_f(vars, "dd_hdr_option", video.dd.hdr_option, dd::hdr_option_from_view); generic_f(vars, "dd_hdr_option", video.dd.hdr_option, dd::hdr_option_from_view);
{ {
int value = -1; int value = -1;
int_between_f(vars, "dd_config_revert_delay", value, { 0, std::numeric_limits<int>::max() }); int_between_f(vars, "dd_config_revert_delay", value, {0, std::numeric_limits<int>::max()});
if (value >= 0) { if (value >= 0) {
video.dd.config_revert_delay = std::chrono::milliseconds { value }; video.dd.config_revert_delay = std::chrono::milliseconds {value};
} }
} }
bool_f(vars, "dd_config_revert_on_disconnect", video.dd.config_revert_on_disconnect);
generic_f(vars, "dd_mode_remapping", video.dd.mode_remapping, dd::mode_remapping_from_view); generic_f(vars, "dd_mode_remapping", video.dd.mode_remapping, dd::mode_remapping_from_view);
bool_f(vars, "dd_wa_hdr_toggle", video.dd.wa.hdr_toggle); bool_f(vars, "dd_wa_hdr_toggle", video.dd.wa.hdr_toggle);
int_between_f(vars, "min_fps_factor", video.min_fps_factor, { 1, 3 }); int_between_f(vars, "min_fps_factor", video.min_fps_factor, {1, 3});
string_f(vars, "fallback_mode", video.fallback_mode); string_f(vars, "fallback_mode", video.fallback_mode);
path_f(vars, "pkey", nvhttp.pkey); path_f(vars, "pkey", nvhttp.pkey);
@@ -1208,19 +1205,19 @@ namespace config {
bool_f(vars, "keep_sink_default", audio.keep_default); bool_f(vars, "keep_sink_default", audio.keep_default);
bool_f(vars, "auto_capture_sink", audio.auto_capture); bool_f(vars, "auto_capture_sink", audio.auto_capture);
string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_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});
int to = -1; int to = -1;
int_between_f(vars, "ping_timeout", to, { -1, std::numeric_limits<int>::max() }); int_between_f(vars, "ping_timeout", to, {-1, std::numeric_limits<int>::max()});
if (to != -1) { if (to != -1) {
stream.ping_timeout = std::chrono::milliseconds(to); stream.ping_timeout = std::chrono::milliseconds(to);
} }
int_between_f(vars, "lan_encryption_mode", stream.lan_encryption_mode, { 0, 2 }); int_between_f(vars, "lan_encryption_mode", stream.lan_encryption_mode, {0, 2});
int_between_f(vars, "wan_encryption_mode", stream.wan_encryption_mode, { 0, 2 }); int_between_f(vars, "wan_encryption_mode", stream.wan_encryption_mode, {0, 2});
path_f(vars, "file_apps", stream.file_apps); path_f(vars, "file_apps", stream.file_apps);
int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 255 }); int_between_f(vars, "fec_percentage", stream.fec_percentage, {1, 255});
map_int_int_f(vars, "keybindings"s, input.keybindings); map_int_int_f(vars, "keybindings"s, input.keybindings);
@@ -1237,20 +1234,20 @@ namespace config {
int_f(vars, "back_button_timeout", to); int_f(vars, "back_button_timeout", to);
if (to > std::numeric_limits<int>::min()) { if (to > std::numeric_limits<int>::min()) {
input.back_button_timeout = std::chrono::milliseconds { to }; input.back_button_timeout = std::chrono::milliseconds {to};
} }
double repeat_frequency { 0 }; double repeat_frequency {0};
double_between_f(vars, "key_repeat_frequency", repeat_frequency, { 0, std::numeric_limits<double>::max() }); double_between_f(vars, "key_repeat_frequency", repeat_frequency, {0, std::numeric_limits<double>::max()});
if (repeat_frequency > 0) { if (repeat_frequency > 0) {
config::input.key_repeat_period = std::chrono::duration<double> { 1 / repeat_frequency }; config::input.key_repeat_period = std::chrono::duration<double> {1 / repeat_frequency};
} }
to = -1; to = -1;
int_f(vars, "key_repeat_delay", to); int_f(vars, "key_repeat_delay", to);
if (to >= 0) { if (to >= 0) {
input.key_repeat_delay = std::chrono::milliseconds { to }; input.key_repeat_delay = std::chrono::milliseconds {to};
} }
string_restricted_f(vars, "gamepad"s, input.gamepad, get_supported_gamepad_options()); string_restricted_f(vars, "gamepad"s, input.gamepad, get_supported_gamepad_options());
@@ -1273,10 +1270,10 @@ namespace config {
bool_f(vars, "notify_pre_releases", sunshine.notify_pre_releases); bool_f(vars, "notify_pre_releases", sunshine.notify_pre_releases);
int port = sunshine.port; int port = sunshine.port;
int_between_f(vars, "port"s, port, { 1024 + nvhttp::PORT_HTTPS, 65535 - rtsp_stream::RTSP_SETUP_PORT }); int_between_f(vars, "port"s, port, {1024 + nvhttp::PORT_HTTPS, 65535 - rtsp_stream::RTSP_SETUP_PORT});
sunshine.port = (std::uint16_t) port; sunshine.port = (std::uint16_t) port;
string_restricted_f(vars, "address_family", sunshine.address_family, { "ipv4"sv, "both"sv }); string_restricted_f(vars, "address_family", sunshine.address_family, {"ipv4"sv, "both"sv});
bool upnp = false; bool upnp = false;
bool_f(vars, "upnp"s, upnp); bool_f(vars, "upnp"s, upnp);
@@ -1312,26 +1309,19 @@ namespace config {
if (!log_level_string.empty()) { if (!log_level_string.empty()) {
if (log_level_string == "verbose"sv) { if (log_level_string == "verbose"sv) {
sunshine.min_log_level = 0; sunshine.min_log_level = 0;
} } else if (log_level_string == "debug"sv) {
else if (log_level_string == "debug"sv) {
sunshine.min_log_level = 1; sunshine.min_log_level = 1;
} } else if (log_level_string == "info"sv) {
else if (log_level_string == "info"sv) {
sunshine.min_log_level = 2; sunshine.min_log_level = 2;
} } else if (log_level_string == "warning"sv) {
else if (log_level_string == "warning"sv) {
sunshine.min_log_level = 3; sunshine.min_log_level = 3;
} } else if (log_level_string == "error"sv) {
else if (log_level_string == "error"sv) {
sunshine.min_log_level = 4; sunshine.min_log_level = 4;
} } else if (log_level_string == "fatal"sv) {
else if (log_level_string == "fatal"sv) {
sunshine.min_log_level = 5; sunshine.min_log_level = 5;
} } else if (log_level_string == "none"sv) {
else if (log_level_string == "none"sv) {
sunshine.min_log_level = 6; sunshine.min_log_level = 6;
} } else {
else {
// accept digit directly // accept digit directly
auto val = log_level_string[0]; auto val = log_level_string[0];
if (val >= '0' && val < '7') { if (val >= '0' && val < '7') {
@@ -1357,8 +1347,7 @@ namespace config {
::video::active_av1_mode = video.av1_mode; ::video::active_av1_mode = video.av1_mode;
} }
int int parse(int argc, char *argv[]) {
parse(int argc, char *argv[]) {
std::unordered_map<std::string, std::string> cmd_vars; std::unordered_map<std::string, std::string> cmd_vars;
#ifdef _WIN32 #ifdef _WIN32
bool shortcut_launch = false; bool shortcut_launch = false;
@@ -1375,8 +1364,7 @@ namespace config {
#ifdef _WIN32 #ifdef _WIN32
else if (line == "--shortcut"sv) { else if (line == "--shortcut"sv) {
shortcut_launch = true; shortcut_launch = true;
} } else if (line == "--shortcut-admin"sv) {
else if (line == "--shortcut-admin"sv) {
service_admin_launch = true; service_admin_launch = true;
} }
#endif #endif
@@ -1392,15 +1380,13 @@ namespace config {
logging::print_help(*argv); logging::print_help(*argv);
return -1; return -1;
} }
} } else {
else {
auto line_end = line + strlen(line); auto line_end = line + strlen(line);
auto pos = std::find(line, line_end, '='); auto pos = std::find(line, line_end, '=');
if (pos == line_end) { if (pos == line_end) {
sunshine.config_file = line; sunshine.config_file = line;
} } else {
else {
TUPLE_EL(var, 1, parse_option(line, line_end)); TUPLE_EL(var, 1, parse_option(line, line_end));
if (!var) { if (!var) {
logging::print_help(*argv); logging::print_help(*argv);
@@ -1426,7 +1412,7 @@ namespace config {
// Create empty config file if it does not exist // Create empty config file if it does not exist
if (!fs::exists(sunshine.config_file)) { if (!fs::exists(sunshine.config_file)) {
auto cfg_file = std::ofstream { sunshine.config_file }; auto cfg_file = std::ofstream {sunshine.config_file};
#ifdef _WIN32 #ifdef _WIN32
cfg_file << "server_cmd = [{\"name\":\"Bubbles\",\"cmd\":\"bubbles.scr\",\"elevated\":false}]\n"; cfg_file << "server_cmd = [{\"name\":\"Bubbles\",\"cmd\":\"bubbles.scr\",\"elevated\":false}]\n";
#endif #endif
@@ -1444,11 +1430,9 @@ namespace config {
// the path is incorrect or inaccessible. // the path is incorrect or inaccessible.
apply_config(std::move(vars)); apply_config(std::move(vars));
config_loaded = true; config_loaded = true;
} } catch (const std::filesystem::filesystem_error &err) {
catch (const std::filesystem::filesystem_error &err) {
BOOST_LOG(fatal) << "Failed to apply config: "sv << err.what(); BOOST_LOG(fatal) << "Failed to apply config: "sv << err.what();
} } catch (const boost::filesystem::filesystem_error &err) {
catch (const boost::filesystem::filesystem_error &err) {
BOOST_LOG(fatal) << "Failed to apply config: "sv << err.what(); BOOST_LOG(fatal) << "Failed to apply config: "sv << err.what();
} }
@@ -1478,7 +1462,7 @@ namespace config {
// Always return 1 to ensure Sunshine doesn't start normally // Always return 1 to ensure Sunshine doesn't start normally
return 1; return 1;
} }
else if (shortcut_launch) { if (shortcut_launch) {
if (!service_ctrl::is_service_running()) { if (!service_ctrl::is_service_running()) {
// If the service isn't running, relaunch ourselves as admin to start it // If the service isn't running, relaunch ourselves as admin to start it
WCHAR executable[MAX_PATH]; WCHAR executable[MAX_PATH];

View File

@@ -4,6 +4,7 @@
*/ */
#pragma once #pragma once
// standard includes
#include <bitset> #include <bitset>
#include <chrono> #include <chrono>
#include <optional> #include <optional>
@@ -11,6 +12,7 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
// local includes
#include "nvenc/nvenc_config.h" #include "nvenc/nvenc_config.h"
namespace config { namespace config {
@@ -25,6 +27,7 @@ namespace config {
int av1_mode; int av1_mode;
int min_threads; // Minimum number of threads/slices for CPU encoding int min_threads; // Minimum number of threads/slices for CPU encoding
struct { struct {
std::string sw_preset; std::string sw_preset;
std::string sw_tune; std::string sw_tune;
@@ -132,6 +135,7 @@ namespace config {
std::string manual_refresh_rate; ///< Manual refresh rate in case `refresh_rate_option == refresh_rate_option_e::manual`. std::string manual_refresh_rate; ///< Manual refresh rate in case `refresh_rate_option == refresh_rate_option_e::manual`.
hdr_option_e hdr_option; hdr_option_e hdr_option;
std::chrono::milliseconds config_revert_delay; ///< Time to wait until settings are reverted (after stream ends/app exists). std::chrono::milliseconds config_revert_delay; ///< Time to wait until settings are reverted (after stream ends/app exists).
bool config_revert_on_disconnect; ///< Specify whether to revert display configuration on client disconnect.
mode_remapping_t mode_remapping; mode_remapping_t mode_remapping;
workarounds_t wa; workarounds_t wa;
} dd; } dd;
@@ -212,13 +216,20 @@ namespace config {
CONST_PIN, ///< Use "universal" pin CONST_PIN, ///< Use "universal" pin
FLAG_SIZE ///< Number of flags FLAG_SIZE ///< Number of flags
}; };
} } // namespace flag
struct prep_cmd_t { struct prep_cmd_t {
prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd, bool elevated): 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(elevated) {} do_cmd(std::move(do_cmd)),
explicit prep_cmd_t(std::string &&do_cmd, bool elevated): undo_cmd(std::move(undo_cmd)),
do_cmd(std::move(do_cmd)), elevated(elevated) {} 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 do_cmd;
std::string undo_cmd; std::string undo_cmd;
bool elevated; bool elevated;
@@ -226,7 +237,10 @@ namespace config {
struct server_cmd_t { struct server_cmd_t {
server_cmd_t(std::string &&cmd_name, std::string &&cmd_val, bool &&elevated): server_cmd_t(std::string &&cmd_name, std::string &&cmd_val, bool &&elevated):
cmd_name(std::move(cmd_name)), cmd_val(std::move(cmd_val)), elevated(std::move(elevated)) {} cmd_name(std::move(cmd_name)),
cmd_val(std::move(cmd_val)),
elevated(std::move(elevated)) {
}
std::string cmd_name; std::string cmd_name;
std::string cmd_val; std::string cmd_val;
bool elevated; bool elevated;
@@ -268,8 +282,6 @@ namespace config {
extern input_t input; extern input_t input;
extern sunshine_t sunshine; extern sunshine_t sunshine;
int int parse(int argc, char *argv[]);
parse(int argc, char *argv[]); std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content);
std::unordered_map<std::string, std::string>
parse_config(const std::string_view &file_content);
} // namespace config } // namespace config

File diff suppressed because it is too large Load Diff

View File

@@ -4,10 +4,12 @@
*/ */
#pragma once #pragma once
// standard includes
#include <functional> #include <functional>
#include <chrono> #include <chrono>
#include <string> #include <string>
// local includes
#include "thread_safe.h" #include "thread_safe.h"
#define WEB_DIR SUNSHINE_ASSETS_DIR "/web/" #define WEB_DIR SUNSHINE_ASSETS_DIR "/web/"
@@ -17,25 +19,24 @@ using namespace std::chrono_literals;
namespace confighttp { namespace confighttp {
constexpr auto PORT_HTTPS = 1; constexpr auto PORT_HTTPS = 1;
constexpr auto SESSION_EXPIRE_DURATION = 24h * 15; constexpr auto SESSION_EXPIRE_DURATION = 24h * 15;
void void start();
start();
} // namespace confighttp } // namespace confighttp
// mime types map // mime types map
const std::map<std::string, std::string> mime_types = { const std::map<std::string, std::string> mime_types = {
{ "css", "text/css" }, {"css", "text/css"},
{ "gif", "image/gif" }, {"gif", "image/gif"},
{ "htm", "text/html" }, {"htm", "text/html"},
{ "html", "text/html" }, {"html", "text/html"},
{ "ico", "image/x-icon" }, {"ico", "image/x-icon"},
{ "jpeg", "image/jpeg" }, {"jpeg", "image/jpeg"},
{ "jpg", "image/jpeg" }, {"jpg", "image/jpeg"},
{ "js", "application/javascript" }, {"js", "application/javascript"},
{ "json", "application/json" }, {"json", "application/json"},
{ "png", "image/png" }, {"png", "image/png"},
{ "svg", "image/svg+xml" }, {"svg", "image/svg+xml"},
{ "ttf", "font/ttf" }, {"ttf", "font/ttf"},
{ "txt", "text/plain" }, {"txt", "text/plain"},
{ "woff2", "font/woff2" }, {"woff2", "font/woff2"},
{ "xml", "text/xml" }, {"xml", "text/xml"},
}; };

View File

@@ -2,29 +2,31 @@
* @file src/crypto.cpp * @file src/crypto.cpp
* @brief Definitions for cryptography functions. * @brief Definitions for cryptography functions.
*/ */
#include "crypto.h" // lib includes
#include <openssl/pem.h> #include <openssl/pem.h>
#include <openssl/rsa.h> #include <openssl/rsa.h>
// local includes
#include "crypto.h"
namespace crypto { namespace crypto {
using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>; using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>;
cert_chain_t::cert_chain_t(): cert_chain_t::cert_chain_t():
_certs {}, _cert_ctx { X509_STORE_CTX_new() } {} _certs {}, _cert_ctx { X509_STORE_CTX_new() } {
void }
cert_chain_t::add(p_named_cert_t& named_cert_p) { void cert_chain_t::add(p_named_cert_t& named_cert_p) {
x509_store_t x509_store { X509_STORE_new() }; x509_store_t x509_store { X509_STORE_new() };
X509_STORE_add_cert(x509_store.get(), x509(named_cert_p->cert).get()); X509_STORE_add_cert(x509_store.get(), x509(named_cert_p->cert).get());
_certs.emplace_back(std::make_pair(named_cert_p, std::move(x509_store))); _certs.emplace_back(std::make_pair(named_cert_p, std::move(x509_store)));
} }
void
cert_chain_t::clear() { void cert_chain_t::clear() {
_certs.clear(); _certs.clear();
} }
static int static int openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
int err_code = X509_STORE_CTX_get_error(ctx); int err_code = X509_STORE_CTX_get_error(ctx);
switch (err_code) { switch (err_code) {
@@ -52,8 +54,7 @@ namespace crypto {
* @param cert The certificate to verify. * @param cert The certificate to verify.
* @return nullptr if the certificate is valid, otherwise an error string. * @return nullptr if the certificate is valid, otherwise an error string.
*/ */
const char * const char * cert_chain_t::verify(x509_t::element_type *cert, p_named_cert_t& named_cert_out) {
cert_chain_t::verify(x509_t::element_type *cert, p_named_cert_t& named_cert_out) {
int err_code = 0; int err_code = 0;
for (auto &[named_cert_p, x509_store] : _certs) { for (auto &[named_cert_p, x509_store] : _certs) {
auto fg = util::fail_guard([this]() { auto fg = util::fail_guard([this]() {
@@ -87,8 +88,7 @@ namespace crypto {
namespace cipher { namespace cipher {
static int static int init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new()); ctx.reset(EVP_CIPHER_CTX_new());
if (!ctx) { if (!ctx) {
@@ -111,8 +111,7 @@ namespace crypto {
return 0; return 0;
} }
static int static int init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new()); ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB // Gen 7 servers use 128-bit AES ECB
@@ -132,8 +131,7 @@ namespace crypto {
return 0; return 0;
} }
static int static int init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new()); ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB // Gen 7 servers use 128-bit AES ECB
@@ -146,8 +144,7 @@ namespace crypto {
return 0; return 0;
} }
int int gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
if (!decrypt_ctx && init_decrypt_gcm(decrypt_ctx, &key, iv, padding)) { if (!decrypt_ctx && init_decrypt_gcm(decrypt_ctx, &key, iv, padding)) {
return -1; return -1;
} }
@@ -186,8 +183,7 @@ namespace crypto {
* The function handles the creation and initialization of the encryption context, and manages the encryption process. * The function handles the creation and initialization of the encryption context, and manages the encryption process.
* The resulting ciphertext and the GCM tag are written into the tagged_cipher buffer. * The resulting ciphertext and the GCM tag are written into the tagged_cipher buffer.
*/ */
int int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv) {
gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv) {
if (!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, iv, padding)) { if (!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, iv, padding)) {
return -1; return -1;
} }
@@ -217,14 +213,12 @@ namespace crypto {
return update_outlen + final_outlen; return update_outlen + final_outlen;
} }
int int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
// This overload handles the common case of [GCM tag][cipher text] buffer layout // This overload handles the common case of [GCM tag][cipher text] buffer layout
return encrypt(plaintext, tagged_cipher, tagged_cipher + tag_size, iv); return encrypt(plaintext, tagged_cipher, tagged_cipher + tag_size, iv);
} }
int int ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
auto fg = util::fail_guard([this]() { auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(decrypt_ctx.get()); EVP_CIPHER_CTX_reset(decrypt_ctx.get());
}); });
@@ -251,8 +245,7 @@ namespace crypto {
return 0; return 0;
} }
int int ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
auto fg = util::fail_guard([this]() { auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(encrypt_ctx.get()); EVP_CIPHER_CTX_reset(encrypt_ctx.get());
}); });
@@ -285,8 +278,7 @@ namespace crypto {
* The function handles the creation and initialization of the encryption context, and manages the encryption process. * The function handles the creation and initialization of the encryption context, and manages the encryption process.
* The resulting ciphertext is written into the cipher buffer. * The resulting ciphertext is written into the cipher buffer.
*/ */
int int cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
if (!encrypt_ctx && init_encrypt_cbc(encrypt_ctx, &key, iv, padding)) { if (!encrypt_ctx && init_encrypt_cbc(encrypt_ctx, &key, iv, padding)) {
return -1; return -1;
} }
@@ -312,18 +304,20 @@ namespace crypto {
} }
ecb_t::ecb_t(const aes_t &key, bool padding): ecb_t::ecb_t(const aes_t &key, bool padding):
cipher_t { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding } {} cipher_t {EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding} {
}
cbc_t::cbc_t(const aes_t &key, bool padding): cbc_t::cbc_t(const aes_t &key, bool padding):
cipher_t { nullptr, nullptr, key, padding } {} cipher_t {nullptr, nullptr, key, padding} {
}
gcm_t::gcm_t(const crypto::aes_t &key, bool padding): gcm_t::gcm_t(const crypto::aes_t &key, bool padding):
cipher_t { nullptr, nullptr, key, padding } {} cipher_t {nullptr, nullptr, key, padding} {
}
} // namespace cipher } // namespace cipher
aes_t aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
aes_t key(16); aes_t key(16);
std::string salt_pin; std::string salt_pin;
@@ -339,16 +333,14 @@ namespace crypto {
return key; return key;
} }
sha256_t sha256_t hash(const std::string_view &plaintext) {
hash(const std::string_view &plaintext) {
sha256_t hsh; sha256_t hsh;
EVP_Digest(plaintext.data(), plaintext.size(), hsh.data(), nullptr, EVP_sha256(), nullptr); EVP_Digest(plaintext.data(), plaintext.size(), hsh.data(), nullptr, EVP_sha256(), nullptr);
return hsh; return hsh;
} }
x509_t x509_t x509(const std::string_view &x) {
x509(const std::string_view &x) { bio_t io {BIO_new(BIO_s_mem())};
bio_t io { BIO_new(BIO_s_mem()) };
BIO_write(io.get(), x.data(), x.size()); BIO_write(io.get(), x.data(), x.size());
@@ -358,9 +350,8 @@ namespace crypto {
return p; return p;
} }
pkey_t pkey_t pkey(const std::string_view &k) {
pkey(const std::string_view &k) { bio_t io {BIO_new(BIO_s_mem())};
bio_t io { BIO_new(BIO_s_mem()) };
BIO_write(io.get(), k.data(), k.size()); BIO_write(io.get(), k.data(), k.size());
@@ -370,40 +361,36 @@ namespace crypto {
return p; return p;
} }
std::string std::string pem(x509_t &x509) {
pem(x509_t &x509) { bio_t bio {BIO_new(BIO_s_mem())};
bio_t bio { BIO_new(BIO_s_mem()) };
PEM_write_bio_X509(bio.get(), x509.get()); PEM_write_bio_X509(bio.get(), x509.get());
BUF_MEM *mem_ptr; BUF_MEM *mem_ptr;
BIO_get_mem_ptr(bio.get(), &mem_ptr); BIO_get_mem_ptr(bio.get(), &mem_ptr);
return { mem_ptr->data, mem_ptr->length }; return {mem_ptr->data, mem_ptr->length};
} }
std::string std::string pem(pkey_t &pkey) {
pem(pkey_t &pkey) { bio_t bio {BIO_new(BIO_s_mem())};
bio_t bio { BIO_new(BIO_s_mem()) };
PEM_write_bio_PrivateKey(bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr); PEM_write_bio_PrivateKey(bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
BUF_MEM *mem_ptr; BUF_MEM *mem_ptr;
BIO_get_mem_ptr(bio.get(), &mem_ptr); BIO_get_mem_ptr(bio.get(), &mem_ptr);
return { mem_ptr->data, mem_ptr->length }; return {mem_ptr->data, mem_ptr->length};
} }
std::string_view std::string_view signature(const x509_t &x) {
signature(const x509_t &x) {
// X509_ALGOR *_ = nullptr; // X509_ALGOR *_ = nullptr;
const ASN1_BIT_STRING *asn1 = nullptr; const ASN1_BIT_STRING *asn1 = nullptr;
X509_get0_signature(&asn1, nullptr, x.get()); X509_get0_signature(&asn1, nullptr, x.get());
return { (const char *) asn1->data, (std::size_t) asn1->length }; return {(const char *) asn1->data, (std::size_t) asn1->length};
} }
std::string std::string rand(std::size_t bytes) {
rand(std::size_t bytes) {
std::string r; std::string r;
r.resize(bytes); r.resize(bytes);
@@ -412,9 +399,8 @@ namespace crypto {
return r; return r;
} }
std::vector<uint8_t> std::vector<uint8_t> sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) { md_ctx_t ctx {EVP_MD_CTX_create()};
md_ctx_t ctx { EVP_MD_CTX_create() };
if (EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, (EVP_PKEY *) pkey.get()) != 1) { if (EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, (EVP_PKEY *) pkey.get()) != 1) {
return {}; return {};
@@ -437,10 +423,9 @@ namespace crypto {
return digest; return digest;
} }
creds_t creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
gen_creds(const std::string_view &cn, std::uint32_t key_bits) { x509_t x509 {X509_new()};
x509_t x509 { X509_new() }; pkey_ctx_t ctx {EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)};
pkey_ctx_t ctx { EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr) };
pkey_t pkey; pkey_t pkey;
EVP_PKEY_keygen_init(ctx.get()); EVP_PKEY_keygen_init(ctx.get());
@@ -450,7 +435,7 @@ namespace crypto {
X509_set_version(x509.get(), 2); X509_set_version(x509.get(), 2);
// Generate a real serial number to avoid SEC_ERROR_REUSED_ISSUER_AND_SERIAL with Firefox // Generate a real serial number to avoid SEC_ERROR_REUSED_ISSUER_AND_SERIAL with Firefox
bignum_t serial { BN_new() }; bignum_t serial {BN_new()};
BN_rand(serial.get(), 159, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); // 159 bits to fit in 20 bytes in DER format BN_rand(serial.get(), 159, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); // 159 bits to fit in 20 bytes in DER format
BN_set_negative(serial.get(), 0); // Serial numbers must be positive BN_set_negative(serial.get(), 0); // Serial numbers must be positive
BN_to_ASN1_INTEGER(serial.get(), X509_get_serialNumber(x509.get())); BN_to_ASN1_INTEGER(serial.get(), X509_get_serialNumber(x509.get()));
@@ -460,8 +445,8 @@ namespace crypto {
X509_gmtime_adj(X509_get_notBefore(x509.get()), 0); X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
X509_gmtime_adj(X509_get_notAfter(x509.get()), 20 * year); X509_gmtime_adj(X509_get_notAfter(x509.get()), 20 * year);
#else #else
asn1_string_t not_before { ASN1_STRING_dup(X509_get0_notBefore(x509.get())) }; asn1_string_t not_before {ASN1_STRING_dup(X509_get0_notBefore(x509.get()))};
asn1_string_t not_after { ASN1_STRING_dup(X509_get0_notAfter(x509.get())) }; asn1_string_t not_after {ASN1_STRING_dup(X509_get0_notAfter(x509.get()))};
X509_gmtime_adj(not_before.get(), 0); X509_gmtime_adj(not_before.get(), 0);
X509_gmtime_adj(not_after.get(), 20 * year); X509_gmtime_adj(not_after.get(), 20 * year);
@@ -473,26 +458,22 @@ namespace crypto {
X509_set_pubkey(x509.get(), pkey.get()); X509_set_pubkey(x509.get(), pkey.get());
auto name = X509_get_subject_name(x509.get()); auto name = X509_get_subject_name(x509.get());
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const std::uint8_t *) cn.data(), cn.size(), -1, 0);
(const std::uint8_t *) cn.data(), cn.size(),
-1, 0);
X509_set_issuer_name(x509.get(), name); X509_set_issuer_name(x509.get(), name);
X509_sign(x509.get(), pkey.get(), EVP_sha256()); X509_sign(x509.get(), pkey.get(), EVP_sha256());
return { pem(x509), pem(pkey) }; return {pem(x509), pem(pkey)};
} }
std::vector<uint8_t> std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data) {
sign256(const pkey_t &pkey, const std::string_view &data) {
return sign(pkey, data, EVP_sha256()); return sign(pkey, data, EVP_sha256());
} }
bool bool verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
auto pkey = X509_get0_pubkey(x509.get()); auto pkey = X509_get0_pubkey(x509.get());
md_ctx_t ctx { EVP_MD_CTX_create() }; md_ctx_t ctx {EVP_MD_CTX_create()};
if (EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) { if (EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
return false; return false;
@@ -509,18 +490,15 @@ namespace crypto {
return true; return true;
} }
bool bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
return verify(x509, data, signature, EVP_sha256()); return verify(x509, data, signature, EVP_sha256());
} }
void void md_ctx_destroy(EVP_MD_CTX *ctx) {
md_ctx_destroy(EVP_MD_CTX *ctx) {
EVP_MD_CTX_destroy(ctx); EVP_MD_CTX_destroy(ctx);
} }
std::string std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
auto value = rand(bytes); auto value = rand(bytes);
for (std::size_t i = 0; i != value.size(); ++i) { for (std::size_t i = 0; i != value.size(); ++i) {

View File

@@ -4,15 +4,18 @@
*/ */
#pragma once #pragma once
// standard includes
#include <array> #include <array>
// lib includes
#include <list> #include <list>
#include <openssl/evp.h> #include <openssl/evp.h>
#include <openssl/rand.h> #include <openssl/rand.h>
#include <openssl/sha.h> #include <openssl/sha.h>
#include <openssl/x509.h> #include <openssl/x509.h>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
// local includes
#include "utility.h" #include "utility.h"
namespace crypto { namespace crypto {
@@ -21,8 +24,7 @@ namespace crypto {
std::string pkey; std::string pkey;
}; };
void void md_ctx_destroy(EVP_MD_CTX *);
md_ctx_destroy(EVP_MD_CTX *);
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>; using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
@@ -110,50 +112,33 @@ namespace crypto {
* @param plaintext * @param plaintext
* @return The SHA-256 hash of the plaintext. * @return The SHA-256 hash of the plaintext.
*/ */
sha256_t sha256_t hash(const std::string_view &plaintext);
hash(const std::string_view &plaintext);
aes_t aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin); x509_t x509(const std::string_view &x);
pkey_t pkey(const std::string_view &k);
std::string pem(x509_t &x509);
std::string pem(pkey_t &pkey);
x509_t std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data);
x509(const std::string_view &x); bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
pkey_t
pkey(const std::string_view &k);
std::string
pem(x509_t &x509);
std::string
pem(pkey_t &pkey);
std::vector<uint8_t> creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits);
sign256(const pkey_t &pkey, const std::string_view &data);
bool
verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
creds_t std::string_view signature(const x509_t &x);
gen_creds(const std::string_view &cn, std::uint32_t key_bits);
std::string_view std::string rand(std::size_t bytes);
signature(const x509_t &x); std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet = std::string_view {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-"});
std::string
rand(std::size_t bytes);
std::string
rand_alphabet(std::size_t bytes,
const std::string_view &alphabet = std::string_view { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-" });
class cert_chain_t { class cert_chain_t {
public: public:
KITTY_DECL_CONSTR(cert_chain_t) KITTY_DECL_CONSTR(cert_chain_t)
void void add(p_named_cert_t& named_cert_p);
add(p_named_cert_t& named_cert_p);
void void clear();
clear();
const char * const char *verify(x509_t::element_type *cert, p_named_cert_t& named_cert_out);
verify(x509_t::element_type *cert, p_named_cert_t& named_cert_out);
private: private:
std::vector<std::pair<p_named_cert_t, x509_store_t>> _certs; std::vector<std::pair<p_named_cert_t, x509_store_t>> _certs;
@@ -162,8 +147,8 @@ namespace crypto {
namespace cipher { namespace cipher {
constexpr std::size_t tag_size = 16; constexpr std::size_t tag_size = 16;
constexpr std::size_t
round_to_pkcs7_padded(std::size_t size) { constexpr std::size_t round_to_pkcs7_padded(std::size_t size) {
return ((size + 15) / 16) * 16; return ((size + 15) / 16) * 16;
} }
@@ -181,23 +166,19 @@ namespace crypto {
public: public:
ecb_t() = default; ecb_t() = default;
ecb_t(ecb_t &&) noexcept = default; ecb_t(ecb_t &&) noexcept = default;
ecb_t & ecb_t &operator=(ecb_t &&) noexcept = default;
operator=(ecb_t &&) noexcept = default;
ecb_t(const aes_t &key, bool padding = true); ecb_t(const aes_t &key, bool padding = true);
int int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher); int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
int
decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
}; };
class gcm_t: public cipher_t { class gcm_t: public cipher_t {
public: public:
gcm_t() = default; gcm_t() = default;
gcm_t(gcm_t &&) noexcept = default; gcm_t(gcm_t &&) noexcept = default;
gcm_t & gcm_t &operator=(gcm_t &&) noexcept = default;
operator=(gcm_t &&) noexcept = default;
gcm_t(const crypto::aes_t &key, bool padding = true); gcm_t(const crypto::aes_t &key, bool padding = true);
@@ -209,8 +190,7 @@ namespace crypto {
* @param iv The initialization vector to be used for the encryption. * @param iv The initialization vector to be used for the encryption.
* @return The total length of the ciphertext and GCM tag. Returns -1 in case of an error. * @return The total length of the ciphertext and GCM tag. Returns -1 in case of an error.
*/ */
int int encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv);
encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv);
/** /**
* @brief Encrypts the plaintext using AES GCM mode. * @brief Encrypts the plaintext using AES GCM mode.
@@ -220,19 +200,16 @@ namespace crypto {
* @param iv The initialization vector to be used for the encryption. * @param iv The initialization vector to be used for the encryption.
* @return The total length of the ciphertext and GCM tag written into tagged_cipher. Returns -1 in case of an error. * @return The total length of the ciphertext and GCM tag written into tagged_cipher. Returns -1 in case of an error.
*/ */
int int encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
int int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
}; };
class cbc_t: public cipher_t { class cbc_t: public cipher_t {
public: public:
cbc_t() = default; cbc_t() = default;
cbc_t(cbc_t &&) noexcept = default; cbc_t(cbc_t &&) noexcept = default;
cbc_t & cbc_t &operator=(cbc_t &&) noexcept = default;
operator=(cbc_t &&) noexcept = default;
cbc_t(const crypto::aes_t &key, bool padding = true); cbc_t(const crypto::aes_t &key, bool padding = true);
@@ -244,8 +221,7 @@ namespace crypto {
* @param iv The initialization vector to be used for the encryption. * @param iv The initialization vector to be used for the encryption.
* @return The total length of the ciphertext written into cipher. Returns -1 in case of an error. * @return The total length of the ciphertext written into cipher. Returns -1 in case of an error.
*/ */
int int encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
}; };
} // namespace cipher } // namespace cipher
} // namespace crypto } // namespace crypto

View File

@@ -29,15 +29,15 @@
namespace display_device { namespace display_device {
namespace { namespace {
constexpr std::chrono::milliseconds DEFAULT_RETRY_INTERVAL { 5000 }; constexpr std::chrono::milliseconds DEFAULT_RETRY_INTERVAL {5000};
/** /**
* @brief A global for the settings manager interface and other settings whose lifetime is managed by `display_device::init(...)`. * @brief A global for the settings manager interface and other settings whose lifetime is managed by `display_device::init(...)`.
*/ */
struct { struct {
std::mutex mutex {}; std::mutex mutex {};
std::chrono::milliseconds config_revert_delay { 0 }; std::chrono::milliseconds config_revert_delay {0};
std::unique_ptr<RetryScheduler<SettingsManagerInterface>> sm_instance { nullptr }; std::unique_ptr<RetryScheduler<SettingsManagerInterface>> sm_instance {nullptr};
} DD_DATA; } DD_DATA;
/** /**
@@ -49,8 +49,7 @@ namespace display_device {
*/ */
class sunshine_audio_context_t: public AudioContextInterface { class sunshine_audio_context_t: public AudioContextInterface {
public: public:
[[nodiscard]] bool [[nodiscard]] bool capture() override {
capture() override {
return context_scheduler.execute([](auto &audio_context) { return context_scheduler.execute([](auto &audio_context) {
// Explicitly releasing the context first in case it was not release yet so that it can be potentially cleaned up. // Explicitly releasing the context first in case it was not release yet so that it can be potentially cleaned up.
audio_context = boost::none; audio_context = boost::none;
@@ -61,8 +60,7 @@ namespace display_device {
}); });
} }
[[nodiscard]] bool [[nodiscard]] bool isCaptured() const override {
isCaptured() const override {
return context_scheduler.execute([](const auto &audio_context) { return context_scheduler.execute([](const auto &audio_context) {
if (audio_context) { if (audio_context) {
// In case we still have context we need to check whether it was released or not. // In case we still have context we need to check whether it was released or not.
@@ -74,8 +72,7 @@ namespace display_device {
}); });
} }
void void release() override {
release() override {
context_scheduler.schedule([](auto &audio_context, auto &stop_token) { context_scheduler.schedule([](auto &audio_context, auto &stop_token) {
if (audio_context) { if (audio_context) {
audio_context->released = true; audio_context->released = true;
@@ -93,7 +90,7 @@ namespace display_device {
audio_context = boost::none; audio_context = boost::none;
stop_token.requestStop(); stop_token.requestStop();
}, },
SchedulerOptions { .m_sleep_durations = { 2s } }); SchedulerOptions {.m_sleep_durations = {2s}});
} }
private: private:
@@ -102,20 +99,20 @@ namespace display_device {
* @brief A reference to the audio context that will automatically extend the audio session. * @brief A reference to the audio context that will automatically extend the audio session.
* @note It is auto-initialized here for convenience. * @note It is auto-initialized here for convenience.
*/ */
decltype(audio::get_audio_ctx_ref()) audio_ctx_ref { audio::get_audio_ctx_ref() }; decltype(audio::get_audio_ctx_ref()) audio_ctx_ref {audio::get_audio_ctx_ref()};
/** /**
* @brief Will be set to true if the capture was released, but we still have to keep the context around, because the device is not available. * @brief Will be set to true if the capture was released, but we still have to keep the context around, because the device is not available.
*/ */
bool released { false }; bool released {false};
/** /**
* @brief How many times to check if the audio sink is available before giving up. * @brief How many times to check if the audio sink is available before giving up.
*/ */
int retry_counter { 15 }; int retry_counter {15};
}; };
RetryScheduler<boost::optional<audio_context_t>> context_scheduler { std::make_unique<boost::optional<audio_context_t>>(boost::none) }; RetryScheduler<boost::optional<audio_context_t>> context_scheduler {std::make_unique<boost::optional<audio_context_t>>(boost::none)};
}; };
/** /**
@@ -124,9 +121,8 @@ namespace display_device {
* @param value String to be converted * @param value String to be converted
* @return Parsed unsigned integer. * @return Parsed unsigned integer.
*/ */
unsigned int unsigned int stou(const std::string &value) {
stou(const std::string &value) { unsigned long result {std::stoul(value)};
unsigned long result { std::stoul(value) };
if (result > std::numeric_limits<unsigned int>::max()) { if (result > std::numeric_limits<unsigned int>::max()) {
throw std::out_of_range("stou"); throw std::out_of_range("stou");
} }
@@ -151,10 +147,9 @@ namespace display_device {
* } * }
* @examples_end * @examples_end
*/ */
bool bool parse_resolution_string(const std::string &input, std::optional<Resolution> &output) {
parse_resolution_string(const std::string &input, std::optional<Resolution> &output) { const std::string trimmed_input {boost::algorithm::trim_copy(input)};
const std::string trimmed_input { boost::algorithm::trim_copy(input) }; const std::regex resolution_regex {R"(^(\d+)x(\d+)$)"};
const std::regex resolution_regex { R"(^(\d+)x(\d+)$)" };
if (std::smatch match; std::regex_match(trimmed_input, match, resolution_regex)) { if (std::smatch match; std::regex_match(trimmed_input, match, resolution_regex)) {
try { try {
@@ -163,16 +158,13 @@ namespace display_device {
stou(match[2].str()) stou(match[2].str())
}; };
return true; return true;
} } catch (const std::out_of_range &) {
catch (const std::out_of_range &) {
BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << " (number out of range)."; BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << " (number out of range).";
} } catch (const std::exception &err) {
catch (const std::exception &err) {
BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << ":\n" BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << ":\n"
<< err.what(); << err.what();
} }
} } else {
else {
if (trimmed_input.empty()) { if (trimmed_input.empty()) {
output = std::nullopt; output = std::nullopt;
return true; return true;
@@ -203,16 +195,17 @@ namespace display_device {
* } * }
* @examples_end * @examples_end
*/ */
bool bool parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output, const bool allow_decimal_point = true) {
parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output, const bool allow_decimal_point = true) { static const auto is_zero {[](const auto &character) {
static const auto is_zero { [](const auto &character) { return character == '0'; } }; return character == '0';
const std::string trimmed_input { boost::algorithm::trim_copy(input) }; }};
const std::regex refresh_rate_regex { allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)" }; const std::string trimmed_input {boost::algorithm::trim_copy(input)};
const std::regex refresh_rate_regex {allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)"};
if (std::smatch match; std::regex_match(trimmed_input, match, refresh_rate_regex)) { if (std::smatch match; std::regex_match(trimmed_input, match, refresh_rate_regex)) {
try { try {
// Here we are trimming zeros from the string to possibly reduce out of bounds case // Here we are trimming zeros from the string to possibly reduce out of bounds case
std::string trimmed_match_1 { boost::algorithm::trim_left_copy_if(match[1].str(), is_zero) }; std::string trimmed_match_1 {boost::algorithm::trim_left_copy_if(match[1].str(), is_zero)};
if (trimmed_match_1.empty()) { if (trimmed_match_1.empty()) {
trimmed_match_1 = "0"s; // Just in case ALL the string is full of zeros, we want to leave one trimmed_match_1 = "0"s; // Just in case ALL the string is full of zeros, we want to leave one
} }
@@ -230,33 +223,29 @@ namespace display_device {
// denominator = 1000 // denominator = 1000
// We are essentially removing the decimal point here: 59.995 -> 59995 // We are essentially removing the decimal point here: 59.995 -> 59995
const std::string numerator_str { trimmed_match_1 + trimmed_match_2 }; const std::string numerator_str {trimmed_match_1 + trimmed_match_2};
const auto numerator { stou(numerator_str) }; const auto numerator {stou(numerator_str)};
// Here we are counting decimal places and calculating denominator: 10^decimal_places // Here we are counting decimal places and calculating denominator: 10^decimal_places
const auto denominator { static_cast<unsigned int>(std::pow(10, trimmed_match_2.size())) }; const auto denominator {static_cast<unsigned int>(std::pow(10, trimmed_match_2.size()))};
output = Rational { numerator, denominator }; output = Rational {numerator, denominator};
} } else {
else {
// We do not have a decimal point, just a valid number. // We do not have a decimal point, just a valid number.
// For example: // For example:
// 60: // 60:
// numerator = 60 // numerator = 60
// denominator = 1 // denominator = 1
output = Rational { stou(trimmed_match_1), 1 }; output = Rational {stou(trimmed_match_1), 1};
} }
return true; return true;
} } catch (const std::out_of_range &) {
catch (const std::out_of_range &) {
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << " (number out of range)."; BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << " (number out of range).";
} } catch (const std::exception &err) {
catch (const std::exception &err) {
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << ":\n" BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << ":\n"
<< err.what(); << err.what();
} }
} } else {
else {
if (trimmed_input.empty()) { if (trimmed_input.empty()) {
output = std::nullopt; output = std::nullopt;
return true; return true;
@@ -279,8 +268,7 @@ namespace display_device {
* const auto device_prep_option = parse_device_prep_option(video_config); * const auto device_prep_option = parse_device_prep_option(video_config);
* @examples_end * @examples_end
*/ */
std::optional<SingleDisplayConfiguration::DevicePreparation> std::optional<SingleDisplayConfiguration::DevicePreparation> parse_device_prep_option(const config::video_t &video_config) {
parse_device_prep_option(const config::video_t &video_config) {
using enum config::video_t::dd_t::config_option_e; using enum config::video_t::dd_t::config_option_e;
using enum SingleDisplayConfiguration::DevicePreparation; using enum SingleDisplayConfiguration::DevicePreparation;
@@ -315,32 +303,30 @@ namespace display_device {
* const bool success = parse_resolution_option(video_config, *launch_session, config); * const bool success = parse_resolution_option(video_config, *launch_session, config);
* @examples_end * @examples_end
*/ */
bool bool parse_resolution_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
parse_resolution_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
using resolution_option_e = config::video_t::dd_t::resolution_option_e; using resolution_option_e = config::video_t::dd_t::resolution_option_e;
switch (video_config.dd.resolution_option) { switch (video_config.dd.resolution_option) {
case resolution_option_e::automatic: { case resolution_option_e::automatic:
{
if (!session.enable_sops) { if (!session.enable_sops) {
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution automatically, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)"; BOOST_LOG(warning) << R"(Sunshine is configured to change resolution automatically, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
} } else if (session.width >= 0 && session.height >= 0) {
else if (session.width >= 0 && session.height >= 0) {
config.m_resolution = Resolution { config.m_resolution = Resolution {
static_cast<unsigned int>(session.width), static_cast<unsigned int>(session.width),
static_cast<unsigned int>(session.height) static_cast<unsigned int>(session.height)
}; };
} } else {
else {
BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height; BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height;
return false; return false;
} }
break; break;
} }
case resolution_option_e::manual: { case resolution_option_e::manual:
{
if (!session.enable_sops) { if (!session.enable_sops) {
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution manually, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)"; BOOST_LOG(warning) << R"(Sunshine is configured to change resolution manually, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
} } else {
else {
if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) { if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) {
BOOST_LOG(error) << "Failed to parse manual resolution string!"; BOOST_LOG(error) << "Failed to parse manual resolution string!";
return false; return false;
@@ -375,22 +361,22 @@ namespace display_device {
* const bool success = parse_refresh_rate_option(video_config, *launch_session, config); * const bool success = parse_refresh_rate_option(video_config, *launch_session, config);
* @examples_end * @examples_end
*/ */
bool bool parse_refresh_rate_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
parse_refresh_rate_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
using refresh_rate_option_e = config::video_t::dd_t::refresh_rate_option_e; using refresh_rate_option_e = config::video_t::dd_t::refresh_rate_option_e;
switch (video_config.dd.refresh_rate_option) { switch (video_config.dd.refresh_rate_option) {
case refresh_rate_option_e::automatic: { case refresh_rate_option_e::automatic:
{
if (session.fps >= 0) { if (session.fps >= 0) {
config.m_refresh_rate = Rational { static_cast<unsigned int>(session.fps), 1 }; config.m_refresh_rate = Rational {static_cast<unsigned int>(session.fps), 1};
} } else {
else {
BOOST_LOG(error) << "FPS value provided by client session config is invalid: " << session.fps; BOOST_LOG(error) << "FPS value provided by client session config is invalid: " << session.fps;
return false; return false;
} }
break; break;
} }
case refresh_rate_option_e::manual: { case refresh_rate_option_e::manual:
{
if (!parse_refresh_rate_string(video_config.dd.manual_refresh_rate, config.m_refresh_rate)) { if (!parse_refresh_rate_string(video_config.dd.manual_refresh_rate, config.m_refresh_rate)) {
BOOST_LOG(error) << "Failed to parse manual refresh rate string!"; BOOST_LOG(error) << "Failed to parse manual refresh rate string!";
return false; return false;
@@ -422,8 +408,7 @@ namespace display_device {
* const auto hdr_option = parse_hdr_option(video_config, *launch_session); * const auto hdr_option = parse_hdr_option(video_config, *launch_session);
* @examples_end * @examples_end
*/ */
std::optional<HdrState> std::optional<HdrState> parse_hdr_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
parse_hdr_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
using hdr_option_e = config::video_t::dd_t::hdr_option_e; using hdr_option_e = config::video_t::dd_t::hdr_option_e;
switch (video_config.dd.hdr_option) { switch (video_config.dd.hdr_option) {
@@ -450,11 +435,10 @@ namespace display_device {
* @param video_config User's video related configuration. * @param video_config User's video related configuration.
* @returns Enum value if remapping can be performed, null optional if remapping shall be skipped. * @returns Enum value if remapping can be performed, null optional if remapping shall be skipped.
*/ */
std::optional<remapping_type_e> std::optional<remapping_type_e> determine_remapping_type(const config::video_t &video_config) {
determine_remapping_type(const config::video_t &video_config) {
using dd_t = config::video_t::dd_t; using dd_t = config::video_t::dd_t;
const bool auto_resolution { video_config.dd.resolution_option == dd_t::resolution_option_e::automatic }; const bool auto_resolution {video_config.dd.resolution_option == dd_t::resolution_option_e::automatic};
const bool auto_refresh_rate { video_config.dd.refresh_rate_option == dd_t::refresh_rate_option_e::automatic }; const bool auto_refresh_rate {video_config.dd.refresh_rate_option == dd_t::refresh_rate_option_e::automatic};
if (auto_resolution && auto_refresh_rate) { if (auto_resolution && auto_refresh_rate) {
return remapping_type_e::mixed; return remapping_type_e::mixed;
@@ -486,8 +470,7 @@ namespace display_device {
* @param type Remapping type to check. * @param type Remapping type to check.
* @returns True if resolution is to be mapped, false otherwise. * @returns True if resolution is to be mapped, false otherwise.
*/ */
bool bool is_resolution_mapped(const remapping_type_e type) {
is_resolution_mapped(const remapping_type_e type) {
return type == remapping_type_e::resolution_only || type == remapping_type_e::mixed; return type == remapping_type_e::resolution_only || type == remapping_type_e::mixed;
} }
@@ -496,8 +479,7 @@ namespace display_device {
* @param type Remapping type to check. * @param type Remapping type to check.
* @returns True if FPS is to be mapped, false otherwise. * @returns True if FPS is to be mapped, false otherwise.
*/ */
bool bool is_fps_mapped(const remapping_type_e type) {
is_fps_mapped(const remapping_type_e type) {
return type == remapping_type_e::refresh_rate_only || type == remapping_type_e::mixed; return type == remapping_type_e::refresh_rate_only || type == remapping_type_e::mixed;
} }
@@ -507,8 +489,7 @@ namespace display_device {
* @param type Specify which entry fields should be parsed. * @param type Specify which entry fields should be parsed.
* @returns Parsed structure or null optional if a necessary field could not be parsed. * @returns Parsed structure or null optional if a necessary field could not be parsed.
*/ */
std::optional<parsed_remapping_entry_t> std::optional<parsed_remapping_entry_t> parse_remapping_entry(const config::video_t::dd_t::mode_remapping_entry_t &entry, const remapping_type_e type) {
parse_remapping_entry(const config::video_t::dd_t::mode_remapping_entry_t &entry, const remapping_type_e type) {
parsed_remapping_entry_t result {}; parsed_remapping_entry_t result {};
if (is_resolution_mapped(type) && (!parse_resolution_string(entry.requested_resolution, result.requested_resolution) || if (is_resolution_mapped(type) && (!parse_resolution_string(entry.requested_resolution, result.requested_resolution) ||
@@ -539,14 +520,13 @@ namespace display_device {
* const bool success = remap_display_mode_if_needed(video_config, *launch_session, config); * const bool success = remap_display_mode_if_needed(video_config, *launch_session, config);
* @examples_end * @examples_end
*/ */
bool bool remap_display_mode_if_needed(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
remap_display_mode_if_needed(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) { const auto remapping_type {determine_remapping_type(video_config)};
const auto remapping_type { determine_remapping_type(video_config) };
if (!remapping_type) { if (!remapping_type) {
return true; return true;
} }
const auto &remapping_list { [&]() { const auto &remapping_list {[&]() {
using enum remapping_type_e; using enum remapping_type_e;
switch (*remapping_type) { switch (*remapping_type) {
@@ -558,7 +538,7 @@ namespace display_device {
default: default:
return video_config.dd.mode_remapping.mixed; return video_config.dd.mode_remapping.mixed;
} }
}() }; }()};
if (remapping_list.empty()) { if (remapping_list.empty()) {
BOOST_LOG(debug) << "No values are available for display mode remapping."; BOOST_LOG(debug) << "No values are available for display mode remapping.";
@@ -566,9 +546,9 @@ namespace display_device {
} }
BOOST_LOG(debug) << "Trying to remap display modes..."; BOOST_LOG(debug) << "Trying to remap display modes...";
const auto entry_to_string { [type = *remapping_type](const config::video_t::dd_t::mode_remapping_entry_t &entry) { const auto entry_to_string {[type = *remapping_type](const config::video_t::dd_t::mode_remapping_entry_t &entry) {
const bool mapping_resolution { is_resolution_mapped(type) }; const bool mapping_resolution {is_resolution_mapped(type)};
const bool mapping_fps { is_fps_mapped(type) }; const bool mapping_fps {is_fps_mapped(type)};
// clang-format off // clang-format off
return (mapping_resolution ? " - requested resolution: "s + entry.requested_resolution + "\n" : "") + return (mapping_resolution ? " - requested resolution: "s + entry.requested_resolution + "\n" : "") +
@@ -576,10 +556,10 @@ namespace display_device {
(mapping_resolution ? " - final resolution: "s + entry.final_resolution + "\n" : "") + (mapping_resolution ? " - final resolution: "s + entry.final_resolution + "\n" : "") +
(mapping_fps ? " - final refresh rate: "s + entry.final_refresh_rate : ""); (mapping_fps ? " - final refresh rate: "s + entry.final_refresh_rate : "");
// clang-format on // clang-format on
} }; }};
for (const auto &entry : remapping_list) { for (const auto &entry : remapping_list) {
const auto parsed_entry { parse_remapping_entry(entry, *remapping_type) }; const auto parsed_entry {parse_remapping_entry(entry, *remapping_type)};
if (!parsed_entry) { if (!parsed_entry) {
BOOST_LOG(error) << "Failed to parse remapping entry from:\n" BOOST_LOG(error) << "Failed to parse remapping entry from:\n"
<< entry_to_string(entry); << entry_to_string(entry);
@@ -632,16 +612,18 @@ namespace display_device {
* @param video_config User's video related configuration. * @param video_config User's video related configuration.
* @return An interface or nullptr if the OS does not support the interface. * @return An interface or nullptr if the OS does not support the interface.
*/ */
std::unique_ptr<SettingsManagerInterface> std::unique_ptr<SettingsManagerInterface> make_settings_manager([[maybe_unused]] const std::filesystem::path &persistence_filepath, [[maybe_unused]] const config::video_t &video_config) {
make_settings_manager([[maybe_unused]] const std::filesystem::path &persistence_filepath, [[maybe_unused]] const config::video_t &video_config) {
#ifdef _WIN32 #ifdef _WIN32
return std::make_unique<SettingsManager>( return std::make_unique<SettingsManager>(
std::make_shared<WinDisplayDevice>(std::make_shared<WinApiLayer>()), std::make_shared<WinDisplayDevice>(std::make_shared<WinApiLayer>()),
std::make_shared<sunshine_audio_context_t>(), std::make_shared<sunshine_audio_context_t>(),
std::make_unique<PersistentState>( std::make_unique<PersistentState>(
std::make_shared<FileSettingsPersistence>(persistence_filepath)), std::make_shared<FileSettingsPersistence>(persistence_filepath)
),
WinWorkarounds { WinWorkarounds {
.m_hdr_blank_delay = video_config.dd.wa.hdr_toggle ? std::make_optional(500ms) : std::nullopt }); .m_hdr_blank_delay = video_config.dd.wa.hdr_toggle ? std::make_optional(500ms) : std::nullopt
}
);
#else #else
return nullptr; return nullptr;
#endif #endif
@@ -660,17 +642,16 @@ namespace display_device {
* @brief Reverts the configuration based on the provided option. * @brief Reverts the configuration based on the provided option.
* @note This is function does not lock mutex. * @note This is function does not lock mutex.
*/ */
void void revert_configuration_unlocked(const revert_option_e option) {
revert_configuration_unlocked(const revert_option_e option) {
if (!DD_DATA.sm_instance) { if (!DD_DATA.sm_instance) {
// Platform is not supported, nothing to do. // Platform is not supported, nothing to do.
return; return;
} }
// Note: by default the executor function is immediately executed in the calling thread. With delay, we want to avoid that. // Note: by default the executor function is immediately executed in the calling thread. With delay, we want to avoid that.
SchedulerOptions scheduler_option { .m_sleep_durations = { DEFAULT_RETRY_INTERVAL } }; SchedulerOptions scheduler_option {.m_sleep_durations = {DEFAULT_RETRY_INTERVAL}};
if (option == revert_option_e::try_indefinitely_with_delay && DD_DATA.config_revert_delay > std::chrono::milliseconds::zero()) { if (option == revert_option_e::try_indefinitely_with_delay && DD_DATA.config_revert_delay > std::chrono::milliseconds::zero()) {
scheduler_option.m_sleep_durations = { DD_DATA.config_revert_delay, DEFAULT_RETRY_INTERVAL }; scheduler_option.m_sleep_durations = {DD_DATA.config_revert_delay, DEFAULT_RETRY_INTERVAL};
scheduler_option.m_execution = SchedulerOptions::Execution::ScheduledOnly; scheduler_option.m_execution = SchedulerOptions::Execution::ScheduledOnly;
} }
@@ -681,17 +662,21 @@ namespace display_device {
return; return;
} }
auto available_devices { [&settings_iface]() { auto available_devices {[&settings_iface]() {
const auto devices { settings_iface.enumAvailableDevices() }; const auto devices {settings_iface.enumAvailableDevices()};
std::set<std::string> parsed_devices; std::set<std::string> parsed_devices;
std::transform( std::transform(
std::begin(devices), std::end(devices), std::begin(devices),
std::end(devices),
std::inserter(parsed_devices, std::end(parsed_devices)), std::inserter(parsed_devices, std::end(parsed_devices)),
[](const auto &device) { return device.m_device_id + " - " + device.m_friendly_name; }); [](const auto &device) {
return device.m_device_id + " - " + device.m_friendly_name;
}
);
return parsed_devices; return parsed_devices;
}() }; }()};
if (available_devices == tried_out_devices) { if (available_devices == tried_out_devices) {
BOOST_LOG(debug) << "Skipping reverting configuration, because no newly added/removed devices were detected since last check. Currently available devices:\n" BOOST_LOG(debug) << "Skipping reverting configuration, because no newly added/removed devices were detected since last check. Currently available devices:\n"
<< toJson(available_devices); << toJson(available_devices);
@@ -699,11 +684,10 @@ namespace display_device {
} }
using enum SettingsManagerInterface::RevertResult; using enum SettingsManagerInterface::RevertResult;
if (const auto result { settings_iface.revertSettings() }; result == Ok) { if (const auto result {settings_iface.revertSettings()}; result == Ok) {
stop_token.requestStop(); stop_token.requestStop();
return; return;
} } else if (result == ApiTemporarilyUnavailable) {
else if (result == ApiTemporarilyUnavailable) {
// Do nothing and retry next time // Do nothing and retry next time
return; return;
} }
@@ -717,9 +701,8 @@ namespace display_device {
} }
} // namespace } // namespace
std::unique_ptr<platf::deinit_t> std::unique_ptr<platf::deinit_t> init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config) {
init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config) { std::lock_guard lock {DD_DATA.mutex};
std::lock_guard lock { DD_DATA.mutex };
// We can support re-init without any issues, however we should make sure to clean up first! // We can support re-init without any issues, however we should make sure to clean up first!
if (video_config.dd.configuration_option == config::video_t::dd_t::config_option_e::disabled) { if (video_config.dd.configuration_option == config::video_t::dd_t::config_option_e::disabled) {
if (!persistence_filepath.empty() && std::filesystem::exists(persistence_filepath)) { if (!persistence_filepath.empty() && std::filesystem::exists(persistence_filepath)) {
@@ -733,10 +716,12 @@ namespace display_device {
// If we fail to create settings manager, this means platform is not supported, and // If we fail to create settings manager, this means platform is not supported, and
// we will need to provided error-free pass-trough in other methods // we will need to provided error-free pass-trough in other methods
if (auto settings_manager { make_settings_manager(persistence_filepath, video_config) }) { if (auto settings_manager {make_settings_manager(persistence_filepath, video_config)}) {
DD_DATA.sm_instance = std::make_unique<RetryScheduler<SettingsManagerInterface>>(std::move(settings_manager)); DD_DATA.sm_instance = std::make_unique<RetryScheduler<SettingsManagerInterface>>(std::move(settings_manager));
const auto available_devices { DD_DATA.sm_instance->execute([](auto &settings_iface) { return settings_iface.enumAvailableDevices(); }) }; const auto available_devices {DD_DATA.sm_instance->execute([](auto &settings_iface) {
return settings_iface.enumAvailableDevices();
})};
BOOST_LOG(info) << "Currently available display devices:\n" BOOST_LOG(info) << "Currently available display devices:\n"
<< toJson(available_devices); << toJson(available_devices);
@@ -748,37 +733,37 @@ namespace display_device {
class deinit_t: public platf::deinit_t { class deinit_t: public platf::deinit_t {
public: public:
~deinit_t() override { ~deinit_t() override {
std::lock_guard lock { DD_DATA.mutex }; std::lock_guard lock {DD_DATA.mutex};
try { try {
// This may throw if used incorrectly. At the moment this will not happen, however // This may throw if used incorrectly. At the moment this will not happen, however
// in case some unforeseen changes are made that could raise an exception, // in case some unforeseen changes are made that could raise an exception,
// we definitely don't want this to happen in destructor. Especially in the // we definitely don't want this to happen in destructor. Especially in the
// deinit_t where the outcome does not really matter. // deinit_t where the outcome does not really matter.
revert_configuration_unlocked(revert_option_e::try_once); revert_configuration_unlocked(revert_option_e::try_once);
} } catch (std::exception &err) {
catch (std::exception &err) {
BOOST_LOG(fatal) << err.what(); BOOST_LOG(fatal) << err.what();
} }
DD_DATA.sm_instance = nullptr; DD_DATA.sm_instance = nullptr;
} }
}; };
return std::make_unique<deinit_t>(); return std::make_unique<deinit_t>();
} }
std::string std::string map_output_name(const std::string &output_name) {
map_output_name(const std::string &output_name) { std::lock_guard lock {DD_DATA.mutex};
std::lock_guard lock { DD_DATA.mutex };
if (!DD_DATA.sm_instance) { if (!DD_DATA.sm_instance) {
// Fallback to giving back the output name if the platform is not supported. // Fallback to giving back the output name if the platform is not supported.
return output_name; return output_name;
} }
return DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) { return settings_iface.getDisplayName(output_name); }); return DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) {
return settings_iface.getDisplayName(output_name);
});
} }
std::string std::string map_display_name(const std::string &display_name) {
map_display_name(const std::string &display_name) {
std::lock_guard lock { DD_DATA.mutex }; std::lock_guard lock { DD_DATA.mutex };
if (!DD_DATA.sm_instance) { if (!DD_DATA.sm_instance) {
return {}; return {};
@@ -795,15 +780,14 @@ namespace display_device {
return {}; return {};
} }
void void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
const auto result { parse_configuration(video_config, session) }; const auto result { parse_configuration(video_config, session) };
if (const auto *parsed_config { std::get_if<SingleDisplayConfiguration>(&result) }; parsed_config) { if (const auto *parsed_config { std::get_if<SingleDisplayConfiguration>(&result) }; parsed_config) {
configure_display(*parsed_config); configure_display(*parsed_config);
return; return;
} }
if (const auto *disabled { std::get_if<configuration_disabled_tag_t>(&result) }; disabled) { if (const auto *disabled {std::get_if<configuration_disabled_tag_t>(&result)}; disabled) {
revert_configuration(); revert_configuration();
return; return;
} }
@@ -812,9 +796,8 @@ namespace display_device {
// want to revert active configuration in case we have any // want to revert active configuration in case we have any
} }
void void configure_display(const SingleDisplayConfiguration &config) {
configure_display(const SingleDisplayConfiguration &config) { std::lock_guard lock {DD_DATA.mutex};
std::lock_guard lock { DD_DATA.mutex };
if (!DD_DATA.sm_instance) { if (!DD_DATA.sm_instance) {
// Platform is not supported, nothing to do. // Platform is not supported, nothing to do.
return; return;
@@ -827,18 +810,16 @@ namespace display_device {
stop_token.requestStop(); stop_token.requestStop();
} }
}, },
{ .m_sleep_durations = { DEFAULT_RETRY_INTERVAL } }); {.m_sleep_durations = {DEFAULT_RETRY_INTERVAL}});
} }
void void revert_configuration() {
revert_configuration() { std::lock_guard lock {DD_DATA.mutex};
std::lock_guard lock { DD_DATA.mutex };
revert_configuration_unlocked(revert_option_e::try_indefinitely_with_delay); revert_configuration_unlocked(revert_option_e::try_indefinitely_with_delay);
} }
bool bool reset_persistence() {
reset_persistence() { std::lock_guard lock {DD_DATA.mutex};
std::lock_guard lock { DD_DATA.mutex };
if (!DD_DATA.sm_instance) { if (!DD_DATA.sm_instance) {
// Platform is not supported, assume success. // Platform is not supported, assume success.
return true; return true;
@@ -852,9 +833,20 @@ namespace display_device {
}); });
} }
std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> EnumeratedDeviceList enumerate_devices() {
parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) { std::lock_guard lock {DD_DATA.mutex};
const auto device_prep { parse_device_prep_option(video_config) }; if (!DD_DATA.sm_instance) {
// Platform is not supported.
return {};
}
return DD_DATA.sm_instance->execute([](auto &settings_iface) {
return settings_iface.enumAvailableDevices();
});
}
std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
const auto device_prep {parse_device_prep_option(video_config)};
if (!device_prep) { if (!device_prep) {
return configuration_disabled_tag_t {}; return configuration_disabled_tag_t {};
} }

View File

@@ -4,18 +4,22 @@
*/ */
#pragma once #pragma once
// lib includes // standard includes
#include <display_device/types.h>
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
// lib includes
#include <display_device/types.h>
// forward declarations // forward declarations
namespace platf { namespace platf {
class deinit_t; class deinit_t;
} }
namespace config { namespace config {
struct video_t; struct video_t;
} }
namespace rtsp_stream { namespace rtsp_stream {
struct launch_session_t; struct launch_session_t;
} }
@@ -32,8 +36,7 @@ namespace display_device {
* const auto init_guard { init("/my/persitence/file.state", video_config) }; * const auto init_guard { init("/my/persitence/file.state", video_config) };
* @examples_end * @examples_end
*/ */
[[nodiscard]] std::unique_ptr<platf::deinit_t> [[nodiscard]] std::unique_ptr<platf::deinit_t> init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config);
init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config);
/** /**
* @brief Map the output name to a specific display. * @brief Map the output name to a specific display.
@@ -45,8 +48,7 @@ namespace display_device {
* const auto mapped_name_custom { map_output_name("{some-device-id}") }; * const auto mapped_name_custom { map_output_name("{some-device-id}") };
* @examples_end * @examples_end
*/ */
[[nodiscard]] std::string [[nodiscard]] std::string map_output_name(const std::string &output_name);
map_output_name(const std::string &output_name);
[[nodiscard]] std::string [[nodiscard]] std::string
map_display_name(const std::string &display_name); map_display_name(const std::string &display_name);
@@ -65,8 +67,7 @@ namespace display_device {
* configure_display(video_config, *launch_session); * configure_display(video_config, *launch_session);
* @examples_end * @examples_end
*/ */
void void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
/** /**
* @brief Configure the display device using the provided configuration. * @brief Configure the display device using the provided configuration.
@@ -86,8 +87,7 @@ namespace display_device {
* configure_display(valid_config); * configure_display(valid_config);
* @examples_end * @examples_end
*/ */
void void configure_display(const SingleDisplayConfiguration &config);
configure_display(const SingleDisplayConfiguration &config);
/** /**
* @brief Revert the display configuration and restore the previous state. * @brief Revert the display configuration and restore the previous state.
@@ -99,8 +99,7 @@ namespace display_device {
* revert_configuration(); * revert_configuration();
* @examples_end * @examples_end
*/ */
void void revert_configuration();
revert_configuration();
/** /**
* @brief Reset the persistence and currently held initial display state. * @brief Reset the persistence and currently held initial display state.
@@ -114,16 +113,25 @@ namespace display_device {
* The user then accepts that Sunshine is not able to restore the state and "agrees" to * The user then accepts that Sunshine is not able to restore the state and "agrees" to
* do it manually. * do it manually.
* *
* @return * @return True if persistence was reset, false otherwise.
* @note Whether the function succeeds or fails, the any of the scheduled "retries" from * @note Whether the function succeeds or fails, any of the scheduled "retries" from
* other methods will be stopped to not interfere with the user actions. * other methods will be stopped to not interfere with the user actions.
* *
* @examples * @examples
* const auto result = reset_persistence(); * const auto result = reset_persistence();
* @examples_end * @examples_end
*/ */
bool bool reset_persistence();
reset_persistence();
/**
* @brief Enumerate the available devices.
* @return A list of devices.
*
* @examples
* const auto devices = enumerate_devices();
* @examples_end
*/
[[nodiscard]] EnumeratedDeviceList enumerate_devices();
/** /**
* @brief A tag structure indicating that configuration parsing has failed. * @brief A tag structure indicating that configuration parsing has failed.
@@ -153,6 +161,5 @@ namespace display_device {
* } * }
* @examples_end * @examples_end
*/ */
[[nodiscard]] std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> [[nodiscard]] std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
} // namespace display_device } // namespace display_device

View File

@@ -26,21 +26,18 @@ extern "C" {
using namespace std::literals; using namespace std::literals;
void void launch_ui() {
launch_ui() {
std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)); std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS));
platf::open_url(url); platf::open_url(url);
} }
void void launch_ui_with_path(std::string path) {
launch_ui_with_path(std::string path) {
std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + path; std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + path;
platf::open_url(url); platf::open_url(url);
} }
namespace args { namespace args {
int int creds(const char *name, int argc, char *argv[]) {
creds(const char *name, int argc, char *argv[]) {
if (argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) { if (argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) {
help(name); help(name);
} }
@@ -50,21 +47,18 @@ namespace args {
return 0; return 0;
} }
int int help(const char *name) {
help(const char *name) {
logging::print_help(name); logging::print_help(name);
return 0; return 0;
} }
int int version() {
version() {
// version was already logged at startup // version was already logged at startup
return 0; return 0;
} }
#ifdef _WIN32 #ifdef _WIN32
int int restore_nvprefs_undo() {
restore_nvprefs_undo() {
if (nvprefs_instance.load()) { if (nvprefs_instance.load()) {
nvprefs_instance.restore_from_and_delete_undo_file_if_exists(); nvprefs_instance.restore_from_and_delete_undo_file_if_exists();
nvprefs_instance.unload(); nvprefs_instance.unload();
@@ -78,8 +72,7 @@ namespace lifetime {
char **argv; char **argv;
std::atomic_int desired_exit_code; std::atomic_int desired_exit_code;
void void exit_sunshine(int exit_code, bool async) {
exit_sunshine(int exit_code, bool async) {
// Store the exit code of the first exit_sunshine() call // Store the exit code of the first exit_sunshine() call
int zero = 0; int zero = 0;
desired_exit_code.compare_exchange_strong(zero, exit_code); desired_exit_code.compare_exchange_strong(zero, exit_code);
@@ -94,8 +87,7 @@ namespace lifetime {
} }
} }
void void debug_trap() {
debug_trap() {
#ifdef _WIN32 #ifdef _WIN32
DebugBreak(); DebugBreak();
#else #else
@@ -105,22 +97,19 @@ namespace lifetime {
abort(); abort();
} }
char ** char **get_argv() {
get_argv() {
return argv; return argv;
} }
} // namespace lifetime } // namespace lifetime
void void log_publisher_data() {
log_publisher_data() {
BOOST_LOG(info) << "Package Publisher: "sv << SUNSHINE_PUBLISHER_NAME; BOOST_LOG(info) << "Package Publisher: "sv << SUNSHINE_PUBLISHER_NAME;
BOOST_LOG(info) << "Publisher Website: "sv << SUNSHINE_PUBLISHER_WEBSITE; BOOST_LOG(info) << "Publisher Website: "sv << SUNSHINE_PUBLISHER_WEBSITE;
BOOST_LOG(info) << "Get support: "sv << SUNSHINE_PUBLISHER_ISSUE_URL; BOOST_LOG(info) << "Get support: "sv << SUNSHINE_PUBLISHER_ISSUE_URL;
} }
#ifdef _WIN32 #ifdef _WIN32
bool bool is_gamestream_enabled() {
is_gamestream_enabled() {
DWORD enabled; DWORD enabled;
DWORD size = sizeof(enabled); DWORD size = sizeof(enabled);
return RegGetValueW( return RegGetValueW(
@@ -130,7 +119,8 @@ is_gamestream_enabled() {
RRF_RT_REG_DWORD, RRF_RT_REG_DWORD,
nullptr, nullptr,
&enabled, &enabled,
&size) == ERROR_SUCCESS && &size
) == ERROR_SUCCESS &&
enabled != 0; enabled != 0;
} }
@@ -170,8 +160,7 @@ namespace service_ctrl {
/** /**
* @brief Asynchronously starts the Sunshine service. * @brief Asynchronously starts the Sunshine service.
*/ */
bool bool start_service() {
start_service() {
if (!service_handle) { if (!service_handle) {
return false; return false;
} }
@@ -191,8 +180,7 @@ namespace service_ctrl {
* @brief Query the service status. * @brief Query the service status.
* @param status The SERVICE_STATUS struct to populate. * @param status The SERVICE_STATUS struct to populate.
*/ */
bool bool query_service_status(SERVICE_STATUS &status) {
query_service_status(SERVICE_STATUS &status) {
if (!service_handle) { if (!service_handle) {
return false; return false;
} }
@@ -211,9 +199,8 @@ namespace service_ctrl {
SC_HANDLE service_handle = NULL; SC_HANDLE service_handle = NULL;
}; };
bool bool is_service_running() {
is_service_running() { service_controller sc {SERVICE_QUERY_STATUS};
service_controller sc { SERVICE_QUERY_STATUS };
SERVICE_STATUS status; SERVICE_STATUS status;
if (!sc.query_service_status(status)) { if (!sc.query_service_status(status)) {
@@ -223,9 +210,8 @@ namespace service_ctrl {
return status.dwCurrentState == SERVICE_RUNNING; return status.dwCurrentState == SERVICE_RUNNING;
} }
bool bool start_service() {
start_service() { service_controller sc {SERVICE_QUERY_STATUS | SERVICE_START};
service_controller sc { SERVICE_QUERY_STATUS | SERVICE_START };
std::cout << "Starting Sunshine..."sv; std::cout << "Starting Sunshine..."sv;
@@ -249,8 +235,7 @@ namespace service_ctrl {
return true; return true;
} }
bool bool wait_for_ui_ready() {
wait_for_ui_ready() {
std::cout << "Waiting for Web UI to be ready..."; std::cout << "Waiting for Web UI to be ready...";
// Wait up to 30 seconds for the web UI to start // Wait up to 30 seconds for the web UI to start

View File

@@ -18,8 +18,7 @@
* launch_ui(); * launch_ui();
* @examples_end * @examples_end
*/ */
void void launch_ui();
launch_ui();
/** /**
* @brief Launch the Web UI at a specific endpoint. * @brief Launch the Web UI at a specific endpoint.
@@ -27,8 +26,7 @@ launch_ui();
* launch_ui_with_path("/pin"); * launch_ui_with_path("/pin");
* @examples_end * @examples_end
*/ */
void void launch_ui_with_path(std::string path);
launch_ui_with_path(std::string path);
/** /**
* @brief Functions for handling command line arguments. * @brief Functions for handling command line arguments.
@@ -43,8 +41,7 @@ namespace args {
* creds("sunshine", 2, {"new_username", "new_password"}); * creds("sunshine", 2, {"new_username", "new_password"});
* @examples_end * @examples_end
*/ */
int int creds(const char *name, int argc, char *argv[]);
creds(const char *name, int argc, char *argv[]);
/** /**
* @brief Print help to stdout, then exit. * @brief Print help to stdout, then exit.
@@ -53,8 +50,7 @@ namespace args {
* help("sunshine"); * help("sunshine");
* @examples_end * @examples_end
*/ */
int int help(const char *name);
help(const char *name);
/** /**
* @brief Print the version to stdout, then exit. * @brief Print the version to stdout, then exit.
@@ -62,8 +58,7 @@ namespace args {
* version(); * version();
* @examples_end * @examples_end
*/ */
int int version();
version();
#ifdef _WIN32 #ifdef _WIN32
/** /**
@@ -75,8 +70,7 @@ namespace args {
* restore_nvprefs_undo(); * restore_nvprefs_undo();
* @examples_end * @examples_end
*/ */
int int restore_nvprefs_undo();
restore_nvprefs_undo();
#endif #endif
} // namespace args } // namespace args
@@ -92,35 +86,30 @@ namespace lifetime {
* @param exit_code The exit code to return from main(). * @param exit_code The exit code to return from main().
* @param async Specifies whether our termination will be non-blocking. * @param async Specifies whether our termination will be non-blocking.
*/ */
void void exit_sunshine(int exit_code, bool async);
exit_sunshine(int exit_code, bool async);
/** /**
* @brief Breaks into the debugger or terminates Sunshine if no debugger is attached. * @brief Breaks into the debugger or terminates Sunshine if no debugger is attached.
*/ */
void void debug_trap();
debug_trap();
/** /**
* @brief Get the argv array passed to main(). * @brief Get the argv array passed to main().
*/ */
char ** char **get_argv();
get_argv();
} // namespace lifetime } // namespace lifetime
/** /**
* @brief Log the publisher metadata provided from CMake. * @brief Log the publisher metadata provided from CMake.
*/ */
void void log_publisher_data();
log_publisher_data();
#ifdef _WIN32 #ifdef _WIN32
/** /**
* @brief Check if NVIDIA's GameStream software is running. * @brief Check if NVIDIA's GameStream software is running.
* @return `true` if GameStream is enabled, `false` otherwise. * @return `true` if GameStream is enabled, `false` otherwise.
*/ */
bool bool is_gamestream_enabled();
is_gamestream_enabled();
/** /**
* @brief Namespace for controlling the Sunshine service model on Windows. * @brief Namespace for controlling the Sunshine service model on Windows.
@@ -132,8 +121,7 @@ namespace service_ctrl {
* is_service_running(); * is_service_running();
* @examples_end * @examples_end
*/ */
bool bool is_service_running();
is_service_running();
/** /**
* @brief Start the service and wait for startup to complete. * @brief Start the service and wait for startup to complete.
@@ -141,8 +129,7 @@ namespace service_ctrl {
* start_service(); * start_service();
* @examples_end * @examples_end
*/ */
bool bool start_service();
start_service();
/** /**
* @brief Wait for the UI to be ready after Sunshine startup. * @brief Wait for the UI to be ready after Sunshine startup.
@@ -150,7 +137,6 @@ namespace service_ctrl {
* wait_for_ui_ready(); * wait_for_ui_ready();
* @examples_end * @examples_end
*/ */
bool bool wait_for_ui_ready();
wait_for_ui_ready();
} // namespace service_ctrl } // namespace service_ctrl
#endif #endif

View File

@@ -12,8 +12,7 @@
#include "logging.h" #include "logging.h"
namespace file_handler { namespace file_handler {
std::string std::string get_parent_directory(const std::string &path) {
get_parent_directory(const std::string &path) {
// remove any trailing path separators // remove any trailing path separators
std::string trimmed_path = path; std::string trimmed_path = path;
while (!trimmed_path.empty() && trimmed_path.back() == '/') { while (!trimmed_path.empty() && trimmed_path.back() == '/') {
@@ -24,8 +23,7 @@ namespace file_handler {
return p.parent_path().string(); return p.parent_path().string();
} }
bool bool make_directory(const std::string &path) {
make_directory(const std::string &path) {
// first, check if the directory already exists // first, check if the directory already exists
if (std::filesystem::exists(path)) { if (std::filesystem::exists(path)) {
return true; return true;
@@ -34,19 +32,17 @@ namespace file_handler {
return std::filesystem::create_directories(path); return std::filesystem::create_directories(path);
} }
std::string std::string read_file(const char *path) {
read_file(const char *path) {
if (!std::filesystem::exists(path)) { if (!std::filesystem::exists(path)) {
BOOST_LOG(debug) << "Missing file: " << path; BOOST_LOG(debug) << "Missing file: " << path;
return {}; return {};
} }
std::ifstream in(path); std::ifstream in(path);
return std::string { (std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>() }; return std::string {(std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()};
} }
int int write_file(const char *path, const std::string_view &contents) {
write_file(const char *path, const std::string_view &contents) {
std::ofstream out(path); std::ofstream out(path);
if (!out.is_open()) { if (!out.is_open()) {

View File

@@ -4,6 +4,7 @@
*/ */
#pragma once #pragma once
// standard includes
#include <string> #include <string>
/** /**
@@ -18,8 +19,7 @@ namespace file_handler {
* std::string parent_dir = get_parent_directory("path/to/file"); * std::string parent_dir = get_parent_directory("path/to/file");
* @examples_end * @examples_end
*/ */
std::string std::string get_parent_directory(const std::string &path);
get_parent_directory(const std::string &path);
/** /**
* @brief Make a directory. * @brief Make a directory.
@@ -29,8 +29,7 @@ namespace file_handler {
* bool dir_created = make_directory("path/to/directory"); * bool dir_created = make_directory("path/to/directory");
* @examples_end * @examples_end
*/ */
bool bool make_directory(const std::string &path);
make_directory(const std::string &path);
/** /**
* @brief Read a file to string. * @brief Read a file to string.
@@ -40,8 +39,7 @@ namespace file_handler {
* std::string contents = read_file("path/to/file"); * std::string contents = read_file("path/to/file");
* @examples_end * @examples_end
*/ */
std::string std::string read_file(const char *path);
read_file(const char *path);
/** /**
* @brief Writes a file. * @brief Writes a file.
@@ -52,6 +50,5 @@ namespace file_handler {
* int write_status = write_file("path/to/file", "file contents"); * int write_status = write_file("path/to/file", "file contents");
* @examples_end * @examples_end
*/ */
int int write_file(const char *path, const std::string_view &contents);
write_file(const char *path, const std::string_view &contents);
} // namespace file_handler } // namespace file_handler

View File

@@ -2,6 +2,7 @@
* @file globals.cpp * @file globals.cpp
* @brief Definitions for globally accessible variables and functions. * @brief Definitions for globally accessible variables and functions.
*/ */
// local includes
#include "globals.h" #include "globals.h"
safe::mail_t mail::man; safe::mail_t mail::man;

View File

@@ -4,6 +4,7 @@
*/ */
#pragma once #pragma once
// local includes
#include "entry_handler.h" #include "entry_handler.h"
#include "thread_pool.h" #include "thread_pool.h"

View File

@@ -4,22 +4,21 @@
*/ */
#define BOOST_BIND_GLOBAL_PLACEHOLDERS #define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include "process.h" // standard includes
#include <filesystem> #include <filesystem>
#include <utility> #include <utility>
// lib includes
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/context_base.hpp>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp> #include <boost/property_tree/xml_parser.hpp>
#include <curl/curl.h>
#include <boost/asio/ssl/context.hpp>
#include <Simple-Web-Server/server_http.hpp> #include <Simple-Web-Server/server_http.hpp>
#include <Simple-Web-Server/server_https.hpp> #include <Simple-Web-Server/server_https.hpp>
#include <boost/asio/ssl/context_base.hpp>
#include <curl/curl.h>
// local includes
#include "config.h" #include "config.h"
#include "crypto.h" #include "crypto.h"
#include "file_handler.h" #include "file_handler.h"
@@ -28,6 +27,7 @@
#include "network.h" #include "network.h"
#include "nvhttp.h" #include "nvhttp.h"
#include "platform/common.h" #include "platform/common.h"
#include "process.h"
#include "rtsp.h" #include "rtsp.h"
#include "utility.h" #include "utility.h"
@@ -36,17 +36,14 @@ namespace http {
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace pt = boost::property_tree; namespace pt = boost::property_tree;
int int reload_user_creds(const std::string &file);
reload_user_creds(const std::string &file); bool user_creds_exist(const std::string &file);
bool
user_creds_exist(const std::string &file);
std::string unique_id; std::string unique_id;
uuid_util::uuid_t uuid; uuid_util::uuid_t uuid;
net::net_e origin_web_ui_allowed; net::net_e origin_web_ui_allowed;
int int init() {
init() {
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed); origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed);
@@ -58,29 +55,25 @@ namespace http {
config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string(); config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string();
} }
if (!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) { if ((!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) &&
if (create_creds(config::nvhttp.pkey, config::nvhttp.cert)) { create_creds(config::nvhttp.pkey, config::nvhttp.cert)) {
return -1; return -1;
} }
} if (!user_creds_exist(config::sunshine.credentials_file)) {
if (user_creds_exist(config::sunshine.credentials_file)) {
if (reload_user_creds(config::sunshine.credentials_file)) return -1;
}
else {
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started"; BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
} else if (reload_user_creds(config::sunshine.credentials_file)) {
return -1;
} }
return 0; return 0;
} }
int int save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
pt::ptree outputTree; pt::ptree outputTree;
if (fs::exists(file)) { if (fs::exists(file)) {
try { try {
pt::read_json(file, outputTree); pt::read_json(file, outputTree);
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what(); BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what();
return -1; return -1;
} }
@@ -92,8 +85,7 @@ namespace http {
outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string()); outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string());
try { try {
pt::write_json(file, outputTree); pt::write_json(file, outputTree);
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(error) << "error writing to the credentials file, perhaps try this again as an administrator? Details: "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; return -1;
} }
@@ -102,8 +94,7 @@ namespace http {
return 0; return 0;
} }
bool bool user_creds_exist(const std::string &file) {
user_creds_exist(const std::string &file) {
if (!fs::exists(file)) { if (!fs::exists(file)) {
return false; return false;
} }
@@ -114,32 +105,28 @@ namespace http {
return inputTree.find("username") != inputTree.not_found() && return inputTree.find("username") != inputTree.not_found() &&
inputTree.find("password") != inputTree.not_found() && inputTree.find("password") != inputTree.not_found() &&
inputTree.find("salt") != inputTree.not_found(); inputTree.find("salt") != inputTree.not_found();
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(error) << "validating user credentials: "sv << e.what(); BOOST_LOG(error) << "validating user credentials: "sv << e.what();
} }
return false; return false;
} }
int int reload_user_creds(const std::string &file) {
reload_user_creds(const std::string &file) {
pt::ptree inputTree; pt::ptree inputTree;
try { try {
pt::read_json(file, inputTree); pt::read_json(file, inputTree);
config::sunshine.username = inputTree.get<std::string>("username"); config::sunshine.username = inputTree.get<std::string>("username");
config::sunshine.password = inputTree.get<std::string>("password"); config::sunshine.password = inputTree.get<std::string>("password");
config::sunshine.salt = inputTree.get<std::string>("salt"); config::sunshine.salt = inputTree.get<std::string>("salt");
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(error) << "loading user credentials: "sv << e.what(); BOOST_LOG(error) << "loading user credentials: "sv << e.what();
return -1; return -1;
} }
return 0; return 0;
} }
int int create_creds(const std::string &pkey, const std::string &cert) {
create_creds(const std::string &pkey, const std::string &cert) {
fs::path pkey_path = pkey; fs::path pkey_path = pkey;
fs::path cert_path = cert; fs::path cert_path = cert;
@@ -173,18 +160,14 @@ namespace http {
return -1; return -1;
} }
fs::permissions(pkey_path, fs::permissions(pkey_path, fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::replace, err_code);
fs::perms::owner_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
if (err_code) { if (err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message(); BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message();
return -1; return -1;
} }
fs::permissions(cert_path, fs::permissions(cert_path, fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, fs::perm_options::replace, err_code);
fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
if (err_code) { if (err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message(); BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message();
@@ -194,16 +177,15 @@ namespace http {
return 0; return 0;
} }
bool bool download_file(const std::string &url, const std::string &file, long ssl_version) {
download_file(const std::string &url, const std::string &file) { // sonar complains about weak ssl and tls versions; however sonar cannot detect the fix
CURL *curl = curl_easy_init(); CURL *curl = curl_easy_init(); // NOSONAR
if (!curl) { if (!curl) {
BOOST_LOG(error) << "Couldn't create CURL instance"; BOOST_LOG(error) << "Couldn't create CURL instance";
return false; return false;
} }
std::string file_dir = file_handler::get_parent_directory(file); if (std::string file_dir = file_handler::get_parent_directory(file); !file_handler::make_directory(file_dir)) {
if (!file_handler::make_directory(file_dir)) {
BOOST_LOG(error) << "Couldn't create directory ["sv << file_dir << ']'; BOOST_LOG(error) << "Couldn't create directory ["sv << file_dir << ']';
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
return false; return false;
@@ -215,6 +197,8 @@ namespace http {
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
return false; return false;
} }
curl_easy_setopt(curl, CURLOPT_SSLVERSION, ssl_version); // NOSONAR
curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
@@ -231,20 +215,16 @@ namespace http {
return result == CURLE_OK; return result == CURLE_OK;
} }
std::string std::string url_escape(const std::string &url) {
url_escape(const std::string &url) { char *string = curl_easy_escape(nullptr, url.c_str(), static_cast<int>(url.length()));
CURL *curl = curl_easy_init();
char *string = curl_easy_escape(curl, url.c_str(), url.length());
std::string result(string); std::string result(string);
curl_free(string); curl_free(string);
curl_easy_cleanup(curl);
return result; return result;
} }
std::string std::string url_get_host(const std::string &url) {
url_get_host(const std::string &url) {
CURLU *curlu = curl_url(); CURLU *curlu = curl_url();
curl_url_set(curlu, CURLUPART_URL, url.c_str(), url.length()); curl_url_set(curlu, CURLUPART_URL, url.c_str(), static_cast<unsigned int>(url.length()));
char *host; char *host;
if (curl_url_get(curlu, CURLUPART_HOST, &host, 0) != CURLUE_OK) { if (curl_url_get(curlu, CURLUPART_HOST, &host, 0) != CURLUE_OK) {
curl_url_cleanup(curlu); curl_url_cleanup(curlu);
@@ -255,5 +235,4 @@ namespace http {
curl_url_cleanup(curlu); curl_url_cleanup(curlu);
return result; return result;
} }
} // namespace http } // namespace http

View File

@@ -4,31 +4,29 @@
*/ */
#pragma once #pragma once
// lib includes
#include <curl/curl.h>
// local includes
#include "network.h" #include "network.h"
#include "thread_safe.h" #include "thread_safe.h"
#include "uuid.h" #include "uuid.h"
namespace http { namespace http {
int int init();
init(); int create_creds(const std::string &pkey, const std::string &cert);
int int save_user_creds(
create_creds(const std::string &pkey, const std::string &cert);
int
save_user_creds(
const std::string &file, const std::string &file,
const std::string &username, const std::string &username,
const std::string &password, const std::string &password,
bool run_our_mouth = false); bool run_our_mouth = false
);
int int reload_user_creds(const std::string &file);
reload_user_creds(const std::string &file); bool download_file(const std::string &url, const std::string &file, long ssl_version = CURL_SSLVERSION_TLSv1_3);
bool std::string url_escape(const std::string &url);
download_file(const std::string &url, const std::string &file); std::string url_get_host(const std::string &url);
std::string
url_escape(const std::string &url);
std::string
url_get_host(const std::string &url);
extern std::string unique_id; extern std::string unique_id;
extern uuid_util::uuid_t uuid; extern uuid_util::uuid_t uuid;

View File

@@ -2,13 +2,13 @@
* @file src/input.cpp * @file src/input.cpp
* @brief Definitions for gamepad, keyboard, and mouse input handling. * @brief Definitions for gamepad, keyboard, and mouse input handling.
*/ */
// define uint32_t for <moonlight-common-c/src/Input.h>
#include <cstdint> #include <cstdint>
extern "C" { extern "C" {
#include <moonlight-common-c/src/Input.h> #include <moonlight-common-c/src/Input.h>
#include <moonlight-common-c/src/Limelight.h> #include <moonlight-common-c/src/Limelight.h>
} }
// standard includes
#include <bitset> #include <bitset>
#include <chrono> #include <chrono>
#include <cmath> #include <cmath>
@@ -16,6 +16,10 @@ extern "C" {
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
// lib includes
#include <boost/endian/buffers.hpp>
// local includes
#include "config.h" #include "config.h"
#include "globals.h" #include "globals.h"
#include "input.h" #include "input.h"
@@ -24,14 +28,13 @@ extern "C" {
#include "thread_pool.h" #include "thread_pool.h"
#include "utility.h" #include "utility.h"
#include <boost/endian/buffers.hpp>
// Win32 WHEEL_DELTA constant // Win32 WHEEL_DELTA constant
#ifndef WHEEL_DELTA #ifndef WHEEL_DELTA
#define WHEEL_DELTA 120 #define WHEEL_DELTA 120
#endif #endif
using namespace std::literals; using namespace std::literals;
namespace input { namespace input {
constexpr auto MAX_GAMEPADS = std::min((std::size_t) platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8); constexpr auto MAX_GAMEPADS = std::min((std::size_t) platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8);
@@ -54,9 +57,8 @@ namespace input {
UP ///< Button is up UP ///< Button is up
}; };
template <std::size_t N> template<std::size_t N>
int int alloc_id(std::bitset<N> &gamepad_mask) {
alloc_id(std::bitset<N> &gamepad_mask) {
for (int x = 0; x < gamepad_mask.size(); ++x) { for (int x = 0; x < gamepad_mask.size(); ++x) {
if (!gamepad_mask[x]) { if (!gamepad_mask[x]) {
gamepad_mask[x] = true; gamepad_mask[x] = true;
@@ -67,23 +69,22 @@ namespace input {
return -1; return -1;
} }
template <std::size_t N> template<std::size_t N>
void void free_id(std::bitset<N> &gamepad_mask, int id) {
free_id(std::bitset<N> &gamepad_mask, int id) {
gamepad_mask[id] = false; gamepad_mask[id] = false;
} }
typedef uint32_t key_press_id_t; typedef uint32_t key_press_id_t;
key_press_id_t
make_kpid(uint16_t vk, uint8_t flags) { key_press_id_t make_kpid(uint16_t vk, uint8_t flags) {
return (key_press_id_t) vk << 8 | flags; return (key_press_id_t) vk << 8 | flags;
} }
uint16_t
vk_from_kpid(key_press_id_t kpid) { uint16_t vk_from_kpid(key_press_id_t kpid) {
return kpid >> 8; return kpid >> 8;
} }
uint8_t
flags_from_kpid(key_press_id_t kpid) { uint8_t flags_from_kpid(key_press_id_t kpid) {
return kpid & 0xFF; return kpid & 0xFF;
} }
@@ -92,8 +93,7 @@ namespace input {
* @param f Netfloat value. * @param f Netfloat value.
* @return The native endianness float value. * @return The native endianness float value.
*/ */
float float from_netfloat(netfloat f) {
from_netfloat(netfloat f) {
return boost::endian::endian_load<float, sizeof(float), boost::endian::order::little>(f); return boost::endian::endian_load<float, sizeof(float), boost::endian::order::little>(f);
} }
@@ -104,8 +104,7 @@ namespace input {
* @param max The maximum value for clamping. * @param max The maximum value for clamping.
* @return Clamped native endianess float value. * @return Clamped native endianess float value.
*/ */
float float from_clamped_netfloat(netfloat f, float min, float max) {
from_clamped_netfloat(netfloat f, float min, float max) {
return std::clamp(from_netfloat(f), min, max); return std::clamp(from_netfloat(f), min, max);
} }
@@ -116,16 +115,21 @@ namespace input {
static platf::input_t platf_input; static platf::input_t platf_input;
static std::bitset<platf::MAX_GAMEPADS> gamepadMask {}; static std::bitset<platf::MAX_GAMEPADS> gamepadMask {};
void void free_gamepad(platf::input_t &platf_input, int id) {
free_gamepad(platf::input_t &platf_input, int id) {
platf::gamepad_update(platf_input, id, platf::gamepad_state_t {}); platf::gamepad_update(platf_input, id, platf::gamepad_state_t {});
platf::free_gamepad(platf_input, id); platf::free_gamepad(platf_input, id);
free_id(gamepadMask, id); free_id(gamepadMask, id);
} }
struct gamepad_t { struct gamepad_t {
gamepad_t(): gamepad_t():
gamepad_state {}, back_timeout_id {}, id { -1 }, back_button_state { button_state_e::NONE } {} gamepad_state {},
back_timeout_id {},
id {-1},
back_button_state {button_state_e::NONE} {
}
~gamepad_t() { ~gamepad_t() {
if (id >= 0) { if (id >= 0) {
task_pool.push([id = this->id]() { task_pool.push([id = this->id]() {
@@ -158,16 +162,18 @@ namespace input {
input_t( input_t(
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event, safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event,
platf::feedback_queue_t feedback_queue): platf::feedback_queue_t feedback_queue
):
shortcutFlags {}, shortcutFlags {},
gamepads(MAX_GAMEPADS), gamepads(MAX_GAMEPADS),
client_context { platf::allocate_client_input_context(platf_input) }, client_context {platf::allocate_client_input_context(platf_input)},
touch_port_event { std::move(touch_port_event) }, touch_port_event {std::move(touch_port_event)},
feedback_queue { std::move(feedback_queue) }, feedback_queue {std::move(feedback_queue)},
mouse_left_button_timeout {}, mouse_left_button_timeout {},
touch_port { { 0, 0, 0, 0 }, 0, 0, 1.0f }, touch_port {{0, 0, 0, 0}, 0, 0, 1.0f},
accumulated_vscroll_delta {}, accumulated_vscroll_delta {},
accumulated_hscroll_delta {} {} accumulated_hscroll_delta {} {
}
// Keep track of alt+ctrl+shift key combo // Keep track of alt+ctrl+shift key combo
int shortcutFlags; int shortcutFlags;
@@ -194,8 +200,7 @@ namespace input {
* @param keyCode The VKEY code * @param keyCode The VKEY code
* @return 0 if no shortcut applied, > 0 if shortcut applied. * @return 0 if no shortcut applied, > 0 if shortcut applied.
*/ */
inline int inline int apply_shortcut(short keyCode) {
apply_shortcut(short keyCode) {
constexpr auto VK_F1 = 0x70; constexpr auto VK_F1 = 0x70;
constexpr auto VK_F13 = 0x7C; constexpr auto VK_F13 = 0x7C;
@@ -215,8 +220,7 @@ namespace input {
return 0; return 0;
} }
void void print(PNV_REL_MOUSE_MOVE_PACKET packet) {
print(PNV_REL_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin relative mouse move packet--"sv << std::endl << "--begin relative mouse move packet--"sv << std::endl
<< "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl << "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl
@@ -224,8 +228,7 @@ namespace input {
<< "--end relative mouse move packet--"sv; << "--end relative mouse move packet--"sv;
} }
void void print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin absolute mouse move packet--"sv << std::endl << "--begin absolute mouse move packet--"sv << std::endl
<< "x ["sv << util::endian::big(packet->x) << ']' << std::endl << "x ["sv << util::endian::big(packet->x) << ']' << std::endl
@@ -235,8 +238,7 @@ namespace input {
<< "--end absolute mouse move packet--"sv; << "--end absolute mouse move packet--"sv;
} }
void void print(PNV_MOUSE_BUTTON_PACKET packet) {
print(PNV_MOUSE_BUTTON_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin mouse button packet--"sv << std::endl << "--begin mouse button packet--"sv << std::endl
<< "action ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl << "action ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl
@@ -244,24 +246,21 @@ namespace input {
<< "--end mouse button packet--"sv; << "--end mouse button packet--"sv;
} }
void void print(PNV_SCROLL_PACKET packet) {
print(PNV_SCROLL_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin mouse scroll packet--"sv << std::endl << "--begin mouse scroll packet--"sv << std::endl
<< "scrollAmt1 ["sv << util::endian::big(packet->scrollAmt1) << ']' << std::endl << "scrollAmt1 ["sv << util::endian::big(packet->scrollAmt1) << ']' << std::endl
<< "--end mouse scroll packet--"sv; << "--end mouse scroll packet--"sv;
} }
void void print(PSS_HSCROLL_PACKET packet) {
print(PSS_HSCROLL_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin mouse hscroll packet--"sv << std::endl << "--begin mouse hscroll packet--"sv << std::endl
<< "scrollAmount ["sv << util::endian::big(packet->scrollAmount) << ']' << std::endl << "scrollAmount ["sv << util::endian::big(packet->scrollAmount) << ']' << std::endl
<< "--end mouse hscroll packet--"sv; << "--end mouse hscroll packet--"sv;
} }
void void print(PNV_KEYBOARD_PACKET packet) {
print(PNV_KEYBOARD_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin keyboard packet--"sv << std::endl << "--begin keyboard packet--"sv << std::endl
<< "keyAction ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl << "keyAction ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl
@@ -271,8 +270,7 @@ namespace input {
<< "--end keyboard packet--"sv; << "--end keyboard packet--"sv;
} }
void void print(PNV_UNICODE_PACKET packet) {
print(PNV_UNICODE_PACKET packet) {
std::string text(packet->text, util::endian::big(packet->header.size) - sizeof(packet->header.magic)); std::string text(packet->text, util::endian::big(packet->header.size) - sizeof(packet->header.magic));
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin unicode packet--"sv << std::endl << "--begin unicode packet--"sv << std::endl
@@ -280,8 +278,7 @@ namespace input {
<< "--end unicode packet--"sv; << "--end unicode packet--"sv;
} }
void void print(PNV_MULTI_CONTROLLER_PACKET packet) {
print(PNV_MULTI_CONTROLLER_PACKET packet) {
// Moonlight spams controller packet even when not necessary // Moonlight spams controller packet even when not necessary
BOOST_LOG(verbose) BOOST_LOG(verbose)
<< "--begin controller packet--"sv << std::endl << "--begin controller packet--"sv << std::endl
@@ -301,8 +298,7 @@ namespace input {
* @brief Prints a touch packet. * @brief Prints a touch packet.
* @param packet The touch packet. * @param packet The touch packet.
*/ */
void void print(PSS_TOUCH_PACKET packet) {
print(PSS_TOUCH_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin touch packet--"sv << std::endl << "--begin touch packet--"sv << std::endl
<< "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl << "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl
@@ -320,8 +316,7 @@ namespace input {
* @brief Prints a pen packet. * @brief Prints a pen packet.
* @param packet The pen packet. * @param packet The pen packet.
*/ */
void void print(PSS_PEN_PACKET packet) {
print(PSS_PEN_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin pen packet--"sv << std::endl << "--begin pen packet--"sv << std::endl
<< "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl << "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl
@@ -341,8 +336,7 @@ namespace input {
* @brief Prints a controller arrival packet. * @brief Prints a controller arrival packet.
* @param packet The controller arrival packet. * @param packet The controller arrival packet.
*/ */
void void print(PSS_CONTROLLER_ARRIVAL_PACKET packet) {
print(PSS_CONTROLLER_ARRIVAL_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin controller arrival packet--"sv << std::endl << "--begin controller arrival packet--"sv << std::endl
<< "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl << "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl
@@ -356,8 +350,7 @@ namespace input {
* @brief Prints a controller touch packet. * @brief Prints a controller touch packet.
* @param packet The controller touch packet. * @param packet The controller touch packet.
*/ */
void void print(PSS_CONTROLLER_TOUCH_PACKET packet) {
print(PSS_CONTROLLER_TOUCH_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin controller touch packet--"sv << std::endl << "--begin controller touch packet--"sv << std::endl
<< "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl << "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl
@@ -373,8 +366,7 @@ namespace input {
* @brief Prints a controller motion packet. * @brief Prints a controller motion packet.
* @param packet The controller motion packet. * @param packet The controller motion packet.
*/ */
void void print(PSS_CONTROLLER_MOTION_PACKET packet) {
print(PSS_CONTROLLER_MOTION_PACKET packet) {
BOOST_LOG(verbose) BOOST_LOG(verbose)
<< "--begin controller motion packet--"sv << std::endl << "--begin controller motion packet--"sv << std::endl
<< "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl << "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl
@@ -389,8 +381,7 @@ namespace input {
* @brief Prints a controller battery packet. * @brief Prints a controller battery packet.
* @param packet The controller battery packet. * @param packet The controller battery packet.
*/ */
void void print(PSS_CONTROLLER_BATTERY_PACKET packet) {
print(PSS_CONTROLLER_BATTERY_PACKET packet) {
BOOST_LOG(verbose) BOOST_LOG(verbose)
<< "--begin controller battery packet--"sv << std::endl << "--begin controller battery packet--"sv << std::endl
<< "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl << "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl
@@ -399,8 +390,7 @@ namespace input {
<< "--end controller battery packet--"sv; << "--end controller battery packet--"sv;
} }
void void print(void *payload) {
print(void *payload) {
auto header = (PNV_INPUT_HEADER) payload; auto header = (PNV_INPUT_HEADER) payload;
switch (util::endian::little(header->magic)) { switch (util::endian::little(header->magic)) {
@@ -451,8 +441,7 @@ namespace input {
} }
} }
void void passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
@@ -468,8 +457,7 @@ namespace input {
* @param size The size of the client's surface containing the value. * @param size The size of the client's surface containing the value.
* @return The host-relative coordinate pair if a touchport is available. * @return The host-relative coordinate pair if a touchport is available.
*/ */
std::optional<std::pair<float, float>> std::optional<std::pair<float, float>> client_to_touchport(std::shared_ptr<input_t> &input, const std::pair<float, float> &val, const std::pair<float, float> &size) {
client_to_touchport(std::shared_ptr<input_t> &input, const std::pair<float, float> &val, const std::pair<float, float> &size) {
auto &touch_port_event = input->touch_port_event; auto &touch_port_event = input->touch_port_event;
auto &touch_port = input->touch_port; auto &touch_port = input->touch_port;
if (touch_port_event->peek()) { if (touch_port_event->peek()) {
@@ -492,7 +480,7 @@ namespace input {
x = std::clamp(x, offsetX, (size.first * scalarX) - offsetX); x = std::clamp(x, offsetX, (size.first * scalarX) - offsetX);
y = std::clamp(y, offsetY, (size.second * scalarY) - offsetY); y = std::clamp(y, offsetY, (size.second * scalarY) - offsetY);
return std::pair { (x - offsetX) * touch_port.scalar_inv, (y - offsetY) * touch_port.scalar_inv }; return std::pair {(x - offsetX) * touch_port.scalar_inv, (y - offsetY) * touch_port.scalar_inv};
} }
/** /**
@@ -502,8 +490,7 @@ namespace input {
* @param scalar The scalar cartesian coordinate pair. * @param scalar The scalar cartesian coordinate pair.
* @return The scaled radial coordinate. * @return The scaled radial coordinate.
*/ */
float float multiply_polar_by_cartesian_scalar(float r, float angle, const std::pair<float, float> &scalar) {
multiply_polar_by_cartesian_scalar(float r, float angle, const std::pair<float, float> &scalar) {
// Convert polar to cartesian coordinates // Convert polar to cartesian coordinates
float x = r * std::cos(angle); float x = r * std::cos(angle);
float y = r * std::sin(angle); float y = r * std::sin(angle);
@@ -516,8 +503,7 @@ namespace input {
return std::sqrt(std::pow(x, 2) + std::pow(y, 2)); return std::sqrt(std::pow(x, 2) + std::pow(y, 2));
} }
std::pair<float, float> std::pair<float, float> scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar) {
scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar) {
// If the rotation is unknown, we'll just scale both axes equally by using // If the rotation is unknown, we'll just scale both axes equally by using
// a 45-degree angle for our scaling calculations // a 45-degree angle for our scaling calculations
float angle = rotation == LI_ROT_UNKNOWN ? (M_PI / 4) : (rotation * (M_PI / 180)); float angle = rotation == LI_ROT_UNKNOWN ? (M_PI / 4) : (rotation * (M_PI / 180));
@@ -527,11 +513,10 @@ namespace input {
float minor = val.second != 0.0f ? val.second : val.first; float minor = val.second != 0.0f ? val.second : val.first;
// The minor axis is perpendicular to major axis so the angle must be rotated by 90 degrees // The minor axis is perpendicular to major axis so the angle must be rotated by 90 degrees
return { multiply_polar_by_cartesian_scalar(major, angle, scalar), multiply_polar_by_cartesian_scalar(minor, angle + (M_PI / 2), scalar) }; return {multiply_polar_by_cartesian_scalar(major, angle, scalar), multiply_polar_by_cartesian_scalar(minor, angle + (M_PI / 2), scalar)};
} }
void void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
@@ -554,22 +539,23 @@ namespace input {
auto width = (float) util::endian::big(packet->width); auto width = (float) util::endian::big(packet->width);
auto height = (float) util::endian::big(packet->height); auto height = (float) util::endian::big(packet->height);
auto tpcoords = client_to_touchport(input, { x, y }, { width, height }); auto tpcoords = client_to_touchport(input, {x, y}, {width, height});
if (!tpcoords) { if (!tpcoords) {
return; return;
} }
auto &touch_port = input->touch_port; auto &touch_port = input->touch_port;
platf::touch_port_t abs_port { platf::touch_port_t abs_port {
touch_port.offset_x, touch_port.offset_y, touch_port.offset_x,
touch_port.env_width, touch_port.env_height touch_port.offset_y,
touch_port.env_width,
touch_port.env_height
}; };
platf::abs_mouse(platf_input, abs_port, tpcoords->first, tpcoords->second); platf::abs_mouse(platf_input, abs_port, tpcoords->first, tpcoords->second);
} }
void void passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
@@ -617,7 +603,8 @@ namespace input {
} }
if ( if (
button == BUTTON_RIGHT && !release && button == BUTTON_RIGHT && !release &&
input->mouse_left_button_timeout > DISABLE_LEFT_BUTTON_DELAY) { input->mouse_left_button_timeout > DISABLE_LEFT_BUTTON_DELAY
) {
platf::button_mouse(platf_input, BUTTON_RIGHT, false); platf::button_mouse(platf_input, BUTTON_RIGHT, false);
platf::button_mouse(platf_input, BUTTON_RIGHT, true); platf::button_mouse(platf_input, BUTTON_RIGHT, true);
@@ -629,8 +616,7 @@ namespace input {
platf::button_mouse(platf_input, button, release); platf::button_mouse(platf_input, button, release);
} }
short short map_keycode(short keycode) {
map_keycode(short keycode) {
auto it = config::input.keybindings.find(keycode); auto it = config::input.keybindings.find(keycode);
if (it != std::end(config::input.keybindings)) { if (it != std::end(config::input.keybindings)) {
return it->second; return it->second;
@@ -642,16 +628,14 @@ namespace input {
/** /**
* @brief Update flags for keyboard shortcut combo's * @brief Update flags for keyboard shortcut combo's
*/ */
inline void inline void update_shortcutFlags(int *flags, short keyCode, bool release) {
update_shortcutFlags(int *flags, short keyCode, bool release) {
switch (keyCode) { switch (keyCode) {
case VKEY_SHIFT: case VKEY_SHIFT:
case VKEY_LSHIFT: case VKEY_LSHIFT:
case VKEY_RSHIFT: case VKEY_RSHIFT:
if (release) { if (release) {
*flags &= ~input_t::SHIFT; *flags &= ~input_t::SHIFT;
} } else {
else {
*flags |= input_t::SHIFT; *flags |= input_t::SHIFT;
} }
break; break;
@@ -660,8 +644,7 @@ namespace input {
case VKEY_RCONTROL: case VKEY_RCONTROL:
if (release) { if (release) {
*flags &= ~input_t::CTRL; *flags &= ~input_t::CTRL;
} } else {
else {
*flags |= input_t::CTRL; *flags |= input_t::CTRL;
} }
break; break;
@@ -670,16 +653,14 @@ namespace input {
case VKEY_RMENU: case VKEY_RMENU:
if (release) { if (release) {
*flags &= ~input_t::ALT; *flags &= ~input_t::ALT;
} } else {
else {
*flags |= input_t::ALT; *flags |= input_t::ALT;
} }
break; break;
} }
} }
bool bool is_modifier(uint16_t keyCode) {
is_modifier(uint16_t keyCode) {
switch (keyCode) { switch (keyCode) {
case VKEY_SHIFT: case VKEY_SHIFT:
case VKEY_LSHIFT: case VKEY_LSHIFT:
@@ -696,8 +677,7 @@ namespace input {
} }
} }
void void send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
if (!release) { if (!release) {
// Press any synthetic modifiers required for this key // Press any synthetic modifiers required for this key
if (synthetic_modifiers & MODIFIER_SHIFT) { if (synthetic_modifiers & MODIFIER_SHIFT) {
@@ -727,8 +707,7 @@ namespace input {
} }
} }
void void repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
// If key no longer pressed, stop repeating // If key no longer pressed, stop repeating
if (!key_press[make_kpid(key_code, flags)]) { if (!key_press[make_kpid(key_code, flags)]) {
key_press_repeat_id = nullptr; key_press_repeat_id = nullptr;
@@ -740,8 +719,7 @@ namespace input {
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags, synthetic_modifiers).task_id; key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags, synthetic_modifiers).task_id;
} }
void void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
if (!config::input.keyboard) { if (!config::input.keyboard) {
return; return;
} }
@@ -780,13 +758,11 @@ namespace input {
if (config::input.key_repeat_delay.count() > 0) { if (config::input.key_repeat_delay.count() > 0) {
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, keyCode, packet->flags, synthetic_modifiers).task_id; key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, keyCode, packet->flags, synthetic_modifiers).task_id;
} }
} } else {
else {
// Already released // Already released
return; return;
} }
} } else if (!release) {
else if (!release) {
// Already pressed down key // Already pressed down key
return; return;
} }
@@ -803,16 +779,14 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The scroll packet. * @param packet The scroll packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PNV_SCROLL_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PNV_SCROLL_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
if (config::input.high_resolution_scrolling) { if (config::input.high_resolution_scrolling) {
platf::scroll(platf_input, util::endian::big(packet->scrollAmt1)); platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
} } else {
else {
input->accumulated_vscroll_delta += util::endian::big(packet->scrollAmt1); input->accumulated_vscroll_delta += util::endian::big(packet->scrollAmt1);
auto full_ticks = input->accumulated_vscroll_delta / WHEEL_DELTA; auto full_ticks = input->accumulated_vscroll_delta / WHEEL_DELTA;
if (full_ticks) { if (full_ticks) {
@@ -828,16 +802,14 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The scroll packet. * @param packet The scroll packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_HSCROLL_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_HSCROLL_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
if (config::input.high_resolution_scrolling) { if (config::input.high_resolution_scrolling) {
platf::hscroll(platf_input, util::endian::big(packet->scrollAmount)); platf::hscroll(platf_input, util::endian::big(packet->scrollAmount));
} } else {
else {
input->accumulated_hscroll_delta += util::endian::big(packet->scrollAmount); input->accumulated_hscroll_delta += util::endian::big(packet->scrollAmount);
auto full_ticks = input->accumulated_hscroll_delta / WHEEL_DELTA; auto full_ticks = input->accumulated_hscroll_delta / WHEEL_DELTA;
if (full_ticks) { if (full_ticks) {
@@ -848,8 +820,7 @@ namespace input {
} }
} }
void void passthrough(PNV_UNICODE_PACKET packet) {
passthrough(PNV_UNICODE_PACKET packet) {
if (!config::input.keyboard) { if (!config::input.keyboard) {
return; return;
} }
@@ -863,8 +834,7 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The controller arrival packet. * @param packet The controller arrival packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_ARRIVAL_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_ARRIVAL_PACKET packet) {
if (!config::input.controller) { if (!config::input.controller) {
return; return;
} }
@@ -891,7 +861,7 @@ namespace input {
} }
// Allocate a new gamepad // Allocate a new gamepad
if (platf::alloc_gamepad(platf_input, { id, packet->controllerNumber }, arrival, input->feedback_queue)) { if (platf::alloc_gamepad(platf_input, {id, packet->controllerNumber}, arrival, input->feedback_queue)) {
free_id(gamepadMask, id); free_id(gamepadMask, id);
return; return;
} }
@@ -904,25 +874,23 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The touch packet. * @param packet The touch packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_TOUCH_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_TOUCH_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
// Convert the client normalized coordinates to touchport coordinates // Convert the client normalized coordinates to touchport coordinates
auto coords = client_to_touchport(input, auto coords = client_to_touchport(input, {from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f, from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f}, {65535.f, 65535.f});
{ from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f,
from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f },
{ 65535.f, 65535.f });
if (!coords) { if (!coords) {
return; return;
} }
auto &touch_port = input->touch_port; auto &touch_port = input->touch_port;
platf::touch_port_t abs_port { platf::touch_port_t abs_port {
touch_port.offset_x, touch_port.offset_y, touch_port.offset_x,
touch_port.env_width, touch_port.env_height touch_port.offset_y,
touch_port.env_width,
touch_port.env_height
}; };
// Renormalize the coordinates // Renormalize the coordinates
@@ -937,10 +905,11 @@ namespace input {
// Normalize the contact area based on the touchport // Normalize the contact area based on the touchport
auto contact_area = scale_client_contact_area( auto contact_area = scale_client_contact_area(
{ from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f, {from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f }, from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f},
rotation, rotation,
{ abs_port.width / 65535.f, abs_port.height / 65535.f }); {abs_port.width / 65535.f, abs_port.height / 65535.f}
);
platf::touch_input_t touch { platf::touch_input_t touch {
packet->eventType, packet->eventType,
@@ -961,25 +930,23 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The pen packet. * @param packet The pen packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_PEN_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_PEN_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
// Convert the client normalized coordinates to touchport coordinates // Convert the client normalized coordinates to touchport coordinates
auto coords = client_to_touchport(input, auto coords = client_to_touchport(input, {from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f, from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f}, {65535.f, 65535.f});
{ from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f,
from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f },
{ 65535.f, 65535.f });
if (!coords) { if (!coords) {
return; return;
} }
auto &touch_port = input->touch_port; auto &touch_port = input->touch_port;
platf::touch_port_t abs_port { platf::touch_port_t abs_port {
touch_port.offset_x, touch_port.offset_y, touch_port.offset_x,
touch_port.env_width, touch_port.env_height touch_port.offset_y,
touch_port.env_width,
touch_port.env_height
}; };
// Renormalize the coordinates // Renormalize the coordinates
@@ -994,10 +961,11 @@ namespace input {
// Normalize the contact area based on the touchport // Normalize the contact area based on the touchport
auto contact_area = scale_client_contact_area( auto contact_area = scale_client_contact_area(
{ from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f, {from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f }, from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f},
rotation, rotation,
{ abs_port.width / 65535.f, abs_port.height / 65535.f }); {abs_port.width / 65535.f, abs_port.height / 65535.f}
);
platf::pen_input_t pen { platf::pen_input_t pen {
packet->eventType, packet->eventType,
@@ -1020,8 +988,7 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The controller touch packet. * @param packet The controller touch packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_TOUCH_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_TOUCH_PACKET packet) {
if (!config::input.controller) { if (!config::input.controller) {
return; return;
} }
@@ -1038,7 +1005,7 @@ namespace input {
} }
platf::gamepad_touch_t touch { platf::gamepad_touch_t touch {
{ gamepad.id, packet->controllerNumber }, {gamepad.id, packet->controllerNumber},
packet->eventType, packet->eventType,
util::endian::little(packet->pointerId), util::endian::little(packet->pointerId),
from_clamped_netfloat(packet->x, 0.0f, 1.0f), from_clamped_netfloat(packet->x, 0.0f, 1.0f),
@@ -1054,8 +1021,7 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The controller motion packet. * @param packet The controller motion packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_MOTION_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_MOTION_PACKET packet) {
if (!config::input.controller) { if (!config::input.controller) {
return; return;
} }
@@ -1072,7 +1038,7 @@ namespace input {
} }
platf::gamepad_motion_t motion { platf::gamepad_motion_t motion {
{ gamepad.id, packet->controllerNumber }, {gamepad.id, packet->controllerNumber},
packet->motionType, packet->motionType,
from_netfloat(packet->x), from_netfloat(packet->x),
from_netfloat(packet->y), from_netfloat(packet->y),
@@ -1087,8 +1053,7 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The controller battery packet. * @param packet The controller battery packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_BATTERY_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_BATTERY_PACKET packet) {
if (!config::input.controller) { if (!config::input.controller) {
return; return;
} }
@@ -1105,7 +1070,7 @@ namespace input {
} }
platf::gamepad_battery_t battery { platf::gamepad_battery_t battery {
{ gamepad.id, packet->controllerNumber }, {gamepad.id, packet->controllerNumber},
packet->batteryState, packet->batteryState,
packet->batteryPercentage packet->batteryPercentage
}; };
@@ -1113,8 +1078,7 @@ namespace input {
platf::gamepad_battery(platf_input, battery); platf::gamepad_battery(platf_input, battery);
} }
void void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
if (!config::input.controller) { if (!config::input.controller) {
return; return;
} }
@@ -1135,14 +1099,13 @@ namespace input {
return; return;
} }
if (platf::alloc_gamepad(platf_input, { id, (uint8_t) packet->controllerNumber }, {}, input->feedback_queue)) { if (platf::alloc_gamepad(platf_input, {id, (uint8_t) packet->controllerNumber}, {}, input->feedback_queue)) {
free_id(gamepadMask, id); free_id(gamepadMask, id);
return; return;
} }
gamepad.id = id; gamepad.id = id;
} } else if (!(packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id >= 0) {
else if (!(packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id >= 0) {
// If this is the final event for a gamepad being removed, free the gamepad and return. // If this is the final event for a gamepad being removed, free the gamepad and return.
free_gamepad(platf_input, gamepad.id); free_gamepad(platf_input, gamepad.id);
gamepad.id = -1; gamepad.id = -1;
@@ -1219,8 +1182,7 @@ namespace input {
gamepad.back_timeout_id = task_pool.pushDelayed(std::move(f), config::input.back_button_timeout).task_id; gamepad.back_timeout_id = task_pool.pushDelayed(std::move(f), config::input.back_button_timeout).task_id;
} }
} } else if (gamepad.back_timeout_id) {
else if (gamepad.back_timeout_id) {
task_pool.cancel(gamepad.back_timeout_id); task_pool.cancel(gamepad.back_timeout_id);
gamepad.back_timeout_id = nullptr; gamepad.back_timeout_id = nullptr;
} }
@@ -1243,8 +1205,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PNV_REL_MOUSE_MOVE_PACKET dest, PNV_REL_MOUSE_MOVE_PACKET src) {
batch(PNV_REL_MOUSE_MOVE_PACKET dest, PNV_REL_MOUSE_MOVE_PACKET src) {
short deltaX, deltaY; short deltaX, deltaY;
// Batching is safe as long as the result doesn't overflow a 16-bit integer // Batching is safe as long as the result doesn't overflow a 16-bit integer
@@ -1267,8 +1228,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PNV_ABS_MOUSE_MOVE_PACKET dest, PNV_ABS_MOUSE_MOVE_PACKET src) {
batch(PNV_ABS_MOUSE_MOVE_PACKET dest, PNV_ABS_MOUSE_MOVE_PACKET src) {
// Batching must only happen if the reference width and height don't change // Batching must only happen if the reference width and height don't change
if (dest->width != src->width || dest->height != src->height) { if (dest->width != src->width || dest->height != src->height) {
return batch_result_e::terminate_batch; return batch_result_e::terminate_batch;
@@ -1285,8 +1245,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PNV_SCROLL_PACKET dest, PNV_SCROLL_PACKET src) {
batch(PNV_SCROLL_PACKET dest, PNV_SCROLL_PACKET src) {
short scrollAmt; short scrollAmt;
// Batching is safe as long as the result doesn't overflow a 16-bit integer // Batching is safe as long as the result doesn't overflow a 16-bit integer
@@ -1306,8 +1265,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PSS_HSCROLL_PACKET dest, PSS_HSCROLL_PACKET src) {
batch(PSS_HSCROLL_PACKET dest, PSS_HSCROLL_PACKET src) {
short scrollAmt; short scrollAmt;
// Batching is safe as long as the result doesn't overflow a 16-bit integer // Batching is safe as long as the result doesn't overflow a 16-bit integer
@@ -1326,8 +1284,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PNV_MULTI_CONTROLLER_PACKET dest, PNV_MULTI_CONTROLLER_PACKET src) {
batch(PNV_MULTI_CONTROLLER_PACKET dest, PNV_MULTI_CONTROLLER_PACKET src) {
// Do not allow batching if the active controllers change // Do not allow batching if the active controllers change
if (dest->activeGamepadMask != src->activeGamepadMask) { if (dest->activeGamepadMask != src->activeGamepadMask) {
return batch_result_e::terminate_batch; return batch_result_e::terminate_batch;
@@ -1355,8 +1312,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PSS_TOUCH_PACKET dest, PSS_TOUCH_PACKET src) {
batch(PSS_TOUCH_PACKET dest, PSS_TOUCH_PACKET src) {
// Only batch hover or move events // Only batch hover or move events
if (dest->eventType != LI_TOUCH_EVENT_MOVE && if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
dest->eventType != LI_TOUCH_EVENT_HOVER) { dest->eventType != LI_TOUCH_EVENT_HOVER) {
@@ -1390,8 +1346,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PSS_PEN_PACKET dest, PSS_PEN_PACKET src) {
batch(PSS_PEN_PACKET dest, PSS_PEN_PACKET src) {
// Only batch hover or move events // Only batch hover or move events
if (dest->eventType != LI_TOUCH_EVENT_MOVE && if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
dest->eventType != LI_TOUCH_EVENT_HOVER) { dest->eventType != LI_TOUCH_EVENT_HOVER) {
@@ -1424,8 +1379,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PSS_CONTROLLER_TOUCH_PACKET dest, PSS_CONTROLLER_TOUCH_PACKET src) {
batch(PSS_CONTROLLER_TOUCH_PACKET dest, PSS_CONTROLLER_TOUCH_PACKET src) {
// Only batch hover or move events // Only batch hover or move events
if (dest->eventType != LI_TOUCH_EVENT_MOVE && if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
dest->eventType != LI_TOUCH_EVENT_HOVER) { dest->eventType != LI_TOUCH_EVENT_HOVER) {
@@ -1465,8 +1419,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PSS_CONTROLLER_MOTION_PACKET dest, PSS_CONTROLLER_MOTION_PACKET src) {
batch(PSS_CONTROLLER_MOTION_PACKET dest, PSS_CONTROLLER_MOTION_PACKET src) {
// We can only batch entries for the same controller, but allow batching attempts to continue // We can only batch entries for the same controller, but allow batching attempts to continue
// in case we have more packets for this controller later in the queue. // in case we have more packets for this controller later in the queue.
if (dest->controllerNumber != src->controllerNumber) { if (dest->controllerNumber != src->controllerNumber) {
@@ -1489,8 +1442,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PNV_INPUT_HEADER dest, PNV_INPUT_HEADER src) {
batch(PNV_INPUT_HEADER dest, PNV_INPUT_HEADER src) {
// We can only batch if the packet types are the same // We can only batch if the packet types are the same
if (dest->magic != src->magic) { if (dest->magic != src->magic) {
return batch_result_e::terminate_batch; return batch_result_e::terminate_batch;
@@ -1526,8 +1478,7 @@ namespace input {
* @brief Called on a thread pool thread to process an input message. * @brief Called on a thread pool thread to process an input message.
* @param input The input context pointer. * @param input The input context pointer.
*/ */
void void passthrough_next_message(std::shared_ptr<input_t> input) {
passthrough_next_message(std::shared_ptr<input_t> input) {
// 'entry' backs the 'payload' pointer, so they must remain in scope together // 'entry' backs the 'payload' pointer, so they must remain in scope together
std::vector<uint8_t> entry; std::vector<uint8_t> entry;
PNV_INPUT_HEADER payload; PNV_INPUT_HEADER payload;
@@ -1558,12 +1509,10 @@ namespace input {
if (batch_result == batch_result_e::terminate_batch) { if (batch_result == batch_result_e::terminate_batch) {
// Stop batching // Stop batching
break; break;
} } else if (batch_result == batch_result_e::batched) {
else if (batch_result == batch_result_e::batched) {
// Erase this entry since it was batched // Erase this entry since it was batched
i = input->input_queue.erase(i); i = input->input_queue.erase(i);
} } else {
else {
// We couldn't batch this entry, but try to batch later entries. // We couldn't batch this entry, but try to batch later entries.
i++; i++;
} }
@@ -1627,8 +1576,7 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param input_data The input message. * @param input_data The input message.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data, const crypto::PERM& permission) {
passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data, const crypto::PERM& permission) {
// No input permissions at all // No input permissions at all
if (!(permission & crypto::PERM::_all_inputs)) { if (!(permission & crypto::PERM::_all_inputs)) {
return; return;
@@ -1695,8 +1643,7 @@ namespace input {
task_pool.push(passthrough_next_message, input); task_pool.push(passthrough_next_message, input);
} }
void void reset(std::shared_ptr<input_t> &input) {
reset(std::shared_ptr<input_t> &input) {
task_pool.cancel(key_press_repeat_id); task_pool.cancel(key_press_repeat_id);
task_pool.cancel(input->mouse_left_button_timeout); task_pool.cancel(input->mouse_left_button_timeout);
@@ -1727,15 +1674,13 @@ namespace input {
} }
}; };
[[nodiscard]] std::unique_ptr<platf::deinit_t> [[nodiscard]] std::unique_ptr<platf::deinit_t> init() {
init() {
platf_input = platf::input(); platf_input = platf::input();
return std::make_unique<deinit_t>(); return std::make_unique<deinit_t>();
} }
bool bool probe_gamepads() {
probe_gamepads() {
auto input = static_cast<platf::input_t *>(platf_input.get()); auto input = static_cast<platf::input_t *>(platf_input.get());
const auto gamepads = platf::supported_gamepads(input); const auto gamepads = platf::supported_gamepads(input);
for (auto &gamepad : gamepads) { for (auto &gamepad : gamepads) {
@@ -1746,11 +1691,11 @@ namespace input {
return true; return true;
} }
std::shared_ptr<input_t> std::shared_ptr<input_t> alloc(safe::mail_t mail) {
alloc(safe::mail_t mail) {
auto input = std::make_shared<input_t>( auto input = std::make_shared<input_t>(
mail->event<input::touch_port_t>(mail::touch_port), mail->event<input::touch_port_t>(mail::touch_port),
mail->queue<platf::gamepad_feedback_msg_t>(mail::gamepad_feedback)); mail->queue<platf::gamepad_feedback_msg_t>(mail::gamepad_feedback)
);
// Workaround to ensure new frames will be captured when a client connects // Workaround to ensure new frames will be captured when a client connects
task_pool.pushDelayed([]() { task_pool.pushDelayed([]() {

View File

@@ -4,8 +4,10 @@
*/ */
#pragma once #pragma once
// standard includes
#include <functional> #include <functional>
// local includes
#include "platform/common.h" #include "platform/common.h"
#include "thread_safe.h" #include "thread_safe.h"
#include "crypto.h" #include "crypto.h"
@@ -13,21 +15,15 @@
namespace input { namespace input {
struct input_t; struct input_t;
void void print(void *input);
print(void *input); void reset(std::shared_ptr<input_t> &input);
void void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data, const crypto::PERM& permission);
reset(std::shared_ptr<input_t> &input);
void
passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data, const crypto::PERM& permission);
[[nodiscard]] std::unique_ptr<platf::deinit_t> [[nodiscard]] std::unique_ptr<platf::deinit_t> init();
init();
bool bool probe_gamepads();
probe_gamepads();
std::shared_ptr<input_t> std::shared_ptr<input_t> alloc(safe::mail_t mail);
alloc(safe::mail_t mail);
struct touch_port_t: public platf::touch_port_t { struct touch_port_t: public platf::touch_port_t {
int env_width, env_height; int env_width, env_height;
@@ -37,8 +33,7 @@ namespace input {
float scalar_inv; float scalar_inv;
explicit explicit operator bool() const {
operator bool() const {
return width != 0 && height != 0 && env_width != 0 && env_height != 0; return width != 0 && height != 0 && env_width != 0 && env_height != 0;
} }
}; };
@@ -50,6 +45,5 @@ namespace input {
* @param scalar The scalar cartesian coordinate pair. * @param scalar The scalar cartesian coordinate pair.
* @return The major and minor axis pair. * @return The major and minor axis pair.
*/ */
std::pair<float, float> std::pair<float, float> scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar);
scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar);
} // namespace input } // namespace input

View File

@@ -48,15 +48,13 @@ namespace logging {
deinit(); deinit();
} }
void void deinit() {
deinit() {
log_flush(); log_flush();
bl::core::get()->remove_sink(sink); bl::core::get()->remove_sink(sink);
sink.reset(); sink.reset();
} }
void void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os) {
formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os) {
constexpr const char *message = "Message"; constexpr const char *message = "Message";
constexpr const char *severity = "Severity"; constexpr const char *severity = "Severity";
@@ -91,7 +89,8 @@ namespace logging {
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>( auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now - std::chrono::time_point_cast<std::chrono::seconds>(now)); now - std::chrono::time_point_cast<std::chrono::seconds>(now)
);
auto t = std::chrono::system_clock::to_time_t(now); auto t = std::chrono::system_clock::to_time_t(now);
auto lt = *std::localtime(&t); auto lt = *std::localtime(&t);
@@ -100,8 +99,7 @@ namespace logging {
<< log_type << view.attribute_values()[message].extract<std::string>(); << log_type << view.attribute_values()[message].extract<std::string>();
} }
[[nodiscard]] std::unique_ptr<deinit_t> [[nodiscard]] std::unique_ptr<deinit_t> init(int min_log_level, const std::string &log_file) {
init(int min_log_level, const std::string &log_file) {
if (sink) { if (sink) {
// Deinitialize the logging system before reinitializing it. This can probably only ever be hit in tests. // Deinitialize the logging system before reinitializing it. This can probably only ever be hit in tests.
deinit(); deinit();
@@ -123,8 +121,8 @@ namespace logging {
sink = boost::make_shared<text_sink>(); sink = boost::make_shared<text_sink>();
#ifndef SUNSHINE_TESTS #ifndef SUNSHINE_TESTS
boost::shared_ptr<std::ostream> stream { &std::cout, boost::null_deleter() }; boost::shared_ptr<std::ostream> stream {&std::cout, boost::null_deleter()};
sink->locked_backend()->add_stream(stream); sink->locked_backend()->add_stream(stream);
#endif #endif
sink->locked_backend()->add_stream(boost::make_shared<std::ofstream>(log_file)); sink->locked_backend()->add_stream(boost::make_shared<std::ofstream>(log_file));
@@ -139,13 +137,10 @@ namespace logging {
return std::make_unique<deinit_t>(); return std::make_unique<deinit_t>();
} }
void setup_av_logging(int min_log_level) {
void
setup_av_logging(int min_log_level) {
if (min_log_level >= 1) { if (min_log_level >= 1) {
av_log_set_level(AV_LOG_QUIET); av_log_set_level(AV_LOG_QUIET);
} } else {
else {
av_log_set_level(AV_LOG_DEBUG); av_log_set_level(AV_LOG_DEBUG);
} }
av_log_set_callback([](void *ptr, int level, const char *fmt, va_list vl) { av_log_set_callback([](void *ptr, int level, const char *fmt, va_list vl) {
@@ -157,28 +152,23 @@ namespace logging {
// We print AV_LOG_FATAL at the error level. FFmpeg prints things as fatal that // We print AV_LOG_FATAL at the error level. FFmpeg prints things as fatal that
// are expected in some cases, such as lack of codec support or similar things. // are expected in some cases, such as lack of codec support or similar things.
BOOST_LOG(error) << buffer; BOOST_LOG(error) << buffer;
} } else if (level <= AV_LOG_WARNING) {
else if (level <= AV_LOG_WARNING) {
BOOST_LOG(warning) << buffer; BOOST_LOG(warning) << buffer;
} } else if (level <= AV_LOG_INFO) {
else if (level <= AV_LOG_INFO) {
BOOST_LOG(info) << buffer; BOOST_LOG(info) << buffer;
} } else if (level <= AV_LOG_VERBOSE) {
else if (level <= AV_LOG_VERBOSE) {
// AV_LOG_VERBOSE is less verbose than AV_LOG_DEBUG // AV_LOG_VERBOSE is less verbose than AV_LOG_DEBUG
BOOST_LOG(debug) << buffer; BOOST_LOG(debug) << buffer;
} } else {
else {
BOOST_LOG(verbose) << buffer; BOOST_LOG(verbose) << buffer;
} }
}); });
} }
void void setup_libdisplaydevice_logging(int min_log_level) {
setup_libdisplaydevice_logging(int min_log_level) { constexpr int min_level {static_cast<int>(display_device::Logger::LogLevel::verbose)};
constexpr int min_level { static_cast<int>(display_device::Logger::LogLevel::verbose) }; constexpr int max_level {static_cast<int>(display_device::Logger::LogLevel::fatal)};
constexpr int max_level { static_cast<int>(display_device::Logger::LogLevel::fatal) }; const auto log_level {static_cast<display_device::Logger::LogLevel>(std::min(std::max(min_level, min_log_level), max_level))};
const auto log_level { static_cast<display_device::Logger::LogLevel>(std::min(std::max(min_level, min_log_level), max_level)) };
display_device::Logger::get().setLogLevel(log_level); display_device::Logger::get().setLogLevel(log_level);
display_device::Logger::get().setCustomCallback([](const display_device::Logger::LogLevel level, const std::string &message) { display_device::Logger::get().setCustomCallback([](const display_device::Logger::LogLevel level, const std::string &message) {
@@ -205,15 +195,13 @@ namespace logging {
}); });
} }
void void log_flush() {
log_flush() {
if (sink) { if (sink) {
sink->flush(); sink->flush();
} }
} }
void void print_help(const char *name) {
print_help(const char *name) {
std::cout std::cout
<< "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl << "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl
<< " Any configurable option can be overwritten with: \"name=value\""sv << std::endl << " Any configurable option can be overwritten with: \"name=value\""sv << std::endl
@@ -233,13 +221,11 @@ namespace logging {
<< std::endl; << std::endl;
} }
std::string std::string bracket(const std::string &input) {
bracket(const std::string &input) {
return "["s + input + "]"s; return "["s + input + "]"s;
} }
std::wstring std::wstring bracket(const std::wstring &input) {
bracket(const std::wstring &input) {
return L"["s + input + L"]"s; return L"["s + input + L"]"s;
} }

View File

@@ -41,11 +41,9 @@ namespace logging {
* deinit(); * deinit();
* @examples_end * @examples_end
*/ */
void void deinit();
deinit();
void void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os);
formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os);
/** /**
* @brief Initialize the logging system. * @brief Initialize the logging system.
@@ -56,22 +54,19 @@ namespace logging {
* log_init(2, "sunshine.log"); * log_init(2, "sunshine.log");
* @examples_end * @examples_end
*/ */
[[nodiscard]] std::unique_ptr<deinit_t> [[nodiscard]] std::unique_ptr<deinit_t> init(int min_log_level, const std::string &log_file);
init(int min_log_level, const std::string &log_file);
/** /**
* @brief Setup AV logging. * @brief Setup AV logging.
* @param min_log_level The log level. * @param min_log_level The log level.
*/ */
void void setup_av_logging(int min_log_level);
setup_av_logging(int min_log_level);
/** /**
* @brief Setup logging for libdisplaydevice. * @brief Setup logging for libdisplaydevice.
* @param min_log_level The log level. * @param min_log_level The log level.
*/ */
void void setup_libdisplaydevice_logging(int min_log_level);
setup_libdisplaydevice_logging(int min_log_level);
/** /**
* @brief Flush the log. * @brief Flush the log.
@@ -79,8 +74,7 @@ namespace logging {
* log_flush(); * log_flush();
* @examples_end * @examples_end
*/ */
void void log_flush();
log_flush();
/** /**
* @brief Print help to stdout. * @brief Print help to stdout.
@@ -89,8 +83,7 @@ namespace logging {
* print_help("sunshine"); * print_help("sunshine");
* @examples_end * @examples_end
*/ */
void void print_help(const char *name);
print_help(const char *name);
/** /**
* @brief A helper class for tracking and logging numerical values across a period of time * @brief A helper class for tracking and logging numerical values across a period of time
@@ -105,28 +98,24 @@ namespace logging {
* // [2024:01:01:12:00:00]: Debug: Test time value (min/max/avg): 1ms/3ms/2.00ms * // [2024:01:01:12:00:00]: Debug: Test time value (min/max/avg): 1ms/3ms/2.00ms
* @examples_end * @examples_end
*/ */
template <typename T> template<typename T>
class min_max_avg_periodic_logger { class min_max_avg_periodic_logger {
public: public:
min_max_avg_periodic_logger(boost::log::sources::severity_logger<int> &severity, min_max_avg_periodic_logger(boost::log::sources::severity_logger<int> &severity, std::string_view message, std::string_view units, std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
std::string_view message,
std::string_view units,
std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
severity(severity), severity(severity),
message(message), message(message),
units(units), units(units),
interval(interval_in_seconds), interval(interval_in_seconds),
enabled(config::sunshine.min_log_level <= severity.default_severity()) {} enabled(config::sunshine.min_log_level <= severity.default_severity()) {
}
void void collect_and_log(const T &value) {
collect_and_log(const T &value) {
if (enabled) { if (enabled) {
auto print_info = [&](const T &min_value, const T &max_value, double avg_value) { auto print_info = [&](const T &min_value, const T &max_value, double avg_value) {
auto f = stat_trackers::two_digits_after_decimal(); auto f = stat_trackers::two_digits_after_decimal();
if constexpr (std::is_floating_point_v<T>) { if constexpr (std::is_floating_point_v<T>) {
BOOST_LOG(severity.get()) << message << " (min/max/avg): " << f % min_value << units << "/" << f % max_value << units << "/" << f % avg_value << units; BOOST_LOG(severity.get()) << message << " (min/max/avg): " << f % min_value << units << "/" << f % max_value << units << "/" << f % avg_value << units;
} } else {
else {
BOOST_LOG(severity.get()) << message << " (min/max/avg): " << min_value << units << "/" << max_value << units << "/" << f % avg_value << units; BOOST_LOG(severity.get()) << message << " (min/max/avg): " << min_value << units << "/" << max_value << units << "/" << f % avg_value << units;
} }
}; };
@@ -134,18 +123,19 @@ namespace logging {
} }
} }
void void collect_and_log(std::function<T()> func) {
collect_and_log(std::function<T()> func) { if (enabled) {
if (enabled) collect_and_log(func()); collect_and_log(func());
}
} }
void void reset() {
reset() { if (enabled) {
if (enabled) tracker.reset(); tracker.reset();
}
} }
bool bool is_enabled() const {
is_enabled() const {
return enabled; return enabled;
} }
@@ -175,40 +165,41 @@ namespace logging {
*/ */
class time_delta_periodic_logger { class time_delta_periodic_logger {
public: public:
time_delta_periodic_logger(boost::log::sources::severity_logger<int> &severity, time_delta_periodic_logger(boost::log::sources::severity_logger<int> &severity, std::string_view message, std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
std::string_view message, logger(severity, message, "ms", interval_in_seconds) {
std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
logger(severity, message, "ms", interval_in_seconds) {}
void
first_point(const std::chrono::steady_clock::time_point &point) {
if (logger.is_enabled()) point1 = point;
} }
void void first_point(const std::chrono::steady_clock::time_point &point) {
first_point_now() { if (logger.is_enabled()) {
if (logger.is_enabled()) first_point(std::chrono::steady_clock::now()); point1 = point;
}
} }
void void first_point_now() {
second_point_and_log(const std::chrono::steady_clock::time_point &point) { if (logger.is_enabled()) {
first_point(std::chrono::steady_clock::now());
}
}
void second_point_and_log(const std::chrono::steady_clock::time_point &point) {
if (logger.is_enabled()) { if (logger.is_enabled()) {
logger.collect_and_log(std::chrono::duration<double, std::milli>(point - point1).count()); logger.collect_and_log(std::chrono::duration<double, std::milli>(point - point1).count());
} }
} }
void void second_point_now_and_log() {
second_point_now_and_log() { if (logger.is_enabled()) {
if (logger.is_enabled()) second_point_and_log(std::chrono::steady_clock::now()); second_point_and_log(std::chrono::steady_clock::now());
}
} }
void void reset() {
reset() { if (logger.is_enabled()) {
if (logger.is_enabled()) logger.reset(); logger.reset();
}
} }
bool bool is_enabled() const {
is_enabled() const {
return logger.is_enabled(); return logger.is_enabled();
} }
@@ -222,15 +213,13 @@ namespace logging {
* @param input Input string. * @param input Input string.
* @return Enclosed string. * @return Enclosed string.
*/ */
std::string std::string bracket(const std::string &input);
bracket(const std::string &input);
/** /**
* @brief Enclose string in square brackets. * @brief Enclose string in square brackets.
* @param input Input string. * @param input Input string.
* @return Enclosed string. * @return Enclosed string.
*/ */
std::wstring std::wstring bracket(const std::wstring &input);
bracket(const std::wstring &input);
} // namespace logging } // namespace logging

View File

@@ -30,31 +30,37 @@ extern "C" {
using namespace std::literals; using namespace std::literals;
std::map<int, std::function<void()>> signal_handlers; std::map<int, std::function<void()>> signal_handlers;
void
on_signal_forwarder(int sig) { void on_signal_forwarder(int sig) {
signal_handlers.at(sig)(); signal_handlers.at(sig)();
} }
template <class FN> template<class FN>
void void on_signal(int sig, FN &&fn) {
on_signal(int sig, FN &&fn) {
signal_handlers.emplace(sig, std::forward<FN>(fn)); signal_handlers.emplace(sig, std::forward<FN>(fn));
std::signal(sig, on_signal_forwarder); std::signal(sig, on_signal_forwarder);
} }
std::map<std::string_view, std::function<int(const char *name, int argc, char **argv)>> cmd_to_func { std::map<std::string_view, std::function<int(const char *name, int argc, char **argv)>> cmd_to_func {
{ "creds"sv, [](const char *name, int argc, char **argv) { return args::creds(name, argc, argv); } }, {"creds"sv, [](const char *name, int argc, char **argv) {
{ "help"sv, [](const char *name, int argc, char **argv) { return args::help(name); } }, return args::creds(name, argc, argv);
{ "version"sv, [](const char *name, int argc, char **argv) { return args::version(); } }, }},
{"help"sv, [](const char *name, int argc, char **argv) {
return args::help(name);
}},
{"version"sv, [](const char *name, int argc, char **argv) {
return args::version();
}},
#ifdef _WIN32 #ifdef _WIN32
{ "restore-nvprefs-undo"sv, [](const char *name, int argc, char **argv) { return args::restore_nvprefs_undo(); } }, {"restore-nvprefs-undo"sv, [](const char *name, int argc, char **argv) {
return args::restore_nvprefs_undo();
}},
#endif #endif
}; };
#ifdef _WIN32 #ifdef _WIN32
LRESULT CALLBACK LRESULT CALLBACK SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) { switch (uMsg) {
case WM_CLOSE: case WM_CLOSE:
DestroyWindow(hwnd); DestroyWindow(hwnd);
@@ -62,7 +68,8 @@ SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
case WM_DESTROY: case WM_DESTROY:
PostQuitMessage(0); PostQuitMessage(0);
return 0; return 0;
case WM_ENDSESSION: { case WM_ENDSESSION:
{
// Terminate ourselves with a blocking exit call // Terminate ourselves with a blocking exit call
std::cout << "Received WM_ENDSESSION"sv << std::endl; std::cout << "Received WM_ENDSESSION"sv << std::endl;
lifetime::exit_sunshine(0, false); lifetime::exit_sunshine(0, false);
@@ -73,8 +80,7 @@ SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
} }
} }
WINAPI BOOL WINAPI BOOL ConsoleCtrlHandler(DWORD type) {
ConsoleCtrlHandler(DWORD type) {
if (type == CTRL_CLOSE_EVENT) { if (type == CTRL_CLOSE_EVENT) {
BOOST_LOG(info) << "Console closed handler called"; BOOST_LOG(info) << "Console closed handler called";
lifetime::exit_sunshine(0, false); lifetime::exit_sunshine(0, false);
@@ -83,8 +89,7 @@ ConsoleCtrlHandler(DWORD type) {
} }
#endif #endif
int int main(int argc, char *argv[]) {
main(int argc, char *argv[]) {
lifetime::argv = argv; lifetime::argv = argv;
task_pool_util::TaskPool::task_id_t force_shutdown = nullptr; task_pool_util::TaskPool::task_id_t force_shutdown = nullptr;
@@ -190,7 +195,8 @@ main(int argc, char *argv[]) {
nullptr, nullptr,
nullptr, nullptr,
nullptr, nullptr,
nullptr); nullptr
);
session_monitor_hwnd_promise.set_value(wnd); session_monitor_hwnd_promise.set_value(wnd);
@@ -218,12 +224,10 @@ main(int argc, char *argv[]) {
if (session_monitor_join_thread_future.wait_for(1s) == std::future_status::ready) { if (session_monitor_join_thread_future.wait_for(1s) == std::future_status::ready) {
session_monitor_thread.join(); session_monitor_thread.join();
return; return;
} } else {
else {
BOOST_LOG(warning) << "session_monitor_join_thread_future reached timeout"; BOOST_LOG(warning) << "session_monitor_join_thread_future reached timeout";
} }
} } else {
else {
BOOST_LOG(warning) << "session_monitor_hwnd_future reached timeout"; BOOST_LOG(warning) << "session_monitor_hwnd_future reached timeout";
} }
@@ -327,8 +331,8 @@ main(int argc, char *argv[]) {
return lifetime::desired_exit_code; return lifetime::desired_exit_code;
} }
std::thread httpThread { nvhttp::start }; std::thread httpThread {nvhttp::start};
std::thread configThread { confighttp::start }; std::thread configThread {confighttp::start};
#ifdef _WIN32 #ifdef _WIN32
// If we're using the default port and GameStream is enabled, warn the user // If we're using the default port and GameStream is enabled, warn the user

View File

@@ -12,5 +12,4 @@
* main(1, const char* args[] = {"sunshine", nullptr}); * main(1, const char* args[] = {"sunshine", nullptr});
* @examples_end * @examples_end
*/ */
int int main(int argc, char *argv[]);
main(int argc, char *argv[]);

View File

@@ -4,6 +4,7 @@
*/ */
#pragma once #pragma once
// standard includes
#include <utility> #include <utility>
/** /**
@@ -14,7 +15,7 @@ namespace move_by_copy_util {
* When a copy is made, it moves the object * When a copy is made, it moves the object
* This allows you to move an object when a move can't be done. * This allows you to move an object when a move can't be done.
*/ */
template <class T> template<class T>
class MoveByCopy { class MoveByCopy {
public: public:
typedef T move_type; typedef T move_type;
@@ -24,7 +25,8 @@ namespace move_by_copy_util {
public: public:
explicit MoveByCopy(move_type &&to_move): explicit MoveByCopy(move_type &&to_move):
_to_move(std::move(to_move)) {} _to_move(std::move(to_move)) {
}
MoveByCopy(MoveByCopy &&other) = default; MoveByCopy(MoveByCopy &&other) = default;
@@ -32,11 +34,9 @@ namespace move_by_copy_util {
*this = other; *this = other;
} }
MoveByCopy & MoveByCopy &operator=(MoveByCopy &&other) = default;
operator=(MoveByCopy &&other) = default;
MoveByCopy & MoveByCopy &operator=(const MoveByCopy &other) {
operator=(const MoveByCopy &other) {
this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move); this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move);
return *this; return *this;
@@ -47,16 +47,14 @@ namespace move_by_copy_util {
} }
}; };
template <class T> template<class T>
MoveByCopy<T> MoveByCopy<T> cmove(T &movable) {
cmove(T &movable) {
return MoveByCopy<T>(std::move(movable)); return MoveByCopy<T>(std::move(movable));
} }
// Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller // Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
template <class T> template<class T>
MoveByCopy<T> MoveByCopy<T> const_cmove(const T &movable) {
const_cmove(const T &movable) {
return MoveByCopy<T>(std::move(const_cast<T &>(movable))); return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
} }
} // namespace move_by_copy_util } // namespace move_by_copy_util

View File

@@ -2,13 +2,16 @@
* @file src/network.cpp * @file src/network.cpp
* @brief Definitions for networking related functions. * @brief Definitions for networking related functions.
*/ */
#include "network.h" // standard includes
#include "config.h"
#include "logging.h"
#include "utility.h"
#include <algorithm> #include <algorithm>
#include <sstream> #include <sstream>
// local includes
#include "config.h"
#include "logging.h"
#include "network.h"
#include "utility.h"
using namespace std::literals; using namespace std::literals;
namespace ip = boost::asio::ip; namespace ip = boost::asio::ip;
@@ -33,8 +36,7 @@ namespace net {
ip::make_network_v6("fe80::/64"sv), ip::make_network_v6("fe80::/64"sv),
}; };
net_e net_e from_enum_string(const std::string_view &view) {
from_enum_string(const std::string_view &view) {
if (view == "wan") { if (view == "wan") {
return WAN; return WAN;
} }
@@ -45,8 +47,7 @@ namespace net {
return PC; return PC;
} }
net_e net_e from_address(const std::string_view &view) {
from_address(const std::string_view &view) {
auto addr = normalize_address(ip::make_address(view)); auto addr = normalize_address(ip::make_address(view));
if (addr.is_v6()) { if (addr.is_v6()) {
@@ -61,8 +62,7 @@ namespace net {
return LAN; return LAN;
} }
} }
} } else {
else {
for (auto &range : pc_ips_v4) { for (auto &range : pc_ips_v4) {
if (range.hosts().find(addr.to_v4()) != range.hosts().end()) { if (range.hosts().find(addr.to_v4()) != range.hosts().end()) {
return PC; return PC;
@@ -79,8 +79,7 @@ namespace net {
return WAN; return WAN;
} }
std::string_view std::string_view to_enum_string(net_e net) {
to_enum_string(net_e net) {
switch (net) { switch (net) {
case PC: case PC:
return "pc"sv; return "pc"sv;
@@ -94,8 +93,7 @@ namespace net {
return "wan"sv; return "wan"sv;
} }
af_e af_e af_from_enum_string(const std::string_view &view) {
af_from_enum_string(const std::string_view &view) {
if (view == "ipv4") { if (view == "ipv4") {
return IPV4; return IPV4;
} }
@@ -107,8 +105,7 @@ namespace net {
return BOTH; return BOTH;
} }
std::string_view std::string_view af_to_any_address_string(af_e af) {
af_to_any_address_string(af_e af) {
switch (af) { switch (af) {
case IPV4: case IPV4:
return "0.0.0.0"sv; return "0.0.0.0"sv;
@@ -120,8 +117,7 @@ namespace net {
return "::"sv; return "::"sv;
} }
boost::asio::ip::address boost::asio::ip::address normalize_address(boost::asio::ip::address address) {
normalize_address(boost::asio::ip::address address) {
// Convert IPv6-mapped IPv4 addresses into regular IPv4 addresses // Convert IPv6-mapped IPv4 addresses into regular IPv4 addresses
if (address.is_v6()) { if (address.is_v6()) {
auto v6 = address.to_v6(); auto v6 = address.to_v6();
@@ -133,37 +129,31 @@ namespace net {
return address; return address;
} }
std::string std::string addr_to_normalized_string(boost::asio::ip::address address) {
addr_to_normalized_string(boost::asio::ip::address address) {
return normalize_address(address).to_string(); return normalize_address(address).to_string();
} }
std::string std::string addr_to_url_escaped_string(boost::asio::ip::address address) {
addr_to_url_escaped_string(boost::asio::ip::address address) {
address = normalize_address(address); address = normalize_address(address);
if (address.is_v6()) { if (address.is_v6()) {
std::stringstream ss; std::stringstream ss;
ss << '[' << address.to_string() << ']'; ss << '[' << address.to_string() << ']';
return ss.str(); return ss.str();
} } else {
else {
return address.to_string(); return address.to_string();
} }
} }
int int encryption_mode_for_address(boost::asio::ip::address address) {
encryption_mode_for_address(boost::asio::ip::address address) {
auto nettype = net::from_address(address.to_string()); auto nettype = net::from_address(address.to_string());
if (nettype == net::net_e::PC || nettype == net::net_e::LAN) { if (nettype == net::net_e::PC || nettype == net::net_e::LAN) {
return config::stream.lan_encryption_mode; return config::stream.lan_encryption_mode;
} } else {
else {
return config::stream.wan_encryption_mode; return config::stream.wan_encryption_mode;
} }
} }
host_t host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port) {
host_create(af_e af, ENetAddress &addr, std::uint16_t port) {
static std::once_flag enet_init_flag; static std::once_flag enet_init_flag;
std::call_once(enet_init_flag, []() { std::call_once(enet_init_flag, []() {
enet_initialize(); enet_initialize();
@@ -174,7 +164,7 @@ namespace net {
enet_address_set_port(&addr, port); enet_address_set_port(&addr, port);
// Maximum of 128 clients, which should be enough for anyone // Maximum of 128 clients, which should be enough for anyone
auto host = host_t { enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, 128, 0, 0, 0) }; auto host = host_t {enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, 128, 0, 0, 0)};
// Enable opportunistic QoS tagging (automatically disables if the network appears to drop tagged packets) // Enable opportunistic QoS tagging (automatically disables if the network appears to drop tagged packets)
enet_socket_set_option(host->socket, ENET_SOCKOPT_QOS, 1); enet_socket_set_option(host->socket, ENET_SOCKOPT_QOS, 1);
@@ -182,8 +172,7 @@ namespace net {
return host; return host;
} }
void void free_host(ENetHost *host) {
free_host(ENetHost *host) {
std::for_each(host->peers, host->peers + host->peerCount, [](ENetPeer &peer_ref) { std::for_each(host->peers, host->peers + host->peerCount, [](ENetPeer &peer_ref) {
ENetPeer *peer = &peer_ref; ENetPeer *peer = &peer_ref;
@@ -195,8 +184,7 @@ namespace net {
enet_host_destroy(host); enet_host_destroy(host);
} }
std::uint16_t std::uint16_t map_port(int port) {
map_port(int port) {
// calculate the port from the config port // calculate the port from the config port
auto mapped_port = (std::uint16_t)((int) config::sunshine.port + port); auto mapped_port = (std::uint16_t)((int) config::sunshine.port + port);
@@ -213,10 +201,9 @@ namespace net {
* @param hostname The hostname to use for instance name generation. * @param hostname The hostname to use for instance name generation.
* @return Hostname-based instance name or "Sunshine" if hostname is invalid. * @return Hostname-based instance name or "Sunshine" if hostname is invalid.
*/ */
std::string std::string mdns_instance_name(const std::string_view &hostname) {
mdns_instance_name(const std::string_view &hostname) {
// Start with the unmodified hostname // Start with the unmodified hostname
std::string instancename { hostname.data(), hostname.size() }; std::string instancename {hostname.data(), hostname.size()};
// Truncate to 63 characters per RFC 6763 section 7.2. // Truncate to 63 characters per RFC 6763 section 7.2.
if (instancename.size() > 63) { if (instancename.size() > 63) {
@@ -227,8 +214,7 @@ namespace net {
// Replace any spaces with dashes // Replace any spaces with dashes
if (instancename[i] == ' ') { if (instancename[i] == ' ') {
instancename[i] = '-'; instancename[i] = '-';
} } else if (!std::isalnum(instancename[i]) && instancename[i] != '-') {
else if (!std::isalnum(instancename[i]) && instancename[i] != '-') {
// Stop at the first invalid character // Stop at the first invalid character
instancename.resize(i); instancename.resize(i);
break; break;

View File

@@ -4,18 +4,19 @@
*/ */
#pragma once #pragma once
// standard includes
#include <tuple> #include <tuple>
#include <utility> #include <utility>
// lib includes
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <enet/enet.h> #include <enet/enet.h>
// local includes
#include "utility.h" #include "utility.h"
namespace net { namespace net {
void void free_host(ENetHost *host);
free_host(ENetHost *host);
/** /**
* @brief Map a specified port based on the base port. * @brief Map a specified port based on the base port.
@@ -26,8 +27,7 @@ namespace net {
* @examples_end * @examples_end
* @todo Ensure port is not already in use by another application. * @todo Ensure port is not already in use by another application.
*/ */
std::uint16_t std::uint16_t map_port(int port);
map_port(int port);
using host_t = util::safe_ptr<ENetHost, free_host>; using host_t = util::safe_ptr<ENetHost, free_host>;
using peer_t = ENetPeer *; using peer_t = ENetPeer *;
@@ -44,32 +44,26 @@ namespace net {
BOTH ///< IPv4 and IPv6 BOTH ///< IPv4 and IPv6
}; };
net_e net_e from_enum_string(const std::string_view &view);
from_enum_string(const std::string_view &view); std::string_view to_enum_string(net_e net);
std::string_view
to_enum_string(net_e net);
net_e net_e from_address(const std::string_view &view);
from_address(const std::string_view &view);
host_t host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port);
host_create(af_e af, ENetAddress &addr, std::uint16_t port);
/** /**
* @brief Get the address family enum value from a string. * @brief Get the address family enum value from a string.
* @param view The config option value. * @param view The config option value.
* @return The address family enum value. * @return The address family enum value.
*/ */
af_e af_e af_from_enum_string(const std::string_view &view);
af_from_enum_string(const std::string_view &view);
/** /**
* @brief Get the wildcard binding address for a given address family. * @brief Get the wildcard binding address for a given address family.
* @param af Address family. * @param af Address family.
* @return Normalized address. * @return Normalized address.
*/ */
std::string_view std::string_view af_to_any_address_string(af_e af);
af_to_any_address_string(af_e af);
/** /**
* @brief Convert an address to a normalized form. * @brief Convert an address to a normalized form.
@@ -77,8 +71,7 @@ namespace net {
* @param address The address to normalize. * @param address The address to normalize.
* @return Normalized address. * @return Normalized address.
*/ */
boost::asio::ip::address boost::asio::ip::address normalize_address(boost::asio::ip::address address);
normalize_address(boost::asio::ip::address address);
/** /**
* @brief Get the given address in normalized string form. * @brief Get the given address in normalized string form.
@@ -86,8 +79,7 @@ namespace net {
* @param address The address to normalize. * @param address The address to normalize.
* @return Normalized address in string form. * @return Normalized address in string form.
*/ */
std::string std::string addr_to_normalized_string(boost::asio::ip::address address);
addr_to_normalized_string(boost::asio::ip::address address);
/** /**
* @brief Get the given address in a normalized form for the host portion of a URL. * @brief Get the given address in a normalized form for the host portion of a URL.
@@ -95,22 +87,19 @@ namespace net {
* @param address The address to normalize and escape. * @param address The address to normalize and escape.
* @return Normalized address in URL-escaped string. * @return Normalized address in URL-escaped string.
*/ */
std::string std::string addr_to_url_escaped_string(boost::asio::ip::address address);
addr_to_url_escaped_string(boost::asio::ip::address address);
/** /**
* @brief Get the encryption mode for the given remote endpoint address. * @brief Get the encryption mode for the given remote endpoint address.
* @param address The address used to look up the desired encryption mode. * @param address The address used to look up the desired encryption mode.
* @return The WAN or LAN encryption mode, based on the provided address. * @return The WAN or LAN encryption mode, based on the provided address.
*/ */
int int encryption_mode_for_address(boost::asio::ip::address address);
encryption_mode_for_address(boost::asio::ip::address address);
/** /**
* @brief Returns a string for use as the instance name for mDNS. * @brief Returns a string for use as the instance name for mDNS.
* @param hostname The hostname to use for instance name generation. * @param hostname The hostname to use for instance name generation.
* @return Hostname-based instance name or "Sunshine" if hostname is invalid. * @return Hostname-based instance name or "Sunshine" if hostname is invalid.
*/ */
std::string std::string mdns_instance_name(const std::string_view &hostname);
mdns_instance_name(const std::string_view &hostname);
} // namespace net } // namespace net

View File

@@ -2,8 +2,10 @@
* @file src/nvenc/nvenc_base.cpp * @file src/nvenc/nvenc_base.cpp
* @brief Definitions for abstract platform-agnostic base of standalone NVENC encoder. * @brief Definitions for abstract platform-agnostic base of standalone NVENC encoder.
*/ */
// this include
#include "nvenc_base.h" #include "nvenc_base.h"
// local includes
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/utility.h" #include "src/utility.h"
@@ -21,9 +23,10 @@
namespace { namespace {
GUID GUID quality_preset_guid_from_number(unsigned number) {
quality_preset_guid_from_number(unsigned number) { if (number > 7) {
if (number > 7) number = 7; number = 7;
}
switch (number) { switch (number) {
case 1: case 1:
@@ -50,13 +53,11 @@ namespace {
} }
}; };
bool bool equal_guids(const GUID &guid1, const GUID &guid2) {
equal_guids(const GUID &guid1, const GUID &guid2) {
return std::memcmp(&guid1, &guid2, sizeof(GUID)) == 0; return std::memcmp(&guid1, &guid2, sizeof(GUID)) == 0;
} }
auto auto quality_preset_string_from_guid(const GUID &guid) {
quality_preset_string_from_guid(const GUID &guid) {
if (equal_guids(guid, NV_ENC_PRESET_P1_GUID)) { if (equal_guids(guid, NV_ENC_PRESET_P1_GUID)) {
return "P1"; return "P1";
} }
@@ -93,23 +94,28 @@ namespace nvenc {
// Use destroy_encoder() instead // Use destroy_encoder() instead
} }
bool bool nvenc_base::create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format) {
nvenc_base::create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format) {
// Pick the minimum NvEncode API version required to support the specified codec // Pick the minimum NvEncode API version required to support the specified codec
// to maximize driver compatibility. AV1 was introduced in SDK v12.0. // to maximize driver compatibility. AV1 was introduced in SDK v12.0.
minimum_api_version = (client_config.videoFormat <= 1) ? MAKE_NVENC_VER(11U, 0U) : MAKE_NVENC_VER(12U, 0U); minimum_api_version = (client_config.videoFormat <= 1) ? MAKE_NVENC_VER(11U, 0U) : MAKE_NVENC_VER(12U, 0U);
if (!nvenc && !init_library()) return false; if (!nvenc && !init_library()) {
return false;
}
if (encoder) destroy_encoder(); if (encoder) {
auto fail_guard = util::fail_guard([this] { destroy_encoder(); }); destroy_encoder();
}
auto fail_guard = util::fail_guard([this] {
destroy_encoder();
});
encoder_params.width = client_config.width; encoder_params.width = client_config.width;
encoder_params.height = client_config.height; encoder_params.height = client_config.height;
encoder_params.buffer_format = buffer_format; encoder_params.buffer_format = buffer_format;
encoder_params.rfi = true; encoder_params.rfi = true;
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = { min_struct_version(NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER) }; NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = {min_struct_version(NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER)};
session_params.device = device; session_params.device = device;
session_params.deviceType = device_type; session_params.deviceType = device_type;
session_params.apiVersion = minimum_api_version; session_params.apiVersion = minimum_api_version;
@@ -130,7 +136,7 @@ namespace nvenc {
return false; return false;
} }
NV_ENC_INITIALIZE_PARAMS init_params = { min_struct_version(NV_ENC_INITIALIZE_PARAMS_VER) }; NV_ENC_INITIALIZE_PARAMS init_params = {min_struct_version(NV_ENC_INITIALIZE_PARAMS_VER)};
switch (client_config.videoFormat) { switch (client_config.videoFormat) {
case 0: case 0:
@@ -164,7 +170,7 @@ namespace nvenc {
} }
auto get_encoder_cap = [&](NV_ENC_CAPS cap) { auto get_encoder_cap = [&](NV_ENC_CAPS cap) {
NV_ENC_CAPS_PARAM param = { min_struct_version(NV_ENC_CAPS_PARAM_VER), cap }; NV_ENC_CAPS_PARAM param = {min_struct_version(NV_ENC_CAPS_PARAM_VER), cap};
int value = 0; int value = 0;
nvenc->nvEncGetEncodeCaps(encoder, init_params.encodeGUID, &param, &value); nvenc->nvEncGetEncodeCaps(encoder, init_params.encodeGUID, &param, &value);
return value; return value;
@@ -217,7 +223,7 @@ namespace nvenc {
init_params.frameRateNum = client_config.framerate; init_params.frameRateNum = client_config.framerate;
init_params.frameRateDen = 1; init_params.frameRateDen = 1;
NV_ENC_PRESET_CONFIG preset_config = { min_struct_version(NV_ENC_PRESET_CONFIG_VER), { min_struct_version(NV_ENC_CONFIG_VER, 7, 8) } }; NV_ENC_PRESET_CONFIG preset_config = {min_struct_version(NV_ENC_PRESET_CONFIG_VER), {min_struct_version(NV_ENC_CONFIG_VER, 7, 8)}};
if (nvenc_failed(nvenc->nvEncGetEncodePresetConfigEx(encoder, init_params.encodeGUID, init_params.presetGUID, init_params.tuningInfo, &preset_config))) { if (nvenc_failed(nvenc->nvEncGetEncodePresetConfigEx(encoder, init_params.encodeGUID, init_params.presetGUID, init_params.tuningInfo, &preset_config))) {
BOOST_LOG(error) << "NvEnc: NvEncGetEncodePresetConfigEx() failed: " << last_nvenc_error_string; BOOST_LOG(error) << "NvEnc: NvEncGetEncodePresetConfigEx() failed: " << last_nvenc_error_string;
return false; return false;
@@ -259,8 +265,7 @@ namespace nvenc {
auto set_ref_frames = [&](uint32_t &ref_frames_option, NV_ENC_NUM_REF_FRAMES &L0_option, uint32_t ref_frames_default) { auto set_ref_frames = [&](uint32_t &ref_frames_option, NV_ENC_NUM_REF_FRAMES &L0_option, uint32_t ref_frames_default) {
if (client_config.numRefFrames > 0) { if (client_config.numRefFrames > 0) {
ref_frames_option = client_config.numRefFrames; ref_frames_option = client_config.numRefFrames;
} } else {
else {
ref_frames_option = ref_frames_default; ref_frames_option = ref_frames_default;
} }
if (ref_frames_option > 0 && !get_encoder_cap(NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES)) { if (ref_frames_option > 0 && !get_encoder_cap(NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES)) {
@@ -294,15 +299,15 @@ namespace nvenc {
}; };
switch (client_config.videoFormat) { switch (client_config.videoFormat) {
case 0: { case 0:
{
// H.264 // H.264
enc_config.profileGUID = buffer_is_yuv444() ? NV_ENC_H264_PROFILE_HIGH_444_GUID : NV_ENC_H264_PROFILE_HIGH_GUID; enc_config.profileGUID = buffer_is_yuv444() ? NV_ENC_H264_PROFILE_HIGH_444_GUID : NV_ENC_H264_PROFILE_HIGH_GUID;
auto &format_config = enc_config.encodeCodecConfig.h264Config; auto &format_config = enc_config.encodeCodecConfig.h264Config;
set_h264_hevc_common_format_config(format_config); set_h264_hevc_common_format_config(format_config);
if (config.h264_cavlc || !get_encoder_cap(NV_ENC_CAPS_SUPPORT_CABAC)) { if (config.h264_cavlc || !get_encoder_cap(NV_ENC_CAPS_SUPPORT_CABAC)) {
format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC; format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC;
} } else {
else {
format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CABAC; format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CABAC;
} }
set_ref_frames(format_config.maxNumRefFrames, format_config.numRefL0, 5); set_ref_frames(format_config.maxNumRefFrames, format_config.numRefL0, 5);
@@ -311,7 +316,8 @@ namespace nvenc {
break; break;
} }
case 1: { case 1:
{
// HEVC // HEVC
auto &format_config = enc_config.encodeCodecConfig.hevcConfig; auto &format_config = enc_config.encodeCodecConfig.hevcConfig;
set_h264_hevc_common_format_config(format_config); set_h264_hevc_common_format_config(format_config);
@@ -321,26 +327,25 @@ namespace nvenc {
set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numRefL0, 5); set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numRefL0, 5);
set_minqp_if_enabled(config.min_qp_hevc); set_minqp_if_enabled(config.min_qp_hevc);
fill_h264_hevc_vui(format_config.hevcVUIParameters); fill_h264_hevc_vui(format_config.hevcVUIParameters);
if (client_config.enableIntraRefresh == 1 || config.intra_refresh) { if (client_config.enableIntraRefresh == 1) {
if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_INTRA_REFRESH)) { if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_INTRA_REFRESH)) {
format_config.enableIntraRefresh = 1; format_config.enableIntraRefresh = 1;
format_config.intraRefreshPeriod = 300; format_config.intraRefreshPeriod = 300;
format_config.intraRefreshCnt = 299; format_config.intraRefreshCnt = 299;
if (get_encoder_cap(NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH)) { if (get_encoder_cap(NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH)) {
format_config.singleSliceIntraRefresh = 1; format_config.singleSliceIntraRefresh = 1;
} } else {
else {
BOOST_LOG(warning) << "NvEnc: Single Slice Intra Refresh not supported"; BOOST_LOG(warning) << "NvEnc: Single Slice Intra Refresh not supported";
} }
} } else {
else {
BOOST_LOG(error) << "NvEnc: Client asked for intra-refresh but the encoder does not support intra-refresh"; BOOST_LOG(error) << "NvEnc: Client asked for intra-refresh but the encoder does not support intra-refresh";
} }
} }
break; break;
} }
case 2: { case 2:
{
// AV1 // AV1
auto &format_config = enc_config.encodeCodecConfig.av1Config; auto &format_config = enc_config.encodeCodecConfig.av1Config;
format_config.repeatSeqHdr = 1; format_config.repeatSeqHdr = 1;
@@ -379,7 +384,7 @@ namespace nvenc {
} }
if (async_event_handle) { if (async_event_handle) {
NV_ENC_EVENT_PARAMS event_params = { min_struct_version(NV_ENC_EVENT_PARAMS_VER) }; NV_ENC_EVENT_PARAMS event_params = {min_struct_version(NV_ENC_EVENT_PARAMS_VER)};
event_params.completionEvent = async_event_handle; event_params.completionEvent = async_event_handle;
if (nvenc_failed(nvenc->nvEncRegisterAsyncEvent(encoder, &event_params))) { if (nvenc_failed(nvenc->nvEncRegisterAsyncEvent(encoder, &event_params))) {
BOOST_LOG(error) << "NvEnc: NvEncRegisterAsyncEvent() failed: " << last_nvenc_error_string; BOOST_LOG(error) << "NvEnc: NvEncRegisterAsyncEvent() failed: " << last_nvenc_error_string;
@@ -387,7 +392,7 @@ namespace nvenc {
} }
} }
NV_ENC_CREATE_BITSTREAM_BUFFER create_bitstream_buffer = { min_struct_version(NV_ENC_CREATE_BITSTREAM_BUFFER_VER) }; NV_ENC_CREATE_BITSTREAM_BUFFER create_bitstream_buffer = {min_struct_version(NV_ENC_CREATE_BITSTREAM_BUFFER_VER)};
if (nvenc_failed(nvenc->nvEncCreateBitstreamBuffer(encoder, &create_bitstream_buffer))) { if (nvenc_failed(nvenc->nvEncCreateBitstreamBuffer(encoder, &create_bitstream_buffer))) {
BOOST_LOG(error) << "NvEnc: NvEncCreateBitstreamBuffer() failed: " << last_nvenc_error_string; BOOST_LOG(error) << "NvEnc: NvEncCreateBitstreamBuffer() failed: " << last_nvenc_error_string;
return false; return false;
@@ -409,16 +414,36 @@ namespace nvenc {
client_config.videoFormat == 2 ? "AV1 " : client_config.videoFormat == 2 ? "AV1 " :
" "; " ";
std::string extra; std::string extra;
if (init_params.enableEncodeAsync) extra += " async"; if (init_params.enableEncodeAsync) {
if (buffer_is_yuv444()) extra += " yuv444"; extra += " async";
if (buffer_is_10bit()) extra += " 10-bit"; }
if (enc_config.rcParams.multiPass != NV_ENC_MULTI_PASS_DISABLED) extra += " two-pass"; if (buffer_is_yuv444()) {
if (config.vbv_percentage_increase > 0 && get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) extra += " vbv+" + std::to_string(config.vbv_percentage_increase); extra += " yuv444";
if (encoder_params.rfi) extra += " rfi"; }
if (init_params.enableWeightedPrediction) extra += " weighted-prediction"; if (buffer_is_10bit()) {
if (enc_config.rcParams.enableAQ) extra += " spatial-aq"; extra += " 10-bit";
if (enc_config.rcParams.enableMinQP) extra += " qpmin=" + std::to_string(enc_config.rcParams.minQP.qpInterP); }
if (config.insert_filler_data) extra += " filler-data"; if (enc_config.rcParams.multiPass != NV_ENC_MULTI_PASS_DISABLED) {
extra += " two-pass";
}
if (config.vbv_percentage_increase > 0 && get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) {
extra += " vbv+" + std::to_string(config.vbv_percentage_increase);
}
if (encoder_params.rfi) {
extra += " rfi";
}
if (init_params.enableWeightedPrediction) {
extra += " weighted-prediction";
}
if (enc_config.rcParams.enableAQ) {
extra += " spatial-aq";
}
if (enc_config.rcParams.enableMinQP) {
extra += " qpmin=" + std::to_string(enc_config.rcParams.minQP.qpInterP);
}
if (config.insert_filler_data) {
extra += " filler-data";
}
BOOST_LOG(info) << "NvEnc: created encoder " << video_format_string << quality_preset_string_from_guid(init_params.presetGUID) << extra; BOOST_LOG(info) << "NvEnc: created encoder " << video_format_string << quality_preset_string_from_guid(init_params.presetGUID) << extra;
} }
@@ -428,8 +453,7 @@ namespace nvenc {
return true; return true;
} }
void void nvenc_base::destroy_encoder() {
nvenc_base::destroy_encoder() {
if (output_bitstream) { if (output_bitstream) {
if (nvenc_failed(nvenc->nvEncDestroyBitstreamBuffer(encoder, output_bitstream))) { if (nvenc_failed(nvenc->nvEncDestroyBitstreamBuffer(encoder, output_bitstream))) {
BOOST_LOG(error) << "NvEnc: NvEncDestroyBitstreamBuffer() failed: " << last_nvenc_error_string; BOOST_LOG(error) << "NvEnc: NvEncDestroyBitstreamBuffer() failed: " << last_nvenc_error_string;
@@ -437,7 +461,7 @@ namespace nvenc {
output_bitstream = nullptr; output_bitstream = nullptr;
} }
if (encoder && async_event_handle) { if (encoder && async_event_handle) {
NV_ENC_EVENT_PARAMS event_params = { min_struct_version(NV_ENC_EVENT_PARAMS_VER) }; NV_ENC_EVENT_PARAMS event_params = {min_struct_version(NV_ENC_EVENT_PARAMS_VER)};
event_params.completionEvent = async_event_handle; event_params.completionEvent = async_event_handle;
if (nvenc_failed(nvenc->nvEncUnregisterAsyncEvent(encoder, &event_params))) { if (nvenc_failed(nvenc->nvEncUnregisterAsyncEvent(encoder, &event_params))) {
BOOST_LOG(error) << "NvEnc: NvEncUnregisterAsyncEvent() failed: " << last_nvenc_error_string; BOOST_LOG(error) << "NvEnc: NvEncUnregisterAsyncEvent() failed: " << last_nvenc_error_string;
@@ -460,8 +484,7 @@ namespace nvenc {
encoder_params = {}; encoder_params = {};
} }
nvenc_encoded_frame nvenc_encoded_frame nvenc_base::encode_frame(uint64_t frame_index, bool force_idr) {
nvenc_base::encode_frame(uint64_t frame_index, bool force_idr) {
if (!encoder) { if (!encoder) {
return {}; return {};
} }
@@ -474,7 +497,7 @@ namespace nvenc {
return {}; return {};
} }
NV_ENC_MAP_INPUT_RESOURCE mapped_input_buffer = { min_struct_version(NV_ENC_MAP_INPUT_RESOURCE_VER) }; NV_ENC_MAP_INPUT_RESOURCE mapped_input_buffer = {min_struct_version(NV_ENC_MAP_INPUT_RESOURCE_VER)};
mapped_input_buffer.registeredResource = registered_input_buffer; mapped_input_buffer.registeredResource = registered_input_buffer;
if (nvenc_failed(nvenc->nvEncMapInputResource(encoder, &mapped_input_buffer))) { if (nvenc_failed(nvenc->nvEncMapInputResource(encoder, &mapped_input_buffer))) {
@@ -487,7 +510,7 @@ namespace nvenc {
} }
}); });
NV_ENC_PIC_PARAMS pic_params = { min_struct_version(NV_ENC_PIC_PARAMS_VER, 4, 6) }; NV_ENC_PIC_PARAMS pic_params = {min_struct_version(NV_ENC_PIC_PARAMS_VER, 4, 6)};
pic_params.inputWidth = encoder_params.width; pic_params.inputWidth = encoder_params.width;
pic_params.inputHeight = encoder_params.height; pic_params.inputHeight = encoder_params.height;
pic_params.encodePicFlags = force_idr ? NV_ENC_PIC_FLAG_FORCEIDR : 0; pic_params.encodePicFlags = force_idr ? NV_ENC_PIC_FLAG_FORCEIDR : 0;
@@ -503,7 +526,7 @@ namespace nvenc {
return {}; return {};
} }
NV_ENC_LOCK_BITSTREAM lock_bitstream = { min_struct_version(NV_ENC_LOCK_BITSTREAM_VER, 1, 2) }; NV_ENC_LOCK_BITSTREAM lock_bitstream = {min_struct_version(NV_ENC_LOCK_BITSTREAM_VER, 1, 2)};
lock_bitstream.outputBitstream = output_bitstream; lock_bitstream.outputBitstream = output_bitstream;
lock_bitstream.doNotWait = 0; lock_bitstream.doNotWait = 0;
@@ -519,7 +542,7 @@ namespace nvenc {
auto data_pointer = (uint8_t *) lock_bitstream.bitstreamBufferPtr; auto data_pointer = (uint8_t *) lock_bitstream.bitstreamBufferPtr;
nvenc_encoded_frame encoded_frame { nvenc_encoded_frame encoded_frame {
{ data_pointer, data_pointer + lock_bitstream.bitstreamSizeInBytes }, {data_pointer, data_pointer + lock_bitstream.bitstreamSizeInBytes},
lock_bitstream.outputTimeStamp, lock_bitstream.outputTimeStamp,
lock_bitstream.pictureType == NV_ENC_PIC_TYPE_IDR, lock_bitstream.pictureType == NV_ENC_PIC_TYPE_IDR,
encoder_state.rfi_needs_confirmation, encoder_state.rfi_needs_confirmation,
@@ -545,9 +568,10 @@ namespace nvenc {
return encoded_frame; return encoded_frame;
} }
bool bool nvenc_base::invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) {
nvenc_base::invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) { if (!encoder || !encoder_params.rfi) {
if (!encoder || !encoder_params.rfi) return false; return false;
}
if (first_frame >= encoder_state.last_rfi_range.first && if (first_frame >= encoder_state.last_rfi_range.first &&
last_frame <= encoder_state.last_rfi_range.second) { last_frame <= encoder_state.last_rfi_range.second) {
@@ -565,7 +589,7 @@ namespace nvenc {
BOOST_LOG(debug) << "NvEnc: rfi request " << first_frame << "-" << last_frame << " expanding to last encoded frame " << encoder_state.last_encoded_frame_index; BOOST_LOG(debug) << "NvEnc: rfi request " << first_frame << "-" << last_frame << " expanding to last encoded frame " << encoder_state.last_encoded_frame_index;
last_frame = encoder_state.last_encoded_frame_index; last_frame = encoder_state.last_encoded_frame_index;
encoder_state.last_rfi_range = { first_frame, last_frame }; encoder_state.last_rfi_range = {first_frame, last_frame};
if (last_frame - first_frame + 1 >= encoder_params.ref_frames_in_dpb) { if (last_frame - first_frame + 1 >= encoder_params.ref_frames_in_dpb) {
BOOST_LOG(debug) << "NvEnc: rfi request too large, generating IDR"; BOOST_LOG(debug) << "NvEnc: rfi request too large, generating IDR";
@@ -582,8 +606,7 @@ namespace nvenc {
return true; return true;
} }
bool bool nvenc_base::nvenc_failed(NVENCSTATUS status) {
nvenc_base::nvenc_failed(NVENCSTATUS status) {
auto status_string = [](NVENCSTATUS status) -> std::string { auto status_string = [](NVENCSTATUS status) -> std::string {
switch (status) { switch (status) {
#define nvenc_status_case(x) \ #define nvenc_status_case(x) \
@@ -637,8 +660,7 @@ namespace nvenc {
return false; return false;
} }
uint32_t uint32_t nvenc_base::min_struct_version(uint32_t version, uint32_t v11_struct_version, uint32_t v12_struct_version) {
nvenc_base::min_struct_version(uint32_t version, uint32_t v11_struct_version, uint32_t v12_struct_version) {
assert(minimum_api_version); assert(minimum_api_version);
// Mask off and replace the original NVENCAPI_VERSION // Mask off and replace the original NVENCAPI_VERSION

View File

@@ -4,15 +4,16 @@
*/ */
#pragma once #pragma once
// lib includes
#include <ffnvcodec/nvEncodeAPI.h>
// local includes
#include "nvenc_colorspace.h" #include "nvenc_colorspace.h"
#include "nvenc_config.h" #include "nvenc_config.h"
#include "nvenc_encoded_frame.h" #include "nvenc_encoded_frame.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/video.h" #include "src/video.h"
#include <ffnvcodec/nvEncodeAPI.h>
/** /**
* @brief Standalone NVENC encoder * @brief Standalone NVENC encoder
*/ */
@@ -31,8 +32,7 @@ namespace nvenc {
virtual ~nvenc_base(); virtual ~nvenc_base();
nvenc_base(const nvenc_base &) = delete; nvenc_base(const nvenc_base &) = delete;
nvenc_base & nvenc_base &operator=(const nvenc_base &) = delete;
operator=(const nvenc_base &) = delete;
/** /**
* @brief Create the encoder. * @brief Create the encoder.
@@ -42,15 +42,13 @@ namespace nvenc {
* @param buffer_format Platform-agnostic input surface format. * @param buffer_format Platform-agnostic input surface format.
* @return `true` on success, `false` on error * @return `true` on success, `false` on error
*/ */
bool bool create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format);
create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format);
/** /**
* @brief Destroy the encoder. * @brief Destroy the encoder.
* Derived classes classes call it in the destructor. * Derived classes classes call it in the destructor.
*/ */
void void destroy_encoder();
destroy_encoder();
/** /**
* @brief Encode the next frame using platform-specific input surface. * @brief Encode the next frame using platform-specific input surface.
@@ -60,8 +58,7 @@ namespace nvenc {
* @param force_idr Whether to encode frame as forced IDR. * @param force_idr Whether to encode frame as forced IDR.
* @return Encoded frame. * @return Encoded frame.
*/ */
nvenc_encoded_frame nvenc_encoded_frame encode_frame(uint64_t frame_index, bool force_idr);
encode_frame(uint64_t frame_index, bool force_idr);
/** /**
* @brief Perform reference frame invalidation (RFI) procedure. * @brief Perform reference frame invalidation (RFI) procedure.
@@ -70,8 +67,7 @@ namespace nvenc {
* @return `true` on success, `false` on error. * @return `true` on success, `false` on error.
* After error next frame must be encoded with `force_idr = true`. * After error next frame must be encoded with `force_idr = true`.
*/ */
bool bool invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
protected: protected:
/** /**
@@ -79,8 +75,7 @@ namespace nvenc {
* Called during `create_encoder()` if `nvenc` variable is not initialized. * Called during `create_encoder()` if `nvenc` variable is not initialized.
* @return `true` on success, `false` on error * @return `true` on success, `false` on error
*/ */
virtual bool virtual bool init_library() = 0;
init_library() = 0;
/** /**
* @brief Required. Used for creating outside-facing input surface, * @brief Required. Used for creating outside-facing input surface,
@@ -88,16 +83,16 @@ namespace nvenc {
* Called during `create_encoder()`. * Called during `create_encoder()`.
* @return `true` on success, `false` on error * @return `true` on success, `false` on error
*/ */
virtual bool virtual bool create_and_register_input_buffer() = 0;
create_and_register_input_buffer() = 0;
/** /**
* @brief Optional. Override if you must perform additional operations on the registered input surface in the beginning of `encode_frame()`. * @brief Optional. Override if you must perform additional operations on the registered input surface in the beginning of `encode_frame()`.
* Typically used for interop copy. * Typically used for interop copy.
* @return `true` on success, `false` on error * @return `true` on success, `false` on error
*/ */
virtual bool virtual bool synchronize_input_buffer() {
synchronize_input_buffer() { return true; } return true;
}
/** /**
* @brief Optional. Override if you want to create encoder in async mode. * @brief Optional. Override if you want to create encoder in async mode.
@@ -105,11 +100,11 @@ namespace nvenc {
* @param timeout_ms Wait timeout in milliseconds * @param timeout_ms Wait timeout in milliseconds
* @return `true` on success, `false` on timeout or error * @return `true` on success, `false` on timeout or error
*/ */
virtual bool virtual bool wait_for_async_event(uint32_t timeout_ms) {
wait_for_async_event(uint32_t timeout_ms) { return false; } return false;
}
bool bool nvenc_failed(NVENCSTATUS status);
nvenc_failed(NVENCSTATUS status);
/** /**
* @brief This function returns the corresponding struct version for the minimum API required by the codec. * @brief This function returns the corresponding struct version for the minimum API required by the codec.
@@ -119,8 +114,7 @@ namespace nvenc {
* @param v12_struct_version Optionally specifies the struct version to use with v12 SDK major versions. * @param v12_struct_version Optionally specifies the struct version to use with v12 SDK major versions.
* @return A suitable struct version for the active codec. * @return A suitable struct version for the active codec.
*/ */
uint32_t uint32_t min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0);
min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0);
const NV_ENC_DEVICE_TYPE device_type; const NV_ENC_DEVICE_TYPE device_type;
@@ -154,7 +148,7 @@ namespace nvenc {
uint64_t last_encoded_frame_index = 0; uint64_t last_encoded_frame_index = 0;
bool rfi_needs_confirmation = false; bool rfi_needs_confirmation = false;
std::pair<uint64_t, uint64_t> last_rfi_range; std::pair<uint64_t, uint64_t> last_rfi_range;
logging::min_max_avg_periodic_logger<double> frame_size_logger = { debug, "NvEnc: encoded frame sizes in kB", "" }; logging::min_max_avg_periodic_logger<double> frame_size_logger = {debug, "NvEnc: encoded frame sizes in kB", ""};
} encoder_state; } encoder_state;
}; };

View File

@@ -4,6 +4,7 @@
*/ */
#pragma once #pragma once
// lib includes
#include <ffnvcodec/nvEncodeAPI.h> #include <ffnvcodec/nvEncodeAPI.h>
namespace nvenc { namespace nvenc {

View File

@@ -2,6 +2,7 @@
* @file src/nvenc/nvenc_d3d11.cpp * @file src/nvenc/nvenc_d3d11.cpp
* @brief Definitions for abstract Direct3D11 NVENC encoder. * @brief Definitions for abstract Direct3D11 NVENC encoder.
*/ */
// local includes
#include "src/logging.h" #include "src/logging.h"
#ifdef _WIN32 #ifdef _WIN32
@@ -16,9 +17,10 @@ namespace nvenc {
} }
} }
bool bool nvenc_d3d11::init_library() {
nvenc_d3d11::init_library() { if (dll) {
if (dll) return true; return true;
}
#ifdef _WIN64 #ifdef _WIN64
constexpr auto dll_name = "nvEncodeAPI64.dll"; constexpr auto dll_name = "nvEncodeAPI64.dll";
@@ -32,17 +34,14 @@ namespace nvenc {
new_nvenc->version = min_struct_version(NV_ENCODE_API_FUNCTION_LIST_VER); new_nvenc->version = min_struct_version(NV_ENCODE_API_FUNCTION_LIST_VER);
if (nvenc_failed(create_instance(new_nvenc.get()))) { if (nvenc_failed(create_instance(new_nvenc.get()))) {
BOOST_LOG(error) << "NvEnc: NvEncodeAPICreateInstance() failed: " << last_nvenc_error_string; BOOST_LOG(error) << "NvEnc: NvEncodeAPICreateInstance() failed: " << last_nvenc_error_string;
} } else {
else {
nvenc = std::move(new_nvenc); nvenc = std::move(new_nvenc);
return true; return true;
} }
} } else {
else {
BOOST_LOG(error) << "NvEnc: No NvEncodeAPICreateInstance() in " << dll_name; BOOST_LOG(error) << "NvEnc: No NvEncodeAPICreateInstance() in " << dll_name;
} }
} } else {
else {
BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << dll_name; BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << dll_name;
} }

View File

@@ -5,9 +5,11 @@
#pragma once #pragma once
#ifdef _WIN32 #ifdef _WIN32
// standard includes
#include <comdef.h> #include <comdef.h>
#include <d3d11.h> #include <d3d11.h>
// local includes
#include "nvenc_base.h" #include "nvenc_base.h"
namespace nvenc { namespace nvenc {
@@ -24,7 +26,8 @@ namespace nvenc {
class nvenc_d3d11: public nvenc_base { class nvenc_d3d11: public nvenc_base {
public: public:
explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type): explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type):
nvenc_base(device_type) {} nvenc_base(device_type) {
}
~nvenc_d3d11(); ~nvenc_d3d11();
@@ -32,12 +35,10 @@ namespace nvenc {
* @brief Get input surface texture. * @brief Get input surface texture.
* @return Input surface texture. * @return Input surface texture.
*/ */
virtual ID3D11Texture2D * virtual ID3D11Texture2D *get_input_texture() = 0;
get_input_texture() = 0;
protected: protected:
bool bool init_library() override;
init_library() override;
private: private:
HMODULE dll = NULL; HMODULE dll = NULL;

View File

@@ -3,8 +3,10 @@
* @brief Definitions for native Direct3D11 NVENC encoder. * @brief Definitions for native Direct3D11 NVENC encoder.
*/ */
#ifdef _WIN32 #ifdef _WIN32
// this include
#include "nvenc_d3d11_native.h" #include "nvenc_d3d11_native.h"
// local includes
#include "nvenc_utils.h" #include "nvenc_utils.h"
namespace nvenc { namespace nvenc {
@@ -16,7 +18,9 @@ namespace nvenc {
} }
nvenc_d3d11_native::~nvenc_d3d11_native() { nvenc_d3d11_native::~nvenc_d3d11_native() {
if (encoder) destroy_encoder(); if (encoder) {
destroy_encoder();
}
} }
ID3D11Texture2D * ID3D11Texture2D *
@@ -24,8 +28,7 @@ namespace nvenc {
return d3d_input_texture.GetInterfacePtr(); return d3d_input_texture.GetInterfacePtr();
} }
bool bool nvenc_d3d11_native::create_and_register_input_buffer() {
nvenc_d3d11_native::create_and_register_input_buffer() {
if (encoder_params.buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) { if (encoder_params.buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
BOOST_LOG(error) << "NvEnc: 10-bit 4:4:4 encoding is incompatible with D3D11 surface formats, use CUDA interop"; BOOST_LOG(error) << "NvEnc: 10-bit 4:4:4 encoding is incompatible with D3D11 surface formats, use CUDA interop";
return false; return false;
@@ -48,7 +51,7 @@ namespace nvenc {
} }
if (!registered_input_buffer) { if (!registered_input_buffer) {
NV_ENC_REGISTER_RESOURCE register_resource = { min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4) }; NV_ENC_REGISTER_RESOURCE register_resource = {min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4)};
register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX; register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX;
register_resource.width = encoder_params.width; register_resource.width = encoder_params.width;
register_resource.height = encoder_params.height; register_resource.height = encoder_params.height;

View File

@@ -4,10 +4,11 @@
*/ */
#pragma once #pragma once
#ifdef _WIN32 #ifdef _WIN32
// standard includes
#include <comdef.h> #include <comdef.h>
#include <d3d11.h> #include <d3d11.h>
// local includes
#include "nvenc_d3d11.h" #include "nvenc_d3d11.h"
namespace nvenc { namespace nvenc {
@@ -23,12 +24,10 @@ namespace nvenc {
explicit nvenc_d3d11_native(ID3D11Device *d3d_device); explicit nvenc_d3d11_native(ID3D11Device *d3d_device);
~nvenc_d3d11_native(); ~nvenc_d3d11_native();
ID3D11Texture2D * ID3D11Texture2D *get_input_texture() override;
get_input_texture() override;
private: private:
bool bool create_and_register_input_buffer() override;
create_and_register_input_buffer() override;
const ID3D11DevicePtr d3d_device; const ID3D11DevicePtr d3d_device;
ID3D11Texture2DPtr d3d_input_texture; ID3D11Texture2DPtr d3d_input_texture;

View File

@@ -3,8 +3,10 @@
* @brief Definitions for CUDA NVENC encoder with Direct3D11 input surfaces. * @brief Definitions for CUDA NVENC encoder with Direct3D11 input surfaces.
*/ */
#ifdef _WIN32 #ifdef _WIN32
// this include
#include "nvenc_d3d11_on_cuda.h" #include "nvenc_d3d11_on_cuda.h"
// local includes
#include "nvenc_utils.h" #include "nvenc_utils.h"
namespace nvenc { namespace nvenc {
@@ -15,7 +17,9 @@ namespace nvenc {
} }
nvenc_d3d11_on_cuda::~nvenc_d3d11_on_cuda() { nvenc_d3d11_on_cuda::~nvenc_d3d11_on_cuda() {
if (encoder) destroy_encoder(); if (encoder) {
destroy_encoder();
}
if (cuda_context) { if (cuda_context) {
{ {
@@ -48,14 +52,14 @@ namespace nvenc {
} }
} }
ID3D11Texture2D * ID3D11Texture2D *nvenc_d3d11_on_cuda::get_input_texture() {
nvenc_d3d11_on_cuda::get_input_texture() {
return d3d_input_texture.GetInterfacePtr(); return d3d_input_texture.GetInterfacePtr();
} }
bool bool nvenc_d3d11_on_cuda::init_library() {
nvenc_d3d11_on_cuda::init_library() { if (!nvenc_d3d11::init_library()) {
if (!nvenc_d3d11::init_library()) return false; return false;
}
constexpr auto dll_name = "nvcuda.dll"; constexpr auto dll_name = "nvcuda.dll";
@@ -82,8 +86,7 @@ namespace nvenc {
FreeLibrary(cuda_functions.dll); FreeLibrary(cuda_functions.dll);
cuda_functions = {}; cuda_functions = {};
} }
} } else {
else {
BOOST_LOG(debug) << "NvEnc: couldn't load CUDA dynamic library " << dll_name; BOOST_LOG(debug) << "NvEnc: couldn't load CUDA dynamic library " << dll_name;
} }
@@ -99,12 +102,10 @@ namespace nvenc {
cuda_succeeded(cuda_functions.cuCtxCreate(&cuda_context, CU_CTX_SCHED_BLOCKING_SYNC, cuda_device)) && cuda_succeeded(cuda_functions.cuCtxCreate(&cuda_context, CU_CTX_SCHED_BLOCKING_SYNC, cuda_device)) &&
cuda_succeeded(cuda_functions.cuCtxPopCurrent(&cuda_context))) { cuda_succeeded(cuda_functions.cuCtxPopCurrent(&cuda_context))) {
device = cuda_context; device = cuda_context;
} } else {
else {
BOOST_LOG(error) << "NvEnc: couldn't create CUDA interop context: error " << last_cuda_error; BOOST_LOG(error) << "NvEnc: couldn't create CUDA interop context: error " << last_cuda_error;
} }
} } else {
else {
BOOST_LOG(error) << "NvEnc: couldn't get DXGI adapter for CUDA interop"; BOOST_LOG(error) << "NvEnc: couldn't get DXGI adapter for CUDA interop";
} }
} }
@@ -112,8 +113,7 @@ namespace nvenc {
return device != nullptr; return device != nullptr;
} }
bool bool nvenc_d3d11_on_cuda::create_and_register_input_buffer() {
nvenc_d3d11_on_cuda::create_and_register_input_buffer() {
if (encoder_params.buffer_format != NV_ENC_BUFFER_FORMAT_YUV444_10BIT) { if (encoder_params.buffer_format != NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
BOOST_LOG(error) << "NvEnc: CUDA interop is expected to be used only for 10-bit 4:4:4 encoding"; BOOST_LOG(error) << "NvEnc: CUDA interop is expected to be used only for 10-bit 4:4:4 encoding";
return false; return false;
@@ -138,13 +138,16 @@ namespace nvenc {
{ {
auto autopop_context = push_context(); auto autopop_context = push_context();
if (!autopop_context) return false; if (!autopop_context) {
return false;
}
if (!cuda_d3d_input_texture) { if (!cuda_d3d_input_texture) {
if (cuda_failed(cuda_functions.cuGraphicsD3D11RegisterResource( if (cuda_failed(cuda_functions.cuGraphicsD3D11RegisterResource(
&cuda_d3d_input_texture, &cuda_d3d_input_texture,
d3d_input_texture, d3d_input_texture,
CU_GRAPHICS_REGISTER_FLAGS_NONE))) { CU_GRAPHICS_REGISTER_FLAGS_NONE
))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsD3D11RegisterResource() failed: error " << last_cuda_error; BOOST_LOG(error) << "NvEnc: cuGraphicsD3D11RegisterResource() failed: error " << last_cuda_error;
return false; return false;
} }
@@ -156,7 +159,9 @@ namespace nvenc {
&cuda_surface_pitch, &cuda_surface_pitch,
// Planar 16-bit YUV // Planar 16-bit YUV
encoder_params.width * 2, encoder_params.width * 2,
encoder_params.height * 3, 16))) { encoder_params.height * 3,
16
))) {
BOOST_LOG(error) << "NvEnc: cuMemAllocPitch() failed: error " << last_cuda_error; BOOST_LOG(error) << "NvEnc: cuMemAllocPitch() failed: error " << last_cuda_error;
return false; return false;
} }
@@ -164,7 +169,7 @@ namespace nvenc {
} }
if (!registered_input_buffer) { if (!registered_input_buffer) {
NV_ENC_REGISTER_RESOURCE register_resource = { min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4) }; NV_ENC_REGISTER_RESOURCE register_resource = {min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4)};
register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR; register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR;
register_resource.width = encoder_params.width; register_resource.width = encoder_params.width;
register_resource.height = encoder_params.height; register_resource.height = encoder_params.height;
@@ -184,10 +189,11 @@ namespace nvenc {
return true; return true;
} }
bool bool nvenc_d3d11_on_cuda::synchronize_input_buffer() {
nvenc_d3d11_on_cuda::synchronize_input_buffer() {
auto autopop_context = push_context(); auto autopop_context = push_context();
if (!autopop_context) return false; if (!autopop_context) {
return false;
}
if (cuda_failed(cuda_functions.cuGraphicsMapResources(1, &cuda_d3d_input_texture, 0))) { if (cuda_failed(cuda_functions.cuGraphicsMapResources(1, &cuda_d3d_input_texture, 0))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsMapResources() failed: error " << last_cuda_error; BOOST_LOG(error) << "NvEnc: cuGraphicsMapResources() failed: error " << last_cuda_error;
@@ -230,14 +236,12 @@ namespace nvenc {
return unmap(); return unmap();
} }
bool bool nvenc_d3d11_on_cuda::cuda_succeeded(CUresult result) {
nvenc_d3d11_on_cuda::cuda_succeeded(CUresult result) {
last_cuda_error = result; last_cuda_error = result;
return result == CUDA_SUCCESS; return result == CUDA_SUCCESS;
} }
bool bool nvenc_d3d11_on_cuda::cuda_failed(CUresult result) {
nvenc_d3d11_on_cuda::cuda_failed(CUresult result) {
last_cuda_error = result; last_cuda_error = result;
return result != CUDA_SUCCESS; return result != CUDA_SUCCESS;
} }
@@ -251,15 +255,13 @@ namespace nvenc {
} }
} }
nvenc_d3d11_on_cuda::autopop_context nvenc_d3d11_on_cuda::autopop_context nvenc_d3d11_on_cuda::push_context() {
nvenc_d3d11_on_cuda::push_context() {
if (cuda_context && if (cuda_context &&
cuda_succeeded(cuda_functions.cuCtxPushCurrent(cuda_context))) { cuda_succeeded(cuda_functions.cuCtxPushCurrent(cuda_context))) {
return { *this, cuda_context }; return {*this, cuda_context};
} } else {
else {
BOOST_LOG(error) << "NvEnc: cuCtxPushCurrent() failed: error " << last_cuda_error; BOOST_LOG(error) << "NvEnc: cuCtxPushCurrent() failed: error " << last_cuda_error;
return { *this, nullptr }; return {*this, nullptr};
} }
} }

View File

@@ -4,11 +4,12 @@
*/ */
#pragma once #pragma once
#ifdef _WIN32 #ifdef _WIN32
// lib includes
#include "nvenc_d3d11.h"
#include <ffnvcodec/dynlink_cuda.h> #include <ffnvcodec/dynlink_cuda.h>
// local includes
#include "nvenc_d3d11.h"
namespace nvenc { namespace nvenc {
/** /**
@@ -24,24 +25,18 @@ namespace nvenc {
explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device); explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device);
~nvenc_d3d11_on_cuda(); ~nvenc_d3d11_on_cuda();
ID3D11Texture2D * ID3D11Texture2D *get_input_texture() override;
get_input_texture() override;
private: private:
bool bool init_library() override;
init_library() override;
bool bool create_and_register_input_buffer() override;
create_and_register_input_buffer() override;
bool bool synchronize_input_buffer() override;
synchronize_input_buffer() override;
bool bool cuda_succeeded(CUresult result);
cuda_succeeded(CUresult result);
bool bool cuda_failed(CUresult result);
cuda_failed(CUresult result);
struct autopop_context { struct autopop_context {
autopop_context(nvenc_d3d11_on_cuda &parent, CUcontext pushed_context): autopop_context(nvenc_d3d11_on_cuda &parent, CUcontext pushed_context):
@@ -51,8 +46,7 @@ namespace nvenc {
~autopop_context(); ~autopop_context();
explicit explicit operator bool() const {
operator bool() const {
return pushed_context != nullptr; return pushed_context != nullptr;
} }
@@ -60,8 +54,7 @@ namespace nvenc {
CUcontext pushed_context = nullptr; CUcontext pushed_context = nullptr;
}; };
autopop_context autopop_context push_context();
push_context();
HMODULE dll = NULL; HMODULE dll = NULL;
const ID3D11DevicePtr d3d_device; const ID3D11DevicePtr d3d_device;

View File

@@ -4,6 +4,7 @@
*/ */
#pragma once #pragma once
// standard includes
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>

View File

@@ -2,15 +2,16 @@
* @file src/nvenc/nvenc_utils.cpp * @file src/nvenc/nvenc_utils.cpp
* @brief Definitions for NVENC utilities. * @brief Definitions for NVENC utilities.
*/ */
// standard includes
#include <cassert> #include <cassert>
// local includes
#include "nvenc_utils.h" #include "nvenc_utils.h"
namespace nvenc { namespace nvenc {
#ifdef _WIN32 #ifdef _WIN32
DXGI_FORMAT DXGI_FORMAT dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format) {
dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format) {
switch (format) { switch (format) {
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT: case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
return DXGI_FORMAT_P010; return DXGI_FORMAT_P010;
@@ -30,8 +31,7 @@ namespace nvenc {
} }
#endif #endif
NV_ENC_BUFFER_FORMAT NV_ENC_BUFFER_FORMAT nvenc_format_from_sunshine_format(platf::pix_fmt_e format) {
nvenc_format_from_sunshine_format(platf::pix_fmt_e format) {
switch (format) { switch (format) {
case platf::pix_fmt_e::nv12: case platf::pix_fmt_e::nv12:
return NV_ENC_BUFFER_FORMAT_NV12; return NV_ENC_BUFFER_FORMAT_NV12;
@@ -50,8 +50,7 @@ namespace nvenc {
} }
} }
nvenc_colorspace_t nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace) {
nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace) {
nvenc_colorspace_t colorspace; nvenc_colorspace_t colorspace;
switch (sunshine_colorspace.colorspace) { switch (sunshine_colorspace.colorspace) {

View File

@@ -4,28 +4,27 @@
*/ */
#pragma once #pragma once
// plafform includes
#ifdef _WIN32 #ifdef _WIN32
#include <dxgiformat.h> #include <dxgiformat.h>
#endif #endif
#include "nvenc_colorspace.h" // lib includes
#include <ffnvcodec/nvEncodeAPI.h>
// local includes
#include "nvenc_colorspace.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/video_colorspace.h" #include "src/video_colorspace.h"
#include <ffnvcodec/nvEncodeAPI.h>
namespace nvenc { namespace nvenc {
#ifdef _WIN32 #ifdef _WIN32
DXGI_FORMAT DXGI_FORMAT dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format);
dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format);
#endif #endif
NV_ENC_BUFFER_FORMAT NV_ENC_BUFFER_FORMAT nvenc_format_from_sunshine_format(platf::pix_fmt_e format);
nvenc_format_from_sunshine_format(platf::pix_fmt_e format);
nvenc_colorspace_t nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace);
nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace);
} // namespace nvenc } // namespace nvenc

View File

@@ -7,16 +7,17 @@
// standard includes // standard includes
#include <filesystem> #include <filesystem>
#include <string>
#include <utility> #include <utility>
#include <string> #include <string>
// lib includes // lib includes
#include <Simple-Web-Server/server_http.hpp>
#include <boost/asio/ssl/context.hpp> #include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/context_base.hpp> #include <boost/asio/ssl/context_base.hpp>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp> #include <boost/property_tree/xml_parser.hpp>
#include <Simple-Web-Server/server_http.hpp>
// local includes // local includes
#include "config.h" #include "config.h"
@@ -41,6 +42,7 @@
#endif #endif
using namespace std::literals; using namespace std::literals;
namespace nvhttp { namespace nvhttp {
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -79,8 +81,7 @@ namespace nvhttp {
protected: protected:
boost::asio::ssl::context context; boost::asio::ssl::context context;
void void after_bind() override {
after_bind() override {
if (verify) { if (verify) {
context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once); context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once);
context.set_verify_callback([](int verified, boost::asio::ssl::verify_context &ctx) { context.set_verify_callback([](int verified, boost::asio::ssl::verify_context &ctx) {
@@ -91,17 +92,18 @@ namespace nvhttp {
} }
// This is Server<HTTPS>::accept() with SSL validation support added // This is Server<HTTPS>::accept() with SSL validation support added
void void accept() override {
accept() override {
auto connection = create_connection(*io_service, context); auto connection = create_connection(*io_service, context);
acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const SimpleWeb::error_code &ec) { acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const SimpleWeb::error_code &ec) {
auto lock = connection->handler_runner->continue_lock(); auto lock = connection->handler_runner->continue_lock();
if (!lock) if (!lock) {
return; return;
}
if (ec != SimpleWeb::error::operation_aborted) if (ec != SimpleWeb::error::operation_aborted) {
this->accept(); this->accept();
}
auto session = std::make_shared<Session>(config.max_request_streambuf_size, connection); auto session = std::make_shared<Session>(config.max_request_streambuf_size, connection);
@@ -114,20 +116,22 @@ namespace nvhttp {
session->connection->socket->async_handshake(boost::asio::ssl::stream_base::server, [this, session](const SimpleWeb::error_code &ec) { session->connection->socket->async_handshake(boost::asio::ssl::stream_base::server, [this, session](const SimpleWeb::error_code &ec) {
session->connection->cancel_timeout(); session->connection->cancel_timeout();
auto lock = session->connection->handler_runner->continue_lock(); auto lock = session->connection->handler_runner->continue_lock();
if (!lock) if (!lock) {
return; return;
}
if (!ec) { if (!ec) {
if (verify && !verify(session->request, session->connection->socket->native_handle())) if (verify && !verify(session->request, session->connection->socket->native_handle()))
this->write(session, on_verify_failed); this->write(session, on_verify_failed);
else } else {
this->read(session); this->read(session);
} }
else if (this->on_error) } else if (this->on_error) {
this->on_error(session->request, ec); this->on_error(session->request, ec);
});
} }
else if (this->on_error) });
} else if (this->on_error) {
this->on_error(session->request, ec); this->on_error(session->request, ec);
}
}); });
} }
}; };
@@ -155,8 +159,7 @@ namespace nvhttp {
REMOVE ///< Remove certificate REMOVE ///< Remove certificate
}; };
std::string std::string get_arg(const args_t &args, const char *name, const char *default_value = nullptr) {
get_arg(const args_t &args, const char *name, const char *default_value) {
auto it = args.find(name); auto it = args.find(name);
if (it == std::end(args)) { if (it == std::end(args)) {
if (default_value != NULL) { if (default_value != NULL) {
@@ -169,8 +172,7 @@ namespace nvhttp {
} }
// Helper function to extract command entries // Helper function to extract command entries
cmd_list_t cmd_list_t extract_command_entries(const pt::ptree& pt, const std::string& key) {
extract_command_entries(const pt::ptree& pt, const std::string& key) {
cmd_list_t commands; cmd_list_t commands;
// Check if the specified key exists // Check if the specified key exists
@@ -203,8 +205,7 @@ namespace nvhttp {
if (fs::exists(config::nvhttp.file_state)) { if (fs::exists(config::nvhttp.file_state)) {
try { try {
pt::read_json(config::nvhttp.file_state, root); pt::read_json(config::nvhttp.file_state, root);
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what(); BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
return; return;
} }
@@ -263,15 +264,13 @@ namespace nvhttp {
try { try {
pt::write_json(config::nvhttp.file_state, root); pt::write_json(config::nvhttp.file_state, root);
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(error) << "Couldn't write "sv << config::nvhttp.file_state << ": "sv << e.what(); BOOST_LOG(error) << "Couldn't write "sv << config::nvhttp.file_state << ": "sv << e.what();
return; return;
} }
} }
void void load_state() {
load_state() {
if (!fs::exists(config::nvhttp.file_state)) { if (!fs::exists(config::nvhttp.file_state)) {
BOOST_LOG(info) << "File "sv << config::nvhttp.file_state << " doesn't exist"sv; BOOST_LOG(info) << "File "sv << config::nvhttp.file_state << " doesn't exist"sv;
http::unique_id = uuid_util::uuid_t::generate().string(); http::unique_id = uuid_util::uuid_t::generate().string();
@@ -281,8 +280,7 @@ namespace nvhttp {
pt::ptree tree; pt::ptree tree;
try { try {
pt::read_json(config::nvhttp.file_state, tree); pt::read_json(config::nvhttp.file_state, tree);
} } catch (std::exception &e) {
catch (std::exception& e) {
BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what(); BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
return; return;
} }
@@ -344,8 +342,7 @@ namespace nvhttp {
client_root = client; client_root = client;
} }
void void add_authorized_client(const p_named_cert_t& named_cert_p) {
add_authorized_client(const p_named_cert_t& named_cert_p) {
client_t &client = client_root; client_t &client = client_root;
client.named_devices.push_back(named_cert_p); client.named_devices.push_back(named_cert_p);
@@ -359,8 +356,7 @@ namespace nvhttp {
} }
} }
std::shared_ptr<rtsp_stream::launch_session_t> std::shared_ptr<rtsp_stream::launch_session_t> make_launch_session(bool host_audio, bool input_only, int appid, const args_t &args, const crypto::named_cert_t* named_cert_p) {
make_launch_session(bool host_audio, bool input_only, int appid, const args_t &args, const crypto::named_cert_t* named_cert_p) {
auto launch_session = std::make_shared<rtsp_stream::launch_session_t>(); auto launch_session = std::make_shared<rtsp_stream::launch_session_t>();
launch_session->id = ++session_id_counter; launch_session->id = ++session_id_counter;
@@ -399,9 +395,15 @@ namespace nvhttp {
int x = 0; int x = 0;
std::string segment; std::string segment;
while (std::getline(mode, segment, 'x')) { while (std::getline(mode, segment, 'x')) {
if (x == 0) launch_session->width = atoi(segment.c_str()); if (x == 0) {
if (x == 1) launch_session->height = atoi(segment.c_str()); launch_session->width = atoi(segment.c_str());
if (x == 2) launch_session->fps = atoi(segment.c_str()); }
if (x == 1) {
launch_session->height = atoi(segment.c_str());
}
if (x == 2) {
launch_session->fps = atoi(segment.c_str());
}
x++; x++;
} }
@@ -425,13 +427,11 @@ namespace nvhttp {
return launch_session; return launch_session;
} }
void void remove_session(const pair_session_t &sess) {
remove_session(const pair_session_t &sess) {
map_id_sess.erase(sess.client.uniqueID); map_id_sess.erase(sess.client.uniqueID);
} }
void void fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
tree.put("root.paired", 0); tree.put("root.paired", 0);
tree.put("root.<xmlattr>.status_code", 400); tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", status_msg); tree.put("root.<xmlattr>.status_message", status_msg);
@@ -439,8 +439,7 @@ namespace nvhttp {
BOOST_LOG(warning) << "Pair attempt failed due to " << status_msg; BOOST_LOG(warning) << "Pair attempt failed due to " << status_msg;
} }
void void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
if (sess.last_phase != PAIR_PHASE::NONE) { if (sess.last_phase != PAIR_PHASE::NONE) {
fail_pair(sess, tree, "Out of order call to getservercert"); fail_pair(sess, tree, "Out of order call to getservercert");
return; return;
@@ -452,7 +451,7 @@ namespace nvhttp {
return; return;
} }
std::string_view salt_view { sess.async_insert_pin.salt.data(), 32 }; std::string_view salt_view {sess.async_insert_pin.salt.data(), 32};
auto salt = util::from_hex<std::array<uint8_t, 16>>(salt_view, true); auto salt = util::from_hex<std::array<uint8_t, 16>>(salt_view, true);
@@ -464,8 +463,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
} }
void void clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
if (sess.last_phase != PAIR_PHASE::GETSERVERCERT) { if (sess.last_phase != PAIR_PHASE::GETSERVERCERT) {
fail_pair(sess, tree, "Out of order call to clientchallenge"); fail_pair(sess, tree, "Out of order call to clientchallenge");
return; return;
@@ -488,7 +486,7 @@ namespace nvhttp {
decrypted.insert(std::end(decrypted), std::begin(sign), std::end(sign)); decrypted.insert(std::end(decrypted), std::begin(sign), std::end(sign));
decrypted.insert(std::end(decrypted), std::begin(serversecret), std::end(serversecret)); decrypted.insert(std::end(decrypted), std::begin(serversecret), std::end(serversecret));
auto hash = crypto::hash({ (char *) decrypted.data(), decrypted.size() }); auto hash = crypto::hash({(char *) decrypted.data(), decrypted.size()});
auto serverchallenge = crypto::rand(16); auto serverchallenge = crypto::rand(16);
std::string plaintext; std::string plaintext;
@@ -508,8 +506,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
} }
void void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
if (sess.last_phase != PAIR_PHASE::CLIENTCHALLENGE) { if (sess.last_phase != PAIR_PHASE::CLIENTCHALLENGE) {
fail_pair(sess, tree, "Out of order call to serverchallengeresp"); fail_pair(sess, tree, "Out of order call to serverchallengeresp");
return; return;
@@ -538,8 +535,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
} }
void void clientpairingsecret(pair_session_t &sess, pt::ptree &tree, const std::string &client_pairing_secret) {
clientpairingsecret(pair_session_t &sess, pt::ptree &tree, const std::string &client_pairing_secret) {
if (sess.last_phase != PAIR_PHASE::SERVERCHALLENGERESP) { if (sess.last_phase != PAIR_PHASE::SERVERCHALLENGERESP) {
fail_pair(sess, tree, "Out of order call to clientpairingsecret"); fail_pair(sess, tree, "Out of order call to clientpairingsecret");
return; return;
@@ -553,8 +549,8 @@ namespace nvhttp {
return; return;
} }
std::string_view secret { client_pairing_secret.data(), 16 }; std::string_view secret {client_pairing_secret.data(), 16};
std::string_view sign { client_pairing_secret.data() + secret.size(), client_pairing_secret.size() - secret.size() }; std::string_view sign {client_pairing_secret.data() + secret.size(), client_pairing_secret.size() - secret.size()};
auto x509 = crypto::x509(client.cert); auto x509 = crypto::x509(client.cert);
if (!x509) { if (!x509) {
@@ -597,8 +593,7 @@ namespace nvhttp {
map_id_sess.erase(it); map_id_sess.erase(it);
add_authorized_client(named_cert_p); add_authorized_client(named_cert_p);
} } else {
else {
tree.put("root.paired", 0); tree.put("root.paired", 0);
BOOST_LOG(warning) << "Pair attempt failed due to same_hash: " << same_hash << ", verify: " << verify; BOOST_LOG(warning) << "Pair attempt failed due to same_hash: " << same_hash << ", verify: " << verify;
} }
@@ -607,27 +602,25 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
} }
template <class T> template<class T>
struct tunnel; struct tunnel;
template <> template<>
struct tunnel<SunshineHTTPS> { struct tunnel<SunshineHTTPS> {
static auto constexpr to_string = "HTTPS"sv; static auto constexpr to_string = "HTTPS"sv;
}; };
template <> template<>
struct tunnel<SimpleWeb::HTTP> { struct tunnel<SimpleWeb::HTTP> {
static auto constexpr to_string = "NONE"sv; static auto constexpr to_string = "NONE"sv;
}; };
inline crypto::named_cert_t* inline crypto::named_cert_t* get_verified_cert(req_https_t request) {
get_verified_cert(req_https_t request) {
return (crypto::named_cert_t*)request->userp.get(); return (crypto::named_cert_t*)request->userp.get();
} }
template <class T> template <class T>
void void print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string; BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string;
BOOST_LOG(debug) << "METHOD :: "sv << request->method; BOOST_LOG(debug) << "METHOD :: "sv << request->method;
@@ -646,9 +639,8 @@ namespace nvhttp {
BOOST_LOG(debug) << " [--] "sv; BOOST_LOG(debug) << " [--] "sv;
} }
template <class T> template<class T>
void void not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request); print_req<T>(request);
pt::ptree tree; pt::ptree tree;
@@ -662,8 +654,7 @@ namespace nvhttp {
} }
template <class T> template <class T>
void void pair(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
pair(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request); print_req<T>(request);
pt::ptree tree; pt::ptree tree;
@@ -691,7 +682,7 @@ namespace nvhttp {
return; return;
} }
auto uniqID { get_arg(args, "uniqueid") }; auto uniqID {get_arg(args, "uniqueid")};
args_t::const_iterator it; args_t::const_iterator it;
if (it = args.find("phrase"); it != std::end(args)) { if (it = args.find("phrase"); it != std::end(args)) {
@@ -751,8 +742,7 @@ namespace nvhttp {
std::getline(std::cin, pin); std::getline(std::cin, pin);
getservercert(ptr->second, tree, pin); getservercert(ptr->second, tree, pin);
} } else {
else {
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 #if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
system_tray::update_tray_require_pin(); system_tray::update_tray_require_pin();
#endif #endif
@@ -761,8 +751,7 @@ namespace nvhttp {
fg.disable(); fg.disable();
return; return;
} }
} } else if (it->second == "pairchallenge"sv) {
else if (it->second == "pairchallenge"sv) {
tree.put("root.paired", 1); tree.put("root.paired", 1);
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
return; return;
@@ -780,23 +769,19 @@ namespace nvhttp {
if (it = args.find("clientchallenge"); it != std::end(args)) { if (it = args.find("clientchallenge"); it != std::end(args)) {
auto challenge = util::from_hex_vec(it->second, true); auto challenge = util::from_hex_vec(it->second, true);
clientchallenge(sess_it->second, tree, challenge); clientchallenge(sess_it->second, tree, challenge);
} } else if (it = args.find("serverchallengeresp"); it != std::end(args)) {
else if (it = args.find("serverchallengeresp"); it != std::end(args)) {
auto encrypted_response = util::from_hex_vec(it->second, true); auto encrypted_response = util::from_hex_vec(it->second, true);
serverchallengeresp(sess_it->second, tree, encrypted_response); serverchallengeresp(sess_it->second, tree, encrypted_response);
} } else if (it = args.find("clientpairingsecret"); it != std::end(args)) {
else if (it = args.find("clientpairingsecret"); it != std::end(args)) {
auto pairingsecret = util::from_hex_vec(it->second, true); auto pairingsecret = util::from_hex_vec(it->second, true);
clientpairingsecret(sess_it->second, tree, pairingsecret); clientpairingsecret(sess_it->second, tree, pairingsecret);
} } else {
else {
tree.put("root.<xmlattr>.status_code", 404); tree.put("root.<xmlattr>.status_code", 404);
tree.put("root.<xmlattr>.status_message", "Invalid pairing request"); tree.put("root.<xmlattr>.status_message", "Invalid pairing request");
} }
} }
bool bool pin(std::string pin, std::string name) {
pin(std::string pin, std::string name) {
pt::ptree tree; pt::ptree tree;
if (map_id_sess.empty()) { if (map_id_sess.empty()) {
return false; return false;
@@ -807,7 +792,9 @@ namespace nvhttp {
tree.put("root.paired", 0); tree.put("root.paired", 0);
tree.put("root.<xmlattr>.status_code", 400); tree.put("root.<xmlattr>.status_code", 400);
tree.put( tree.put(
"root.<xmlattr>.status_message", "Pin must be 4 digits, " + std::to_string(pin.size()) + " provided"); "root.<xmlattr>.status_message",
"Pin must be 4 digits, " + std::to_string(pin.size()) + " provided"
);
return false; return false;
} }
@@ -833,11 +820,9 @@ namespace nvhttp {
auto &async_response = sess.async_insert_pin.response; auto &async_response = sess.async_insert_pin.response;
if (async_response.has_left() && async_response.left()) { if (async_response.has_left() && async_response.left()) {
async_response.left()->write(data.str()); async_response.left()->write(data.str());
} } else if (async_response.has_right() && async_response.right()) {
else if (async_response.has_right() && async_response.right()) {
async_response.right()->write(data.str()); async_response.right()->write(data.str());
} } else {
else {
return false; return false;
} }
@@ -847,9 +832,8 @@ namespace nvhttp {
return true; return true;
} }
template <class T> template<class T>
void void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request); print_req<T>(request);
int pair_status = 0; int pair_status = 0;
@@ -907,8 +891,7 @@ namespace nvhttp {
tree.put("root.VirtualDisplayDriverReady", true); tree.put("root.VirtualDisplayDriverReady", true);
} }
#endif #endif
} } else {
else {
tree.put("root.mac", "00:00:00:00:00:00"); tree.put("root.mac", "00:00:00:00:00:00");
tree.put("root.Permission", "0"); tree.put("root.Permission", "0");
} }
@@ -924,8 +907,7 @@ namespace nvhttp {
// support know to ignore this bogus address. // support know to ignore this bogus address.
if (local_endpoint.address().is_v6() && !local_endpoint.address().to_v6().is_v4_mapped()) { if (local_endpoint.address().is_v6() && !local_endpoint.address().to_v6().is_v4_mapped()) {
tree.put("root.LocalIP", "127.0.0.1"); tree.put("root.LocalIP", "127.0.0.1");
} } else {
else {
tree.put("root.LocalIP", net::addr_to_normalized_string(local_endpoint.address())); tree.put("root.LocalIP", net::addr_to_normalized_string(local_endpoint.address()));
} }
@@ -981,59 +963,57 @@ namespace nvhttp {
response->close_connection_after_response = true; response->close_connection_after_response = true;
} }
pt::ptree nlohmann::json get_all_clients() {
get_all_clients() { nlohmann::json named_cert_nodes = nlohmann::json::array();
pt::ptree named_cert_nodes;
client_t &client = client_root; client_t &client = client_root;
std::list<std::string> connected_uuids = rtsp_stream::get_all_session_uuids(); std::list<std::string> connected_uuids = rtsp_stream::get_all_session_uuids();
for (auto &named_cert_p : client.named_devices) { for (auto &named_cert : client.named_devices) {
pt::ptree named_cert_node; nlohmann::json named_cert_node;
named_cert_node.put("name"s, named_cert_p->name); named_cert_node["name"] = named_cert.name;
named_cert_node.put("uuid"s, named_cert_p->uuid); named_cert_node["uuid"] = named_cert.uuid;
named_cert_node.put("perm", (uint32_t)named_cert_p->perm); named_cert_node["perm"] = static_cast<uint32_t>(named_cert.perm);
// Add "do" commands // Add "do" commands if available
if (!named_cert_p->do_cmds.empty()) { if (!named_cert.do_cmds.empty()) {
pt::ptree do_cmds_node; nlohmann::json do_cmds_node = nlohmann::json::array();
for (const auto& cmd : named_cert_p->do_cmds) { for (const auto &cmd : named_cert.do_cmds) {
do_cmds_node.push_back(std::make_pair(""s, crypto::command_entry_t::serialize(cmd))); do_cmds_node.push_back(crypto::command_entry_t::serialize(cmd));
} }
named_cert_node.add_child("do", do_cmds_node); named_cert_node["do"] = do_cmds_node;
} }
// Add "undo" commands // Add "undo" commands if available
if (!named_cert_p->undo_cmds.empty()) { if (!named_cert.undo_cmds.empty()) {
pt::ptree undo_cmds_node; nlohmann::json undo_cmds_node = nlohmann::json::array();
for (const auto& cmd : named_cert_p->undo_cmds) { for (const auto &cmd : named_cert.undo_cmds) {
undo_cmds_node.push_back(std::make_pair(""s, crypto::command_entry_t::serialize(cmd))); undo_cmds_node.push_back(crypto::command_entry_t::serialize(cmd));
} }
named_cert_node.add_child("undo", undo_cmds_node); named_cert_node["undo"] = undo_cmds_node;
} }
if (connected_uuids.empty()) { // Determine connection status
named_cert_node.put("connected"s, false);
} else {
bool connected = false; bool connected = false;
if (connected_uuids.empty()) {
connected = false;
} else {
for (auto it = connected_uuids.begin(); it != connected_uuids.end(); ++it) { for (auto it = connected_uuids.begin(); it != connected_uuids.end(); ++it) {
if (*it == named_cert_p->uuid) { if (*it == named_cert.uuid) {
connected = true; connected = true;
connected_uuids.erase(it); connected_uuids.erase(it);
break; break;
} }
} }
named_cert_node.put("connected"s, connected);
} }
named_cert_node["connected"] = connected;
named_cert_nodes.push_back(std::make_pair(""s, named_cert_node)); named_cert_nodes.push_back(named_cert_node);
} }
return named_cert_nodes; return named_cert_nodes;
} }
void void applist(resp_https_t response, req_https_t request) {
applist(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request); print_req<SunshineHTTPS>(request);
pt::ptree tree; pt::ptree tree;
@@ -1100,8 +1080,7 @@ namespace nvhttp {
} }
void void launch(bool &host_audio, resp_https_t response, req_https_t request) {
launch(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request); print_req<SunshineHTTPS>(request);
pt::ptree tree; pt::ptree tree;
@@ -1141,7 +1120,8 @@ namespace nvhttp {
args.find("rikey"s) == std::end(args) || args.find("rikey"s) == std::end(args) ||
args.find("rikeyid"s) == std::end(args) || args.find("rikeyid"s) == std::end(args) ||
args.find("localAudioPlayMode"s) == std::end(args) || args.find("localAudioPlayMode"s) == std::end(args) ||
args.find("appid"s) == std::end(args)) { args.find("appid"s) == std::end(args)
) {
tree.put("root.resume", 0); tree.put("root.resume", 0);
tree.put("root.<xmlattr>.status_code", 400); tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", "Missing a required launch parameter"); tree.put("root.<xmlattr>.status_message", "Missing a required launch parameter");
@@ -1261,16 +1241,13 @@ namespace nvhttp {
} }
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' +
std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
tree.put("root.gamesession", 1); tree.put("root.gamesession", 1);
rtsp_stream::launch_session_raise(launch_session); rtsp_stream::launch_session_raise(launch_session);
} }
void void resume(bool &host_audio, resp_https_t response, req_https_t request) {
resume(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request); print_req<SunshineHTTPS>(request);
pt::ptree tree; pt::ptree tree;
@@ -1305,7 +1282,8 @@ namespace nvhttp {
auto args = request->parse_query_string(); auto args = request->parse_query_string();
if ( if (
args.find("rikey"s) == std::end(args) || args.find("rikey"s) == std::end(args) ||
args.find("rikeyid"s) == std::end(args)) { args.find("rikeyid"s) == std::end(args)
) {
tree.put("root.resume", 0); tree.put("root.resume", 0);
tree.put("root.<xmlattr>.status_code", 400); tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", "Missing a required resume parameter"); tree.put("root.<xmlattr>.status_message", "Missing a required resume parameter");
@@ -1316,7 +1294,7 @@ namespace nvhttp {
// Newer Moonlight clients send localAudioPlayMode on /resume too, // Newer Moonlight clients send localAudioPlayMode on /resume too,
// so we should use it if it's present in the args and there are // so we should use it if it's present in the args and there are
// no active sessions we could be interfering with. // no active sessions we could be interfering with.
const bool no_active_sessions { rtsp_stream::session_count() == 0 }; const bool no_active_sessions {rtsp_stream::session_count() == 0};
if (no_active_sessions && args.find("localAudioPlayMode"s) != std::end(args)) { if (no_active_sessions && args.find("localAudioPlayMode"s) != std::end(args)) {
host_audio = util::from_view(get_arg(args, "localAudioPlayMode")); host_audio = util::from_view(get_arg(args, "localAudioPlayMode"));
} }
@@ -1362,9 +1340,7 @@ namespace nvhttp {
} }
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' +
std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
tree.put("root.resume", 1); tree.put("root.resume", 1);
rtsp_stream::launch_session_raise(launch_session); rtsp_stream::launch_session_raise(launch_session);
@@ -1374,8 +1350,7 @@ namespace nvhttp {
#endif #endif
} }
void void cancel(resp_https_t response, req_https_t request) {
cancel(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request); print_req<SunshineHTTPS>(request);
pt::ptree tree; pt::ptree tree;
@@ -1411,8 +1386,7 @@ namespace nvhttp {
display_device::revert_configuration(); display_device::revert_configuration();
} }
void void appasset(resp_https_t response, req_https_t request) {
appasset(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request); print_req<SunshineHTTPS>(request);
auto fg = util::fail_guard([&]() { auto fg = util::fail_guard([&]() {
@@ -1443,8 +1417,7 @@ namespace nvhttp {
response->close_connection_after_response = true; response->close_connection_after_response = true;
} }
void void getClipboard(resp_https_t response, req_https_t request) {
getClipboard(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request); print_req<SunshineHTTPS>(request);
auto named_cert_p = get_verified_cert(request); auto named_cert_p = get_verified_cert(request);
@@ -1549,14 +1522,12 @@ namespace nvhttp {
return; return;
} }
void void setup(const std::string &pkey, const std::string &cert) {
setup(const std::string &pkey, const std::string &cert) {
conf_intern.pkey = pkey; conf_intern.pkey = pkey;
conf_intern.servercert = cert; conf_intern.servercert = cert;
} }
void void start() {
start() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown); auto shutdown_event = mail::man->event<bool>(mail::shutdown);
auto port_http = net::map_port(PORT_HTTP); auto port_http = net::map_port(PORT_HTTP);
@@ -1577,7 +1548,7 @@ namespace nvhttp {
// launch will store it in host_audio // launch will store it in host_audio
bool host_audio {}; bool host_audio {};
https_server_t https_server { config::nvhttp.cert, config::nvhttp.pkey }; https_server_t https_server {config::nvhttp.cert, config::nvhttp.pkey};
http_server_t http_server; http_server_t http_server;
// Verify certificates after establishing connection // Verify certificates after establishing connection
@@ -1642,8 +1613,12 @@ namespace nvhttp {
https_server.resource["^/pair$"]["GET"] = pair<SunshineHTTPS>; https_server.resource["^/pair$"]["GET"] = pair<SunshineHTTPS>;
https_server.resource["^/applist$"]["GET"] = applist; https_server.resource["^/applist$"]["GET"] = applist;
https_server.resource["^/appasset$"]["GET"] = appasset; https_server.resource["^/appasset$"]["GET"] = appasset;
https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) { launch(host_audio, resp, req); }; https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) {
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) { resume(host_audio, resp, req); }; launch(host_audio, resp, req);
};
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) {
resume(host_audio, resp, req);
};
https_server.resource["^/cancel$"]["GET"] = cancel; https_server.resource["^/cancel$"]["GET"] = cancel;
https_server.resource["^/actions/clipboard$"]["GET"] = getClipboard; https_server.resource["^/actions/clipboard$"]["GET"] = getClipboard;
https_server.resource["^/actions/clipboard$"]["POST"] = setClipboard; https_server.resource["^/actions/clipboard$"]["POST"] = setClipboard;
@@ -1663,8 +1638,7 @@ namespace nvhttp {
auto accept_and_run = [&](auto *http_server) { auto accept_and_run = [&](auto *http_server) {
try { try {
http_server->start(); http_server->start();
} } catch (boost::system::system_error &err) {
catch (boost::system::system_error &err) {
// It's possible the exception gets thrown after calling http_server->stop() from a different thread // It's possible the exception gets thrown after calling http_server->stop() from a different thread
if (shutdown_event->peek()) { if (shutdown_event->peek()) {
return; return;
@@ -1675,8 +1649,8 @@ namespace nvhttp {
return; return;
} }
}; };
std::thread ssl { accept_and_run, &https_server }; std::thread ssl {accept_and_run, &https_server};
std::thread tcp { accept_and_run, &http_server }; std::thread tcp {accept_and_run, &http_server};
// Wait for any event // Wait for any event
shutdown_event->view(); shutdown_event->view();
@@ -1768,16 +1742,14 @@ namespace nvhttp {
return false; return false;
} }
int bool unpair_client(const std::string_view uuid) {
unpair_client(std::string uuid) { bool removed = false;
int removed = 0;
client_t &client = client_root; client_t &client = client_root;
for (auto it = client.named_devices.begin(); it != client.named_devices.end();) { for (auto it = client.named_devices.begin(); it != client.named_devices.end();) {
if ((*it)->uuid == uuid) { if ((*it)->uuid == uuid) {
it = client.named_devices.erase(it); it = client.named_devices.erase(it);
removed++; removed = true;
} } else {
else {
++it; ++it;
} }
} }

View File

@@ -11,8 +11,9 @@
#include <list> #include <list>
// lib includes // lib includes
#include <Simple-Web-Server/server_https.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <nlohmann/json.hpp>
#include <Simple-Web-Server/server_https.hpp>
// local includes // local includes
#include "crypto.h" #include "crypto.h"
@@ -59,8 +60,7 @@ namespace nvhttp {
* nvhttp::start(); * nvhttp::start();
* @examples_end * @examples_end
*/ */
void void start();
start();
std::string std::string
get_arg(const args_t &args, const char *name, const char *default_value = nullptr); get_arg(const args_t &args, const char *name, const char *default_value = nullptr);
@@ -77,13 +77,13 @@ namespace nvhttp {
* @param pkey * @param pkey
* @param cert * @param cert
*/ */
void void setup(const std::string &pkey, const std::string &cert);
setup(const std::string &pkey, const std::string &cert);
class SunshineHTTPS: public SimpleWeb::HTTPS { class SunshineHTTPS: public SimpleWeb::HTTPS {
public: public:
SunshineHTTPS(boost::asio::io_context &io_context, boost::asio::ssl::context &ctx): SunshineHTTPS(boost::asio::io_context &io_context, boost::asio::ssl::context &ctx):
SimpleWeb::HTTPS(io_context, ctx) {} SimpleWeb::HTTPS(io_context, ctx) {
}
virtual ~SunshineHTTPS() { virtual ~SunshineHTTPS() {
// Gracefully shutdown the TLS connection // Gracefully shutdown the TLS connection
@@ -131,8 +131,7 @@ namespace nvhttp {
* @brief removes the temporary pairing session * @brief removes the temporary pairing session
* @param sess * @param sess
*/ */
void void remove_session(const pair_session_t &sess);
remove_session(const pair_session_t &sess);
/** /**
* @brief Pair, phase 1 * @brief Pair, phase 1
@@ -144,8 +143,7 @@ namespace nvhttp {
* *
* At this stage we only have to send back our public certificate. * At this stage we only have to send back our public certificate.
*/ */
void void getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin);
getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin);
/** /**
* @brief Pair, phase 2 * @brief Pair, phase 2
@@ -159,8 +157,7 @@ namespace nvhttp {
* *
* The hash + server_challenge will then be AES encrypted and sent as the `challengeresponse` in the returned XML * The hash + server_challenge will then be AES encrypted and sent as the `challengeresponse` in the returned XML
*/ */
void void clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge);
clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge);
/** /**
* @brief Pair, phase 3 * @brief Pair, phase 3
@@ -169,8 +166,7 @@ namespace nvhttp {
* we have to send back the `pairingsecret`: * we have to send back the `pairingsecret`:
* using our private key we have to sign the certificate_signature + server_secret (generated in phase 2) * using our private key we have to sign the certificate_signature + server_secret (generated in phase 2)
*/ */
void void serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response);
serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response);
/** /**
* @brief Pair, phase 4 (final) * @brief Pair, phase 4 (final)
@@ -186,8 +182,7 @@ namespace nvhttp {
* Then using the client certificate public key we should be able to verify that * Then using the client certificate public key we should be able to verify that
* the client secret has been signed by Moonlight * the client secret has been signed by Moonlight
*/ */
void void clientpairingsecret(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &client_pairing_secret);
clientpairingsecret(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &client_pairing_secret);
/** /**
* @brief Compare the user supplied pin to the Moonlight pin. * @brief Compare the user supplied pin to the Moonlight pin.
@@ -198,29 +193,27 @@ namespace nvhttp {
* bool pin_status = nvhttp::pin("1234", "laptop"); * bool pin_status = nvhttp::pin("1234", "laptop");
* @examples_end * @examples_end
*/ */
bool bool pin(std::string pin, std::string name);
pin(std::string pin, std::string name);
std::string request_otp(const std::string& passphrase, const std::string& deviceName); std::string request_otp(const std::string& passphrase, const std::string& deviceName);
/** /**
* @brief Remove single client. * @brief Remove single client.
* @param uuid The UUID of the client to remove.
* @examples * @examples
* nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1"); * nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1");
* @examples_end * @examples_end
*/ */
int bool unpair_client(std::string_view uuid);
unpair_client(std::string uniqueid);
/** /**
* @brief Get all paired clients. * @brief Get all paired clients.
* @return The list of all paired clients. * @return The list of all paired clients.
* @examples * @examples
* boost::property_tree::ptree clients = nvhttp::get_all_clients(); * nlohmann::json clients = nvhttp::get_all_clients();
* @examples_end * @examples_end
*/ */
boost::property_tree::ptree nlohmann::json get_all_clients();
get_all_clients();
/** /**
* @brief Remove all paired clients. * @brief Remove all paired clients.
@@ -228,8 +221,7 @@ namespace nvhttp {
* nvhttp::erase_all_clients(); * nvhttp::erase_all_clients();
* @examples_end * @examples_end
*/ */
void void erase_all_clients();
erase_all_clients();
/** /**
* @brief Stops a session. * @brief Stops a session.

View File

@@ -4,18 +4,21 @@
*/ */
#pragma once #pragma once
// standard includes
#include <bitset> #include <bitset>
#include <filesystem> #include <filesystem>
#include <functional> #include <functional>
#include <mutex> #include <mutex>
#include <string> #include <string>
// lib includes
#include <boost/core/noncopyable.hpp> #include <boost/core/noncopyable.hpp>
#ifndef _WIN32 #ifndef _WIN32
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/process.hpp> #include <boost/process.hpp>
#endif #endif
// local includes
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/thread_safe.h" #include "src/thread_safe.h"
@@ -44,13 +47,15 @@ namespace boost {
class address; class address;
} // namespace ip } // namespace ip
} // namespace asio } // namespace asio
namespace filesystem { namespace filesystem {
class path; class path;
} }
namespace process::inline v1 { namespace process::inline v1 {
class child; class child;
class group; class group;
template <typename Char> template<typename Char>
class basic_environment; class basic_environment;
typedef basic_environment<char> environment; typedef basic_environment<char> environment;
} // namespace process::inline v1 } // namespace process::inline v1
@@ -59,6 +64,7 @@ namespace boost {
namespace video { namespace video {
struct config_t; struct config_t;
} // namespace video } // namespace video
namespace nvenc { namespace nvenc {
class nvenc_base; class nvenc_base;
} }
@@ -103,26 +109,23 @@ namespace platf {
}; };
struct gamepad_feedback_msg_t { struct gamepad_feedback_msg_t {
static gamepad_feedback_msg_t static gamepad_feedback_msg_t make_rumble(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) {
make_rumble(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) {
gamepad_feedback_msg_t msg; gamepad_feedback_msg_t msg;
msg.type = gamepad_feedback_e::rumble; msg.type = gamepad_feedback_e::rumble;
msg.id = id; msg.id = id;
msg.data.rumble = { lowfreq, highfreq }; msg.data.rumble = {lowfreq, highfreq};
return msg; return msg;
} }
static gamepad_feedback_msg_t static gamepad_feedback_msg_t make_rumble_triggers(std::uint16_t id, std::uint16_t left, std::uint16_t right) {
make_rumble_triggers(std::uint16_t id, std::uint16_t left, std::uint16_t right) {
gamepad_feedback_msg_t msg; gamepad_feedback_msg_t msg;
msg.type = gamepad_feedback_e::rumble_triggers; msg.type = gamepad_feedback_e::rumble_triggers;
msg.id = id; msg.id = id;
msg.data.rumble_triggers = { left, right }; msg.data.rumble_triggers = {left, right};
return msg; return msg;
} }
static gamepad_feedback_msg_t static gamepad_feedback_msg_t make_motion_event_state(std::uint16_t id, std::uint8_t motion_type, std::uint16_t report_rate) {
make_motion_event_state(std::uint16_t id, std::uint8_t motion_type, std::uint16_t report_rate) {
gamepad_feedback_msg_t msg; gamepad_feedback_msg_t msg;
msg.type = gamepad_feedback_e::set_motion_event_state; msg.type = gamepad_feedback_e::set_motion_event_state;
msg.id = id; msg.id = id;
@@ -131,30 +134,33 @@ namespace platf {
return msg; return msg;
} }
static gamepad_feedback_msg_t static gamepad_feedback_msg_t make_rgb_led(std::uint16_t id, std::uint8_t r, std::uint8_t g, std::uint8_t b) {
make_rgb_led(std::uint16_t id, std::uint8_t r, std::uint8_t g, std::uint8_t b) {
gamepad_feedback_msg_t msg; gamepad_feedback_msg_t msg;
msg.type = gamepad_feedback_e::set_rgb_led; msg.type = gamepad_feedback_e::set_rgb_led;
msg.id = id; msg.id = id;
msg.data.rgb_led = { r, g, b }; msg.data.rgb_led = {r, g, b};
return msg; return msg;
} }
gamepad_feedback_e type; gamepad_feedback_e type;
std::uint16_t id; std::uint16_t id;
union { union {
struct { struct {
std::uint16_t lowfreq; std::uint16_t lowfreq;
std::uint16_t highfreq; std::uint16_t highfreq;
} rumble; } rumble;
struct { struct {
std::uint16_t left_trigger; std::uint16_t left_trigger;
std::uint16_t right_trigger; std::uint16_t right_trigger;
} rumble_triggers; } rumble_triggers;
struct { struct {
std::uint16_t report_rate; std::uint16_t report_rate;
std::uint8_t motion_type; std::uint8_t motion_type;
} motion_event_state; } motion_event_state;
struct { struct {
std::uint8_t r; std::uint8_t r;
std::uint8_t g; std::uint8_t g;
@@ -179,7 +185,8 @@ namespace platf {
}; };
constexpr std::uint8_t map_stereo[] { constexpr std::uint8_t map_stereo[] {
FRONT_LEFT, FRONT_RIGHT FRONT_LEFT,
FRONT_RIGHT
}; };
constexpr std::uint8_t map_surround51[] { constexpr std::uint8_t map_surround51[] {
FRONT_LEFT, FRONT_LEFT,
@@ -221,8 +228,7 @@ namespace platf {
unknown ///< Unknown unknown ///< Unknown
}; };
inline std::string_view inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) {
from_pix_fmt(pix_fmt_e pix_fmt) {
using namespace std::literals; using namespace std::literals;
#define _CONVERT(x) \ #define _CONVERT(x) \
case pix_fmt_e::x: \ case pix_fmt_e::x: \
@@ -344,10 +350,8 @@ namespace platf {
img_t(img_t &&) = delete; img_t(img_t &&) = delete;
img_t(const img_t &) = delete; img_t(const img_t &) = delete;
img_t & img_t &operator=(img_t &&) = delete;
operator=(img_t &&) = delete; img_t &operator=(const img_t &) = delete;
img_t &
operator=(const img_t &) = delete;
std::uint8_t *data {}; std::uint8_t *data {};
std::int32_t width {}; std::int32_t width {};
@@ -371,14 +375,14 @@ namespace platf {
std::string surround51; std::string surround51;
std::string surround71; std::string surround71;
}; };
std::optional<null_t> null; std::optional<null_t> null;
}; };
struct encode_device_t { struct encode_device_t {
virtual ~encode_device_t() = default; virtual ~encode_device_t() = default;
virtual int virtual int convert(platf::img_t &img) = 0;
convert(platf::img_t &img) = 0;
video::sunshine_colorspace_t colorspace; video::sunshine_colorspace_t colorspace;
}; };
@@ -387,21 +391,18 @@ namespace platf {
void *data {}; void *data {};
AVFrame *frame {}; AVFrame *frame {};
int int convert(platf::img_t &img) override {
convert(platf::img_t &img) override {
return -1; return -1;
} }
virtual void virtual void apply_colorspace() {
apply_colorspace() {
} }
/** /**
* @brief Set the frame to be encoded. * @brief Set the frame to be encoded.
* @note Implementations must take ownership of 'frame'. * @note Implementations must take ownership of 'frame'.
*/ */
virtual int virtual int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?"; BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?";
return -1; return -1;
}; };
@@ -410,29 +411,25 @@ namespace platf {
* @brief Initialize the hwframes context. * @brief Initialize the hwframes context.
* @note Implementations may set parameters during initialization of the hwframes context. * @note Implementations may set parameters during initialization of the hwframes context.
*/ */
virtual void virtual void init_hwframes(AVHWFramesContext *frames) {};
init_hwframes(AVHWFramesContext *frames) {};
/** /**
* @brief Provides a hook for allow platform-specific code to adjust codec options. * @brief Provides a hook for allow platform-specific code to adjust codec options.
* @note Implementations may set or modify codec options prior to codec initialization. * @note Implementations may set or modify codec options prior to codec initialization.
*/ */
virtual void virtual void init_codec_options(AVCodecContext *ctx, AVDictionary **options) {};
init_codec_options(AVCodecContext *ctx, AVDictionary **options) {};
/** /**
* @brief Prepare to derive a context. * @brief Prepare to derive a context.
* @note Implementations may make modifications required before context derivation * @note Implementations may make modifications required before context derivation
*/ */
virtual int virtual int prepare_to_derive_context(int hw_device_type) {
prepare_to_derive_context(int hw_device_type) {
return 0; return 0;
}; };
}; };
struct nvenc_encode_device_t: encode_device_t { struct nvenc_encode_device_t: encode_device_t {
virtual bool virtual bool init_encoder(const video::config_t &client_config, const video::sunshine_colorspace_t &colorspace) = 0;
init_encoder(const video::config_t &client_config, const video::sunshine_colorspace_t &colorspace) = 0;
nvenc::nvenc_base *nvenc = nullptr; nvenc::nvenc_base *nvenc = nullptr;
}; };
@@ -466,7 +463,9 @@ namespace platf {
using pull_free_image_cb_t = std::function<bool(std::shared_ptr<img_t> &img_out)>; using pull_free_image_cb_t = std::function<bool(std::shared_ptr<img_t> &img_out)>;
display_t() noexcept: display_t() noexcept:
offset_x { 0 }, offset_y { 0 } {} offset_x {0},
offset_y {0} {
}
/** /**
* @brief Capture a frame. * @brief Capture a frame.
@@ -480,32 +479,25 @@ namespace platf {
* @retval capture_e::error On error * @retval capture_e::error On error
* @retval capture_e::reinit When need of reinitialization * @retval capture_e::reinit When need of reinitialization
*/ */
virtual capture_e virtual capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, 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> virtual std::shared_ptr<img_t> alloc_img() = 0;
alloc_img() = 0;
virtual int virtual int dummy_img(img_t *img) = 0;
dummy_img(img_t *img) = 0;
virtual std::unique_ptr<avcodec_encode_device_t> virtual std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) {
make_avcodec_encode_device(pix_fmt_e pix_fmt) {
return nullptr; return nullptr;
} }
virtual std::unique_ptr<nvenc_encode_device_t> virtual std::unique_ptr<nvenc_encode_device_t> make_nvenc_encode_device(pix_fmt_e pix_fmt) {
make_nvenc_encode_device(pix_fmt_e pix_fmt) {
return nullptr; return nullptr;
} }
virtual bool virtual bool is_hdr() {
is_hdr() {
return false; return false;
} }
virtual bool virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) {
get_hdr_metadata(SS_HDR_METADATA &metadata) {
std::memset(&metadata, 0, sizeof(metadata)); std::memset(&metadata, 0, sizeof(metadata));
return false; return false;
} }
@@ -516,8 +508,7 @@ namespace platf {
* @param config The codec configuration. * @param config The codec configuration.
* @return `true` if supported, `false` otherwise. * @return `true` if supported, `false` otherwise.
*/ */
virtual bool virtual bool is_codec_supported(std::string_view name, const ::video::config_t &config) {
is_codec_supported(std::string_view name, const ::video::config_t &config) {
return true; return true;
} }
@@ -531,60 +522,48 @@ namespace platf {
protected: protected:
// collect capture timing data (at loglevel debug) // collect capture timing data (at loglevel debug)
logging::time_delta_periodic_logger sleep_overshoot_logger = { debug, "Frame capture sleep overshoot" }; logging::time_delta_periodic_logger sleep_overshoot_logger = {debug, "Frame capture sleep overshoot"};
}; };
class mic_t { class mic_t {
public: public:
virtual capture_e virtual capture_e sample(std::vector<float> &frame_buffer) = 0;
sample(std::vector<float> &frame_buffer) = 0;
virtual ~mic_t() = default; virtual ~mic_t() = default;
}; };
class audio_control_t { class audio_control_t {
public: public:
virtual int virtual int set_sink(const std::string &sink) = 0;
set_sink(const std::string &sink) = 0;
virtual std::unique_ptr<mic_t> virtual std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0;
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0;
/** /**
* @brief Check if the audio sink is available in the system. * @brief Check if the audio sink is available in the system.
* @param sink Sink to be checked. * @param sink Sink to be checked.
* @returns True if available, false otherwise. * @returns True if available, false otherwise.
*/ */
virtual bool virtual bool is_sink_available(const std::string &sink) = 0;
is_sink_available(const std::string &sink) = 0;
virtual std::optional<sink_t> virtual std::optional<sink_t> sink_info() = 0;
sink_info() = 0;
virtual ~audio_control_t() = default; virtual ~audio_control_t() = default;
}; };
void void freeInput(void *);
freeInput(void *);
using input_t = util::safe_ptr<void, freeInput>; using input_t = util::safe_ptr<void, freeInput>;
std::filesystem::path std::filesystem::path appdata();
appdata();
std::string std::string get_mac_address(const std::string_view &address);
get_mac_address(const std::string_view &address);
std::string std::string get_local_ip_for_gateway();
get_local_ip_for_gateway();
std::string std::string from_sockaddr(const sockaddr *const);
from_sockaddr(const sockaddr *const); std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const);
std::pair<std::uint16_t, std::string>
from_sockaddr_ex(const sockaddr *const);
std::unique_ptr<audio_control_t> std::unique_ptr<audio_control_t> audio_control();
audio_control();
/** /**
* @brief Get the display_t instance for the given hwdevice_type. * @brief Get the display_t instance for the given hwdevice_type.
@@ -594,22 +573,18 @@ namespace platf {
* @param config Stream configuration * @param config Stream configuration
* @return The display_t instance based on hwdevice_type. * @return The display_t instance based on hwdevice_type.
*/ */
std::shared_ptr<display_t> std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
// A list of names of displays accepted as display_name with the mem_type_e // A list of names of displays accepted as display_name with the mem_type_e
std::vector<std::string> std::vector<std::string> display_names(mem_type_e hwdevice_type);
display_names(mem_type_e hwdevice_type);
/** /**
* @brief Check if GPUs/drivers have changed since the last call to this function. * @brief Check if GPUs/drivers have changed since the last call to this function.
* @return `true` if a change has occurred or if it is unknown whether a change occurred. * @return `true` if a change has occurred or if it is unknown whether a change occurred.
*/ */
bool bool needs_encoder_reenumeration();
needs_encoder_reenumeration();
boost::process::v1::child boost::process::v1::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const boost::process::v1::environment &env, FILE *file, std::error_code &ec, boost::process::v1::group *group);
run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const boost::process::v1::environment &env, FILE *file, std::error_code &ec, boost::process::v1::group *group);
enum class thread_priority_e : int { enum class thread_priority_e : int {
low, ///< Low priority low, ///< Low priority
@@ -617,17 +592,13 @@ namespace platf {
high, ///< High priority high, ///< High priority
critical ///< Critical priority critical ///< Critical priority
}; };
void void adjust_thread_priority(thread_priority_e priority);
adjust_thread_priority(thread_priority_e priority);
// Allow OS-specific actions to be taken to prepare for streaming // Allow OS-specific actions to be taken to prepare for streaming
void void streaming_will_start();
streaming_will_start(); void streaming_will_stop();
void
streaming_will_stop();
void void restart();
restart();
/** /**
* @brief Set an environment variable. * @brief Set an environment variable.
@@ -635,16 +606,14 @@ namespace platf {
* @param value The value to set the environment variable to. * @param value The value to set the environment variable to.
* @return 0 on success, non-zero on failure. * @return 0 on success, non-zero on failure.
*/ */
int int set_env(const std::string &name, const std::string &value);
set_env(const std::string &name, const std::string &value);
/** /**
* @brief Unset an environment variable. * @brief Unset an environment variable.
* @param name The name of the environment variable. * @param name The name of the environment variable.
* @return 0 on success, non-zero on failure. * @return 0 on success, non-zero on failure.
*/ */
int int unset_env(const std::string &name);
unset_env(const std::string &name);
struct buffer_descriptor_t { struct buffer_descriptor_t {
const char *buffer; const char *buffer;
@@ -652,9 +621,14 @@ namespace platf {
// Constructors required for emplace_back() prior to C++20 // Constructors required for emplace_back() prior to C++20
buffer_descriptor_t(const char *buffer, size_t size): buffer_descriptor_t(const char *buffer, size_t size):
buffer(buffer), size(size) {} buffer(buffer),
size(size) {
}
buffer_descriptor_t(): buffer_descriptor_t():
buffer(nullptr), size(0) {} buffer(nullptr),
size(0) {
}
}; };
struct batched_send_info_t { struct batched_send_info_t {
@@ -685,24 +659,22 @@ namespace platf {
* @param offset The offset in the total payload data (bytes). * @param offset The offset in the total payload data (bytes).
* @return Buffer descriptor describing the region at the given offset. * @return Buffer descriptor describing the region at the given offset.
*/ */
buffer_descriptor_t buffer_descriptor_t buffer_for_payload_offset(ptrdiff_t offset) {
buffer_for_payload_offset(ptrdiff_t offset) {
for (const auto &desc : payload_buffers) { for (const auto &desc : payload_buffers) {
if (offset < desc.size) { if (offset < desc.size) {
return { return {
desc.buffer + offset, desc.buffer + offset,
desc.size - offset, desc.size - offset,
}; };
} } else {
else {
offset -= desc.size; offset -= desc.size;
} }
} }
return {}; return {};
} }
}; };
bool
send_batch(batched_send_info_t &send_info); bool send_batch(batched_send_info_t &send_info);
struct send_info_t { struct send_info_t {
const char *header; const char *header;
@@ -715,8 +687,8 @@ namespace platf {
uint16_t target_port; uint16_t target_port;
boost::asio::ip::address &source_address; boost::asio::ip::address &source_address;
}; };
bool
send(send_info_t &send_info); bool send(send_info_t &send_info);
enum class qos_data_type_e : int { enum class qos_data_type_e : int {
audio, ///< Audio audio, ///< Audio
@@ -731,34 +703,29 @@ namespace platf {
* @param data_type The type of traffic sent on this socket. * @param data_type The type of traffic sent on this socket.
* @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic.
*/ */
std::unique_ptr<deinit_t> 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, bool dscp_tagging);
enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging);
/** /**
* @brief Open a url in the default web browser. * @brief Open a url in the default web browser.
* @param url The url to open. * @param url The url to open.
*/ */
void void open_url(const std::string &url);
open_url(const std::string &url);
/** /**
* @brief Attempt to gracefully terminate a process group. * @brief Attempt to gracefully terminate a process group.
* @param native_handle The native handle of the process group. * @param native_handle The native handle of the process group.
* @return `true` if termination was successfully requested. * @return `true` if termination was successfully requested.
*/ */
bool bool request_process_group_exit(std::uintptr_t native_handle);
request_process_group_exit(std::uintptr_t native_handle);
/** /**
* @brief Check if a process group still has running children. * @brief Check if a process group still has running children.
* @param native_handle The native handle of the process group. * @param native_handle The native handle of the process group.
* @return `true` if processes are still running. * @return `true` if processes are still running.
*/ */
bool bool process_group_running(std::uintptr_t native_handle);
process_group_running(std::uintptr_t native_handle);
input_t input_t input();
input();
/** /**
* @brief Get the current mouse position on screen * @brief Get the current mouse position on screen
* @param input The input_t instance to use. * @param input The input_t instance to use.
@@ -767,24 +734,15 @@ namespace platf {
* auto [x, y] = get_mouse_loc(input); * auto [x, y] = get_mouse_loc(input);
* @examples_end * @examples_end
*/ */
util::point_t util::point_t get_mouse_loc(input_t &input);
get_mouse_loc(input_t &input); void move_mouse(input_t &input, int deltaX, int deltaY);
void void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
move_mouse(input_t &input, int deltaX, int deltaY); void button_mouse(input_t &input, int button, bool release);
void void scroll(input_t &input, int distance);
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y); void hscroll(input_t &input, int distance);
void void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags);
button_mouse(input_t &input, int button, bool release); void gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state);
void void unicode(input_t &input, char *utf8, int size);
scroll(input_t &input, int distance);
void
hscroll(input_t &input, int distance);
void
keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags);
void
gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state);
void
unicode(input_t &input, char *utf8, int size);
typedef deinit_t client_input_t; typedef deinit_t client_input_t;
@@ -793,8 +751,7 @@ namespace platf {
* @param input The global input context. * @param input The global input context.
* @return A unique pointer to a per-client input data context. * @return A unique pointer to a per-client input data context.
*/ */
std::unique_ptr<client_input_t> std::unique_ptr<client_input_t> allocate_client_input_context(input_t &input);
allocate_client_input_context(input_t &input);
/** /**
* @brief Send a touch event to the OS. * @brief Send a touch event to the OS.
@@ -802,8 +759,7 @@ namespace platf {
* @param touch_port The current viewport for translating to screen coordinates. * @param touch_port The current viewport for translating to screen coordinates.
* @param touch The touch event. * @param touch The touch event.
*/ */
void void touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch);
touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch);
/** /**
* @brief Send a pen event to the OS. * @brief Send a pen event to the OS.
@@ -811,32 +767,28 @@ namespace platf {
* @param touch_port The current viewport for translating to screen coordinates. * @param touch_port The current viewport for translating to screen coordinates.
* @param pen The pen event. * @param pen The pen event.
*/ */
void void pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen);
pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen);
/** /**
* @brief Send a gamepad touch event to the OS. * @brief Send a gamepad touch event to the OS.
* @param input The global input context. * @param input The global input context.
* @param touch The touch event. * @param touch The touch event.
*/ */
void void gamepad_touch(input_t &input, const gamepad_touch_t &touch);
gamepad_touch(input_t &input, const gamepad_touch_t &touch);
/** /**
* @brief Send a gamepad motion event to the OS. * @brief Send a gamepad motion event to the OS.
* @param input The global input context. * @param input The global input context.
* @param motion The motion event. * @param motion The motion event.
*/ */
void void gamepad_motion(input_t &input, const gamepad_motion_t &motion);
gamepad_motion(input_t &input, const gamepad_motion_t &motion);
/** /**
* @brief Send a gamepad battery event to the OS. * @brief Send a gamepad battery event to the OS.
* @param input The global input context. * @param input The global input context.
* @param battery The battery event. * @param battery The battery event.
*/ */
void void gamepad_battery(input_t &input, const gamepad_battery_t &battery);
gamepad_battery(input_t &input, const gamepad_battery_t &battery);
/** /**
* @brief Create a new virtual gamepad. * @brief Create a new virtual gamepad.
@@ -846,35 +798,29 @@ namespace platf {
* @param feedback_queue The queue for posting messages back to the client. * @param feedback_queue The queue for posting messages back to the client.
* @return 0 on success. * @return 0 on success.
*/ */
int int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue); void free_gamepad(input_t &input, int nr);
void
free_gamepad(input_t &input, int nr);
/** /**
* @brief Get the supported platform capabilities to advertise to the client. * @brief Get the supported platform capabilities to advertise to the client.
* @return Capability flags. * @return Capability flags.
*/ */
platform_caps::caps_t platform_caps::caps_t get_capabilities();
get_capabilities();
#define SERVICE_NAME "Apollo" #define SERVICE_NAME "Apollo"
#define SERVICE_TYPE "_nvstream._tcp" #define SERVICE_TYPE "_nvstream._tcp"
namespace publish { namespace publish {
[[nodiscard]] std::unique_ptr<deinit_t> [[nodiscard]] std::unique_ptr<deinit_t> start();
start();
} }
[[nodiscard]] std::unique_ptr<deinit_t> [[nodiscard]] std::unique_ptr<deinit_t> init();
init();
/** /**
* @brief Returns the current computer name in UTF-8. * @brief Returns the current computer name in UTF-8.
* @return Computer name or a placeholder upon failure. * @return Computer name or a placeholder upon failure.
*/ */
std::string std::string get_host_name();
get_host_name();
/** /**
* @brief Gets the supported gamepads for this platform backend. * @brief Gets the supported gamepads for this platform backend.
@@ -882,8 +828,7 @@ namespace platf {
* @param input Pointer to the platform's `input_t` or `nullptr`. * @param input Pointer to the platform's `input_t` or `nullptr`.
* @return Vector of gamepad options and status. * @return Vector of gamepad options and status.
*/ */
std::vector<supported_gamepad_t> & std::vector<supported_gamepad_t> &supported_gamepads(input_t *input);
supported_gamepads(input_t *input);
struct high_precision_timer: private boost::noncopyable { struct high_precision_timer: private boost::noncopyable {
virtual ~high_precision_timer() = default; virtual ~high_precision_timer() = default;
@@ -892,23 +837,20 @@ namespace platf {
* @brief Sleep for the duration * @brief Sleep for the duration
* @param duration Sleep duration * @param duration Sleep duration
*/ */
virtual void virtual void sleep_for(const std::chrono::nanoseconds &duration) = 0;
sleep_for(const std::chrono::nanoseconds &duration) = 0;
/** /**
* @brief Check if platform-specific timer backend has been initialized successfully * @brief Check if platform-specific timer backend has been initialized successfully
* @return `true` on success, `false` on error * @return `true` on success, `false` on error
*/ */
virtual virtual operator bool() = 0;
operator bool() = 0;
}; };
/** /**
* @brief Create platform-specific timer capable of high-precision sleep * @brief Create platform-specific timer capable of high-precision sleep
* @return A unique pointer to timer * @return A unique pointer to timer
*/ */
std::unique_ptr<high_precision_timer> std::unique_ptr<high_precision_timer> create_high_precision_timer();
create_high_precision_timer();
std::string std::string
get_clipboard(); get_clipboard();

View File

@@ -2,20 +2,21 @@
* @file src/platform/linux/audio.cpp * @file src/platform/linux/audio.cpp
* @brief Definitions for audio control on Linux. * @brief Definitions for audio control on Linux.
*/ */
// standard includes
#include <bitset> #include <bitset>
#include <sstream> #include <sstream>
#include <thread> #include <thread>
// lib includes
#include <boost/regex.hpp> #include <boost/regex.hpp>
#include <pulse/error.h> #include <pulse/error.h>
#include <pulse/pulseaudio.h> #include <pulse/pulseaudio.h>
#include <pulse/simple.h> #include <pulse/simple.h>
#include "src/platform/common.h" // local includes
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h"
#include "src/thread_safe.h" #include "src/thread_safe.h"
namespace platf { namespace platf {
@@ -32,8 +33,7 @@ namespace platf {
PA_CHANNEL_POSITION_SIDE_RIGHT, PA_CHANNEL_POSITION_SIDE_RIGHT,
}; };
std::string std::string to_string(const char *name, const std::uint8_t *mapping, int channels) {
to_string(const char *name, const std::uint8_t *mapping, int channels) {
std::stringstream ss; std::stringstream ss;
ss << "rate=48000 sink_name="sv << name << " format=float channels="sv << channels << " channel_map="sv; ss << "rate=48000 sink_name="sv << name << " format=float channels="sv << channels << " channel_map="sv;
@@ -53,8 +53,7 @@ namespace platf {
struct mic_attr_t: public mic_t { struct mic_attr_t: public mic_t {
util::safe_ptr<pa_simple, pa_simple_free> mic; util::safe_ptr<pa_simple, pa_simple_free> mic;
capture_e capture_e sample(std::vector<float> &sample_buf) override {
sample(std::vector<float> &sample_buf) override {
auto sample_size = sample_buf.size(); auto sample_size = sample_buf.size();
auto buf = sample_buf.data(); auto buf = sample_buf.data();
@@ -69,11 +68,10 @@ namespace platf {
} }
}; };
std::unique_ptr<mic_t> std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
auto mic = std::make_unique<mic_attr_t>(); auto mic = std::make_unique<mic_attr_t>();
pa_sample_spec ss { PA_SAMPLE_FLOAT32, sample_rate, (std::uint8_t) channels }; pa_sample_spec ss {PA_SAMPLE_FLOAT32, sample_rate, (std::uint8_t) channels};
pa_channel_map pa_map; pa_channel_map pa_map;
pa_map.channels = channels; pa_map.channels = channels;
@@ -92,9 +90,8 @@ namespace platf {
int status; int status;
mic->mic.reset( mic->mic.reset(
pa_simple_new(nullptr, "sunshine", pa_simple_new(nullptr, "sunshine", pa_stream_direction_t::PA_STREAM_RECORD, source_name.c_str(), "sunshine-record", &ss, &pa_map, &pa_attr, &status)
pa_stream_direction_t::PA_STREAM_RECORD, source_name.c_str(), );
"sunshine-record", &ss, &pa_map, &pa_attr, &status));
if (!mic->mic) { if (!mic->mic) {
auto err_str = pa_strerror(status); auto err_str = pa_strerror(status);
@@ -106,38 +103,37 @@ namespace platf {
} }
namespace pa { namespace pa {
template <bool B, class T> template<bool B, class T>
struct add_const_helper; struct add_const_helper;
template <class T> template<class T>
struct add_const_helper<true, T> { struct add_const_helper<true, T> {
using type = const std::remove_pointer_t<T> *; using type = const std::remove_pointer_t<T> *;
}; };
template <class T> template<class T>
struct add_const_helper<false, T> { struct add_const_helper<false, T> {
using type = const T *; using type = const T *;
}; };
template <class T> template<class T>
using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type; using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type;
template <class T> template<class T>
void void pa_free(T *p) {
pa_free(T *p) {
pa_xfree(p); pa_xfree(p);
} }
using ctx_t = util::safe_ptr<pa_context, pa_context_unref>; using ctx_t = util::safe_ptr<pa_context, pa_context_unref>;
using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>; using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>;
using op_t = util::safe_ptr<pa_operation, pa_operation_unref>; using op_t = util::safe_ptr<pa_operation, pa_operation_unref>;
using string_t = util::safe_ptr<char, pa_free<char>>; using string_t = util::safe_ptr<char, pa_free<char>>;
template <class T> template<class T>
using cb_simple_t = std::function<void(ctx_t::pointer, add_const_t<T> i)>; using cb_simple_t = std::function<void(ctx_t::pointer, add_const_t<T> i)>;
template <class T> template<class T>
void void cb(ctx_t::pointer ctx, add_const_t<T> i, void *userdata) {
cb(ctx_t::pointer ctx, add_const_t<T> i, void *userdata) {
auto &f = *(cb_simple_t<T> *) userdata; auto &f = *(cb_simple_t<T> *) userdata;
// Cannot similarly filter on eol here. Unless reported otherwise assume // Cannot similarly filter on eol here. Unless reported otherwise assume
@@ -145,12 +141,11 @@ namespace platf {
f(ctx, i); f(ctx, i);
} }
template <class T> template<class T>
using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>; using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>;
template <class T> template<class T>
void void cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
auto &f = *(cb_t<T> *) userdata; auto &f = *(cb_t<T> *) userdata;
// For some reason, pulseaudio calls this callback after disconnecting // For some reason, pulseaudio calls this callback after disconnecting
@@ -161,22 +156,19 @@ namespace platf {
f(ctx, i, eol); f(ctx, i, eol);
} }
void void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
auto alarm = (safe::alarm_raw_t<int> *) userdata; auto alarm = (safe::alarm_raw_t<int> *) userdata;
alarm->ring(i); alarm->ring(i);
} }
void void ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
auto &f = *(std::function<void(ctx_t::pointer)> *) userdata; auto &f = *(std::function<void(ctx_t::pointer)> *) userdata;
f(ctx); f(ctx);
} }
void void success_cb(ctx_t::pointer ctx, int status, void *userdata) {
success_cb(ctx_t::pointer ctx, int status, void *userdata) {
assert(userdata != nullptr); assert(userdata != nullptr);
auto alarm = (safe::alarm_raw_t<int> *) userdata; auto alarm = (safe::alarm_raw_t<int> *) userdata;
@@ -205,8 +197,8 @@ namespace platf {
std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb; std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb;
std::thread worker; std::thread worker;
int
init() { int init() {
events = std::make_unique<safe::event_t<ctx_event_e>>(); events = std::make_unique<safe::event_t<ctx_event_e>>();
loop.reset(pa_mainloop_new()); loop.reset(pa_mainloop_new());
ctx.reset(pa_context_new(pa_mainloop_get_api(loop.get()), "sunshine")); ctx.reset(pa_context_new(pa_mainloop_get_api(loop.get()), "sunshine"));
@@ -262,8 +254,7 @@ namespace platf {
return 0; return 0;
} }
int int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
auto alarm = safe::make_alarm<int>(); auto alarm = safe::make_alarm<int>();
op_t op { op_t op {
@@ -272,15 +263,15 @@ namespace platf {
"module-null-sink", "module-null-sink",
to_string(name, channel_mapping, channels).c_str(), to_string(name, channel_mapping, channels).c_str(),
cb_i, cb_i,
alarm.get()), alarm.get()
),
}; };
alarm->wait(); alarm->wait();
return *alarm->status(); return *alarm->status();
} }
int int unload_null(std::uint32_t i) {
unload_null(std::uint32_t i) {
if (i == PA_INVALID_INDEX) { if (i == PA_INVALID_INDEX) {
return 0; return 0;
} }
@@ -301,8 +292,7 @@ namespace platf {
return 0; return 0;
} }
std::optional<sink_t> std::optional<sink_t> sink_info() override {
sink_info() override {
constexpr auto stereo = "sink-sunshine-stereo"; constexpr auto stereo = "sink-sunshine-stereo";
constexpr auto surround51 = "sink-sunshine-surround51"; constexpr auto surround51 = "sink-sunshine-surround51";
constexpr auto surround71 = "sink-sunshine-surround71"; constexpr auto surround71 = "sink-sunshine-surround71";
@@ -331,20 +321,18 @@ namespace platf {
index.stereo = sink_info->owner_module; index.stereo = sink_info->owner_module;
++nullcount; ++nullcount;
} } else if (!std::strcmp(sink_info->name, surround51)) {
else if (!std::strcmp(sink_info->name, surround51)) {
index.surround51 = sink_info->owner_module; index.surround51 = sink_info->owner_module;
++nullcount; ++nullcount;
} } else if (!std::strcmp(sink_info->name, surround71)) {
else if (!std::strcmp(sink_info->name, surround71)) {
index.surround71 = sink_info->owner_module; index.surround71 = sink_info->owner_module;
++nullcount; ++nullcount;
} }
}; };
op_t op { pa_context_get_sink_info_list(ctx.get(), cb<pa_sink_info *>, &f) }; op_t op {pa_context_get_sink_info_list(ctx.get(), cb<pa_sink_info *>, &f)};
if (!op) { if (!op) {
BOOST_LOG(error) << "Couldn't create card info operation: "sv << pa_strerror(pa_context_errno(ctx.get())); BOOST_LOG(error) << "Couldn't create card info operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
@@ -365,8 +353,7 @@ namespace platf {
index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo)); index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo));
if (index.stereo == PA_INVALID_INDEX) { if (index.stereo == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for stereo: "sv << pa_strerror(pa_context_errno(ctx.get())); BOOST_LOG(warning) << "Couldn't create virtual sink for stereo: "sv << pa_strerror(pa_context_errno(ctx.get()));
} } else {
else {
++nullcount; ++nullcount;
} }
} }
@@ -375,8 +362,7 @@ namespace platf {
index.surround51 = load_null(surround51, speaker::map_surround51, sizeof(speaker::map_surround51)); index.surround51 = load_null(surround51, speaker::map_surround51, sizeof(speaker::map_surround51));
if (index.surround51 == PA_INVALID_INDEX) { if (index.surround51 == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-51: "sv << pa_strerror(pa_context_errno(ctx.get())); BOOST_LOG(warning) << "Couldn't create virtual sink for surround-51: "sv << pa_strerror(pa_context_errno(ctx.get()));
} } else {
else {
++nullcount; ++nullcount;
} }
} }
@@ -385,8 +371,7 @@ namespace platf {
index.surround71 = load_null(surround71, speaker::map_surround71, sizeof(speaker::map_surround71)); index.surround71 = load_null(surround71, speaker::map_surround71, sizeof(speaker::map_surround71));
if (index.surround71 == PA_INVALID_INDEX) { if (index.surround71 == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-71: "sv << pa_strerror(pa_context_errno(ctx.get())); BOOST_LOG(warning) << "Couldn't create virtual sink for surround-71: "sv << pa_strerror(pa_context_errno(ctx.get()));
} } else {
else {
++nullcount; ++nullcount;
} }
} }
@@ -396,14 +381,13 @@ namespace platf {
} }
if (nullcount == 3) { if (nullcount == 3) {
sink.null = std::make_optional(sink_t::null_t { stereo, surround51, surround71 }); sink.null = std::make_optional(sink_t::null_t {stereo, surround51, surround71});
} }
return std::make_optional(std::move(sink)); return std::make_optional(std::move(sink));
} }
std::string std::string get_default_sink_name() {
get_default_sink_name() {
std::string sink_name; std::string sink_name;
auto alarm = safe::make_alarm<int>(); auto alarm = safe::make_alarm<int>();
@@ -419,14 +403,13 @@ namespace platf {
alarm->ring(0); alarm->ring(0);
}; };
op_t server_op { pa_context_get_server_info(ctx.get(), cb<pa_server_info *>, &server_f) }; op_t server_op {pa_context_get_server_info(ctx.get(), cb<pa_server_info *>, &server_f)};
alarm->wait(); alarm->wait();
// No need to check status. If it failed just return default name. // No need to check status. If it failed just return default name.
return sink_name; return sink_name;
} }
std::string std::string get_monitor_name(const std::string &sink_name) {
get_monitor_name(const std::string &sink_name) {
std::string monitor_name; std::string monitor_name;
auto alarm = safe::make_alarm<int>(); auto alarm = safe::make_alarm<int>();
@@ -449,7 +432,7 @@ namespace platf {
monitor_name = sink_info->monitor_source_name; monitor_name = sink_info->monitor_source_name;
}; };
op_t sink_op { pa_context_get_sink_info_by_name(ctx.get(), sink_name.c_str(), cb<pa_sink_info *>, &sink_f) }; op_t sink_op {pa_context_get_sink_info_by_name(ctx.get(), sink_name.c_str(), cb<pa_sink_info *>, &sink_f)};
alarm->wait(); alarm->wait();
// No need to check status. If it failed just return default name. // No need to check status. If it failed just return default name.
@@ -457,8 +440,7 @@ namespace platf {
return monitor_name; return monitor_name;
} }
std::unique_ptr<mic_t> std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
// Sink choice priority: // Sink choice priority:
// 1. Config sink // 1. Config sink
// 2. Last sink swapped to (Usually virtual in this case) // 2. Last sink swapped to (Usually virtual in this case)
@@ -467,26 +449,32 @@ namespace platf {
// but this happens right after the swap so the default returned by PA was not // but this happens right after the swap so the default returned by PA was not
// the new one just set! // the new one just set!
auto sink_name = config::audio.sink; auto sink_name = config::audio.sink;
if (sink_name.empty()) sink_name = requested_sink; if (sink_name.empty()) {
if (sink_name.empty()) sink_name = get_default_sink_name(); sink_name = requested_sink;
}
if (sink_name.empty()) {
sink_name = get_default_sink_name();
}
return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name)); return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name));
} }
bool bool is_sink_available(const std::string &sink) override {
is_sink_available(const std::string &sink) override {
BOOST_LOG(warning) << "audio_control_t::is_sink_available() unimplemented: "sv << sink; BOOST_LOG(warning) << "audio_control_t::is_sink_available() unimplemented: "sv << sink;
return true; return true;
} }
int int set_sink(const std::string &sink) override {
set_sink(const std::string &sink) override {
auto alarm = safe::make_alarm<int>(); auto alarm = safe::make_alarm<int>();
BOOST_LOG(info) << "Setting default sink to: ["sv << sink << "]"sv; BOOST_LOG(info) << "Setting default sink to: ["sv << sink << "]"sv;
op_t op { op_t op {
pa_context_set_default_sink( pa_context_set_default_sink(
ctx.get(), sink.c_str(), success_cb, alarm.get()), ctx.get(),
sink.c_str(),
success_cb,
alarm.get()
),
}; };
if (!op) { if (!op) {
@@ -525,8 +513,7 @@ namespace platf {
}; };
} // namespace pa } // namespace pa
std::unique_ptr<audio_control_t> std::unique_ptr<audio_control_t> audio_control() {
audio_control() {
auto audio = std::make_unique<pa::server_t>(); auto audio = std::make_unique<pa::server_t>();
if (audio->init()) { if (audio->init()) {

View File

@@ -2,13 +2,15 @@
* @file src/platform/linux/cuda.cpp * @file src/platform/linux/cuda.cpp
* @brief Definitions for CUDA encoding. * @brief Definitions for CUDA encoding.
*/ */
// standard includes
#include <bitset> #include <bitset>
#include <fcntl.h> #include <fcntl.h>
#include <filesystem> #include <filesystem>
#include <thread> #include <thread>
#include <NvFBC.h> // lib includes
#include <ffnvcodec/dynlink_loader.h> #include <ffnvcodec/dynlink_loader.h>
#include <NvFBC.h>
extern "C" { extern "C" {
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
@@ -16,6 +18,7 @@ extern "C" {
#include <libavutil/imgutils.h> #include <libavutil/imgutils.h>
} }
// local includes
#include "cuda.h" #include "cuda.h"
#include "graphics.h" #include "graphics.h"
#include "src/logging.h" #include "src/logging.h"
@@ -27,7 +30,8 @@ extern "C" {
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x) #define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
#define CU_CHECK(x, y) \ #define CU_CHECK(x, y) \
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) return -1 if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return -1
#define CU_CHECK_IGNORE(x, y) \ #define CU_CHECK_IGNORE(x, y) \
check((x), SUNSHINE_STRINGVIEW(y ": ")) check((x), SUNSHINE_STRINGVIEW(y ": "))
@@ -35,17 +39,16 @@ extern "C" {
namespace fs = std::filesystem; namespace fs = std::filesystem;
using namespace std::literals; using namespace std::literals;
namespace cuda { namespace cuda {
constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1; constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1;
constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39; constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39;
void void pass_error(const std::string_view &sv, const char *name, const char *description) {
pass_error(const std::string_view &sv, const char *name, const char *description) {
BOOST_LOG(error) << sv << name << ':' << description; BOOST_LOG(error) << sv << name << ':' << description;
} }
void void cff(CudaFunctions *cf) {
cff(CudaFunctions *cf) {
cuda_free_functions(&cf); cuda_free_functions(&cf);
} }
@@ -53,8 +56,7 @@ namespace cuda {
static cdf_t cdf; static cdf_t cdf;
inline static int inline static int check(CUresult result, const std::string_view &sv) {
check(CUresult result, const std::string_view &sv) {
if (result != CUDA_SUCCESS) { if (result != CUDA_SUCCESS) {
const char *name; const char *name;
const char *description; const char *description;
@@ -69,13 +71,11 @@ namespace cuda {
return 0; return 0;
} }
void void freeStream(CUstream stream) {
freeStream(CUstream stream) {
CU_CHECK_IGNORE(cdf->cuStreamDestroy(stream), "Couldn't destroy cuda stream"); CU_CHECK_IGNORE(cdf->cuStreamDestroy(stream), "Couldn't destroy cuda stream");
} }
void void unregisterResource(CUgraphicsResource resource) {
unregisterResource(CUgraphicsResource resource) {
CU_CHECK_IGNORE(cdf->cuGraphicsUnregisterResource(resource), "Couldn't unregister resource"); CU_CHECK_IGNORE(cdf->cuGraphicsUnregisterResource(resource), "Couldn't unregister resource");
} }
@@ -86,8 +86,7 @@ namespace cuda {
tex_t tex; tex_t tex;
}; };
int int init() {
init() {
auto status = cuda_load_functions(&cdf, nullptr); auto status = cuda_load_functions(&cdf, nullptr);
if (status) { if (status) {
BOOST_LOG(error) << "Couldn't load cuda: "sv << status; BOOST_LOG(error) << "Couldn't load cuda: "sv << status;
@@ -102,8 +101,7 @@ namespace cuda {
class cuda_t: public platf::avcodec_encode_device_t { class cuda_t: public platf::avcodec_encode_device_t {
public: public:
int int init(int in_width, int in_height) {
init(int in_width, int in_height) {
if (!cdf) { if (!cdf) {
BOOST_LOG(warning) << "cuda not initialized"sv; BOOST_LOG(warning) << "cuda not initialized"sv;
return -1; return -1;
@@ -117,8 +115,7 @@ namespace cuda {
return 0; return 0;
} }
int int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
this->hwframe.reset(frame); this->hwframe.reset(frame);
this->frame = frame; this->frame = frame;
@@ -156,8 +153,7 @@ namespace cuda {
return 0; return 0;
} }
void void apply_colorspace() override {
apply_colorspace() override {
sws.apply_colorspace(colorspace); sws.apply_colorspace(colorspace);
auto tex = tex_t::make(height, width * 4); auto tex = tex_t::make(height, width * 4);
@@ -182,11 +178,10 @@ namespace cuda {
return; return;
} }
sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex->texture.linear, stream.get(), { frame->width, frame->height, 0, 0 }); sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex->texture.linear, stream.get(), {frame->width, frame->height, 0, 0});
} }
cudaTextureObject_t cudaTextureObject_t tex_obj(const tex_t &tex) const {
tex_obj(const tex_t &tex) const {
return linear_interpolation ? tex.texture.linear : tex.texture.point; return linear_interpolation ? tex.texture.linear : tex.texture.point;
} }
@@ -203,13 +198,11 @@ namespace cuda {
class cuda_ram_t: public cuda_t { class cuda_ram_t: public cuda_t {
public: public:
int int convert(platf::img_t &img) override {
convert(platf::img_t &img) override {
return sws.load_ram(img, tex.array) || sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(tex), stream.get()); return sws.load_ram(img, tex.array) || sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(tex), stream.get());
} }
int int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
if (cuda_t::set_frame(frame, hw_frames_ctx)) { if (cuda_t::set_frame(frame, hw_frames_ctx)) {
return -1; return -1;
} }
@@ -229,8 +222,7 @@ namespace cuda {
class cuda_vram_t: public cuda_t { class cuda_vram_t: public cuda_t {
public: public:
int int convert(platf::img_t &img) override {
convert(platf::img_t &img) override {
return sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(((img_t *) &img)->tex), stream.get()); return sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(((img_t *) &img)->tex), stream.get());
} }
}; };
@@ -240,8 +232,7 @@ namespace cuda {
* @param index CUDA device index to open. * @param index CUDA device index to open.
* @return File descriptor or -1 on failure. * @return File descriptor or -1 on failure.
*/ */
file_t file_t open_drm_fd_for_cuda_device(int index) {
open_drm_fd_for_cuda_device(int index) {
CUdevice device; CUdevice device;
CU_CHECK(cdf->cuDeviceGet(&device, index), "Couldn't get CUDA device"); CU_CHECK(cdf->cuDeviceGet(&device, index), "Couldn't get CUDA device");
@@ -252,29 +243,29 @@ namespace cuda {
BOOST_LOG(debug) << "Found CUDA device with PCI bus ID: "sv << pci_bus_id.data(); BOOST_LOG(debug) << "Found CUDA device with PCI bus ID: "sv << pci_bus_id.data();
// Linux uses lowercase hexadecimal while CUDA uses uppercase // Linux uses lowercase hexadecimal while CUDA uses uppercase
std::transform(pci_bus_id.begin(), pci_bus_id.end(), pci_bus_id.begin(), std::transform(pci_bus_id.begin(), pci_bus_id.end(), pci_bus_id.begin(), [](char c) {
[](char c) { return std::tolower(c); }); return std::tolower(c);
});
// Look for the name of the primary node in sysfs // Look for the name of the primary node in sysfs
try { try {
char sysfs_path[PATH_MAX]; char sysfs_path[PATH_MAX];
std::snprintf(sysfs_path, sizeof(sysfs_path), "/sys/bus/pci/devices/%s/drm", pci_bus_id.data()); std::snprintf(sysfs_path, sizeof(sysfs_path), "/sys/bus/pci/devices/%s/drm", pci_bus_id.data());
fs::path sysfs_dir { sysfs_path }; fs::path sysfs_dir {sysfs_path};
for (auto &entry : fs::directory_iterator { sysfs_dir }) { for (auto &entry : fs::directory_iterator {sysfs_dir}) {
auto file = entry.path().filename(); auto file = entry.path().filename();
auto filestring = file.generic_string(); auto filestring = file.generic_string();
if (std::string_view { filestring }.substr(0, 4) != "card"sv) { if (std::string_view {filestring}.substr(0, 4) != "card"sv) {
continue; continue;
} }
BOOST_LOG(debug) << "Found DRM primary node: "sv << filestring; BOOST_LOG(debug) << "Found DRM primary node: "sv << filestring;
fs::path dri_path { "/dev/dri"sv }; fs::path dri_path {"/dev/dri"sv};
auto device_path = dri_path / file; auto device_path = dri_path / file;
return open(device_path.c_str(), O_RDWR); return open(device_path.c_str(), O_RDWR);
} }
} } catch (const std::filesystem::filesystem_error &err) {
catch (const std::filesystem::filesystem_error &err) {
BOOST_LOG(error) << "Failed to read sysfs: "sv << err.what(); BOOST_LOG(error) << "Failed to read sysfs: "sv << err.what();
} }
@@ -292,8 +283,7 @@ namespace cuda {
* @param offset_y Offset of content in captured frame. * @param offset_y Offset of content in captured frame.
* @return 0 on success or -1 on failure. * @return 0 on success or -1 on failure.
*/ */
int int init(int in_width, int in_height, int offset_x, int offset_y) {
init(int in_width, int in_height, int offset_x, int offset_y) {
// This must be non-zero to tell the video core that it's a hardware encoding device. // This must be non-zero to tell the video core that it's a hardware encoding device.
data = (void *) 0x1; data = (void *) 0x1;
@@ -340,8 +330,7 @@ namespace cuda {
* @param hw_frames_ctx_buf FFmpeg hardware frame context. * @param hw_frames_ctx_buf FFmpeg hardware frame context.
* @return 0 on success or -1 on failure. * @return 0 on success or -1 on failure.
*/ */
int int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
this->hwframe.reset(frame); this->hwframe.reset(frame);
this->frame = frame; this->frame = frame;
@@ -377,10 +366,8 @@ namespace cuda {
cuda_ctx->stream = stream.get(); cuda_ctx->stream = stream.get();
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&y_res, nv12->tex[0], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), CU_CHECK(cdf->cuGraphicsGLRegisterImage(&y_res, nv12->tex[0], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), "Couldn't register Y plane texture");
"Couldn't register Y plane texture"); CU_CHECK(cdf->cuGraphicsGLRegisterImage(&uv_res, nv12->tex[1], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), "Couldn't register UV plane texture");
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&uv_res, nv12->tex[1], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY),
"Couldn't register UV plane texture");
return 0; return 0;
} }
@@ -390,15 +377,13 @@ namespace cuda {
* @param img Captured screen image. * @param img Captured screen image.
* @return 0 on success or -1 on failure. * @return 0 on success or -1 on failure.
*/ */
int int convert(platf::img_t &img) override {
convert(platf::img_t &img) override {
auto &descriptor = (egl::img_descriptor_t &) img; auto &descriptor = (egl::img_descriptor_t &) img;
if (descriptor.sequence == 0) { if (descriptor.sequence == 0) {
// For dummy images, use a blank RGB texture instead of importing a DMA-BUF // For dummy images, use a blank RGB texture instead of importing a DMA-BUF
rgb = egl::create_blank(img); rgb = egl::create_blank(img);
} } else if (descriptor.sequence > sequence) {
else if (descriptor.sequence > sequence) {
sequence = descriptor.sequence; sequence = descriptor.sequence;
rgb = egl::rgb_t {}; rgb = egl::rgb_t {};
@@ -419,7 +404,7 @@ namespace cuda {
auto fmt_desc = av_pix_fmt_desc_get(sw_format); auto fmt_desc = av_pix_fmt_desc_get(sw_format);
// Map the GL textures to read for CUDA // Map the GL textures to read for CUDA
CUgraphicsResource resources[2] = { y_res.get(), uv_res.get() }; CUgraphicsResource resources[2] = {y_res.get(), uv_res.get()};
CU_CHECK(cdf->cuGraphicsMapResources(2, resources, stream.get()), "Couldn't map GL textures in CUDA"); CU_CHECK(cdf->cuGraphicsMapResources(2, resources, stream.get()), "Couldn't map GL textures in CUDA");
// Copy from the GL textures to the target CUDA frame // Copy from the GL textures to the target CUDA frame
@@ -445,8 +430,7 @@ namespace cuda {
/** /**
* @brief Configures shader parameters for the specified colorspace. * @brief Configures shader parameters for the specified colorspace.
*/ */
void void apply_colorspace() override {
apply_colorspace() override {
sws.apply_colorspace(colorspace); sws.apply_colorspace(colorspace);
} }
@@ -474,8 +458,7 @@ namespace cuda {
int offset_x, offset_y; int offset_x, offset_y;
}; };
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram) {
make_avcodec_encode_device(int width, int height, bool vram) {
if (init()) { if (init()) {
return nullptr; return nullptr;
} }
@@ -484,8 +467,7 @@ namespace cuda {
if (vram) { if (vram) {
cuda = std::make_unique<cuda_vram_t>(); cuda = std::make_unique<cuda_vram_t>();
} } else {
else {
cuda = std::make_unique<cuda_ram_t>(); cuda = std::make_unique<cuda_ram_t>();
} }
@@ -504,8 +486,7 @@ namespace cuda {
* @param offset_y Offset of content in captured frame. * @param offset_y Offset of content in captured frame.
* @return FFmpeg encoding device context. * @return FFmpeg encoding device context.
*/ */
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y) {
make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y) {
if (init()) { if (init()) {
return nullptr; return nullptr;
} }
@@ -521,29 +502,30 @@ namespace cuda {
namespace nvfbc { namespace nvfbc {
static PNVFBCCREATEINSTANCE createInstance {}; static PNVFBCCREATEINSTANCE createInstance {};
static NVFBC_API_FUNCTION_LIST func { NVFBC_VERSION }; static NVFBC_API_FUNCTION_LIST func {NVFBC_VERSION};
static constexpr inline NVFBC_BOOL static constexpr inline NVFBC_BOOL nv_bool(bool b) {
nv_bool(bool b) {
return b ? NVFBC_TRUE : NVFBC_FALSE; return b ? NVFBC_TRUE : NVFBC_FALSE;
} }
static void *handle { nullptr }; static void *handle {nullptr};
int
init() { int init() {
static bool funcs_loaded = false; static bool funcs_loaded = false;
if (funcs_loaded) return 0; if (funcs_loaded) {
return 0;
}
if (!handle) { if (!handle) {
handle = dyn::handle({ "libnvidia-fbc.so.1", "libnvidia-fbc.so" }); handle = dyn::handle({"libnvidia-fbc.so.1", "libnvidia-fbc.so"});
if (!handle) { if (!handle) {
return -1; return -1;
} }
} }
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs { std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &createInstance, "NvFBCCreateInstance" }, {(dyn::apiproc *) &createInstance, "NvFBCCreateInstance"},
}; };
if (dyn::load(handle, funcs)) { if (dyn::load(handle, funcs)) {
@@ -569,7 +551,7 @@ namespace cuda {
class ctx_t { class ctx_t {
public: public:
ctx_t(NVFBC_SESSION_HANDLE handle) { ctx_t(NVFBC_SESSION_HANDLE handle) {
NVFBC_BIND_CONTEXT_PARAMS params { NVFBC_BIND_CONTEXT_PARAMS_VER }; NVFBC_BIND_CONTEXT_PARAMS params {NVFBC_BIND_CONTEXT_PARAMS_VER};
if (func.nvFBCBindContext(handle, &params)) { if (func.nvFBCBindContext(handle, &params)) {
BOOST_LOG(error) << "Couldn't bind NvFBC context to current thread: " << func.nvFBCGetLastErrorStr(handle); BOOST_LOG(error) << "Couldn't bind NvFBC context to current thread: " << func.nvFBCGetLastErrorStr(handle);
@@ -579,7 +561,7 @@ namespace cuda {
} }
~ctx_t() { ~ctx_t() {
NVFBC_RELEASE_CONTEXT_PARAMS params { NVFBC_RELEASE_CONTEXT_PARAMS_VER }; NVFBC_RELEASE_CONTEXT_PARAMS params {NVFBC_RELEASE_CONTEXT_PARAMS_VER};
if (func.nvFBCReleaseContext(handle, &params)) { if (func.nvFBCReleaseContext(handle, &params)) {
BOOST_LOG(error) << "Couldn't release NvFBC context from current thread: " << func.nvFBCGetLastErrorStr(handle); BOOST_LOG(error) << "Couldn't release NvFBC context from current thread: " << func.nvFBCGetLastErrorStr(handle);
} }
@@ -597,26 +579,26 @@ namespace cuda {
public: public:
handle_t() = default; handle_t() = default;
handle_t(handle_t &&other): handle_t(handle_t &&other):
handle_flags { other.handle_flags }, handle { other.handle } { handle_flags {other.handle_flags},
handle {other.handle} {
other.handle_flags.reset(); other.handle_flags.reset();
} }
handle_t & handle_t &operator=(handle_t &&other) {
operator=(handle_t &&other) {
std::swap(handle_flags, other.handle_flags); std::swap(handle_flags, other.handle_flags);
std::swap(handle, other.handle); std::swap(handle, other.handle);
return *this; return *this;
} }
static std::optional<handle_t> static std::optional<handle_t> make() {
make() { NVFBC_CREATE_HANDLE_PARAMS params {NVFBC_CREATE_HANDLE_PARAMS_VER};
NVFBC_CREATE_HANDLE_PARAMS params { NVFBC_CREATE_HANDLE_PARAMS_VER };
// Set privateData to allow NvFBC on consumer NVIDIA GPUs. // Set privateData to allow NvFBC on consumer NVIDIA GPUs.
// Based on https://github.com/keylase/nvidia-patch/blob/3193b4b1cea91527bf09ea9b8db5aade6a3f3c0a/win/nvfbcwrp/nvfbcwrp_main.cpp#L23-L25 . // Based on https://github.com/keylase/nvidia-patch/blob/3193b4b1cea91527bf09ea9b8db5aade6a3f3c0a/win/nvfbcwrp/nvfbcwrp_main.cpp#L23-L25 .
const unsigned int MAGIC_PRIVATE_DATA[4] = { 0xAEF57AC5, 0x401D1A39, 0x1B856BBE, 0x9ED0CEBA }; const unsigned int MAGIC_PRIVATE_DATA[4] = {0xAEF57AC5, 0x401D1A39, 0x1B856BBE, 0x9ED0CEBA};
params.privateData = MAGIC_PRIVATE_DATA; params.privateData = MAGIC_PRIVATE_DATA;
params.privateDataSize = sizeof(MAGIC_PRIVATE_DATA); params.privateDataSize = sizeof(MAGIC_PRIVATE_DATA);
@@ -633,14 +615,12 @@ namespace cuda {
return handle; return handle;
} }
const char * const char *last_error() {
last_error() {
return func.nvFBCGetLastErrorStr(handle); return func.nvFBCGetLastErrorStr(handle);
} }
std::optional<NVFBC_GET_STATUS_PARAMS> std::optional<NVFBC_GET_STATUS_PARAMS> status() {
status() { NVFBC_GET_STATUS_PARAMS params {NVFBC_GET_STATUS_PARAMS_VER};
NVFBC_GET_STATUS_PARAMS params { NVFBC_GET_STATUS_PARAMS_VER };
auto status = func.nvFBCGetStatus(handle, &params); auto status = func.nvFBCGetStatus(handle, &params);
if (status) { if (status) {
@@ -652,8 +632,7 @@ namespace cuda {
return params; return params;
} }
int int capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
if (func.nvFBCCreateCaptureSession(handle, &capture_params)) { if (func.nvFBCCreateCaptureSession(handle, &capture_params)) {
BOOST_LOG(error) << "Failed to start capture session: "sv << last_error(); BOOST_LOG(error) << "Failed to start capture session: "sv << last_error();
return -1; return -1;
@@ -673,13 +652,12 @@ namespace cuda {
return 0; return 0;
} }
int int stop() {
stop() {
if (!handle_flags[SESSION_CAPTURE]) { if (!handle_flags[SESSION_CAPTURE]) {
return 0; return 0;
} }
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS params { NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER }; NVFBC_DESTROY_CAPTURE_SESSION_PARAMS params {NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER};
if (func.nvFBCDestroyCaptureSession(handle, &params)) { if (func.nvFBCDestroyCaptureSession(handle, &params)) {
BOOST_LOG(error) << "Couldn't destroy capture session: "sv << last_error(); BOOST_LOG(error) << "Couldn't destroy capture session: "sv << last_error();
@@ -692,17 +670,16 @@ namespace cuda {
return 0; return 0;
} }
int int reset() {
reset() {
if (!handle_flags[SESSION_HANDLE]) { if (!handle_flags[SESSION_HANDLE]) {
return 0; return 0;
} }
stop(); stop();
NVFBC_DESTROY_HANDLE_PARAMS params { NVFBC_DESTROY_HANDLE_PARAMS_VER }; NVFBC_DESTROY_HANDLE_PARAMS params {NVFBC_DESTROY_HANDLE_PARAMS_VER};
ctx_t ctx { handle }; ctx_t ctx {handle};
if (func.nvFBCDestroyHandle(handle, &params)) { if (func.nvFBCDestroyHandle(handle, &params)) {
BOOST_LOG(error) << "Couldn't destroy session handle: "sv << func.nvFBCGetLastErrorStr(handle); BOOST_LOG(error) << "Couldn't destroy session handle: "sv << func.nvFBCGetLastErrorStr(handle);
} }
@@ -723,14 +700,13 @@ namespace cuda {
class display_t: public platf::display_t { class display_t: public platf::display_t {
public: public:
int int init(const std::string_view &display_name, const ::video::config_t &config) {
init(const std::string_view &display_name, const ::video::config_t &config) {
auto handle = handle_t::make(); auto handle = handle_t::make();
if (!handle) { if (!handle) {
return -1; return -1;
} }
ctx_t ctx { handle->handle }; ctx_t ctx {handle->handle};
auto status_params = handle->status(); auto status_params = handle->status();
if (!status_params) { if (!status_params) {
@@ -744,19 +720,17 @@ namespace cuda {
if (monitor_nr < 0 || monitor_nr >= status_params->dwOutputNum) { if (monitor_nr < 0 || monitor_nr >= status_params->dwOutputNum) {
BOOST_LOG(warning) << "Can't stream monitor ["sv << monitor_nr << "], it needs to be between [0] and ["sv << status_params->dwOutputNum - 1 << "], defaulting to virtual desktop"sv; BOOST_LOG(warning) << "Can't stream monitor ["sv << monitor_nr << "], it needs to be between [0] and ["sv << status_params->dwOutputNum - 1 << "], defaulting to virtual desktop"sv;
} } else {
else {
streamedMonitor = monitor_nr; streamedMonitor = monitor_nr;
} }
} } else {
else {
BOOST_LOG(warning) << "XrandR not available, streaming entire virtual desktop"sv; BOOST_LOG(warning) << "XrandR not available, streaming entire virtual desktop"sv;
} }
} }
delay = std::chrono::nanoseconds { 1s } / config.framerate; delay = std::chrono::nanoseconds {1s} / config.framerate;
capture_params = NVFBC_CREATE_CAPTURE_SESSION_PARAMS { NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER }; capture_params = NVFBC_CREATE_CAPTURE_SESSION_PARAMS {NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER};
capture_params.eCaptureType = NVFBC_CAPTURE_SHARED_CUDA; capture_params.eCaptureType = NVFBC_CAPTURE_SHARED_CUDA;
capture_params.bDisableAutoModesetRecovery = nv_bool(true); capture_params.bDisableAutoModesetRecovery = nv_bool(true);
@@ -773,8 +747,7 @@ namespace cuda {
capture_params.eTrackingType = NVFBC_TRACKING_OUTPUT; capture_params.eTrackingType = NVFBC_TRACKING_OUTPUT;
capture_params.dwOutputId = output.dwId; capture_params.dwOutputId = output.dwId;
} } else {
else {
capture_params.eTrackingType = NVFBC_TRACKING_SCREEN; capture_params.eTrackingType = NVFBC_TRACKING_SCREEN;
width = status_params->screenSize.w; width = status_params->screenSize.w;
@@ -788,8 +761,7 @@ namespace cuda {
return 0; return 0;
} }
platf::capture_e platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, 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(); auto next_frame = std::chrono::steady_clock::now();
{ {
@@ -802,7 +774,7 @@ namespace cuda {
// Force display_t::capture to initialize handle_t::capture // Force display_t::capture to initialize handle_t::capture
cursor_visible = !*cursor; cursor_visible = !*cursor;
ctx_t ctx { handle.handle }; ctx_t ctx {handle.handle};
auto fg = util::fail_guard([&]() { auto fg = util::fail_guard([&]() {
handle.reset(); handle.reset();
}); });
@@ -849,8 +821,7 @@ namespace cuda {
} }
// Reinitialize the capture session. // Reinitialize the capture session.
platf::capture_e platf::capture_e reinit(bool cursor) {
reinit(bool cursor) {
if (handle.stop()) { if (handle.stop()) {
return platf::capture_e::error; return platf::capture_e::error;
} }
@@ -860,8 +831,7 @@ namespace cuda {
capture_params.bPushModel = nv_bool(false); capture_params.bPushModel = nv_bool(false);
capture_params.bWithCursor = nv_bool(true); capture_params.bWithCursor = nv_bool(true);
capture_params.bAllowDirectCapture = nv_bool(false); capture_params.bAllowDirectCapture = nv_bool(false);
} } else {
else {
capture_params.bPushModel = nv_bool(true); capture_params.bPushModel = nv_bool(true);
capture_params.bWithCursor = nv_bool(false); capture_params.bWithCursor = nv_bool(false);
capture_params.bAllowDirectCapture = nv_bool(true); capture_params.bAllowDirectCapture = nv_bool(true);
@@ -919,8 +889,7 @@ namespace cuda {
return platf::capture_e::ok; return platf::capture_e::ok;
} }
platf::capture_e platf::capture_e 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) {
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) { if (cursor != cursor_visible) {
auto status = reinit(cursor); auto status = reinit(cursor);
if (status != platf::capture_e::ok) { if (status != platf::capture_e::ok) {
@@ -960,13 +929,11 @@ namespace cuda {
return platf::capture_e::ok; return platf::capture_e::ok;
} }
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) {
make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) {
return ::cuda::make_avcodec_encode_device(width, height, true); return ::cuda::make_avcodec_encode_device(width, height, true);
} }
std::shared_ptr<platf::img_t> std::shared_ptr<platf::img_t> alloc_img() override {
alloc_img() override {
auto img = std::make_shared<cuda::img_t>(); auto img = std::make_shared<cuda::img_t>();
img->data = nullptr; img->data = nullptr;
@@ -985,8 +952,7 @@ namespace cuda {
return img; return img;
}; };
int int dummy_img(platf::img_t *) override {
dummy_img(platf::img_t *) override {
return 0; return 0;
} }
@@ -1001,8 +967,7 @@ namespace cuda {
} // namespace cuda } // namespace cuda
namespace platf { namespace platf {
std::shared_ptr<display_t> std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (hwdevice_type != mem_type_e::cuda) { if (hwdevice_type != mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv; BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv;
return nullptr; return nullptr;
@@ -1017,8 +982,7 @@ namespace platf {
return display; return display;
} }
std::vector<std::string> std::vector<std::string> nvfbc_display_names() {
nvfbc_display_names() {
if (cuda::init() || cuda::nvfbc::init()) { if (cuda::init() || cuda::nvfbc::init()) {
return {}; return {};
} }

View File

@@ -2,14 +2,17 @@
* @file src/platform/linux/cuda.cu * @file src/platform/linux/cuda.cu
* @brief CUDA implementation for Linux. * @brief CUDA implementation for Linux.
*/ */
// #include <algorithm> // standard includes
#include <helper_math.h>
#include <chrono> #include <chrono>
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string_view> #include <string_view>
// platform includes
#include <helper_math.h>
// local includes
#include "cuda.h" #include "cuda.h"
using namespace std::literals; using namespace std::literals;
@@ -18,16 +21,20 @@ using namespace std::literals;
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x) #define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
#define CU_CHECK(x, y) \ #define CU_CHECK(x, y) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return -1 if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return -1
#define CU_CHECK_VOID(x, y) \ #define CU_CHECK_VOID(x, y) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return; if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return;
#define CU_CHECK_PTR(x, y) \ #define CU_CHECK_PTR(x, y) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return nullptr; if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return nullptr;
#define CU_CHECK_OPT(x, y) \ #define CU_CHECK_OPT(x, y) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return std::nullopt; if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return std::nullopt;
#define CU_CHECK_IGNORE(x, y) \ #define CU_CHECK_IGNORE(x, y) \
check((x), SUNSHINE_STRINGVIEW(y ": ")) check((x), SUNSHINE_STRINGVIEW(y ": "))
@@ -42,8 +49,8 @@ using namespace std::literals;
* Not pretty and extremely error-prone, fix at earliest convenience. * Not pretty and extremely error-prone, fix at earliest convenience.
*/ */
namespace platf { namespace platf {
struct img_t: std::enable_shared_from_this<img_t> { struct img_t: std::enable_shared_from_this<img_t> {
public: public:
std::uint8_t *data {}; std::uint8_t *data {};
std::int32_t width {}; std::int32_t width {};
std::int32_t height {}; std::int32_t height {};
@@ -53,33 +60,34 @@ public:
std::optional<std::chrono::steady_clock::time_point> frame_timestamp; std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
virtual ~img_t() = default; virtual ~img_t() = default;
}; };
} // namespace platf } // namespace platf
// End special declarations // End special declarations
namespace cuda { namespace cuda {
struct alignas(16) cuda_color_t { struct alignas(16) cuda_color_t {
float4 color_vec_y; float4 color_vec_y;
float4 color_vec_u; float4 color_vec_u;
float4 color_vec_v; float4 color_vec_v;
float2 range_y; float2 range_y;
float2 range_uv; float2 range_uv;
}; };
static_assert(sizeof(video::color_t) == sizeof(cuda::cuda_color_t), "color matrix struct mismatch"); static_assert(sizeof(video::color_t) == sizeof(cuda::cuda_color_t), "color matrix struct mismatch");
auto constexpr INVALID_TEXTURE = std::numeric_limits<cudaTextureObject_t>::max(); auto constexpr INVALID_TEXTURE = std::numeric_limits<cudaTextureObject_t>::max();
template<class T> template<class T>
inline T div_align(T l, T r) { inline T div_align(T l, T r) {
return (l + r - 1) / r; return (l + r - 1) / r;
} }
void pass_error(const std::string_view &sv, const char *name, const char *description); void pass_error(const std::string_view &sv, const char *name, const char *description);
inline static int check(cudaError_t result, const std::string_view &sv) {
if(result) { inline static int check(cudaError_t result, const std::string_view &sv) {
if (result) {
auto name = cudaGetErrorName(result); auto name = cudaGetErrorName(result);
auto description = cudaGetErrorString(result); auto description = cudaGetErrorString(result);
@@ -88,48 +96,47 @@ inline static int check(cudaError_t result, const std::string_view &sv) {
} }
return 0; return 0;
} }
template<class T> template<class T>
ptr_t make_ptr() { ptr_t make_ptr() {
void *p; void *p;
CU_CHECK_PTR(cudaMalloc(&p, sizeof(T)), "Couldn't allocate color matrix"); CU_CHECK_PTR(cudaMalloc(&p, sizeof(T)), "Couldn't allocate color matrix");
ptr_t ptr { p }; ptr_t ptr {p};
return ptr; return ptr;
} }
void freeCudaPtr_t::operator()(void *ptr) { void freeCudaPtr_t::operator()(void *ptr) {
CU_CHECK_IGNORE(cudaFree(ptr), "Couldn't free cuda device pointer"); CU_CHECK_IGNORE(cudaFree(ptr), "Couldn't free cuda device pointer");
} }
void freeCudaStream_t::operator()(cudaStream_t ptr) { void freeCudaStream_t::operator()(cudaStream_t ptr) {
CU_CHECK_IGNORE(cudaStreamDestroy(ptr), "Couldn't free cuda stream"); CU_CHECK_IGNORE(cudaStreamDestroy(ptr), "Couldn't free cuda stream");
} }
stream_t make_stream(int flags) { stream_t make_stream(int flags) {
cudaStream_t stream; cudaStream_t stream;
if(!flags) { if (!flags) {
CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream"); CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream");
} } else {
else {
CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags"); CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags");
} }
return stream_t { stream }; return stream_t {stream};
} }
inline __device__ float3 bgra_to_rgb(uchar4 vec) { inline __device__ float3 bgra_to_rgb(uchar4 vec) {
return make_float3((float)vec.z, (float)vec.y, (float)vec.x); return make_float3((float) vec.z, (float) vec.y, (float) vec.x);
} }
inline __device__ float3 bgra_to_rgb(float4 vec) { inline __device__ float3 bgra_to_rgb(float4 vec) {
return make_float3(vec.z, vec.y, vec.x); return make_float3(vec.z, vec.y, vec.x);
} }
inline __device__ float2 calcUV(float3 pixel, const cuda_color_t *const color_matrix) { inline __device__ float2 calcUV(float3 pixel, const cuda_color_t *const color_matrix) {
float4 vec_u = color_matrix->color_vec_u; float4 vec_u = color_matrix->color_vec_u;
float4 vec_v = color_matrix->color_vec_v; float4 vec_v = color_matrix->color_vec_v;
@@ -140,24 +147,33 @@ inline __device__ float2 calcUV(float3 pixel, const cuda_color_t *const color_ma
v = v * color_matrix->range_uv.x + color_matrix->range_uv.y; v = v * color_matrix->range_uv.x + color_matrix->range_uv.y;
return make_float2(u, v); return make_float2(u, v);
} }
inline __device__ float calcY(float3 pixel, const cuda_color_t *const color_matrix) { inline __device__ float calcY(float3 pixel, const cuda_color_t *const color_matrix) {
float4 vec_y = color_matrix->color_vec_y; float4 vec_y = color_matrix->color_vec_y;
return (dot(pixel, make_float3(vec_y)) + vec_y.w) * color_matrix->range_y.x + color_matrix->range_y.y; return (dot(pixel, make_float3(vec_y)) + vec_y.w) * color_matrix->range_y.x + color_matrix->range_y.y;
} }
__global__ void RGBA_to_NV12(
cudaTextureObject_t srcImage, std::uint8_t *dstY, std::uint8_t *dstUV,
std::uint32_t dstPitchY, std::uint32_t dstPitchUV,
float scale, const viewport_t viewport, const cuda_color_t *const color_matrix) {
__global__ void RGBA_to_NV12(
cudaTextureObject_t srcImage,
std::uint8_t *dstY,
std::uint8_t *dstUV,
std::uint32_t dstPitchY,
std::uint32_t dstPitchUV,
float scale,
const viewport_t viewport,
const cuda_color_t *const color_matrix
) {
int idX = (threadIdx.x + blockDim.x * blockIdx.x) * 2; int idX = (threadIdx.x + blockDim.x * blockIdx.x) * 2;
int idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2; int idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2;
if(idX >= viewport.width) return; if (idX >= viewport.width) {
if(idY >= viewport.height) return; return;
}
if (idY >= viewport.height) {
return;
}
float x = idX * scale; float x = idX * scale;
float y = idY * scale; float y = idY * scale;
@@ -187,15 +203,15 @@ __global__ void RGBA_to_NV12(
dstY0[1] = calcY(rgb_rt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible dstY0[1] = calcY(rgb_rt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY1[0] = calcY(rgb_lb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible dstY1[0] = calcY(rgb_lb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY1[1] = calcY(rgb_rb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible dstY1[1] = calcY(rgb_rb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
} }
int tex_t::copy(std::uint8_t *src, int height, int pitch) { int tex_t::copy(std::uint8_t *src, int height, int pitch) {
CU_CHECK(cudaMemcpy2DToArray(array, 0, 0, src, pitch, pitch, height, cudaMemcpyDeviceToDevice), "Couldn't copy to cuda array from deviceptr"); CU_CHECK(cudaMemcpy2DToArray(array, 0, 0, src, pitch, pitch, height, cudaMemcpyDeviceToDevice), "Couldn't copy to cuda array from deviceptr");
return 0; return 0;
} }
std::optional<tex_t> tex_t::make(int height, int pitch) { std::optional<tex_t> tex_t::make(int height, int pitch) {
tex_t tex; tex_t tex;
auto format = cudaCreateChannelDesc<uchar4>(); auto format = cudaCreateChannelDesc<uchar4>();
@@ -220,46 +236,53 @@ 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"); CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.linear, &res, &desc, nullptr), "Couldn't create cuda texture that uses linear interpolation");
return tex; return tex;
} }
tex_t::tex_t() : array {}, texture { INVALID_TEXTURE, INVALID_TEXTURE } {} tex_t::tex_t():
tex_t::tex_t(tex_t &&other) : array { other.array }, texture { other.texture } { array {},
texture {INVALID_TEXTURE, INVALID_TEXTURE} {
}
tex_t::tex_t(tex_t &&other):
array {other.array},
texture {other.texture} {
other.array = 0; other.array = 0;
other.texture.point = INVALID_TEXTURE; other.texture.point = INVALID_TEXTURE;
other.texture.linear = INVALID_TEXTURE; other.texture.linear = INVALID_TEXTURE;
} }
tex_t &tex_t::operator=(tex_t &&other) { tex_t &tex_t::operator=(tex_t &&other) {
std::swap(array, other.array); std::swap(array, other.array);
std::swap(texture, other.texture); std::swap(texture, other.texture);
return *this; return *this;
} }
tex_t::~tex_t() { tex_t::~tex_t() {
if(texture.point != INVALID_TEXTURE) { if (texture.point != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.point), "Couldn't deallocate cuda texture that uses point interpolation"); CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.point), "Couldn't deallocate cuda texture that uses point interpolation");
texture.point = INVALID_TEXTURE; texture.point = INVALID_TEXTURE;
} }
if(texture.linear != INVALID_TEXTURE) { if (texture.linear != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.linear), "Couldn't deallocate cuda texture that uses linear interpolation"); CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.linear), "Couldn't deallocate cuda texture that uses linear interpolation");
texture.linear = INVALID_TEXTURE; texture.linear = INVALID_TEXTURE;
} }
if(array) { if (array) {
CU_CHECK_IGNORE(cudaFreeArray(array), "Couldn't deallocate cuda array"); CU_CHECK_IGNORE(cudaFreeArray(array), "Couldn't deallocate cuda array");
array = cudaArray_t {}; array = cudaArray_t {};
} }
} }
sws_t::sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix) sws_t::sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix):
: threadsPerBlock { threadsPerBlock }, color_matrix { std::move(color_matrix) } { threadsPerBlock {threadsPerBlock},
color_matrix {std::move(color_matrix)} {
// Ensure aspect ratio is maintained // Ensure aspect ratio is maintained
auto scalar = std::fminf(out_width / (float)in_width, out_height / (float)in_height); auto scalar = std::fminf(out_width / (float) in_width, out_height / (float) in_height);
auto out_width_f = in_width * scalar; auto out_width_f = in_width * scalar;
auto out_height_f = in_height * scalar; auto out_height_f = in_height * scalar;
@@ -274,45 +297,45 @@ sws_t::sws_t(int in_width, int in_height, int out_width, int out_height, int pit
viewport.offsetY = offsetY_f; viewport.offsetY = offsetY_f;
scale = 1.0f / scalar; scale = 1.0f / scalar;
} }
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, int pitch) { std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, int pitch) {
cudaDeviceProp props; cudaDeviceProp props;
int device; int device;
CU_CHECK_OPT(cudaGetDevice(&device), "Couldn't get cuda device"); CU_CHECK_OPT(cudaGetDevice(&device), "Couldn't get cuda device");
CU_CHECK_OPT(cudaGetDeviceProperties(&props, device), "Couldn't get cuda device properties"); CU_CHECK_OPT(cudaGetDeviceProperties(&props, device), "Couldn't get cuda device properties");
auto ptr = make_ptr<cuda_color_t>(); auto ptr = make_ptr<cuda_color_t>();
if(!ptr) { if (!ptr) {
return std::nullopt; return std::nullopt;
} }
return std::make_optional<sws_t>(in_width, in_height, out_width, out_height, pitch, props.maxThreadsPerMultiProcessor / props.maxBlocksPerMultiProcessor, std::move(ptr)); return std::make_optional<sws_t>(in_width, in_height, out_width, out_height, pitch, props.maxThreadsPerMultiProcessor / props.maxBlocksPerMultiProcessor, std::move(ptr));
} }
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream) { int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream) {
return convert(Y, UV, pitchY, pitchUV, texture, stream, viewport); return convert(Y, UV, pitchY, pitchUV, texture, stream, viewport);
} }
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) { int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) {
int threadsX = viewport.width / 2; int threadsX = viewport.width / 2;
int threadsY = viewport.height / 2; int threadsY = viewport.height / 2;
dim3 block(threadsPerBlock); dim3 block(threadsPerBlock);
dim3 grid(div_align(threadsX, threadsPerBlock), threadsY); dim3 grid(div_align(threadsX, threadsPerBlock), threadsY);
RGBA_to_NV12<<<grid, block, 0, stream>>>(texture, Y, UV, pitchY, pitchUV, scale, viewport, (cuda_color_t *)color_matrix.get()); RGBA_to_NV12<<<grid, block, 0, stream>>>(texture, Y, UV, pitchY, pitchUV, scale, viewport, (cuda_color_t *) color_matrix.get());
return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_NV12 failed"); return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_NV12 failed");
} }
void sws_t::apply_colorspace(const video::sunshine_colorspace_t& colorspace) { void sws_t::apply_colorspace(const video::sunshine_colorspace_t &colorspace) {
auto color_p = video::color_vectors_from_colorspace(colorspace); auto color_p = video::color_vectors_from_colorspace(colorspace);
CU_CHECK_IGNORE(cudaMemcpy(color_matrix.get(), color_p, sizeof(video::color_t), cudaMemcpyHostToDevice), "Couldn't copy color matrix to cuda"); CU_CHECK_IGNORE(cudaMemcpy(color_matrix.get(), color_p, sizeof(video::color_t), cudaMemcpyHostToDevice), "Couldn't copy color matrix to cuda");
} }
int sws_t::load_ram(platf::img_t &img, cudaArray_t array) { int sws_t::load_ram(platf::img_t &img, cudaArray_t array) {
return CU_CHECK_IGNORE(cudaMemcpy2DToArray(array, 0, 0, img.data, img.row_pitch, img.width * img.pixel_pitch, img.height, cudaMemcpyHostToDevice), "Couldn't copy to cuda array"); return CU_CHECK_IGNORE(cudaMemcpy2DToArray(array, 0, 0, img.data, img.row_pitch, img.width * img.pixel_pitch, img.height, cudaMemcpyHostToDevice), "Couldn't copy to cuda array");
} }
} // namespace cuda } // namespace cuda

View File

@@ -5,15 +5,16 @@
#pragma once #pragma once
#if defined(SUNSHINE_BUILD_CUDA) #if defined(SUNSHINE_BUILD_CUDA)
// standard includes
#include "src/video_colorspace.h"
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
// local includes
#include "src/video_colorspace.h"
namespace platf { namespace platf {
class avcodec_encode_device_t; class avcodec_encode_device_t;
class img_t; class img_t;
@@ -22,11 +23,10 @@ namespace platf {
namespace cuda { namespace cuda {
namespace nvfbc { namespace nvfbc {
std::vector<std::string> std::vector<std::string> display_names();
display_names();
} }
std::unique_ptr<platf::avcodec_encode_device_t>
make_avcodec_encode_device(int width, int height, bool vram); std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram);
/** /**
* @brief Create a GL->CUDA encoding device for consuming captured dmabufs. * @brief Create a GL->CUDA encoding device for consuming captured dmabufs.
@@ -36,11 +36,9 @@ namespace cuda {
* @param offset_y Offset of content in captured frame. * @param offset_y Offset of content in captured frame.
* @return FFmpeg encoding device context. * @return FFmpeg encoding device context.
*/ */
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y);
make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y);
int int init();
init();
} // namespace cuda } // namespace cuda
typedef struct cudaArray *cudaArray_t; typedef struct cudaArray *cudaArray_t;
@@ -57,21 +55,18 @@ namespace cuda {
class freeCudaPtr_t { class freeCudaPtr_t {
public: public:
void void operator()(void *ptr);
operator()(void *ptr);
}; };
class freeCudaStream_t { class freeCudaStream_t {
public: public:
void void operator()(cudaStream_t ptr);
operator()(cudaStream_t ptr);
}; };
using ptr_t = std::unique_ptr<void, freeCudaPtr_t>; using ptr_t = std::unique_ptr<void, freeCudaPtr_t>;
using stream_t = std::unique_ptr<CUstream_st, freeCudaStream_t>; using stream_t = std::unique_ptr<CUstream_st, freeCudaStream_t>;
stream_t stream_t make_stream(int flags = 0);
make_stream(int flags = 0);
struct viewport_t { struct viewport_t {
int width, height; int width, height;
@@ -80,19 +75,16 @@ namespace cuda {
class tex_t { class tex_t {
public: public:
static std::optional<tex_t> static std::optional<tex_t> make(int height, int pitch);
make(int height, int pitch);
tex_t(); tex_t();
tex_t(tex_t &&); tex_t(tex_t &&);
tex_t & tex_t &operator=(tex_t &&other);
operator=(tex_t &&other);
~tex_t(); ~tex_t();
int int copy(std::uint8_t *src, int height, int pitch);
copy(std::uint8_t *src, int height, int pitch);
cudaArray_t array; cudaArray_t array;
@@ -113,20 +105,15 @@ namespace cuda {
* *
* pitch -- The size of a single row of pixels in bytes * pitch -- The size of a single row of pixels in bytes
*/ */
static std::optional<sws_t> static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, int pitch);
make(int in_width, int in_height, int out_width, int out_height, int pitch);
// Converts loaded image into a CUDevicePtr // Converts loaded image into a CUDevicePtr
int int convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream);
convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream); int convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport);
int
convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport);
void void apply_colorspace(const video::sunshine_colorspace_t &colorspace);
apply_colorspace(const video::sunshine_colorspace_t &colorspace);
int int load_ram(platf::img_t &img, cudaArray_t array);
load_ram(platf::img_t &img, cudaArray_t array);
ptr_t color_matrix; ptr_t color_matrix;

View File

@@ -2,13 +2,15 @@
* @file src/platform/linux/graphics.cpp * @file src/platform/linux/graphics.cpp
* @brief Definitions for graphics related functions. * @brief Definitions for graphics related functions.
*/ */
// standard includes
#include <fcntl.h>
// local includes
#include "graphics.h" #include "graphics.h"
#include "src/file_handler.h" #include "src/file_handler.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/video.h" #include "src/video.h"
#include <fcntl.h>
extern "C" { extern "C" {
#include <libavutil/pixdesc.h> #include <libavutil/pixdesc.h>
} }
@@ -17,8 +19,7 @@ extern "C" {
// There aren't that many DRM_FORMAT I need to use, so define them here // There aren't that many DRM_FORMAT I need to use, so define them here
// //
// They aren't likely to change any time soon. // They aren't likely to change any time soon.
#define fourcc_code(a, b, c, d) ((std::uint32_t)(a) | ((std::uint32_t)(b) << 8) | \ #define fourcc_code(a, b, c, d) ((std::uint32_t)(a) | ((std::uint32_t)(b) << 8) | ((std::uint32_t)(c) << 16) | ((std::uint32_t)(d) << 24))
((std::uint32_t)(c) << 16) | ((std::uint32_t)(d) << 24))
#define fourcc_mod_code(vendor, val) ((((uint64_t) vendor) << 56) | ((val) & 0x00ffffffffffffffULL)) #define fourcc_mod_code(vendor, val) ((((uint64_t) vendor) << 56) | ((val) & 0x00ffffffffffffffULL))
#define DRM_FORMAT_MOD_INVALID fourcc_mod_code(0, ((1ULL << 56) - 1)) #define DRM_FORMAT_MOD_INVALID fourcc_mod_code(0, ((1ULL << 56) - 1))
@@ -27,11 +28,11 @@ extern "C" {
#endif #endif
using namespace std::literals; using namespace std::literals;
namespace gl { namespace gl {
GladGLContext ctx; GladGLContext ctx;
void void drain_errors(const std::string_view &prefix) {
drain_errors(const std::string_view &prefix) {
GLenum err; GLenum err;
while ((err = ctx.GetError()) != GL_NO_ERROR) { while ((err = ctx.GetError()) != GL_NO_ERROR) {
BOOST_LOG(error) << "GL: "sv << prefix << ": ["sv << util::hex(err).to_string_view() << ']'; BOOST_LOG(error) << "GL: "sv << prefix << ": ["sv << util::hex(err).to_string_view() << ']';
@@ -44,13 +45,12 @@ namespace gl {
} }
} }
tex_t tex_t tex_t::make(std::size_t count) {
tex_t::make(std::size_t count) { tex_t textures {count};
tex_t textures { count };
ctx.GenTextures(textures.size(), textures.begin()); ctx.GenTextures(textures.size(), textures.begin());
float color[] = { 0.0f, 0.0f, 0.0f, 1.0f }; float color[] = {0.0f, 0.0f, 0.0f, 1.0f};
for (auto tex : textures) { for (auto tex : textures) {
gl::ctx.BindTexture(GL_TEXTURE_2D, tex); gl::ctx.BindTexture(GL_TEXTURE_2D, tex);
@@ -70,25 +70,22 @@ namespace gl {
} }
} }
frame_buf_t frame_buf_t frame_buf_t::make(std::size_t count) {
frame_buf_t::make(std::size_t count) { frame_buf_t frame_buf {count};
frame_buf_t frame_buf { count };
ctx.GenFramebuffers(frame_buf.size(), frame_buf.begin()); ctx.GenFramebuffers(frame_buf.size(), frame_buf.begin());
return frame_buf; return frame_buf;
} }
void void frame_buf_t::copy(int id, int texture, int offset_x, int offset_y, int width, int height) {
frame_buf_t::copy(int id, int texture, int offset_x, int offset_y, int width, int height) {
gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, (*this)[id]); gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, (*this)[id]);
gl::ctx.ReadBuffer(GL_COLOR_ATTACHMENT0 + id); gl::ctx.ReadBuffer(GL_COLOR_ATTACHMENT0 + id);
gl::ctx.BindTexture(GL_TEXTURE_2D, texture); gl::ctx.BindTexture(GL_TEXTURE_2D, texture);
gl::ctx.CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, offset_x, offset_y, width, height); gl::ctx.CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, offset_x, offset_y, width, height);
} }
std::string std::string shader_t::err_str() {
shader_t::err_str() {
int length; int length;
ctx.GetShaderiv(handle(), GL_INFO_LOG_LENGTH, &length); ctx.GetShaderiv(handle(), GL_INFO_LOG_LENGTH, &length);
@@ -102,8 +99,7 @@ namespace gl {
return string; return string;
} }
util::Either<shader_t, std::string> util::Either<shader_t, std::string> shader_t::compile(const std::string_view &source, GLenum type) {
shader_t::compile(const std::string_view &source, GLenum type) {
shader_t shader; shader_t shader;
auto data = source.data(); auto data = source.data();
@@ -123,13 +119,11 @@ namespace gl {
return shader; return shader;
} }
GLuint GLuint shader_t::handle() const {
shader_t::handle() const {
return _shader.el; return _shader.el;
} }
buffer_t buffer_t buffer_t::make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data) {
buffer_t::make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data) {
buffer_t buffer; buffer_t buffer;
buffer._block = block; buffer._block = block;
buffer._size = data.size(); buffer._size = data.size();
@@ -142,25 +136,21 @@ namespace gl {
return buffer; return buffer;
} }
GLuint GLuint buffer_t::handle() const {
buffer_t::handle() const {
return _buffer.el; return _buffer.el;
} }
const char * const char *buffer_t::block() const {
buffer_t::block() const {
return _block; return _block;
} }
void void buffer_t::update(const std::string_view &view, std::size_t offset) {
buffer_t::update(const std::string_view &view, std::size_t offset) {
ctx.BindBuffer(GL_UNIFORM_BUFFER, handle()); ctx.BindBuffer(GL_UNIFORM_BUFFER, handle());
ctx.BufferSubData(GL_UNIFORM_BUFFER, offset, view.size(), (const void *) view.data()); ctx.BufferSubData(GL_UNIFORM_BUFFER, offset, view.size(), (const void *) view.data());
} }
void void buffer_t::update(std::string_view *members, std::size_t count, std::size_t offset) {
buffer_t::update(std::string_view *members, std::size_t count, std::size_t offset) { util::buffer_t<std::uint8_t> buffer {_size};
util::buffer_t<std::uint8_t> buffer { _size };
for (int x = 0; x < count; ++x) { for (int x = 0; x < count; ++x) {
auto val = members[x]; auto val = members[x];
@@ -171,8 +161,7 @@ namespace gl {
update(util::view(buffer.begin(), buffer.end()), offset); update(util::view(buffer.begin(), buffer.end()), offset);
} }
std::string std::string program_t::err_str() {
program_t::err_str() {
int length; int length;
ctx.GetProgramiv(handle(), GL_INFO_LOG_LENGTH, &length); ctx.GetProgramiv(handle(), GL_INFO_LOG_LENGTH, &length);
@@ -186,8 +175,7 @@ namespace gl {
return string; return string;
} }
util::Either<program_t, std::string> util::Either<program_t, std::string> program_t::link(const shader_t &vert, const shader_t &frag) {
program_t::link(const shader_t &vert, const shader_t &frag) {
program_t program; program_t program;
program._program.el = ctx.CreateProgram(); program._program.el = ctx.CreateProgram();
@@ -214,16 +202,14 @@ namespace gl {
return program; return program;
} }
void void program_t::bind(const buffer_t &buffer) {
program_t::bind(const buffer_t &buffer) {
ctx.UseProgram(handle()); ctx.UseProgram(handle());
auto i = ctx.GetUniformBlockIndex(handle(), buffer.block()); auto i = ctx.GetUniformBlockIndex(handle(), buffer.block());
ctx.BindBufferBase(GL_UNIFORM_BUFFER, i, buffer.handle()); ctx.BindBufferBase(GL_UNIFORM_BUFFER, i, buffer.handle());
} }
std::optional<buffer_t> std::optional<buffer_t> program_t::uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count) {
program_t::uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count) {
auto i = ctx.GetUniformBlockIndex(handle(), block); auto i = ctx.GetUniformBlockIndex(handle(), block);
if (i == GL_INVALID_INDEX) { if (i == GL_INVALID_INDEX) {
BOOST_LOG(error) << "Couldn't find index of ["sv << block << ']'; BOOST_LOG(error) << "Couldn't find index of ["sv << block << ']';
@@ -235,7 +221,7 @@ namespace gl {
bool error_flag = false; bool error_flag = false;
util::buffer_t<GLint> offsets { count }; util::buffer_t<GLint> offsets {count};
auto indices = (std::uint32_t *) alloca(count * sizeof(std::uint32_t)); auto indices = (std::uint32_t *) alloca(count * sizeof(std::uint32_t));
auto names = (const char **) alloca(count * sizeof(const char *)); auto names = (const char **) alloca(count * sizeof(const char *));
auto names_p = names; auto names_p = names;
@@ -260,7 +246,7 @@ namespace gl {
} }
ctx.GetActiveUniformsiv(handle(), count, indices, GL_UNIFORM_OFFSET, offsets.begin()); ctx.GetActiveUniformsiv(handle(), count, indices, GL_UNIFORM_OFFSET, offsets.begin());
util::buffer_t<std::uint8_t> buffer { (std::size_t) size }; util::buffer_t<std::uint8_t> buffer {(std::size_t) size};
for (int x = 0; x < count; ++x) { for (int x = 0; x < count; ++x) {
auto val = std::get<1>(members[x]); auto val = std::get<1>(members[x]);
@@ -268,11 +254,10 @@ namespace gl {
std::copy_n((const std::uint8_t *) val.data(), val.size(), &buffer[offsets[x]]); std::copy_n((const std::uint8_t *) val.data(), val.size(), &buffer[offsets[x]]);
} }
return buffer_t::make(std::move(offsets), block, std::string_view { (char *) buffer.begin(), buffer.size() }); return buffer_t::make(std::move(offsets), block, std::string_view {(char *) buffer.begin(), buffer.size()});
} }
GLuint GLuint program_t::handle() const {
program_t::handle() const {
return _program.el; return _program.el;
} }
@@ -282,23 +267,24 @@ namespace gbm {
device_destroy_fn device_destroy; device_destroy_fn device_destroy;
create_device_fn create_device; create_device_fn create_device;
int int init() {
init() { static void *handle {nullptr};
static void *handle { nullptr };
static bool funcs_loaded = false; static bool funcs_loaded = false;
if (funcs_loaded) return 0; if (funcs_loaded) {
return 0;
}
if (!handle) { if (!handle) {
handle = dyn::handle({ "libgbm.so.1", "libgbm.so" }); handle = dyn::handle({"libgbm.so.1", "libgbm.so"});
if (!handle) { if (!handle) {
return -1; return -1;
} }
} }
std::vector<std::tuple<GLADapiproc *, const char *>> funcs { std::vector<std::tuple<GLADapiproc *, const char *>> funcs {
{ (GLADapiproc *) &device_destroy, "gbm_device_destroy" }, {(GLADapiproc *) &device_destroy, "gbm_device_destroy"},
{ (GLADapiproc *) &create_device, "gbm_create_device" }, {(GLADapiproc *) &create_device, "gbm_create_device"},
}; };
if (dyn::load(handle, funcs)) { if (dyn::load(handle, funcs)) {
@@ -334,16 +320,14 @@ namespace egl {
constexpr auto EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT = 0x3449; constexpr auto EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT = 0x3449;
constexpr auto EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT = 0x344A; constexpr auto EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT = 0x344A;
bool bool fail() {
fail() {
return eglGetError() != EGL_SUCCESS; return eglGetError() != EGL_SUCCESS;
} }
/** /**
* @memberof egl::display_t * @memberof egl::display_t
*/ */
display_t display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display) {
make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display) {
constexpr auto EGL_PLATFORM_GBM_MESA = 0x31D7; constexpr auto EGL_PLATFORM_GBM_MESA = 0x31D7;
constexpr auto EGL_PLATFORM_WAYLAND_KHR = 0x31D8; constexpr auto EGL_PLATFORM_WAYLAND_KHR = 0x31D8;
constexpr auto EGL_PLATFORM_X11_KHR = 0x31D5; constexpr auto EGL_PLATFORM_X11_KHR = 0x31D5;
@@ -408,10 +392,11 @@ namespace egl {
return display; return display;
} }
std::optional<ctx_t> std::optional<ctx_t> make_ctx(display_t::pointer display) {
make_ctx(display_t::pointer display) {
constexpr int conf_attr[] { constexpr int conf_attr[] {
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE EGL_RENDERABLE_TYPE,
EGL_OPENGL_BIT,
EGL_NONE
}; };
int count; int count;
@@ -427,10 +412,12 @@ namespace egl {
} }
constexpr int attr[] { constexpr int attr[] {
EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE EGL_CONTEXT_CLIENT_VERSION,
3,
EGL_NONE
}; };
ctx_t ctx { display, eglCreateContext(display, conf, EGL_NO_CONTEXT, attr) }; ctx_t ctx {display, eglCreateContext(display, conf, EGL_NO_CONTEXT, attr)};
if (fail()) { if (fail()) {
BOOST_LOG(error) << "Couldn't create EGL context: ["sv << util::hex(eglGetError()).to_string_view() << ']'; BOOST_LOG(error) << "Couldn't create EGL context: ["sv << util::hex(eglGetError()).to_string_view() << ']';
return std::nullopt; return std::nullopt;
@@ -465,8 +452,7 @@ namespace egl {
EGLAttrib hi; EGLAttrib hi;
}; };
inline plane_attr_t inline plane_attr_t get_plane(std::uint32_t plane_indice) {
get_plane(std::uint32_t plane_indice) {
switch (plane_indice) { switch (plane_indice) {
case 0: case 0:
return { return {
@@ -511,8 +497,7 @@ namespace egl {
* @param surface The surface descriptor. * @param surface The surface descriptor.
* @return Vector of EGL attributes. * @return Vector of EGL attributes.
*/ */
std::vector<EGLAttrib> std::vector<EGLAttrib> surface_descriptor_to_egl_attribs(const surface_descriptor_t &surface) {
surface_descriptor_to_egl_attribs(const surface_descriptor_t &surface) {
std::vector<EGLAttrib> attribs; std::vector<EGLAttrib> attribs;
attribs.emplace_back(EGL_WIDTH); attribs.emplace_back(EGL_WIDTH);
@@ -549,8 +534,7 @@ namespace egl {
return attribs; return attribs;
} }
std::optional<rgb_t> std::optional<rgb_t> import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) {
import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) {
auto attribs = surface_descriptor_to_egl_attribs(xrgb); auto attribs = surface_descriptor_to_egl_attribs(xrgb);
rgb_t rgb { rgb_t rgb {
@@ -580,8 +564,7 @@ namespace egl {
* @param img The image to use for texture sizing. * @param img The image to use for texture sizing.
* @return The new RGB texture. * @return The new RGB texture.
*/ */
rgb_t rgb_t create_blank(platf::img_t &img) {
create_blank(platf::img_t &img) {
rgb_t rgb { rgb_t rgb {
EGL_NO_DISPLAY, EGL_NO_DISPLAY,
EGL_NO_IMAGE, EGL_NO_IMAGE,
@@ -597,7 +580,7 @@ namespace egl {
GLenum attachment = GL_COLOR_ATTACHMENT0; GLenum attachment = GL_COLOR_ATTACHMENT0;
gl::ctx.DrawBuffers(1, &attachment); gl::ctx.DrawBuffers(1, &attachment);
const GLuint rgb_black[] = { 0, 0, 0, 0 }; const GLuint rgb_black[] = {0, 0, 0, 0};
gl::ctx.ClearBufferuiv(GL_COLOR, 0, rgb_black); gl::ctx.ClearBufferuiv(GL_COLOR, 0, rgb_black);
gl_drain_errors; gl_drain_errors;
@@ -605,8 +588,7 @@ namespace egl {
return rgb; return rgb;
} }
std::optional<nv12_t> std::optional<nv12_t> import_target(display_t::pointer egl_display, std::array<file_t, nv12_img_t::num_fds> &&fds, const surface_descriptor_t &y, const surface_descriptor_t &uv) {
import_target(display_t::pointer egl_display, std::array<file_t, nv12_img_t::num_fds> &&fds, const surface_descriptor_t &y, const surface_descriptor_t &uv) {
auto y_attribs = surface_descriptor_to_egl_attribs(y); auto y_attribs = surface_descriptor_to_egl_attribs(y);
auto uv_attribs = surface_descriptor_to_egl_attribs(uv); auto uv_attribs = surface_descriptor_to_egl_attribs(uv);
@@ -642,8 +624,8 @@ namespace egl {
gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]); gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]);
gl::ctx.DrawBuffers(1, &attachments[x]); gl::ctx.DrawBuffers(1, &attachments[x]);
const float y_black[] = { 0.0f, 0.0f, 0.0f, 0.0f }; const float y_black[] = {0.0f, 0.0f, 0.0f, 0.0f};
const float uv_black[] = { 0.5f, 0.5f, 0.5f, 0.5f }; const float uv_black[] = {0.5f, 0.5f, 0.5f, 0.5f};
gl::ctx.ClearBufferfv(GL_COLOR, 0, x == 0 ? y_black : uv_black); gl::ctx.ClearBufferfv(GL_COLOR, 0, x == 0 ? y_black : uv_black);
} }
@@ -661,8 +643,7 @@ namespace egl {
* @param format Format of the target frame. * @param format Format of the target frame.
* @return The new RGB texture. * @return The new RGB texture.
*/ */
std::optional<nv12_t> std::optional<nv12_t> create_target(int width, int height, AVPixelFormat format) {
create_target(int width, int height, AVPixelFormat format) {
nv12_t nv12 { nv12_t nv12 {
EGL_NO_DISPLAY, EGL_NO_DISPLAY,
EGL_NO_IMAGE, EGL_NO_IMAGE,
@@ -679,12 +660,10 @@ namespace egl {
if (fmt_desc->comp[0].depth <= 8) { if (fmt_desc->comp[0].depth <= 8) {
y_format = GL_R8; y_format = GL_R8;
uv_format = GL_RG8; uv_format = GL_RG8;
} } else if (fmt_desc->comp[0].depth <= 16) {
else if (fmt_desc->comp[0].depth <= 16) {
y_format = GL_R16; y_format = GL_R16;
uv_format = GL_RG16; uv_format = GL_RG16;
} } else {
else {
BOOST_LOG(error) << "Unsupported target pixel format: "sv << format; BOOST_LOG(error) << "Unsupported target pixel format: "sv << format;
return std::nullopt; return std::nullopt;
} }
@@ -693,8 +672,7 @@ namespace egl {
gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, y_format, width, height); gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, y_format, width, height);
gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[1]); gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[1]);
gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, uv_format, gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, uv_format, width >> fmt_desc->log2_chroma_w, height >> fmt_desc->log2_chroma_h);
width >> fmt_desc->log2_chroma_w, height >> fmt_desc->log2_chroma_h);
nv12->buf.bind(std::begin(nv12->tex), std::end(nv12->tex)); nv12->buf.bind(std::begin(nv12->tex), std::end(nv12->tex));
@@ -707,8 +685,8 @@ namespace egl {
gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]); gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]);
gl::ctx.DrawBuffers(1, &attachments[x]); gl::ctx.DrawBuffers(1, &attachments[x]);
const float y_black[] = { 0.0f, 0.0f, 0.0f, 0.0f }; const float y_black[] = {0.0f, 0.0f, 0.0f, 0.0f};
const float uv_black[] = { 0.5f, 0.5f, 0.5f, 0.5f }; const float uv_black[] = {0.5f, 0.5f, 0.5f, 0.5f};
gl::ctx.ClearBufferfv(GL_COLOR, 0, x == 0 ? y_black : uv_black); gl::ctx.ClearBufferfv(GL_COLOR, 0, x == 0 ? y_black : uv_black);
} }
@@ -719,8 +697,7 @@ namespace egl {
return nv12; return nv12;
} }
void void sws_t::apply_colorspace(const video::sunshine_colorspace_t &colorspace) {
sws_t::apply_colorspace(const video::sunshine_colorspace_t &colorspace) {
auto color_p = video::color_vectors_from_colorspace(colorspace); auto color_p = video::color_vectors_from_colorspace(colorspace);
std::string_view members[] { std::string_view members[] {
@@ -737,8 +714,7 @@ namespace egl {
program[1].bind(color_matrix); program[1].bind(color_matrix);
} }
std::optional<sws_t> std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex) {
sws_t::make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex) {
sws_t sws; sws_t sws;
sws.serial = std::numeric_limits<std::uint64_t>::max(); sws.serial = std::numeric_limits<std::uint64_t>::max();
@@ -866,8 +842,7 @@ namespace egl {
return sws; return sws;
} }
int int sws_t::blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height) {
sws_t::blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height) {
auto f = [&]() { auto f = [&]() {
std::swap(offsetX, this->offsetX); std::swap(offsetX, this->offsetX);
std::swap(offsetY, this->offsetY); std::swap(offsetY, this->offsetY);
@@ -881,8 +856,7 @@ namespace egl {
return convert(fb); return convert(fb);
} }
std::optional<sws_t> std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format) {
sws_t::make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format) {
GLint gl_format; GLint gl_format;
// Decide the bit depth format of the backing texture based the target frame format // Decide the bit depth format of the backing texture based the target frame format
@@ -916,16 +890,14 @@ namespace egl {
return make(in_width, in_height, out_width, out_height, std::move(tex)); return make(in_width, in_height, out_width, out_height, std::move(tex));
} }
void void sws_t::load_ram(platf::img_t &img) {
sws_t::load_ram(platf::img_t &img) {
loaded_texture = tex[0]; loaded_texture = tex[0];
gl::ctx.BindTexture(GL_TEXTURE_2D, loaded_texture); gl::ctx.BindTexture(GL_TEXTURE_2D, loaded_texture);
gl::ctx.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, img.width, img.height, GL_BGRA, GL_UNSIGNED_BYTE, img.data); gl::ctx.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, img.width, img.height, GL_BGRA, GL_UNSIGNED_BYTE, img.data);
} }
void void sws_t::load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture) {
sws_t::load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture) {
// When only a sub-part of the image must be encoded... // When only a sub-part of the image must be encoded...
const bool copy = offset_x || offset_y || img.sd.width != in_width || img.sd.height != in_height; const bool copy = offset_x || offset_y || img.sd.width != in_width || img.sd.height != in_height;
if (copy) { if (copy) {
@@ -934,8 +906,7 @@ namespace egl {
loaded_texture = tex[0]; loaded_texture = tex[0];
framebuf.copy(0, loaded_texture, offset_x, offset_y, in_width, in_height); framebuf.copy(0, loaded_texture, offset_x, offset_y, in_width, in_height);
} } else {
else {
loaded_texture = texture; loaded_texture = texture;
} }
@@ -985,8 +956,7 @@ namespace egl {
} }
} }
int int sws_t::convert(gl::frame_buf_t &fb) {
sws_t::convert(gl::frame_buf_t &fb) {
gl::ctx.BindTexture(GL_TEXTURE_2D, loaded_texture); gl::ctx.BindTexture(GL_TEXTURE_2D, loaded_texture);
GLenum attachments[] { GLenum attachments[] {
@@ -1019,7 +989,6 @@ namespace egl {
} }
} // namespace egl } // namespace egl
void void free_frame(AVFrame *frame) {
free_frame(AVFrame *frame) {
av_frame_free(&frame); av_frame_free(&frame);
} }

View File

@@ -4,12 +4,15 @@
*/ */
#pragma once #pragma once
// standard includes
#include <optional> #include <optional>
#include <string_view> #include <string_view>
// lib includes
#include <glad/egl.h> #include <glad/egl.h>
#include <glad/gl.h> #include <glad/gl.h>
// local includes
#include "misc.h" #include "misc.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
@@ -21,35 +24,30 @@
#define gl_drain_errors_helper(x) gl::drain_errors(x) #define gl_drain_errors_helper(x) gl::drain_errors(x)
#define gl_drain_errors gl_drain_errors_helper(__FILE__ ":" SUNSHINE_STRINGIFY(__LINE__)) #define gl_drain_errors gl_drain_errors_helper(__FILE__ ":" SUNSHINE_STRINGIFY(__LINE__))
extern "C" int extern "C" int close(int __fd);
close(int __fd);
// X11 Display // X11 Display
extern "C" struct _XDisplay; extern "C" struct _XDisplay;
struct AVFrame; struct AVFrame;
void void free_frame(AVFrame *frame);
free_frame(AVFrame *frame);
using frame_t = util::safe_ptr<AVFrame, free_frame>; using frame_t = util::safe_ptr<AVFrame, free_frame>;
namespace gl { namespace gl {
extern GladGLContext ctx; extern GladGLContext ctx;
void void drain_errors(const std::string_view &prefix);
drain_errors(const std::string_view &prefix);
class tex_t: public util::buffer_t<GLuint> { class tex_t: public util::buffer_t<GLuint> {
using util::buffer_t<GLuint>::buffer_t; using util::buffer_t<GLuint>::buffer_t;
public: public:
tex_t(tex_t &&) = default; tex_t(tex_t &&) = default;
tex_t & tex_t &operator=(tex_t &&) = default;
operator=(tex_t &&) = default;
~tex_t(); ~tex_t();
static tex_t static tex_t make(std::size_t count);
make(std::size_t count);
}; };
class frame_buf_t: public util::buffer_t<GLuint> { class frame_buf_t: public util::buffer_t<GLuint> {
@@ -57,16 +55,13 @@ namespace gl {
public: public:
frame_buf_t(frame_buf_t &&) = default; frame_buf_t(frame_buf_t &&) = default;
frame_buf_t & frame_buf_t &operator=(frame_buf_t &&) = default;
operator=(frame_buf_t &&) = default;
~frame_buf_t(); ~frame_buf_t();
static frame_buf_t static frame_buf_t make(std::size_t count);
make(std::size_t count);
inline void inline void bind(std::nullptr_t, std::nullptr_t) {
bind(std::nullptr_t, std::nullptr_t) {
int x = 0; int x = 0;
for (auto fb : (*this)) { for (auto fb : (*this)) {
ctx.BindFramebuffer(GL_FRAMEBUFFER, fb); ctx.BindFramebuffer(GL_FRAMEBUFFER, fb);
@@ -77,9 +72,8 @@ namespace gl {
return; return;
} }
template <class It> template<class It>
void void bind(It it_begin, It it_end) {
bind(It it_begin, It it_end) {
using namespace std::literals; using namespace std::literals;
if (std::distance(it_begin, it_end) > size()) { if (std::distance(it_begin, it_end) > size()) {
BOOST_LOG(warning) << "To many elements to bind"sv; BOOST_LOG(warning) << "To many elements to bind"sv;
@@ -100,8 +94,7 @@ namespace gl {
/** /**
* Copies a part of the framebuffer to texture * Copies a part of the framebuffer to texture
*/ */
void void copy(int id, int texture, int offset_x, int offset_y, int width, int height);
copy(int id, int texture, int offset_x, int offset_y, int width, int height);
}; };
class shader_t { class shader_t {
@@ -112,14 +105,11 @@ namespace gl {
}); });
public: public:
std::string std::string err_str();
err_str();
static util::Either<shader_t, std::string> static util::Either<shader_t, std::string> compile(const std::string_view &source, GLenum type);
compile(const std::string_view &source, GLenum type);
GLuint GLuint handle() const;
handle() const;
private: private:
shader_internal_t _shader; shader_internal_t _shader;
@@ -133,19 +123,14 @@ namespace gl {
}); });
public: public:
static buffer_t static buffer_t make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data);
make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data);
GLuint GLuint handle() const;
handle() const;
const char * const char *block() const;
block() const;
void void update(const std::string_view &view, std::size_t offset = 0);
update(const std::string_view &view, std::size_t offset = 0); void update(std::string_view *members, std::size_t count, std::size_t offset = 0);
void
update(std::string_view *members, std::size_t count, std::size_t offset = 0);
private: private:
const char *_block; const char *_block;
@@ -165,20 +150,15 @@ namespace gl {
}); });
public: public:
std::string std::string err_str();
err_str();
static util::Either<program_t, std::string> static util::Either<program_t, std::string> link(const shader_t &vert, const shader_t &frag);
link(const shader_t &vert, const shader_t &frag);
void void bind(const buffer_t &buffer);
bind(const buffer_t &buffer);
std::optional<buffer_t> std::optional<buffer_t> uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count);
uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count);
GLuint GLuint handle() const;
handle() const;
private: private:
program_internal_t _program; program_internal_t _program;
@@ -195,8 +175,7 @@ namespace gbm {
using gbm_t = util::dyn_safe_ptr<device, &device_destroy>; using gbm_t = util::dyn_safe_ptr<device, &device_destroy>;
int int init();
init();
} // namespace gbm } // namespace gbm
@@ -258,24 +237,23 @@ namespace egl {
std::uint32_t offsets[4]; std::uint32_t offsets[4];
}; };
display_t display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display);
make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display); std::optional<ctx_t> make_ctx(display_t::pointer display);
std::optional<ctx_t>
make_ctx(display_t::pointer display);
std::optional<rgb_t> std::optional<rgb_t>
import_source( import_source(
display_t::pointer egl_display, display_t::pointer egl_display,
const surface_descriptor_t &xrgb); const surface_descriptor_t &xrgb
);
rgb_t rgb_t create_blank(platf::img_t &img);
create_blank(platf::img_t &img);
std::optional<nv12_t> std::optional<nv12_t> import_target(
import_target(
display_t::pointer egl_display, display_t::pointer egl_display,
std::array<file_t, nv12_img_t::num_fds> &&fds, std::array<file_t, nv12_img_t::num_fds> &&fds,
const surface_descriptor_t &y, const surface_descriptor_t &uv); const surface_descriptor_t &y,
const surface_descriptor_t &uv
);
/** /**
* @brief Creates biplanar YUV textures to render into. * @brief Creates biplanar YUV textures to render into.
@@ -284,8 +262,7 @@ namespace egl {
* @param format Format of the target frame. * @param format Format of the target frame.
* @return The new RGB texture. * @return The new RGB texture.
*/ */
std::optional<nv12_t> std::optional<nv12_t> create_target(int width, int height, AVPixelFormat format);
create_target(int width, int height, AVPixelFormat format);
class cursor_t: public platf::img_t { class cursor_t: public platf::img_t {
public: public:
@@ -304,8 +281,7 @@ namespace egl {
reset(); reset();
} }
void void reset() {
reset() {
for (auto x = 0; x < 4; ++x) { for (auto x = 0; x < 4; ++x) {
if (sd.fds[x] >= 0) { if (sd.fds[x] >= 0) {
close(sd.fds[x]); close(sd.fds[x]);
@@ -323,26 +299,19 @@ namespace egl {
class sws_t { class sws_t {
public: public:
static std::optional<sws_t> static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex);
make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex); static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format);
static std::optional<sws_t>
make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format);
// Convert the loaded image into the first two framebuffers // Convert the loaded image into the first two framebuffers
int int convert(gl::frame_buf_t &fb);
convert(gl::frame_buf_t &fb);
// Make an area of the image black // Make an area of the image black
int int blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height);
blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height);
void void load_ram(platf::img_t &img);
load_ram(platf::img_t &img); void load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture);
void
load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture);
void void apply_colorspace(const video::sunshine_colorspace_t &colorspace);
apply_colorspace(const video::sunshine_colorspace_t &colorspace);
// The first texture is the monitor image. // The first texture is the monitor image.
// The second texture is the cursor image // The second texture is the cursor image
@@ -367,6 +336,5 @@ namespace egl {
std::uint64_t serial; std::uint64_t serial;
}; };
bool bool fail();
fail();
} // namespace egl } // namespace egl

View File

@@ -2,132 +2,114 @@
* @file src/platform/linux/input/inputtino.cpp * @file src/platform/linux/input/inputtino.cpp
* @brief Definitions for the inputtino Linux input handling. * @brief Definitions for the inputtino Linux input handling.
*/ */
// lib includes
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
#include "src/config.h" // local includes
#include "src/platform/common.h"
#include "src/utility.h"
#include "inputtino_common.h" #include "inputtino_common.h"
#include "inputtino_gamepad.h" #include "inputtino_gamepad.h"
#include "inputtino_keyboard.h" #include "inputtino_keyboard.h"
#include "inputtino_mouse.h" #include "inputtino_mouse.h"
#include "inputtino_pen.h" #include "inputtino_pen.h"
#include "inputtino_touch.h" #include "inputtino_touch.h"
#include "src/config.h"
#include "src/platform/common.h"
#include "src/utility.h"
using namespace std::literals; using namespace std::literals;
namespace platf { namespace platf {
input_t input_t input() {
input() { return {new input_raw_t()};
return { new input_raw_t() };
} }
std::unique_ptr<client_input_t> std::unique_ptr<client_input_t> allocate_client_input_context(input_t &input) {
allocate_client_input_context(input_t &input) {
return std::make_unique<client_input_raw_t>(input); return std::make_unique<client_input_raw_t>(input);
} }
void void freeInput(void *p) {
freeInput(void *p) {
auto *input = (input_raw_t *) p; auto *input = (input_raw_t *) p;
delete input; delete input;
} }
void void move_mouse(input_t &input, int deltaX, int deltaY) {
move_mouse(input_t &input, int deltaX, int deltaY) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
platf::mouse::move(raw, deltaX, deltaY); platf::mouse::move(raw, deltaX, deltaY);
} }
void void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
platf::mouse::move_abs(raw, touch_port, x, y); platf::mouse::move_abs(raw, touch_port, x, y);
} }
void void button_mouse(input_t &input, int button, bool release) {
button_mouse(input_t &input, int button, bool release) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
platf::mouse::button(raw, button, release); platf::mouse::button(raw, button, release);
} }
void void scroll(input_t &input, int high_res_distance) {
scroll(input_t &input, int high_res_distance) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
platf::mouse::scroll(raw, high_res_distance); platf::mouse::scroll(raw, high_res_distance);
} }
void void hscroll(input_t &input, int high_res_distance) {
hscroll(input_t &input, int high_res_distance) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
platf::mouse::hscroll(raw, high_res_distance); platf::mouse::hscroll(raw, high_res_distance);
} }
void void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
platf::keyboard::update(raw, modcode, release, flags); platf::keyboard::update(raw, modcode, release, flags);
} }
void void unicode(input_t &input, char *utf8, int size) {
unicode(input_t &input, char *utf8, int size) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
platf::keyboard::unicode(raw, utf8, size); platf::keyboard::unicode(raw, utf8, size);
} }
void void touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
auto raw = (client_input_raw_t *) input; auto raw = (client_input_raw_t *) input;
platf::touch::update(raw, touch_port, touch); platf::touch::update(raw, touch_port, touch);
} }
void void pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
auto raw = (client_input_raw_t *) input; auto raw = (client_input_raw_t *) input;
platf::pen::update(raw, touch_port, pen); platf::pen::update(raw, touch_port, pen);
} }
int int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
return platf::gamepad::alloc(raw, id, metadata, feedback_queue); return platf::gamepad::alloc(raw, id, metadata, feedback_queue);
} }
void void free_gamepad(input_t &input, int nr) {
free_gamepad(input_t &input, int nr) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
platf::gamepad::free(raw, nr); platf::gamepad::free(raw, nr);
} }
void void gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
platf::gamepad::update(raw, nr, gamepad_state); platf::gamepad::update(raw, nr, gamepad_state);
} }
void void gamepad_touch(input_t &input, const gamepad_touch_t &touch) {
gamepad_touch(input_t &input, const gamepad_touch_t &touch) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
platf::gamepad::touch(raw, touch); platf::gamepad::touch(raw, touch);
} }
void void gamepad_motion(input_t &input, const gamepad_motion_t &motion) {
gamepad_motion(input_t &input, const gamepad_motion_t &motion) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
platf::gamepad::motion(raw, motion); platf::gamepad::motion(raw, motion);
} }
void void gamepad_battery(input_t &input, const gamepad_battery_t &battery) {
gamepad_battery(input_t &input, const gamepad_battery_t &battery) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
platf::gamepad::battery(raw, battery); platf::gamepad::battery(raw, battery);
} }
platform_caps::caps_t platform_caps::caps_t get_capabilities() {
get_capabilities() {
platform_caps::caps_t caps = 0; platform_caps::caps_t caps = 0;
// TODO: if has_uinput // TODO: if has_uinput
caps |= platform_caps::pen_touch; caps |= platform_caps::pen_touch;
@@ -140,14 +122,12 @@ namespace platf {
return caps; return caps;
} }
util::point_t util::point_t get_mouse_loc(input_t &input) {
get_mouse_loc(input_t &input) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
return platf::mouse::get_location(raw); return platf::mouse::get_location(raw);
} }
std::vector<supported_gamepad_t> & std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
supported_gamepads(input_t *input) {
return platf::gamepad::supported_gamepads(input); return platf::gamepad::supported_gamepads(input);
} }
} // namespace platf } // namespace platf

View File

@@ -4,10 +4,12 @@
*/ */
#pragma once #pragma once
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
// local includes
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
@@ -94,8 +96,7 @@ namespace platf {
inputtino::Result<inputtino::PenTablet> pen; inputtino::Result<inputtino::PenTablet> pen;
}; };
inline float inline float deg2rad(float degree) {
deg2rad(float degree) {
return degree * (M_PI / 180.f); return degree * (M_PI / 180.f);
} }
} // namespace platf } // namespace platf

View File

@@ -2,18 +2,19 @@
* @file src/platform/linux/input/inputtino_gamepad.cpp * @file src/platform/linux/input/inputtino_gamepad.cpp
* @brief Definitions for inputtino gamepad input handling. * @brief Definitions for inputtino gamepad input handling.
*/ */
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/utility.h" #include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
using namespace std::literals; using namespace std::literals;
namespace platf::gamepad { namespace platf::gamepad {
@@ -25,69 +26,54 @@ namespace platf::gamepad {
GAMEPAD_STATUS ///< Helper to indicate the number of status GAMEPAD_STATUS ///< Helper to indicate the number of status
}; };
auto auto create_xbox_one() {
create_xbox_one() { return inputtino::XboxOneJoypad::create({.name = "Sunshine X-Box One (virtual) pad",
return inputtino::XboxOneJoypad::create({ .name = "Sunshine X-Box One (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147 // https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
.vendor_id = 0x045E, .vendor_id = 0x045E,
.product_id = 0x02EA, .product_id = 0x02EA,
.version = 0x0408 }); .version = 0x0408});
} }
auto auto create_switch() {
create_switch() { return inputtino::SwitchJoypad::create({.name = "Sunshine Nintendo (virtual) pad",
return inputtino::SwitchJoypad::create({ .name = "Sunshine Nintendo (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981 // https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
.vendor_id = 0x057e, .vendor_id = 0x057e,
.product_id = 0x2009, .product_id = 0x2009,
.version = 0x8111 }); .version = 0x8111});
} }
auto auto create_ds5() {
create_ds5() { return inputtino::PS5Joypad::create({.name = "Sunshine DualSense (virtual) pad", .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111});
return inputtino::PS5Joypad::create({ .name = "Sunshine DualSense (virtual) pad",
.vendor_id = 0x054C,
.product_id = 0x0CE6,
.version = 0x8111 });
} }
int int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
ControllerType selectedGamepadType; ControllerType selectedGamepadType;
if (config::input.gamepad == "xone"sv) { if (config::input.gamepad == "xone"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (manual selection)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (manual selection)"sv;
selectedGamepadType = XboxOneWired; selectedGamepadType = XboxOneWired;
} } else if (config::input.gamepad == "ds5"sv) {
else if (config::input.gamepad == "ds5"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualSense 5 controller (manual selection)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualSense 5 controller (manual selection)"sv;
selectedGamepadType = DualSenseWired; selectedGamepadType = DualSenseWired;
} } else if (config::input.gamepad == "switch"sv) {
else if (config::input.gamepad == "switch"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (manual selection)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (manual selection)"sv;
selectedGamepadType = SwitchProWired; selectedGamepadType = SwitchProWired;
} } else if (metadata.type == LI_CTYPE_XBOX) {
else if (metadata.type == LI_CTYPE_XBOX) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (auto-selected by client-reported type)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (auto-selected by client-reported type)"sv;
selectedGamepadType = XboxOneWired; selectedGamepadType = XboxOneWired;
} } else if (metadata.type == LI_CTYPE_PS) {
else if (metadata.type == LI_CTYPE_PS) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by client-reported type)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by client-reported type)"sv;
selectedGamepadType = DualSenseWired; selectedGamepadType = DualSenseWired;
} } else if (metadata.type == LI_CTYPE_NINTENDO) {
else if (metadata.type == LI_CTYPE_NINTENDO) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (auto-selected by client-reported type)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (auto-selected by client-reported type)"sv;
selectedGamepadType = SwitchProWired; selectedGamepadType = SwitchProWired;
} } else if (config::input.motion_as_ds4 && (metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
else if (config::input.motion_as_ds4 && (metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by motion sensor presence)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by motion sensor presence)"sv;
selectedGamepadType = DualSenseWired; selectedGamepadType = DualSenseWired;
} } else if (config::input.touchpad_as_ds4 && (metadata.capabilities & LI_CCAP_TOUCHPAD)) {
else if (config::input.touchpad_as_ds4 && (metadata.capabilities & LI_CCAP_TOUCHPAD)) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by touchpad presence)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by touchpad presence)"sv;
selectedGamepadType = DualSenseWired; selectedGamepadType = DualSenseWired;
} } else {
else {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (default)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (default)"sv;
selectedGamepadType = XboxOneWired; selectedGamepadType = XboxOneWired;
} }
@@ -102,8 +88,7 @@ namespace platf::gamepad {
if (metadata.capabilities & LI_CCAP_RGB_LED) { if (metadata.capabilities & LI_CCAP_RGB_LED) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " has an RGB LED, but it is not usable when emulating a joypad different from DS5"sv; BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " has an RGB LED, but it is not usable when emulating a joypad different from DS5"sv;
} }
} } else if (selectedGamepadType == DualSenseWired) {
else if (selectedGamepadType == DualSenseWired) {
if (!(metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) { if (!(metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " is emulating a DualShock 5 controller, but the client gamepad doesn't have motion sensors active"sv; BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " is emulating a DualShock 5 controller, but the client gamepad doesn't have motion sensors active"sv;
} }
@@ -125,33 +110,34 @@ namespace platf::gamepad {
}; };
switch (selectedGamepadType) { switch (selectedGamepadType) {
case XboxOneWired: { case XboxOneWired:
{
auto xOne = create_xbox_one(); auto xOne = create_xbox_one();
if (xOne) { if (xOne) {
(*xOne).set_on_rumble(on_rumble_fn); (*xOne).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne)); gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne));
raw->gamepads[id.globalIndex] = std::move(gamepad); raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0; return 0;
} } else {
else {
BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage(); BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage();
return -1; return -1;
} }
} }
case SwitchProWired: { case SwitchProWired:
{
auto switchPro = create_switch(); auto switchPro = create_switch();
if (switchPro) { if (switchPro) {
(*switchPro).set_on_rumble(on_rumble_fn); (*switchPro).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro)); gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro));
raw->gamepads[id.globalIndex] = std::move(gamepad); raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0; return 0;
} } else {
else {
BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage(); BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage();
return -1; return -1;
} }
} }
case DualSenseWired: { case DualSenseWired:
{
auto ds5 = create_ds5(); auto ds5 = create_ds5();
if (ds5) { if (ds5) {
(*ds5).set_on_rumble(on_rumble_fn); (*ds5).set_on_rumble(on_rumble_fn);
@@ -173,8 +159,7 @@ namespace platf::gamepad {
gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5)); gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5));
raw->gamepads[id.globalIndex] = std::move(gamepad); raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0; return 0;
} } else {
else {
BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage(); BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage();
return -1; return -1;
} }
@@ -183,15 +168,13 @@ namespace platf::gamepad {
return -1; return -1;
} }
void void free(input_raw_t *raw, int nr) {
free(input_raw_t *raw, int nr) {
// This will call the destructor which in turn will stop the background threads for rumble and LED (and ultimately remove the joypad device) // This will call the destructor which in turn will stop the background threads for rumble and LED (and ultimately remove the joypad device)
raw->gamepads[nr]->joypad.reset(); raw->gamepads[nr]->joypad.reset();
raw->gamepads[nr].reset(); raw->gamepads[nr].reset();
} }
void void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
auto gamepad = raw->gamepads[nr]; auto gamepad = raw->gamepads[nr];
if (!gamepad) { if (!gamepad) {
return; return;
@@ -206,8 +189,7 @@ namespace platf::gamepad {
*gamepad->joypad); *gamepad->joypad);
} }
void void touch(input_raw_t *raw, const gamepad_touch_t &touch) {
touch(input_raw_t *raw, const gamepad_touch_t &touch) {
auto gamepad = raw->gamepads[touch.id.globalIndex]; auto gamepad = raw->gamepads[touch.id.globalIndex];
if (!gamepad) { if (!gamepad) {
return; return;
@@ -216,15 +198,13 @@ namespace platf::gamepad {
if (std::holds_alternative<inputtino::PS5Joypad>(*gamepad->joypad)) { if (std::holds_alternative<inputtino::PS5Joypad>(*gamepad->joypad)) {
if (touch.pressure > 0.5) { if (touch.pressure > 0.5) {
std::get<inputtino::PS5Joypad>(*gamepad->joypad).place_finger(touch.pointerId, touch.x * inputtino::PS5Joypad::touchpad_width, touch.y * inputtino::PS5Joypad::touchpad_height); std::get<inputtino::PS5Joypad>(*gamepad->joypad).place_finger(touch.pointerId, touch.x * inputtino::PS5Joypad::touchpad_width, touch.y * inputtino::PS5Joypad::touchpad_height);
} } else {
else {
std::get<inputtino::PS5Joypad>(*gamepad->joypad).release_finger(touch.pointerId); std::get<inputtino::PS5Joypad>(*gamepad->joypad).release_finger(touch.pointerId);
} }
} }
} }
void void motion(input_raw_t *raw, const gamepad_motion_t &motion) {
motion(input_raw_t *raw, const gamepad_motion_t &motion) {
auto gamepad = raw->gamepads[motion.id.globalIndex]; auto gamepad = raw->gamepads[motion.id.globalIndex];
if (!gamepad) { if (!gamepad) {
return; return;
@@ -242,8 +222,7 @@ namespace platf::gamepad {
} }
} }
void void battery(input_raw_t *raw, const gamepad_battery_t &battery) {
battery(input_raw_t *raw, const gamepad_battery_t &battery) {
auto gamepad = raw->gamepads[battery.id.globalIndex]; auto gamepad = raw->gamepads[battery.id.globalIndex];
if (!gamepad) { if (!gamepad) {
return; return;
@@ -272,14 +251,13 @@ namespace platf::gamepad {
} }
} }
std::vector<supported_gamepad_t> & std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
supported_gamepads(input_t *input) {
if (!input) { if (!input) {
static std::vector gps { static std::vector gps {
supported_gamepad_t { "auto", true, "" }, supported_gamepad_t {"auto", true, ""},
supported_gamepad_t { "xone", false, "" }, supported_gamepad_t {"xone", false, ""},
supported_gamepad_t { "ds5", false, "" }, supported_gamepad_t {"ds5", false, ""},
supported_gamepad_t { "switch", false, "" }, supported_gamepad_t {"switch", false, ""},
}; };
return gps; return gps;
@@ -290,10 +268,10 @@ namespace platf::gamepad {
auto xOne = create_xbox_one(); auto xOne = create_xbox_one();
static std::vector gps { static std::vector gps {
supported_gamepad_t { "auto", true, "" }, supported_gamepad_t {"auto", true, ""},
supported_gamepad_t { "xone", static_cast<bool>(xOne), !xOne ? xOne.getErrorMessage() : "" }, supported_gamepad_t {"xone", static_cast<bool>(xOne), !xOne ? xOne.getErrorMessage() : ""},
supported_gamepad_t { "ds5", static_cast<bool>(ds5), !ds5 ? ds5.getErrorMessage() : "" }, supported_gamepad_t {"ds5", static_cast<bool>(ds5), !ds5 ? ds5.getErrorMessage() : ""},
supported_gamepad_t { "switch", static_cast<bool>(switchPro), !switchPro ? switchPro.getErrorMessage() : "" }, supported_gamepad_t {"switch", static_cast<bool>(switchPro), !switchPro ? switchPro.getErrorMessage() : ""},
}; };
for (auto &[name, is_enabled, reason_disabled] : gps) { for (auto &[name, is_enabled, reason_disabled] : gps) {

View File

@@ -4,13 +4,14 @@
*/ */
#pragma once #pragma once
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
#include "src/platform/common.h" // local includes
#include "inputtino_common.h" #include "inputtino_common.h"
#include "src/platform/common.h"
using namespace std::literals; using namespace std::literals;
@@ -22,24 +23,17 @@ namespace platf::gamepad {
SwitchProWired ///< Switch Pro Wired Controller SwitchProWired ///< Switch Pro Wired Controller
}; };
int int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
void void free(input_raw_t *raw, int nr);
free(input_raw_t *raw, int nr);
void void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state);
update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state);
void void touch(input_raw_t *raw, const gamepad_touch_t &touch);
touch(input_raw_t *raw, const gamepad_touch_t &touch);
void void motion(input_raw_t *raw, const gamepad_motion_t &motion);
motion(input_raw_t *raw, const gamepad_motion_t &motion);
void void battery(input_raw_t *raw, const gamepad_battery_t &battery);
battery(input_raw_t *raw, const gamepad_battery_t &battery);
std::vector<supported_gamepad_t> & std::vector<supported_gamepad_t> &supported_gamepads(input_t *input);
supported_gamepads(input_t *input);
} // namespace platf::gamepad } // namespace platf::gamepad

View File

@@ -2,18 +2,19 @@
* @file src/platform/linux/input/inputtino_keyboard.cpp * @file src/platform/linux/input/inputtino_keyboard.cpp
* @brief Definitions for inputtino keyboard input handling. * @brief Definitions for inputtino keyboard input handling.
*/ */
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_keyboard.h"
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/utility.h" #include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_keyboard.h"
using namespace std::literals; using namespace std::literals;
namespace platf::keyboard { namespace platf::keyboard {
@@ -25,8 +26,7 @@ namespace platf::keyboard {
* *
* adapted from: https://stackoverflow.com/a/7639754 * adapted from: https://stackoverflow.com/a/7639754
*/ */
std::string std::string to_hex(const std::basic_string<char32_t> &str) {
to_hex(const std::basic_string<char32_t> &str) {
std::stringstream ss; std::stringstream ss;
ss << std::hex << std::setfill('0'); ss << std::hex << std::setfill('0');
for (const auto &ch : str) { for (const auto &ch : str) {
@@ -42,48 +42,123 @@ namespace platf::keyboard {
* A map of linux scan code -> Moonlight keyboard code * A map of linux scan code -> Moonlight keyboard code
*/ */
static const std::map<short, short> key_mappings = { static const std::map<short, short> key_mappings = {
{ KEY_BACKSPACE, 0x08 }, { KEY_TAB, 0x09 }, { KEY_ENTER, 0x0D }, { KEY_LEFTSHIFT, 0x10 }, {KEY_BACKSPACE, 0x08},
{ KEY_LEFTCTRL, 0x11 }, { KEY_CAPSLOCK, 0x14 }, { KEY_ESC, 0x1B }, { KEY_SPACE, 0x20 }, {KEY_TAB, 0x09},
{ KEY_PAGEUP, 0x21 }, { KEY_PAGEDOWN, 0x22 }, { KEY_END, 0x23 }, { KEY_HOME, 0x24 }, {KEY_ENTER, 0x0D},
{ KEY_LEFT, 0x25 }, { KEY_UP, 0x26 }, { KEY_RIGHT, 0x27 }, { KEY_DOWN, 0x28 }, {KEY_LEFTSHIFT, 0x10},
{ KEY_SYSRQ, 0x2C }, { KEY_INSERT, 0x2D }, { KEY_DELETE, 0x2E }, { KEY_0, 0x30 }, {KEY_LEFTCTRL, 0x11},
{ KEY_1, 0x31 }, { KEY_2, 0x32 }, { KEY_3, 0x33 }, { KEY_4, 0x34 }, {KEY_CAPSLOCK, 0x14},
{ KEY_5, 0x35 }, { KEY_6, 0x36 }, { KEY_7, 0x37 }, { KEY_8, 0x38 }, {KEY_ESC, 0x1B},
{ KEY_9, 0x39 }, { KEY_A, 0x41 }, { KEY_B, 0x42 }, { KEY_C, 0x43 }, {KEY_SPACE, 0x20},
{ KEY_D, 0x44 }, { KEY_E, 0x45 }, { KEY_F, 0x46 }, { KEY_G, 0x47 }, {KEY_PAGEUP, 0x21},
{ KEY_H, 0x48 }, { KEY_I, 0x49 }, { KEY_J, 0x4A }, { KEY_K, 0x4B }, {KEY_PAGEDOWN, 0x22},
{ KEY_L, 0x4C }, { KEY_M, 0x4D }, { KEY_N, 0x4E }, { KEY_O, 0x4F }, {KEY_END, 0x23},
{ KEY_P, 0x50 }, { KEY_Q, 0x51 }, { KEY_R, 0x52 }, { KEY_S, 0x53 }, {KEY_HOME, 0x24},
{ KEY_T, 0x54 }, { KEY_U, 0x55 }, { KEY_V, 0x56 }, { KEY_W, 0x57 }, {KEY_LEFT, 0x25},
{ KEY_X, 0x58 }, { KEY_Y, 0x59 }, { KEY_Z, 0x5A }, { KEY_LEFTMETA, 0x5B }, {KEY_UP, 0x26},
{ KEY_RIGHTMETA, 0x5C }, { KEY_KP0, 0x60 }, { KEY_KP1, 0x61 }, { KEY_KP2, 0x62 }, {KEY_RIGHT, 0x27},
{ KEY_KP3, 0x63 }, { KEY_KP4, 0x64 }, { KEY_KP5, 0x65 }, { KEY_KP6, 0x66 }, {KEY_DOWN, 0x28},
{ KEY_KP7, 0x67 }, { KEY_KP8, 0x68 }, { KEY_KP9, 0x69 }, { KEY_KPASTERISK, 0x6A }, {KEY_SYSRQ, 0x2C},
{ KEY_KPPLUS, 0x6B }, { KEY_KPMINUS, 0x6D }, { KEY_KPDOT, 0x6E }, { KEY_KPSLASH, 0x6F }, {KEY_INSERT, 0x2D},
{ KEY_F1, 0x70 }, { KEY_F2, 0x71 }, { KEY_F3, 0x72 }, { KEY_F4, 0x73 }, {KEY_DELETE, 0x2E},
{ KEY_F5, 0x74 }, { KEY_F6, 0x75 }, { KEY_F7, 0x76 }, { KEY_F8, 0x77 }, {KEY_0, 0x30},
{ KEY_F9, 0x78 }, { KEY_F10, 0x79 }, { KEY_F11, 0x7A }, { KEY_F12, 0x7B }, {KEY_1, 0x31},
{ KEY_NUMLOCK, 0x90 }, { KEY_SCROLLLOCK, 0x91 }, { KEY_LEFTSHIFT, 0xA0 }, { KEY_RIGHTSHIFT, 0xA1 }, {KEY_2, 0x32},
{ KEY_LEFTCTRL, 0xA2 }, { KEY_RIGHTCTRL, 0xA3 }, { KEY_LEFTALT, 0xA4 }, { KEY_RIGHTALT, 0xA5 }, {KEY_3, 0x33},
{ KEY_SEMICOLON, 0xBA }, { KEY_EQUAL, 0xBB }, { KEY_COMMA, 0xBC }, { KEY_MINUS, 0xBD }, {KEY_4, 0x34},
{ KEY_DOT, 0xBE }, { KEY_SLASH, 0xBF }, { KEY_GRAVE, 0xC0 }, { KEY_LEFTBRACE, 0xDB }, {KEY_5, 0x35},
{ KEY_BACKSLASH, 0xDC }, { KEY_RIGHTBRACE, 0xDD }, { KEY_APOSTROPHE, 0xDE }, { KEY_102ND, 0xE2 } {KEY_6, 0x36},
{KEY_7, 0x37},
{KEY_8, 0x38},
{KEY_9, 0x39},
{KEY_A, 0x41},
{KEY_B, 0x42},
{KEY_C, 0x43},
{KEY_D, 0x44},
{KEY_E, 0x45},
{KEY_F, 0x46},
{KEY_G, 0x47},
{KEY_H, 0x48},
{KEY_I, 0x49},
{KEY_J, 0x4A},
{KEY_K, 0x4B},
{KEY_L, 0x4C},
{KEY_M, 0x4D},
{KEY_N, 0x4E},
{KEY_O, 0x4F},
{KEY_P, 0x50},
{KEY_Q, 0x51},
{KEY_R, 0x52},
{KEY_S, 0x53},
{KEY_T, 0x54},
{KEY_U, 0x55},
{KEY_V, 0x56},
{KEY_W, 0x57},
{KEY_X, 0x58},
{KEY_Y, 0x59},
{KEY_Z, 0x5A},
{KEY_LEFTMETA, 0x5B},
{KEY_RIGHTMETA, 0x5C},
{KEY_KP0, 0x60},
{KEY_KP1, 0x61},
{KEY_KP2, 0x62},
{KEY_KP3, 0x63},
{KEY_KP4, 0x64},
{KEY_KP5, 0x65},
{KEY_KP6, 0x66},
{KEY_KP7, 0x67},
{KEY_KP8, 0x68},
{KEY_KP9, 0x69},
{KEY_KPASTERISK, 0x6A},
{KEY_KPPLUS, 0x6B},
{KEY_KPMINUS, 0x6D},
{KEY_KPDOT, 0x6E},
{KEY_KPSLASH, 0x6F},
{KEY_F1, 0x70},
{KEY_F2, 0x71},
{KEY_F3, 0x72},
{KEY_F4, 0x73},
{KEY_F5, 0x74},
{KEY_F6, 0x75},
{KEY_F7, 0x76},
{KEY_F8, 0x77},
{KEY_F9, 0x78},
{KEY_F10, 0x79},
{KEY_F11, 0x7A},
{KEY_F12, 0x7B},
{KEY_NUMLOCK, 0x90},
{KEY_SCROLLLOCK, 0x91},
{KEY_LEFTSHIFT, 0xA0},
{KEY_RIGHTSHIFT, 0xA1},
{KEY_LEFTCTRL, 0xA2},
{KEY_RIGHTCTRL, 0xA3},
{KEY_LEFTALT, 0xA4},
{KEY_RIGHTALT, 0xA5},
{KEY_SEMICOLON, 0xBA},
{KEY_EQUAL, 0xBB},
{KEY_COMMA, 0xBC},
{KEY_MINUS, 0xBD},
{KEY_DOT, 0xBE},
{KEY_SLASH, 0xBF},
{KEY_GRAVE, 0xC0},
{KEY_LEFTBRACE, 0xDB},
{KEY_BACKSLASH, 0xDC},
{KEY_RIGHTBRACE, 0xDD},
{KEY_APOSTROPHE, 0xDE},
{KEY_102ND, 0xE2}
}; };
void void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) {
update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) {
if (raw->keyboard) { if (raw->keyboard) {
if (release) { if (release) {
(*raw->keyboard).release(modcode); (*raw->keyboard).release(modcode);
} } else {
else {
(*raw->keyboard).press(modcode); (*raw->keyboard).press(modcode);
} }
} }
} }
void void unicode(input_raw_t *raw, char *utf8, int size) {
unicode(input_raw_t *raw, char *utf8, int size) {
if (raw->keyboard) { if (raw->keyboard) {
/* Reading input text as UTF-8 */ /* Reading input text as UTF-8 */
auto utf8_str = boost::locale::conv::to_utf<wchar_t>(utf8, utf8 + size, "UTF-8"); auto utf8_str = boost::locale::conv::to_utf<wchar_t>(utf8, utf8 + size, "UTF-8");
@@ -106,8 +181,7 @@ namespace platf::keyboard {
auto wincode = key_mappings.find(keycode); auto wincode = key_mappings.find(keycode);
if (keycode == -1 || wincode == key_mappings.end()) { if (keycode == -1 || wincode == key_mappings.end()) {
BOOST_LOG(warning) << "Unicode, unable to find keycode for: "sv << ch; BOOST_LOG(warning) << "Unicode, unable to find keycode for: "sv << ch;
} } else {
else {
(*raw->keyboard).press(wincode->second); (*raw->keyboard).press(wincode->second);
(*raw->keyboard).release(wincode->second); (*raw->keyboard).release(wincode->second);
} }

View File

@@ -4,18 +4,18 @@
*/ */
#pragma once #pragma once
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h" #include "inputtino_common.h"
using namespace std::literals; using namespace std::literals;
namespace platf::keyboard { namespace platf::keyboard {
void void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags);
update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags);
void void unicode(input_raw_t *raw, char *utf8, int size);
unicode(input_raw_t *raw, char *utf8, int size);
} // namespace platf::keyboard } // namespace platf::keyboard

View File

@@ -2,38 +2,36 @@
* @file src/platform/linux/input/inputtino_mouse.cpp * @file src/platform/linux/input/inputtino_mouse.cpp
* @brief Definitions for inputtino mouse input handling. * @brief Definitions for inputtino mouse input handling.
*/ */
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_mouse.h"
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/utility.h" #include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_mouse.h"
using namespace std::literals; using namespace std::literals;
namespace platf::mouse { namespace platf::mouse {
void void move(input_raw_t *raw, int deltaX, int deltaY) {
move(input_raw_t *raw, int deltaX, int deltaY) {
if (raw->mouse) { if (raw->mouse) {
(*raw->mouse).move(deltaX, deltaY); (*raw->mouse).move(deltaX, deltaY);
} }
} }
void void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y) {
move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y) {
if (raw->mouse) { if (raw->mouse) {
(*raw->mouse).move_abs(x, y, touch_port.width, touch_port.height); (*raw->mouse).move_abs(x, y, touch_port.width, touch_port.height);
} }
} }
void void button(input_raw_t *raw, int button, bool release) {
button(input_raw_t *raw, int button, bool release) {
if (raw->mouse) { if (raw->mouse) {
inputtino::Mouse::MOUSE_BUTTON btn_type; inputtino::Mouse::MOUSE_BUTTON btn_type;
switch (button) { switch (button) {
@@ -58,35 +56,31 @@ namespace platf::mouse {
} }
if (release) { if (release) {
(*raw->mouse).release(btn_type); (*raw->mouse).release(btn_type);
} } else {
else {
(*raw->mouse).press(btn_type); (*raw->mouse).press(btn_type);
} }
} }
} }
void void scroll(input_raw_t *raw, int high_res_distance) {
scroll(input_raw_t *raw, int high_res_distance) {
if (raw->mouse) { if (raw->mouse) {
(*raw->mouse).vertical_scroll(high_res_distance); (*raw->mouse).vertical_scroll(high_res_distance);
} }
} }
void void hscroll(input_raw_t *raw, int high_res_distance) {
hscroll(input_raw_t *raw, int high_res_distance) {
if (raw->mouse) { if (raw->mouse) {
(*raw->mouse).horizontal_scroll(high_res_distance); (*raw->mouse).horizontal_scroll(high_res_distance);
} }
} }
util::point_t util::point_t get_location(input_raw_t *raw) {
get_location(input_raw_t *raw) {
if (raw->mouse) { if (raw->mouse) {
// TODO: decide what to do after https://github.com/games-on-whales/inputtino/issues/6 is resolved. // TODO: decide what to do after https://github.com/games-on-whales/inputtino/issues/6 is resolved.
// TODO: auto x = (*raw->mouse).get_absolute_x(); // TODO: auto x = (*raw->mouse).get_absolute_x();
// TODO: auto y = (*raw->mouse).get_absolute_y(); // TODO: auto y = (*raw->mouse).get_absolute_y();
return { 0, 0 }; return {0, 0};
} }
return { 0, 0 }; return {0, 0};
} }
} // namespace platf::mouse } // namespace platf::mouse

View File

@@ -3,33 +3,27 @@
* @brief Declarations for inputtino mouse input handling. * @brief Declarations for inputtino mouse input handling.
*/ */
#pragma once #pragma once
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
#include "src/platform/common.h" // local includes
#include "inputtino_common.h" #include "inputtino_common.h"
#include "src/platform/common.h"
using namespace std::literals; using namespace std::literals;
namespace platf::mouse { namespace platf::mouse {
void void move(input_raw_t *raw, int deltaX, int deltaY);
move(input_raw_t *raw, int deltaX, int deltaY);
void void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y);
move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y);
void void button(input_raw_t *raw, int button, bool release);
button(input_raw_t *raw, int button, bool release);
void void scroll(input_raw_t *raw, int high_res_distance);
scroll(input_raw_t *raw, int high_res_distance);
void void hscroll(input_raw_t *raw, int high_res_distance);
hscroll(input_raw_t *raw, int high_res_distance);
util::point_t util::point_t get_location(input_raw_t *raw);
get_location(input_raw_t *raw);
} // namespace platf::mouse } // namespace platf::mouse

View File

@@ -2,23 +2,23 @@
* @file src/platform/linux/input/inputtino_pen.cpp * @file src/platform/linux/input/inputtino_pen.cpp
* @brief Definitions for inputtino pen input handling. * @brief Definitions for inputtino pen input handling.
*/ */
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_pen.h"
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/utility.h" #include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_pen.h"
using namespace std::literals; using namespace std::literals;
namespace platf::pen { namespace platf::pen {
void void update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen) {
update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen) {
if (raw->pen) { if (raw->pen) {
// First set the buttons // First set the buttons
(*raw->pen).set_btn(inputtino::PenTablet::PRIMARY, pen.penButtons & LI_PEN_BUTTON_PRIMARY); (*raw->pen).set_btn(inputtino::PenTablet::PRIMARY, pen.penButtons & LI_PEN_BUTTON_PRIMARY);
@@ -63,13 +63,7 @@ namespace platf::pen {
bool is_touching = pen.eventType == LI_TOUCH_EVENT_DOWN || pen.eventType == LI_TOUCH_EVENT_MOVE; bool is_touching = pen.eventType == LI_TOUCH_EVENT_DOWN || pen.eventType == LI_TOUCH_EVENT_MOVE;
(*raw->pen).place_tool(tool, (*raw->pen).place_tool(tool, pen.x, pen.y, is_touching ? pen.pressureOrDistance : -1, is_touching ? -1 : pen.pressureOrDistance, tilt_x, tilt_y);
pen.x,
pen.y,
is_touching ? pen.pressureOrDistance : -1,
is_touching ? -1 : pen.pressureOrDistance,
tilt_x,
tilt_y);
} }
} }
} // namespace platf::pen } // namespace platf::pen

View File

@@ -4,17 +4,17 @@
*/ */
#pragma once #pragma once
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
#include "src/platform/common.h" // local includes
#include "inputtino_common.h" #include "inputtino_common.h"
#include "src/platform/common.h"
using namespace std::literals; using namespace std::literals;
namespace platf::pen { namespace platf::pen {
void void update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen);
update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen);
} }

View File

@@ -2,28 +2,29 @@
* @file src/platform/linux/input/inputtino_touch.cpp * @file src/platform/linux/input/inputtino_touch.cpp
* @brief Definitions for inputtino touch input handling. * @brief Definitions for inputtino touch input handling.
*/ */
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_touch.h"
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/utility.h" #include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_touch.h"
using namespace std::literals; using namespace std::literals;
namespace platf::touch { namespace platf::touch {
void void update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch) {
update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch) {
if (raw->touch) { if (raw->touch) {
switch (touch.eventType) { switch (touch.eventType) {
case LI_TOUCH_EVENT_HOVER: case LI_TOUCH_EVENT_HOVER:
case LI_TOUCH_EVENT_DOWN: case LI_TOUCH_EVENT_DOWN:
case LI_TOUCH_EVENT_MOVE: { case LI_TOUCH_EVENT_MOVE:
{
// Convert our 0..360 range to -90..90 relative to Y axis // Convert our 0..360 range to -90..90 relative to Y axis
int adjusted_angle = touch.rotation; int adjusted_angle = touch.rotation;
@@ -35,8 +36,7 @@ namespace platf::touch {
// Wrap the value if it's out of range // Wrap the value if it's out of range
if (adjusted_angle > 90) { if (adjusted_angle > 90) {
adjusted_angle -= 360; adjusted_angle -= 360;
} } else if (adjusted_angle < -90) {
else if (adjusted_angle < -90) {
adjusted_angle += 360; adjusted_angle += 360;
} }
(*raw->touch).place_finger(touch.pointerId, touch.x, touch.y, touch.pressureOrDistance, adjusted_angle); (*raw->touch).place_finger(touch.pointerId, touch.x, touch.y, touch.pressureOrDistance, adjusted_angle);
@@ -44,7 +44,8 @@ namespace platf::touch {
} }
case LI_TOUCH_EVENT_CANCEL: case LI_TOUCH_EVENT_CANCEL:
case LI_TOUCH_EVENT_UP: case LI_TOUCH_EVENT_UP:
case LI_TOUCH_EVENT_HOVER_LEAVE: { case LI_TOUCH_EVENT_HOVER_LEAVE:
{
(*raw->touch).release_finger(touch.pointerId); (*raw->touch).release_finger(touch.pointerId);
break; break;
} }

View File

@@ -4,17 +4,17 @@
*/ */
#pragma once #pragma once
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
#include "src/platform/common.h" // local includes
#include "inputtino_common.h" #include "inputtino_common.h"
#include "src/platform/common.h"
using namespace std::literals; using namespace std::literals;
namespace platf::touch { namespace platf::touch {
void void update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch);
update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -2,28 +2,30 @@
* @file src/platform/linux/kmsgrab.cpp * @file src/platform/linux/kmsgrab.cpp
* @brief Definitions for KMS screen capture. * @brief Definitions for KMS screen capture.
*/ */
#include <drm_fourcc.h> // standard includes
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <filesystem>
#include <thread>
#include <unistd.h>
// platform includes
#include <drm_fourcc.h>
#include <linux/dma-buf.h> #include <linux/dma-buf.h>
#include <sys/capability.h> #include <sys/capability.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h> #include <xf86drm.h>
#include <xf86drmMode.h> #include <xf86drmMode.h>
#include <filesystem> // local includes
#include <thread> #include "cuda.h"
#include "graphics.h"
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/round_robin.h" #include "src/round_robin.h"
#include "src/utility.h" #include "src/utility.h"
#include "src/video.h" #include "src/video.h"
#include "cuda.h"
#include "graphics.h"
#include "vaapi.h" #include "vaapi.h"
#include "wayland.h" #include "wayland.h"
@@ -59,7 +61,10 @@ namespace platf {
class wrapper_fb { class wrapper_fb {
public: public:
wrapper_fb(drmModeFB *fb): wrapper_fb(drmModeFB *fb):
fb { fb }, fb_id { fb->fb_id }, width { fb->width }, height { fb->height } { fb {fb},
fb_id {fb->fb_id},
width {fb->width},
height {fb->height} {
pixel_format = DRM_FORMAT_XRGB8888; pixel_format = DRM_FORMAT_XRGB8888;
modifier = DRM_FORMAT_MOD_INVALID; modifier = DRM_FORMAT_MOD_INVALID;
std::fill_n(handles, 4, 0); std::fill_n(handles, 4, 0);
@@ -70,7 +75,10 @@ namespace platf {
} }
wrapper_fb(drmModeFB2 *fb2): wrapper_fb(drmModeFB2 *fb2):
fb2 { fb2 }, fb_id { fb2->fb_id }, width { fb2->width }, height { fb2->height } { fb2 {fb2},
fb_id {fb2->fb_id},
width {fb2->width},
height {fb2->height} {
pixel_format = fb2->pixel_format; pixel_format = fb2->pixel_format;
modifier = (fb2->flags & DRM_MODE_FB_MODIFIERS) ? fb2->modifier : DRM_FORMAT_MOD_INVALID; modifier = (fb2->flags & DRM_MODE_FB_MODIFIERS) ? fb2->modifier : DRM_FORMAT_MOD_INVALID;
@@ -82,8 +90,7 @@ namespace platf {
~wrapper_fb() { ~wrapper_fb() {
if (fb) { if (fb) {
drmModeFreeFB(fb); drmModeFreeFB(fb);
} } else if (fb2) {
else if (fb2) {
drmModeFreeFB2(fb2); drmModeFreeFB2(fb2);
} }
} }
@@ -116,8 +123,7 @@ namespace platf {
static int env_width; static int env_width;
static int env_height; static int env_height;
std::string_view std::string_view plane_type(std::uint64_t val) {
plane_type(std::uint64_t val) {
switch (val) { switch (val) {
case DRM_PLANE_TYPE_OVERLAY: case DRM_PLANE_TYPE_OVERLAY:
return "DRM_PLANE_TYPE_OVERLAY"sv; return "DRM_PLANE_TYPE_OVERLAY"sv;
@@ -165,10 +171,10 @@ namespace platf {
static std::vector<card_descriptor_t> card_descriptors; static std::vector<card_descriptor_t> card_descriptors;
static std::uint32_t static std::uint32_t from_view(const std::string_view &string) {
from_view(const std::string_view &string) {
#define _CONVERT(x, y) \ #define _CONVERT(x, y) \
if (string == x) return DRM_MODE_CONNECTOR_##y if (string == x) \
return DRM_MODE_CONNECTOR_##y
// This list was created from the following sources: // This list was created from the following sources:
// https://gitlab.freedesktop.org/mesa/drm/-/blob/main/xf86drmMode.c (drmModeGetConnectorTypeName) // https://gitlab.freedesktop.org/mesa/drm/-/blob/main/xf86drmMode.c (drmModeGetConnectorTypeName)
@@ -212,7 +218,7 @@ namespace platf {
// value appended to the string. Let's try to read it. // value appended to the string. Let's try to read it.
if (string.find("Unknown"sv) == 0) { if (string.find("Unknown"sv) == 0) {
std::uint32_t type; std::uint32_t type;
std::string null_terminated_string { string }; std::string null_terminated_string {string};
if (std::sscanf(null_terminated_string.c_str(), "Unknown%u", &type) == 1) { if (std::sscanf(null_terminated_string.c_str(), "Unknown%u", &type) == 1) {
return type; return type;
} }
@@ -225,15 +231,19 @@ namespace platf {
class plane_it_t: public round_robin_util::it_wrap_t<plane_t::element_type, plane_it_t> { class plane_it_t: public round_robin_util::it_wrap_t<plane_t::element_type, plane_it_t> {
public: public:
plane_it_t(int fd, std::uint32_t *plane_p, std::uint32_t *end): plane_it_t(int fd, std::uint32_t *plane_p, std::uint32_t *end):
fd { fd }, plane_p { plane_p }, end { end } { fd {fd},
plane_p {plane_p},
end {end} {
load_next_valid_plane(); load_next_valid_plane();
} }
plane_it_t(int fd, std::uint32_t *end): plane_it_t(int fd, std::uint32_t *end):
fd { fd }, plane_p { end }, end { end } {} fd {fd},
plane_p {end},
end {end} {
}
void void load_next_valid_plane() {
load_next_valid_plane() {
this->plane.reset(); this->plane.reset();
for (; plane_p != end; ++plane_p) { for (; plane_p != end; ++plane_p) {
@@ -248,19 +258,16 @@ namespace platf {
} }
} }
void void inc() {
inc() {
++plane_p; ++plane_p;
load_next_valid_plane(); load_next_valid_plane();
} }
bool bool eq(const plane_it_t &other) const {
eq(const plane_it_t &other) const {
return plane_p == other.plane_p; return plane_p == other.plane_p;
} }
plane_t::pointer plane_t::pointer get() {
get() {
return plane.get(); return plane.get();
} }
@@ -289,8 +296,7 @@ namespace platf {
public: public:
using connector_interal_t = util::safe_ptr<drmModeConnector, drmModeFreeConnector>; using connector_interal_t = util::safe_ptr<drmModeConnector, drmModeFreeConnector>;
int int init(const char *path) {
init(const char *path) {
cap_sys_admin admin; cap_sys_admin admin;
fd.el = open(path, O_RDWR); fd.el = open(path, O_RDWR);
@@ -299,7 +305,7 @@ namespace platf {
return -1; return -1;
} }
version_t ver { drmGetVersion(fd.el) }; version_t ver {drmGetVersion(fd.el)};
BOOST_LOG(info) << path << " -> "sv << ((ver && ver->name) ? ver->name : "UNKNOWN"); BOOST_LOG(info) << path << " -> "sv << ((ver && ver->name) ? ver->name : "UNKNOWN");
// Open the render node for this card to share with libva. // Open the render node for this card to share with libva.
@@ -313,8 +319,7 @@ namespace platf {
render_fd.el = dup(fd.el); render_fd.el = dup(fd.el);
} }
free(rendernode_path); free(rendernode_path);
} } else {
else {
BOOST_LOG(warning) << "No render device name for: "sv << path; BOOST_LOG(warning) << "No render device name for: "sv << path;
render_fd.el = dup(fd.el); render_fd.el = dup(fd.el);
} }
@@ -346,8 +351,7 @@ namespace platf {
return 0; return 0;
} }
fb_t fb_t fb(plane_t::pointer plane) {
fb(plane_t::pointer plane) {
cap_sys_admin admin; cap_sys_admin admin;
auto fb2 = drmModeGetFB2(fd.el, plane->fb_id); auto fb2 = drmModeGetFB2(fd.el, plane->fb_id);
@@ -363,36 +367,30 @@ namespace platf {
return nullptr; return nullptr;
} }
crtc_t crtc_t crtc(std::uint32_t id) {
crtc(std::uint32_t id) {
return drmModeGetCrtc(fd.el, id); return drmModeGetCrtc(fd.el, id);
} }
encoder_t encoder_t encoder(std::uint32_t id) {
encoder(std::uint32_t id) {
return drmModeGetEncoder(fd.el, id); return drmModeGetEncoder(fd.el, id);
} }
res_t res_t res() {
res() {
return drmModeGetResources(fd.el); return drmModeGetResources(fd.el);
} }
bool bool is_nvidia() {
is_nvidia() { version_t ver {drmGetVersion(fd.el)};
version_t ver { drmGetVersion(fd.el) };
return ver && ver->name && strncmp(ver->name, "nvidia-drm", 10) == 0; return ver && ver->name && strncmp(ver->name, "nvidia-drm", 10) == 0;
} }
bool bool is_cursor(std::uint32_t plane_id) {
is_cursor(std::uint32_t plane_id) {
auto props = plane_props(plane_id); auto props = plane_props(plane_id);
for (auto &[prop, val] : props) { for (auto &[prop, val] : props) {
if (prop->name == "type"sv) { if (prop->name == "type"sv) {
if (val == DRM_PLANE_TYPE_CURSOR) { if (val == DRM_PLANE_TYPE_CURSOR) {
return true; return true;
} } else {
else {
return false; return false;
} }
} }
@@ -401,8 +399,7 @@ namespace platf {
return false; return false;
} }
std::optional<std::uint64_t> std::optional<std::uint64_t> prop_value_by_name(const std::vector<std::pair<prop_t, std::uint64_t>> &props, std::string_view name) {
prop_value_by_name(const std::vector<std::pair<prop_t, std::uint64_t>> &props, std::string_view name) {
for (auto &[prop, val] : props) { for (auto &[prop, val] : props) {
if (prop->name == name) { if (prop->name == name) {
return val; return val;
@@ -411,8 +408,7 @@ namespace platf {
return std::nullopt; return std::nullopt;
} }
std::uint32_t std::uint32_t get_panel_orientation(std::uint32_t plane_id) {
get_panel_orientation(std::uint32_t plane_id) {
auto props = plane_props(plane_id); auto props = plane_props(plane_id);
auto value = prop_value_by_name(props, "rotation"sv); auto value = prop_value_by_name(props, "rotation"sv);
if (value) { if (value) {
@@ -423,8 +419,7 @@ namespace platf {
return DRM_MODE_ROTATE_0; return DRM_MODE_ROTATE_0;
} }
int int get_crtc_index_by_id(std::uint32_t crtc_id) {
get_crtc_index_by_id(std::uint32_t crtc_id) {
auto resources = res(); auto resources = res();
for (int i = 0; i < resources->count_crtcs; i++) { for (int i = 0; i < resources->count_crtcs; i++) {
if (resources->crtcs[i] == crtc_id) { if (resources->crtcs[i] == crtc_id) {
@@ -434,13 +429,11 @@ namespace platf {
return -1; return -1;
} }
connector_interal_t connector_interal_t connector(std::uint32_t id) {
connector(std::uint32_t id) {
return drmModeGetConnector(fd.el, id); return drmModeGetConnector(fd.el, id);
} }
std::vector<connector_t> std::vector<connector_t> monitors(conn_type_count_t &conn_type_count) {
monitors(conn_type_count_t &conn_type_count) {
auto resources = res(); auto resources = res();
if (!resources) { if (!resources) {
BOOST_LOG(error) << "Couldn't get connector resources"sv; BOOST_LOG(error) << "Couldn't get connector resources"sv;
@@ -474,8 +467,7 @@ namespace platf {
return monitors; return monitors;
} }
file_t file_t handleFD(std::uint32_t handle) {
handleFD(std::uint32_t handle) {
file_t fb_fd; file_t fb_fd;
auto status = drmPrimeHandleToFD(fd.el, handle, 0 /* flags */, &fb_fd.el); auto status = drmPrimeHandleToFD(fd.el, handle, 0 /* flags */, &fb_fd.el);
@@ -486,8 +478,7 @@ namespace platf {
return fb_fd; return fb_fd;
} }
std::vector<std::pair<prop_t, std::uint64_t>> std::vector<std::pair<prop_t, std::uint64_t>> props(std::uint32_t id, std::uint32_t type) {
props(std::uint32_t id, std::uint32_t type) {
obj_prop_t obj_prop = drmModeObjectGetProperties(fd.el, id, type); obj_prop_t obj_prop = drmModeObjectGetProperties(fd.el, id, type);
if (!obj_prop) { if (!obj_prop) {
return {}; return {};
@@ -503,39 +494,32 @@ namespace platf {
return props; return props;
} }
std::vector<std::pair<prop_t, std::uint64_t>> std::vector<std::pair<prop_t, std::uint64_t>> plane_props(std::uint32_t id) {
plane_props(std::uint32_t id) {
return props(id, DRM_MODE_OBJECT_PLANE); return props(id, DRM_MODE_OBJECT_PLANE);
} }
std::vector<std::pair<prop_t, std::uint64_t>> std::vector<std::pair<prop_t, std::uint64_t>> crtc_props(std::uint32_t id) {
crtc_props(std::uint32_t id) {
return props(id, DRM_MODE_OBJECT_CRTC); return props(id, DRM_MODE_OBJECT_CRTC);
} }
std::vector<std::pair<prop_t, std::uint64_t>> std::vector<std::pair<prop_t, std::uint64_t>> connector_props(std::uint32_t id) {
connector_props(std::uint32_t id) {
return props(id, DRM_MODE_OBJECT_CONNECTOR); return props(id, DRM_MODE_OBJECT_CONNECTOR);
} }
plane_t plane_t operator[](std::uint32_t index) {
operator[](std::uint32_t index) {
return drmModeGetPlane(fd.el, plane_res->planes[index]); return drmModeGetPlane(fd.el, plane_res->planes[index]);
} }
std::uint32_t std::uint32_t count() {
count() {
return plane_res->count_planes; return plane_res->count_planes;
} }
plane_it_t plane_it_t begin() const {
begin() const { return plane_it_t {fd.el, plane_res->planes, plane_res->planes + plane_res->count_planes};
return plane_it_t { fd.el, plane_res->planes, plane_res->planes + plane_res->count_planes };
} }
plane_it_t plane_it_t end() const {
end() const { return plane_it_t {fd.el, plane_res->planes + plane_res->count_planes};
return plane_it_t { fd.el, plane_res->planes + plane_res->count_planes };
} }
file_t fd; file_t fd;
@@ -543,13 +527,11 @@ namespace platf {
plane_res_t plane_res; plane_res_t plane_res;
}; };
std::map<std::uint32_t, monitor_t> std::map<std::uint32_t, monitor_t> map_crtc_to_monitor(const std::vector<connector_t> &connectors) {
map_crtc_to_monitor(const std::vector<connector_t> &connectors) {
std::map<std::uint32_t, monitor_t> result; std::map<std::uint32_t, monitor_t> result;
for (auto &connector : connectors) { for (auto &connector : connectors) {
result.emplace(connector.crtc_id, result.emplace(connector.crtc_id, monitor_t {
monitor_t {
connector.type, connector.type,
connector.index, connector.index,
}); });
@@ -565,8 +547,7 @@ namespace platf {
} }
}; };
void void print(plane_t::pointer plane, fb_t::pointer fb, crtc_t::pointer crtc) {
print(plane_t::pointer plane, fb_t::pointer fb, crtc_t::pointer crtc) {
if (crtc) { if (crtc) {
BOOST_LOG(debug) << "crtc("sv << crtc->x << ", "sv << crtc->y << ')'; BOOST_LOG(debug) << "crtc("sv << crtc->x << ", "sv << crtc->y << ')';
BOOST_LOG(debug) << "crtc("sv << crtc->width << ", "sv << crtc->height << ')'; BOOST_LOG(debug) << "crtc("sv << crtc->width << ", "sv << crtc->height << ')';
@@ -601,21 +582,22 @@ namespace platf {
class display_t: public platf::display_t { class display_t: public platf::display_t {
public: public:
display_t(mem_type_e mem_type): display_t(mem_type_e mem_type):
platf::display_t(), mem_type { mem_type } {} platf::display_t(),
mem_type {mem_type} {
}
int int init(const std::string &display_name, const ::video::config_t &config) {
init(const std::string &display_name, const ::video::config_t &config) { delay = std::chrono::nanoseconds {1s} / config.framerate;
delay = std::chrono::nanoseconds { 1s } / config.framerate;
int monitor_index = util::from_view(display_name); int monitor_index = util::from_view(display_name);
int monitor = 0; int monitor = 0;
fs::path card_dir { "/dev/dri"sv }; fs::path card_dir {"/dev/dri"sv};
for (auto &entry : fs::directory_iterator { card_dir }) { for (auto &entry : fs::directory_iterator {card_dir}) {
auto file = entry.path().filename(); auto file = entry.path().filename();
auto filestring = file.generic_string(); auto filestring = file.generic_string();
if (filestring.size() < 4 || std::string_view { filestring }.substr(0, 4) != "card"sv) { if (filestring.size() < 4 || std::string_view {filestring}.substr(0, 4) != "card"sv) {
continue; continue;
} }
@@ -779,8 +761,7 @@ namespace platf {
if (!(plane->possible_crtcs & (1 << crtc_index))) { if (!(plane->possible_crtcs & (1 << crtc_index))) {
// Skip cursor planes for other CRTCs // Skip cursor planes for other CRTCs
continue; continue;
} } else if (plane->possible_crtcs != (1 << crtc_index)) {
else if (plane->possible_crtcs != (1 << crtc_index)) {
// We assume a 1:1 mapping between cursor planes and CRTCs, which seems to // We assume a 1:1 mapping between cursor planes and CRTCs, which seems to
// match the behavior of drivers in the real world. If it's violated, we'll // match the behavior of drivers in the real world. If it's violated, we'll
// proceed anyway but print a warning in the log. // proceed anyway but print a warning in the log.
@@ -799,8 +780,7 @@ namespace platf {
return 0; return 0;
} }
bool bool is_hdr() {
is_hdr() {
if (!hdr_metadata_blob_id || *hdr_metadata_blob_id == 0) { if (!hdr_metadata_blob_id || *hdr_metadata_blob_id == 0) {
return false; return false;
} }
@@ -846,8 +826,7 @@ namespace platf {
} }
} }
bool bool get_hdr_metadata(SS_HDR_METADATA &metadata) {
get_hdr_metadata(SS_HDR_METADATA &metadata) {
// This performs all the metadata validation // This performs all the metadata validation
if (!is_hdr()) { if (!is_hdr()) {
return false; return false;
@@ -876,8 +855,7 @@ namespace platf {
return true; return true;
} }
void void update_cursor() {
update_cursor() {
if (cursor_plane_id < 0) { if (cursor_plane_id < 0) {
return; return;
} }
@@ -898,26 +876,19 @@ namespace platf {
for (auto &[prop, val] : props) { for (auto &[prop, val] : props) {
if (prop->name == "CRTC_X"sv) { if (prop->name == "CRTC_X"sv) {
prop_crtc_x = val; prop_crtc_x = val;
} } else if (prop->name == "CRTC_Y"sv) {
else if (prop->name == "CRTC_Y"sv) {
prop_crtc_y = val; prop_crtc_y = val;
} } else if (prop->name == "CRTC_W"sv) {
else if (prop->name == "CRTC_W"sv) {
prop_crtc_w = val; prop_crtc_w = val;
} } else if (prop->name == "CRTC_H"sv) {
else if (prop->name == "CRTC_H"sv) {
prop_crtc_h = val; prop_crtc_h = val;
} } else if (prop->name == "SRC_X"sv) {
else if (prop->name == "SRC_X"sv) {
prop_src_x = val; prop_src_x = val;
} } else if (prop->name == "SRC_Y"sv) {
else if (prop->name == "SRC_Y"sv) {
prop_src_y = val; prop_src_y = val;
} } else if (prop->name == "SRC_W"sv) {
else if (prop->name == "SRC_W"sv) {
prop_src_w = val; prop_src_w = val;
} } else if (prop->name == "SRC_H"sv) {
else if (prop->name == "SRC_H"sv) {
prop_src_h = val; prop_src_h = val;
} }
} }
@@ -951,12 +922,10 @@ namespace platf {
if (!plane->fb_id) { if (!plane->fb_id) {
captured_cursor.visible = false; captured_cursor.visible = false;
captured_cursor.fb_id = 0; captured_cursor.fb_id = 0;
} } else if (plane->fb_id != captured_cursor.fb_id) {
else if (plane->fb_id != captured_cursor.fb_id) {
BOOST_LOG(debug) << "Refreshing cursor image after FB changed"sv; BOOST_LOG(debug) << "Refreshing cursor image after FB changed"sv;
cursor_dirty = true; cursor_dirty = true;
} } else if (*prop_src_x != captured_cursor.prop_src_x ||
else if (*prop_src_x != captured_cursor.prop_src_x ||
*prop_src_y != captured_cursor.prop_src_y || *prop_src_y != captured_cursor.prop_src_y ||
*prop_src_w != captured_cursor.prop_src_w || *prop_src_w != captured_cursor.prop_src_w ||
*prop_src_h != captured_cursor.prop_src_h) { *prop_src_h != captured_cursor.prop_src_h) {
@@ -1041,8 +1010,7 @@ namespace platf {
// If the image is tightly packed, copy it in one shot // If the image is tightly packed, copy it in one shot
if (fb->pitches[0] == src_w * 4 && src_x == 0) { if (fb->pitches[0] == src_w * 4 && src_x == 0) {
memcpy(captured_cursor.pixels.data(), &((std::uint8_t *) mapped_data)[src_y * fb->pitches[0]], src_h * fb->pitches[0]); memcpy(captured_cursor.pixels.data(), &((std::uint8_t *) mapped_data)[src_y * fb->pitches[0]], src_h * fb->pitches[0]);
} } else {
else {
// Copy row by row to deal with mismatched pitch or an X offset // Copy row by row to deal with mismatched pitch or an X offset
auto pixel_dst = captured_cursor.pixels.data(); auto pixel_dst = captured_cursor.pixels.data();
for (int y = 0; y < src_h; y++) { for (int y = 0; y < src_h; y++) {
@@ -1068,8 +1036,7 @@ namespace platf {
} }
} }
inline capture_e inline capture_e refresh(file_t *file, egl::surface_descriptor_t *sd, std::optional<std::chrono::steady_clock::time_point> &frame_timestamp) {
refresh(file_t *file, egl::surface_descriptor_t *sd, std::optional<std::chrono::steady_clock::time_point> &frame_timestamp) {
// Check for a change in HDR metadata // Check for a change in HDR metadata
if (connector_id) { if (connector_id) {
auto connector_props = card.connector_props(*connector_id); auto connector_props = card.connector_props(*connector_id);
@@ -1123,7 +1090,8 @@ namespace platf {
if ( if (
fb->width != img_width || fb->width != img_width ||
fb->height != img_height) { fb->height != img_height
) {
return capture_e::reinit; return capture_e::reinit;
} }
@@ -1155,10 +1123,10 @@ namespace platf {
class display_ram_t: public display_t { class display_ram_t: public display_t {
public: public:
display_ram_t(mem_type_e mem_type): display_ram_t(mem_type_e mem_type):
display_t(mem_type) {} display_t(mem_type) {
}
int int init(const std::string &display_name, const ::video::config_t &config) {
init(const std::string &display_name, const ::video::config_t &config) {
if (!gbm::create_device) { if (!gbm::create_device) {
BOOST_LOG(warning) << "libgbm not initialized"sv; BOOST_LOG(warning) << "libgbm not initialized"sv;
return -1; return -1;
@@ -1189,8 +1157,7 @@ namespace platf {
return 0; return 0;
} }
capture_e capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, 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(); auto next_frame = std::chrono::steady_clock::now();
sleep_overshoot_logger.reset(); sleep_overshoot_logger.reset();
@@ -1235,8 +1202,7 @@ namespace platf {
return capture_e::ok; return capture_e::ok;
} }
std::unique_ptr<avcodec_encode_device_t> std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI #ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == mem_type_e::vaapi) { if (mem_type == mem_type_e::vaapi) {
return va::make_avcodec_encode_device(width, height, false); return va::make_avcodec_encode_device(width, height, false);
@@ -1252,8 +1218,7 @@ namespace platf {
return std::make_unique<avcodec_encode_device_t>(); return std::make_unique<avcodec_encode_device_t>();
} }
void void blend_cursor(img_t &img) {
blend_cursor(img_t &img) {
// TODO: Cursor scaling is not supported in this codepath. // TODO: Cursor scaling is not supported in this codepath.
// We always draw the cursor at the source size. // We always draw the cursor at the source size.
auto pixels = (int *) img.data; auto pixels = (int *) img.data;
@@ -1290,8 +1255,7 @@ namespace platf {
auto alpha = (*(uint *) &cursor_pixel) >> 24u; auto alpha = (*(uint *) &cursor_pixel) >> 24u;
if (alpha == 255) { if (alpha == 255) {
*pixels_begin = cursor_pixel; *pixels_begin = cursor_pixel;
} } else {
else {
auto colors_out = (uint8_t *) &cursor_pixel; auto colors_out = (uint8_t *) &cursor_pixel;
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255; colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
@@ -1302,8 +1266,7 @@ namespace platf {
} }
} }
capture_e capture_e 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) {
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]; file_t fb_fd[4];
egl::surface_descriptor_t sd; egl::surface_descriptor_t sd;
@@ -1345,8 +1308,7 @@ namespace platf {
return capture_e::ok; return capture_e::ok;
} }
std::shared_ptr<img_t> std::shared_ptr<img_t> alloc_img() override {
alloc_img() override {
auto img = std::make_shared<kms_img_t>(); auto img = std::make_shared<kms_img_t>();
img->width = width; img->width = width;
img->height = height; img->height = height;
@@ -1357,8 +1319,7 @@ namespace platf {
return img; return img;
} }
int int dummy_img(platf::img_t *img) override {
dummy_img(platf::img_t *img) override {
return 0; return 0;
} }
@@ -1370,10 +1331,10 @@ namespace platf {
class display_vram_t: public display_t { class display_vram_t: public display_t {
public: public:
display_vram_t(mem_type_e mem_type): display_vram_t(mem_type_e mem_type):
display_t(mem_type) {} display_t(mem_type) {
}
std::unique_ptr<avcodec_encode_device_t> std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI #ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == mem_type_e::vaapi) { if (mem_type == mem_type_e::vaapi) {
return va::make_avcodec_encode_device(width, height, dup(card.render_fd.el), img_offset_x, img_offset_y, true); return va::make_avcodec_encode_device(width, height, dup(card.render_fd.el), img_offset_x, img_offset_y, true);
@@ -1390,8 +1351,7 @@ namespace platf {
return nullptr; return nullptr;
} }
std::shared_ptr<img_t> std::shared_ptr<img_t> alloc_img() override {
alloc_img() override {
auto img = std::make_shared<egl::img_descriptor_t>(); auto img = std::make_shared<egl::img_descriptor_t>();
img->width = width; img->width = width;
@@ -1406,14 +1366,12 @@ namespace platf {
return img; return img;
} }
int int dummy_img(platf::img_t *img) override {
dummy_img(platf::img_t *img) override {
// Empty images are recognized as dummies by the zero sequence number // Empty images are recognized as dummies by the zero sequence number
return 0; return 0;
} }
capture_e capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, 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(); auto next_frame = std::chrono::steady_clock::now();
sleep_overshoot_logger.reset(); sleep_overshoot_logger.reset();
@@ -1458,8 +1416,7 @@ namespace platf {
return capture_e::ok; return capture_e::ok;
} }
capture_e capture_e 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) {
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]; file_t fb_fd[4];
if (!pull_free_image_cb(img_out)) { if (!pull_free_image_cb(img_out)) {
@@ -1491,8 +1448,7 @@ namespace platf {
img->pixel_pitch = 4; img->pixel_pitch = 4;
img->row_pitch = img->pixel_pitch * img->width; img->row_pitch = img->pixel_pitch * img->width;
img->data = img->buffer.data(); img->data = img->buffer.data();
} } else {
else {
img->data = nullptr; img->data = nullptr;
} }
@@ -1502,8 +1458,7 @@ namespace platf {
return capture_e::ok; return capture_e::ok;
} }
int int init(const std::string &display_name, const ::video::config_t &config) {
init(const std::string &display_name, const ::video::config_t &config) {
if (display_t::init(display_name, config)) { if (display_t::init(display_name, config)) {
return -1; return -1;
} }
@@ -1530,8 +1485,7 @@ namespace platf {
} // namespace kms } // namespace kms
std::shared_ptr<display_t> std::shared_ptr<display_t> kms_display(mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
kms_display(mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
if (hwdevice_type == mem_type_e::vaapi || hwdevice_type == mem_type_e::cuda) { if (hwdevice_type == mem_type_e::vaapi || hwdevice_type == mem_type_e::cuda) {
auto disp = std::make_shared<kms::display_vram_t>(hwdevice_type); auto disp = std::make_shared<kms::display_vram_t>(hwdevice_type);
@@ -1561,8 +1515,7 @@ namespace platf {
* *
* This is an ugly hack :( * This is an ugly hack :(
*/ */
void void correlate_to_wayland(std::vector<kms::card_descriptor_t> &cds) {
correlate_to_wayland(std::vector<kms::card_descriptor_t> &cds) {
auto monitors = wl::monitors(); auto monitors = wl::monitors();
BOOST_LOG(info) << "-------- Start of KMS monitor list --------"sv; BOOST_LOG(info) << "-------- Start of KMS monitor list --------"sv;
@@ -1578,8 +1531,7 @@ namespace platf {
std::uint32_t index; std::uint32_t index;
if (index_begin == std::string_view::npos) { if (index_begin == std::string_view::npos) {
index = 1; index = 1;
} } else {
else {
index = std::max<int64_t>(1, util::from_view(name.substr(index_begin + 1))); index = std::max<int64_t>(1, util::from_view(name.substr(index_begin + 1)));
} }
@@ -1594,7 +1546,8 @@ namespace platf {
// A sanity check, it's guesswork after all. // A sanity check, it's guesswork after all.
if ( if (
monitor_descriptor.viewport.width != monitor->viewport.width || monitor_descriptor.viewport.width != monitor->viewport.width ||
monitor_descriptor.viewport.height != monitor->viewport.height) { monitor_descriptor.viewport.height != monitor->viewport.height
) {
BOOST_LOG(warning) BOOST_LOG(warning)
<< "Mismatch on expected Resolution compared to actual resolution: "sv << "Mismatch on expected Resolution compared to actual resolution: "sv
<< monitor_descriptor.viewport.width << 'x' << monitor_descriptor.viewport.height << monitor_descriptor.viewport.width << 'x' << monitor_descriptor.viewport.height
@@ -1616,8 +1569,7 @@ namespace platf {
} }
// A list of names of displays accepted as display_name // A list of names of displays accepted as display_name
std::vector<std::string> std::vector<std::string> kms_display_names(mem_type_e hwdevice_type) {
kms_display_names(mem_type_e hwdevice_type) {
int count = 0; int count = 0;
if (!fs::exists("/dev/dri")) { if (!fs::exists("/dev/dri")) {
@@ -1635,12 +1587,12 @@ namespace platf {
std::vector<kms::card_descriptor_t> cds; std::vector<kms::card_descriptor_t> cds;
std::vector<std::string> display_names; std::vector<std::string> display_names;
fs::path card_dir { "/dev/dri"sv }; fs::path card_dir {"/dev/dri"sv};
for (auto &entry : fs::directory_iterator { card_dir }) { for (auto &entry : fs::directory_iterator {card_dir}) {
auto file = entry.path().filename(); auto file = entry.path().filename();
auto filestring = file.generic_string(); auto filestring = file.generic_string();
if (std::string_view { filestring }.substr(0, 4) != "card"sv) { if (std::string_view {filestring}.substr(0, 4) != "card"sv) {
continue; continue;
} }
@@ -1655,8 +1607,7 @@ namespace platf {
BOOST_LOG(debug) << file << " is not a CUDA device"sv; BOOST_LOG(debug) << file << " is not a CUDA device"sv;
if (config::video.encoder == "nvenc") { if (config::video.encoder == "nvenc") {
BOOST_LOG(warning) << "Using NVENC with your display connected to a different GPU may not work properly!"sv; BOOST_LOG(warning) << "Using NVENC with your display connected to a different GPU may not work properly!"sv;
} } else {
else {
continue; continue;
} }
} }

View File

@@ -12,16 +12,18 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
// lib includes // platform includes
#include <arpa/inet.h> #include <arpa/inet.h>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/host_name.hpp>
#include <boost/process/v1.hpp>
#include <dlfcn.h> #include <dlfcn.h>
#include <fcntl.h>
#include <ifaddrs.h> #include <ifaddrs.h>
#include <netinet/udp.h> #include <netinet/udp.h>
#include <pwd.h> #include <pwd.h>
// lib includes
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/host_name.hpp>
#include <boost/process/v1.hpp>
#include <fcntl.h>
#include <unistd.h> #include <unistd.h>
// local includes // local includes
@@ -48,8 +50,7 @@ namespace bp = boost::process;
window_system_e window_system; window_system_e window_system;
namespace dyn { namespace dyn {
void * void *handle(const std::vector<const char *> &libs) {
handle(const std::vector<const char *> &libs) {
void *handle; void *handle;
for (auto lib : libs) { for (auto lib : libs) {
@@ -72,8 +73,7 @@ namespace dyn {
return nullptr; return nullptr;
} }
int int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
int err = 0; int err = 0;
for (auto &func : funcs) { for (auto &func : funcs) {
TUPLE_2D_REF(fn, name, func); TUPLE_2D_REF(fn, name, func);
@@ -90,16 +90,16 @@ namespace dyn {
return err; return err;
} }
} // namespace dyn } // namespace dyn
namespace platf { namespace platf {
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>; using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
ifaddr_t ifaddr_t get_ifaddrs() {
get_ifaddrs() { ifaddrs *p {nullptr};
ifaddrs *p { nullptr };
getifaddrs(&p); getifaddrs(&p);
return ifaddr_t { p }; return ifaddr_t {p};
} }
/** /**
@@ -107,8 +107,7 @@ namespace platf {
* @details This is used for the log directory, so it cannot invoke Boost logging! * @details This is used for the log directory, so it cannot invoke Boost logging!
* @return The path of the appdata directory that should be used. * @return The path of the appdata directory that should be used.
*/ */
fs::path fs::path appdata() {
appdata() {
static std::once_flag migration_flag; static std::once_flag migration_flag;
static fs::path config_path; static fs::path config_path;
@@ -174,8 +173,7 @@ namespace platf {
std::cerr << "Migration failed: " << ec.message() << std::endl; std::cerr << "Migration failed: " << ec.message() << std::endl;
config_path = old_config_path; config_path = old_config_path;
} }
} } else {
else {
// We cannot use Boost logging because it hasn't been initialized yet! // We cannot use Boost logging because it hasn't been initialized yet!
std::cerr << "Config exists in both "sv << old_config_path << " and "sv << config_path << ". Using "sv << config_path << " for config" << std::endl; std::cerr << "Config exists in both "sv << old_config_path << " and "sv << config_path << ". Using "sv << config_path << " for config" << std::endl;
std::cerr << "It is recommended to remove "sv << old_config_path << std::endl; std::cerr << "It is recommended to remove "sv << old_config_path << std::endl;
@@ -187,45 +185,36 @@ namespace platf {
return config_path; return config_path;
} }
std::string std::string from_sockaddr(const sockaddr *const ip_addr) {
from_sockaddr(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN] = {}; char data[INET6_ADDRSTRLEN] = {};
auto family = ip_addr->sa_family; auto family = ip_addr->sa_family;
if (family == AF_INET6) { if (family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
INET6_ADDRSTRLEN); } else if (family == AF_INET) {
} inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
else if (family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
} }
return std::string { data }; return std::string {data};
} }
std::pair<std::uint16_t, std::string> std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
from_sockaddr_ex(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN] = {}; char data[INET6_ADDRSTRLEN] = {};
auto family = ip_addr->sa_family; auto family = ip_addr->sa_family;
std::uint16_t port = 0; std::uint16_t port = 0;
if (family == AF_INET6) { if (family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
INET6_ADDRSTRLEN);
port = ((sockaddr_in6 *) ip_addr)->sin6_port; port = ((sockaddr_in6 *) ip_addr)->sin6_port;
} } else if (family == AF_INET) {
else if (family == AF_INET) { inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
port = ((sockaddr_in *) ip_addr)->sin_port; port = ((sockaddr_in *) ip_addr)->sin_port;
} }
return { port, std::string { data } }; return {port, std::string {data}};
} }
std::string std::string get_mac_address(const std::string_view &address) {
get_mac_address(const std::string_view &address) {
auto ifaddrs = get_ifaddrs(); auto ifaddrs = get_ifaddrs();
for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) { if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) {
@@ -242,8 +231,7 @@ namespace platf {
return "00:00:00:00:00:00"s; return "00:00:00:00:00:00"s;
} }
std::string std::string get_local_ip_for_gateway() {
get_local_ip_for_gateway() {
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (fd < 0) { if (fd < 0) {
BOOST_LOG(warning) << "Socket creation failed: " << strerror(errno); BOOST_LOG(warning) << "Socket creation failed: " << strerror(errno);
@@ -323,8 +311,7 @@ get_local_ip_for_gateway() {
return local_ip; return local_ip;
} }
bp::child bp::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
// clang-format off // clang-format off
if (!group) { if (!group) {
if (!file) { if (!file) {
@@ -349,8 +336,7 @@ get_local_ip_for_gateway() {
* @brief Open a url in the default web browser. * @brief Open a url in the default web browser.
* @param url The url to open. * @param url The url to open.
*/ */
void void open_url(const std::string &url) {
open_url(const std::string &url) {
// set working dir to user home directory // set working dir to user home directory
auto working_dir = boost::filesystem::path(std::getenv("HOME")); auto working_dir = boost::filesystem::path(std::getenv("HOME"));
std::string cmd = R"(xdg-open ")" + url + R"(")"; std::string cmd = R"(xdg-open ")" + url + R"(")";
@@ -360,30 +346,25 @@ get_local_ip_for_gateway() {
auto child = run_command(false, false, cmd, working_dir, _env, nullptr, ec, nullptr); auto child = run_command(false, false, cmd, working_dir, _env, nullptr, ec, nullptr);
if (ec) { if (ec) {
BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message(); BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message();
} } else {
else {
BOOST_LOG(info) << "Opened url ["sv << url << "]"sv; BOOST_LOG(info) << "Opened url ["sv << url << "]"sv;
child.detach(); child.detach();
} }
} }
void void adjust_thread_priority(thread_priority_e priority) {
adjust_thread_priority(thread_priority_e priority) {
// Unimplemented // Unimplemented
} }
void void streaming_will_start() {
streaming_will_start() {
// Nothing to do // Nothing to do
} }
void void streaming_will_stop() {
streaming_will_stop() {
// Nothing to do // Nothing to do
} }
void void restart_on_exit() {
restart_on_exit() {
char executable[PATH_MAX]; char executable[PATH_MAX];
ssize_t len = readlink("/proc/self/exe", executable, PATH_MAX - 1); ssize_t len = readlink("/proc/self/exe", executable, PATH_MAX - 1);
if (len == -1) { if (len == -1) {
@@ -405,42 +386,35 @@ get_local_ip_for_gateway() {
} }
} }
void void restart() {
restart() {
// Gracefully clean up and restart ourselves instead of exiting // Gracefully clean up and restart ourselves instead of exiting
atexit(restart_on_exit); atexit(restart_on_exit);
lifetime::exit_sunshine(0, true); lifetime::exit_sunshine(0, true);
} }
int int set_env(const std::string &name, const std::string &value) {
set_env(const std::string &name, const std::string &value) {
return setenv(name.c_str(), value.c_str(), 1); return setenv(name.c_str(), value.c_str(), 1);
} }
int int unset_env(const std::string &name) {
unset_env(const std::string &name) {
return unsetenv(name.c_str()); return unsetenv(name.c_str());
} }
bool bool request_process_group_exit(std::uintptr_t native_handle) {
request_process_group_exit(std::uintptr_t native_handle) {
if (kill(-((pid_t) native_handle), SIGTERM) == 0 || errno == ESRCH) { if (kill(-((pid_t) native_handle), SIGTERM) == 0 || errno == ESRCH) {
BOOST_LOG(debug) << "Successfully sent SIGTERM to process group: "sv << native_handle; BOOST_LOG(debug) << "Successfully sent SIGTERM to process group: "sv << native_handle;
return true; return true;
} } else {
else {
BOOST_LOG(warning) << "Unable to send SIGTERM to process group ["sv << native_handle << "]: "sv << errno; BOOST_LOG(warning) << "Unable to send SIGTERM to process group ["sv << native_handle << "]: "sv << errno;
return false; return false;
} }
} }
bool bool process_group_running(std::uintptr_t native_handle) {
process_group_running(std::uintptr_t native_handle) {
return waitpid(-((pid_t) native_handle), nullptr, WNOHANG) >= 0; return waitpid(-((pid_t) native_handle), nullptr, WNOHANG) >= 0;
} }
struct sockaddr_in struct sockaddr_in to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
struct sockaddr_in saddr_v4 = {}; struct sockaddr_in saddr_v4 = {};
saddr_v4.sin_family = AF_INET; saddr_v4.sin_family = AF_INET;
@@ -452,8 +426,7 @@ get_local_ip_for_gateway() {
return saddr_v4; return saddr_v4;
} }
struct sockaddr_in6 struct sockaddr_in6 to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
struct sockaddr_in6 saddr_v6 = {}; struct sockaddr_in6 saddr_v6 = {};
saddr_v6.sin6_family = AF_INET6; saddr_v6.sin6_family = AF_INET6;
@@ -466,8 +439,7 @@ get_local_ip_for_gateway() {
return saddr_v6; return saddr_v6;
} }
bool bool send_batch(batched_send_info_t &send_info) {
send_batch(batched_send_info_t &send_info) {
auto sockfd = (int) send_info.native_socket; auto sockfd = (int) send_info.native_socket;
struct msghdr msg = {}; struct msghdr msg = {};
@@ -479,8 +451,7 @@ get_local_ip_for_gateway() {
msg.msg_name = (struct sockaddr *) &taddr_v6; msg.msg_name = (struct sockaddr *) &taddr_v6;
msg.msg_namelen = sizeof(taddr_v6); msg.msg_namelen = sizeof(taddr_v6);
} } else {
else {
taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port); taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
msg.msg_name = (struct sockaddr *) &taddr_v4; msg.msg_name = (struct sockaddr *) &taddr_v4;
@@ -488,10 +459,10 @@ get_local_ip_for_gateway() {
} }
union { union {
char buf[CMSG_SPACE(sizeof(uint16_t)) + char buf[CMSG_SPACE(sizeof(uint16_t)) + std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
struct cmsghdr alignment; struct cmsghdr alignment;
} cmbuf = {}; // Must be zeroed for CMSG_NXTHDR() } cmbuf = {}; // Must be zeroed for CMSG_NXTHDR()
socklen_t cmbuflen = 0; socklen_t cmbuflen = 0;
msg.msg_control = cmbuf.buf; msg.msg_control = cmbuf.buf;
@@ -513,8 +484,7 @@ get_local_ip_for_gateway() {
pktinfo_cm->cmsg_type = IPV6_PKTINFO; pktinfo_cm->cmsg_type = IPV6_PKTINFO;
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo)); pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo));
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo)); memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
} } else {
else {
struct in_pktinfo pktInfo; struct in_pktinfo pktInfo;
struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0); struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0);
@@ -552,8 +522,7 @@ get_local_ip_for_gateway() {
iovs[iovlen].iov_len = send_info.payload_size; iovs[iovlen].iov_len = send_info.payload_size;
iovlen++; iovlen++;
} }
} } else {
else {
// Translate buffer descriptors into iovs // Translate buffer descriptors into iovs
auto payload_offset = (send_info.block_offset + seg_index) * send_info.payload_size; auto payload_offset = (send_info.block_offset + seg_index) * send_info.payload_size;
auto payload_length = payload_offset + (segs_in_batch * send_info.payload_size); auto payload_length = payload_offset + (segs_in_batch * send_info.payload_size);
@@ -579,8 +548,7 @@ get_local_ip_for_gateway() {
cm->cmsg_type = UDP_SEGMENT; cm->cmsg_type = UDP_SEGMENT;
cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
*((uint16_t *) CMSG_DATA(cm)) = msg_size; *((uint16_t *) CMSG_DATA(cm)) = msg_size;
} } else {
else {
msg.msg_controllen = cmbuflen; msg.msg_controllen = cmbuflen;
} }
@@ -676,8 +644,7 @@ get_local_ip_for_gateway() {
} }
} }
bool bool send(send_info_t &send_info) {
send(send_info_t &send_info) {
auto sockfd = (int) send_info.native_socket; auto sockfd = (int) send_info.native_socket;
struct msghdr msg = {}; struct msghdr msg = {};
@@ -689,8 +656,7 @@ get_local_ip_for_gateway() {
msg.msg_name = (struct sockaddr *) &taddr_v6; msg.msg_name = (struct sockaddr *) &taddr_v6;
msg.msg_namelen = sizeof(taddr_v6); msg.msg_namelen = sizeof(taddr_v6);
} } else {
else {
taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port); taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
msg.msg_name = (struct sockaddr *) &taddr_v4; msg.msg_name = (struct sockaddr *) &taddr_v4;
@@ -701,6 +667,7 @@ get_local_ip_for_gateway() {
char buf[std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))]; char buf[std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
struct cmsghdr alignment; struct cmsghdr alignment;
} cmbuf; } cmbuf;
socklen_t cmbuflen = 0; socklen_t cmbuflen = 0;
msg.msg_control = cmbuf.buf; msg.msg_control = cmbuf.buf;
@@ -720,8 +687,7 @@ get_local_ip_for_gateway() {
pktinfo_cm->cmsg_type = IPV6_PKTINFO; pktinfo_cm->cmsg_type = IPV6_PKTINFO;
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo)); pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo));
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo)); memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
} } else {
else {
struct in_pktinfo pktInfo; struct in_pktinfo pktInfo;
struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0); struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0);
@@ -786,7 +752,8 @@ get_local_ip_for_gateway() {
class qos_t: public deinit_t { class qos_t: public deinit_t {
public: public:
qos_t(int sockfd, std::vector<std::tuple<int, int, int>> options): qos_t(int sockfd, std::vector<std::tuple<int, int, int>> options):
sockfd(sockfd), options(options) { sockfd(sockfd),
options(options) {
qos_ref_count++; qos_ref_count++;
} }
@@ -814,8 +781,7 @@ get_local_ip_for_gateway() {
* @param data_type The type of traffic sent on this socket. * @param data_type The type of traffic sent on this socket.
* @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic.
*/ */
std::unique_ptr<deinit_t> 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, bool dscp_tagging) {
enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) {
int sockfd = (int) native_socket; int sockfd = (int) native_socket;
std::vector<std::tuple<int, int, int>> reset_options; std::vector<std::tuple<int, int, int>> reset_options;
@@ -828,8 +794,7 @@ get_local_ip_for_gateway() {
if (address.is_v6() && !address.to_v6().is_v4_mapped()) { if (address.is_v6() && !address.to_v6().is_v4_mapped()) {
level = SOL_IPV6; level = SOL_IPV6;
option = IPV6_TCLASS; option = IPV6_TCLASS;
} } else {
else {
level = SOL_IP; level = SOL_IP;
option = IP_TOS; option = IP_TOS;
} }
@@ -856,8 +821,7 @@ get_local_ip_for_gateway() {
if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) == 0) { if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) == 0) {
// Reset TOS to -1 when QoS is disabled // Reset TOS to -1 when QoS is disabled
reset_options.emplace_back(std::make_tuple(level, option, -1)); reset_options.emplace_back(std::make_tuple(level, option, -1));
} } else {
else {
BOOST_LOG(error) << "Failed to set TOS/TCLASS: "sv << errno; BOOST_LOG(error) << "Failed to set TOS/TCLASS: "sv << errno;
} }
} }
@@ -873,20 +837,17 @@ get_local_ip_for_gateway() {
if (setsockopt(sockfd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)) == 0) { if (setsockopt(sockfd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)) == 0) {
// Reset SO_PRIORITY to 0 when QoS is disabled // Reset SO_PRIORITY to 0 when QoS is disabled
reset_options.emplace_back(std::make_tuple(SOL_SOCKET, SO_PRIORITY, 0)); reset_options.emplace_back(std::make_tuple(SOL_SOCKET, SO_PRIORITY, 0));
} } else {
else {
BOOST_LOG(error) << "Failed to set SO_PRIORITY: "sv << errno; BOOST_LOG(error) << "Failed to set SO_PRIORITY: "sv << errno;
} }
return std::make_unique<qos_t>(sockfd, reset_options); return std::make_unique<qos_t>(sockfd, reset_options);
} }
std::string std::string get_host_name() {
get_host_name() {
try { try {
return boost::asio::ip::host_name(); return boost::asio::ip::host_name();
} } catch (boost::system::system_error &err) {
catch (boost::system::system_error &err) {
BOOST_LOG(error) << "Failed to get hostname: "sv << err.what(); BOOST_LOG(error) << "Failed to get hostname: "sv << err.what();
return "Sunshine"s; return "Sunshine"s;
} }
@@ -913,67 +874,62 @@ get_local_ip_for_gateway() {
static std::bitset<source::MAX_FLAGS> sources; static std::bitset<source::MAX_FLAGS> sources;
#ifdef SUNSHINE_BUILD_CUDA #ifdef SUNSHINE_BUILD_CUDA
std::vector<std::string> std::vector<std::string> nvfbc_display_names();
nvfbc_display_names(); std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
std::shared_ptr<display_t>
nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
bool bool verify_nvfbc() {
verify_nvfbc() {
return !nvfbc_display_names().empty(); return !nvfbc_display_names().empty();
} }
#endif #endif
#ifdef SUNSHINE_BUILD_WAYLAND #ifdef SUNSHINE_BUILD_WAYLAND
std::vector<std::string> std::vector<std::string> wl_display_names();
wl_display_names(); std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
std::shared_ptr<display_t>
wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
bool bool verify_wl() {
verify_wl() {
return window_system == window_system_e::WAYLAND && !wl_display_names().empty(); return window_system == window_system_e::WAYLAND && !wl_display_names().empty();
} }
#endif #endif
#ifdef SUNSHINE_BUILD_DRM #ifdef SUNSHINE_BUILD_DRM
std::vector<std::string> std::vector<std::string> kms_display_names(mem_type_e hwdevice_type);
kms_display_names(mem_type_e hwdevice_type); std::shared_ptr<display_t> kms_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
std::shared_ptr<display_t>
kms_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
bool bool verify_kms() {
verify_kms() {
return !kms_display_names(mem_type_e::unknown).empty(); return !kms_display_names(mem_type_e::unknown).empty();
} }
#endif #endif
#ifdef SUNSHINE_BUILD_X11 #ifdef SUNSHINE_BUILD_X11
std::vector<std::string> std::vector<std::string> x11_display_names();
x11_display_names(); std::shared_ptr<display_t> x11_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
std::shared_ptr<display_t>
x11_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
bool bool verify_x11() {
verify_x11() {
return window_system == window_system_e::X11 && !x11_display_names().empty(); return window_system == window_system_e::X11 && !x11_display_names().empty();
} }
#endif #endif
std::vector<std::string> std::vector<std::string> display_names(mem_type_e hwdevice_type) {
display_names(mem_type_e hwdevice_type) {
#ifdef SUNSHINE_BUILD_CUDA #ifdef SUNSHINE_BUILD_CUDA
// display using NvFBC only supports mem_type_e::cuda // display using NvFBC only supports mem_type_e::cuda
if (sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) return nvfbc_display_names(); if (sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) {
return nvfbc_display_names();
}
#endif #endif
#ifdef SUNSHINE_BUILD_WAYLAND #ifdef SUNSHINE_BUILD_WAYLAND
if (sources[source::WAYLAND]) return wl_display_names(); if (sources[source::WAYLAND]) {
return wl_display_names();
}
#endif #endif
#ifdef SUNSHINE_BUILD_DRM #ifdef SUNSHINE_BUILD_DRM
if (sources[source::KMS]) return kms_display_names(hwdevice_type); if (sources[source::KMS]) {
return kms_display_names(hwdevice_type);
}
#endif #endif
#ifdef SUNSHINE_BUILD_X11 #ifdef SUNSHINE_BUILD_X11
if (sources[source::X11]) return x11_display_names(); if (sources[source::X11]) {
return x11_display_names();
}
#endif #endif
return {}; return {};
} }
@@ -982,14 +938,12 @@ get_local_ip_for_gateway() {
* @brief Returns if GPUs/drivers have changed since the last call to this function. * @brief Returns if GPUs/drivers have changed since the last call to this function.
* @return `true` if a change has occurred or if it is unknown whether a change occurred. * @return `true` if a change has occurred or if it is unknown whether a change occurred.
*/ */
bool bool needs_encoder_reenumeration() {
needs_encoder_reenumeration() {
// We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on Linux. // We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on Linux.
return true; return true;
} }
std::shared_ptr<display_t> std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
#ifdef SUNSHINE_BUILD_CUDA #ifdef SUNSHINE_BUILD_CUDA
if (sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) { if (sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) {
BOOST_LOG(info) << "Screencasting with NvFBC"sv; BOOST_LOG(info) << "Screencasting with NvFBC"sv;
@@ -1018,8 +972,7 @@ get_local_ip_for_gateway() {
return nullptr; return nullptr;
} }
std::unique_ptr<deinit_t> std::unique_ptr<deinit_t> init() {
init() {
// enable low latency mode for AMD // enable low latency mode for AMD
// https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/30039 // https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/30039
set_env("AMD_DEBUG", "lowlatencyenc"); set_env("AMD_DEBUG", "lowlatencyenc");
@@ -1088,8 +1041,7 @@ get_local_ip_for_gateway() {
class linux_high_precision_timer: public high_precision_timer { class linux_high_precision_timer: public high_precision_timer {
public: public:
void void sleep_for(const std::chrono::nanoseconds &duration) override {
sleep_for(const std::chrono::nanoseconds &duration) override {
std::this_thread::sleep_for(duration); std::this_thread::sleep_for(duration);
} }
@@ -1098,8 +1050,7 @@ get_local_ip_for_gateway() {
} }
}; };
std::unique_ptr<high_precision_timer> std::unique_ptr<high_precision_timer> create_high_precision_timer() {
create_high_precision_timer() {
return std::make_unique<linux_high_precision_timer>(); return std::make_unique<linux_high_precision_timer>();
} }

View File

@@ -4,9 +4,11 @@
*/ */
#pragma once #pragma once
// standard includes
#include <unistd.h> #include <unistd.h>
#include <vector> #include <vector>
// local includes
#include "src/utility.h" #include "src/utility.h"
KITTY_USING_MOVE_T(file_t, int, -1, { KITTY_USING_MOVE_T(file_t, int, -1, {
@@ -26,9 +28,7 @@ extern window_system_e window_system;
namespace dyn { namespace dyn {
typedef void (*apiproc)(void); typedef void (*apiproc)(void);
int int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true); void *handle(const std::vector<const char *> &libs);
void *
handle(const std::vector<const char *> &libs);
} // namespace dyn } // namespace dyn

View File

@@ -3,8 +3,10 @@
* @brief Definitions for publishing services on Linux. * @brief Definitions for publishing services on Linux.
* @note Adapted from https://www.avahi.org/doxygen/html/client-publish-service_8c-example.html * @note Adapted from https://www.avahi.org/doxygen/html/client-publish-service_8c-example.html
*/ */
// standard includes
#include <thread> #include <thread>
// local includes
#include "misc.h" #include "misc.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/network.h" #include "src/network.h"
@@ -84,6 +86,7 @@ namespace avahi {
}; };
constexpr auto IF_UNSPEC = -1; constexpr auto IF_UNSPEC = -1;
enum proto { enum proto {
PROTO_INET = 0, ///< IPv4 PROTO_INET = 0, ///< IPv4
PROTO_INET6 = 1, ///< IPv6 PROTO_INET6 = 1, ///< IPv6
@@ -164,7 +167,8 @@ namespace avahi {
const char *domain, const char *domain,
const char *host, const char *host,
uint16_t port, uint16_t port,
...); ...
);
typedef int (*entry_group_is_empty_fn)(EntryGroup *); typedef int (*entry_group_is_empty_fn)(EntryGroup *);
typedef int (*entry_group_reset_fn)(EntryGroup *); typedef int (*entry_group_reset_fn)(EntryGroup *);
@@ -199,30 +203,31 @@ namespace avahi {
simple_poll_new_fn simple_poll_new; simple_poll_new_fn simple_poll_new;
simple_poll_free_fn simple_poll_free; simple_poll_free_fn simple_poll_free;
int int init_common() {
init_common() { static void *handle {nullptr};
static void *handle { nullptr };
static bool funcs_loaded = false; static bool funcs_loaded = false;
if (funcs_loaded) return 0; if (funcs_loaded) {
return 0;
}
if (!handle) { if (!handle) {
handle = dyn::handle({ "libavahi-common.so.3", "libavahi-common.so" }); handle = dyn::handle({"libavahi-common.so.3", "libavahi-common.so"});
if (!handle) { if (!handle) {
return -1; return -1;
} }
} }
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs { std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &alternative_service_name, "avahi_alternative_service_name" }, {(dyn::apiproc *) &alternative_service_name, "avahi_alternative_service_name"},
{ (dyn::apiproc *) &free, "avahi_free" }, {(dyn::apiproc *) &free, "avahi_free"},
{ (dyn::apiproc *) &strdup, "avahi_strdup" }, {(dyn::apiproc *) &strdup, "avahi_strdup"},
{ (dyn::apiproc *) &strerror, "avahi_strerror" }, {(dyn::apiproc *) &strerror, "avahi_strerror"},
{ (dyn::apiproc *) &simple_poll_get, "avahi_simple_poll_get" }, {(dyn::apiproc *) &simple_poll_get, "avahi_simple_poll_get"},
{ (dyn::apiproc *) &simple_poll_loop, "avahi_simple_poll_loop" }, {(dyn::apiproc *) &simple_poll_loop, "avahi_simple_poll_loop"},
{ (dyn::apiproc *) &simple_poll_quit, "avahi_simple_poll_quit" }, {(dyn::apiproc *) &simple_poll_quit, "avahi_simple_poll_quit"},
{ (dyn::apiproc *) &simple_poll_new, "avahi_simple_poll_new" }, {(dyn::apiproc *) &simple_poll_new, "avahi_simple_poll_new"},
{ (dyn::apiproc *) &simple_poll_free, "avahi_simple_poll_free" }, {(dyn::apiproc *) &simple_poll_free, "avahi_simple_poll_free"},
}; };
if (dyn::load(handle, funcs)) { if (dyn::load(handle, funcs)) {
@@ -233,34 +238,35 @@ namespace avahi {
return 0; return 0;
} }
int int init_client() {
init_client() {
if (init_common()) { if (init_common()) {
return -1; return -1;
} }
static void *handle { nullptr }; static void *handle {nullptr};
static bool funcs_loaded = false; static bool funcs_loaded = false;
if (funcs_loaded) return 0; if (funcs_loaded) {
return 0;
}
if (!handle) { if (!handle) {
handle = dyn::handle({ "libavahi-client.so.3", "libavahi-client.so" }); handle = dyn::handle({"libavahi-client.so.3", "libavahi-client.so"});
if (!handle) { if (!handle) {
return -1; return -1;
} }
} }
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs { std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &client_new, "avahi_client_new" }, {(dyn::apiproc *) &client_new, "avahi_client_new"},
{ (dyn::apiproc *) &client_free, "avahi_client_free" }, {(dyn::apiproc *) &client_free, "avahi_client_free"},
{ (dyn::apiproc *) &entry_group_get_client, "avahi_entry_group_get_client" }, {(dyn::apiproc *) &entry_group_get_client, "avahi_entry_group_get_client"},
{ (dyn::apiproc *) &entry_group_new, "avahi_entry_group_new" }, {(dyn::apiproc *) &entry_group_new, "avahi_entry_group_new"},
{ (dyn::apiproc *) &entry_group_add_service, "avahi_entry_group_add_service" }, {(dyn::apiproc *) &entry_group_add_service, "avahi_entry_group_add_service"},
{ (dyn::apiproc *) &entry_group_is_empty, "avahi_entry_group_is_empty" }, {(dyn::apiproc *) &entry_group_is_empty, "avahi_entry_group_is_empty"},
{ (dyn::apiproc *) &entry_group_reset, "avahi_entry_group_reset" }, {(dyn::apiproc *) &entry_group_reset, "avahi_entry_group_reset"},
{ (dyn::apiproc *) &entry_group_commit, "avahi_entry_group_commit" }, {(dyn::apiproc *) &entry_group_commit, "avahi_entry_group_commit"},
{ (dyn::apiproc *) &client_errno, "avahi_client_errno" }, {(dyn::apiproc *) &client_errno, "avahi_client_errno"},
}; };
if (dyn::load(handle, funcs)) { if (dyn::load(handle, funcs)) {
@@ -274,13 +280,12 @@ namespace avahi {
namespace platf::publish { namespace platf::publish {
template <class T> template<class T>
void void free(T *p) {
free(T *p) {
avahi::free(p); avahi::free(p);
} }
template <class T> template<class T>
using ptr_t = util::safe_ptr<T, free<T>>; using ptr_t = util::safe_ptr<T, free<T>>;
using client_t = util::dyn_safe_ptr<avahi::Client, &avahi::client_free>; using client_t = util::dyn_safe_ptr<avahi::Client, &avahi::client_free>;
using poll_t = util::dyn_safe_ptr<avahi::SimplePoll, &avahi::simple_poll_free>; using poll_t = util::dyn_safe_ptr<avahi::SimplePoll, &avahi::simple_poll_free>;
@@ -292,11 +297,9 @@ namespace platf::publish {
ptr_t<char> name; ptr_t<char> name;
void void create_services(avahi::Client *c);
create_services(avahi::Client *c);
void void entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
group = g; group = g;
switch (state) { switch (state) {
@@ -319,8 +322,7 @@ namespace platf::publish {
} }
} }
void void create_services(avahi::Client *c) {
create_services(avahi::Client *c) {
int ret; int ret;
auto fg = util::fail_guard([]() { auto fg = util::fail_guard([]() {
@@ -339,13 +341,16 @@ namespace platf::publish {
ret = avahi::entry_group_add_service( ret = avahi::entry_group_add_service(
group, group,
avahi::IF_UNSPEC, avahi::PROTO_UNSPEC, avahi::IF_UNSPEC,
avahi::PROTO_UNSPEC,
avahi::PublishFlags(0), avahi::PublishFlags(0),
name.get(), name.get(),
SERVICE_TYPE, SERVICE_TYPE,
nullptr, nullptr, nullptr,
nullptr,
net::map_port(nvhttp::PORT_HTTP), net::map_port(nvhttp::PORT_HTTP),
nullptr); nullptr
);
if (ret < 0) { if (ret < 0) {
if (ret == avahi::ERR_COLLISION) { if (ret == avahi::ERR_COLLISION) {
@@ -375,8 +380,7 @@ namespace platf::publish {
fg.disable(); fg.disable();
} }
void void client_callback(avahi::Client *c, avahi::ClientState state, void *) {
client_callback(avahi::Client *c, avahi::ClientState state, void *) {
switch (state) { switch (state) {
case avahi::CLIENT_S_RUNNING: case avahi::CLIENT_S_RUNNING:
create_services(c); create_services(c);
@@ -387,8 +391,9 @@ namespace platf::publish {
break; break;
case avahi::CLIENT_S_COLLISION: case avahi::CLIENT_S_COLLISION:
case avahi::CLIENT_S_REGISTERING: case avahi::CLIENT_S_REGISTERING:
if (group) if (group) {
avahi::entry_group_reset(group); avahi::entry_group_reset(group);
}
break; break;
case avahi::CLIENT_CONNECTING:; case avahi::CLIENT_CONNECTING:;
} }
@@ -399,7 +404,8 @@ namespace platf::publish {
std::thread poll_thread; std::thread poll_thread;
deinit_t(std::thread poll_thread): deinit_t(std::thread poll_thread):
poll_thread { std::move(poll_thread) } {} poll_thread {std::move(poll_thread)} {
}
~deinit_t() override { ~deinit_t() override {
if (avahi::simple_poll_quit && poll) { if (avahi::simple_poll_quit && poll) {
@@ -412,8 +418,7 @@ namespace platf::publish {
} }
}; };
[[nodiscard]] std::unique_ptr<::platf::deinit_t> [[nodiscard]] std::unique_ptr<::platf::deinit_t> start() {
start() {
if (avahi::init_client()) { if (avahi::init_client()) {
return nullptr; return nullptr;
} }
@@ -430,13 +435,14 @@ namespace platf::publish {
name.reset(avahi::strdup(instance_name.c_str())); name.reset(avahi::strdup(instance_name.c_str()));
client.reset( client.reset(
avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error)); avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error)
);
if (!client) { if (!client) {
BOOST_LOG(error) << "Failed to create client: "sv << avahi::strerror(avhi_error); BOOST_LOG(error) << "Failed to create client: "sv << avahi::strerror(avhi_error);
return nullptr; return nullptr;
} }
return std::make_unique<deinit_t>(std::thread { avahi::simple_poll_loop, poll.get() }); return std::make_unique<deinit_t>(std::thread {avahi::simple_poll_loop, poll.get()});
} }
} // namespace platf::publish } // namespace platf::publish

View File

@@ -2,28 +2,30 @@
* @file src/platform/linux/vaapi.cpp * @file src/platform/linux/vaapi.cpp
* @brief Definitions for VA-API hardware accelerated capture. * @brief Definitions for VA-API hardware accelerated capture.
*/ */
// standard includes
#include <fcntl.h>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <fcntl.h>
extern "C" { extern "C" {
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
#include <libavutil/pixdesc.h> #include <libavutil/pixdesc.h>
#include <va/va.h> #include <va/va.h>
#include <va/va_drm.h> #include <va/va_drm.h>
#if !VA_CHECK_VERSION(1, 9, 0) #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 VAStatus
vaSyncBuffer( vaSyncBuffer(
VADisplay dpy, VADisplay dpy,
VABufferID buf_id, VABufferID buf_id,
uint64_t timeout_ns) { uint64_t timeout_ns
) {
return VA_STATUS_ERROR_UNIMPLEMENTED; return VA_STATUS_ERROR_UNIMPLEMENTED;
} }
#endif #endif
} }
// local includes
#include "graphics.h" #include "graphics.h"
#include "misc.h" #include "misc.h"
#include "src/config.h" #include "src/config.h"
@@ -69,6 +71,7 @@ namespace va {
// Number of layers making up the surface. // Number of layers making up the surface.
uint32_t num_layers; uint32_t num_layers;
struct { struct {
// DRM format fourcc of this layer (DRM_FOURCC_*). // DRM format fourcc of this layer (DRM_FOURCC_*).
uint32_t drm_format; uint32_t drm_format;
@@ -89,13 +92,11 @@ namespace va {
using display_t = util::safe_ptr_v2<void, VAStatus, vaTerminate>; using display_t = util::safe_ptr_v2<void, VAStatus, vaTerminate>;
int int vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *encode_device, AVBufferRef **hw_device_buf);
vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *encode_device, AVBufferRef **hw_device_buf);
class va_t: public platf::avcodec_encode_device_t { class va_t: public platf::avcodec_encode_device_t {
public: public:
int int init(int in_width, int in_height, file_t &&render_device) {
init(int in_width, int in_height, file_t &&render_device) {
file = std::move(render_device); file = std::move(render_device);
if (!gbm::create_device) { if (!gbm::create_device) {
@@ -135,8 +136,7 @@ namespace va {
* @param profile The profile to match. * @param profile The profile to match.
* @return A valid encoding entrypoint or 0 on failure. * @return A valid encoding entrypoint or 0 on failure.
*/ */
VAEntrypoint VAEntrypoint select_va_entrypoint(VAProfile profile) {
select_va_entrypoint(VAProfile profile) {
std::vector<VAEntrypoint> entrypoints(vaMaxNumEntrypoints(va_display)); std::vector<VAEntrypoint> entrypoints(vaMaxNumEntrypoints(va_display));
int num_eps; int num_eps;
auto status = vaQueryConfigEntrypoints(va_display, profile, entrypoints.data(), &num_eps); auto status = vaQueryConfigEntrypoints(va_display, profile, entrypoints.data(), &num_eps);
@@ -166,8 +166,7 @@ namespace va {
* @param profile The profile to match. * @param profile The profile to match.
* @return Boolean value indicating if the profile is supported. * @return Boolean value indicating if the profile is supported.
*/ */
bool bool is_va_profile_supported(VAProfile profile) {
is_va_profile_supported(VAProfile profile) {
std::vector<VAProfile> profiles(vaMaxNumProfiles(va_display)); std::vector<VAProfile> profiles(vaMaxNumProfiles(va_display));
int num_profs; int num_profs;
auto status = vaQueryConfigProfiles(va_display, profiles.data(), &num_profs); auto status = vaQueryConfigProfiles(va_display, profiles.data(), &num_profs);
@@ -185,13 +184,11 @@ namespace va {
* @param ctx The FFmpeg codec context. * @param ctx The FFmpeg codec context.
* @return The matching VA profile or `VAProfileNone` on failure. * @return The matching VA profile or `VAProfileNone` on failure.
*/ */
VAProfile VAProfile get_va_profile(AVCodecContext *ctx) {
get_va_profile(AVCodecContext *ctx) {
if (ctx->codec_id == AV_CODEC_ID_H264) { if (ctx->codec_id == AV_CODEC_ID_H264) {
// There's no VAAPI profile for H.264 4:4:4 // There's no VAAPI profile for H.264 4:4:4
return VAProfileH264High; return VAProfileH264High;
} } else if (ctx->codec_id == AV_CODEC_ID_HEVC) {
else if (ctx->codec_id == AV_CODEC_ID_HEVC) {
switch (ctx->profile) { switch (ctx->profile) {
case FF_PROFILE_HEVC_REXT: case FF_PROFILE_HEVC_REXT:
switch (av_pix_fmt_desc_get(ctx->sw_pix_fmt)->comp[0].depth) { switch (av_pix_fmt_desc_get(ctx->sw_pix_fmt)->comp[0].depth) {
@@ -206,8 +203,7 @@ namespace va {
case FF_PROFILE_HEVC_MAIN: case FF_PROFILE_HEVC_MAIN:
return VAProfileHEVCMain; return VAProfileHEVCMain;
} }
} } else if (ctx->codec_id == AV_CODEC_ID_AV1) {
else if (ctx->codec_id == AV_CODEC_ID_AV1) {
switch (ctx->profile) { switch (ctx->profile) {
case FF_PROFILE_AV1_HIGH: case FF_PROFILE_AV1_HIGH:
return VAProfileAV1Profile1; return VAProfileAV1Profile1;
@@ -220,8 +216,7 @@ namespace va {
return VAProfileNone; return VAProfileNone;
} }
void void init_codec_options(AVCodecContext *ctx, AVDictionary **options) override {
init_codec_options(AVCodecContext *ctx, AVDictionary **options) override {
auto va_profile = get_va_profile(ctx); auto va_profile = get_va_profile(ctx);
if (va_profile == VAProfileNone || !is_va_profile_supported(va_profile)) { if (va_profile == VAProfileNone || !is_va_profile_supported(va_profile)) {
// Don't bother doing anything if the profile isn't supported // Don't bother doing anything if the profile isn't supported
@@ -239,19 +234,18 @@ namespace va {
if (va_entrypoint == VAEntrypointEncSliceLP) { if (va_entrypoint == VAEntrypointEncSliceLP) {
BOOST_LOG(info) << "Using LP encoding mode"sv; BOOST_LOG(info) << "Using LP encoding mode"sv;
av_dict_set_int(options, "low_power", 1, 0); av_dict_set_int(options, "low_power", 1, 0);
} } else {
else {
BOOST_LOG(info) << "Using normal encoding mode"sv; BOOST_LOG(info) << "Using normal encoding mode"sv;
} }
VAConfigAttrib rc_attr = { VAConfigAttribRateControl }; VAConfigAttrib rc_attr = {VAConfigAttribRateControl};
auto status = vaGetConfigAttributes(va_display, va_profile, va_entrypoint, &rc_attr, 1); auto status = vaGetConfigAttributes(va_display, va_profile, va_entrypoint, &rc_attr, 1);
if (status != VA_STATUS_SUCCESS) { if (status != VA_STATUS_SUCCESS) {
// Stick to the default rate control (CQP) // Stick to the default rate control (CQP)
rc_attr.value = 0; rc_attr.value = 0;
} }
VAConfigAttrib slice_attr = { VAConfigAttribEncMaxSlices }; VAConfigAttrib slice_attr = {VAConfigAttribEncMaxSlices};
status = vaGetConfigAttributes(va_display, va_profile, va_entrypoint, &slice_attr, 1); status = vaGetConfigAttributes(va_display, va_profile, va_entrypoint, &slice_attr, 1);
if (status != VA_STATUS_SUCCESS) { if (status != VA_STATUS_SUCCESS) {
// Assume only a single slice is supported // Assume only a single slice is supported
@@ -281,27 +275,22 @@ namespace va {
if (rc_attr.value & VA_RC_VBR) { if (rc_attr.value & VA_RC_VBR) {
BOOST_LOG(info) << "Using VBR with single frame VBV size"sv; BOOST_LOG(info) << "Using VBR with single frame VBV size"sv;
av_dict_set(options, "rc_mode", "VBR", 0); av_dict_set(options, "rc_mode", "VBR", 0);
} } else if (rc_attr.value & VA_RC_CBR) {
else if (rc_attr.value & VA_RC_CBR) {
BOOST_LOG(info) << "Using CBR with single frame VBV size"sv; BOOST_LOG(info) << "Using CBR with single frame VBV size"sv;
av_dict_set(options, "rc_mode", "CBR", 0); av_dict_set(options, "rc_mode", "CBR", 0);
} } else {
else {
BOOST_LOG(warning) << "Using CQP with single frame VBV size"sv; BOOST_LOG(warning) << "Using CQP with single frame VBV size"sv;
av_dict_set_int(options, "qp", config::video.qp, 0); av_dict_set_int(options, "qp", config::video.qp, 0);
} }
} } else if (!(rc_attr.value & (VA_RC_CBR | VA_RC_VBR))) {
else if (!(rc_attr.value & (VA_RC_CBR | VA_RC_VBR))) {
BOOST_LOG(warning) << "Using CQP rate control"sv; BOOST_LOG(warning) << "Using CQP rate control"sv;
av_dict_set_int(options, "qp", config::video.qp, 0); av_dict_set_int(options, "qp", config::video.qp, 0);
} } else {
else {
BOOST_LOG(info) << "Using default rate control"sv; BOOST_LOG(info) << "Using default rate control"sv;
} }
} }
int int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
this->hwframe.reset(frame); this->hwframe.reset(frame);
this->frame = frame; this->frame = frame;
@@ -321,7 +310,8 @@ namespace va {
surface, surface,
va::SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, va::SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
va::EXPORT_SURFACE_WRITE_ONLY | va::EXPORT_SURFACE_SEPARATE_LAYERS, va::EXPORT_SURFACE_WRITE_ONLY | va::EXPORT_SURFACE_SEPARATE_LAYERS,
&prime); &prime
);
if (status) { if (status) {
BOOST_LOG(error) << "Couldn't export va surface handle: ["sv << (int) surface << "]: "sv << vaErrorStr(status); BOOST_LOG(error) << "Couldn't export va surface handle: ["sv << (int) surface << "]: "sv << vaErrorStr(status);
@@ -377,8 +367,7 @@ namespace va {
return 0; return 0;
} }
void void apply_colorspace() override {
apply_colorspace() override {
sws.apply_colorspace(colorspace); sws.apply_colorspace(colorspace);
} }
@@ -401,8 +390,7 @@ namespace va {
class va_ram_t: public va_t { class va_ram_t: public va_t {
public: public:
int int convert(platf::img_t &img) override {
convert(platf::img_t &img) override {
sws.load_ram(img); sws.load_ram(img);
sws.convert(nv12->buf); sws.convert(nv12->buf);
@@ -412,15 +400,13 @@ namespace va {
class va_vram_t: public va_t { class va_vram_t: public va_t {
public: public:
int int convert(platf::img_t &img) override {
convert(platf::img_t &img) override {
auto &descriptor = (egl::img_descriptor_t &) img; auto &descriptor = (egl::img_descriptor_t &) img;
if (descriptor.sequence == 0) { if (descriptor.sequence == 0) {
// For dummy images, use a blank RGB texture instead of importing a DMA-BUF // For dummy images, use a blank RGB texture instead of importing a DMA-BUF
rgb = egl::create_blank(img); rgb = egl::create_blank(img);
} } else if (descriptor.sequence > sequence) {
else if (descriptor.sequence > sequence) {
sequence = descriptor.sequence; sequence = descriptor.sequence;
rgb = egl::rgb_t {}; rgb = egl::rgb_t {};
@@ -440,8 +426,7 @@ namespace va {
return 0; return 0;
} }
int int init(int in_width, int in_height, file_t &&render_device, int offset_x, int offset_y) {
init(int in_width, int in_height, file_t &&render_device, int offset_x, int offset_y) {
if (va_t::init(in_width, in_height, std::move(render_device))) { if (va_t::init(in_width, in_height, std::move(render_device))) {
return -1; return -1;
} }
@@ -471,6 +456,7 @@ namespace va {
void *xdisplay; void *xdisplay;
int fd; int fd;
} drm; } drm;
int drm_fd; int drm_fd;
} VAAPIDevicePriv; } VAAPIDevicePriv;
@@ -494,13 +480,11 @@ namespace va {
unsigned int driver_quirks; unsigned int driver_quirks;
} AVVAAPIDeviceContext; } AVVAAPIDeviceContext;
static void static void __log(void *level, const char *msg) {
__log(void *level, const char *msg) {
BOOST_LOG(*(boost::log::sources::severity_logger<int> *) level) << msg; BOOST_LOG(*(boost::log::sources::severity_logger<int> *) level) << msg;
} }
static void static void vaapi_hwdevice_ctx_free(AVHWDeviceContext *ctx) {
vaapi_hwdevice_ctx_free(AVHWDeviceContext *ctx) {
auto hwctx = (AVVAAPIDeviceContext *) ctx->hwctx; auto hwctx = (AVVAAPIDeviceContext *) ctx->hwctx;
auto priv = (VAAPIDevicePriv *) ctx->user_opaque; auto priv = (VAAPIDevicePriv *) ctx->user_opaque;
@@ -509,8 +493,7 @@ namespace va {
av_freep(&priv); av_freep(&priv);
} }
int int vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *base, AVBufferRef **hw_device_buf) {
vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *base, AVBufferRef **hw_device_buf) {
auto va = (va::va_t *) base; auto va = (va::va_t *) base;
auto fd = dup(va->file.el); auto fd = dup(va->file.el);
@@ -522,7 +505,7 @@ namespace va {
av_free(priv); av_free(priv);
}); });
va::display_t display { vaGetDisplayDRM(fd) }; va::display_t display {vaGetDisplayDRM(fd)};
if (!display) { if (!display) {
auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str(); auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str();
@@ -556,7 +539,7 @@ namespace va {
auto err = av_hwdevice_ctx_init(*hw_device_buf); auto err = av_hwdevice_ctx_init(*hw_device_buf);
if (err) { if (err) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
BOOST_LOG(error) << "Failed to create FFMpeg hardware device context: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); BOOST_LOG(error) << "Failed to create FFMpeg hardware device context: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return err; return err;
@@ -565,8 +548,7 @@ namespace va {
return 0; return 0;
} }
static bool static bool query(display_t::pointer display, VAProfile profile) {
query(display_t::pointer display, VAProfile profile) {
std::vector<VAEntrypoint> entrypoints; std::vector<VAEntrypoint> entrypoints;
entrypoints.resize(vaMaxNumEntrypoints(display)); entrypoints.resize(vaMaxNumEntrypoints(display));
@@ -587,15 +569,14 @@ namespace va {
return false; return false;
} }
bool bool validate(int fd) {
validate(int fd) { va::display_t display {vaGetDisplayDRM(fd)};
va::display_t display { vaGetDisplayDRM(fd) };
if (!display) { if (!display) {
char string[1024]; char string[1024];
auto bytes = readlink(("/proc/self/fd/" + std::to_string(fd)).c_str(), string, sizeof(string)); auto bytes = readlink(("/proc/self/fd/" + std::to_string(fd)).c_str(), string, sizeof(string));
std::string_view render_device { string, (std::size_t) bytes }; std::string_view render_device {string, (std::size_t) bytes};
BOOST_LOG(error) << "Couldn't open a va display from DRM with device: "sv << render_device; BOOST_LOG(error) << "Couldn't open a va display from DRM with device: "sv << render_device;
return false; return false;
@@ -623,8 +604,7 @@ namespace va {
return true; return true;
} }
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram) {
make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram) {
if (vram) { if (vram) {
auto egl = std::make_unique<va::va_vram_t>(); auto egl = std::make_unique<va::va_vram_t>();
if (egl->init(width, height, std::move(card), offset_x, offset_y)) { if (egl->init(width, height, std::move(card), offset_x, offset_y)) {
@@ -644,8 +624,7 @@ namespace va {
} }
} }
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram) {
make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram) {
auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str(); auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str();
file_t file = open(render_device, O_RDWR); file_t file = open(render_device, O_RDWR);
@@ -659,8 +638,7 @@ namespace va {
return make_avcodec_encode_device(width, height, std::move(file), offset_x, offset_y, vram); return make_avcodec_encode_device(width, height, std::move(file), offset_x, offset_y, vram);
} }
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram) {
make_avcodec_encode_device(int width, int height, bool vram) {
return make_avcodec_encode_device(width, height, 0, 0, vram); return make_avcodec_encode_device(width, height, 0, 0, vram);
} }
} // namespace va } // namespace va

View File

@@ -4,12 +4,14 @@
*/ */
#pragma once #pragma once
// local includes
#include "misc.h" #include "misc.h"
#include "src/platform/common.h" #include "src/platform/common.h"
namespace egl { namespace egl {
struct surface_descriptor_t; struct surface_descriptor_t;
} }
namespace va { namespace va {
/** /**
* Width --> Width of the image * Width --> Width of the image
@@ -18,14 +20,10 @@ namespace va {
* offset_y --> Vertical 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 * file_t card --> The file descriptor of the render device used for encoding
*/ */
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram);
make_avcodec_encode_device(int width, int height, bool vram); std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram);
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram);
make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram);
std::unique_ptr<platf::avcodec_encode_device_t>
make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram);
// Ensure the render device pointed to by fd is capable of encoding h264 with the hevc_mode configured // Ensure the render device pointed to by fd is capable of encoding h264 with the hevc_mode configured
bool bool validate(int fd);
validate(int fd);
} // namespace va } // namespace va

View File

@@ -2,12 +2,15 @@
* @file src/platform/linux/wayland.cpp * @file src/platform/linux/wayland.cpp
* @brief Definitions for Wayland capture. * @brief Definitions for Wayland capture.
*/ */
// standard includes
#include <cstdlib>
// platform includes
#include <poll.h> #include <poll.h>
#include <wayland-client.h> #include <wayland-client.h>
#include <wayland-util.h> #include <wayland-util.h>
#include <cstdlib> // local includes
#include "graphics.h" #include "graphics.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
@@ -27,16 +30,14 @@ using namespace std::literals;
namespace wl { namespace wl {
// Helper to call C++ method from wayland C callback // Helper to call C++ method from wayland C callback
template <class T, class Method, Method m, class... Params> template<class T, class Method, Method m, class... Params>
static auto static auto classCall(void *data, Params... params) -> decltype(((*reinterpret_cast<T *>(data)).*m)(params...)) {
classCall(void *data, Params... params) -> decltype(((*reinterpret_cast<T *>(data)).*m)(params...)) {
return ((*reinterpret_cast<T *>(data)).*m)(params...); return ((*reinterpret_cast<T *>(data)).*m)(params...);
} }
#define CLASS_CALL(c, m) classCall<c, decltype(&c::m), &c::m> #define CLASS_CALL(c, m) classCall<c, decltype(&c::m), &c::m>
int int display_t::init(const char *display_name) {
display_t::init(const char *display_name) {
if (!display_name) { if (!display_name) {
display_name = std::getenv("WAYLAND_DISPLAY"); display_name = std::getenv("WAYLAND_DISPLAY");
} }
@@ -57,8 +58,7 @@ namespace wl {
return 0; return 0;
} }
void void display_t::roundtrip() {
display_t::roundtrip() {
wl_display_roundtrip(display_internal.get()); wl_display_roundtrip(display_internal.get());
} }
@@ -67,8 +67,7 @@ namespace wl {
* @param timeout The timeout in milliseconds. * @param timeout The timeout in milliseconds.
* @return `true` if new events were dispatched or `false` if the timeout expired. * @return `true` if new events were dispatched or `false` if the timeout expired.
*/ */
bool bool display_t::dispatch(std::chrono::milliseconds timeout) {
display_t::dispatch(std::chrono::milliseconds timeout) {
// Check if any events are queued already. If not, flush // Check if any events are queued already. If not, flush
// outgoing events, and prepare to wait for readability. // outgoing events, and prepare to wait for readability.
if (wl_display_prepare_read(display_internal.get()) == 0) { if (wl_display_prepare_read(display_internal.get()) == 0) {
@@ -81,8 +80,7 @@ namespace wl {
if (poll(&pfd, 1, timeout.count()) == 1 && (pfd.revents & POLLIN)) { if (poll(&pfd, 1, timeout.count()) == 1 && (pfd.revents & POLLIN)) {
// Read the new event(s) // Read the new event(s)
wl_display_read_events(display_internal.get()); wl_display_read_events(display_internal.get());
} } else {
else {
// We timed out, so unlock the queue now // We timed out, so unlock the queue now
wl_display_cancel_read(display_internal.get()); wl_display_cancel_read(display_internal.get());
return false; return false;
@@ -94,13 +92,12 @@ namespace wl {
return true; return true;
} }
wl_registry * wl_registry *display_t::registry() {
display_t::registry() {
return wl_display_get_registry(display_internal.get()); return wl_display_get_registry(display_internal.get());
} }
inline monitor_t::monitor_t(wl_output *output): inline monitor_t::monitor_t(wl_output *output):
output { output }, output {output},
wl_listener { wl_listener {
&CLASS_CALL(monitor_t, wl_geometry), &CLASS_CALL(monitor_t, wl_geometry),
&CLASS_CALL(monitor_t, wl_mode), &CLASS_CALL(monitor_t, wl_mode),
@@ -113,46 +110,40 @@ namespace wl {
&CLASS_CALL(monitor_t, xdg_done), &CLASS_CALL(monitor_t, xdg_done),
&CLASS_CALL(monitor_t, xdg_name), &CLASS_CALL(monitor_t, xdg_name),
&CLASS_CALL(monitor_t, xdg_description) &CLASS_CALL(monitor_t, xdg_description)
} {} } {
}
inline void inline void monitor_t::xdg_name(zxdg_output_v1 *, const char *name) {
monitor_t::xdg_name(zxdg_output_v1 *, const char *name) {
this->name = name; this->name = name;
BOOST_LOG(info) << "Name: "sv << this->name; BOOST_LOG(info) << "Name: "sv << this->name;
} }
void void monitor_t::xdg_description(zxdg_output_v1 *, const char *description) {
monitor_t::xdg_description(zxdg_output_v1 *, const char *description) {
this->description = description; this->description = description;
BOOST_LOG(info) << "Found monitor: "sv << this->description; BOOST_LOG(info) << "Found monitor: "sv << this->description;
} }
void void monitor_t::xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y) {
monitor_t::xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y) {
viewport.offset_x = x; viewport.offset_x = x;
viewport.offset_y = y; viewport.offset_y = y;
BOOST_LOG(info) << "Offset: "sv << x << 'x' << y; BOOST_LOG(info) << "Offset: "sv << x << 'x' << y;
} }
void void monitor_t::xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height) {
monitor_t::xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height) {
BOOST_LOG(info) << "Logical size: "sv << width << 'x' << height; BOOST_LOG(info) << "Logical size: "sv << width << 'x' << height;
} }
void void monitor_t::wl_mode(wl_output *wl_output, std::uint32_t flags, std::int32_t width, std::int32_t height, std::int32_t refresh) {
monitor_t::wl_mode(wl_output *wl_output, std::uint32_t flags,
std::int32_t width, std::int32_t height, std::int32_t refresh) {
viewport.width = width; viewport.width = width;
viewport.height = height; viewport.height = height;
BOOST_LOG(info) << "Resolution: "sv << width << 'x' << height; BOOST_LOG(info) << "Resolution: "sv << width << 'x' << height;
} }
void void monitor_t::listen(zxdg_output_manager_v1 *output_manager) {
monitor_t::listen(zxdg_output_manager_v1 *output_manager) {
auto xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, output); auto xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, output);
zxdg_output_v1_add_listener(xdg_output, &xdg_listener, this); zxdg_output_v1_add_listener(xdg_output, &xdg_listener, this);
wl_output_add_listener(output, &wl_listener, this); wl_output_add_listener(output, &wl_listener, this);
@@ -160,34 +151,33 @@ namespace wl {
interface_t::interface_t() noexcept interface_t::interface_t() noexcept
: :
output_manager { nullptr }, output_manager {nullptr},
listener { listener {
&CLASS_CALL(interface_t, add_interface), &CLASS_CALL(interface_t, add_interface),
&CLASS_CALL(interface_t, del_interface) &CLASS_CALL(interface_t, del_interface)
} {} } {
}
void void interface_t::listen(wl_registry *registry) {
interface_t::listen(wl_registry *registry) {
wl_registry_add_listener(registry, &listener, this); wl_registry_add_listener(registry, &listener, this);
} }
void void interface_t::add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version) {
interface_t::add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version) {
BOOST_LOG(debug) << "Available interface: "sv << interface << '(' << id << ") version "sv << version; BOOST_LOG(debug) << "Available interface: "sv << interface << '(' << id << ") version "sv << version;
if (!std::strcmp(interface, wl_output_interface.name)) { if (!std::strcmp(interface, wl_output_interface.name)) {
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
monitors.emplace_back( monitors.emplace_back(
std::make_unique<monitor_t>( std::make_unique<monitor_t>(
(wl_output *) wl_registry_bind(registry, id, &wl_output_interface, 2))); (wl_output *) wl_registry_bind(registry, id, &wl_output_interface, 2)
} )
else if (!std::strcmp(interface, zxdg_output_manager_v1_interface.name)) { );
} else if (!std::strcmp(interface, zxdg_output_manager_v1_interface.name)) {
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
output_manager = (zxdg_output_manager_v1 *) wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, version); output_manager = (zxdg_output_manager_v1 *) wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, version);
this->interface[XDG_OUTPUT] = true; this->interface[XDG_OUTPUT] = true;
} } else if (!std::strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) {
else if (!std::strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) {
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
dmabuf_manager = (zwlr_export_dmabuf_manager_v1 *) wl_registry_bind(registry, id, &zwlr_export_dmabuf_manager_v1_interface, version); dmabuf_manager = (zwlr_export_dmabuf_manager_v1 *) wl_registry_bind(registry, id, &zwlr_export_dmabuf_manager_v1_interface, version);
@@ -195,13 +185,15 @@ namespace wl {
} }
} }
void void interface_t::del_interface(wl_registry *registry, uint32_t id) {
interface_t::del_interface(wl_registry *registry, uint32_t id) {
BOOST_LOG(info) << "Delete: "sv << id; BOOST_LOG(info) << "Delete: "sv << id;
} }
dmabuf_t::dmabuf_t(): dmabuf_t::dmabuf_t():
status { READY }, frames {}, current_frame { &frames[0] }, listener { status {READY},
frames {},
current_frame {&frames[0]},
listener {
&CLASS_CALL(dmabuf_t, frame), &CLASS_CALL(dmabuf_t, frame),
&CLASS_CALL(dmabuf_t, object), &CLASS_CALL(dmabuf_t, object),
&CLASS_CALL(dmabuf_t, ready), &CLASS_CALL(dmabuf_t, ready),
@@ -209,8 +201,7 @@ namespace wl {
} { } {
} }
void void dmabuf_t::listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor) {
dmabuf_t::listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor) {
auto frame = zwlr_export_dmabuf_manager_v1_capture_output(dmabuf_manager, blend_cursor, output); auto frame = zwlr_export_dmabuf_manager_v1_capture_output(dmabuf_manager, blend_cursor, output);
zwlr_export_dmabuf_frame_v1_add_listener(frame, &listener, this); zwlr_export_dmabuf_frame_v1_add_listener(frame, &listener, this);
@@ -223,15 +214,19 @@ namespace wl {
} }
} }
void void dmabuf_t::frame(
dmabuf_t::frame(
zwlr_export_dmabuf_frame_v1 *frame, zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t width, std::uint32_t height, std::uint32_t width,
std::uint32_t x, std::uint32_t y, std::uint32_t height,
std::uint32_t buffer_flags, std::uint32_t flags, std::uint32_t x,
std::uint32_t y,
std::uint32_t buffer_flags,
std::uint32_t flags,
std::uint32_t format, std::uint32_t format,
std::uint32_t high, std::uint32_t low, std::uint32_t high,
std::uint32_t obj_count) { std::uint32_t low,
std::uint32_t obj_count
) {
auto next_frame = get_next_frame(); auto next_frame = get_next_frame();
next_frame->sd.fourcc = format; next_frame->sd.fourcc = format;
@@ -240,15 +235,15 @@ namespace wl {
next_frame->sd.modifier = (((std::uint64_t) high) << 32) | low; next_frame->sd.modifier = (((std::uint64_t) high) << 32) | low;
} }
void void dmabuf_t::object(
dmabuf_t::object(
zwlr_export_dmabuf_frame_v1 *frame, zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t index, std::uint32_t index,
std::int32_t fd, std::int32_t fd,
std::uint32_t size, std::uint32_t size,
std::uint32_t offset, std::uint32_t offset,
std::uint32_t stride, std::uint32_t stride,
std::uint32_t plane_index) { std::uint32_t plane_index
) {
auto next_frame = get_next_frame(); auto next_frame = get_next_frame();
next_frame->sd.fds[plane_index] = fd; next_frame->sd.fds[plane_index] = fd;
@@ -256,10 +251,12 @@ namespace wl {
next_frame->sd.offsets[plane_index] = offset; next_frame->sd.offsets[plane_index] = offset;
} }
void void dmabuf_t::ready(
dmabuf_t::ready(
zwlr_export_dmabuf_frame_v1 *frame, zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec) { std::uint32_t tv_sec_hi,
std::uint32_t tv_sec_lo,
std::uint32_t tv_nsec
) {
zwlr_export_dmabuf_frame_v1_destroy(frame); zwlr_export_dmabuf_frame_v1_destroy(frame);
current_frame->destroy(); current_frame->destroy();
@@ -268,10 +265,10 @@ namespace wl {
status = READY; status = READY;
} }
void void dmabuf_t::cancel(
dmabuf_t::cancel(
zwlr_export_dmabuf_frame_v1 *frame, zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t reason) { std::uint32_t reason
) {
zwlr_export_dmabuf_frame_v1_destroy(frame); zwlr_export_dmabuf_frame_v1_destroy(frame);
auto next_frame = get_next_frame(); auto next_frame = get_next_frame();
@@ -280,8 +277,7 @@ namespace wl {
status = REINIT; status = REINIT;
} }
void void frame_t::destroy() {
frame_t::destroy() {
for (auto x = 0; x < 4; ++x) { for (auto x = 0; x < 4; ++x) {
if (sd.fds[x] >= 0) { if (sd.fds[x] >= 0) {
close(sd.fds[x]); close(sd.fds[x]);
@@ -296,8 +292,7 @@ namespace wl {
std::fill_n(sd.fds, 4, -1); std::fill_n(sd.fds, 4, -1);
}; };
std::vector<std::unique_ptr<monitor_t>> std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name) {
monitors(const char *display_name) {
display_t display; display_t display;
if (display.init(display_name)) { if (display.init(display_name)) {
@@ -323,15 +318,13 @@ namespace wl {
return std::move(interface.monitors); return std::move(interface.monitors);
} }
static bool static bool validate() {
validate() {
display_t display; display_t display;
return display.init() == 0; return display.init() == 0;
} }
int int init() {
init() {
static bool validated = validate(); static bool validated = validate();
return !validated; return !validated;

View File

@@ -4,6 +4,7 @@
*/ */
#pragma once #pragma once
// standard includes
#include <bitset> #include <bitset>
#ifdef SUNSHINE_BUILD_WAYLAND #ifdef SUNSHINE_BUILD_WAYLAND
@@ -11,6 +12,7 @@
#include <xdg-output-unstable-v1.h> #include <xdg-output-unstable-v1.h>
#endif #endif
// local includes
#include "graphics.h" #include "graphics.h"
/** /**
@@ -27,8 +29,7 @@ namespace wl {
frame_t(); frame_t();
egl::surface_descriptor_t sd; egl::surface_descriptor_t sd;
void void destroy();
destroy();
}; };
class dmabuf_t { class dmabuf_t {
@@ -42,50 +43,52 @@ namespace wl {
dmabuf_t(dmabuf_t &&) = delete; dmabuf_t(dmabuf_t &&) = delete;
dmabuf_t(const dmabuf_t &) = delete; dmabuf_t(const dmabuf_t &) = delete;
dmabuf_t & dmabuf_t &operator=(const dmabuf_t &) = delete;
operator=(const dmabuf_t &) = delete; dmabuf_t &operator=(dmabuf_t &&) = delete;
dmabuf_t &
operator=(dmabuf_t &&) = delete;
dmabuf_t(); dmabuf_t();
void void listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor = false);
listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor = false);
~dmabuf_t(); ~dmabuf_t();
void void frame(
frame(
zwlr_export_dmabuf_frame_v1 *frame, zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t width, std::uint32_t height, std::uint32_t width,
std::uint32_t x, std::uint32_t y, std::uint32_t height,
std::uint32_t buffer_flags, std::uint32_t flags, std::uint32_t x,
std::uint32_t y,
std::uint32_t buffer_flags,
std::uint32_t flags,
std::uint32_t format, std::uint32_t format,
std::uint32_t high, std::uint32_t low, std::uint32_t high,
std::uint32_t obj_count); std::uint32_t low,
std::uint32_t obj_count
);
void void object(
object(
zwlr_export_dmabuf_frame_v1 *frame, zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t index, std::uint32_t index,
std::int32_t fd, std::int32_t fd,
std::uint32_t size, std::uint32_t size,
std::uint32_t offset, std::uint32_t offset,
std::uint32_t stride, std::uint32_t stride,
std::uint32_t plane_index); std::uint32_t plane_index
);
void void ready(
ready(
zwlr_export_dmabuf_frame_v1 *frame, zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec); std::uint32_t tv_sec_hi,
std::uint32_t tv_sec_lo,
std::uint32_t tv_nsec
);
void void cancel(
cancel(
zwlr_export_dmabuf_frame_v1 *frame, zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t reason); std::uint32_t reason
);
inline frame_t * inline frame_t *get_next_frame() {
get_next_frame() {
return current_frame == &frames[0] ? &frames[1] : &frames[0]; return current_frame == &frames[0] ? &frames[1] : &frames[0];
} }
@@ -102,38 +105,31 @@ namespace wl {
monitor_t(monitor_t &&) = delete; monitor_t(monitor_t &&) = delete;
monitor_t(const monitor_t &) = delete; monitor_t(const monitor_t &) = delete;
monitor_t & monitor_t &operator=(const monitor_t &) = delete;
operator=(const monitor_t &) = delete; monitor_t &operator=(monitor_t &&) = delete;
monitor_t &
operator=(monitor_t &&) = delete;
monitor_t(wl_output *output); monitor_t(wl_output *output);
void void xdg_name(zxdg_output_v1 *, const char *name);
xdg_name(zxdg_output_v1 *, const char *name); void xdg_description(zxdg_output_v1 *, const char *description);
void void xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y);
xdg_description(zxdg_output_v1 *, const char *description); void xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height);
void
xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y);
void
xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height);
void
xdg_done(zxdg_output_v1 *) {}
void void xdg_done(zxdg_output_v1 *) {
wl_geometry(wl_output *wl_output, std::int32_t x, std::int32_t y, }
std::int32_t physical_width, std::int32_t physical_height, std::int32_t subpixel,
const char *make, const char *model, std::int32_t transform) {}
void
wl_mode(wl_output *wl_output, std::uint32_t flags,
std::int32_t width, std::int32_t height, std::int32_t refresh);
void
wl_done(wl_output *wl_output) {}
void
wl_scale(wl_output *wl_output, std::int32_t factor) {}
void void wl_geometry(wl_output *wl_output, std::int32_t x, std::int32_t y, std::int32_t physical_width, std::int32_t physical_height, std::int32_t subpixel, const char *make, const char *model, std::int32_t transform) {
listen(zxdg_output_manager_v1 *output_manager); }
void wl_mode(wl_output *wl_output, std::uint32_t flags, std::int32_t width, std::int32_t height, std::int32_t refresh);
void wl_done(wl_output *wl_output) {
}
void wl_scale(wl_output *wl_output, std::int32_t factor) {
}
void listen(zxdg_output_manager_v1 *output_manager);
wl_output *output; wl_output *output;
@@ -162,31 +158,25 @@ namespace wl {
interface_t(interface_t &&) = delete; interface_t(interface_t &&) = delete;
interface_t(const interface_t &) = delete; interface_t(const interface_t &) = delete;
interface_t & interface_t &operator=(const interface_t &) = delete;
operator=(const interface_t &) = delete; interface_t &operator=(interface_t &&) = delete;
interface_t &
operator=(interface_t &&) = delete;
interface_t() noexcept; interface_t() noexcept;
void void listen(wl_registry *registry);
listen(wl_registry *registry);
std::vector<std::unique_ptr<monitor_t>> monitors; std::vector<std::unique_ptr<monitor_t>> monitors;
zwlr_export_dmabuf_manager_v1 *dmabuf_manager; zwlr_export_dmabuf_manager_v1 *dmabuf_manager;
zxdg_output_manager_v1 *output_manager; zxdg_output_manager_v1 *output_manager;
bool bool operator[](interface_e bit) const {
operator[](interface_e bit) const {
return interface[bit]; return interface[bit];
} }
private: private:
void void add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version);
add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version); void del_interface(wl_registry *registry, uint32_t id);
void
del_interface(wl_registry *registry, uint32_t id);
std::bitset<MAX_INTERFACES> interface; std::bitset<MAX_INTERFACES> interface;
@@ -201,24 +191,19 @@ namespace wl {
* @param display_name The name of the display. * @param display_name The name of the display.
* @return 0 on success, -1 on failure. * @return 0 on success, -1 on failure.
*/ */
int int init(const char *display_name = nullptr);
init(const char *display_name = nullptr);
// Roundtrip with Wayland connection // Roundtrip with Wayland connection
void void roundtrip();
roundtrip();
// Wait up to the timeout to read and dispatch new events // Wait up to the timeout to read and dispatch new events
bool bool dispatch(std::chrono::milliseconds timeout);
dispatch(std::chrono::milliseconds timeout);
// Get the registry associated with the display // Get the registry associated with the display
// No need to manually free the registry // No need to manually free the registry
wl_registry * wl_registry *registry();
registry();
inline display_internal_t::pointer inline display_internal_t::pointer get() {
get() {
return display_internal.get(); return display_internal.get();
} }
@@ -226,11 +211,9 @@ namespace wl {
display_internal_t display_internal; display_internal_t display_internal;
}; };
std::vector<std::unique_ptr<monitor_t>> std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name = nullptr);
monitors(const char *display_name = nullptr);
int int init();
init();
} // namespace wl } // namespace wl
#else #else
@@ -243,15 +226,12 @@ namespace wl {
monitor_t(monitor_t &&) = delete; monitor_t(monitor_t &&) = delete;
monitor_t(const monitor_t &) = delete; monitor_t(const monitor_t &) = delete;
monitor_t & monitor_t &operator=(const monitor_t &) = delete;
operator=(const monitor_t &) = delete; monitor_t &operator=(monitor_t &&) = delete;
monitor_t &
operator=(monitor_t &&) = delete;
monitor_t(wl_output *output); monitor_t(wl_output *output);
void void listen(zxdg_output_manager_v1 *output_manager);
listen(zxdg_output_manager_v1 *output_manager);
wl_output *output; wl_output *output;
@@ -261,10 +241,12 @@ namespace wl {
platf::touch_port_t viewport; platf::touch_port_t viewport;
}; };
inline std::vector<std::unique_ptr<monitor_t>> inline std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name = nullptr) {
monitors(const char *display_name = nullptr) { return {}; } return {};
}
inline int inline int init() {
init() { return -1; } return -1;
}
} // namespace wl } // namespace wl
#endif #endif

View File

@@ -2,18 +2,19 @@
* @file src/platform/linux/wlgrab.cpp * @file src/platform/linux/wlgrab.cpp
* @brief Definitions for wlgrab capture. * @brief Definitions for wlgrab capture.
*/ */
// standard includes
#include <thread> #include <thread>
#include "src/platform/common.h" // local includes
#include "src/logging.h"
#include "src/video.h"
#include "cuda.h" #include "cuda.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/video.h"
#include "vaapi.h" #include "vaapi.h"
#include "wayland.h" #include "wayland.h"
using namespace std::literals; using namespace std::literals;
namespace wl { namespace wl {
static int env_width; static int env_width;
static int env_height; static int env_height;
@@ -27,9 +28,8 @@ namespace wl {
class wlr_t: public platf::display_t { class wlr_t: public platf::display_t {
public: public:
int int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { delay = std::chrono::nanoseconds {1s} / config.framerate;
delay = std::chrono::nanoseconds { 1s } / config.framerate;
mem_type = hwdevice_type; mem_type = hwdevice_type;
if (display.init()) { if (display.init()) {
@@ -82,13 +82,11 @@ namespace wl {
return 0; return 0;
} }
int int dummy_img(platf::img_t *img) override {
dummy_img(platf::img_t *img) override {
return 0; return 0;
} }
inline platf::capture_e inline platf::capture_e 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) {
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; auto to = std::chrono::steady_clock::now() + timeout;
// Dispatch events until we get a new frame or the timeout expires // Dispatch events until we get a new frame or the timeout expires
@@ -105,7 +103,8 @@ namespace wl {
if ( if (
dmabuf.status == dmabuf_t::REINIT || dmabuf.status == dmabuf_t::REINIT ||
current_frame->sd.width != width || current_frame->sd.width != width ||
current_frame->sd.height != height) { current_frame->sd.height != height
) {
return platf::capture_e::reinit; return platf::capture_e::reinit;
} }
@@ -125,8 +124,7 @@ namespace wl {
class wlr_ram_t: public wlr_t { class wlr_ram_t: public wlr_t {
public: public:
platf::capture_e platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, 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(); auto next_frame = std::chrono::steady_clock::now();
sleep_overshoot_logger.reset(); sleep_overshoot_logger.reset();
@@ -171,8 +169,7 @@ namespace wl {
return platf::capture_e::ok; return platf::capture_e::ok;
} }
platf::capture_e platf::capture_e 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) {
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); auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor);
if (status != platf::capture_e::ok) { if (status != platf::capture_e::ok) {
return status; return status;
@@ -204,8 +201,7 @@ namespace wl {
return platf::capture_e::ok; return platf::capture_e::ok;
} }
int int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
if (wlr_t::init(hwdevice_type, display_name, config)) { if (wlr_t::init(hwdevice_type, display_name, config)) {
return -1; return -1;
} }
@@ -225,8 +221,7 @@ namespace wl {
return 0; return 0;
} }
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI #ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == platf::mem_type_e::vaapi) { if (mem_type == platf::mem_type_e::vaapi) {
return va::make_avcodec_encode_device(width, height, false); return va::make_avcodec_encode_device(width, height, false);
@@ -242,8 +237,7 @@ namespace wl {
return std::make_unique<platf::avcodec_encode_device_t>(); return std::make_unique<platf::avcodec_encode_device_t>();
} }
std::shared_ptr<platf::img_t> std::shared_ptr<platf::img_t> alloc_img() override {
alloc_img() override {
auto img = std::make_shared<img_t>(); auto img = std::make_shared<img_t>();
img->width = width; img->width = width;
img->height = height; img->height = height;
@@ -260,8 +254,7 @@ namespace wl {
class wlr_vram_t: public wlr_t { class wlr_vram_t: public wlr_t {
public: public:
platf::capture_e platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, 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(); auto next_frame = std::chrono::steady_clock::now();
sleep_overshoot_logger.reset(); sleep_overshoot_logger.reset();
@@ -306,8 +299,7 @@ namespace wl {
return platf::capture_e::ok; return platf::capture_e::ok;
} }
platf::capture_e platf::capture_e 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) {
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); auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor);
if (status != platf::capture_e::ok) { if (status != platf::capture_e::ok) {
return status; return status;
@@ -332,8 +324,7 @@ namespace wl {
return platf::capture_e::ok; return platf::capture_e::ok;
} }
std::shared_ptr<platf::img_t> std::shared_ptr<platf::img_t> alloc_img() override {
alloc_img() override {
auto img = std::make_shared<egl::img_descriptor_t>(); auto img = std::make_shared<egl::img_descriptor_t>();
img->width = width; img->width = width;
@@ -348,8 +339,7 @@ namespace wl {
return img; return img;
} }
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI #ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == platf::mem_type_e::vaapi) { if (mem_type == platf::mem_type_e::vaapi) {
return va::make_avcodec_encode_device(width, height, 0, 0, true); return va::make_avcodec_encode_device(width, height, 0, 0, true);
@@ -365,8 +355,7 @@ namespace wl {
return std::make_unique<platf::avcodec_encode_device_t>(); return std::make_unique<platf::avcodec_encode_device_t>();
} }
int int dummy_img(platf::img_t *img) override {
dummy_img(platf::img_t *img) override {
// Empty images are recognized as dummies by the zero sequence number // Empty images are recognized as dummies by the zero sequence number
return 0; return 0;
} }
@@ -377,8 +366,7 @@ namespace wl {
} // namespace wl } // namespace wl
namespace platf { namespace platf {
std::shared_ptr<display_t> std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) { if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv; BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
return nullptr; return nullptr;
@@ -401,8 +389,7 @@ namespace platf {
return wlr; return wlr;
} }
std::vector<std::string> std::vector<std::string> wl_display_names() {
wl_display_names() {
std::vector<std::string> display_names; std::vector<std::string> display_names;
wl::display_t display; wl::display_t display;

View File

@@ -2,61 +2,49 @@
* @file src/platform/linux/x11grab.cpp * @file src/platform/linux/x11grab.cpp
* @brief Definitions for x11 capture. * @brief Definitions for x11 capture.
*/ */
#include "src/platform/common.h" // standard includes
#include <fstream> #include <fstream>
#include <thread> #include <thread>
// plaform includes
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/Xrandr.h>
#include <X11/X.h> #include <X11/X.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/Xrandr.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <xcb/shm.h> #include <xcb/shm.h>
#include <xcb/xfixes.h> #include <xcb/xfixes.h>
#include "src/config.h" // local includes
#include "src/globals.h"
#include "src/logging.h"
#include "src/task_pool.h"
#include "src/video.h"
#include "cuda.h" #include "cuda.h"
#include "graphics.h" #include "graphics.h"
#include "misc.h" #include "misc.h"
#include "src/config.h"
#include "src/globals.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/task_pool.h"
#include "src/video.h"
#include "vaapi.h" #include "vaapi.h"
#include "x11grab.h" #include "x11grab.h"
using namespace std::literals; using namespace std::literals;
namespace platf { namespace platf {
int int load_xcb();
load_xcb(); int load_x11();
int
load_x11();
namespace x11 { namespace x11 {
#define _FN(x, ret, args) \ #define _FN(x, ret, args) \
typedef ret(*x##_fn) args; \ typedef ret(*x##_fn) args; \
static x##_fn x static x##_fn x
_FN(GetImage, XImage *, _FN(GetImage, XImage *, (Display * display, Drawable d, int x, int y, unsigned int width, unsigned int height, unsigned long plane_mask, int format));
(
Display * display,
Drawable d,
int x, int y,
unsigned int width, unsigned int height,
unsigned long plane_mask,
int format));
_FN(OpenDisplay, Display *, (_Xconst char *display_name)); _FN(OpenDisplay, Display *, (_Xconst char *display_name));
_FN(GetWindowAttributes, Status, _FN(GetWindowAttributes, Status, (Display * display, Window w, XWindowAttributes *window_attributes_return));
(
Display * display,
Window w,
XWindowAttributes *window_attributes_return));
_FN(CloseDisplay, int, (Display * display)); _FN(CloseDisplay, int, (Display * display));
_FN(Free, int, (void *data)); _FN(Free, int, (void *data));
@@ -70,27 +58,28 @@ namespace platf {
_FN(FreeOutputInfo, void, (XRROutputInfo * outputInfo)); _FN(FreeOutputInfo, void, (XRROutputInfo * outputInfo));
_FN(FreeCrtcInfo, void, (XRRCrtcInfo * crtcInfo)); _FN(FreeCrtcInfo, void, (XRRCrtcInfo * crtcInfo));
static int static int init() {
init() { static void *handle {nullptr};
static void *handle { nullptr };
static bool funcs_loaded = false; static bool funcs_loaded = false;
if (funcs_loaded) return 0; if (funcs_loaded) {
return 0;
}
if (!handle) { if (!handle) {
handle = dyn::handle({ "libXrandr.so.2", "libXrandr.so" }); handle = dyn::handle({"libXrandr.so.2", "libXrandr.so"});
if (!handle) { if (!handle) {
return -1; return -1;
} }
} }
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs { std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &GetScreenResources, "XRRGetScreenResources" }, {(dyn::apiproc *) &GetScreenResources, "XRRGetScreenResources"},
{ (dyn::apiproc *) &GetOutputInfo, "XRRGetOutputInfo" }, {(dyn::apiproc *) &GetOutputInfo, "XRRGetOutputInfo"},
{ (dyn::apiproc *) &GetCrtcInfo, "XRRGetCrtcInfo" }, {(dyn::apiproc *) &GetCrtcInfo, "XRRGetCrtcInfo"},
{ (dyn::apiproc *) &FreeScreenResources, "XRRFreeScreenResources" }, {(dyn::apiproc *) &FreeScreenResources, "XRRFreeScreenResources"},
{ (dyn::apiproc *) &FreeOutputInfo, "XRRFreeOutputInfo" }, {(dyn::apiproc *) &FreeOutputInfo, "XRRFreeOutputInfo"},
{ (dyn::apiproc *) &FreeCrtcInfo, "XRRFreeCrtcInfo" }, {(dyn::apiproc *) &FreeCrtcInfo, "XRRFreeCrtcInfo"},
}; };
if (dyn::load(handle, funcs)) { if (dyn::load(handle, funcs)) {
@@ -102,25 +91,27 @@ namespace platf {
} }
} // namespace rr } // namespace rr
namespace fix { namespace fix {
_FN(GetCursorImage, XFixesCursorImage *, (Display * dpy)); _FN(GetCursorImage, XFixesCursorImage *, (Display * dpy));
static int static int init() {
init() { static void *handle {nullptr};
static void *handle { nullptr };
static bool funcs_loaded = false; static bool funcs_loaded = false;
if (funcs_loaded) return 0; if (funcs_loaded) {
return 0;
}
if (!handle) { if (!handle) {
handle = dyn::handle({ "libXfixes.so.3", "libXfixes.so" }); handle = dyn::handle({"libXfixes.so.3", "libXfixes.so"});
if (!handle) { if (!handle) {
return -1; return -1;
} }
} }
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs { std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &GetCursorImage, "XFixesGetCursorImage" }, {(dyn::apiproc *) &GetCursorImage, "XFixesGetCursorImage"},
}; };
if (dyn::load(handle, funcs)) { if (dyn::load(handle, funcs)) {
@@ -132,27 +123,28 @@ namespace platf {
} }
} // namespace fix } // namespace fix
static int static int init() {
init() { static void *handle {nullptr};
static void *handle { nullptr };
static bool funcs_loaded = false; static bool funcs_loaded = false;
if (funcs_loaded) return 0; if (funcs_loaded) {
return 0;
}
if (!handle) { if (!handle) {
handle = dyn::handle({ "libX11.so.6", "libX11.so" }); handle = dyn::handle({"libX11.so.6", "libX11.so"});
if (!handle) { if (!handle) {
return -1; return -1;
} }
} }
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs { std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &GetImage, "XGetImage" }, {(dyn::apiproc *) &GetImage, "XGetImage"},
{ (dyn::apiproc *) &OpenDisplay, "XOpenDisplay" }, {(dyn::apiproc *) &OpenDisplay, "XOpenDisplay"},
{ (dyn::apiproc *) &GetWindowAttributes, "XGetWindowAttributes" }, {(dyn::apiproc *) &GetWindowAttributes, "XGetWindowAttributes"},
{ (dyn::apiproc *) &Free, "XFree" }, {(dyn::apiproc *) &Free, "XFree"},
{ (dyn::apiproc *) &CloseDisplay, "XCloseDisplay" }, {(dyn::apiproc *) &CloseDisplay, "XCloseDisplay"},
{ (dyn::apiproc *) &InitThreads, "XInitThreads" }, {(dyn::apiproc *) &InitThreads, "XInitThreads"},
}; };
if (dyn::load(handle, funcs)) { if (dyn::load(handle, funcs)) {
@@ -167,31 +159,13 @@ namespace platf {
namespace xcb { namespace xcb {
static xcb_extension_t *shm_id; static xcb_extension_t *shm_id;
_FN(shm_get_image_reply, xcb_shm_get_image_reply_t *, _FN(shm_get_image_reply, xcb_shm_get_image_reply_t *, (xcb_connection_t * c, xcb_shm_get_image_cookie_t cookie, xcb_generic_error_t **e));
(
xcb_connection_t * c,
xcb_shm_get_image_cookie_t cookie,
xcb_generic_error_t **e));
_FN(shm_get_image_unchecked, xcb_shm_get_image_cookie_t, _FN(shm_get_image_unchecked, xcb_shm_get_image_cookie_t, (xcb_connection_t * c, xcb_drawable_t drawable, int16_t x, int16_t y, uint16_t width, uint16_t height, uint32_t plane_mask, uint8_t format, xcb_shm_seg_t shmseg, uint32_t offset));
(
xcb_connection_t * c,
xcb_drawable_t drawable,
int16_t x, int16_t y,
uint16_t width, uint16_t height,
uint32_t plane_mask,
uint8_t format,
xcb_shm_seg_t shmseg,
uint32_t offset));
_FN(shm_attach, xcb_void_cookie_t, _FN(shm_attach, xcb_void_cookie_t, (xcb_connection_t * c, xcb_shm_seg_t shmseg, uint32_t shmid, uint8_t read_only));
(xcb_connection_t * c,
xcb_shm_seg_t shmseg,
uint32_t shmid,
uint8_t read_only));
_FN(get_extension_data, xcb_query_extension_reply_t *, _FN(get_extension_data, xcb_query_extension_reply_t *, (xcb_connection_t * c, xcb_extension_t *ext));
(xcb_connection_t * c, xcb_extension_t *ext));
_FN(get_setup, xcb_setup_t *, (xcb_connection_t * c)); _FN(get_setup, xcb_setup_t *, (xcb_connection_t * c));
_FN(disconnect, void, (xcb_connection_t * c)); _FN(disconnect, void, (xcb_connection_t * c));
@@ -200,25 +174,26 @@ namespace platf {
_FN(setup_roots_iterator, xcb_screen_iterator_t, (const xcb_setup_t *R)); _FN(setup_roots_iterator, xcb_screen_iterator_t, (const xcb_setup_t *R));
_FN(generate_id, std::uint32_t, (xcb_connection_t * c)); _FN(generate_id, std::uint32_t, (xcb_connection_t * c));
int int init_shm() {
init_shm() { static void *handle {nullptr};
static void *handle { nullptr };
static bool funcs_loaded = false; static bool funcs_loaded = false;
if (funcs_loaded) return 0; if (funcs_loaded) {
return 0;
}
if (!handle) { if (!handle) {
handle = dyn::handle({ "libxcb-shm.so.0", "libxcb-shm.so" }); handle = dyn::handle({"libxcb-shm.so.0", "libxcb-shm.so"});
if (!handle) { if (!handle) {
return -1; return -1;
} }
} }
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs { std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &shm_id, "xcb_shm_id" }, {(dyn::apiproc *) &shm_id, "xcb_shm_id"},
{ (dyn::apiproc *) &shm_get_image_reply, "xcb_shm_get_image_reply" }, {(dyn::apiproc *) &shm_get_image_reply, "xcb_shm_get_image_reply"},
{ (dyn::apiproc *) &shm_get_image_unchecked, "xcb_shm_get_image_unchecked" }, {(dyn::apiproc *) &shm_get_image_unchecked, "xcb_shm_get_image_unchecked"},
{ (dyn::apiproc *) &shm_attach, "xcb_shm_attach" }, {(dyn::apiproc *) &shm_attach, "xcb_shm_attach"},
}; };
if (dyn::load(handle, funcs)) { if (dyn::load(handle, funcs)) {
@@ -229,28 +204,29 @@ namespace platf {
return 0; return 0;
} }
int int init() {
init() { static void *handle {nullptr};
static void *handle { nullptr };
static bool funcs_loaded = false; static bool funcs_loaded = false;
if (funcs_loaded) return 0; if (funcs_loaded) {
return 0;
}
if (!handle) { if (!handle) {
handle = dyn::handle({ "libxcb.so.1", "libxcb.so" }); handle = dyn::handle({"libxcb.so.1", "libxcb.so"});
if (!handle) { if (!handle) {
return -1; return -1;
} }
} }
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs { std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &get_extension_data, "xcb_get_extension_data" }, {(dyn::apiproc *) &get_extension_data, "xcb_get_extension_data"},
{ (dyn::apiproc *) &get_setup, "xcb_get_setup" }, {(dyn::apiproc *) &get_setup, "xcb_get_setup"},
{ (dyn::apiproc *) &disconnect, "xcb_disconnect" }, {(dyn::apiproc *) &disconnect, "xcb_disconnect"},
{ (dyn::apiproc *) &connection_has_error, "xcb_connection_has_error" }, {(dyn::apiproc *) &connection_has_error, "xcb_connection_has_error"},
{ (dyn::apiproc *) &connect, "xcb_connect" }, {(dyn::apiproc *) &connect, "xcb_connect"},
{ (dyn::apiproc *) &setup_roots_iterator, "xcb_setup_roots_iterator" }, {(dyn::apiproc *) &setup_roots_iterator, "xcb_setup_roots_iterator"},
{ (dyn::apiproc *) &generate_id, "xcb_generate_id" }, {(dyn::apiproc *) &generate_id, "xcb_generate_id"},
}; };
if (dyn::load(handle, funcs)) { if (dyn::load(handle, funcs)) {
@@ -264,10 +240,8 @@ namespace platf {
#undef _FN #undef _FN
} // namespace xcb } // namespace xcb
void void freeImage(XImage *);
freeImage(XImage *); void freeX(XFixesCursorImage *);
void
freeX(XFixesCursorImage *);
using xcb_connect_t = util::dyn_safe_ptr<xcb_connection_t, &xcb::disconnect>; using xcb_connect_t = util::dyn_safe_ptr<xcb_connection_t, &xcb::disconnect>;
using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>; using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;
@@ -282,9 +256,13 @@ namespace platf {
class shm_id_t { class shm_id_t {
public: public:
shm_id_t(): shm_id_t():
id { -1 } {} id {-1} {
}
shm_id_t(int id): shm_id_t(int id):
id { id } {} id {id} {
}
shm_id_t(shm_id_t &&other) noexcept: shm_id_t(shm_id_t &&other) noexcept:
id(other.id) { id(other.id) {
other.id = -1; other.id = -1;
@@ -296,15 +274,19 @@ namespace platf {
id = -1; id = -1;
} }
} }
int id; int id;
}; };
class shm_data_t { class shm_data_t {
public: public:
shm_data_t(): shm_data_t():
data { (void *) -1 } {} data {(void *) -1} {
}
shm_data_t(void *data): shm_data_t(void *data):
data { data } {} data {data} {
}
shm_data_t(shm_data_t &&other) noexcept: shm_data_t(shm_data_t &&other) noexcept:
data(other.data) { data(other.data) {
@@ -331,9 +313,8 @@ namespace platf {
} }
}; };
static void static void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) { xcursor_t overlay {x11::fix::GetCursorImage(display)};
xcursor_t overlay { x11::fix::GetCursorImage(display) };
if (!overlay) { if (!overlay) {
BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv; BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv;
@@ -370,8 +351,7 @@ namespace platf {
auto alpha = (*(uint *) pixel_p) >> 24u; auto alpha = (*(uint *) pixel_p) >> 24u;
if (alpha == 255) { if (alpha == 255) {
*pixels_begin = *pixel_p; *pixels_begin = *pixel_p;
} } else {
else {
auto colors_out = (uint8_t *) pixel_p; auto colors_out = (uint8_t *) pixel_p;
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255; colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
@@ -398,18 +378,20 @@ namespace platf {
// int env_width, env_height; // int env_width, env_height;
x11_attr_t(mem_type_e mem_type): x11_attr_t(mem_type_e mem_type):
xdisplay { x11::OpenDisplay(nullptr) }, xwindow {}, xattr {}, mem_type { mem_type } { xdisplay {x11::OpenDisplay(nullptr)},
xwindow {},
xattr {},
mem_type {mem_type} {
x11::InitThreads(); x11::InitThreads();
} }
int int init(const std::string &display_name, const ::video::config_t &config) {
init(const std::string &display_name, const ::video::config_t &config) {
if (!xdisplay) { if (!xdisplay) {
BOOST_LOG(error) << "Could not open X11 display"sv; BOOST_LOG(error) << "Could not open X11 display"sv;
return -1; return -1;
} }
delay = std::chrono::nanoseconds { 1s } / config.framerate; delay = std::chrono::nanoseconds {1s} / config.framerate;
xwindow = DefaultRootWindow(xdisplay.get()); xwindow = DefaultRootWindow(xdisplay.get());
@@ -422,13 +404,13 @@ namespace platf {
if (streamedMonitor != -1) { if (streamedMonitor != -1) {
BOOST_LOG(info) << "Configuring selected display ("sv << streamedMonitor << ") to stream"sv; BOOST_LOG(info) << "Configuring selected display ("sv << streamedMonitor << ") to stream"sv;
screen_res_t screenr { x11::rr::GetScreenResources(xdisplay.get(), xwindow) }; screen_res_t screenr {x11::rr::GetScreenResources(xdisplay.get(), xwindow)};
int output = screenr->noutput; int output = screenr->noutput;
output_info_t result; output_info_t result;
int monitor = 0; int monitor = 0;
for (int x = 0; x < output; ++x) { for (int x = 0; x < output; ++x) {
output_info_t out_info { x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) }; output_info_t out_info {x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x])};
if (out_info) { if (out_info) {
if (monitor++ == streamedMonitor) { if (monitor++ == streamedMonitor) {
result = std::move(out_info); result = std::move(out_info);
@@ -443,7 +425,7 @@ namespace platf {
} }
if (result->crtc) { if (result->crtc) {
crtc_info_t crt_info { x11::rr::GetCrtcInfo(xdisplay.get(), screenr.get(), result->crtc) }; crtc_info_t crt_info {x11::rr::GetCrtcInfo(xdisplay.get(), screenr.get(), result->crtc)};
BOOST_LOG(info) BOOST_LOG(info)
<< "Streaming display: "sv << result->name << " with res "sv << crt_info->width << 'x' << crt_info->height << " offset by "sv << crt_info->x << 'x' << crt_info->y; << "Streaming display: "sv << result->name << " with res "sv << crt_info->width << 'x' << crt_info->height << " offset by "sv << crt_info->x << 'x' << crt_info->y;
@@ -451,14 +433,12 @@ namespace platf {
height = crt_info->height; height = crt_info->height;
offset_x = crt_info->x; offset_x = crt_info->x;
offset_y = crt_info->y; offset_y = crt_info->y;
} } else {
else {
BOOST_LOG(warning) << "Couldn't get requested display info, defaulting to recording entire virtual desktop"sv; BOOST_LOG(warning) << "Couldn't get requested display info, defaulting to recording entire virtual desktop"sv;
width = xattr.width; width = xattr.width;
height = xattr.height; height = xattr.height;
} }
} } else {
else {
width = xattr.width; width = xattr.width;
height = xattr.height; height = xattr.height;
} }
@@ -472,13 +452,11 @@ namespace platf {
/** /**
* Called when the display attributes should change. * Called when the display attributes should change.
*/ */
void void refresh() {
refresh() {
x11::GetWindowAttributes(xdisplay.get(), xwindow, &xattr); // Update xattr's x11::GetWindowAttributes(xdisplay.get(), xwindow, &xattr); // Update xattr's
} }
capture_e capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, 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(); auto next_frame = std::chrono::steady_clock::now();
sleep_overshoot_logger.reset(); sleep_overshoot_logger.reset();
@@ -523,8 +501,7 @@ namespace platf {
return capture_e::ok; return capture_e::ok;
} }
capture_e capture_e 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) {
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(); refresh();
// The whole X server changed, so we must reinit everything // The whole X server changed, so we must reinit everything
@@ -538,7 +515,7 @@ namespace platf {
} }
auto img = (x11_img_t *) img_out.get(); auto img = (x11_img_t *) img_out.get();
XImage *x_img { x11::GetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) }; XImage *x_img {x11::GetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap)};
img->frame_timestamp = std::chrono::steady_clock::now(); img->frame_timestamp = std::chrono::steady_clock::now();
img->width = x_img->width; img->width = x_img->width;
@@ -555,13 +532,11 @@ namespace platf {
return capture_e::ok; return capture_e::ok;
} }
std::shared_ptr<img_t> std::shared_ptr<img_t> alloc_img() override {
alloc_img() override {
return std::make_shared<x11_img_t>(); return std::make_shared<x11_img_t>();
} }
std::unique_ptr<avcodec_encode_device_t> std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI #ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == mem_type_e::vaapi) { if (mem_type == mem_type_e::vaapi) {
return va::make_avcodec_encode_device(width, height, false); return va::make_avcodec_encode_device(width, height, false);
@@ -577,8 +552,7 @@ namespace platf {
return std::make_unique<avcodec_encode_device_t>(); return std::make_unique<avcodec_encode_device_t>();
} }
int int dummy_img(img_t *img) override {
dummy_img(img_t *img) override {
// TODO: stop cheating and give black image // TODO: stop cheating and give black image
if (!img) { if (!img) {
return -1; return -1;
@@ -605,15 +579,15 @@ namespace platf {
task_pool_util::TaskPool::task_id_t refresh_task_id; task_pool_util::TaskPool::task_id_t refresh_task_id;
void void delayed_refresh() {
delayed_refresh() {
refresh(); refresh();
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
} }
shm_attr_t(mem_type_e mem_type): shm_attr_t(mem_type_e mem_type):
x11_attr_t(mem_type), shm_xdisplay { x11::OpenDisplay(nullptr) } { x11_attr_t(mem_type),
shm_xdisplay {x11::OpenDisplay(nullptr)} {
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
} }
@@ -621,8 +595,7 @@ namespace platf {
while (!task_pool.cancel(refresh_task_id)); while (!task_pool.cancel(refresh_task_id));
} }
capture_e capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, 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(); auto next_frame = std::chrono::steady_clock::now();
sleep_overshoot_logger.reset(); sleep_overshoot_logger.reset();
@@ -667,18 +640,16 @@ namespace platf {
return capture_e::ok; return capture_e::ok;
} }
capture_e capture_e 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) {
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 // The whole X server changed, so we must reinit everything
if (xattr.width != env_width || xattr.height != env_height) { if (xattr.width != env_width || xattr.height != env_height) {
BOOST_LOG(warning) << "X dimensions changed in SHM mode, request reinit"sv; BOOST_LOG(warning) << "X dimensions changed in SHM mode, request reinit"sv;
return capture_e::reinit; return capture_e::reinit;
} } else {
else {
auto img_cookie = xcb::shm_get_image_unchecked(xcb.get(), display->root, offset_x, offset_y, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0); auto img_cookie = xcb::shm_get_image_unchecked(xcb.get(), display->root, offset_x, offset_y, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0);
auto frame_timestamp = std::chrono::steady_clock::now(); auto frame_timestamp = std::chrono::steady_clock::now();
xcb_img_t img_reply { xcb::shm_get_image_reply(xcb.get(), img_cookie, nullptr) }; xcb_img_t img_reply {xcb::shm_get_image_reply(xcb.get(), img_cookie, nullptr)};
if (!img_reply) { if (!img_reply) {
BOOST_LOG(error) << "Could not get image reply"sv; BOOST_LOG(error) << "Could not get image reply"sv;
return capture_e::reinit; return capture_e::reinit;
@@ -699,8 +670,7 @@ namespace platf {
} }
} }
std::shared_ptr<img_t> std::shared_ptr<img_t> alloc_img() override {
alloc_img() override {
auto img = std::make_shared<shm_img_t>(); auto img = std::make_shared<shm_img_t>();
img->width = width; img->width = width;
img->height = height; img->height = height;
@@ -711,13 +681,11 @@ namespace platf {
return img; return img;
} }
int int dummy_img(platf::img_t *img) override {
dummy_img(platf::img_t *img) override {
return 0; return 0;
} }
int int init(const std::string &display_name, const ::video::config_t &config) {
init(const std::string &display_name, const ::video::config_t &config) {
if (x11_attr_t::init(display_name, config)) { if (x11_attr_t::init(display_name, config)) {
return 1; return 1;
} }
@@ -756,14 +724,12 @@ namespace platf {
return 0; return 0;
} }
std::uint32_t std::uint32_t frame_size() {
frame_size() {
return width * height * 4; return width * height * 4;
} }
}; };
std::shared_ptr<display_t> std::shared_ptr<display_t> x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) { if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize x11 display with the given hw device type"sv; BOOST_LOG(error) << "Could not initialize x11 display with the given hw device type"sv;
return nullptr; return nullptr;
@@ -797,8 +763,7 @@ namespace platf {
return x11_disp; return x11_disp;
} }
std::vector<std::string> std::vector<std::string> x11_display_names() {
x11_display_names() {
if (load_x11() || load_xcb()) { if (load_x11() || load_xcb()) {
BOOST_LOG(error) << "Couldn't init x11 libraries"sv; BOOST_LOG(error) << "Couldn't init x11 libraries"sv;
@@ -807,18 +772,18 @@ namespace platf {
BOOST_LOG(info) << "Detecting displays"sv; BOOST_LOG(info) << "Detecting displays"sv;
x11::xdisplay_t xdisplay { x11::OpenDisplay(nullptr) }; x11::xdisplay_t xdisplay {x11::OpenDisplay(nullptr)};
if (!xdisplay) { if (!xdisplay) {
return {}; return {};
} }
auto xwindow = DefaultRootWindow(xdisplay.get()); auto xwindow = DefaultRootWindow(xdisplay.get());
screen_res_t screenr { x11::rr::GetScreenResources(xdisplay.get(), xwindow) }; screen_res_t screenr {x11::rr::GetScreenResources(xdisplay.get(), xwindow)};
int output = screenr->noutput; int output = screenr->noutput;
int monitor = 0; int monitor = 0;
for (int x = 0; x < output; ++x) { for (int x = 0; x < output; ++x) {
output_info_t out_info { x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) }; output_info_t out_info {x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x])};
if (out_info) { if (out_info) {
BOOST_LOG(info) << "Detected display: "sv << out_info->name << " (id: "sv << monitor << ")"sv << out_info->name << " connected: "sv << (out_info->connection == RR_Connected); BOOST_LOG(info) << "Detected display: "sv << out_info->name << " (id: "sv << monitor << ")"sv << out_info->name << " connected: "sv << (out_info->connection == RR_Connected);
++monitor; ++monitor;
@@ -835,25 +800,22 @@ namespace platf {
return names; return names;
} }
void void freeImage(XImage *p) {
freeImage(XImage *p) {
XDestroyImage(p); XDestroyImage(p);
} }
void
freeX(XFixesCursorImage *p) { void freeX(XFixesCursorImage *p) {
x11::Free(p); x11::Free(p);
} }
int int load_xcb() {
load_xcb() {
// This will be called once only // This will be called once only
static int xcb_status = xcb::init_shm() || xcb::init(); static int xcb_status = xcb::init_shm() || xcb::init();
return xcb_status; return xcb_status;
} }
int int load_x11() {
load_x11() {
// This will be called once only // This will be called once only
static int x11_status = static int x11_status =
window_system == window_system_e::NONE || window_system == window_system_e::NONE ||
@@ -863,8 +825,7 @@ namespace platf {
} }
namespace x11 { namespace x11 {
std::optional<cursor_t> std::optional<cursor_t> cursor_t::make() {
cursor_t::make() {
if (load_x11()) { if (load_x11()) {
return std::nullopt; return std::nullopt;
} }
@@ -876,8 +837,7 @@ namespace platf {
return cursor; return cursor;
} }
void void cursor_t::capture(egl::cursor_t &img) {
cursor_t::capture(egl::cursor_t &img) {
auto display = (xdisplay_t::pointer) ctx.get(); auto display = (xdisplay_t::pointer) ctx.get();
xcursor_t xcursor = fix::GetCursorImage(display); xcursor_t xcursor = fix::GetCursorImage(display);
@@ -904,23 +864,19 @@ namespace platf {
img.serial = xcursor->cursor_serial; img.serial = xcursor->cursor_serial;
} }
void void cursor_t::blend(img_t &img, int offsetX, int offsetY) {
cursor_t::blend(img_t &img, int offsetX, int offsetY) {
blend_cursor((xdisplay_t::pointer) ctx.get(), img, offsetX, offsetY); blend_cursor((xdisplay_t::pointer) ctx.get(), img, offsetX, offsetY);
} }
xdisplay_t xdisplay_t make_display() {
make_display() {
return OpenDisplay(nullptr); return OpenDisplay(nullptr);
} }
void void freeDisplay(_XDisplay *xdisplay) {
freeDisplay(_XDisplay *xdisplay) {
CloseDisplay(xdisplay); CloseDisplay(xdisplay);
} }
void void freeCursorCtx(cursor_ctx_t::pointer ctx) {
freeCursorCtx(cursor_ctx_t::pointer ctx) {
CloseDisplay((xdisplay_t::pointer) ctx); CloseDisplay((xdisplay_t::pointer) ctx);
} }
} // namespace x11 } // namespace x11

View File

@@ -4,8 +4,10 @@
*/ */
#pragma once #pragma once
// standard includes
#include <optional> #include <optional>
// local includes
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/utility.h" #include "src/utility.h"
@@ -18,21 +20,17 @@ namespace egl {
namespace platf::x11 { namespace platf::x11 {
struct cursor_ctx_raw_t; struct cursor_ctx_raw_t;
void void freeCursorCtx(cursor_ctx_raw_t *ctx);
freeCursorCtx(cursor_ctx_raw_t *ctx); void freeDisplay(_XDisplay *xdisplay);
void
freeDisplay(_XDisplay *xdisplay);
using cursor_ctx_t = util::safe_ptr<cursor_ctx_raw_t, freeCursorCtx>; using cursor_ctx_t = util::safe_ptr<cursor_ctx_raw_t, freeCursorCtx>;
using xdisplay_t = util::safe_ptr<_XDisplay, freeDisplay>; using xdisplay_t = util::safe_ptr<_XDisplay, freeDisplay>;
class cursor_t { class cursor_t {
public: public:
static std::optional<cursor_t> static std::optional<cursor_t> make();
make();
void void capture(egl::cursor_t &img);
capture(egl::cursor_t &img);
/** /**
* Capture and blend the cursor into the image * Capture and blend the cursor into the image
@@ -40,12 +38,10 @@ namespace platf::x11 {
* img <-- destination image * img <-- destination image
* offsetX, offsetY <--- Top left corner of the virtual screen * offsetX, offsetY <--- Top left corner of the virtual screen
*/ */
void void blend(img_t &img, int offsetX, int offsetY);
blend(img_t &img, int offsetX, int offsetY);
cursor_ctx_t ctx; cursor_ctx_t ctx;
}; };
xdisplay_t xdisplay_t make_display();
make_display();
} // namespace platf::x11 } // namespace platf::x11

View File

@@ -4,8 +4,10 @@
*/ */
#pragma once #pragma once
// platform includes
#import <AVFoundation/AVFoundation.h> #import <AVFoundation/AVFoundation.h>
// lib includes
#include "third-party/TPCircularBuffer/TPCircularBuffer.h" #include "third-party/TPCircularBuffer/TPCircularBuffer.h"
#define kBufferLength 4096 #define kBufferLength 4096

View File

@@ -2,12 +2,13 @@
* @file src/platform/macos/av_audio.m * @file src/platform/macos/av_audio.m
* @brief Definitions for audio capture on macOS. * @brief Definitions for audio capture on macOS.
*/ */
// local includes
#import "av_audio.h" #import "av_audio.h"
@implementation AVAudio @implementation AVAudio
+ (NSArray<AVCaptureDevice *> *)microphones { + (NSArray<AVCaptureDevice *> *)microphones {
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) { 10, 15, 0 })]) { if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) {10, 15, 0})]) {
// This will generate a warning about AVCaptureDeviceDiscoverySession being // This will generate a warning about AVCaptureDeviceDiscoverySession being
// unavailable before macOS 10.15, but we have a guard to prevent it from // unavailable before macOS 10.15, but we have a guard to prevent it from
// being called on those earlier systems. // being called on those earlier systems.
@@ -16,14 +17,12 @@
// a different method. // a different method.
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new" #pragma clang diagnostic ignored "-Wunguarded-availability-new"
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInMicrophone, AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInMicrophone, AVCaptureDeviceTypeExternalUnknown]
AVCaptureDeviceTypeExternalUnknown]
mediaType:AVMediaTypeAudio mediaType:AVMediaTypeAudio
position:AVCaptureDevicePositionUnspecified]; position:AVCaptureDevicePositionUnspecified];
return discoverySession.devices; return discoverySession.devices;
#pragma clang diagnostic pop #pragma clang diagnostic pop
} } else {
else {
// We're intentionally using a deprecated API here specifically for versions // We're intentionally using a deprecated API here specifically for versions
// of macOS where it's not deprecated, so we can ignore any deprecation // of macOS where it's not deprecated, so we can ignore any deprecation
// warnings: // warnings:
@@ -75,8 +74,7 @@
if ([self.audioCaptureSession canAddInput:audioInput]) { if ([self.audioCaptureSession canAddInput:audioInput]) {
[self.audioCaptureSession addInput:audioInput]; [self.audioCaptureSession addInput:audioInput];
} } else {
else {
[audioInput dealloc]; [audioInput dealloc];
return -1; return -1;
} }
@@ -92,17 +90,14 @@
(NSString *) AVLinearPCMIsNonInterleaved: @NO (NSString *) AVLinearPCMIsNonInterleaved: @NO
}]; }];
dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INITIATED, DISPATCH_QUEUE_PRIORITY_HIGH);
QOS_CLASS_USER_INITIATED,
DISPATCH_QUEUE_PRIORITY_HIGH);
dispatch_queue_t recordingQueue = dispatch_queue_create("audioSamplingQueue", qos); dispatch_queue_t recordingQueue = dispatch_queue_create("audioSamplingQueue", qos);
[audioOutput setSampleBufferDelegate:self queue:recordingQueue]; [audioOutput setSampleBufferDelegate:self queue:recordingQueue];
if ([self.audioCaptureSession canAddOutput:audioOutput]) { if ([self.audioCaptureSession canAddOutput:audioOutput]) {
[self.audioCaptureSession addOutput:audioOutput]; [self.audioCaptureSession addOutput:audioOutput];
} } else {
else {
[audioInput release]; [audioInput release];
[audioOutput release]; [audioOutput release];
return -1; return -1;

View File

@@ -4,17 +4,20 @@
*/ */
#pragma once #pragma once
#include "src/platform/common.h" // platform includes
#include <CoreMedia/CoreMedia.h> #include <CoreMedia/CoreMedia.h>
#include <CoreVideo/CoreVideo.h> #include <CoreVideo/CoreVideo.h>
// local includes
#include "src/platform/common.h"
namespace platf { namespace platf {
struct av_sample_buf_t { struct av_sample_buf_t {
CMSampleBufferRef buf; CMSampleBufferRef buf;
explicit av_sample_buf_t(CMSampleBufferRef buf): explicit av_sample_buf_t(CMSampleBufferRef buf):
buf((CMSampleBufferRef) CFRetain(buf)) {} buf((CMSampleBufferRef) CFRetain(buf)) {
}
~av_sample_buf_t() { ~av_sample_buf_t() {
if (buf != nullptr) { if (buf != nullptr) {
@@ -29,12 +32,12 @@ namespace platf {
// Constructor // Constructor
explicit av_pixel_buf_t(CMSampleBufferRef sb): explicit av_pixel_buf_t(CMSampleBufferRef sb):
buf( buf(
CMSampleBufferGetImageBuffer(sb)) { CMSampleBufferGetImageBuffer(sb)
) {
CVPixelBufferLockBaseAddress(buf, kCVPixelBufferLock_ReadOnly); CVPixelBufferLockBaseAddress(buf, kCVPixelBufferLock_ReadOnly);
} }
[[nodiscard]] uint8_t * [[nodiscard]] uint8_t *data() const {
data() const {
return static_cast<uint8_t *>(CVPixelBufferGetBaseAddress(buf)); return static_cast<uint8_t *>(CVPixelBufferGetBaseAddress(buf));
} }
@@ -59,8 +62,11 @@ namespace platf {
temp_retain_av_img_t( temp_retain_av_img_t(
std::shared_ptr<av_sample_buf_t> sb, std::shared_ptr<av_sample_buf_t> sb,
std::shared_ptr<av_pixel_buf_t> pb, std::shared_ptr<av_pixel_buf_t> pb,
uint8_t *dt): uint8_t *dt
):
sample_buffer(std::move(sb)), sample_buffer(std::move(sb)),
pixel_buffer(std::move(pb)), data(dt) {} pixel_buffer(std::move(pb)),
data(dt) {
}
}; };
} // namespace platf } // namespace platf

View File

@@ -4,8 +4,9 @@
*/ */
#pragma once #pragma once
#import <AVFoundation/AVFoundation.h> // platform includes
#import <AppKit/AppKit.h> #import <AppKit/AppKit.h>
#import <AVFoundation/AVFoundation.h>
struct CaptureSession { struct CaptureSession {
AVCaptureVideoDataOutput *output; AVCaptureVideoDataOutput *output;

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