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

View File

@@ -149,6 +149,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${CMAKE_THREAD_LIBS_INIT}
enet
libdisplaydevice::display_device
nlohmann_json::nlohmann_json
opus
${FFMPEG_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 SUNSHINE_EXTERNAL_LIBRARIES ${APPINDICATOR_LIBRARIES} ${LIBNOTIFY_LIBRARIES})
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()
set(SUNSHINE_TRAY 0)
message(STATUS "Tray icon disabled")
endif()
if(${SUNSHINE_USE_LEGACY_INPUT}) # TODO: Remove this legacy option after the next stable release
list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/platform/linux/input/legacy_input.cpp")
else()
# 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}")
# 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")
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES inputtino::libinputtino)
file(GLOB_RECURSE INPUTTINO_SOURCES
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.h
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.cpp)
list(APPEND PLATFORM_TARGET_FILES ${INPUTTINO_SOURCES})
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/inputtino")
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES inputtino::libinputtino)
file(GLOB_RECURSE INPUTTINO_SOURCES
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.h
${CMAKE_SOURCE_DIR}/src/platform/linux/input/inputtino*.cpp)
list(APPEND PLATFORM_TARGET_FILES ${INPUTTINO_SOURCES})
# build libevdev before the libinputtino target
if(EXTERNAL_PROJECT_LIBEVDEV_USED)
add_dependencies(libinputtino libevdev)
endif()
# build libevdev before the libinputtino target
if(EXTERNAL_PROJECT_LIBEVDEV_USED)
add_dependencies(libinputtino libevdev)
endif()
# AppImage and Flatpak

View File

@@ -82,7 +82,6 @@ list(PREPEND PLATFORM_LIBRARIES
libstdc++.a
libwinpthread.a
minhook::minhook
nlohmann_json::nlohmann_json
ntdll
setupapi
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")
# common dependencies
include("${CMAKE_MODULE_PATH}/dependencies/nlohmann_json.cmake")
find_package(OpenSSL REQUIRED)
find_package(PkgConfig 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
# nlohmann_json
find_package(nlohmann_json CONFIG 3.11 REQUIRED)
# Make sure MinHook is installed
find_library(MINHOOK_LIBRARY libMinHook.a REQUIRED)
find_path(MINHOOK_INCLUDE_DIR MinHook.h PATH_SUFFIXES include REQUIRED)

View File

@@ -100,15 +100,31 @@ endif()
# tray icon
if(${SUNSHINE_TRAY} STREQUAL 1)
install(FILES "${CMAKE_SOURCE_DIR}/apollo.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "apollo-tray.svg")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/apollo-playing.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/apollo-pausing.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/apollo-locked.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
if(NOT ${SUNSHINE_BUILD_FLATPAK})
install(FILES "${CMAKE_SOURCE_DIR}/apollo.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
RENAME "apollo-tray.svg")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/apollo-playing.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/apollo-pausing.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/apollo-locked.svg"
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 "\
${CPACK_DEBIAN_PACKAGE_DEPENDS}, \
@@ -128,15 +144,8 @@ else()
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
RENAME "${PROJECT_FQDN}.desktop")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_kms.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
RENAME "${PROJECT_FQDN}_kms.desktop")
endif()
if(${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})
if(NOT ${SUNSHINE_BUILD_APPIMAGE} AND NOT ${SUNSHINE_BUILD_FLATPAK})
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine_terminal.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
endif()

View File

@@ -8,10 +8,10 @@ elseif (UNIX)
endif()
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}")
else()
set(SUNSHINE_SERVICE_START_COMMAND "ExecStart=${SUNSHINE_EXECUTABLE_PATH}")
set(SUNSHINE_SERVICE_STOP_COMMAND "")
endif()
endif ()
endif()

View File

@@ -65,6 +65,4 @@ elseif(UNIX) # Linux
"Enable building wayland specific code." ON)
option(SUNSHINE_ENABLE_X11
"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()

View File

@@ -14,10 +14,8 @@ elseif(UNIX)
if(${SUNSHINE_BUILD_APPIMAGE})
configure_file(packaging/linux/AppImage/sunshine.desktop sunshine.desktop @ONLY)
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_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
${PROJECT_FQDN}.metainfo.xml @ONLY)
else()

View File

