mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-08-10 00:52:16 +00:00
Compare commits
12 Commits
fix/config
...
build/dock
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88fbd5a398 | ||
|
|
997093986d | ||
|
|
6efc687036 | ||
|
|
a995e578fa | ||
|
|
257a102127 | ||
|
|
5b36357133 | ||
|
|
ce28e36a47 | ||
|
|
23e131439f | ||
|
|
24cce3a666 | ||
|
|
0631472533 | ||
|
|
a5c791658e | ||
|
|
a513acc16b |
12
.github/ISSUE_TEMPLATE/config.yml
vendored
12
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -5,9 +5,15 @@
|
||||
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Discussions
|
||||
url: https://github.com/orgs/LizardByte/discussions
|
||||
about: Community discussions
|
||||
- name: Questions
|
||||
url: https://github.com/orgs/LizardByte/discussions
|
||||
about: Ask questions
|
||||
- name: Feature Requests
|
||||
url: https://github.com/orgs/LizardByte/discussions
|
||||
about: Request new features
|
||||
- name: Support Center
|
||||
url: https://app.lizardbyte.dev/support
|
||||
about: Official LizardByte support
|
||||
- name: Discussions
|
||||
url: https://github.com/orgs/LizardByte/discussions
|
||||
about: Community discussions, questions, and feature requests
|
||||
|
||||
1
.github/workflows/CI.yml
vendored
1
.github/workflows/CI.yml
vendored
@@ -887,7 +887,6 @@ jobs:
|
||||
mingw-w64-ucrt-x86_64-graphviz
|
||||
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
|
||||
|
||||
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@@ -123,7 +123,7 @@ jobs:
|
||||
docker:
|
||||
needs: [check_dockerfiles, setup_release]
|
||||
if: ${{ needs.check_dockerfiles.outputs.dockerfiles }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
packages: write
|
||||
contents: write
|
||||
|
||||
6
.github/workflows/update-flathub-repo.yml
vendored
6
.github/workflows/update-flathub-repo.yml
vendored
@@ -143,9 +143,9 @@ jobs:
|
||||
BEGIN { replaced = 0 }
|
||||
/<release version=.*>/ {
|
||||
if (!replaced) {
|
||||
print "<release version=\"" version "\" date=\"" date "\">"
|
||||
print "<description><p>" changelog "</p></description>"
|
||||
print "</release>"
|
||||
print " <release version=\"" version "\" date=\"" date "\">"
|
||||
print " <description><p>" changelog "</p></description>"
|
||||
print " </release>"
|
||||
replaced = 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -76,7 +76,6 @@ list(PREPEND PLATFORM_LIBRARIES
|
||||
libstdc++.a
|
||||
libwinpthread.a
|
||||
minhook::minhook
|
||||
nlohmann_json::nlohmann_json
|
||||
ntdll
|
||||
setupapi
|
||||
shlwapi
|
||||
|
||||
@@ -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)
|
||||
|
||||
18
cmake/dependencies/nlohmann_json.cmake
Normal file
18
cmake/dependencies/nlohmann_json.cmake
Normal file
@@ -0,0 +1,18 @@
|
||||
#
|
||||
# Loads the nlohmann_json library giving the priority to the system package first, with a fallback to FetchContent.
|
||||
#
|
||||
include_guard(GLOBAL)
|
||||
|
||||
find_package(nlohmann_json 3.11 QUIET GLOBAL)
|
||||
if(NOT nlohmann_json_FOUND)
|
||||
message(STATUS "nlohmann_json v3.11.x package not found in the system. Falling back to FetchContent.")
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
json
|
||||
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
|
||||
URL_HASH MD5=c23a33f04786d85c29fda8d16b5f0efd
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP
|
||||
)
|
||||
FetchContent_MakeAvailable(json)
|
||||
endif()
|
||||
@@ -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)
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# artifacts: true
|
||||
# platforms: linux/amd64,linux/arm64/v8
|
||||
# platforms_pr: linux/amd64
|
||||
# platforms_pr: linux/amd64,linux/arm64/v8
|
||||
# no-cache-filters: sunshine-base,artifacts,sunshine
|
||||
ARG BASE=debian
|
||||
ARG TAG=bookworm
|
||||
FROM ${BASE}:${TAG} AS sunshine-base
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
FROM sunshine-base AS sunshine-build
|
||||
ARG BASE
|
||||
ARG TAG
|
||||
FROM --platform=$BUILDPLATFORM ${BASE}:${TAG} AS sunshine-build
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
ARG BRANCH
|
||||
ARG BUILD_VERSION
|
||||
ARG COMMIT
|
||||
@@ -30,8 +33,33 @@ COPY --link .. .
|
||||
RUN <<_BUILD
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [[ "${BUILDPLATFORM}" != "${TARGETPLATFORM}" ]]; then
|
||||
cross_compile="--cross-compile"
|
||||
else
|
||||
cross_compile=""
|
||||
fi
|
||||
|
||||
case "${TARGETPLATFORM}" in
|
||||
linux/amd64)
|
||||
cc_target_tuple="x86_64-linux-gnu"
|
||||
cc_target_arch="amd64"
|
||||
;;
|
||||
linux/arm64)
|
||||
cc_target_tuple="aarch64-linux-gnu"
|
||||
cc_target_arch="arm64"
|
||||
;;
|
||||
*)
|
||||
echo "unsupported platform: ${TARGETPLATFORM}";
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
chmod +x ./scripts/linux_build.sh
|
||||
./scripts/linux_build.sh \
|
||||
${cross_compile} \
|
||||
--cc-target-tuple="${cc_target_tuple}" \
|
||||
--cc-target-arch="${cc_target_arch}" \
|
||||
--publisher-name='LizardByte' \
|
||||
--publisher-website='https://app.lizardbyte.dev' \
|
||||
--publisher-issue-url='https://app.lizardbyte.dev/support' \
|
||||
|
||||
38
docs/api.md
38
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()
|
||||
|
||||
<div class="section_buttons">
|
||||
|
||||
|
||||
@@ -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": ""
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -225,7 +225,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||
<tr>
|
||||
<td>Example</td>
|
||||
<td colspan="2">@code{}
|
||||
global_prep_cmd = [{"do":"nircmd.exe setdisplay 1280 720 32 144","undo":"nircmd.exe setdisplay 2560 1440 32 144"}]
|
||||
global_prep_cmd = [{"do":"nircmd.exe setdisplay 1280 720 32 144","elevated":true,"undo":"nircmd.exe setdisplay 2560 1440 32 144"}]
|
||||
@endcode</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -1203,6 +1203,31 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
### dd_config_revert_on_disconnect
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td colspan="2">
|
||||
When enabled, display configuration is reverted upon disconnect of all clients instead of app close or last session termination.
|
||||
This can be useful for returning to physical usage of the host machine without closing the active app.
|
||||
@warning{Some applications may not function properly when display configuration is changed while active.}
|
||||
@note{Applies to Windows only.}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Default</td>
|
||||
<td colspan="2">@code{}disabled@endcode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example</td>
|
||||
<td colspan="2">@code{}
|
||||
dd_config_revert_on_disconnect = enabled
|
||||
@endcode</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### dd_mode_remapping
|
||||
|
||||
<table>
|
||||
|
||||
@@ -29,14 +29,18 @@
|
||||
</p>
|
||||
|
||||
<p>NOTE: Sunshine requires additional installation steps.</p>
|
||||
<p>flatpak run --command=additional-install.sh @PROJECT_FQDN@</p>
|
||||
<p>
|
||||
<code>flatpak run --command=additional-install.sh @PROJECT_FQDN@</code>
|
||||
</p>
|
||||
<p>NOTE: Sunshine uses a self-signed certificate. The web browser will report it as not secure, but it is safe.</p>
|
||||
<p>NOTE: KMS Grab (Optional)</p>
|
||||
<p>sudo -i PULSE_SERVER=unix:/run/user/$(id -u $whoami)/pulse/native flatpak run @PROJECT_FQDN@</p>
|
||||
<p>
|
||||
<code>sudo -i PULSE_SERVER=unix:/run/user/$(id -u $whoami)/pulse/native flatpak run @PROJECT_FQDN@</code>
|
||||
</p>
|
||||
</description>
|
||||
|
||||
<releases>
|
||||
<release version="@PROJECT_VERSION@" date="1970-01-01"></release>
|
||||
<release version="@PROJECT_VERSION@" date="1990-01-01"></release>
|
||||
</releases>
|
||||
|
||||
<developer_name>LizardByte</developer_name>
|
||||
|
||||
@@ -3,6 +3,11 @@ set -e
|
||||
|
||||
# Default value for arguments
|
||||
appimage_build=0
|
||||
cc_install_root="/mnt/cross"
|
||||
cc_target_tuple="$(uname -m)-linux-gnu"
|
||||
cc_target_arch="$(dpkg --print-architecture || rpm --eval '%{_host}')"
|
||||
cross_compile=0
|
||||
num_processors=$(nproc)
|
||||
publisher_name="Third Party Publisher"
|
||||
publisher_website=""
|
||||
publisher_issue_url="https://app.lizardbyte.dev/support"
|
||||
@@ -27,6 +32,11 @@ 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.
|
||||
--cc-install-root The root directory to install the cross compiler. Default is /mnt/cross. (Fedora only)
|
||||
--cc-target-tuple The target tuple for the cross compiler.
|
||||
--cc-target-arch The target architecture for the cross compiler.
|
||||
--cross-compile Enable cross compilation.
|
||||
--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 +63,19 @@ while getopts ":hs-:" opt; do
|
||||
appimage_build=1
|
||||
skip_libva=1
|
||||
;;
|
||||
cc-install-root=*)
|
||||
cc_install_root="${OPTARG#*=}"
|
||||
;;
|
||||
cc-target-tuple=*)
|
||||
cc_target_tuple="${OPTARG#*=}"
|
||||
;;
|
||||
cc-target-arch=*)
|
||||
cc_target_arch="${OPTARG#*=}"
|
||||
;;
|
||||
cross-compile) cross_compile=1 ;;
|
||||
num-processors=*)
|
||||
num_processors="${OPTARG#*=}"
|
||||
;;
|
||||
publisher-name=*)
|
||||
publisher_name="${OPTARG#*=}"
|
||||
;;
|
||||
@@ -92,28 +115,28 @@ function add_debain_based_deps() {
|
||||
"cmake"
|
||||
"doxygen"
|
||||
"flex" # required if we need to compile doxygen
|
||||
"gcc-${gcc_version}"
|
||||
"g++-${gcc_version}"
|
||||
"gcc-${gcc_version}:${cc_target_arch}"
|
||||
"g++-${gcc_version}:${cc_target_arch}"
|
||||
"git"
|
||||
"graphviz"
|
||||
"libcap-dev" # KMS
|
||||
"libcurl4-openssl-dev"
|
||||
"libdrm-dev" # KMS
|
||||
"libevdev-dev"
|
||||
"libminiupnpc-dev"
|
||||
"libnotify-dev"
|
||||
"libnuma-dev"
|
||||
"libopus-dev"
|
||||
"libpulse-dev"
|
||||
"libssl-dev"
|
||||
"libwayland-dev" # Wayland
|
||||
"libx11-dev" # X11
|
||||
"libxcb-shm0-dev" # X11
|
||||
"libxcb-xfixes0-dev" # X11
|
||||
"libxcb1-dev" # X11
|
||||
"libxfixes-dev" # X11
|
||||
"libxrandr-dev" # X11
|
||||
"libxtst-dev" # X11
|
||||
"libcap-dev:${cc_target_arch}" # KMS
|
||||
"libcurl4-openssl-dev:${cc_target_arch}"
|
||||
"libdrm-dev:${cc_target_arch}" # KMS
|
||||
"libevdev-dev:${cc_target_arch}"
|
||||
"libminiupnpc-dev:${cc_target_arch}"
|
||||
"libnotify-dev:${cc_target_arch}"
|
||||
"libnuma-dev:${cc_target_arch}"
|
||||
"libopus-dev:${cc_target_arch}"
|
||||
"libpulse-dev:${cc_target_arch}"
|
||||
"libssl-dev:${cc_target_arch}"
|
||||
"libwayland-dev:${cc_target_arch}" # Wayland
|
||||
"libx11-dev:${cc_target_arch}" # X11
|
||||
"libxcb-shm0-dev:${cc_target_arch}" # X11
|
||||
"libxcb-xfixes0-dev:${cc_target_arch}" # X11
|
||||
"libxcb1-dev:${cc_target_arch}" # X11
|
||||
"libxfixes-dev:${cc_target_arch}" # X11
|
||||
"libxrandr-dev:${cc_target_arch}" # X11
|
||||
"libxtst-dev:${cc_target_arch}" # X11
|
||||
"ninja-build"
|
||||
"npm" # web-ui
|
||||
"udev"
|
||||
@@ -123,7 +146,13 @@ function add_debain_based_deps() {
|
||||
|
||||
if [ "$skip_libva" == 0 ]; then
|
||||
dependencies+=(
|
||||
"libva-dev" # VA-API
|
||||
"libva-dev:${cc_target_arch}" # VA-API
|
||||
)
|
||||
fi
|
||||
|
||||
if [ "$cross_compile" == 1 ]; then
|
||||
dependencies+=(
|
||||
"crossbuild-essential-${cc_target_arch}"
|
||||
)
|
||||
fi
|
||||
}
|
||||
@@ -131,7 +160,7 @@ function add_debain_based_deps() {
|
||||
function add_debain_deps() {
|
||||
add_debain_based_deps
|
||||
dependencies+=(
|
||||
"libayatana-appindicator3-dev"
|
||||
"libayatana-appindicator3-dev:${cc_target_arch}"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -143,7 +172,7 @@ function add_ubuntu_deps() {
|
||||
|
||||
add_debain_based_deps
|
||||
dependencies+=(
|
||||
"libappindicator3-dev"
|
||||
"libappindicator3-dev:${cc_target_arch}"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -151,10 +180,19 @@ function add_fedora_deps() {
|
||||
dependencies+=(
|
||||
"cmake"
|
||||
"doxygen"
|
||||
"gcc"
|
||||
"g++"
|
||||
"git"
|
||||
"graphviz"
|
||||
"ninja-build"
|
||||
"npm"
|
||||
"rpm-build" # if you want to build an RPM binary package
|
||||
"wget" # necessary for cuda install with `run` file
|
||||
"which" # necessary for cuda install with `run` file
|
||||
"xorg-x11-server-Xvfb" # necessary for headless unit testing
|
||||
)
|
||||
|
||||
arch_dependencies=(
|
||||
"gcc"
|
||||
"g++"
|
||||
"libappindicator-gtk3-devel"
|
||||
"libcap-devel"
|
||||
"libcurl-devel"
|
||||
@@ -171,20 +209,14 @@ function add_fedora_deps() {
|
||||
"libXtst-devel" # X11
|
||||
"mesa-libGL-devel"
|
||||
"miniupnpc-devel"
|
||||
"ninja-build"
|
||||
"npm"
|
||||
"numactl-devel"
|
||||
"openssl-devel"
|
||||
"opus-devel"
|
||||
"pulseaudio-libs-devel"
|
||||
"rpm-build" # if you want to build an RPM binary package
|
||||
"wget" # necessary for cuda install with `run` file
|
||||
"which" # necessary for cuda install with `run` file
|
||||
"xorg-x11-server-Xvfb" # necessary for headless unit testing
|
||||
)
|
||||
|
||||
if [ "$skip_libva" == 0 ]; then
|
||||
dependencies+=(
|
||||
arch_dependencies+=(
|
||||
"libva-devel" # VA-API
|
||||
)
|
||||
fi
|
||||
@@ -199,15 +231,15 @@ function install_cuda() {
|
||||
|
||||
local cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
|
||||
local cuda_suffix=""
|
||||
if [ "$architecture" == "aarch64" ]; then
|
||||
if [ "$cc_target_arch" == "aarch64" ]; then
|
||||
local cuda_suffix="_sbsa"
|
||||
fi
|
||||
|
||||
if [ "$architecture" == "aarch64" ]; then
|
||||
if [ "$cc_target_arch" == "aarch64" ]; then
|
||||
# we need to patch the math-vector.h file for aarch64 fedora
|
||||
# back up /usr/include/bits/math-vector.h
|
||||
math_vector_file=""
|
||||
if [ "$distro" == "ubuntu" ] || [ "$version" == "24.04" ]; then
|
||||
if [ "$distro" == "ubuntu" ] && [ "$version" == "24.04" ]; then
|
||||
math_vector_file="/usr/include/aarch64-linux-gnu/bits/math-vector.h"
|
||||
elif [ "$distro" == "fedora" ]; then
|
||||
math_vector_file="/usr/include/bits/math-vector.h"
|
||||
@@ -278,6 +310,11 @@ function run_install() {
|
||||
"-DSUNSHINE_ENABLE_DRM=ON"
|
||||
)
|
||||
|
||||
if [ "$cross_compile" == 1 ]; then
|
||||
cmake_args+=("-DCMAKE_C_COMPILER=${cc_target_tuple}-gcc")
|
||||
cmake_args+=("-DCMAKE_CXX_COMPILER=${cc_target_tuple}-g++")
|
||||
fi
|
||||
|
||||
if [ "$appimage_build" == 1 ]; then
|
||||
cmake_args+=("-DSUNSHINE_BUILD_APPIMAGE=ON")
|
||||
fi
|
||||
@@ -308,6 +345,11 @@ function run_install() {
|
||||
# Install the dependencies
|
||||
$package_install_command "${dependencies[@]}"
|
||||
|
||||
# Fedora has a special command for installing architecture specific packages
|
||||
if [ "$distro" == "fedora" ]; then
|
||||
$package_install_command_arch "${arch_dependencies[@]}"
|
||||
fi
|
||||
|
||||
# reload the environment
|
||||
# shellcheck source=/dev/null
|
||||
source ~/.bashrc
|
||||
@@ -367,7 +409,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"
|
||||
@@ -443,6 +485,11 @@ elif grep -q "PLATFORM_ID=\"platform:f39\"" /etc/os-release; then
|
||||
version="39"
|
||||
package_update_command="${sudo_cmd} dnf update -y"
|
||||
package_install_command="${sudo_cmd} dnf install -y"
|
||||
if [ "$cross_compile" == 0 ]; then
|
||||
package_install_command_arch="${sudo_cmd} dnf -y --releasever=39 --forcearch=${cc_target_arch} install"
|
||||
else
|
||||
package_install_command_arch="${sudo_cmd} dnf -y --installroot=${cc_install_root} --releasever=39 --forcearch=${cc_target_arch} install"
|
||||
fi
|
||||
cuda_version="12.4.0"
|
||||
cuda_build="550.54.14"
|
||||
gcc_version="13"
|
||||
@@ -452,6 +499,11 @@ elif grep -q "PLATFORM_ID=\"platform:f40\"" /etc/os-release; then
|
||||
version="40"
|
||||
package_update_command="${sudo_cmd} dnf update -y"
|
||||
package_install_command="${sudo_cmd} dnf install -y"
|
||||
if [ "$cross_compile" == 0 ]; then
|
||||
package_install_command_arch="${sudo_cmd} dnf -y --releasever=39 --forcearch=${cc_target_arch} install"
|
||||
else
|
||||
package_install_command_arch="${sudo_cmd} dnf -y --installroot=${cc_install_root} --releasever=39 --forcearch=${cc_target_arch} install"
|
||||
fi
|
||||
cuda_version=
|
||||
cuda_build=
|
||||
gcc_version="13"
|
||||
|
||||
@@ -504,6 +504,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
|
||||
@@ -1132,6 +1133,7 @@ namespace config {
|
||||
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);
|
||||
|
||||
|
||||
@@ -132,6 +132,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;
|
||||
|
||||
@@ -8,16 +8,14 @@
|
||||
|
||||
// standard includes
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
|
||||
// lib includes
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/asio/ssl/context_base.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <Simple-Web-Server/crypto.hpp>
|
||||
#include <Simple-Web-Server/server_https.hpp>
|
||||
|
||||
@@ -34,7 +32,6 @@
|
||||
#include "nvhttp.h"
|
||||
#include "platform/common.h"
|
||||
#include "process.h"
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
#include "uuid.h"
|
||||
#include "version.h"
|
||||
@@ -43,7 +40,6 @@ using namespace std::literals;
|
||||
|
||||
namespace confighttp {
|
||||
namespace fs = std::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
using https_server_t = SimpleWeb::Server<SimpleWeb::HTTPS>;
|
||||
|
||||
@@ -82,10 +78,11 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,10 +93,20 @@ namespace confighttp {
|
||||
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;
|
||||
|
||||
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(SimpleWeb::StatusCode::client_error_unauthorized, headers);
|
||||
|
||||
response->write(code, tree.dump(), headers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,17 +183,14 @@ namespace confighttp {
|
||||
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<int>(code));
|
||||
tree.put("error", "Not Found");
|
||||
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, tree);
|
||||
nlohmann::json tree;
|
||||
tree["status_code"] = 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,18 +202,15 @@ namespace confighttp {
|
||||
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<int>(code));
|
||||
tree.put("status", false);
|
||||
tree.put("error", error_message);
|
||||
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, tree);
|
||||
nlohmann::json tree;
|
||||
tree["status_code"] = 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -455,30 +456,50 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string content = file_handler::read_file(config::stream.file_apps.c_str());
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("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);
|
||||
|
||||
/**
|
||||
* @brief Get the logs from the log file.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/logs| GET| null}
|
||||
*/
|
||||
void getLogs(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
// Legacy versions of Sunshine used strings for boolean and integers, let's convert them
|
||||
// List of keys to convert to boolean
|
||||
std::vector<std::string> boolean_keys = {
|
||||
"exclude-global-prep-cmd",
|
||||
"elevated",
|
||||
"auto-detach",
|
||||
"wait-all"
|
||||
};
|
||||
|
||||
// List of keys to convert to integers
|
||||
std::vector<std::string> integer_keys = {
|
||||
"exit-timeout"
|
||||
};
|
||||
|
||||
// Walk fileTree and convert true/false strings to boolean or integer values
|
||||
for (auto &app : file_tree["apps"]) {
|
||||
for (const auto &key : boolean_keys) {
|
||||
if (app.contains(key) && app[key].is_string()) {
|
||||
app[key] = app[key] == "true";
|
||||
}
|
||||
}
|
||||
for (const auto &key : integer_keys) {
|
||||
if (app.contains(key) && app[key].is_string()) {
|
||||
app[key] = std::stoi(app[key].get<std::string>());
|
||||
}
|
||||
}
|
||||
if (app.contains("prep-cmd")) {
|
||||
for (auto &prep : app["prep-cmd"]) {
|
||||
if (prep.contains("elevated") && prep["elevated"].is_string()) {
|
||||
prep["elevated"] = prep["elevated"] == "true";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send_response(response, file_tree);
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "GetApps: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string content = file_handler::read_file(config::sunshine.log_file.c_str());
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/plain");
|
||||
response->write(SimpleWeb::StatusCode::success_ok, content, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,74 +543,78 @@ namespace confighttp {
|
||||
|
||||
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);
|
||||
nlohmann::json output_tree;
|
||||
nlohmann::json input_tree = nlohmann::json::parse(ss);
|
||||
std::string file = file_handler::read_file(config::stream.file_apps.c_str());
|
||||
BOOST_LOG(info) << file;
|
||||
nlohmann::json file_tree = nlohmann::json::parse(file);
|
||||
|
||||
if (inputTree.get_child("prep-cmd").empty()) {
|
||||
inputTree.erase("prep-cmd");
|
||||
if (input_tree["prep-cmd"].empty()) {
|
||||
input_tree.erase("prep-cmd");
|
||||
}
|
||||
|
||||
if (inputTree.get_child("detached").empty()) {
|
||||
inputTree.erase("detached");
|
||||
if (input_tree["detached"].empty()) {
|
||||
input_tree.erase("detached");
|
||||
}
|
||||
|
||||
auto &apps_node = fileTree.get_child("apps"s);
|
||||
int index = inputTree.get<int>("index");
|
||||
auto &apps_node = file_tree["apps"];
|
||||
int index = input_tree["index"].get<int>(); // this will intentionally cause exception if the provided value is the wrong type
|
||||
|
||||
inputTree.erase("index");
|
||||
input_tree.erase("index");
|
||||
|
||||
if (index == -1) {
|
||||
apps_node.push_back(std::make_pair("", inputTree));
|
||||
apps_node.push_back(input_tree);
|
||||
} else {
|
||||
// Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick
|
||||
pt::ptree newApps;
|
||||
int i = 0;
|
||||
for (const auto &[k, v] : apps_node) {
|
||||
nlohmann::json newApps = nlohmann::json::array();
|
||||
for (size_t i = 0; i < apps_node.size(); ++i) {
|
||||
if (i == index) {
|
||||
newApps.push_back(std::make_pair("", inputTree));
|
||||
newApps.push_back(input_tree);
|
||||
} else {
|
||||
newApps.push_back(std::make_pair("", v));
|
||||
newApps.push_back(apps_node[i]);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
fileTree.erase("apps");
|
||||
fileTree.push_back(std::make_pair("apps", newApps));
|
||||
file_tree["apps"] = newApps;
|
||||
}
|
||||
|
||||
// Sort the apps array by name
|
||||
std::vector<pt::ptree> apps_vector;
|
||||
for (const auto &[k, v] : fileTree.get_child("apps")) {
|
||||
apps_vector.push_back(v);
|
||||
}
|
||||
std::ranges::sort(apps_vector, [](const pt::ptree &a, const pt::ptree &b) {
|
||||
return a.get<std::string>("name") < b.get<std::string>("name");
|
||||
std::sort(apps_node.begin(), apps_node.end(), [](const nlohmann::json &a, const nlohmann::json &b) {
|
||||
return a["name"].get<std::string>() < b["name"].get<std::string>();
|
||||
});
|
||||
|
||||
pt::ptree sorted_apps;
|
||||
for (const auto &app : apps_vector) {
|
||||
sorted_apps.push_back(std::make_pair("", app));
|
||||
}
|
||||
fileTree.erase("apps");
|
||||
fileTree.add_child("apps", sorted_apps);
|
||||
|
||||
pt::write_json(config::stream.file_apps, fileTree);
|
||||
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);
|
||||
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.
|
||||
*
|
||||
* @api_examples{/api/apps/close| POST| null}
|
||||
*/
|
||||
void closeApp(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
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.
|
||||
@@ -604,13 +629,13 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
try {
|
||||
pt::ptree fileTree;
|
||||
pt::ptree newApps;
|
||||
pt::read_json(config::stream.file_apps, fileTree);
|
||||
auto &apps_node = fileTree.get_child("apps"s);
|
||||
int index = stoi(request->path_match[1]);
|
||||
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"];
|
||||
const int index = std::stoi(request->path_match[1]);
|
||||
|
||||
if (index < 0 || index >= static_cast<int>(apps_node.size())) {
|
||||
std::string error;
|
||||
@@ -623,22 +648,19 @@ namespace confighttp {
|
||||
return;
|
||||
}
|
||||
|
||||
// Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick
|
||||
int i = 0;
|
||||
for (const auto &[k, v] : apps_node) {
|
||||
if (i++ != index) {
|
||||
newApps.push_back(std::make_pair("", v));
|
||||
for (size_t i = 0; i < apps_node.size(); ++i) {
|
||||
if (i != index) {
|
||||
new_apps.push_back(apps_node[i]);
|
||||
}
|
||||
}
|
||||
fileTree.erase("apps");
|
||||
fileTree.push_back(std::make_pair("apps", newApps));
|
||||
file_tree["apps"] = new_apps;
|
||||
|
||||
pt::write_json(config::stream.file_apps, fileTree);
|
||||
file_handler::write_file(config::stream.file_apps.c_str(), file_tree.dump(4));
|
||||
proc::refresh(config::stream.file_apps);
|
||||
|
||||
outputTree.put("status", true);
|
||||
outputTree.put("result", "application "s + std::to_string(index) + " deleted");
|
||||
send_response(response, outputTree);
|
||||
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());
|
||||
@@ -646,66 +668,83 @@ namespace confighttp {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Upload a cover image.
|
||||
* @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 getClients(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
const nlohmann::json named_certs = nvhttp::get_all_clients();
|
||||
|
||||
nlohmann::json output_tree;
|
||||
output_tree["named_certs"] = named_certs;
|
||||
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}
|
||||
* {
|
||||
* "key": "igdb_<game_id>",
|
||||
* "url": "https://images.igdb.com/igdb/image/upload/t_cover_big_2x/<slug>.png"
|
||||
* "uuid": "<uuid>"
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @api_examples{/api/covers/upload| POST| {"key":"igdb_1234","url":"https://images.igdb.com/igdb/image/upload/t_cover_big_2x/abc123.png"}}
|
||||
* @api_examples{/api/unpair| POST| {"uuid":"1234"}}
|
||||
*/
|
||||
void uploadCover(resp_https_t response, req_https_t request) {
|
||||
void unpair(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::stringstream ss;
|
||||
std::stringstream configStream;
|
||||
ss << request->content.rdbuf();
|
||||
pt::ptree outputTree;
|
||||
pt::ptree inputTree;
|
||||
|
||||
try {
|
||||
pt::read_json(ss, inputTree);
|
||||
// TODO: Input Validation
|
||||
nlohmann::json output_tree;
|
||||
const nlohmann::json input_tree = nlohmann::json::parse(ss);
|
||||
const 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) << "UploadCover: "sv << e.what();
|
||||
BOOST_LOG(warning) << "Unpair: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
auto key = inputTree.get("key", "");
|
||||
if (key.empty()) {
|
||||
bad_request(response, request, "Cover key is required");
|
||||
return;
|
||||
}
|
||||
auto url = inputTree.get("url", "");
|
||||
print_req(request);
|
||||
|
||||
const std::string coverdir = platf::appdata().string() + "/covers/";
|
||||
file_handler::make_directory(coverdir);
|
||||
nvhttp::erase_all_clients();
|
||||
proc::proc.terminate();
|
||||
|
||||
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<std::string>("data"));
|
||||
|
||||
std::ofstream imgfile(path);
|
||||
imgfile.write(data.data(), (int) data.size());
|
||||
}
|
||||
outputTree.put("status", true);
|
||||
outputTree.put("path", path);
|
||||
send_response(response, outputTree);
|
||||
nlohmann::json output_tree;
|
||||
output_tree["status"] = true;
|
||||
send_response(response, output_tree);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -722,18 +761,18 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
outputTree.put("status", true);
|
||||
outputTree.put("platform", SUNSHINE_PLATFORM);
|
||||
outputTree.put("version", PROJECT_VER);
|
||||
nlohmann::json output_tree;
|
||||
output_tree["status"] = true;
|
||||
output_tree["platform"] = SUNSHINE_PLATFORM;
|
||||
output_tree["version"] = PROJECT_VER;
|
||||
|
||||
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] = std::move(value);
|
||||
}
|
||||
|
||||
send_response(response, outputTree);
|
||||
send_response(response, output_tree);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -748,10 +787,10 @@ namespace confighttp {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -777,24 +816,24 @@ namespace confighttp {
|
||||
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 &[k, v] : inputTree) {
|
||||
std::string value = inputTree.get<std::string>(k);
|
||||
if (value.length() == 0 || value.compare("null") == 0) {
|
||||
std::stringstream config_stream;
|
||||
nlohmann::json output_tree;
|
||||
nlohmann::json input_tree = nlohmann::json::parse(ss);
|
||||
for (const auto &[k, v] : input_tree.items()) {
|
||||
if (v.is_null() || (v.is_string() && v.get<std::string>().empty())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
configStream << k << " = " << value << std::endl;
|
||||
// v.dump() will dump valid json, which we do not want for strings in the config right now
|
||||
// we should migrate the config file to straight json and get rid of all this nonsense
|
||||
config_stream << k << " = " << (v.is_string() ? v.get<std::string>() : v.dump()) << std::endl;
|
||||
}
|
||||
file_handler::write_file(config::sunshine.config_file.c_str(), configStream.str());
|
||||
outputTree.put("status", true);
|
||||
send_response(response, outputTree);
|
||||
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());
|
||||
@@ -802,40 +841,83 @@ namespace confighttp {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restart Sunshine.
|
||||
* @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_<game_id>",
|
||||
* "url": "https://images.igdb.com/igdb/image/upload/t_cover_big_2x/<slug>.png"
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @api_examples{/api/restart| POST| null}
|
||||
* @api_examples{/api/covers/upload| POST| {"key":"igdb_1234","url":"https://images.igdb.com/igdb/image/upload/t_cover_big_2x/abc123.png"}}
|
||||
*/
|
||||
void restart(resp_https_t response, req_https_t request) {
|
||||
void uploadCover(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
std::stringstream ss;
|
||||
ss << request->content.rdbuf();
|
||||
try {
|
||||
nlohmann::json output_tree;
|
||||
nlohmann::json input_tree = nlohmann::json::parse(ss);
|
||||
|
||||
// We may not return from this call
|
||||
platf::restart();
|
||||
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::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(input_tree.value("data", ""));
|
||||
|
||||
std::ofstream imgfile(path);
|
||||
imgfile.write(data.data(), static_cast<int>(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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset the display device persistence.
|
||||
* @brief Get the logs from the log file.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/reset-display-device-persistence| POST| null}
|
||||
* @api_examples{/api/logs| GET| null}
|
||||
*/
|
||||
void resetDisplayDevicePersistence(resp_https_t response, req_https_t request) {
|
||||
void getLogs(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
outputTree.put("status", display_device::reset_persistence());
|
||||
send_response(response, outputTree);
|
||||
std::string content = file_handler::read_file(config::sunshine.log_file.c_str());
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/plain");
|
||||
response->write(SimpleWeb::StatusCode::success_ok, content, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -864,23 +946,21 @@ namespace confighttp {
|
||||
|
||||
std::vector<std::string> errors = {};
|
||||
std::stringstream ss;
|
||||
std::stringstream configStream;
|
||||
std::stringstream config_stream;
|
||||
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<std::string>("currentUsername") : "";
|
||||
auto newUsername = inputTree.get<std::string>("newUsername");
|
||||
auto password = inputTree.count("currentPassword") > 0 ? inputTree.get<std::string>("currentPassword") : "";
|
||||
auto newPassword = inputTree.count("newPassword") > 0 ? inputTree.get<std::string>("newPassword") : "";
|
||||
auto confirmPassword = inputTree.count("confirmNewPassword") > 0 ? inputTree.get<std::string>("confirmNewPassword") : "";
|
||||
if (newUsername.length() == 0) {
|
||||
nlohmann::json output_tree;
|
||||
nlohmann::json input_tree = nlohmann::json::parse(ss);
|
||||
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.length() == 0) {
|
||||
if (newUsername.empty()) {
|
||||
errors.emplace_back("Invalid Username");
|
||||
} else {
|
||||
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
|
||||
@@ -890,7 +970,7 @@ namespace confighttp {
|
||||
} else {
|
||||
http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword);
|
||||
http::reload_user_creds(config::sunshine.credentials_file);
|
||||
outputTree.put("status", true);
|
||||
output_tree["status"] = true;
|
||||
}
|
||||
} else {
|
||||
errors.emplace_back("Invalid Current Credentials");
|
||||
@@ -906,7 +986,7 @@ namespace confighttp {
|
||||
return;
|
||||
}
|
||||
|
||||
send_response(response, outputTree);
|
||||
send_response(response, output_tree);
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "SavePassword: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
@@ -936,16 +1016,20 @@ namespace confighttp {
|
||||
|
||||
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<std::string>("pin");
|
||||
std::string name = inputTree.get<std::string>("name");
|
||||
outputTree.put("status", nvhttp::pin(pin, name));
|
||||
send_response(response, outputTree);
|
||||
nlohmann::json output_tree;
|
||||
nlohmann::json input_tree = nlohmann::json::parse(ss);
|
||||
const std::string name = input_tree.value("name", "");
|
||||
const std::string pin = input_tree.value("pin", "");
|
||||
|
||||
int _pin = 0;
|
||||
_pin = std::stoi(pin);
|
||||
if (_pin < 0 || _pin > 9999) {
|
||||
bad_request(response, request, "PIN must be between 0000 and 9999");
|
||||
}
|
||||
|
||||
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());
|
||||
@@ -953,106 +1037,40 @@ namespace confighttp {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unpair all clients.
|
||||
* @brief Reset the display device persistence.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/clients/unpair-all| POST| null}
|
||||
* @api_examples{/api/reset-display-device-persistence| POST| null}
|
||||
*/
|
||||
void unpairAll(resp_https_t response, req_https_t request) {
|
||||
void resetDisplayDevicePersistence(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
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"] = display_device::reset_persistence();
|
||||
send_response(response, output_tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unpair a client.
|
||||
* @brief Restart Sunshine.
|
||||
* @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": "<uuid>"
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @api_examples{/api/unpair| POST| {"uuid":"1234"}}
|
||||
* @api_examples{/api/restart| POST| null}
|
||||
*/
|
||||
void unpair(resp_https_t response, req_https_t request) {
|
||||
void restart(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
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<std::string>("uuid");
|
||||
outputTree.put("status", nvhttp::unpair_client(uuid));
|
||||
send_response(response, outputTree);
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "Unpair: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
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);
|
||||
outputTree.put("status", true);
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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}
|
||||
*/
|
||||
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);
|
||||
// We may not return from this call
|
||||
platf::restart();
|
||||
}
|
||||
|
||||
void start() {
|
||||
@@ -1095,7 +1113,7 @@ namespace confighttp {
|
||||
server.resource["^/api/password$"]["POST"] = savePassword;
|
||||
server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
|
||||
server.resource["^/api/clients/unpair-all$"]["POST"] = unpairAll;
|
||||
server.resource["^/api/clients/list$"]["GET"] = listClients;
|
||||
server.resource["^/api/clients/list$"]["GET"] = getClients;
|
||||
server.resource["^/api/clients/unpair$"]["POST"] = unpair;
|
||||
server.resource["^/api/apps/close$"]["POST"] = closeApp;
|
||||
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
// local includes
|
||||
|
||||
@@ -810,6 +810,18 @@ namespace display_device {
|
||||
});
|
||||
}
|
||||
|
||||
EnumeratedDeviceList enumerate_devices() {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
if (!DD_DATA.sm_instance) {
|
||||
// Platform is not supported.
|
||||
return {};
|
||||
}
|
||||
|
||||
return DD_DATA.sm_instance->execute([](auto &settings_iface) {
|
||||
return settings_iface.enumAvailableDevices();
|
||||
});
|
||||
}
|
||||
|
||||
std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
const auto device_prep {parse_device_prep_option(video_config)};
|
||||
if (!device_prep) {
|
||||
|
||||
@@ -110,8 +110,8 @@ namespace display_device {
|
||||
* The user then accepts that Sunshine is not able to restore the state and "agrees" to
|
||||
* do it manually.
|
||||
*
|
||||
* @return
|
||||
* @note Whether the function succeeds or fails, the any of the scheduled "retries" from
|
||||
* @return True if persistence was reset, false otherwise.
|
||||
* @note Whether the function succeeds or fails, any of the scheduled "retries" from
|
||||
* other methods will be stopped to not interfere with the user actions.
|
||||
*
|
||||
* @examples
|
||||
@@ -120,6 +120,16 @@ namespace display_device {
|
||||
*/
|
||||
[[nodiscard]] bool reset_persistence();
|
||||
|
||||
/**
|
||||
* @brief Enumerate the available devices.
|
||||
* @return A list of devices.
|
||||
*
|
||||
* @examples
|
||||
* const auto devices = enumerate_devices();
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] EnumeratedDeviceList enumerate_devices();
|
||||
|
||||
/**
|
||||
* @brief A tag structure indicating that configuration parsing has failed.
|
||||
*/
|
||||
|
||||
@@ -765,14 +765,14 @@ namespace nvhttp {
|
||||
response->close_connection_after_response = true;
|
||||
}
|
||||
|
||||
pt::ptree get_all_clients() {
|
||||
pt::ptree named_cert_nodes;
|
||||
nlohmann::json get_all_clients() {
|
||||
nlohmann::json named_cert_nodes = nlohmann::json::array();
|
||||
client_t &client = client_root;
|
||||
for (auto &named_cert : client.named_devices) {
|
||||
pt::ptree named_cert_node;
|
||||
named_cert_node.put("name"s, named_cert.name);
|
||||
named_cert_node.put("uuid"s, named_cert.uuid);
|
||||
named_cert_nodes.push_back(std::make_pair(""s, named_cert_node));
|
||||
nlohmann::json named_cert_node;
|
||||
named_cert_node["name"] = named_cert.name;
|
||||
named_cert_node["uuid"] = named_cert.uuid;
|
||||
named_cert_nodes.push_back(named_cert_node);
|
||||
}
|
||||
|
||||
return named_cert_nodes;
|
||||
@@ -852,14 +852,14 @@ namespace nvhttp {
|
||||
auto launch_session = make_launch_session(host_audio, args);
|
||||
|
||||
if (rtsp_stream::session_count() == 0) {
|
||||
// The display should be restored in case something fails as there are no other sessions.
|
||||
revert_display_configuration = true;
|
||||
|
||||
// We want to prepare display only if there are no active sessions at
|
||||
// the moment. This should be done before probing encoders as it could
|
||||
// change the active displays.
|
||||
display_device::configure_display(config::video, *launch_session);
|
||||
|
||||
// The display should be restored in case something fails as there are no other sessions.
|
||||
revert_display_configuration = true;
|
||||
|
||||
// Probe encoders again before streaming to ensure our chosen
|
||||
// encoder matches the active GPU (which could have changed
|
||||
// due to hotplugging, driver crash, primary monitor change,
|
||||
@@ -1177,13 +1177,13 @@ namespace nvhttp {
|
||||
save_state();
|
||||
}
|
||||
|
||||
int unpair_client(std::string uuid) {
|
||||
int removed = 0;
|
||||
bool unpair_client(const std::string_view uuid) {
|
||||
bool removed = false;
|
||||
client_t &client = client_root;
|
||||
for (auto it = client.named_devices.begin(); it != client.named_devices.end();) {
|
||||
if ((*it).uuid == uuid) {
|
||||
it = client.named_devices.erase(it);
|
||||
removed++;
|
||||
removed = true;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
// lib includes
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <Simple-Web-Server/server_https.hpp>
|
||||
|
||||
// local includes
|
||||
@@ -176,20 +177,21 @@ namespace nvhttp {
|
||||
|
||||
/**
|
||||
* @brief Remove single client.
|
||||
* @param uuid The UUID of the client to remove.
|
||||
* @examples
|
||||
* nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1");
|
||||
* @examples_end
|
||||
*/
|
||||
int unpair_client(std::string uniqueid);
|
||||
bool unpair_client(std::string_view uuid);
|
||||
|
||||
/**
|
||||
* @brief Get all paired clients.
|
||||
* @return The list of all paired clients.
|
||||
* @examples
|
||||
* boost::property_tree::ptree clients = nvhttp::get_all_clients();
|
||||
* nlohmann::json clients = nvhttp::get_all_clients();
|
||||
* @examples_end
|
||||
*/
|
||||
boost::property_tree::ptree get_all_clients();
|
||||
nlohmann::json get_all_clients();
|
||||
|
||||
/**
|
||||
* @brief Remove all paired clients.
|
||||
|
||||
@@ -1882,11 +1882,17 @@ namespace stream {
|
||||
|
||||
// If this is the last session, invoke the platform callbacks
|
||||
if (--running_sessions == 0) {
|
||||
bool revert_display_config {config::video.dd.config_revert_on_disconnect};
|
||||
if (proc::proc.running()) {
|
||||
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
|
||||
system_tray::update_tray_pausing(proc::proc.get_last_run_app_name());
|
||||
#endif
|
||||
} else {
|
||||
// We have no app running and also no clients anymore.
|
||||
revert_display_config = true;
|
||||
}
|
||||
|
||||
if (revert_display_config) {
|
||||
display_device::revert_configuration();
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,37 @@ using namespace std::literals;
|
||||
|
||||
namespace video {
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* @brief Check if we can allow probing for the encoders.
|
||||
* @return True if there should be no issues with the probing, false if we should prevent it.
|
||||
*/
|
||||
bool allow_encoder_probing() {
|
||||
const auto devices {display_device::enumerate_devices()};
|
||||
|
||||
// If there are no devices, then either the API is not working correctly or OS does not support the lib.
|
||||
// Either way we should not block the probing in this case as we can't tell what's wrong.
|
||||
if (devices.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Since Windows 11 24H2, it is possible that there will be no active devices present
|
||||
// for some reason (probably a bug). Trying to probe encoders in such a state locks/breaks the DXGI
|
||||
// and also the display device for Windows. So we must have at least 1 active device.
|
||||
const bool at_least_one_device_is_active = std::any_of(std::begin(devices), std::end(devices), [](const auto &device) {
|
||||
// If device has additional info, it is active.
|
||||
return static_cast<bool>(device.m_info);
|
||||
});
|
||||
|
||||
if (at_least_one_device_is_active) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOST_LOG(error) << "No display devices are active at the moment! Cannot probe the encoders.";
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void free_ctx(AVCodecContext *ctx) {
|
||||
avcodec_free_context(&ctx);
|
||||
}
|
||||
@@ -2550,6 +2581,11 @@ namespace video {
|
||||
}
|
||||
|
||||
int probe_encoders() {
|
||||
if (!allow_encoder_probing()) {
|
||||
// Error already logged
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto encoder_list = encoders;
|
||||
|
||||
// If we already have a good encoder, check to see if another probe is required
|
||||
|
||||
@@ -234,8 +234,8 @@
|
||||
<!-- exit timeout -->
|
||||
<div class="mb-3">
|
||||
<label for="exitTimeout" class="form-label">{{ $t('apps.exit_timeout') }}</label>
|
||||
<input type="text" class="form-control monospace" id="exitTimeout" aria-describedby="exitTimeoutHelp"
|
||||
v-model="editForm['exit-timeout']" />
|
||||
<input type="number" class="form-control monospace" id="exitTimeout" aria-describedby="exitTimeoutHelp"
|
||||
v-model="editForm['exit-timeout']" min="0" placeholder="5" />
|
||||
<div id="exitTimeoutHelp" class="form-text">{{ $t('apps.exit_timeout_desc') }}</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
@@ -441,7 +441,7 @@
|
||||
);
|
||||
if (resp) {
|
||||
fetch("./api/apps/" + id, { method: "DELETE" }).then((r) => {
|
||||
if (r.status == 200) document.location.reload();
|
||||
if (r.status === 200) document.location.reload();
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -557,7 +557,7 @@
|
||||
method: "POST",
|
||||
body: JSON.stringify(this.editForm),
|
||||
}).then((r) => {
|
||||
if (r.status == 200) document.location.reload();
|
||||
if (r.status === 200) document.location.reload();
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
"dd_manual_refresh_rate": "",
|
||||
"dd_hdr_option": "auto",
|
||||
"dd_config_revert_delay": 3000,
|
||||
"dd_config_revert_on_disconnect": "disabled",
|
||||
"dd_mode_remapping": {"mixed": [], "resolution_only": [], "refresh_rate_only": []},
|
||||
"dd_wa_hdr_toggle": "disabled",
|
||||
"min_fps_factor": 1,
|
||||
@@ -342,10 +343,7 @@
|
||||
this.$forceUpdate()
|
||||
},
|
||||
serialize() {
|
||||
let config = JSON.parse(JSON.stringify(this.config));
|
||||
config.global_prep_cmd = JSON.stringify(config.global_prep_cmd);
|
||||
config.dd_mode_remapping = JSON.stringify(config.dd_mode_remapping);
|
||||
return config;
|
||||
return JSON.parse(JSON.stringify(this.config));
|
||||
},
|
||||
save() {
|
||||
this.saved = false;
|
||||
@@ -359,17 +357,8 @@
|
||||
Object.keys(tab.options).forEach(optionKey => {
|
||||
let delete_value = false
|
||||
|
||||
if (["global_prep_cmd", "dd_mode_remapping"].includes(optionKey)) {
|
||||
const config_value = config[optionKey]
|
||||
const default_value = JSON.stringify(tab.options[optionKey])
|
||||
|
||||
if (config_value === default_value) {
|
||||
delete_value = true
|
||||
}
|
||||
}
|
||||
|
||||
// todo: add proper type checking
|
||||
if (String(config[optionKey]) === String(tab.options[optionKey])) {
|
||||
if (JSON.stringify(config[optionKey]) === JSON.stringify(tab.options[optionKey])) {
|
||||
delete_value = true
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +151,15 @@ function addRemappingEntry() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Config revert on disconnect -->
|
||||
<div class="mb-3" v-if="config.dd_configuration_option !== 'disabled'">
|
||||
<Checkbox id="dd_config_revert_on_disconnect"
|
||||
locale-prefix="config"
|
||||
v-model="config.dd_config_revert_on_disconnect"
|
||||
default="false"
|
||||
></Checkbox>
|
||||
</div>
|
||||
|
||||
<!-- Display mode remapping -->
|
||||
<div class="mb-3" v-if="canBeRemapped()">
|
||||
<label for="dd_mode_remapping" class="form-label">
|
||||
|
||||
@@ -39,14 +39,12 @@ const config = ref(props.config)
|
||||
</div>
|
||||
|
||||
<!-- Spatial AQ -->
|
||||
<div class="mb-3">
|
||||
<label for="nvenc_spatial_aq" class="form-label">{{ $t('config.nvenc_spatial_aq') }}</label>
|
||||
<select id="nvenc_spatial_aq" class="form-select" v-model="config.nvenc_spatial_aq">
|
||||
<option value="disabled">{{ $t('config.nvenc_spatial_aq_disabled') }}</option>
|
||||
<option value="enabled">{{ $t('config.nvenc_spatial_aq_enabled') }}</option>
|
||||
</select>
|
||||
<div class="form-text">{{ $t('config.nvenc_spatial_aq_desc') }}</div>
|
||||
</div>
|
||||
<Checkbox class="mb-3"
|
||||
id="nvenc_spatial_aq"
|
||||
locale-prefix="config"
|
||||
v-model="config.nvenc_spatial_aq"
|
||||
default="false"
|
||||
></Checkbox>
|
||||
|
||||
<!-- Single-frame VBV/HRD percentage increase -->
|
||||
<div class="mb-3">
|
||||
|
||||
@@ -94,10 +94,10 @@
|
||||
method: "POST",
|
||||
body: JSON.stringify(this.passwordData),
|
||||
}).then((r) => {
|
||||
if (r.status == 200) {
|
||||
if (r.status === 200) {
|
||||
r.json().then((rj) => {
|
||||
if (rj.status.toString() === "true") {
|
||||
this.success = true;
|
||||
this.success = rj.status;
|
||||
if (this.success === true) {
|
||||
setTimeout(() => {
|
||||
document.location.reload();
|
||||
}, 5000);
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
fetch("./api/pin", {method: "POST", body: b})
|
||||
.then((response) => response.json())
|
||||
.then((response) => {
|
||||
if (response.status.toString().toLowerCase() === "true") {
|
||||
if (response.status === true) {
|
||||
document.querySelector(
|
||||
"#status"
|
||||
).innerHTML = `<div class="alert alert-success" role="alert">${this.i18n.t('pin.pair_success')}</div>`;
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"env_vars_desc": "All commands get these environment variables by default:",
|
||||
"env_xrandr_example": "Example - Xrandr for Resolution Automation:",
|
||||
"exit_timeout": "Exit Timeout",
|
||||
"exit_timeout_desc": "Number of seconds to wait for all app processes to gracefully exit when requested to quit. If unset, the default is to wait up to 5 seconds. If set to zero or a negative value, the app will be immediately terminated.",
|
||||
"exit_timeout_desc": "Number of seconds to wait for all app processes to gracefully exit when requested to quit. If unset, the default is to wait up to 5 seconds. If set to 0, the app will be immediately terminated.",
|
||||
"find_cover": "Find Cover",
|
||||
"global_prep_desc": "Enable/Disable the execution of Global Prep Commands for this application.",
|
||||
"global_prep_name": "Global Prep Commands",
|
||||
@@ -158,6 +158,8 @@
|
||||
"dd_config_label": "Device configuration",
|
||||
"dd_config_revert_delay": "Config revert delay",
|
||||
"dd_config_revert_delay_desc": "Additional delay in milliseconds to wait before reverting configuration when the app has been closed or the last session terminated. Main purpose is to provide a smoother transition when quickly switching between apps.",
|
||||
"dd_config_revert_on_disconnect": "Config revert on disconnect",
|
||||
"dd_config_revert_on_disconnect_desc": "Revert configuration upon disconnect of all clients instead of app close or last session termination.",
|
||||
"dd_config_verify_only": "Verify that the display is enabled (default)",
|
||||
"dd_hdr_option": "HDR",
|
||||
"dd_hdr_option_auto": "Switch on/off the HDR mode as requested by the client (default)",
|
||||
@@ -277,8 +279,6 @@
|
||||
"nvenc_realtime_hags_desc": "Currently NVIDIA drivers may freeze in encoder when HAGS is enabled, realtime priority is used and VRAM utilization is close to maximum. Disabling this option lowers the priority to high, sidestepping the freeze at the cost of reduced capture performance when the GPU is heavily loaded.",
|
||||
"nvenc_spatial_aq": "Spatial AQ",
|
||||
"nvenc_spatial_aq_desc": "Assign higher QP values to flat regions of the video. Recommended to enable when streaming at lower bitrates.",
|
||||
"nvenc_spatial_aq_disabled": "Disabled (faster, default)",
|
||||
"nvenc_spatial_aq_enabled": "Enabled (slower)",
|
||||
"nvenc_twopass": "Two-pass mode",
|
||||
"nvenc_twopass_desc": "Adds preliminary encoding pass. This allows to detect more motion vectors, better distribute bitrate across the frame and more strictly adhere to bitrate limits. Disabling it is not recommended since this can lead to occasional bitrate overshoot and subsequent packet loss.",
|
||||
"nvenc_twopass_disabled": "Disabled (fastest, not recommended)",
|
||||
|
||||
@@ -120,13 +120,14 @@
|
||||
</div>
|
||||
<ul id="client-list" class="list-group list-group-flush list-group-item-light" v-if="clients && clients.length > 0">
|
||||
<div v-for="client in clients" class="list-group-item d-flex">
|
||||
<div class="p-2 flex-grow-1">{{client.name != "" ? client.name : $t('troubleshooting.unpair_single_unknown')}}</div><div class="me-2 ms-auto btn btn-danger" @click="unpairSingle(client.uuid)"><i class="fas fa-trash"></i></div>
|
||||
<div class="p-2 flex-grow-1">{{ client.name !== "" ? client.name : $t('troubleshooting.unpair_single_unknown') }}</div>
|
||||
<div class="me-2 ms-auto btn btn-danger" @click="unpairSingle(client.uuid)"><i class="fas fa-trash"></i></div>
|
||||
</div>
|
||||
</ul>
|
||||
<ul v-else class="list-group list-group-flush list-group-item-light">
|
||||
<div class="list-group-item p-3 text-center"><em>{{ $t('troubleshooting.unpair_single_no_devices') }}</em></div>
|
||||
</ul>
|
||||
|
||||
|
||||
</div>
|
||||
<!-- Logs -->
|
||||
<div class="card p-2 my-4">
|
||||
@@ -176,7 +177,7 @@
|
||||
actualLogs() {
|
||||
if (!this.logFilter) return this.logs;
|
||||
let lines = this.logs.split("\n");
|
||||
lines = lines.filter(x => x.indexOf(this.logFilter) != -1);
|
||||
lines = lines.filter(x => x.indexOf(this.logFilter) !== -1);
|
||||
return lines.join("\n");
|
||||
}
|
||||
},
|
||||
@@ -210,7 +211,7 @@
|
||||
.then((r) => r.json())
|
||||
.then((r) => {
|
||||
this.closeAppPressed = false;
|
||||
this.closeAppStatus = r.status.toString() === "true";
|
||||
this.closeAppStatus = r.status;
|
||||
setTimeout(() => {
|
||||
this.closeAppStatus = null;
|
||||
}, 5000);
|
||||
@@ -222,7 +223,7 @@
|
||||
.then((r) => r.json())
|
||||
.then((r) => {
|
||||
this.unpairAllPressed = false;
|
||||
this.unpairAllStatus = r.status.toString() === "true";
|
||||
this.unpairAllStatus = r.status;
|
||||
setTimeout(() => {
|
||||
this.unpairAllStatus = null;
|
||||
}, 5000);
|
||||
@@ -240,9 +241,9 @@
|
||||
.then((response) => response.json())
|
||||
.then((response) => {
|
||||
const clientList = document.querySelector("#client-list");
|
||||
if (response.status === 'true' && response.named_certs && response.named_certs.length) {
|
||||
if (response.status === true && response.named_certs && response.named_certs.length) {
|
||||
this.clients = response.named_certs.sort((a, b) => {
|
||||
return (a.name.toLowerCase() > b.name.toLowerCase() || a.name == "" ? 1 : -1)
|
||||
return (a.name.toLowerCase() > b.name.toLowerCase() || a.name === "" ? 1 : -1)
|
||||
});
|
||||
} else {
|
||||
this.clients = [];
|
||||
@@ -270,7 +271,7 @@
|
||||
.then((r) => r.json())
|
||||
.then((r) => {
|
||||
this.ddResetPressed = false;
|
||||
this.ddResetStatus = r.status.toString() === "true";
|
||||
this.ddResetStatus = r.status;
|
||||
setTimeout(() => {
|
||||
this.ddResetStatus = null;
|
||||
}, 5000);
|
||||
|
||||
@@ -81,10 +81,10 @@
|
||||
body: JSON.stringify(this.passwordData),
|
||||
}).then((r) => {
|
||||
this.loading = false;
|
||||
if (r.status == 200) {
|
||||
if (r.status === 200) {
|
||||
r.json().then((rj) => {
|
||||
if (rj.status.toString() === "true") {
|
||||
this.success = true;
|
||||
this.success = rj.status;
|
||||
if (this.success === true) {
|
||||
setTimeout(() => {
|
||||
document.location.reload();
|
||||
}, 5000);
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
{
|
||||
"name": "Steam Big Picture",
|
||||
"cmd": "steam://open/bigpicture",
|
||||
"auto-detach": "true",
|
||||
"wait-all": "true",
|
||||
"auto-detach": true,
|
||||
"wait-all": true,
|
||||
"image-path": "steam.png"
|
||||
}
|
||||
]
|
||||
|
||||
2
third-party/libdisplaydevice
vendored
2
third-party/libdisplaydevice
vendored
Submodule third-party/libdisplaydevice updated: 1975f75add...53a0ea5317
2
third-party/wayland-protocols
vendored
2
third-party/wayland-protocols
vendored
Submodule third-party/wayland-protocols updated: 9ac1a0977e...c7b582cb71
Reference in New Issue
Block a user