diff --git a/.clang-format b/.clang-format
index e72a1e27..d6da5d94 100644
--- a/.clang-format
+++ b/.clang-format
@@ -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
diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake
index 0c576426..723a1a7a 100644
--- a/cmake/compile_definitions/common.cmake
+++ b/cmake/compile_definitions/common.cmake
@@ -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}
diff --git a/cmake/compile_definitions/linux.cmake b/cmake/compile_definitions/linux.cmake
index 5bf24ba6..08b2417e 100644
--- a/cmake/compile_definitions/linux.cmake
+++ b/cmake/compile_definitions/linux.cmake
@@ -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
diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake
index afc7341a..20a177b0 100644
--- a/cmake/compile_definitions/windows.cmake
+++ b/cmake/compile_definitions/windows.cmake
@@ -82,7 +82,6 @@ list(PREPEND PLATFORM_LIBRARIES
libstdc++.a
libwinpthread.a
minhook::minhook
- nlohmann_json::nlohmann_json
ntdll
setupapi
shlwapi
diff --git a/cmake/dependencies/common.cmake b/cmake/dependencies/common.cmake
index b6ca8447..73f22de2 100644
--- a/cmake/dependencies/common.cmake
+++ b/cmake/dependencies/common.cmake
@@ -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)
diff --git a/cmake/dependencies/nlohmann_json.cmake b/cmake/dependencies/nlohmann_json.cmake
new file mode 100644
index 00000000..c0029b42
--- /dev/null
+++ b/cmake/dependencies/nlohmann_json.cmake
@@ -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()
diff --git a/cmake/dependencies/windows.cmake b/cmake/dependencies/windows.cmake
index 11a40ecf..3faad7df 100644
--- a/cmake/dependencies/windows.cmake
+++ b/cmake/dependencies/windows.cmake
@@ -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)
diff --git a/cmake/packaging/linux.cmake b/cmake/packaging/linux.cmake
index 785ecb61..90f3fffe 100644
--- a/cmake/packaging/linux.cmake
+++ b/cmake/packaging/linux.cmake
@@ -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()
diff --git a/cmake/prep/init.cmake b/cmake/prep/init.cmake
index ae15d9f2..4c03ecfb 100644
--- a/cmake/prep/init.cmake
+++ b/cmake/prep/init.cmake
@@ -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()
diff --git a/cmake/prep/options.cmake b/cmake/prep/options.cmake
index c7e71dc2..df152c76 100644
--- a/cmake/prep/options.cmake
+++ b/cmake/prep/options.cmake
@@ -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()
diff --git a/cmake/prep/special_package_configuration.cmake b/cmake/prep/special_package_configuration.cmake
index d0928712..5e03f5ad 100644
--- a/cmake/prep/special_package_configuration.cmake
+++ b/cmake/prep/special_package_configuration.cmake
@@ -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()
diff --git a/docs/api.md b/docs/api.md
index e93f500c..5313d598 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -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()
diff --git a/docs/app_examples.md b/docs/app_examples.md
index 0db2ad93..0a814f75 100644
--- a/docs/app_examples.md
+++ b/docs/app_examples.md
@@ -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": ""
diff --git a/docs/building.md b/docs/building.md
index 3c27ac82..b7f5d402 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -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"
diff --git a/docs/configuration.md b/docs/configuration.md
index 9a08d0c2..f5e31f72 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -225,7 +225,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
| Example |
@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 |
@@ -974,7 +974,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
| Default |
- @code{}verify_only@endcode |
+ @code{}
+ disabled
+ @endcode |
| Example |
@@ -1203,6 +1205,31 @@ editing the `conf` file in a text editor. Use the examples as reference.
+
+### dd_config_revert_on_disconnect
+
+
+
+ | Description |
+
+ 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.}
+ |
+
+
+ | Default |
+ @code{}disabled@endcode |
+
+
+ | Example |
+ @code{}
+ dd_config_revert_on_disconnect = enabled
+ @endcode |
+
+
+
### dd_mode_remapping
diff --git a/docs/guides.md b/docs/guides.md
index d7271c7e..1b9f654a 100644
--- a/docs/guides.md
+++ b/docs/guides.md
@@ -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).}
diff --git a/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.metainfo.xml b/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.metainfo.xml
index 6ccadf74..fe5f9d76 100644
--- a/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.metainfo.xml
+++ b/packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.metainfo.xml
@@ -29,14 +29,18 @@
NOTE: Sunshine requires additional installation steps.
- flatpak run --command=additional-install.sh @PROJECT_FQDN@
+
+ flatpak run --command=additional-install.sh @PROJECT_FQDN@
+
NOTE: Sunshine uses a self-signed certificate. The web browser will report it as not secure, but it is safe.
NOTE: KMS Grab (Optional)
- sudo -i PULSE_SERVER=unix:/run/user/$(id -u $whoami)/pulse/native flatpak run @PROJECT_FQDN@
+
+ sudo -i PULSE_SERVER=unix:/run/user/$(id -u $whoami)/pulse/native flatpak run @PROJECT_FQDN@
+
-
+
LizardByte
diff --git a/packaging/linux/flatpak/scripts/sunshine.sh b/packaging/linux/flatpak/scripts/sunshine.sh
new file mode 100644
index 00000000..6fb51457
--- /dev/null
+++ b/packaging/linux/flatpak/scripts/sunshine.sh
@@ -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
diff --git a/packaging/linux/flatpak/sunshine.desktop b/packaging/linux/flatpak/sunshine.desktop
index f1753c9c..eca745ef 100644
--- a/packaging/linux/flatpak/sunshine.desktop
+++ b/packaging/linux/flatpak/sunshine.desktop
@@ -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
diff --git a/packaging/linux/flatpak/sunshine_kms.desktop b/packaging/linux/flatpak/sunshine_kms.desktop
deleted file mode 100644
index 59d35d71..00000000
--- a/packaging/linux/flatpak/sunshine_kms.desktop
+++ /dev/null
@@ -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
diff --git a/scripts/linux_build.sh b/scripts/linux_build.sh
index 4bfd4393..368edcef 100644
--- a/scripts/linux_build.sh
+++ b/scripts/linux_build.sh
@@ -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"
diff --git a/scripts/update_clang_format.py b/scripts/update_clang_format.py
index 9e0dacda..7ae027fa 100644
--- a/scripts/update_clang_format.py
+++ b/scripts/update_clang_format.py
@@ -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'
]
diff --git a/src/audio.cpp b/src/audio.cpp
index 05672b2e..8eaa7f76 100644
--- a/src/audio.cpp
+++ b/src/audio.cpp
@@ -2,16 +2,18 @@
* @file src/audio.cpp
* @brief Definitions for audio capture and encoding.
*/
+// standard includes
#include
+// lib includes
#include
-#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;
using sample_queue_t = std::shared_ptr>>;
- 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 ¶ms);
+ 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 ¶ms);
- 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(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(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(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(start_audio_control, stop_audio_control) };
+ audio_ctx_ref_t get_audio_ctx_ref() {
+ static auto control_shared {safe::make_shared(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 ¶ms) {
+ void apply_surround_params(opus_stream_config_t &stream, const stream_params_t ¶ms) {
stream.channelCount = params.channelCount;
stream.streams = params.streams;
stream.coupledStreams = params.coupledStreams;
diff --git a/src/audio.h b/src/audio.h
index 6add0584..b6c75a1c 100644
--- a/src/audio.h
+++ b/src/audio.h
@@ -73,8 +73,7 @@ namespace audio {
using packet_t = std::pair;
using audio_ctx_ref_t = safe::shared_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
diff --git a/src/cbs.cpp b/src/cbs.cpp
index af4b2f8a..bce14b6e 100644
--- a/src/cbs.cpp
+++ b/src/cbs.cpp
@@ -3,6 +3,7 @@
* @brief Definitions for FFmpeg Coded Bitstream API.
*/
extern "C" {
+// lib includes
#include
#include
#include
@@ -10,14 +11,15 @@ extern "C" {
#include
}
+// 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
- write(cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
+ util::buffer_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 data { frag.data_size };
+ util::buffer_t data {frag.data_size};
std::copy_n(frag.data, frag.data_size, std::begin(data));
return data;
}
- util::buffer_t
- write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
+ util::buffer_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;
diff --git a/src/cbs.h b/src/cbs.h
index a22a756b..5dfddab5 100644
--- a/src/cbs.h
+++ b/src/cbs.h
@@ -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
diff --git a/src/config.cpp b/src/config.cpp
index efa6af68..77ed1ce1 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -2,6 +2,7 @@
* @file src/config.cpp
* @brief Definitions for the configuration of Sunshine.
*/
+// standard includes
#include
#include
#include
@@ -11,22 +12,23 @@
#include
#include
+// lib includes
#include
#include
#include
#include
+// 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
#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
- std::optional
- quality_from_view(const std::string_view &quality_type, const std::optional(&original)) {
+ template
+ std::optional quality_from_view(const std::string_view &quality_type, const std::optional(&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
- std::optional
- rc_from_view(const std::string_view &rc, const std::optional(&original)) {
+ template
+ std::optional rc_from_view(const std::string_view &rc, const std::optional(&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
- std::optional
- usage_from_view(const std::string_view &usage, const std::optional(&original)) {
+ template
+ std::optional usage_from_view(const std::string_view &usage, const std::optional(&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
- preset_from_view(const std::string_view &preset) {
+ std::optional 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
- 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 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("requested_resolution"s);
auto requested_fps = entry.template get_optional("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 { 1 / 24.9 }, // key_repeat_period
+ std::chrono::duration {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
- It
- skip_list(It skipper, It end) {
+ template
+ 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>>
- 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
- parse_config(const std::string_view &file_content) {
+ std::unordered_map parse_config(const std::string_view &file_content) {
std::unordered_map vars;
auto pos = std::begin(file_content);
@@ -678,8 +700,7 @@ namespace config {
return vars;
}
- void
- string_f(std::unordered_map &vars, const std::string &name, std::string &input) {
+ void string_f(std::unordered_map &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
- void
- generic_f(std::unordered_map &vars, const std::string &name, T &input, F &&f) {
+ template
+ void generic_f(std::unordered_map &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 &vars, const std::string &name, std::string &input, const std::vector &allowed_vals) {
+ void string_restricted_f(std::unordered_map &vars, const std::string &name, std::string &input, const std::vector &allowed_vals) {
std::string temp;
string_f(vars, name, temp);
@@ -713,8 +732,7 @@ namespace config {
}
}
- void
- path_f(std::unordered_map &vars, const std::string &name, fs::path &input) {
+ void path_f(std::unordered_map &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 &vars, const std::string &name, std::string &input) {
+ void path_f(std::unordered_map &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 &vars, const std::string &name, int &input) {
+ void int_f(std::unordered_map &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(val.substr(2));
- }
- else {
+ } else {
input = util::from_view(val);
}
vars.erase(it);
}
- void
- int_f(std::unordered_map &vars, const std::string &name, std::optional &input) {
+ void int_f(std::unordered_map &vars, const std::string &name, std::optional &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(val.substr(2));
- }
- else {
+ } else {
input = util::from_view(val);
}
vars.erase(it);
}
- template
- void
- int_f(std::unordered_map &vars, const std::string &name, int &input, F &&f) {
+ template
+ void int_f(std::unordered_map &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
- void
- int_f(std::unordered_map &vars, const std::string &name, std::optional &input, F &&f) {
+ template
+ void int_f(std::unordered_map &vars, const std::string &name, std::optional &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 &vars, const std::string &name, int &input, const std::pair &range) {
+ void int_between_f(std::unordered_map &vars, const std::string &name, int &input, const std::pair &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 &vars, const std::string &name, bool &input) {
+ void bool_f(std::unordered_map &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 &vars, const std::string &name, double &input) {
+ void double_f(std::unordered_map &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 &vars, const std::string &name, double &input, const std::pair &range) {
+ void double_between_f(std::unordered_map &vars, const std::string &name, double &input, const std::pair &range) {
double temp = input;
double_f(vars, name, temp);
@@ -886,8 +894,7 @@ namespace config {
}
}
- void
- list_string_f(std::unordered_map &vars, const std::string &name, std::vector &input) {
+ void list_string_f(std::unordered_map &vars, const std::string &name, std::vector &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 &vars, const std::string &name, std::vector &input) {
+ void list_prep_cmd_f(std::unordered_map &vars, const std::string &name, std::vector &input) {
std::string string;
string_f(vars, name, string);
@@ -956,8 +959,7 @@ namespace config {
}
}
- void
- list_server_cmd_f(std::unordered_map &vars, const std::string &name, std::vector &input) {
+ void list_server_cmd_f(std::unordered_map &vars, const std::string &name, std::vector &input) {
std::string string;
string_f(vars, name, string);
@@ -983,8 +985,7 @@ namespace config {
}
}
- void
- list_int_f(std::unordered_map &vars, const std::string &name, std::vector &input) {
+ void list_int_f(std::unordered_map &vars, const std::string &name, std::vector &input) {
std::vector 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(val.substr(2));
- }
- else {
+ } else {
tmp = util::from_view(val);
}
input.emplace_back(tmp);
}
}
- void
- map_int_int_f(std::unordered_map &vars, const std::string &name, std::unordered_map &input) {
+ void map_int_int_f(std::unordered_map &vars, const std::string &name, std::unordered_map &input) {
std::vector 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 &
- get_supported_gamepad_options() {
+ std::vector &get_supported_gamepad_options() {
const auto options = platf::supported_gamepads(nullptr);
static std::vector opts {};
opts.reserve(options.size());
@@ -1077,8 +1074,7 @@ namespace config {
return opts;
}
- void
- apply_config(std::unordered_map &&vars) {
+ void apply_config(std::unordered_map &&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::max() });
+ int_between_f(vars, "dd_config_revert_delay", value, {0, std::numeric_limits::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::max() });
+ int_between_f(vars, "ping_timeout", to, {-1, std::numeric_limits::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::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::max() });
+ double repeat_frequency {0};
+ double_between_f(vars, "key_repeat_frequency", repeat_frequency, {0, std::numeric_limits::max()});
if (repeat_frequency > 0) {
- config::input.key_repeat_period = std::chrono::duration { 1 / repeat_frequency };
+ config::input.key_repeat_period = std::chrono::duration {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 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];
diff --git a/src/config.h b/src/config.h
index 2543957a..3693dc6e 100644
--- a/src/config.h
+++ b/src/config.h
@@ -4,6 +4,7 @@
*/
#pragma once
+// standard includes
#include
#include
#include
@@ -11,6 +12,7 @@
#include
#include
+// 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
- parse_config(const std::string_view &file_content);
+ int parse(int argc, char *argv[]);
+ std::unordered_map parse_config(const std::string_view &file_content);
} // namespace config
diff --git a/src/confighttp.cpp b/src/confighttp.cpp
index 62705358..ea3f45e1 100644
--- a/src/confighttp.cpp
+++ b/src/confighttp.cpp
@@ -1,30 +1,29 @@
/**
* @file src/confighttp.cpp
- * @brief Definitions for the Web UI Config HTTP server.
+ * @brief Definitions for the Web UI Config HTTPS server.
*
* @todo Authentication, better handling of routes common to nvhttp, cleanup
*/
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
-#include "process.h"
-
+// standard includes
#include
+#include
#include
+#include
+#include
+#include
+#include
-#include
-#include
-#include
-
+// lib includes
#include
-
#include
-
#include
-
+#include
#include
#include
-#include
+// local includes
#include "config.h"
#include "confighttp.h"
#include "crypto.h"
@@ -36,51 +35,45 @@
#include "network.h"
#include "nvhttp.h"
#include "platform/common.h"
-#include "rtsp.h"
+#include "process.h"
#include "utility.h"
#include "uuid.h"
#include "version.h"
-#include "process.h"
using namespace std::literals;
namespace confighttp {
namespace fs = std::filesystem;
- namespace pt = boost::property_tree;
using https_server_t = SimpleWeb::Server;
-
using args_t = SimpleWeb::CaseInsensitiveMultimap;
using resp_https_t = std::shared_ptr::Response>;
using req_https_t = std::shared_ptr::Request>;
- std::string sessionCookie;
- static std::chrono::time_point cookie_creation_time;
-
+ // Keep the base enum for client operations.
enum class op_e {
- ADD, ///< Add client
+ ADD, ///< Add client
REMOVE ///< Remove client
};
+ // SESSION COOKIE (base logic)
+ std::string sessionCookie;
+ static std::chrono::time_point cookie_creation_time;
+
/**
* @brief Log the request details.
* @param request The HTTP request object.
*/
- void
- print_req(const req_https_t &request) {
+ void print_req(const req_https_t &request) {
BOOST_LOG(debug) << "METHOD :: "sv << request->method;
BOOST_LOG(debug) << "DESTINATION :: "sv << request->path;
-
for (auto &[name, val] : request->header) {
BOOST_LOG(debug) << name << " -- " << (name == "Authorization" ? "CREDENTIALS REDACTED" : val);
}
-
BOOST_LOG(debug) << " [--] "sv;
-
for (auto &[name, val] : request->parse_query_string()) {
BOOST_LOG(debug) << name << " -- " << val;
}
-
BOOST_LOG(debug) << " [--] "sv;
}
@@ -89,11 +82,10 @@ namespace confighttp {
* @param response The HTTP response object.
* @param output_tree The JSON tree to send.
*/
- void
- send_response(resp_https_t response, const pt::ptree &output_tree) {
- std::ostringstream data;
- pt::write_json(data, output_tree);
- response->write(data.str());
+ void send_response(resp_https_t response, const nlohmann::json &output_tree) {
+ SimpleWeb::CaseInsensitiveMultimap headers;
+ headers.emplace("Content-Type", "application/json");
+ response->write(output_tree.dump(), headers);
}
/**
@@ -101,11 +93,19 @@ namespace confighttp {
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
- void
- send_unauthorized(resp_https_t response, req_https_t request) {
+ void send_unauthorized(resp_https_t response, req_https_t request) {
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
- response->write(SimpleWeb::StatusCode::client_error_unauthorized);
+ constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_unauthorized;
+ nlohmann::json tree;
+ tree["status_code"] = code;
+ tree["status"] = false;
+ tree["error"] = "Unauthorized";
+ const SimpleWeb::CaseInsensitiveMultimap headers {
+ {"Content-Type", "application/json"},
+ {"WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")"}
+ };
+ response->write(code, tree.dump(), headers);
}
/**
@@ -114,60 +114,68 @@ namespace confighttp {
* @param request The HTTP request object.
* @param path The path to redirect to.
*/
- void
- send_redirect(resp_https_t response, req_https_t request, const char *path) {
+ void send_redirect(resp_https_t response, req_https_t request, const char *path) {
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
- BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
+ BOOST_LOG(info) << "Web UI: ["sv << address << "] -- redirecting"sv;
const SimpleWeb::CaseInsensitiveMultimap headers {
- { "Location", path }
+ {"Location", path}
};
response->write(SimpleWeb::StatusCode::redirection_temporary_redirect, headers);
}
+ /**
+ * @brief Retrieve the value of a key from a cookie string.
+ * @param cookieString The cookie header string.
+ * @param key The key to search.
+ * @return The value if found, empty string otherwise.
+ */
std::string getCookieValue(const std::string& cookieString, const std::string& key) {
std::string keyWithEqual = key + "=";
std::size_t startPos = cookieString.find(keyWithEqual);
-
- if (startPos == std::string::npos) {
+ if (startPos == std::string::npos)
return "";
- }
-
startPos += keyWithEqual.length();
std::size_t endPos = cookieString.find(";", startPos);
-
- if (endPos == std::string::npos) {
+ if (endPos == std::string::npos)
return cookieString.substr(startPos);
- }
-
return cookieString.substr(startPos, endPos - startPos);
}
- bool
- checkIPOrigin(resp_https_t response, req_https_t request) {
+ /**
+ * @brief Check if the IP origin is allowed.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ * @return True if allowed, false otherwise.
+ */
+ bool checkIPOrigin(resp_https_t response, req_https_t request) {
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
auto ip_type = net::from_address(address);
-
if (ip_type > http::origin_web_ui_allowed) {
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- denied"sv;
response->write(SimpleWeb::StatusCode::client_error_forbidden);
return false;
}
-
return true;
}
- bool
- authenticate(resp_https_t response, req_https_t request, bool needsRedirect = false) {
- if (!checkIPOrigin(response, request)) {
+ /**
+ * @brief Authenticate the request.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ * @param needsRedirect Whether to redirect on failure.
+ * @return True if authenticated, false otherwise.
+ *
+ * This function uses session cookies (if set) and ensures they have not expired.
+ */
+ bool authenticate(resp_https_t response, req_https_t request, bool needsRedirect = false) {
+ if (!checkIPOrigin(response, request))
return false;
- }
-
- // If credentials are shown, redirect the user to a /welcome page
+ // If credentials not set, redirect to welcome.
if (config::sunshine.username.empty()) {
send_redirect(response, request, "/welcome");
return false;
}
-
+ // Guard: on failure, redirect if requested.
auto fg = util::fail_guard([&]() {
if (needsRedirect) {
std::string redir_path = "/login?redir=.";
@@ -177,27 +185,20 @@ namespace confighttp {
send_unauthorized(response, request);
}
});
-
- if (sessionCookie.empty()) {
+ if (sessionCookie.empty())
return false;
- }
-
- // Cookie has expired
+ // Check for expiry (SESSION_EXPIRE_DURATION assumed defined elsewhere)
if (std::chrono::steady_clock::now() - cookie_creation_time > SESSION_EXPIRE_DURATION) {
sessionCookie.clear();
return false;
}
-
auto cookies = request->header.find("cookie");
- if (cookies == request->header.end()) {
+ if (cookies == request->header.end())
return false;
- }
-
auto authCookie = getCookieValue(cookies->second, "auth");
- if (authCookie.empty() || util::hex(crypto::hash(authCookie + config::sunshine.salt)).to_string() != sessionCookie) {
+ if (authCookie.empty() ||
+ util::hex(crypto::hash(authCookie + config::sunshine.salt)).to_string() != sessionCookie)
return false;
- }
-
fg.disable();
return true;
}
@@ -207,77 +208,63 @@ namespace confighttp {
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
- void
- not_found(resp_https_t response, [[maybe_unused]] req_https_t request) {
+ void not_found(resp_https_t response, [[maybe_unused]] req_https_t request) {
constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_not_found;
-
- pt::ptree tree;
- tree.put("status_code", static_cast(code));
- tree.put("error", "Not Found");
-
- std::ostringstream data;
- pt::write_json(data, tree);
-
+ nlohmann::json tree;
+ tree["status_code"] = static_cast(code);
+ tree["error"] = "Not Found";
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "application/json");
-
- response->write(code, data.str(), headers);
+ response->write(code, tree.dump(), headers);
}
/**
* @brief Send a 400 Bad Request response.
* @param response The HTTP response object.
* @param request The HTTP request object.
- * @param error_message The error message to include in the response.
+ * @param error_message The error message.
*/
- void
- bad_request(resp_https_t response, [[maybe_unused]] req_https_t request, const std::string &error_message = "Bad Request") {
+ void bad_request(resp_https_t response, [[maybe_unused]] req_https_t request, const std::string &error_message = "Bad Request") {
constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_bad_request;
-
- pt::ptree tree;
- tree.put("status_code", static_cast(code));
- tree.put("status", false);
- tree.put("error", error_message);
-
- std::ostringstream data;
- pt::write_json(data, tree);
-
+ nlohmann::json tree;
+ tree["status_code"] = static_cast(code);
+ tree["status"] = false;
+ tree["error"] = error_message;
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "application/json");
-
- response->write(code, data.str(), headers);
+ response->write(code, tree.dump(), headers);
}
/**
* @brief Get the index page.
* @param response The HTTP response object.
* @param request The HTTP request object.
- * @todo combine these functions into a single function that accepts the page, i.e "index", "pin", "apps"
*/
- void
- fetchStaticPage(resp_https_t response, req_https_t request, const std::string& page, bool needsAuthenticate) {
- if (needsAuthenticate) {
- if (!authenticate(response, request, true)) return;
- }
-
+ void getIndexPage(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
- std::string content = file_handler::read_file((WEB_DIR + page).c_str());
- const SimpleWeb::CaseInsensitiveMultimap headers {
- { "Content-Type", "text/html; charset=utf-8" },
- { "Access-Control-Allow-Origin", "https://images.igdb.com/"}
+ std::string content = file_handler::read_file(WEB_DIR "index.html");
+ SimpleWeb::CaseInsensitiveMultimap headers {
+ {"Content-Type", "text/html; charset=utf-8"}
};
response->write(content, headers);
- };
+ }
/**
* @brief Get the PIN page.
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
- void
- getIndexPage(resp_https_t response, req_https_t request) {
- fetchStaticPage(response, request, "index.html", true);
+ void getPinPage(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
+ print_req(request);
+ std::string content = file_handler::read_file(WEB_DIR "pin.html");
+ SimpleWeb::CaseInsensitiveMultimap headers {
+ {"Content-Type", "text/html; charset=utf-8"}
+ };
+ response->write(content, headers);
}
/**
@@ -285,9 +272,16 @@ namespace confighttp {
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
- void
- getPinPage(resp_https_t response, req_https_t request) {
- fetchStaticPage(response, request, "pin.html", true);
+ void getAppsPage(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
+ print_req(request);
+ std::string content = file_handler::read_file(WEB_DIR "apps.html");
+ SimpleWeb::CaseInsensitiveMultimap headers {
+ {"Content-Type", "text/html; charset=utf-8"},
+ {"Access-Control-Allow-Origin", "https://images.igdb.com/"}
+ };
+ response->write(content, headers);
}
/**
@@ -295,9 +289,15 @@ namespace confighttp {
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
- void
- getAppsPage(resp_https_t response, req_https_t request) {
- fetchStaticPage(response, request, "apps.html", true);
+ void getClientsPage(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
+ print_req(request);
+ std::string content = file_handler::read_file(WEB_DIR "clients.html");
+ SimpleWeb::CaseInsensitiveMultimap headers {
+ {"Content-Type", "text/html; charset=utf-8"}
+ };
+ response->write(content, headers);
}
/**
@@ -305,9 +305,15 @@ namespace confighttp {
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
- void
- getConfigPage(resp_https_t response, req_https_t request) {
- fetchStaticPage(response, request, "config.html", true);
+ void getConfigPage(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
+ print_req(request);
+ std::string content = file_handler::read_file(WEB_DIR "config.html");
+ SimpleWeb::CaseInsensitiveMultimap headers {
+ {"Content-Type", "text/html; charset=utf-8"}
+ };
+ response->write(content, headers);
}
/**
@@ -315,9 +321,15 @@ namespace confighttp {
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
- void
- getPasswordPage(resp_https_t response, req_https_t request) {
- fetchStaticPage(response, request, "password.html", true);
+ void getPasswordPage(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
+ print_req(request);
+ std::string content = file_handler::read_file(WEB_DIR "password.html");
+ SimpleWeb::CaseInsensitiveMultimap headers {
+ {"Content-Type", "text/html; charset=utf-8"}
+ };
+ response->write(content, headers);
}
/**
@@ -325,18 +337,17 @@ namespace confighttp {
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
- void
- getWelcomePage(resp_https_t response, req_https_t request) {
- if (!checkIPOrigin(response, request)) {
- return;
- }
-
+ void getWelcomePage(resp_https_t response, req_https_t request) {
+ print_req(request);
if (!config::sunshine.username.empty()) {
send_redirect(response, request, "/");
return;
}
-
- fetchStaticPage(response, request, "welcome.html", false);
+ std::string content = file_handler::read_file(WEB_DIR "welcome.html");
+ SimpleWeb::CaseInsensitiveMultimap headers {
+ {"Content-Type", "text/html; charset=utf-8"}
+ };
+ response->write(content, headers);
}
/**
@@ -344,43 +355,27 @@ namespace confighttp {
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
- void
- getLoginPage(resp_https_t response, req_https_t request) {
- if (!checkIPOrigin(response, request)) {
+ void getTroubleshootingPage(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
return;
- }
-
- if (config::sunshine.username.empty()) {
- send_redirect(response, request, "/welcome");
- return;
- }
-
- fetchStaticPage(response, request, "login.html", false);
- }
-
- void
- getTroubleshootingPage(resp_https_t response, req_https_t request) {
- fetchStaticPage(response, request, "troubleshooting.html", true);
+ print_req(request);
+ std::string content = file_handler::read_file(WEB_DIR "troubleshooting.html");
+ SimpleWeb::CaseInsensitiveMultimap headers {
+ {"Content-Type", "text/html; charset=utf-8"}
+ };
+ response->write(content, headers);
}
/**
* @brief Get the favicon image.
* @param response The HTTP response object.
* @param request The HTTP request object.
- * @todo combine function with getSunshineLogoImage and possibly getNodeModules
- * @todo use mime_types map
*/
- void
- getFaviconImage(resp_https_t response, req_https_t request) {
- if (!checkIPOrigin(response, request)) {
- return;
- }
-
+ void getFaviconImage(resp_https_t response, req_https_t request) {
print_req(request);
-
- std::ifstream in(WEB_DIR "images/apollo.ico", std::ios::binary);
- const SimpleWeb::CaseInsensitiveMultimap headers {
- { "Content-Type", "image/x-icon" }
+ std::ifstream in(WEB_DIR "images/sunshine.ico", std::ios::binary);
+ SimpleWeb::CaseInsensitiveMultimap headers {
+ {"Content-Type", "image/x-icon"}
};
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
}
@@ -389,20 +384,12 @@ namespace confighttp {
* @brief Get the Sunshine logo image.
* @param response The HTTP response object.
* @param request The HTTP request object.
- * @todo combine function with getFaviconImage and possibly getNodeModules
- * @todo use mime_types map
*/
- void
- getSunshineLogoImage(resp_https_t response, req_https_t request) {
- if (!checkIPOrigin(response, request)) {
- return;
- }
-
+ void getSunshineLogoImage(resp_https_t response, req_https_t request) {
print_req(request);
-
- std::ifstream in(WEB_DIR "images/logo-apollo-45.png", std::ios::binary);
- const SimpleWeb::CaseInsensitiveMultimap headers {
- { "Content-Type", "image/png" }
+ std::ifstream in(WEB_DIR "images/logo-sunshine-45.png", std::ios::binary);
+ SimpleWeb::CaseInsensitiveMultimap headers {
+ {"Content-Type", "image/png"}
};
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
}
@@ -411,10 +398,9 @@ namespace confighttp {
* @brief Check if a path is a child of another path.
* @param base The base path.
* @param query The path to check.
- * @return True if the path is a child of the base path, false otherwise.
+ * @return True if the query is within base, false otherwise.
*/
- bool
- isChildPath(fs::path const &base, fs::path const &query) {
+ bool isChildPath(fs::path const &base, fs::path const &query) {
auto relPath = fs::relative(base, query);
return *(relPath.begin()) != fs::path("..");
}
@@ -424,21 +410,12 @@ namespace confighttp {
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
- void
- getNodeModules(resp_https_t response, req_https_t request) {
- if (!checkIPOrigin(response, request)) {
- return;
- }
-
+ void getNodeModules(resp_https_t response, req_https_t request) {
print_req(request);
fs::path webDirPath(WEB_DIR);
- fs::path nodeModulesPath(webDirPath / "assets");
-
- // .relative_path is needed to shed any leading slash that might exist in the request path
+ fs::path nodeModulesPath = webDirPath / "assets";
auto filePath = fs::weakly_canonical(webDirPath / fs::path(request->path).relative_path());
-
- // Don't do anything if file does not exist or is outside the assets directory
- if (!isChildPath(filePath, nodeModulesPath)) {
+ if (!isChildPath(nodeModulesPath, filePath)) {
BOOST_LOG(warning) << "Someone requested a path " << filePath << " that is outside the assets folder";
bad_request(response, request);
return;
@@ -447,20 +424,14 @@ namespace confighttp {
not_found(response, request);
return;
}
-
auto relPath = fs::relative(filePath, webDirPath);
- // get the mime type from the file extension mime_types map
- // remove the leading period from the extension
- auto mimeType = mime_types.find(relPath.extension().string().substr(1));
- // check if the extension is in the map at the x position
- if (mimeType == mime_types.end()) {
+ auto it = mime_types.find(relPath.extension().string().substr(1));
+ if (it == mime_types.end()) {
bad_request(response, request);
return;
}
-
- // if it is, set the content type to the mime type
SimpleWeb::CaseInsensitiveMultimap headers;
- headers.emplace("Content-Type", mimeType->second);
+ headers.emplace("Content-Type", it->second);
std::ifstream in(filePath.string(), std::ios::binary);
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
}
@@ -469,153 +440,125 @@ namespace confighttp {
* @brief Get the list of available applications.
* @param response The HTTP response object.
* @param request The HTTP request object.
- *
- * @api_examples{/api/apps| GET| null}
*/
- void
- getApps(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ void getApps(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
- std::string content = file_handler::read_file(config::stream.file_apps.c_str());
- const SimpleWeb::CaseInsensitiveMultimap headers {
- { "Content-Type", "application/json" }
- };
- response->write(content, headers);
+ try {
+ std::string content = file_handler::read_file(config::stream.file_apps.c_str());
+ nlohmann::json file_tree = nlohmann::json::parse(content);
+ // (Legacy conversions omitted for brevity.)
+ send_response(response, file_tree);
+ } catch (std::exception &e) {
+ BOOST_LOG(warning) << "GetApps: "sv << e.what();
+ bad_request(response, request, e.what());
+ }
}
/**
- * @brief Get the logs from the log file.
+ * @brief Save an application.
* @param response The HTTP response object.
* @param request The HTTP request object.
*
- * @api_examples{/api/logs| GET| null}
+ * To save a new application the index must be -1.
*/
- void
- getLogs(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ void saveApp(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
- std::string content = file_handler::read_file(config::sunshine.log_file.c_str());
- const SimpleWeb::CaseInsensitiveMultimap headers {
- { "Content-Type", "text/plain" }
- };
- response->write(SimpleWeb::StatusCode::success_ok, content, headers);
- }
-
- /**
- * @brief Save an application. To save a new application the index must be `-1`. To update an existing application, you must provide the current index of the application.
- * @param response The HTTP response object.
- * @param request The HTTP request object.
- * The body for the post request should be JSON serialized in the following format:
- * @code{.json}
- * {
- * "name": "",
- * "output": "",
- * "cmd": "",
- * "index": -1,
- * "exclude-global-prep-cmd": false,
- * "elevated": false,
- * "auto-detach": true,
- * "wait-all": true,
- * "exit-timeout": 5,
- * "prep-cmd": [
- * {
- * "do": "",
- * "undo": "",
- * "elevated": false
- * }
- * ],
- * "detached": [
- * ""
- * ],
- * "image-path": "",
- * "uuid": "C3445C24-871A-FD23-0708-615C121B5B78"
- * }
- * @endcode
- *
- * @api_examples{/api/apps| POST| {"name":"Hello, World!","index":-1}}
- */
- void
- saveApp(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
- print_req(request);
-
std::stringstream ss;
ss << request->content.rdbuf();
-
- BOOST_LOG(info) << config::stream.file_apps;
try {
- // TODO: Input Validation
- pt::ptree fileTree;
- pt::ptree inputTree;
- pt::ptree outputTree;
- pt::read_json(ss, inputTree);
- pt::read_json(config::stream.file_apps, fileTree);
-
- proc::migrate_apps(&fileTree, &inputTree);
-
- pt::write_json(config::stream.file_apps, fileTree);
+ nlohmann::json input_tree = nlohmann::json::parse(ss.str());
+ std::string file = file_handler::read_file(config::stream.file_apps.c_str());
+ nlohmann::json file_tree = nlohmann::json::parse(file);
+ // Remove empty keys if needed
+ if (input_tree.contains("prep-cmd") && input_tree["prep-cmd"].empty())
+ input_tree.erase("prep-cmd");
+ if (input_tree.contains("detached") && input_tree["detached"].empty())
+ input_tree.erase("detached");
+ auto &apps_node = file_tree["apps"];
+ int index = input_tree["index"].get();
+ input_tree.erase("index");
+ if (index == -1) {
+ apps_node.push_back(input_tree);
+ } else {
+ nlohmann::json newApps = nlohmann::json::array();
+ for (size_t i = 0; i < apps_node.size(); ++i) {
+ if (static_cast(i) == index)
+ newApps.push_back(input_tree);
+ else
+ newApps.push_back(apps_node[i]);
+ }
+ file_tree["apps"] = newApps;
+ }
+ std::sort(apps_node.begin(), apps_node.end(), [](const nlohmann::json &a, const nlohmann::json &b) {
+ return a["name"].get() < b["name"].get();
+ });
+ file_handler::write_file(config::stream.file_apps.c_str(), file_tree.dump(4));
proc::refresh(config::stream.file_apps);
-
- outputTree.put("status", true);
- send_response(response, outputTree);
- }
- catch (std::exception &e) {
+ nlohmann::json output_tree;
+ output_tree["status"] = true;
+ send_response(response, output_tree);
+ } catch (std::exception &e) {
BOOST_LOG(warning) << "SaveApp: "sv << e.what();
bad_request(response, request, e.what());
}
}
+ /**
+ * @brief Close the currently running application.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
+ void closeApp(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
+ print_req(request);
+ proc::proc.terminate();
+ nlohmann::json output_tree;
+ output_tree["status"] = true;
+ send_response(response, output_tree);
+ }
+
/**
* @brief Delete an application.
* @param response The HTTP response object.
* @param request The HTTP request object.
- *
- * @api_examples{/api/apps/9999| DELETE| null}
*/
- void
- deleteApp(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
- print_req(request);
-
- auto args = request->parse_query_string();
- if (
- args.find("uuid"s) == std::end(args)
- ) {
- bad_request(response, request, "Missing a required parameter to delete app");
+ void deleteApp(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
return;
- }
-
- auto uuid = nvhttp::get_arg(args, "uuid");
-
- pt::ptree fileTree;
+ print_req(request);
try {
- pt::read_json(config::stream.file_apps, fileTree);
- auto &apps_node = fileTree.get_child("apps"s);
-
- // Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick
- pt::ptree newApps;
- for (const auto &kv : apps_node) {
- auto app_uuid = kv.second.get_optional("uuid"s);
- if (!app_uuid || app_uuid.value() != uuid) {
- newApps.push_back(std::make_pair("", kv.second));
- }
+ nlohmann::json output_tree;
+ nlohmann::json new_apps = nlohmann::json::array();
+ std::string file = file_handler::read_file(config::stream.file_apps.c_str());
+ nlohmann::json file_tree = nlohmann::json::parse(file);
+ auto &apps_node = file_tree["apps"];
+ // In this merged version we assume the app index is part of the URL match (convert as needed)
+ const int index = std::stoi(request->path_match[1]);
+ if (index < 0 || index >= static_cast(apps_node.size())) {
+ std::string error;
+ if (apps_node.empty())
+ error = "No applications to delete";
+ else
+ error = "'index' out of range, max index is " + std::to_string(apps_node.size() - 1);
+ bad_request(response, request, error);
+ return;
}
- fileTree.erase("apps");
- fileTree.push_back(std::make_pair("apps", newApps));
-
- pt::write_json(config::stream.file_apps, fileTree);
-
- pt::ptree outputTree;
- outputTree.put("status", true);
- send_response(response, outputTree);
- }
- catch (std::exception &e) {
+ for (size_t i = 0; i < apps_node.size(); ++i) {
+ if (static_cast(i) != index)
+ new_apps.push_back(apps_node[i]);
+ }
+ file_tree["apps"] = new_apps;
+ file_handler::write_file(config::stream.file_apps.c_str(), file_tree.dump(4));
+ proc::refresh(config::stream.file_apps);
+ output_tree["status"] = true;
+ output_tree["result"] = "application " + std::to_string(index) + " deleted";
+ send_response(response, output_tree);
+ } catch (std::exception &e) {
BOOST_LOG(warning) << "DeleteApp: "sv << e.what();
bad_request(response, request, e.what());
}
@@ -625,155 +568,107 @@ namespace confighttp {
* @brief Upload a cover image.
* @param response The HTTP response object.
* @param request The HTTP request object.
- * The body for the post request should be JSON serialized in the following format:
- * @code{.json}
- * {
- * "key": "igdb_",
- * "url": "https://images.igdb.com/igdb/image/upload/t_cover_big_2x/.png"
- * }
- * @endcode
- *
- * @api_examples{/api/covers/upload| POST| {"key":"igdb_1234","url":"https://images.igdb.com/igdb/image/upload/t_cover_big_2x/abc123.png"}}
*/
- void
- uploadCover(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ void uploadCover(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
std::stringstream ss;
- std::stringstream configStream;
ss << request->content.rdbuf();
- pt::ptree outputTree;
- pt::ptree inputTree;
try {
- pt::read_json(ss, inputTree);
- }
- catch (std::exception &e) {
+ nlohmann::json input_tree = nlohmann::json::parse(ss.str());
+ nlohmann::json output_tree;
+ std::string key = input_tree.value("key", "");
+ if (key.empty()) {
+ bad_request(response, request, "Cover key is required");
+ return;
+ }
+ std::string url = input_tree.value("url", "");
+ const std::string coverdir = platf::appdata().string() + "/covers/";
+ file_handler::make_directory(coverdir);
+ std::string path = coverdir + http::url_escape(key) + ".png";
+ if (!url.empty()) {
+ if (http::url_get_host(url) != "images.igdb.com") {
+ bad_request(response, request, "Only images.igdb.com is allowed");
+ return;
+ }
+ if (!http::download_file(url, path)) {
+ bad_request(response, request, "Failed to download cover");
+ return;
+ }
+ } else {
+ auto data = SimpleWeb::Crypto::Base64::decode(input_tree.value("data", ""));
+ std::ofstream imgfile(path);
+ imgfile.write(data.data(), static_cast(data.size()));
+ }
+ output_tree["status"] = true;
+ output_tree["path"] = path;
+ send_response(response, output_tree);
+ } catch (std::exception &e) {
BOOST_LOG(warning) << "UploadCover: "sv << e.what();
bad_request(response, request, e.what());
- return;
}
-
- auto key = inputTree.get("key", "");
- if (key.empty()) {
- bad_request(response, request, "Cover key is required");
- return;
- }
- auto url = inputTree.get("url", "");
-
- const std::string coverdir = platf::appdata().string() + "/covers/";
- file_handler::make_directory(coverdir);
-
- std::basic_string path = coverdir + http::url_escape(key) + ".png";
- if (!url.empty()) {
- if (http::url_get_host(url) != "images.igdb.com") {
- bad_request(response, request, "Only images.igdb.com is allowed");
- return;
- }
- if (!http::download_file(url, path)) {
- bad_request(response, request, "Failed to download cover");
- return;
- }
- }
- else {
- auto data = SimpleWeb::Crypto::Base64::decode(inputTree.get("data"));
-
- std::ofstream imgfile(path);
- imgfile.write(data.data(), (int) data.size());
- }
- outputTree.put("status", true);
- outputTree.put("path", path);
- send_response(response, outputTree);
}
/**
* @brief Get the configuration settings.
* @param response The HTTP response object.
* @param request The HTTP request object.
- *
- * @api_examples{/api/config| GET| null}
*/
- void
- getConfig(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ void getConfig(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
- pt::ptree outputTree;
- outputTree.put("status", true);
- outputTree.put("platform", SUNSHINE_PLATFORM);
- outputTree.put("version", PROJECT_VER);
- #ifdef _WIN32
- outputTree.put("vdisplayStatus", (int)proc::vDisplayDriverStatus);
- #endif
-
+ nlohmann::json output_tree;
+ output_tree["status"] = true;
+ output_tree["platform"] = SUNSHINE_PLATFORM;
+ output_tree["version"] = PROJECT_VER;
+#ifdef _WIN32
+ output_tree["vdisplayStatus"] = static_cast(proc::vDisplayDriverStatus);
+#endif
auto vars = config::parse_config(file_handler::read_file(config::sunshine.config_file.c_str()));
-
for (auto &[name, value] : vars) {
- outputTree.put(std::move(name), std::move(value));
+ output_tree[name] = value;
}
-
- send_response(response, outputTree);
+ send_response(response, output_tree);
}
/**
- * @brief Get the locale setting. This endpoint does not require authentication.
+ * @brief Get the locale setting.
* @param response The HTTP response object.
* @param request The HTTP request object.
- *
- * @api_examples{/api/configLocale| GET| null}
*/
- void
- getLocale(resp_https_t response, req_https_t request) {
- // we need to return the locale whether authenticated or not
-
+ void getLocale(resp_https_t response, req_https_t request) {
print_req(request);
-
- pt::ptree outputTree;
- outputTree.put("status", true);
- outputTree.put("locale", config::sunshine.locale);
- send_response(response, outputTree);
+ nlohmann::json output_tree;
+ output_tree["status"] = true;
+ output_tree["locale"] = config::sunshine.locale;
+ send_response(response, output_tree);
}
/**
* @brief Save the configuration settings.
* @param response The HTTP response object.
* @param request The HTTP request object.
- * The body for the post request should be JSON serialized in the following format:
- * @code{.json}
- * {
- * "key": "value"
- * }
- * @endcode
- *
- * @attention{It is recommended to ONLY save the config settings that differ from the default behavior.}
- *
- * @api_examples{/api/config| POST| {"key":"value"}}
*/
- void
- saveConfig(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ void saveConfig(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
std::stringstream ss;
- std::stringstream configStream;
ss << request->content.rdbuf();
try {
- // TODO: Input Validation
- pt::ptree inputTree;
- pt::ptree outputTree;
- pt::read_json(ss, inputTree);
- for (const auto &kv : inputTree) {
- std::string value = inputTree.get(kv.first);
- if (value.length() == 0 || value.compare("null") == 0) continue;
-
- configStream << kv.first << " = " << value << std::endl;
+ std::stringstream config_stream;
+ nlohmann::json output_tree;
+ nlohmann::json input_tree = nlohmann::json::parse(ss.str());
+ for (const auto &[k, v] : input_tree.items()) {
+ if (v.is_null() || (v.is_string() && v.get().empty()))
+ continue;
+ config_stream << k << " = " << (v.is_string() ? v.get() : v.dump()) << std::endl;
}
- file_handler::write_file(config::sunshine.config_file.c_str(), configStream.str());
- outputTree.put("status", true);
- send_response(response, outputTree);
- }
- catch (std::exception &e) {
+ file_handler::write_file(config::sunshine.config_file.c_str(), config_stream.str());
+ output_tree["status"] = true;
+ send_response(response, output_tree);
+ } catch (std::exception &e) {
BOOST_LOG(warning) << "SaveConfig: "sv << e.what();
bad_request(response, request, e.what());
}
@@ -783,41 +678,35 @@ namespace confighttp {
* @brief Restart Sunshine.
* @param response The HTTP response object.
* @param request The HTTP request object.
- *
- * @api_examples{/api/restart| POST| null}
*/
- void
- restart(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ void restart(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
- // We may not return from this call
+ // This call may not return.
platf::restart();
}
- void
- quit(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ /**
+ * @brief Quit Sunshine.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ *
+ * On Windows, if running as a service, a special shutdown code is returned.
+ */
+ void quit(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
BOOST_LOG(warning) << "Requested quit from config page!"sv;
-
- #ifdef _WIN32
- // If we're running in a service, return a special status to
- // tell it to terminate too, otherwise it will just respawn us.
+#ifdef _WIN32
if (GetConsoleWindow() == NULL) {
lifetime::exit_sunshine(ERROR_SHUTDOWN_IN_PROGRESS, true);
} else
- #endif
+#endif
{
lifetime::exit_sunshine(0, true);
}
-
- // We do want to return here
- // If user get a return, then the exit has failed.
- // This might not be thread safe but we're exiting anyways
std::thread write_resp([response]{
std::this_thread::sleep_for(5s);
response->write();
@@ -829,246 +718,151 @@ namespace confighttp {
* @brief Reset the display device persistence.
* @param response The HTTP response object.
* @param request The HTTP request object.
- *
- * @api_examples{/api/reset-display-device-persistence| POST| null}
*/
- void
- resetDisplayDevicePersistence(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ void resetDisplayDevicePersistence(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
- pt::ptree outputTree;
- outputTree.put("status", display_device::reset_persistence());
- send_response(response, outputTree);
+ nlohmann::json output_tree;
+ output_tree["status"] = display_device::reset_persistence();
+ send_response(response, output_tree);
}
/**
* @brief Update existing credentials.
* @param response The HTTP response object.
* @param request The HTTP request object.
- * The body for the post request should be JSON serialized in the following format:
- * @code{.json}
- * {
- * "currentUsername": "Current Username",
- * "currentPassword": "Current Password",
- * "newUsername": "New Username",
- * "newPassword": "New Password",
- * "confirmNewPassword": "Confirm New Password"
- * }
- * @endcode
- *
- * @api_examples{/api/password| POST| {"currentUsername":"admin","currentPassword":"admin","newUsername":"admin","newPassword":"admin","confirmNewPassword":"admin"}}
*/
- void
- savePassword(resp_https_t response, req_https_t request) {
- if (!config::sunshine.username.empty() && !authenticate(response, request)) return;
-
+ void savePassword(resp_https_t response, req_https_t request) {
+ if (!config::sunshine.username.empty() && !authenticate(response, request, true))
+ return;
print_req(request);
-
- std::vector errors = {};
+ std::vector errors;
std::stringstream ss;
- std::stringstream configStream;
ss << request->content.rdbuf();
-
try {
- // TODO: Input Validation
- pt::ptree inputTree;
- pt::ptree outputTree;
- pt::read_json(ss, inputTree);
- auto username = inputTree.count("currentUsername") > 0 ? inputTree.get("currentUsername") : "";
- auto newUsername = inputTree.get("newUsername");
- auto password = inputTree.count("currentPassword") > 0 ? inputTree.get("currentPassword") : "";
- auto newPassword = inputTree.count("newPassword") > 0 ? inputTree.get("newPassword") : "";
- auto confirmPassword = inputTree.count("confirmNewPassword") > 0 ? inputTree.get("confirmNewPassword") : "";
- if (newUsername.length() == 0) newUsername = username;
- if (newUsername.length() == 0) {
- errors.emplace_back("Invalid Username");
- }
- else {
+ nlohmann::json input_tree = nlohmann::json::parse(ss.str());
+ nlohmann::json output_tree;
+ std::string username = input_tree.value("currentUsername", "");
+ std::string newUsername = input_tree.value("newUsername", "");
+ std::string password = input_tree.value("currentPassword", "");
+ std::string newPassword = input_tree.value("newPassword", "");
+ std::string confirmPassword = input_tree.value("confirmNewPassword", "");
+ if (newUsername.empty())
+ newUsername = username;
+ if (newUsername.empty()) {
+ errors.push_back("Invalid Username");
+ } else {
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
- if (config::sunshine.username.empty() || (boost::iequals(username, config::sunshine.username) && hash == config::sunshine.password)) {
- if (newPassword.empty() || newPassword != confirmPassword) {
- errors.emplace_back("Password Mismatch");
- }
+ if (config::sunshine.username.empty() ||
+ (boost::iequals(username, config::sunshine.username) && hash == config::sunshine.password)) {
+ if (newPassword.empty() || newPassword != confirmPassword)
+ errors.push_back("Password Mismatch");
else {
http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword);
http::reload_user_creds(config::sunshine.credentials_file);
-
- // Force user to re-login
- sessionCookie.clear();
-
- outputTree.put("status", true);
+ sessionCookie.clear(); // force re-login
+ output_tree["status"] = true;
}
- }
- else {
- errors.emplace_back("Invalid Current Credentials");
+ } else {
+ errors.push_back("Invalid Current Credentials");
}
}
-
if (!errors.empty()) {
- // join the errors array
std::string error = std::accumulate(errors.begin(), errors.end(), std::string(),
- [](const std::string &a, const std::string &b) {
- return a.empty() ? b : a + ", " + b;
- });
+ [](const std::string &a, const std::string &b) {
+ return a.empty() ? b : a + ", " + b;
+ });
bad_request(response, request, error);
return;
}
-
- send_response(response, outputTree);
- }
- catch (std::exception &e) {
+ send_response(response, output_tree);
+ } catch (std::exception &e) {
BOOST_LOG(warning) << "SavePassword: "sv << e.what();
bad_request(response, request, e.what());
}
}
- void
- login(resp_https_t response, req_https_t request) {
- if (!checkIPOrigin(response, request)) {
- return;
- }
-
- auto fg = util::fail_guard([&]{
- response->write(SimpleWeb::StatusCode::client_error_unauthorized);
- });
-
- std::stringstream ss;
- ss << request->content.rdbuf();
-
- pt::ptree inputTree;
-
- try {
- pt::read_json(ss, inputTree);
- std::string username = inputTree.get("username");
- std::string password = inputTree.get("password");
- std::string hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
-
- if (!boost::iequals(username, config::sunshine.username) || hash != config::sunshine.password) {
- return;
- }
-
- std::string sessionCookieRaw = crypto::rand_alphabet(64);
- sessionCookie = util::hex(crypto::hash(sessionCookieRaw + config::sunshine.salt)).to_string();
- cookie_creation_time = std::chrono::steady_clock::now();
-
- const SimpleWeb::CaseInsensitiveMultimap headers {
- { "Set-Cookie", "auth=" + sessionCookieRaw + "; Secure; Max-Age=2592000; Path=/" }
- };
-
- response->write(headers);
-
- fg.disable();
- } catch (std::exception &e) {
- BOOST_LOG(warning) << "Web UI Login failed: ["sv << net::addr_to_normalized_string(request->remote_endpoint().address()) << "]: "sv << e.what();
- response->write(SimpleWeb::StatusCode::server_error_internal_server_error);
- fg.disable();
- return;
- }
- }
-
/**
- * @brief Send a pin code to the host. The pin is generated from the Moonlight client during the pairing process.
+ * @brief Send a PIN code to the host.
* @param response The HTTP response object.
* @param request The HTTP request object.
- * The body for the post request should be JSON serialized in the following format:
- * @code{.json}
- * {
- * "pin": "",
- * "name": "Friendly Client Name"
- * }
- * @endcode
- *
- * @api_examples{/api/pin| POST| {"pin":"1234","name":"My PC"}}
*/
- void
- savePin(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ void savePin(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
std::stringstream ss;
ss << request->content.rdbuf();
-
try {
- // TODO: Input Validation
- pt::ptree inputTree;
- pt::ptree outputTree;
- pt::read_json(ss, inputTree);
- std::string pin = inputTree.get("pin");
- std::string name = inputTree.get("name");
- outputTree.put("status", nvhttp::pin(pin, name));
- send_response(response, outputTree);
- }
- catch (std::exception &e) {
+ nlohmann::json input_tree = nlohmann::json::parse(ss.str());
+ nlohmann::json output_tree;
+ std::string pin = input_tree.get("pin");
+ std::string name = input_tree.get("name");
+ output_tree["status"] = nvhttp::pin(pin, name);
+ send_response(response, output_tree);
+ } catch (std::exception &e) {
BOOST_LOG(warning) << "SavePin: "sv << e.what();
bad_request(response, request, e.what());
}
}
- void
- getOTP(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ /**
+ * @brief Get a one-time password (OTP).
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
+ void getOTP(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
- pt::ptree outputTree;
-
+ nlohmann::json output_tree;
try {
auto args = request->parse_query_string();
auto it = args.find("passphrase");
- if (it == std::end(args)) {
+ if (it == args.end())
throw std::runtime_error("Passphrase not provided!");
- }
-
- if (it->second.size() < 4) {
+ if (it->second.size() < 4)
throw std::runtime_error("Passphrase too short!");
- }
-
std::string passphrase = it->second;
std::string deviceName;
-
it = args.find("deviceName");
- if (it != std::end(args)) {
+ if (it != args.end())
deviceName = it->second;
- }
-
- outputTree.put("otp", nvhttp::request_otp(passphrase, deviceName));
- outputTree.put("ip", platf::get_local_ip_for_gateway());
- outputTree.put("name", config::nvhttp.sunshine_name);
- outputTree.put("status", true);
- outputTree.put("message", "OTP created, effective within 3 minutes.");
- send_response(response, outputTree);
- }
- catch (std::exception &e) {
+ output_tree["otp"] = nvhttp::request_otp(passphrase, deviceName);
+ output_tree["ip"] = platf::get_local_ip_for_gateway();
+ output_tree["name"] = config::nvhttp.sunshine_name;
+ output_tree["status"] = true;
+ output_tree["message"] = "OTP created, effective within 3 minutes.";
+ send_response(response, output_tree);
+ } catch (std::exception &e) {
BOOST_LOG(warning) << "OTP creation failed: "sv << e.what();
bad_request(response, request, e.what());
}
}
- void
- updateClient(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ /**
+ * @brief Update client information.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
+ void updateClient(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
std::stringstream ss;
ss << request->content.rdbuf();
-
- pt::ptree inputTree, outputTree;
-
try {
- pt::read_json(ss, inputTree);
- std::string uuid = inputTree.get("uuid");
- std::string name = inputTree.get("name");
- auto do_cmds = nvhttp::extract_command_entries(inputTree, "do");
- auto undo_cmds = nvhttp::extract_command_entries(inputTree, "undo");
- auto perm = (crypto::PERM)inputTree.get("perm") & crypto::PERM::_all;
- outputTree.put("status", nvhttp::update_device_info(uuid, name, do_cmds, undo_cmds, perm));
- send_response(response, outputTree);
- }
- catch (std::exception &e) {
+ nlohmann::json input_tree = nlohmann::json::parse(ss.str());
+ nlohmann::json output_tree;
+ std::string uuid = input_tree.get("uuid");
+ std::string name = input_tree.get("name");
+ auto do_cmds = nvhttp::extract_command_entries(input_tree, "do");
+ auto undo_cmds = nvhttp::extract_command_entries(input_tree, "undo");
+ auto perm = static_cast(input_tree.get("perm") & crypto::PERM::_all);
+ output_tree["status"] = nvhttp::update_device_info(uuid, name, do_cmds, undo_cmds, perm);
+ send_response(response, output_tree);
+ } catch (std::exception &e) {
BOOST_LOG(warning) << "Update Client: "sv << e.what();
bad_request(response, request, e.what());
}
@@ -1078,185 +872,133 @@ namespace confighttp {
* @brief Unpair all clients.
* @param response The HTTP response object.
* @param request The HTTP request object.
- *
- * @api_examples{/api/clients/unpair-all| POST| null}
*/
- void
- unpairAll(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ void unpairAll(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
nvhttp::erase_all_clients();
proc::proc.terminate();
-
- pt::ptree outputTree;
- outputTree.put("status", true);
- send_response(response, outputTree);
+ nlohmann::json output_tree;
+ output_tree["status"] = true;
+ send_response(response, output_tree);
}
/**
* @brief Unpair a client.
* @param response The HTTP response object.
* @param request The HTTP request object.
- * The body for the post request should be JSON serialized in the following format:
- * @code{.json}
- * {
- * "uuid": ""
- * }
- * @endcode
- *
- * @api_examples{/api/unpair| POST| {"uuid":"1234"}}
*/
- void
- unpair(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ void unpair(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
std::stringstream ss;
ss << request->content.rdbuf();
-
try {
- // TODO: Input Validation
- pt::ptree inputTree;
- pt::ptree outputTree;
- pt::read_json(ss, inputTree);
- std::string uuid = inputTree.get("uuid");
- outputTree.put("status", nvhttp::unpair_client(uuid));
- send_response(response, outputTree);
- }
- catch (std::exception &e) {
+ nlohmann::json input_tree = nlohmann::json::parse(ss.str());
+ nlohmann::json output_tree;
+ std::string uuid = input_tree.value("uuid", "");
+ output_tree["status"] = nvhttp::unpair_client(uuid);
+ send_response(response, output_tree);
+ } catch (std::exception &e) {
BOOST_LOG(warning) << "Unpair: "sv << e.what();
bad_request(response, request, e.what());
}
}
+ /**
+ * @brief Launch an application.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
void launchApp(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
- pt::ptree outputTree;
-
+ nlohmann::json output_tree;
auto args = request->parse_query_string();
- if (
- args.find("uuid"s) == std::end(args)
- ) {
+ if (args.find("uuid") == args.end()) {
bad_request(response, request, "Missing a required launch parameter");
return;
}
-
- auto uuid = nvhttp::get_arg(args, "uuid");
-
- const auto& apps = proc::proc.get_apps();
-
- for (auto& app : apps) {
+ std::string uuid = nvhttp::get_arg(args, "uuid");
+ const auto &apps = proc::proc.get_apps();
+ for (auto &app : apps) {
if (app.uuid == uuid) {
auto appid = util::from_view(app.id);
-
crypto::named_cert_t named_cert {
.name = "",
.uuid = http::unique_id,
.perm = crypto::PERM::_all,
};
-
BOOST_LOG(info) << "Launching app ["sv << app.name << "] from web UI"sv;
-
auto launch_session = nvhttp::make_launch_session(true, false, appid, args, &named_cert);
auto err = proc::proc.execute(appid, app, launch_session);
if (err) {
- bad_request(response, request, err == 503
- ? "Failed to initialize video capture/encoding. Is a display connected and turned on?"
- : "Failed to start the specified application");
+ bad_request(response, request, err == 503 ?
+ "Failed to initialize video capture/encoding. Is a display connected and turned on?" :
+ "Failed to start the specified application");
} else {
- outputTree.put("status", true);
- send_response(response, outputTree);
+ output_tree["status"] = true;
+ send_response(response, output_tree);
}
-
return;
}
}
-
BOOST_LOG(error) << "Couldn't find app with uuid ["sv << uuid << ']';
bad_request(response, request, "Cannot find requested application");
}
- void
- disconnect(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ /**
+ * @brief Disconnect a client.
+ * @param response The HTTP response object.
+ * @param request The HTTP request object.
+ */
+ void disconnect(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
std::stringstream ss;
ss << request->content.rdbuf();
-
- pt::ptree inputTree, outputTree;
-
+ nlohmann::json output_tree;
try {
- pt::read_json(ss, inputTree);
- std::string uuid = inputTree.get("uuid");
- outputTree.put("status", nvhttp::find_and_stop_session(uuid, true));
- }
- catch (std::exception &e) {
+ nlohmann::json input_tree = nlohmann::json::parse(ss.str());
+ std::string uuid = input_tree.get("uuid");
+ output_tree["status"] = nvhttp::find_and_stop_session(uuid, true);
+ } catch (std::exception &e) {
BOOST_LOG(warning) << "Disconnect: "sv << e.what();
bad_request(response, request, e.what());
}
- send_response(response, outputTree);
+ send_response(response, output_tree);
}
/**
* @brief Get the list of paired clients.
* @param response The HTTP response object.
* @param request The HTTP request object.
- *
- * @api_examples{/api/clients/list| GET| null}
*/
- void
- listClients(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
+ void listClients(resp_https_t response, req_https_t request) {
+ if (!authenticate(response, request, true))
+ return;
print_req(request);
-
- const pt::ptree named_certs = nvhttp::get_all_clients();
-
- pt::ptree outputTree;
- outputTree.put("status", false);
- outputTree.add_child("named_certs", named_certs);
- #ifdef _WIN32
- outputTree.put("platform", "windows");
- #endif
- outputTree.put("status", true);
- send_response(response, outputTree);
+ nlohmann::json named_certs = nvhttp::get_all_clients();
+ nlohmann::json output_tree;
+ output_tree["named_certs"] = named_certs;
+#ifdef _WIN32
+ output_tree["platform"] = "windows";
+#endif
+ output_tree["status"] = true;
+ send_response(response, output_tree);
}
/**
- * @brief Close the currently running application.
- * @param response The HTTP response object.
- * @param request The HTTP request object.
- *
- * @api_examples{/api/apps/close| POST| null}
+ * @brief Start the HTTPS server.
*/
- void
- closeApp(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) return;
-
- print_req(request);
-
- proc::proc.terminate();
-
- pt::ptree outputTree;
- outputTree.put("status", true);
- send_response(response, outputTree);
- }
-
- void
- start() {
+ void start() {
auto shutdown_event = mail::man->event(mail::shutdown);
-
auto port_https = net::map_port(PORT_HTTPS);
auto address_family = net::af_from_enum_string(config::sunshine.address_family);
-
https_server_t server { config::nvhttp.cert, config::nvhttp.pkey };
server.default_resource["DELETE"] = [](resp_https_t response, req_https_t request) {
bad_request(response, request);
@@ -1274,20 +1016,16 @@ namespace confighttp {
server.resource["^/$"]["GET"] = getIndexPage;
server.resource["^/pin/?$"]["GET"] = getPinPage;
server.resource["^/apps/?$"]["GET"] = getAppsPage;
+ server.resource["^/clients/?$"]["GET"] = getClientsPage;
server.resource["^/config/?$"]["GET"] = getConfigPage;
server.resource["^/password/?$"]["GET"] = getPasswordPage;
server.resource["^/welcome/?$"]["GET"] = getWelcomePage;
- server.resource["^/login/?$"]["GET"] = getLoginPage;
server.resource["^/troubleshooting/?$"]["GET"] = getTroubleshootingPage;
- server.resource["^/api/login"]["POST"] = login;
server.resource["^/api/pin$"]["POST"] = savePin;
- server.resource["^/api/otp$"]["GET"] = getOTP;
server.resource["^/api/apps$"]["GET"] = getApps;
- server.resource["^/api/apps$"]["POST"] = saveApp;
- server.resource["^/api/apps/delete$"]["POST"] = deleteApp;
- server.resource["^/api/apps/launch$"]["POST"] = launchApp;
- server.resource["^/api/apps/close$"]["POST"] = closeApp;
server.resource["^/api/logs$"]["GET"] = getLogs;
+ server.resource["^/api/apps$"]["POST"] = saveApp;
+ server.resource["^/api/apps/close$"]["POST"] = closeApp;
server.resource["^/api/config$"]["GET"] = getConfig;
server.resource["^/api/config$"]["POST"] = saveConfig;
server.resource["^/api/configLocale$"]["GET"] = getLocale;
@@ -1300,38 +1038,29 @@ namespace confighttp {
server.resource["^/api/clients/update$"]["POST"] = updateClient;
server.resource["^/api/clients/unpair$"]["POST"] = unpair;
server.resource["^/api/clients/disconnect$"]["POST"] = disconnect;
- server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
- server.resource["^/images/apollo.ico$"]["GET"] = getFaviconImage;
- server.resource["^/images/logo-apollo-45.png$"]["GET"] = getSunshineLogoImage;
+ server.resource["^/api/apps/launch$"]["POST"] = launchApp;
+ server.resource["^/images/sunshine.ico$"]["GET"] = getFaviconImage;
+ server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage;
server.resource["^/assets\\/.+$"]["GET"] = getNodeModules;
server.config.reuse_address = true;
server.config.address = net::af_to_any_address_string(address_family);
server.config.port = port_https;
-
auto accept_and_run = [&](auto *server) {
try {
- server->start([](unsigned short port) {
+ server->start([port_https](unsigned short port) {
BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << port << "]";
});
- }
- catch (boost::system::system_error &err) {
- // It's possible the exception gets thrown after calling server->stop() from a different thread
- if (shutdown_event->peek()) {
+ } catch (boost::system::system_error &err) {
+ if (shutdown_event->peek())
return;
- }
-
BOOST_LOG(fatal) << "Couldn't start Configuration HTTPS server on port ["sv << port_https << "]: "sv << err.what();
shutdown_event->raise(true);
return;
}
};
std::thread tcp { accept_and_run, &server };
-
- // Wait for any event
shutdown_event->view();
-
server.stop();
-
tcp.join();
}
} // namespace confighttp
diff --git a/src/confighttp.h b/src/confighttp.h
index 232ccc7a..ff026815 100644
--- a/src/confighttp.h
+++ b/src/confighttp.h
@@ -4,10 +4,12 @@
*/
#pragma once
+// standard includes
#include
#include
#include
+// 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 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"},
};
diff --git a/src/crypto.cpp b/src/crypto.cpp
index 3644ef5b..0f7fbe2c 100644
--- a/src/crypto.cpp
+++ b/src/crypto.cpp
@@ -2,29 +2,31 @@
* @file src/crypto.cpp
* @brief Definitions for cryptography functions.
*/
-#include "crypto.h"
+// lib includes
#include
#include
+// local includes
+#include "crypto.h"
+
namespace crypto {
using asn1_string_t = util::safe_ptr;
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 &plaintext, aes_t *iv) {
+ int gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector &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 &plaintext) {
+ int ecb_t::decrypt(const std::string_view &cipher, std::vector &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 &cipher) {
+ int ecb_t::encrypt(const std::string_view &plaintext, std::vector &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 &salt, const std::string_view &pin) {
+ aes_t gen_aes_key(const std::array &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
- sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
- md_ctx_t ctx { EVP_MD_CTX_create() };
+ std::vector 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
- sign256(const pkey_t &pkey, const std::string_view &data) {
+ std::vector 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) {
diff --git a/src/crypto.h b/src/crypto.h
index a2a7fc6e..a997f22a 100644
--- a/src/crypto.h
+++ b/src/crypto.h
@@ -4,15 +4,18 @@
*/
#pragma once
+// standard includes
#include
+
+// lib includes
#include
#include
#include
#include
#include
-
#include