@@ -12,17 +12,23 @@ basic authentication with the admin username and password.
## GET /api/apps
@copydoc confighttp::getApps()
## GET /api/logs
@copydoc confighttp::getLogs()
## POST /api/apps
@copydoc confighttp::saveApp()
## POST /api/apps/close
@copydoc confighttp::closeApp()
## DELETE /api/apps/{index}
@copydoc confighttp::deleteApp()
## POST /api/covers/upload
@copydoc confighttp::uploadCover()
## GET /api/clients/list
@copydoc confighttp::getClients()
## POST /api/clients/unpair
@copydoc confighttp::unpair()
## POST /api/clients/unpair-all
@copydoc confighttp::unpairAll()
## GET /api/config
@copydoc confighttp::getConfig()
@@ -33,11 +39,11 @@ basic authentication with the admin username and password.
## POST /api/config
@copydoc confighttp::saveConfig()
## POST /api/restart
@copydoc confighttp::restart()
## POST /api/covers/upload
@copydoc confighttp::uploadCover()
## POST /api/reset-display-device-persistence
@copydoc confighttp::resetDisplayDevicePersistence()
## GET /api/logs
@copydoc confighttp::getLogs()
## POST /api/password
@copydoc confighttp::savePassword()
@@ -45,17 +51,11 @@ basic authentication with the admin username and password.
## POST /api/pin
@copydoc confighttp::savePin()
## POST /api/clients/unpair-all
@copydoc confighttp::unpairAll()
## POST /api/reset-display-device-persistence
@copydoc confighttp::resetDisplayDevicePersistence()
## POST /api/clients/unpair
@copydoc confighttp::unpair()
## GET /api/clients/list
@copydoc confighttp::listClients()
## POST /api/apps/close
@copydoc confighttp::closeApp()
## POST /api/restart
@copydoc confighttp::restart()
<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
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**
```json
{
"name": "Game With AntiCheat that Requires Admin",
"output": "",
"cmd": "ping 127.0.0.1",
"exclude-global-prep-cmd": "false",
"elevated": "true",
"exclude-global-prep-cmd": false,
"elevated": true,
"prep-cmd": [
{
"do": "powershell.exe -command \"Start-Streaming\"",
"undo": "powershell.exe -command \"Stop-Streaming\"",
"elevated": "false"
"elevated": false
}
],
"image-path": ""

View File

@@ -92,7 +92,6 @@ dependencies=(
"mingw-w64-ucrt-x86_64-graphviz" # Optional, for docs
"mingw-w64-ucrt-x86_64-MinHook"
"mingw-w64-ucrt-x86_64-miniupnpc"
"mingw-w64-ucrt-x86_64-nlohmann-json"
"mingw-w64-ucrt-x86_64-nodejs"
"mingw-w64-ucrt-x86_64-nsis"
"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>
<td>Example</td>
<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>
</tr>
</table>
@@ -974,7 +974,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}verify_only@endcode</td>
<td colspan="2">@code{}
disabled
@endcode</td>
</tr>
<tr>
<td>Example</td>
@@ -1203,6 +1205,31 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
</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
<table>

View File

@@ -1,7 +1,7 @@
# Guides
@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
[LizardByte.github.io](https://github.com/LizardByte/LizardByte.github.io).}

View File

@@ -29,14 +29,18 @@
</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: 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>
<releases>
<release version="@PROJECT_VERSION@" date="1970-01-01"></release>
<release version="@PROJECT_VERSION@" date="1990-01-01"></release>
</releases>
<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]
Type=Application
Name=@PROJECT_NAME@
Exec=@PROJECT_FQDN@
Version=1.0
Categories=AudioVideo;Network;RemoteAccess;
Comment=@PROJECT_DESCRIPTION@
Exec=sunshine.sh
Icon=@SUNSHINE_DESKTOP_ICON@
Keywords=gamestream;stream;moonlight;remote play;
Categories=AudioVideo;Network;RemoteAccess;
Actions=RunInTerminal;KMS;
[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
Name=@PROJECT_NAME@
Type=Application
Version=1.0

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
appimage_build=0
num_processors=$(nproc)
publisher_name="Third Party Publisher"
publisher_website=""
publisher_issue_url="https://app.lizardbyte.dev/support"
@@ -27,6 +28,7 @@ Options:
-h, --help Display this help message.
-s, --sudo-off Disable sudo command.
--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-website The URL of the publisher's website.
--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
skip_libva=1
;;
num-processors=*)
num_processors="${OPTARG#*=}"
;;
publisher-name=*)
publisher_name="${OPTARG#*=}"
;;
@@ -373,7 +378,7 @@ function run_install() {
tar -xzf "${build_dir}/doxygen.tar.gz"
cd "doxygen-${doxygen_min}"
cmake -DCMAKE_BUILD_TYPE=Release -G="Ninja" -B="build" -S="."
ninja -C "build"
ninja -C "build" -j"${num_processors}"
ninja -C "build" install
else
echo "Doxygen version too low, skipping docs"

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,7 @@
* @brief Definitions for FFmpeg Coded Bitstream API.
*/
extern "C" {
// lib includes
#include <libavcodec/avcodec.h>
#include <cbs/cbs_h264.h>
#include <cbs/cbs_h265.h>
@@ -10,14 +11,15 @@ extern "C" {
#include <libavutil/pixdesc.h>
}
// local includes
#include "cbs.h"
#include "logging.h"
#include "utility.h"
using namespace std::literals;
namespace cbs {
void
close(CodedBitstreamContext *c) {
void close(CodedBitstreamContext *c) {
ff_cbs_close(&c);
}
@@ -36,8 +38,7 @@ namespace cbs {
std::fill_n((std::uint8_t *) this, sizeof(*this), 0);
}
frag_t &
operator=(frag_t &&o) {
frag_t &operator=(frag_t &&o) {
std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this);
o.data = nullptr;
@@ -53,12 +54,11 @@ namespace cbs {
}
};
util::buffer_t<std::uint8_t>
write(cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
util::buffer_t<std::uint8_t> write(cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::frag_t frag;
auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr);
if (err < 0) {
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);
return {};
@@ -66,29 +66,27 @@ namespace cbs {
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag);
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);
return {};
}
// 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));
return data;
}
util::buffer_t<std::uint8_t>
write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::ctx_t cbs_ctx;
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
return write(cbs_ctx, nal, uh, codec_id);
}
h264_t
make_sps_h264(const AVCodecContext *avctx, const AVPacket *packet) {
h264_t make_sps_h264(const AVCodecContext *avctx, const AVPacket *packet) {
cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
return {};
@@ -98,7 +96,7 @@ namespace cbs {
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
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);
return {};
@@ -144,8 +142,7 @@ namespace cbs {
};
}
hevc_t
make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) {
return {};
@@ -155,7 +152,7 @@ namespace cbs {
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
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);
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.
* This is done for both H264 and H265 codecs.
*/
bool
validate_sps(const AVPacket *packet, int codec_id) {
bool validate_sps(const AVPacket *packet, int codec_id) {
cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, (AVCodecID) codec_id, nullptr)) {
return false;
@@ -233,7 +229,7 @@ namespace cbs {
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
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);
return false;

View File

@@ -4,6 +4,7 @@
*/
#pragma once
// local includes
#include "utility.h"
struct AVPacket;
@@ -25,10 +26,8 @@ namespace cbs {
nal_t sps;
};
hevc_t
make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet);
h264_t
make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet);
hevc_t make_sps_hevc(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.
@@ -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).
* @return True if the SPS->VUI is present in the active SPS of the packet, false otherwise.
*/
bool
validate_sps(const AVPacket *packet, int codec_id);
bool validate_sps(const AVPacket *packet, int codec_id);
} // namespace cbs

View File

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

View File

@@ -4,6 +4,7 @@
*/
#pragma once
// standard includes
#include <bitset>
#include <chrono>
#include <optional>
@@ -11,6 +12,7 @@
#include <unordered_map>
#include <vector>
// local includes
#include "nvenc/nvenc_config.h"
namespace config {
@@ -25,6 +27,7 @@ namespace config {
int av1_mode;
int min_threads; // Minimum number of threads/slices for CPU encoding
struct {
std::string sw_preset;
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`.
hdr_option_e hdr_option;
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;
workarounds_t wa;
} dd;
@@ -212,13 +216,20 @@ namespace config {
CONST_PIN, ///< Use "universal" pin
FLAG_SIZE ///< Number of flags
};
}
} // namespace flag
struct prep_cmd_t {
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) {}
explicit prep_cmd_t(std::string &&do_cmd, bool elevated):
do_cmd(std::move(do_cmd)), elevated(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(std::move(elevated)) {
}
explicit prep_cmd_t(std::string &&do_cmd, bool &&elevated):
do_cmd(std::move(do_cmd)),
elevated(std::move(elevated)) {
}
std::string do_cmd;
std::string undo_cmd;
bool elevated;
@@ -226,7 +237,10 @@ namespace config {
struct server_cmd_t {
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_val;
bool elevated;
@@ -268,8 +282,6 @@ namespace config {
extern input_t input;
extern sunshine_t sunshine;
int
parse(int argc, char *argv[]);
std::unordered_map<std::string, std::string>
parse_config(const std::string_view &file_content);
int parse(int argc, char *argv[]);
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content);
} // namespace config

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -29,15 +29,15 @@
namespace display_device {
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(...)`.
*/
struct {
std::mutex mutex {};
std::chrono::milliseconds config_revert_delay { 0 };
std::unique_ptr<RetryScheduler<SettingsManagerInterface>> sm_instance { nullptr };
std::chrono::milliseconds config_revert_delay {0};
std::unique_ptr<RetryScheduler<SettingsManagerInterface>> sm_instance {nullptr};
} DD_DATA;
/**
@@ -49,8 +49,7 @@ namespace display_device {
*/
class sunshine_audio_context_t: public AudioContextInterface {
public:
[[nodiscard]] bool
capture() override {
[[nodiscard]] bool capture() override {
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.
audio_context = boost::none;
@@ -61,8 +60,7 @@ namespace display_device {
});
}
[[nodiscard]] bool
isCaptured() const override {
[[nodiscard]] bool isCaptured() const override {
return context_scheduler.execute([](const auto &audio_context) {
if (audio_context) {
// In case we still have context we need to check whether it was released or not.
@@ -74,8 +72,7 @@ namespace display_device {
});
}
void
release() override {
void release() override {
context_scheduler.schedule([](auto &audio_context, auto &stop_token) {
if (audio_context) {
audio_context->released = true;
@@ -93,7 +90,7 @@ namespace display_device {
audio_context = boost::none;
stop_token.requestStop();
},
SchedulerOptions { .m_sleep_durations = { 2s } });
SchedulerOptions {.m_sleep_durations = {2s}});
}
private:
@@ -102,20 +99,20 @@ namespace display_device {
* @brief A reference to the audio context that will automatically extend the audio session.
* @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.
*/
bool released { false };
bool released {false};
/**
* @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
* @return Parsed unsigned integer.
*/
unsigned int
stou(const std::string &value) {
unsigned long result { std::stoul(value) };
unsigned int stou(const std::string &value) {
unsigned long result {std::stoul(value)};
if (result > std::numeric_limits<unsigned int>::max()) {
throw std::out_of_range("stou");
}
@@ -151,10 +147,9 @@ namespace display_device {
* }
* @examples_end
*/
bool
parse_resolution_string(const std::string &input, std::optional<Resolution> &output) {
const std::string trimmed_input { boost::algorithm::trim_copy(input) };
const std::regex resolution_regex { R"(^(\d+)x(\d+)$)" };
bool parse_resolution_string(const std::string &input, std::optional<Resolution> &output) {
const std::string trimmed_input {boost::algorithm::trim_copy(input)};
const std::regex resolution_regex {R"(^(\d+)x(\d+)$)"};
if (std::smatch match; std::regex_match(trimmed_input, match, resolution_regex)) {
try {
@@ -163,16 +158,13 @@ namespace display_device {
stou(match[2].str())
};
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).";
}
catch (const std::exception &err) {
} catch (const std::exception &err) {
BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << ":\n"
<< err.what();
}
}
else {
} else {
if (trimmed_input.empty()) {
output = std::nullopt;
return true;
@@ -203,16 +195,17 @@ namespace display_device {
* }
* @examples_end
*/
bool
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) { 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+)$)" };
bool 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) {
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+)$)"};
if (std::smatch match; std::regex_match(trimmed_input, match, refresh_rate_regex)) {
try {
// 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()) {
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
// We are essentially removing the decimal point here: 59.995 -> 59995
const std::string numerator_str { trimmed_match_1 + trimmed_match_2 };
const auto numerator { stou(numerator_str) };
const std::string numerator_str {trimmed_match_1 + trimmed_match_2};
const auto numerator {stou(numerator_str)};
// 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 };
}
else {
output = Rational {numerator, denominator};
} else {
// We do not have a decimal point, just a valid number.
// For example:
// 60:
// numerator = 60
// denominator = 1
output = Rational { stou(trimmed_match_1), 1 };
output = Rational {stou(trimmed_match_1), 1};
}
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).";
}
catch (const std::exception &err) {
} catch (const std::exception &err) {
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << ":\n"
<< err.what();
}
}
else {
} else {
if (trimmed_input.empty()) {
output = std::nullopt;
return true;
@@ -279,8 +268,7 @@ namespace display_device {
* const auto device_prep_option = parse_device_prep_option(video_config);
* @examples_end
*/
std::optional<SingleDisplayConfiguration::DevicePreparation>
parse_device_prep_option(const config::video_t &video_config) {
std::optional<SingleDisplayConfiguration::DevicePreparation> parse_device_prep_option(const config::video_t &video_config) {
using enum config::video_t::dd_t::config_option_e;
using enum SingleDisplayConfiguration::DevicePreparation;
@@ -315,44 +303,42 @@ namespace display_device {
* const bool success = parse_resolution_option(video_config, *launch_session, config);
* @examples_end
*/
bool
parse_resolution_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
bool 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;
switch (video_config.dd.resolution_option) {
case resolution_option_e::automatic: {
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.)";
}
else if (session.width >= 0 && session.height >= 0) {
config.m_resolution = Resolution {
static_cast<unsigned int>(session.width),
static_cast<unsigned int>(session.height)
};
}
else {
BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height;
return false;
}
break;
}
case resolution_option_e::manual: {
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.)";
}
else {
if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) {
BOOST_LOG(error) << "Failed to parse manual resolution string!";
case resolution_option_e::automatic:
{
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.)";
} else if (session.width >= 0 && session.height >= 0) {
config.m_resolution = Resolution {
static_cast<unsigned int>(session.width),
static_cast<unsigned int>(session.height)
};
} else {
BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height;
return false;
}
break;
}
case resolution_option_e::manual:
{
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.)";
} else {
if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) {
BOOST_LOG(error) << "Failed to parse manual resolution string!";
return false;
}
if (!config.m_resolution) {
BOOST_LOG(error) << "Manual resolution must be specified!";
return false;
if (!config.m_resolution) {
BOOST_LOG(error) << "Manual resolution must be specified!";
return false;
}
}
break;
}
break;
}
case resolution_option_e::disabled:
break;
}
@@ -375,33 +361,33 @@ namespace display_device {
* const bool success = parse_refresh_rate_option(video_config, *launch_session, config);
* @examples_end
*/
bool
parse_refresh_rate_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
bool 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;
switch (video_config.dd.refresh_rate_option) {
case refresh_rate_option_e::automatic: {
if (session.fps >= 0) {
config.m_refresh_rate = Rational { static_cast<unsigned int>(session.fps), 1 };
}
else {
BOOST_LOG(error) << "FPS value provided by client session config is invalid: " << session.fps;
return false;
}
break;
}
case refresh_rate_option_e::manual: {
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!";
return false;
case refresh_rate_option_e::automatic:
{
if (session.fps >= 0) {
config.m_refresh_rate = Rational {static_cast<unsigned int>(session.fps), 1};
} else {
BOOST_LOG(error) << "FPS value provided by client session config is invalid: " << session.fps;
return false;
}
break;
}
case refresh_rate_option_e::manual:
{
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!";
return false;
}
if (!config.m_refresh_rate) {
BOOST_LOG(error) << "Manual refresh rate must be specified!";
return false;
if (!config.m_refresh_rate) {
BOOST_LOG(error) << "Manual refresh rate must be specified!";
return false;
}
break;
}
break;
}
case refresh_rate_option_e::disabled:
break;
}
@@ -422,8 +408,7 @@ namespace display_device {
* const auto hdr_option = parse_hdr_option(video_config, *launch_session);
* @examples_end
*/
std::optional<HdrState>
parse_hdr_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
std::optional<HdrState> 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;
switch (video_config.dd.hdr_option) {
@@ -450,11 +435,10 @@ namespace display_device {
* @param video_config User's video related configuration.
* @returns Enum value if remapping can be performed, null optional if remapping shall be skipped.
*/
std::optional<remapping_type_e>
determine_remapping_type(const config::video_t &video_config) {
std::optional<remapping_type_e> determine_remapping_type(const config::video_t &video_config) {
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_refresh_rate { video_config.dd.refresh_rate_option == dd_t::refresh_rate_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};
if (auto_resolution && auto_refresh_rate) {
return remapping_type_e::mixed;
@@ -486,8 +470,7 @@ namespace display_device {
* @param type Remapping type to check.
* @returns True if resolution is to be mapped, false otherwise.
*/
bool
is_resolution_mapped(const remapping_type_e type) {
bool is_resolution_mapped(const remapping_type_e type) {
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.
* @returns True if FPS is to be mapped, false otherwise.
*/
bool
is_fps_mapped(const remapping_type_e type) {
bool is_fps_mapped(const remapping_type_e type) {
return type == remapping_type_e::refresh_rate_only || type == remapping_type_e::mixed;
}
@@ -507,17 +489,16 @@ namespace display_device {
* @param type Specify which entry fields should be parsed.
* @returns Parsed structure or null optional if a necessary field could not be parsed.
*/
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) {
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) {
parsed_remapping_entry_t result {};
if (is_resolution_mapped(type) && (!parse_resolution_string(entry.requested_resolution, result.requested_resolution) ||
!parse_resolution_string(entry.final_resolution, result.final_resolution))) {
!parse_resolution_string(entry.final_resolution, result.final_resolution))) {
return std::nullopt;
}
if (is_fps_mapped(type) && (!parse_refresh_rate_string(entry.requested_fps, result.requested_fps, false) ||
!parse_refresh_rate_string(entry.final_refresh_rate, result.final_refresh_rate))) {
!parse_refresh_rate_string(entry.final_refresh_rate, result.final_refresh_rate))) {
return std::nullopt;
}
@@ -539,14 +520,13 @@ namespace display_device {
* const bool success = remap_display_mode_if_needed(video_config, *launch_session, config);
* @examples_end
*/
bool
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) };
bool 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)};
if (!remapping_type) {
return true;
}
const auto &remapping_list { [&]() {
const auto &remapping_list {[&]() {
using enum remapping_type_e;
switch (*remapping_type) {
@@ -558,7 +538,7 @@ namespace display_device {
default:
return video_config.dd.mode_remapping.mixed;
}
}() };
}()};
if (remapping_list.empty()) {
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...";
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_fps { is_fps_mapped(type) };
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_fps {is_fps_mapped(type)};
// clang-format off
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_fps ? " - final refresh rate: "s + entry.final_refresh_rate : "");
// clang-format on
} };
}};
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) {
BOOST_LOG(error) << "Failed to parse remapping entry from:\n"
<< entry_to_string(entry);
@@ -632,16 +612,18 @@ namespace display_device {
* @param video_config User's video related configuration.
* @return An interface or nullptr if the OS does not support the interface.
*/
std::unique_ptr<SettingsManagerInterface>
make_settings_manager([[maybe_unused]] const std::filesystem::path &persistence_filepath, [[maybe_unused]] const config::video_t &video_config) {
std::unique_ptr<SettingsManagerInterface> make_settings_manager([[maybe_unused]] const std::filesystem::path &persistence_filepath, [[maybe_unused]] const config::video_t &video_config) {
#ifdef _WIN32
return std::make_unique<SettingsManager>(
std::make_shared<WinDisplayDevice>(std::make_shared<WinApiLayer>()),
std::make_shared<sunshine_audio_context_t>(),
std::make_unique<PersistentState>(
std::make_shared<FileSettingsPersistence>(persistence_filepath)),
std::make_shared<FileSettingsPersistence>(persistence_filepath)
),
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
return nullptr;
#endif
@@ -660,17 +642,16 @@ namespace display_device {
* @brief Reverts the configuration based on the provided option.
* @note This is function does not lock mutex.
*/
void
revert_configuration_unlocked(const revert_option_e option) {
void revert_configuration_unlocked(const revert_option_e option) {
if (!DD_DATA.sm_instance) {
// Platform is not supported, nothing to do.
return;
}
// 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()) {
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;
}
@@ -681,17 +662,21 @@ namespace display_device {
return;
}
auto available_devices { [&settings_iface]() {
const auto devices { settings_iface.enumAvailableDevices() };
auto available_devices {[&settings_iface]() {
const auto devices {settings_iface.enumAvailableDevices()};
std::set<std::string> parsed_devices;
std::transform(
std::begin(devices), std::end(devices),
std::begin(devices),
std::end(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;
}() };
}()};
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"
<< toJson(available_devices);
@@ -699,11 +684,10 @@ namespace display_device {
}
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();
return;
}
else if (result == ApiTemporarilyUnavailable) {
} else if (result == ApiTemporarilyUnavailable) {
// Do nothing and retry next time
return;
}
@@ -713,13 +697,12 @@ namespace display_device {
<< toJson(available_devices);
tried_out_devices.swap(available_devices);
},
scheduler_option);
scheduler_option);
}
} // namespace
std::unique_ptr<platf::deinit_t>
init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config) {
std::lock_guard lock { DD_DATA.mutex };
std::unique_ptr<platf::deinit_t> init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config) {
std::lock_guard lock {DD_DATA.mutex};
// 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 (!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
// 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));
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"
<< toJson(available_devices);
@@ -748,37 +733,37 @@ namespace display_device {
class deinit_t: public platf::deinit_t {
public:
~deinit_t() override {
std::lock_guard lock { DD_DATA.mutex };
std::lock_guard lock {DD_DATA.mutex};
try {
// 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,
// we definitely don't want this to happen in destructor. Especially in the
// deinit_t where the outcome does not really matter.
revert_configuration_unlocked(revert_option_e::try_once);
}
catch (std::exception &err) {
} catch (std::exception &err) {
BOOST_LOG(fatal) << err.what();
}
DD_DATA.sm_instance = nullptr;
}
};
return std::make_unique<deinit_t>();
}
std::string
map_output_name(const std::string &output_name) {
std::lock_guard lock { DD_DATA.mutex };
std::string map_output_name(const std::string &output_name) {
std::lock_guard lock {DD_DATA.mutex};
if (!DD_DATA.sm_instance) {
// Fallback to giving back the output name if the platform is not supported.
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
map_display_name(const std::string &display_name) {
std::string map_display_name(const std::string &display_name) {
std::lock_guard lock { DD_DATA.mutex };
if (!DD_DATA.sm_instance) {
return {};
@@ -795,15 +780,14 @@ namespace display_device {
return {};
}
void
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
const auto result { parse_configuration(video_config, session) };
if (const auto *parsed_config { std::get_if<SingleDisplayConfiguration>(&result) }; parsed_config) {
configure_display(*parsed_config);
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();
return;
}
@@ -812,9 +796,8 @@ namespace display_device {
// want to revert active configuration in case we have any
}
void
configure_display(const SingleDisplayConfiguration &config) {
std::lock_guard lock { DD_DATA.mutex };
void configure_display(const SingleDisplayConfiguration &config) {
std::lock_guard lock {DD_DATA.mutex};
if (!DD_DATA.sm_instance) {
// Platform is not supported, nothing to do.
return;
@@ -827,18 +810,16 @@ namespace display_device {
stop_token.requestStop();
}
},
{ .m_sleep_durations = { DEFAULT_RETRY_INTERVAL } });
{.m_sleep_durations = {DEFAULT_RETRY_INTERVAL}});
}
void
revert_configuration() {
std::lock_guard lock { DD_DATA.mutex };
void revert_configuration() {
std::lock_guard lock {DD_DATA.mutex};
revert_configuration_unlocked(revert_option_e::try_indefinitely_with_delay);
}
bool
reset_persistence() {
std::lock_guard lock { DD_DATA.mutex };
bool reset_persistence() {
std::lock_guard lock {DD_DATA.mutex};
if (!DD_DATA.sm_instance) {
// Platform is not supported, assume success.
return true;
@@ -852,9 +833,20 @@ namespace display_device {
});
}
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) };
EnumeratedDeviceList enumerate_devices() {
std::lock_guard lock {DD_DATA.mutex};
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) {
return configuration_disabled_tag_t {};
}

View File

@@ -4,18 +4,22 @@
*/
#pragma once
// lib includes
#include <display_device/types.h>
// standard includes
#include <filesystem>
#include <memory>
// lib includes
#include <display_device/types.h>
// forward declarations
namespace platf {
class deinit_t;
}
namespace config {
struct video_t;
}
namespace rtsp_stream {
struct launch_session_t;
}
@@ -32,8 +36,7 @@ namespace display_device {
* const auto init_guard { init("/my/persitence/file.state", video_config) };
* @examples_end
*/
[[nodiscard]] std::unique_ptr<platf::deinit_t>
init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config);
[[nodiscard]] std::unique_ptr<platf::deinit_t> init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config);
/**
* @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}") };
* @examples_end
*/
[[nodiscard]] std::string
map_output_name(const std::string &output_name);
[[nodiscard]] std::string map_output_name(const std::string &output_name);
[[nodiscard]] std::string
map_display_name(const std::string &display_name);
@@ -65,8 +67,7 @@ namespace display_device {
* configure_display(video_config, *launch_session);
* @examples_end
*/
void
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
/**
* @brief Configure the display device using the provided configuration.
@@ -86,8 +87,7 @@ namespace display_device {
* configure_display(valid_config);
* @examples_end
*/
void
configure_display(const SingleDisplayConfiguration &config);
void configure_display(const SingleDisplayConfiguration &config);
/**
* @brief Revert the display configuration and restore the previous state.
@@ -99,8 +99,7 @@ namespace display_device {
* revert_configuration();
* @examples_end
*/
void
revert_configuration();
void revert_configuration();
/**
* @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
* do it manually.
*
* @return
* @note Whether the function succeeds or fails, the any of the scheduled "retries" from
* @return True if persistence was reset, false otherwise.
* @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.
*
* @examples
* const auto result = reset_persistence();
* @examples_end
*/
bool
reset_persistence();
bool 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.
@@ -153,6 +161,5 @@ namespace display_device {
* }
* @examples_end
*/
[[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);
[[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);
} // namespace display_device

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@
*/
#pragma once
// local includes
#include "entry_handler.h"
#include "thread_pool.h"
@@ -31,9 +32,9 @@ extern nvprefs::nvprefs_interface nvprefs_instance;
* @brief Handles process-wide communication.
*/
namespace mail {
#define MAIL(x) \
#define MAIL(x) \
constexpr auto x = std::string_view { \
#x \
#x \
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,11 +41,9 @@ namespace logging {
* deinit();
* @examples_end
*/
void
deinit();
void deinit();
void
formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os);
void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os);
/**
* @brief Initialize the logging system.
@@ -56,22 +54,19 @@ namespace logging {
* log_init(2, "sunshine.log");
* @examples_end
*/
[[nodiscard]] std::unique_ptr<deinit_t>
init(int min_log_level, const std::string &log_file);
[[nodiscard]] std::unique_ptr<deinit_t> init(int min_log_level, const std::string &log_file);
/**
* @brief Setup AV logging.
* @param min_log_level The log level.
*/
void
setup_av_logging(int min_log_level);
void setup_av_logging(int min_log_level);
/**
* @brief Setup logging for libdisplaydevice.
* @param min_log_level The log level.
*/
void
setup_libdisplaydevice_logging(int min_log_level);
void setup_libdisplaydevice_logging(int min_log_level);
/**
* @brief Flush the log.
@@ -79,8 +74,7 @@ namespace logging {
* log_flush();
* @examples_end
*/
void
log_flush();
void log_flush();
/**
* @brief Print help to stdout.
@@ -89,8 +83,7 @@ namespace logging {
* print_help("sunshine");
* @examples_end
*/
void
print_help(const char *name);
void print_help(const char *name);
/**
* @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
* @examples_end
*/
template <typename T>
template<typename T>
class min_max_avg_periodic_logger {
public:
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)):
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)):
severity(severity),
message(message),
units(units),
interval(interval_in_seconds),
enabled(config::sunshine.min_log_level <= severity.default_severity()) {}
enabled(config::sunshine.min_log_level <= severity.default_severity()) {
}
void
collect_and_log(const T &value) {
void collect_and_log(const T &value) {
if (enabled) {
auto print_info = [&](const T &min_value, const T &max_value, double avg_value) {
auto f = stat_trackers::two_digits_after_decimal();
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;
}
else {
} else {
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
collect_and_log(std::function<T()> func) {
if (enabled) collect_and_log(func());
void collect_and_log(std::function<T()> func) {
if (enabled) {
collect_and_log(func());
}
}
void
reset() {
if (enabled) tracker.reset();
void reset() {
if (enabled) {
tracker.reset();
}
}
bool
is_enabled() const {
bool is_enabled() const {
return enabled;
}
@@ -175,40 +165,41 @@ namespace logging {
*/
class time_delta_periodic_logger {
public:
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)):
logger(severity, message, "ms", interval_in_seconds) {}
void
first_point(const std::chrono::steady_clock::time_point &point) {
if (logger.is_enabled()) point1 = point;
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)):
logger(severity, message, "ms", interval_in_seconds) {
}
void
first_point_now() {
if (logger.is_enabled()) first_point(std::chrono::steady_clock::now());
void first_point(const std::chrono::steady_clock::time_point &point) {
if (logger.is_enabled()) {
point1 = point;
}
}
void
second_point_and_log(const std::chrono::steady_clock::time_point &point) {
void first_point_now() {
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()) {
logger.collect_and_log(std::chrono::duration<double, std::milli>(point - point1).count());
}
}
void
second_point_now_and_log() {
if (logger.is_enabled()) second_point_and_log(std::chrono::steady_clock::now());
void second_point_now_and_log() {
if (logger.is_enabled()) {
second_point_and_log(std::chrono::steady_clock::now());
}
}
void
reset() {
if (logger.is_enabled()) logger.reset();
void reset() {
if (logger.is_enabled()) {
logger.reset();
}
}
bool
is_enabled() const {
bool is_enabled() const {
return logger.is_enabled();
}
@@ -222,15 +213,13 @@ namespace logging {
* @param input Input string.
* @return Enclosed string.
*/
std::string
bracket(const std::string &input);
std::string bracket(const std::string &input);
/**
* @brief Enclose string in square brackets.
* @param input Input string.
* @return Enclosed string.
*/
std::wstring
bracket(const std::wstring &input);
std::wstring bracket(const std::wstring &input);
} // namespace logging

View File

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

View File

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

View File

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

View File

@@ -2,13 +2,16 @@
* @file src/network.cpp
* @brief Definitions for networking related functions.
*/
#include "network.h"
#include "config.h"
#include "logging.h"
#include "utility.h"
// standard includes
#include <algorithm>
#include <sstream>
// local includes
#include "config.h"
#include "logging.h"
#include "network.h"
#include "utility.h"
using namespace std::literals;
namespace ip = boost::asio::ip;
@@ -33,8 +36,7 @@ namespace net {
ip::make_network_v6("fe80::/64"sv),
};
net_e
from_enum_string(const std::string_view &view) {
net_e from_enum_string(const std::string_view &view) {
if (view == "wan") {
return WAN;
}
@@ -45,8 +47,7 @@ namespace net {
return PC;
}
net_e
from_address(const std::string_view &view) {
net_e from_address(const std::string_view &view) {
auto addr = normalize_address(ip::make_address(view));
if (addr.is_v6()) {
@@ -61,8 +62,7 @@ namespace net {
return LAN;
}
}
}
else {
} else {
for (auto &range : pc_ips_v4) {
if (range.hosts().find(addr.to_v4()) != range.hosts().end()) {
return PC;
@@ -79,8 +79,7 @@ namespace net {
return WAN;
}
std::string_view
to_enum_string(net_e net) {
std::string_view to_enum_string(net_e net) {
switch (net) {
case PC:
return "pc"sv;
@@ -94,8 +93,7 @@ namespace net {
return "wan"sv;
}
af_e
af_from_enum_string(const std::string_view &view) {
af_e af_from_enum_string(const std::string_view &view) {
if (view == "ipv4") {
return IPV4;
}
@@ -107,8 +105,7 @@ namespace net {
return BOTH;
}
std::string_view
af_to_any_address_string(af_e af) {
std::string_view af_to_any_address_string(af_e af) {
switch (af) {
case IPV4:
return "0.0.0.0"sv;
@@ -120,8 +117,7 @@ namespace net {
return "::"sv;
}
boost::asio::ip::address
normalize_address(boost::asio::ip::address address) {
boost::asio::ip::address normalize_address(boost::asio::ip::address address) {
// Convert IPv6-mapped IPv4 addresses into regular IPv4 addresses
if (address.is_v6()) {
auto v6 = address.to_v6();
@@ -133,37 +129,31 @@ namespace net {
return address;
}
std::string
addr_to_normalized_string(boost::asio::ip::address address) {
std::string addr_to_normalized_string(boost::asio::ip::address address) {
return normalize_address(address).to_string();
}
std::string
addr_to_url_escaped_string(boost::asio::ip::address address) {
std::string addr_to_url_escaped_string(boost::asio::ip::address address) {
address = normalize_address(address);
if (address.is_v6()) {
std::stringstream ss;
ss << '[' << address.to_string() << ']';
return ss.str();
}
else {
} else {
return address.to_string();
}
}
int
encryption_mode_for_address(boost::asio::ip::address address) {
int encryption_mode_for_address(boost::asio::ip::address address) {
auto nettype = net::from_address(address.to_string());
if (nettype == net::net_e::PC || nettype == net::net_e::LAN) {
return config::stream.lan_encryption_mode;
}
else {
} else {
return config::stream.wan_encryption_mode;
}
}
host_t
host_create(af_e af, ENetAddress &addr, std::uint16_t port) {
host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port) {
static std::once_flag enet_init_flag;
std::call_once(enet_init_flag, []() {
enet_initialize();
@@ -174,7 +164,7 @@ namespace net {
enet_address_set_port(&addr, port);
// 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)
enet_socket_set_option(host->socket, ENET_SOCKOPT_QOS, 1);
@@ -182,8 +172,7 @@ namespace net {
return host;
}
void
free_host(ENetHost *host) {
void free_host(ENetHost *host) {
std::for_each(host->peers, host->peers + host->peerCount, [](ENetPeer &peer_ref) {
ENetPeer *peer = &peer_ref;
@@ -195,8 +184,7 @@ namespace net {
enet_host_destroy(host);
}
std::uint16_t
map_port(int port) {
std::uint16_t map_port(int port) {
// calculate the port from the config 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.
* @return Hostname-based instance name or "Sunshine" if hostname is invalid.
*/
std::string
mdns_instance_name(const std::string_view &hostname) {
std::string mdns_instance_name(const std::string_view &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.
if (instancename.size() > 63) {
@@ -227,8 +214,7 @@ namespace net {
// Replace any spaces with dashes
if (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
instancename.resize(i);
break;

View File

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

View File

@@ -2,8 +2,10 @@
* @file src/nvenc/nvenc_base.cpp
* @brief Definitions for abstract platform-agnostic base of standalone NVENC encoder.
*/
// this include
#include "nvenc_base.h"
// local includes
#include "src/config.h"
#include "src/logging.h"
#include "src/utility.h"
@@ -21,9 +23,10 @@
namespace {
GUID
quality_preset_guid_from_number(unsigned number) {
if (number > 7) number = 7;
GUID quality_preset_guid_from_number(unsigned number) {
if (number > 7) {
number = 7;
}
switch (number) {
case 1:
@@ -50,13 +53,11 @@ namespace {
}
};
bool
equal_guids(const GUID &guid1, const GUID &guid2) {
bool equal_guids(const GUID &guid1, const GUID &guid2) {
return std::memcmp(&guid1, &guid2, sizeof(GUID)) == 0;
}
auto
quality_preset_string_from_guid(const GUID &guid) {
auto quality_preset_string_from_guid(const GUID &guid) {
if (equal_guids(guid, NV_ENC_PRESET_P1_GUID)) {
return "P1";
}
@@ -93,23 +94,28 @@ namespace nvenc {
// Use destroy_encoder() instead
}
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) {
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) {
// Pick the minimum NvEncode API version required to support the specified codec
// 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);
if (!nvenc && !init_library()) return false;
if (!nvenc && !init_library()) {
return false;
}
if (encoder) destroy_encoder();
auto fail_guard = util::fail_guard([this] { destroy_encoder(); });
if (encoder) {
destroy_encoder();
}
auto fail_guard = util::fail_guard([this] {
destroy_encoder();
});
encoder_params.width = client_config.width;
encoder_params.height = client_config.height;
encoder_params.buffer_format = buffer_format;
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.deviceType = device_type;
session_params.apiVersion = minimum_api_version;
@@ -130,7 +136,7 @@ namespace nvenc {
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) {
case 0:
@@ -164,7 +170,7 @@ namespace nvenc {
}
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;
nvenc->nvEncGetEncodeCaps(encoder, init_params.encodeGUID, &param, &value);
return value;
@@ -217,7 +223,7 @@ namespace nvenc {
init_params.frameRateNum = client_config.framerate;
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))) {
BOOST_LOG(error) << "NvEnc: NvEncGetEncodePresetConfigEx() failed: " << last_nvenc_error_string;
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) {
if (client_config.numRefFrames > 0) {
ref_frames_option = client_config.numRefFrames;
}
else {
} else {
ref_frames_option = ref_frames_default;
}
if (ref_frames_option > 0 && !get_encoder_cap(NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES)) {
@@ -294,81 +299,81 @@ namespace nvenc {
};
switch (client_config.videoFormat) {
case 0: {
// H.264
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;
set_h264_hevc_common_format_config(format_config);
if (config.h264_cavlc || !get_encoder_cap(NV_ENC_CAPS_SUPPORT_CABAC)) {
format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC;
case 0:
{
// H.264
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;
set_h264_hevc_common_format_config(format_config);
if (config.h264_cavlc || !get_encoder_cap(NV_ENC_CAPS_SUPPORT_CABAC)) {
format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC;
} else {
format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CABAC;
}
set_ref_frames(format_config.maxNumRefFrames, format_config.numRefL0, 5);
set_minqp_if_enabled(config.min_qp_h264);
fill_h264_hevc_vui(format_config.h264VUIParameters);
break;
}
else {
format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CABAC;
}
set_ref_frames(format_config.maxNumRefFrames, format_config.numRefL0, 5);
set_minqp_if_enabled(config.min_qp_h264);
fill_h264_hevc_vui(format_config.h264VUIParameters);
break;
}
case 1: {
// HEVC
auto &format_config = enc_config.encodeCodecConfig.hevcConfig;
set_h264_hevc_common_format_config(format_config);
if (buffer_is_10bit()) {
format_config.pixelBitDepthMinus8 = 2;
}
set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numRefL0, 5);
set_minqp_if_enabled(config.min_qp_hevc);
fill_h264_hevc_vui(format_config.hevcVUIParameters);
if (client_config.enableIntraRefresh == 1 || config.intra_refresh) {
if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_INTRA_REFRESH)) {
format_config.enableIntraRefresh = 1;
format_config.intraRefreshPeriod = 300;
format_config.intraRefreshCnt = 299;
if (get_encoder_cap(NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH)) {
format_config.singleSliceIntraRefresh = 1;
}
else {
BOOST_LOG(warning) << "NvEnc: Single Slice Intra Refresh not supported";
case 1:
{
// HEVC
auto &format_config = enc_config.encodeCodecConfig.hevcConfig;
set_h264_hevc_common_format_config(format_config);
if (buffer_is_10bit()) {
format_config.pixelBitDepthMinus8 = 2;
}
set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numRefL0, 5);
set_minqp_if_enabled(config.min_qp_hevc);
fill_h264_hevc_vui(format_config.hevcVUIParameters);
if (client_config.enableIntraRefresh == 1) {
if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_INTRA_REFRESH)) {
format_config.enableIntraRefresh = 1;
format_config.intraRefreshPeriod = 300;
format_config.intraRefreshCnt = 299;
if (get_encoder_cap(NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH)) {
format_config.singleSliceIntraRefresh = 1;
} else {
BOOST_LOG(warning) << "NvEnc: Single Slice Intra Refresh not supported";
}
} else {
BOOST_LOG(error) << "NvEnc: Client asked for intra-refresh but the encoder does not support intra-refresh";
}
}
else {
BOOST_LOG(error) << "NvEnc: Client asked for intra-refresh but the encoder does not support intra-refresh";
break;
}
case 2:
{
// AV1
auto &format_config = enc_config.encodeCodecConfig.av1Config;
format_config.repeatSeqHdr = 1;
format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH;
if (buffer_is_yuv444()) {
format_config.chromaFormatIDC = 3;
}
}
break;
}
format_config.enableBitstreamPadding = config.insert_filler_data;
if (buffer_is_10bit()) {
format_config.inputPixelBitDepthMinus8 = 2;
format_config.pixelBitDepthMinus8 = 2;
}
format_config.colorPrimaries = colorspace.primaries;
format_config.transferCharacteristics = colorspace.tranfer_function;
format_config.matrixCoefficients = colorspace.matrix;
format_config.colorRange = colorspace.full_range;
format_config.chromaSamplePosition = buffer_is_yuv444() ? 0 : 1;
set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numFwdRefs, 8);
set_minqp_if_enabled(config.min_qp_av1);
case 2: {
// AV1
auto &format_config = enc_config.encodeCodecConfig.av1Config;
format_config.repeatSeqHdr = 1;
format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH;
if (buffer_is_yuv444()) {
format_config.chromaFormatIDC = 3;
if (client_config.slicesPerFrame > 1) {
// NVENC only supports slice counts that are powers of two, so we'll pick powers of two
// with bias to rows due to hopefully more similar macroblocks with a row vs a column.
format_config.numTileRows = std::pow(2, std::ceil(std::log2(client_config.slicesPerFrame) / 2));
format_config.numTileColumns = std::pow(2, std::floor(std::log2(client_config.slicesPerFrame) / 2));
}
break;
}
format_config.enableBitstreamPadding = config.insert_filler_data;
if (buffer_is_10bit()) {
format_config.inputPixelBitDepthMinus8 = 2;
format_config.pixelBitDepthMinus8 = 2;
}
format_config.colorPrimaries = colorspace.primaries;
format_config.transferCharacteristics = colorspace.tranfer_function;
format_config.matrixCoefficients = colorspace.matrix;
format_config.colorRange = colorspace.full_range;
format_config.chromaSamplePosition = buffer_is_yuv444() ? 0 : 1;
set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numFwdRefs, 8);
set_minqp_if_enabled(config.min_qp_av1);
if (client_config.slicesPerFrame > 1) {
// NVENC only supports slice counts that are powers of two, so we'll pick powers of two
// with bias to rows due to hopefully more similar macroblocks with a row vs a column.
format_config.numTileRows = std::pow(2, std::ceil(std::log2(client_config.slicesPerFrame) / 2));
format_config.numTileColumns = std::pow(2, std::floor(std::log2(client_config.slicesPerFrame) / 2));
}
break;
}
}
init_params.encodeConfig = &enc_config;
@@ -379,7 +384,7 @@ namespace nvenc {
}
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;
if (nvenc_failed(nvenc->nvEncRegisterAsyncEvent(encoder, &event_params))) {
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))) {
BOOST_LOG(error) << "NvEnc: NvEncCreateBitstreamBuffer() failed: " << last_nvenc_error_string;
return false;
@@ -409,16 +414,36 @@ namespace nvenc {
client_config.videoFormat == 2 ? "AV1 " :
" ";
std::string extra;
if (init_params.enableEncodeAsync) extra += " async";
if (buffer_is_yuv444()) extra += " yuv444";
if (buffer_is_10bit()) extra += " 10-bit";
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";
if (init_params.enableEncodeAsync) {
extra += " async";
}
if (buffer_is_yuv444()) {
extra += " yuv444";
}
if (buffer_is_10bit()) {
extra += " 10-bit";
}
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;
}
@@ -428,8 +453,7 @@ namespace nvenc {
return true;
}
void
nvenc_base::destroy_encoder() {
void nvenc_base::destroy_encoder() {
if (output_bitstream) {
if (nvenc_failed(nvenc->nvEncDestroyBitstreamBuffer(encoder, output_bitstream))) {
BOOST_LOG(error) << "NvEnc: NvEncDestroyBitstreamBuffer() failed: " << last_nvenc_error_string;
@@ -437,7 +461,7 @@ namespace nvenc {
output_bitstream = nullptr;
}
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;
if (nvenc_failed(nvenc->nvEncUnregisterAsyncEvent(encoder, &event_params))) {
BOOST_LOG(error) << "NvEnc: NvEncUnregisterAsyncEvent() failed: " << last_nvenc_error_string;
@@ -460,8 +484,7 @@ namespace nvenc {
encoder_params = {};
}
nvenc_encoded_frame
nvenc_base::encode_frame(uint64_t frame_index, bool force_idr) {
nvenc_encoded_frame nvenc_base::encode_frame(uint64_t frame_index, bool force_idr) {
if (!encoder) {
return {};
}
@@ -474,7 +497,7 @@ namespace nvenc {
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;
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.inputHeight = encoder_params.height;
pic_params.encodePicFlags = force_idr ? NV_ENC_PIC_FLAG_FORCEIDR : 0;
@@ -503,7 +526,7 @@ namespace nvenc {
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.doNotWait = 0;
@@ -519,7 +542,7 @@ namespace nvenc {
auto data_pointer = (uint8_t *) lock_bitstream.bitstreamBufferPtr;
nvenc_encoded_frame encoded_frame {
{ data_pointer, data_pointer + lock_bitstream.bitstreamSizeInBytes },
{data_pointer, data_pointer + lock_bitstream.bitstreamSizeInBytes},
lock_bitstream.outputTimeStamp,
lock_bitstream.pictureType == NV_ENC_PIC_TYPE_IDR,
encoder_state.rfi_needs_confirmation,
@@ -545,9 +568,10 @@ namespace nvenc {
return encoded_frame;
}
bool
nvenc_base::invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) {
if (!encoder || !encoder_params.rfi) return false;
bool nvenc_base::invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) {
if (!encoder || !encoder_params.rfi) {
return false;
}
if (first_frame >= encoder_state.last_rfi_range.first &&
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;
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) {
BOOST_LOG(debug) << "NvEnc: rfi request too large, generating IDR";
@@ -582,12 +606,11 @@ namespace nvenc {
return true;
}
bool
nvenc_base::nvenc_failed(NVENCSTATUS status) {
bool nvenc_base::nvenc_failed(NVENCSTATUS status) {
auto status_string = [](NVENCSTATUS status) -> std::string {
switch (status) {
#define nvenc_status_case(x) \
case x: \
case x: \
return #x;
nvenc_status_case(NV_ENC_SUCCESS);
nvenc_status_case(NV_ENC_ERR_NO_ENCODE_DEVICE);
@@ -637,8 +660,7 @@ namespace nvenc {
return false;
}
uint32_t
nvenc_base::min_struct_version(uint32_t version, uint32_t v11_struct_version, uint32_t v12_struct_version) {
uint32_t nvenc_base::min_struct_version(uint32_t version, uint32_t v11_struct_version, uint32_t v12_struct_version) {
assert(minimum_api_version);
// Mask off and replace the original NVENCAPI_VERSION

View File

@@ -4,15 +4,16 @@
*/
#pragma once
// lib includes
#include <ffnvcodec/nvEncodeAPI.h>
// local includes
#include "nvenc_colorspace.h"
#include "nvenc_config.h"
#include "nvenc_encoded_frame.h"
#include "src/logging.h"
#include "src/video.h"
#include <ffnvcodec/nvEncodeAPI.h>
/**
* @brief Standalone NVENC encoder
*/
@@ -31,8 +32,7 @@ namespace nvenc {
virtual ~nvenc_base();
nvenc_base(const nvenc_base &) = delete;
nvenc_base &
operator=(const nvenc_base &) = delete;
nvenc_base &operator=(const nvenc_base &) = delete;
/**
* @brief Create the encoder.
@@ -42,15 +42,13 @@ namespace nvenc {
* @param buffer_format Platform-agnostic input surface format.
* @return `true` on success, `false` on error
*/
bool
create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format);
bool 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.
* Derived classes classes call it in the destructor.
*/
void
destroy_encoder();
void destroy_encoder();
/**
* @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.
* @return Encoded frame.
*/
nvenc_encoded_frame
encode_frame(uint64_t frame_index, bool force_idr);
nvenc_encoded_frame encode_frame(uint64_t frame_index, bool force_idr);
/**
* @brief Perform reference frame invalidation (RFI) procedure.
@@ -70,8 +67,7 @@ namespace nvenc {
* @return `true` on success, `false` on error.
* After error next frame must be encoded with `force_idr = true`.
*/
bool
invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
bool invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
protected:
/**
@@ -79,8 +75,7 @@ namespace nvenc {
* Called during `create_encoder()` if `nvenc` variable is not initialized.
* @return `true` on success, `false` on error
*/
virtual bool
init_library() = 0;
virtual bool init_library() = 0;
/**
* @brief Required. Used for creating outside-facing input surface,
@@ -88,16 +83,16 @@ namespace nvenc {
* Called during `create_encoder()`.
* @return `true` on success, `false` on error
*/
virtual bool
create_and_register_input_buffer() = 0;
virtual bool 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()`.
* Typically used for interop copy.
* @return `true` on success, `false` on error
*/
virtual bool
synchronize_input_buffer() { return true; }
virtual bool synchronize_input_buffer() {
return true;
}
/**
* @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
* @return `true` on success, `false` on timeout or error
*/
virtual bool
wait_for_async_event(uint32_t timeout_ms) { return false; }
virtual bool wait_for_async_event(uint32_t timeout_ms) {
return false;
}
bool
nvenc_failed(NVENCSTATUS status);
bool nvenc_failed(NVENCSTATUS status);
/**
* @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.
* @return A suitable struct version for the active codec.
*/
uint32_t
min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0);
uint32_t 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;
@@ -154,7 +148,7 @@ namespace nvenc {
uint64_t last_encoded_frame_index = 0;
bool rfi_needs_confirmation = false;
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;
};

View File

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

View File

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

View File

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

View File

@@ -3,8 +3,10 @@
* @brief Definitions for native Direct3D11 NVENC encoder.
*/
#ifdef _WIN32
// this include
#include "nvenc_d3d11_native.h"
// local includes
#include "nvenc_utils.h"
namespace nvenc {
@@ -16,16 +18,17 @@ namespace nvenc {
}
nvenc_d3d11_native::~nvenc_d3d11_native() {
if (encoder) destroy_encoder();
if (encoder) {
destroy_encoder();
}
}
ID3D11Texture2D *
nvenc_d3d11_native::get_input_texture() {
nvenc_d3d11_native::get_input_texture() {
return d3d_input_texture.GetInterfacePtr();
}
bool
nvenc_d3d11_native::create_and_register_input_buffer() {
bool nvenc_d3d11_native::create_and_register_input_buffer() {
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";
return false;
@@ -48,7 +51,7 @@ namespace nvenc {
}
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.width = encoder_params.width;
register_resource.height = encoder_params.height;

View File

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

View File

@@ -3,8 +3,10 @@
* @brief Definitions for CUDA NVENC encoder with Direct3D11 input surfaces.
*/
#ifdef _WIN32
// this include
#include "nvenc_d3d11_on_cuda.h"
// local includes
#include "nvenc_utils.h"
namespace nvenc {
@@ -15,7 +17,9 @@ namespace nvenc {
}
nvenc_d3d11_on_cuda::~nvenc_d3d11_on_cuda() {
if (encoder) destroy_encoder();
if (encoder) {
destroy_encoder();
}
if (cuda_context) {
{
@@ -48,14 +52,14 @@ namespace nvenc {
}
}
ID3D11Texture2D *
nvenc_d3d11_on_cuda::get_input_texture() {
ID3D11Texture2D *nvenc_d3d11_on_cuda::get_input_texture() {
return d3d_input_texture.GetInterfacePtr();
}
bool
nvenc_d3d11_on_cuda::init_library() {
if (!nvenc_d3d11::init_library()) return false;
bool nvenc_d3d11_on_cuda::init_library() {
if (!nvenc_d3d11::init_library()) {
return false;
}
constexpr auto dll_name = "nvcuda.dll";
@@ -82,8 +86,7 @@ namespace nvenc {
FreeLibrary(cuda_functions.dll);
cuda_functions = {};
}
}
else {
} else {
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.cuCtxPopCurrent(&cuda_context))) {
device = cuda_context;
}
else {
} else {
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";
}
}
@@ -112,8 +113,7 @@ namespace nvenc {
return device != nullptr;
}
bool
nvenc_d3d11_on_cuda::create_and_register_input_buffer() {
bool nvenc_d3d11_on_cuda::create_and_register_input_buffer() {
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";
return false;
@@ -138,13 +138,16 @@ namespace nvenc {
{
auto autopop_context = push_context();
if (!autopop_context) return false;
if (!autopop_context) {
return false;
}
if (!cuda_d3d_input_texture) {
if (cuda_failed(cuda_functions.cuGraphicsD3D11RegisterResource(
&cuda_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;
return false;
}
@@ -156,7 +159,9 @@ namespace nvenc {
&cuda_surface_pitch,
// Planar 16-bit YUV
encoder_params.width * 2,
encoder_params.height * 3, 16))) {
encoder_params.height * 3,
16
))) {
BOOST_LOG(error) << "NvEnc: cuMemAllocPitch() failed: error " << last_cuda_error;
return false;
}
@@ -164,7 +169,7 @@ namespace nvenc {
}
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.width = encoder_params.width;
register_resource.height = encoder_params.height;
@@ -184,10 +189,11 @@ namespace nvenc {
return true;
}
bool
nvenc_d3d11_on_cuda::synchronize_input_buffer() {
bool nvenc_d3d11_on_cuda::synchronize_input_buffer() {
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))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsMapResources() failed: error " << last_cuda_error;
@@ -230,14 +236,12 @@ namespace nvenc {
return unmap();
}
bool
nvenc_d3d11_on_cuda::cuda_succeeded(CUresult result) {
bool nvenc_d3d11_on_cuda::cuda_succeeded(CUresult result) {
last_cuda_error = result;
return result == CUDA_SUCCESS;
}
bool
nvenc_d3d11_on_cuda::cuda_failed(CUresult result) {
bool nvenc_d3d11_on_cuda::cuda_failed(CUresult result) {
last_cuda_error = result;
return result != CUDA_SUCCESS;
}
@@ -251,15 +255,13 @@ namespace nvenc {
}
}
nvenc_d3d11_on_cuda::autopop_context
nvenc_d3d11_on_cuda::push_context() {
nvenc_d3d11_on_cuda::autopop_context nvenc_d3d11_on_cuda::push_context() {
if (cuda_context &&
cuda_succeeded(cuda_functions.cuCtxPushCurrent(cuda_context))) {
return { *this, cuda_context };
}
else {
return {*this, cuda_context};
} else {
BOOST_LOG(error) << "NvEnc: cuCtxPushCurrent() failed: error " << last_cuda_error;
return { *this, nullptr };
return {*this, nullptr};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,14 +2,17 @@
* @file src/platform/linux/cuda.cu
* @brief CUDA implementation for Linux.
*/
// #include <algorithm>
#include <helper_math.h>
// standard includes
#include <chrono>
#include <limits>
#include <memory>
#include <optional>
#include <string_view>
// platform includes
#include <helper_math.h>
// local includes
#include "cuda.h"
using namespace std::literals;
@@ -18,16 +21,20 @@ using namespace std::literals;
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
#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) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return;
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return;
#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) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return std::nullopt;
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return std::nullopt;
#define CU_CHECK_IGNORE(x, y) \
check((x), SUNSHINE_STRINGVIEW(y ": "))
@@ -42,277 +49,293 @@ using namespace std::literals;
* Not pretty and extremely error-prone, fix at earliest convenience.
*/
namespace platf {
struct img_t: std::enable_shared_from_this<img_t> {
public:
std::uint8_t *data {};
std::int32_t width {};
std::int32_t height {};
std::int32_t pixel_pitch {};
std::int32_t row_pitch {};
struct img_t: std::enable_shared_from_this<img_t> {
public:
std::uint8_t *data {};
std::int32_t width {};
std::int32_t height {};
std::int32_t pixel_pitch {};
std::int32_t row_pitch {};
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
virtual ~img_t() = default;
};
} // namespace platf
virtual ~img_t() = default;
};
} // namespace platf
// End special declarations
namespace cuda {
struct alignas(16) cuda_color_t {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
struct alignas(16) cuda_color_t {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
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>
inline T div_align(T l, T r) {
return (l + r - 1) / r;
}
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) {
auto name = cudaGetErrorName(result);
auto description = cudaGetErrorString(result);
pass_error(sv, name, description);
return -1;
template<class T>
inline T div_align(T l, T r) {
return (l + r - 1) / r;
}
return 0;
}
void pass_error(const std::string_view &sv, const char *name, const char *description);
template<class T>
ptr_t make_ptr() {
void *p;
CU_CHECK_PTR(cudaMalloc(&p, sizeof(T)), "Couldn't allocate color matrix");
inline static int check(cudaError_t result, const std::string_view &sv) {
if (result) {
auto name = cudaGetErrorName(result);
auto description = cudaGetErrorString(result);
ptr_t ptr { p };
pass_error(sv, name, description);
return -1;
}
return ptr;
}
void freeCudaPtr_t::operator()(void *ptr) {
CU_CHECK_IGNORE(cudaFree(ptr), "Couldn't free cuda device pointer");
}
void freeCudaStream_t::operator()(cudaStream_t ptr) {
CU_CHECK_IGNORE(cudaStreamDestroy(ptr), "Couldn't free cuda stream");
}
stream_t make_stream(int flags) {
cudaStream_t stream;
if(!flags) {
CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream");
}
else {
CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags");
return 0;
}
return stream_t { stream };
}
template<class T>
ptr_t make_ptr() {
void *p;
CU_CHECK_PTR(cudaMalloc(&p, sizeof(T)), "Couldn't allocate color matrix");
inline __device__ float3 bgra_to_rgb(uchar4 vec) {
return make_float3((float)vec.z, (float)vec.y, (float)vec.x);
}
ptr_t ptr {p};
inline __device__ float3 bgra_to_rgb(float4 vec) {
return make_float3(vec.z, vec.y, vec.x);
}
inline __device__ float2 calcUV(float3 pixel, const cuda_color_t *const color_matrix) {
float4 vec_u = color_matrix->color_vec_u;
float4 vec_v = color_matrix->color_vec_v;
float u = dot(pixel, make_float3(vec_u)) + vec_u.w;
float v = dot(pixel, make_float3(vec_v)) + vec_v.w;
u = u * 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);
}
inline __device__ float calcY(float3 pixel, const cuda_color_t *const color_matrix) {
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;
}
__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 idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2;
if(idX >= viewport.width) return;
if(idY >= viewport.height) return;
float x = idX * scale;
float y = idY * scale;
idX += viewport.offsetX;
idY += viewport.offsetY;
uint8_t *dstY0 = dstY + idX + idY * dstPitchY;
uint8_t *dstY1 = dstY + idX + (idY + 1) * dstPitchY;
dstUV = dstUV + idX + (idY / 2 * dstPitchUV);
float3 rgb_lt = bgra_to_rgb(tex2D<float4>(srcImage, x, y));
float3 rgb_rt = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y));
float3 rgb_lb = bgra_to_rgb(tex2D<float4>(srcImage, x, y + scale));
float3 rgb_rb = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y + scale));
float2 uv_lt = calcUV(rgb_lt, color_matrix) * 256.0f;
float2 uv_rt = calcUV(rgb_rt, color_matrix) * 256.0f;
float2 uv_lb = calcUV(rgb_lb, color_matrix) * 256.0f;
float2 uv_rb = calcUV(rgb_rb, color_matrix) * 256.0f;
float2 uv = (uv_lt + uv_lb + uv_rt + uv_rb) * 0.25f;
dstUV[0] = uv.x;
dstUV[1] = uv.y;
dstY0[0] = calcY(rgb_lt, 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[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) {
CU_CHECK(cudaMemcpy2DToArray(array, 0, 0, src, pitch, pitch, height, cudaMemcpyDeviceToDevice), "Couldn't copy to cuda array from deviceptr");
return 0;
}
std::optional<tex_t> tex_t::make(int height, int pitch) {
tex_t tex;
auto format = cudaCreateChannelDesc<uchar4>();
CU_CHECK_OPT(cudaMallocArray(&tex.array, &format, pitch, height, cudaArrayDefault), "Couldn't allocate cuda array");
cudaResourceDesc res {};
res.resType = cudaResourceTypeArray;
res.res.array.array = tex.array;
cudaTextureDesc desc {};
desc.readMode = cudaReadModeNormalizedFloat;
desc.filterMode = cudaFilterModePoint;
desc.normalizedCoords = false;
std::fill_n(std::begin(desc.addressMode), 2, cudaAddressModeClamp);
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.point, &res, &desc, nullptr), "Couldn't create cuda texture that uses point interpolation");
desc.filterMode = cudaFilterModeLinear;
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.linear, &res, &desc, nullptr), "Couldn't create cuda texture that uses linear interpolation");
return tex;
}
tex_t::tex_t() : array {}, texture { INVALID_TEXTURE, INVALID_TEXTURE } {}
tex_t::tex_t(tex_t &&other) : array { other.array }, texture { other.texture } {
other.array = 0;
other.texture.point = INVALID_TEXTURE;
other.texture.linear = INVALID_TEXTURE;
}
tex_t &tex_t::operator=(tex_t &&other) {
std::swap(array, other.array);
std::swap(texture, other.texture);
return *this;
}
tex_t::~tex_t() {
if(texture.point != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.point), "Couldn't deallocate cuda texture that uses point interpolation");
texture.point = INVALID_TEXTURE;
return ptr;
}
if(texture.linear != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.linear), "Couldn't deallocate cuda texture that uses linear interpolation");
texture.linear = INVALID_TEXTURE;
void freeCudaPtr_t::operator()(void *ptr) {
CU_CHECK_IGNORE(cudaFree(ptr), "Couldn't free cuda device pointer");
}
if(array) {
CU_CHECK_IGNORE(cudaFreeArray(array), "Couldn't deallocate cuda array");
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)
: threadsPerBlock { threadsPerBlock }, color_matrix { std::move(color_matrix) } {
// Ensure aspect ratio is maintained
auto scalar = std::fminf(out_width / (float)in_width, out_height / (float)in_height);
auto out_width_f = in_width * scalar;
auto out_height_f = in_height * scalar;
// result is always positive
auto offsetX_f = (out_width - out_width_f) / 2;
auto offsetY_f = (out_height - out_height_f) / 2;
viewport.width = out_width_f;
viewport.height = out_height_f;
viewport.offsetX = offsetX_f;
viewport.offsetY = offsetY_f;
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) {
cudaDeviceProp props;
int device;
CU_CHECK_OPT(cudaGetDevice(&device), "Couldn't get cuda device");
CU_CHECK_OPT(cudaGetDeviceProperties(&props, device), "Couldn't get cuda device properties");
auto ptr = make_ptr<cuda_color_t>();
if(!ptr) {
return std::nullopt;
void freeCudaStream_t::operator()(cudaStream_t ptr) {
CU_CHECK_IGNORE(cudaStreamDestroy(ptr), "Couldn't free cuda stream");
}
return std::make_optional<sws_t>(in_width, in_height, out_width, out_height, pitch, props.maxThreadsPerMultiProcessor / props.maxBlocksPerMultiProcessor, std::move(ptr));
}
stream_t make_stream(int flags) {
cudaStream_t 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);
}
if (!flags) {
CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream");
} else {
CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags");
}
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 threadsY = viewport.height / 2;
return stream_t {stream};
}
dim3 block(threadsPerBlock);
dim3 grid(div_align(threadsX, threadsPerBlock), threadsY);
inline __device__ float3 bgra_to_rgb(uchar4 vec) {
return make_float3((float) vec.z, (float) vec.y, (float) vec.x);
}
RGBA_to_NV12<<<grid, block, 0, stream>>>(texture, Y, UV, pitchY, pitchUV, scale, viewport, (cuda_color_t *)color_matrix.get());
inline __device__ float3 bgra_to_rgb(float4 vec) {
return make_float3(vec.z, vec.y, vec.x);
}
return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_NV12 failed");
}
inline __device__ float2 calcUV(float3 pixel, const cuda_color_t *const color_matrix) {
float4 vec_u = color_matrix->color_vec_u;
float4 vec_v = color_matrix->color_vec_v;
void sws_t::apply_colorspace(const video::sunshine_colorspace_t& 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");
}
float u = dot(pixel, make_float3(vec_u)) + vec_u.w;
float v = dot(pixel, make_float3(vec_v)) + vec_v.w;
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");
}
u = u * color_matrix->range_uv.x + color_matrix->range_uv.y;
v = v * color_matrix->range_uv.x + color_matrix->range_uv.y;
} // namespace cuda
return make_float2(u, v);
}
inline __device__ float calcY(float3 pixel, const cuda_color_t *const color_matrix) {
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;
}
__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 idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2;
if (idX >= viewport.width) {
return;
}
if (idY >= viewport.height) {
return;
}
float x = idX * scale;
float y = idY * scale;
idX += viewport.offsetX;
idY += viewport.offsetY;
uint8_t *dstY0 = dstY + idX + idY * dstPitchY;
uint8_t *dstY1 = dstY + idX + (idY + 1) * dstPitchY;
dstUV = dstUV + idX + (idY / 2 * dstPitchUV);
float3 rgb_lt = bgra_to_rgb(tex2D<float4>(srcImage, x, y));
float3 rgb_rt = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y));
float3 rgb_lb = bgra_to_rgb(tex2D<float4>(srcImage, x, y + scale));
float3 rgb_rb = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y + scale));
float2 uv_lt = calcUV(rgb_lt, color_matrix) * 256.0f;
float2 uv_rt = calcUV(rgb_rt, color_matrix) * 256.0f;
float2 uv_lb = calcUV(rgb_lb, color_matrix) * 256.0f;
float2 uv_rb = calcUV(rgb_rb, color_matrix) * 256.0f;
float2 uv = (uv_lt + uv_lb + uv_rt + uv_rb) * 0.25f;
dstUV[0] = uv.x;
dstUV[1] = uv.y;
dstY0[0] = calcY(rgb_lt, 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[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) {
CU_CHECK(cudaMemcpy2DToArray(array, 0, 0, src, pitch, pitch, height, cudaMemcpyDeviceToDevice), "Couldn't copy to cuda array from deviceptr");
return 0;
}
std::optional<tex_t> tex_t::make(int height, int pitch) {
tex_t tex;
auto format = cudaCreateChannelDesc<uchar4>();
CU_CHECK_OPT(cudaMallocArray(&tex.array, &format, pitch, height, cudaArrayDefault), "Couldn't allocate cuda array");
cudaResourceDesc res {};
res.resType = cudaResourceTypeArray;
res.res.array.array = tex.array;
cudaTextureDesc desc {};
desc.readMode = cudaReadModeNormalizedFloat;
desc.filterMode = cudaFilterModePoint;
desc.normalizedCoords = false;
std::fill_n(std::begin(desc.addressMode), 2, cudaAddressModeClamp);
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.point, &res, &desc, nullptr), "Couldn't create cuda texture that uses point interpolation");
desc.filterMode = cudaFilterModeLinear;
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.linear, &res, &desc, nullptr), "Couldn't create cuda texture that uses linear interpolation");
return tex;
}
tex_t::tex_t():
array {},
texture {INVALID_TEXTURE, INVALID_TEXTURE} {
}
tex_t::tex_t(tex_t &&other):
array {other.array},
texture {other.texture} {
other.array = 0;
other.texture.point = INVALID_TEXTURE;
other.texture.linear = INVALID_TEXTURE;
}
tex_t &tex_t::operator=(tex_t &&other) {
std::swap(array, other.array);
std::swap(texture, other.texture);
return *this;
}
tex_t::~tex_t() {
if (texture.point != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.point), "Couldn't deallocate cuda texture that uses point interpolation");
texture.point = INVALID_TEXTURE;
}
if (texture.linear != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.linear), "Couldn't deallocate cuda texture that uses linear interpolation");
texture.linear = INVALID_TEXTURE;
}
if (array) {
CU_CHECK_IGNORE(cudaFreeArray(array), "Couldn't deallocate cuda array");
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):
threadsPerBlock {threadsPerBlock},
color_matrix {std::move(color_matrix)} {
// Ensure aspect ratio is maintained
auto scalar = std::fminf(out_width / (float) in_width, out_height / (float) in_height);
auto out_width_f = in_width * scalar;
auto out_height_f = in_height * scalar;
// result is always positive
auto offsetX_f = (out_width - out_width_f) / 2;
auto offsetY_f = (out_height - out_height_f) / 2;
viewport.width = out_width_f;
viewport.height = out_height_f;
viewport.offsetX = offsetX_f;
viewport.offsetY = offsetY_f;
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) {
cudaDeviceProp props;
int device;
CU_CHECK_OPT(cudaGetDevice(&device), "Couldn't get cuda device");
CU_CHECK_OPT(cudaGetDeviceProperties(&props, device), "Couldn't get cuda device properties");
auto ptr = make_ptr<cuda_color_t>();
if (!ptr) {
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));
}
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);
}
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 threadsY = viewport.height / 2;
dim3 block(threadsPerBlock);
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());
return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_NV12 failed");
}
void sws_t::apply_colorspace(const video::sunshine_colorspace_t &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");
}
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");
}
} // namespace cuda

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,18 +2,19 @@
* @file src/platform/linux/input/inputtino_gamepad.cpp
* @brief Definitions for inputtino gamepad input handling.
*/
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
using namespace std::literals;
namespace platf::gamepad {
@@ -25,69 +26,54 @@ namespace platf::gamepad {
GAMEPAD_STATUS ///< Helper to indicate the number of status
};
auto
create_xbox_one() {
return inputtino::XboxOneJoypad::create({ .name = "Sunshine X-Box One (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
.vendor_id = 0x045E,
.product_id = 0x02EA,
.version = 0x0408 });
auto create_xbox_one() {
return inputtino::XboxOneJoypad::create({.name = "Sunshine X-Box One (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
.vendor_id = 0x045E,
.product_id = 0x02EA,
.version = 0x0408});
}
auto
create_switch() {
return inputtino::SwitchJoypad::create({ .name = "Sunshine Nintendo (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
.vendor_id = 0x057e,
.product_id = 0x2009,
.version = 0x8111 });
auto create_switch() {
return inputtino::SwitchJoypad::create({.name = "Sunshine Nintendo (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
.vendor_id = 0x057e,
.product_id = 0x2009,
.version = 0x8111});
}
auto
create_ds5() {
return inputtino::PS5Joypad::create({ .name = "Sunshine DualSense (virtual) pad",
.vendor_id = 0x054C,
.product_id = 0x0CE6,
.version = 0x8111 });
auto create_ds5() {
return inputtino::PS5Joypad::create({.name = "Sunshine DualSense (virtual) pad", .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111});
}
int
alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
ControllerType selectedGamepadType;
if (config::input.gamepad == "xone"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (manual selection)"sv;
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;
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;
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;
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;
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;
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;
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;
selectedGamepadType = DualSenseWired;
}
else {
} else {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (default)"sv;
selectedGamepadType = XboxOneWired;
}
@@ -102,8 +88,7 @@ namespace platf::gamepad {
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;
}
}
else if (selectedGamepadType == DualSenseWired) {
} else if (selectedGamepadType == DualSenseWired) {
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;
}
@@ -125,73 +110,71 @@ namespace platf::gamepad {
};
switch (selectedGamepadType) {
case XboxOneWired: {
auto xOne = create_xbox_one();
if (xOne) {
(*xOne).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
case XboxOneWired:
{
auto xOne = create_xbox_one();
if (xOne) {
(*xOne).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
} else {
BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage();
return -1;
}
}
else {
BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage();
return -1;
case SwitchProWired:
{
auto switchPro = create_switch();
if (switchPro) {
(*switchPro).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
} else {
BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage();
return -1;
}
}
}
case SwitchProWired: {
auto switchPro = create_switch();
if (switchPro) {
(*switchPro).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
}
else {
BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage();
return -1;
}
}
case DualSenseWired: {
auto ds5 = create_ds5();
if (ds5) {
(*ds5).set_on_rumble(on_rumble_fn);
(*ds5).set_on_led([feedback_queue, idx = id.clientRelativeIndex, gamepad](int r, int g, int b) {
// Don't resend duplicate LED data
if (gamepad->last_rgb_led.type == platf::gamepad_feedback_e::set_rgb_led && gamepad->last_rgb_led.data.rgb_led.r == r && gamepad->last_rgb_led.data.rgb_led.g == g && gamepad->last_rgb_led.data.rgb_led.b == b) {
return;
}
case DualSenseWired:
{
auto ds5 = create_ds5();
if (ds5) {
(*ds5).set_on_rumble(on_rumble_fn);
(*ds5).set_on_led([feedback_queue, idx = id.clientRelativeIndex, gamepad](int r, int g, int b) {
// Don't resend duplicate LED data
if (gamepad->last_rgb_led.type == platf::gamepad_feedback_e::set_rgb_led && gamepad->last_rgb_led.data.rgb_led.r == r && gamepad->last_rgb_led.data.rgb_led.g == g && gamepad->last_rgb_led.data.rgb_led.b == b) {
return;
}
auto msg = gamepad_feedback_msg_t::make_rgb_led(idx, r, g, b);
feedback_queue->raise(msg);
gamepad->last_rgb_led = msg;
});
auto msg = gamepad_feedback_msg_t::make_rgb_led(idx, r, g, b);
feedback_queue->raise(msg);
gamepad->last_rgb_led = msg;
});
// Activate the motion sensors
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_ACCEL, 100));
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_GYRO, 100));
// Activate the motion sensors
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_ACCEL, 100));
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_GYRO, 100));
gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
} else {
BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage();
return -1;
}
}
else {
BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage();
return -1;
}
}
}
return -1;
}
void
free(input_raw_t *raw, int nr) {
void 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)
raw->gamepads[nr]->joypad.reset();
raw->gamepads[nr].reset();
}
void
update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
auto gamepad = raw->gamepads[nr];
if (!gamepad) {
return;
@@ -203,11 +186,10 @@ namespace platf::gamepad {
gc.set_stick(inputtino::Joypad::RS, gamepad_state.rsX, gamepad_state.rsY);
gc.set_triggers(gamepad_state.lt, gamepad_state.rt);
},
*gamepad->joypad);
*gamepad->joypad);
}
void
touch(input_raw_t *raw, const gamepad_touch_t &touch) {
void touch(input_raw_t *raw, const gamepad_touch_t &touch) {
auto gamepad = raw->gamepads[touch.id.globalIndex];
if (!gamepad) {
return;
@@ -216,15 +198,13 @@ namespace platf::gamepad {
if (std::holds_alternative<inputtino::PS5Joypad>(*gamepad->joypad)) {
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);
}
else {
} else {
std::get<inputtino::PS5Joypad>(*gamepad->joypad).release_finger(touch.pointerId);
}
}
}
void
motion(input_raw_t *raw, const gamepad_motion_t &motion) {
void motion(input_raw_t *raw, const gamepad_motion_t &motion) {
auto gamepad = raw->gamepads[motion.id.globalIndex];
if (!gamepad) {
return;
@@ -242,8 +222,7 @@ namespace platf::gamepad {
}
}
void
battery(input_raw_t *raw, const gamepad_battery_t &battery) {
void battery(input_raw_t *raw, const gamepad_battery_t &battery) {
auto gamepad = raw->gamepads[battery.id.globalIndex];
if (!gamepad) {
return;
@@ -272,14 +251,13 @@ namespace platf::gamepad {
}
}
std::vector<supported_gamepad_t> &
supported_gamepads(input_t *input) {
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
if (!input) {
static std::vector gps {
supported_gamepad_t { "auto", true, "" },
supported_gamepad_t { "xone", false, "" },
supported_gamepad_t { "ds5", false, "" },
supported_gamepad_t { "switch", false, "" },
supported_gamepad_t {"auto", true, ""},
supported_gamepad_t {"xone", false, ""},
supported_gamepad_t {"ds5", false, ""},
supported_gamepad_t {"switch", false, ""},
};
return gps;
@@ -290,10 +268,10 @@ namespace platf::gamepad {
auto xOne = create_xbox_one();
static std::vector gps {
supported_gamepad_t { "auto", true, "" },
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 { "switch", static_cast<bool>(switchPro), !switchPro ? switchPro.getErrorMessage() : "" },
supported_gamepad_t {"auto", true, ""},
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 {"switch", static_cast<bool>(switchPro), !switchPro ? switchPro.getErrorMessage() : ""},
};
for (auto &[name, is_enabled, reason_disabled] : gps) {

View File

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

View File

@@ -2,18 +2,19 @@
* @file src/platform/linux/input/inputtino_keyboard.cpp
* @brief Definitions for inputtino keyboard input handling.
*/
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_keyboard.h"
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_keyboard.h"
using namespace std::literals;
namespace platf::keyboard {
@@ -25,8 +26,7 @@ namespace platf::keyboard {
*
* adapted from: https://stackoverflow.com/a/7639754
*/
std::string
to_hex(const std::basic_string<char32_t> &str) {
std::string to_hex(const std::basic_string<char32_t> &str) {
std::stringstream ss;
ss << std::hex << std::setfill('0');
for (const auto &ch : str) {
@@ -42,48 +42,123 @@ namespace platf::keyboard {
* A map of linux scan code -> Moonlight keyboard code
*/
static const std::map<short, short> key_mappings = {
{ KEY_BACKSPACE, 0x08 }, { KEY_TAB, 0x09 }, { KEY_ENTER, 0x0D }, { KEY_LEFTSHIFT, 0x10 },
{ KEY_LEFTCTRL, 0x11 }, { KEY_CAPSLOCK, 0x14 }, { KEY_ESC, 0x1B }, { KEY_SPACE, 0x20 },
{ KEY_PAGEUP, 0x21 }, { KEY_PAGEDOWN, 0x22 }, { KEY_END, 0x23 }, { KEY_HOME, 0x24 },
{ KEY_LEFT, 0x25 }, { KEY_UP, 0x26 }, { KEY_RIGHT, 0x27 }, { KEY_DOWN, 0x28 },
{ KEY_SYSRQ, 0x2C }, { KEY_INSERT, 0x2D }, { KEY_DELETE, 0x2E }, { KEY_0, 0x30 },
{ KEY_1, 0x31 }, { KEY_2, 0x32 }, { KEY_3, 0x33 }, { KEY_4, 0x34 },
{ KEY_5, 0x35 }, { 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 }
{KEY_BACKSPACE, 0x08},
{KEY_TAB, 0x09},
{KEY_ENTER, 0x0D},
{KEY_LEFTSHIFT, 0x10},
{KEY_LEFTCTRL, 0x11},
{KEY_CAPSLOCK, 0x14},
{KEY_ESC, 0x1B},
{KEY_SPACE, 0x20},
{KEY_PAGEUP, 0x21},
{KEY_PAGEDOWN, 0x22},
{KEY_END, 0x23},
{KEY_HOME, 0x24},
{KEY_LEFT, 0x25},
{KEY_UP, 0x26},
{KEY_RIGHT, 0x27},
{KEY_DOWN, 0x28},
{KEY_SYSRQ, 0x2C},
{KEY_INSERT, 0x2D},
{KEY_DELETE, 0x2E},
{KEY_0, 0x30},
{KEY_1, 0x31},
{KEY_2, 0x32},
{KEY_3, 0x33},
{KEY_4, 0x34},
{KEY_5, 0x35},
{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
update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) {
void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) {
if (raw->keyboard) {
if (release) {
(*raw->keyboard).release(modcode);
}
else {
} else {
(*raw->keyboard).press(modcode);
}
}
}
void
unicode(input_raw_t *raw, char *utf8, int size) {
void unicode(input_raw_t *raw, char *utf8, int size) {
if (raw->keyboard) {
/* Reading input text as 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);
if (keycode == -1 || wincode == key_mappings.end()) {
BOOST_LOG(warning) << "Unicode, unable to find keycode for: "sv << ch;
}
else {
} else {
(*raw->keyboard).press(wincode->second);
(*raw->keyboard).release(wincode->second);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,12 +4,14 @@
*/
#pragma once
// local includes
#include "misc.h"
#include "src/platform/common.h"
namespace egl {
struct surface_descriptor_t;
}
namespace va {
/**
* Width --> Width of the image
@@ -18,14 +20,10 @@ namespace va {
* offset_y --> Vertical offset of the image in the texture
* file_t card --> The file descriptor of the render device used for encoding
*/
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, 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);
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, 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
bool
validate(int fd);
bool validate(int fd);
} // namespace va

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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