mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-08-10 00:52:16 +00:00
Compare commits
22 Commits
v2025.118.
...
feat/api/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b863f760b | ||
|
|
257a102127 | ||
|
|
5b36357133 | ||
|
|
ce28e36a47 | ||
|
|
23e131439f | ||
|
|
24cce3a666 | ||
|
|
0631472533 | ||
|
|
a5c791658e | ||
|
|
a513acc16b | ||
|
|
64544e7960 | ||
|
|
2a31ee5422 | ||
|
|
8263d8976f | ||
|
|
eb6916ef34 | ||
|
|
5af21bde88 | ||
|
|
31866fde35 | ||
|
|
299b12347f | ||
|
|
2ac87fdc36 | ||
|
|
a88c01f3b8 | ||
|
|
f5b923c406 | ||
|
|
3048e6fe20 | ||
|
|
c2420427b1 | ||
|
|
f57aee9025 |
@@ -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
|
||||
|
||||
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
|
||||
|
||||
3
.github/workflows/release-notifier.yml
vendored
3
.github/workflows/release-notifier.yml
vendored
@@ -80,13 +80,14 @@ jobs:
|
||||
month_day=$(printf "%04d" "$month_day")
|
||||
|
||||
# create the filename
|
||||
file_name="_posts/releases/${year}-${month_day:0:2}-${month_day:2:2}-v${semver}.md"
|
||||
file_name="_posts/releases/${repo_lower}/${year}-${month_day:0:2}-${month_day:2:2}-v${semver}.md"
|
||||
mkdir -p "$(dirname "${file_name}")"
|
||||
|
||||
# create jekyll blog post
|
||||
echo "---" > "${file_name}"
|
||||
echo "layout: release" >> "${file_name}"
|
||||
echo "title: ${{ github.event.repository.name }} ${tag_name} Released" >> "${file_name}"
|
||||
echo "release-tag: ${tag_name}" >> "${file_name}"
|
||||
echo "gh-repo: ${{ github.repository }}" >> "${file_name}"
|
||||
echo "gh-badge: [follow, fork, star]" >> "${file_name}"
|
||||
echo "tags: [release, ${repo_lower}]" >> "${file_name}"
|
||||
|
||||
9
.github/workflows/update-docs.yml
vendored
9
.github/workflows/update-docs.yml
vendored
@@ -81,9 +81,16 @@ jobs:
|
||||
--arg description "${{ github.event.repository.description }}" \
|
||||
'{default_branch: $default_branch}')
|
||||
|
||||
# change the default branch to the latest release
|
||||
curl \
|
||||
-X PATCH \
|
||||
-H "Authorization: Token ${RTD_TOKEN}" \
|
||||
https://readthedocs.org/api/v3/projects/${RTD_SLUG}/ \
|
||||
-H "Content-Type: application/json" \
|
||||
https://readthedocs.org/api/v3/projects/${RTD_SLUG}/ \
|
||||
-d "$json_body"
|
||||
|
||||
# trigger a build for the latest version
|
||||
curl \
|
||||
-X POST \
|
||||
-H "Authorization: Token ${RTD_TOKEN}" \
|
||||
https://readthedocs.org/api/v3/projects/${RTD_SLUG}/versions/latest/builds/
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
17
api/components/responses/400.yml
Normal file
17
api/components/responses/400.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
description: Bad Request - A parameter was not specified, or was specified incorrectly.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status_code:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
error:
|
||||
type: string
|
||||
example:
|
||||
status_code: 400
|
||||
status: false
|
||||
error: "Bad Request"
|
||||
17
api/components/responses/401.yml
Normal file
17
api/components/responses/401.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
description: Unauthorized - The request requires user authentication.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status_code:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
error:
|
||||
type: string
|
||||
example:
|
||||
status_code: 401
|
||||
status: false
|
||||
error: "Unauthorized"
|
||||
7
api/components/responses/403.yml
Normal file
7
api/components/responses/403.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
description: Forbidden - The server understood the request, but is refusing to fulfill it.
|
||||
content:
|
||||
# TODO: return JSON response.
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
15
api/components/responses/404.yml
Normal file
15
api/components/responses/404.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
description: Not Found - The requested resource could not be found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status_code:
|
||||
type: integer
|
||||
format: int32
|
||||
error:
|
||||
type: string
|
||||
example:
|
||||
status_code: 404
|
||||
error: "Not Found"
|
||||
48
api/components/schemas/app.yml
Normal file
48
api/components/schemas/app.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: Application Name, as shown on Moonlight
|
||||
output:
|
||||
type: string
|
||||
description: The file where the output of the command is stored, if it is not specified, the output is ignored
|
||||
cmd:
|
||||
$ref: "./cmd.yml"
|
||||
description: The main application to start. If blank, no application will be started.
|
||||
exclude-global-prep-cmd:
|
||||
type: boolean
|
||||
description: Enable/Disable the execution of Global Prep Commands for this application.
|
||||
elevated:
|
||||
type: boolean
|
||||
description: Run the application as an elevated process.
|
||||
auto-detach:
|
||||
type: boolean
|
||||
description: Continue streaming if the application exits quickly
|
||||
wait-all:
|
||||
type: boolean
|
||||
description: Continue streaming until all app processes exit
|
||||
exit-timeout:
|
||||
type: integer
|
||||
description: Number of seconds to wait for all app processes to gracefully exit when requested to quit.
|
||||
image-path:
|
||||
type: string
|
||||
description: |
|
||||
Application icon/picture/image path that will be sent to client. Image must be a PNG file.
|
||||
If not set, Sunshine will send default box image.
|
||||
working-dir:
|
||||
type: string
|
||||
description: |
|
||||
The working directory that should be passed to the process.
|
||||
For example, some applications use the working directory to search for configuration files.
|
||||
If not set, Sunshine will default to the parent directory of the command
|
||||
prep-cmd:
|
||||
type: array
|
||||
items:
|
||||
$ref: "./prep-cmd.yml"
|
||||
detached:
|
||||
type: array
|
||||
items:
|
||||
$ref: "./cmd.yml"
|
||||
3
api/components/schemas/cmd.yml
Normal file
3
api/components/schemas/cmd.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
type: string
|
||||
description: Command to execute
|
||||
16
api/components/schemas/prep-cmd.yml
Normal file
16
api/components/schemas/prep-cmd.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
type: object
|
||||
required:
|
||||
- do
|
||||
- undo
|
||||
- elevated
|
||||
properties:
|
||||
do:
|
||||
$ref: "./cmd.yml"
|
||||
description: Command to run before the application starts.
|
||||
undo:
|
||||
$ref: "./cmd.yml"
|
||||
description: Command to run after the application exits.
|
||||
elevated:
|
||||
type: boolean
|
||||
description: Run the command as an elevated process.
|
||||
44
api/openapi.yml
Normal file
44
api/openapi.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
# https://openapi.tools
|
||||
|
||||
openapi: 3.1.0
|
||||
|
||||
info:
|
||||
title: Sunshine
|
||||
summary: Self-hosted game stream host for Moonlight.
|
||||
version: 0.0.0
|
||||
contact:
|
||||
name: LizardByte
|
||||
url: https://app.lizardbyte.dev/support
|
||||
license:
|
||||
name: GNU General Public License v3.0 only
|
||||
url: https://github.com/LizardByte/Sunshine/blob/master/LICENSE
|
||||
|
||||
servers:
|
||||
- url: "https://{host}:{ui-port}"
|
||||
description: Sunshine server
|
||||
variables:
|
||||
host:
|
||||
default: "localhost"
|
||||
ui-port:
|
||||
default: 47990
|
||||
|
||||
security:
|
||||
- basicAuth: []
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
# TODO: update when JWT is implemented (https://github.com/LizardByte/Sunshine/pull/2995)
|
||||
# https://swagger.io/specification/#security-scheme-object-examples
|
||||
basicAuth:
|
||||
description: HTTP Basic authentication
|
||||
type: http
|
||||
scheme: basic
|
||||
|
||||
paths:
|
||||
/api/apps:
|
||||
$ref: "./paths/confighttp/apps/apps.yml"
|
||||
/api/apps/{index}:
|
||||
$ref: "./paths/confighttp/apps/apps-by-index.yml"
|
||||
/api/logs:
|
||||
$ref: "./paths/confighttp/logs/logs.yml"
|
||||
37
api/paths/confighttp/apps/apps-by-index.yml
Normal file
37
api/paths/confighttp/apps/apps-by-index.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
delete:
|
||||
summary: Delete an application.
|
||||
description: |
|
||||
Delete an application.
|
||||
operationId: deleteApps
|
||||
tags:
|
||||
- Apps
|
||||
parameters:
|
||||
- name: index
|
||||
in: path
|
||||
description: The index of the application to delete.
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
responses:
|
||||
'200':
|
||||
description: The application was deleted successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
result:
|
||||
type: string
|
||||
example:
|
||||
status: true
|
||||
result: "application 9999 deleted"
|
||||
'400':
|
||||
$ref: "../../../components/responses/400.yml"
|
||||
'401':
|
||||
$ref: "../../../components/responses/401.yml"
|
||||
'403':
|
||||
$ref: "../../../components/responses/403.yml"
|
||||
151
api/paths/confighttp/apps/apps.yml
Normal file
151
api/paths/confighttp/apps/apps.yml
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
get:
|
||||
summary: Get the list of available applications.
|
||||
description: |
|
||||
Get the list of available applications.
|
||||
operationId: getApps
|
||||
tags:
|
||||
- Apps
|
||||
responses:
|
||||
'200':
|
||||
description: A list of available applications.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "../../../components/schemas/app.yml"
|
||||
example:
|
||||
- name: "Example App"
|
||||
output: "/path/to/output.log"
|
||||
cmd: "example-command"
|
||||
exclude-global-prep-cmd: false
|
||||
elevated: false
|
||||
auto-detach: true
|
||||
wait-all: false
|
||||
exit-timeout: 30
|
||||
image-path: "/path/to/image.png"
|
||||
working-dir: "/path/to/working-dir"
|
||||
prep-cmd:
|
||||
- do: "prep-command-1"
|
||||
undo: "undo-command-1"
|
||||
elevated: false
|
||||
detached:
|
||||
- "detached-command-1"
|
||||
'401':
|
||||
$ref: "../../../components/responses/401.yml"
|
||||
'403':
|
||||
$ref: "../../../components/responses/403.yml"
|
||||
|
||||
post:
|
||||
summary: Save an application.
|
||||
description: |
|
||||
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.
|
||||
operationId: postApps
|
||||
tags:
|
||||
- Apps
|
||||
parameters:
|
||||
- name: index
|
||||
in: query
|
||||
description: The index of the application to update. If the index is -1, a new application will be created.
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
- name: name
|
||||
in: query
|
||||
description: Application Name
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: output
|
||||
in: query
|
||||
description: Log Output Path
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: cmd
|
||||
in: query
|
||||
description: Command to run the application
|
||||
required: false
|
||||
schema:
|
||||
$ref: "../../../components/schemas/cmd.yml"
|
||||
- name: exclude-global-prep-cmd
|
||||
in: query
|
||||
description: Enable/Disable the execution of Global Prep Commands for this application.
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
- name: elevated
|
||||
in: query
|
||||
description: Run the application as an elevated process.
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
- name: auto-detach
|
||||
in: query
|
||||
description: Continue streaming if the application exits quickly
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
- name: wait-all
|
||||
in: query
|
||||
description: Continue streaming until all app processes exit
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
- name: exit-timeout
|
||||
in: query
|
||||
description: Number of seconds to wait for all app processes to gracefully exit when requested to quit.
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
- name: prep-cmd
|
||||
in: query
|
||||
description: Commands to run before the main application
|
||||
required: false
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "../../../components/schemas/prep-cmd.yml"
|
||||
- name: detached
|
||||
in: query
|
||||
description: Commands to run in detached processes
|
||||
required: false
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "../../../components/schemas/cmd.yml"
|
||||
- name: image-path
|
||||
in: query
|
||||
description: Full path to the application image. Must be a png file.
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: working-dir
|
||||
in: query
|
||||
description: The working directory that should be passed to the process.
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: The application was saved successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example:
|
||||
status: true
|
||||
'400':
|
||||
$ref: "../../../components/responses/400.yml"
|
||||
'401':
|
||||
$ref: "../../../components/responses/401.yml"
|
||||
'403':
|
||||
$ref: "../../../components/responses/403.yml"
|
||||
20
api/paths/confighttp/logs/logs.yml
Normal file
20
api/paths/confighttp/logs/logs.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
get:
|
||||
summary: Get the logs from the log file.
|
||||
description: |
|
||||
Get the logs from the log file.
|
||||
operationId: getLogs
|
||||
tags:
|
||||
- Logs
|
||||
responses:
|
||||
'200':
|
||||
description: The contents of the log file.
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
example: '[2025-01-15 17:07:58.131]: Info: Sunshine version: v...'
|
||||
'401':
|
||||
$ref: "../../../components/responses/401.yml"
|
||||
'403':
|
||||
$ref: "../../../components/responses/403.yml"
|
||||
@@ -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}
|
||||
|
||||
@@ -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 "sunshine")
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -100,15 +100,31 @@ endif()
|
||||
|
||||
# tray icon
|
||||
if(${SUNSHINE_TRAY} STREQUAL 1)
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
|
||||
RENAME "sunshine-tray.svg")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-playing.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-pausing.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-locked.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
|
||||
if(NOT ${SUNSHINE_BUILD_FLATPAK})
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status"
|
||||
RENAME "sunshine-tray.svg")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-playing.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-pausing.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-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}/sunshine.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/sunshine-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/sunshine-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/sunshine-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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -10,14 +10,12 @@ if(APPLE)
|
||||
endif()
|
||||
elseif(UNIX)
|
||||
# configure the .desktop file
|
||||
set(SUNSHINE_DESKTOP_ICON "sunshine.svg")
|
||||
set(SUNSHINE_DESKTOP_ICON "sunshine")
|
||||
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()
|
||||
|
||||
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>
|
||||
|
||||
@@ -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).}
|
||||
|
||||
|
||||
Submodule packaging/linux/flatpak/deps/shared-modules updated: f5d368a31d...26def5f1d2
@@ -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>
|
||||
|
||||
11
packaging/linux/flatpak/scripts/sunshine.sh
Normal file
11
packaging/linux/flatpak/scripts/sunshine.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
PORT=47990
|
||||
|
||||
if ! curl -k https://localhost:$PORT > /dev/null 2>&1; then
|
||||
(sleep 3 && xdg-open https://localhost:$PORT) &
|
||||
exec sunshine "$@"
|
||||
else
|
||||
echo "Sunshine is already running, opening the web interface..."
|
||||
xdg-open https://localhost:$PORT
|
||||
fi
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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'
|
||||
]
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
* @file src/audio.cpp
|
||||
* @brief Definitions for audio capture and encoding.
|
||||
*/
|
||||
// standard includes
|
||||
#include <thread>
|
||||
|
||||
// lib includes
|
||||
#include <opus/opus_multistream.h>
|
||||
|
||||
#include "platform/common.h"
|
||||
|
||||
// local includes
|
||||
#include "audio.h"
|
||||
#include "config.h"
|
||||
#include "globals.h"
|
||||
#include "logging.h"
|
||||
#include "platform/common.h"
|
||||
#include "thread_safe.h"
|
||||
#include "utility.h"
|
||||
|
||||
@@ -20,15 +22,11 @@ namespace audio {
|
||||
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
|
||||
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<float>>>;
|
||||
|
||||
static int
|
||||
start_audio_control(audio_ctx_t &ctx);
|
||||
static void
|
||||
stop_audio_control(audio_ctx_t &);
|
||||
static void
|
||||
apply_surround_params(opus_stream_config_t &stream, const stream_params_t ¶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<packet_t>(mail::audio_packets);
|
||||
auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
|
||||
if (config.flags[config_t::CUSTOM_SURROUND_PARAMS]) {
|
||||
@@ -96,14 +93,15 @@ namespace audio {
|
||||
// Encoding takes place on this thread
|
||||
platf::adjust_thread_priority(platf::thread_priority_e::high);
|
||||
|
||||
opus_t opus { opus_multistream_encoder_create(
|
||||
opus_t opus {opus_multistream_encoder_create(
|
||||
stream.sampleRate,
|
||||
stream.channelCount,
|
||||
stream.streams,
|
||||
stream.coupledStreams,
|
||||
stream.mapping,
|
||||
OPUS_APPLICATION_RESTRICTED_LOWDELAY,
|
||||
nullptr) };
|
||||
nullptr
|
||||
)};
|
||||
|
||||
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_BITRATE(stream.bitrate));
|
||||
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_VBR(0));
|
||||
@@ -114,7 +112,7 @@ namespace audio {
|
||||
|
||||
auto frame_size = config.packetDuration * stream.sampleRate / 1000;
|
||||
while (auto sample = samples->pop()) {
|
||||
buffer_t packet { 1400 };
|
||||
buffer_t packet {1400};
|
||||
|
||||
int bytes = opus_multistream_encode_float(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
|
||||
if (bytes < 0) {
|
||||
@@ -129,8 +127,7 @@ namespace audio {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
capture(safe::mail_t mail, config_t config, void *channel_data) {
|
||||
void capture(safe::mail_t mail, config_t config, void *channel_data) {
|
||||
auto shutdown_event = mail->event<bool>(mail::shutdown);
|
||||
auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
|
||||
if (config.flags[config_t::CUSTOM_SURROUND_PARAMS]) {
|
||||
@@ -204,7 +201,7 @@ namespace audio {
|
||||
platf::adjust_thread_priority(platf::thread_priority_e::critical);
|
||||
|
||||
auto samples = std::make_shared<sample_queue_t::element_type>(30);
|
||||
std::thread thread { encodeThread, samples, config, channel_data };
|
||||
std::thread thread {encodeThread, samples, config, channel_data};
|
||||
|
||||
auto fg = util::fail_guard([&]() {
|
||||
samples->stop();
|
||||
@@ -243,14 +240,12 @@ namespace audio {
|
||||
}
|
||||
}
|
||||
|
||||
audio_ctx_ref_t
|
||||
get_audio_ctx_ref() {
|
||||
static auto control_shared { safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control) };
|
||||
audio_ctx_ref_t get_audio_ctx_ref() {
|
||||
static auto control_shared {safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control)};
|
||||
return control_shared.ref();
|
||||
}
|
||||
|
||||
bool
|
||||
is_audio_ctx_sink_available(const audio_ctx_t &ctx) {
|
||||
bool is_audio_ctx_sink_available(const audio_ctx_t &ctx) {
|
||||
if (!ctx.control) {
|
||||
return false;
|
||||
}
|
||||
@@ -263,8 +258,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:
|
||||
@@ -277,8 +271,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;
|
||||
});
|
||||
@@ -305,8 +298,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;
|
||||
@@ -320,8 +312,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;
|
||||
|
||||
@@ -71,8 +71,7 @@ namespace audio {
|
||||
using packet_t = std::pair<void *, buffer_t>;
|
||||
using audio_ctx_ref_t = safe::shared_t<audio_ctx_t>::ptr_t;
|
||||
|
||||
void
|
||||
capture(safe::mail_t mail, config_t config, void *channel_data);
|
||||
void capture(safe::mail_t mail, config_t config, void *channel_data);
|
||||
|
||||
/**
|
||||
* @brief Get the reference to the audio context.
|
||||
@@ -84,8 +83,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.
|
||||
@@ -101,6 +99,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
|
||||
|
||||
36
src/cbs.cpp
36
src/cbs.cpp
@@ -3,6 +3,7 @@
|
||||
* @brief Definitions for FFmpeg Coded Bitstream API.
|
||||
*/
|
||||
extern "C" {
|
||||
// lib includes
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/cbs_h264.h>
|
||||
#include <libavcodec/cbs_h265.h>
|
||||
@@ -10,14 +11,15 @@ extern "C" {
|
||||
#include <libavutil/pixdesc.h>
|
||||
}
|
||||
|
||||
// local includes
|
||||
#include "cbs.h"
|
||||
#include "logging.h"
|
||||
#include "utility.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace cbs {
|
||||
void
|
||||
close(CodedBitstreamContext *c) {
|
||||
void close(CodedBitstreamContext *c) {
|
||||
ff_cbs_close(&c);
|
||||
}
|
||||
|
||||
@@ -36,8 +38,7 @@ namespace cbs {
|
||||
std::fill_n((std::uint8_t *) this, sizeof(*this), 0);
|
||||
}
|
||||
|
||||
frag_t &
|
||||
operator=(frag_t &&o) {
|
||||
frag_t &operator=(frag_t &&o) {
|
||||
std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this);
|
||||
|
||||
o.data = nullptr;
|
||||
@@ -53,12 +54,11 @@ namespace cbs {
|
||||
}
|
||||
};
|
||||
|
||||
util::buffer_t<std::uint8_t>
|
||||
write(cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
|
||||
util::buffer_t<std::uint8_t> write(cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
|
||||
cbs::frag_t frag;
|
||||
auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr);
|
||||
if (err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
|
||||
BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return {};
|
||||
@@ -66,29 +66,27 @@ namespace cbs {
|
||||
|
||||
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag);
|
||||
if (err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
|
||||
BOOST_LOG(error) << "Could not write fragment data: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// frag.data_size * 8 - frag.data_bit_padding == bits in fragment
|
||||
util::buffer_t<std::uint8_t> data { frag.data_size };
|
||||
util::buffer_t<std::uint8_t> data {frag.data_size};
|
||||
std::copy_n(frag.data, frag.data_size, std::begin(data));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
util::buffer_t<std::uint8_t>
|
||||
write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
|
||||
util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
|
||||
cbs::ctx_t cbs_ctx;
|
||||
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
|
||||
|
||||
return write(cbs_ctx, nal, uh, codec_id);
|
||||
}
|
||||
|
||||
h264_t
|
||||
make_sps_h264(const AVCodecContext *avctx, const AVPacket *packet) {
|
||||
h264_t make_sps_h264(const AVCodecContext *avctx, const AVPacket *packet) {
|
||||
cbs::ctx_t ctx;
|
||||
if (ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
|
||||
return {};
|
||||
@@ -98,7 +96,7 @@ namespace cbs {
|
||||
|
||||
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
|
||||
if (err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
|
||||
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return {};
|
||||
@@ -144,8 +142,7 @@ namespace cbs {
|
||||
};
|
||||
}
|
||||
|
||||
hevc_t
|
||||
make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
|
||||
hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
|
||||
cbs::ctx_t ctx;
|
||||
if (ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) {
|
||||
return {};
|
||||
@@ -155,7 +152,7 @@ namespace cbs {
|
||||
|
||||
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
|
||||
if (err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
|
||||
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return {};
|
||||
@@ -222,8 +219,7 @@ namespace cbs {
|
||||
* It then checks if the SPS->VUI (Video Usability Information) is present in the active SPS of the packet.
|
||||
* This is done for both H264 and H265 codecs.
|
||||
*/
|
||||
bool
|
||||
validate_sps(const AVPacket *packet, int codec_id) {
|
||||
bool validate_sps(const AVPacket *packet, int codec_id) {
|
||||
cbs::ctx_t ctx;
|
||||
if (ff_cbs_init(&ctx, (AVCodecID) codec_id, nullptr)) {
|
||||
return false;
|
||||
@@ -233,7 +229,7 @@ namespace cbs {
|
||||
|
||||
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
|
||||
if (err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
|
||||
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return false;
|
||||
|
||||
10
src/cbs.h
10
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
|
||||
|
||||
365
src/config.cpp
365
src/config.cpp
@@ -2,6 +2,7 @@
|
||||
* @file src/config.cpp
|
||||
* @brief Definitions for the configuration of Sunshine.
|
||||
*/
|
||||
// standard includes
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@@ -11,21 +12,22 @@
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
// lib includes
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
// local includes
|
||||
#include "config.h"
|
||||
#include "entry_handler.h"
|
||||
#include "file_handler.h"
|
||||
#include "logging.h"
|
||||
#include "nvhttp.h"
|
||||
#include "platform/common.h"
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include "platform/common.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
@@ -43,15 +45,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;
|
||||
}
|
||||
@@ -178,11 +186,11 @@ namespace config {
|
||||
cavlc = AMF_VIDEO_ENCODER_CALV ///< CAVLC
|
||||
};
|
||||
|
||||
template <class T>
|
||||
std::optional<int>
|
||||
quality_from_view(const std::string_view &quality_type, const std::optional<int>(&original)) {
|
||||
template<class T>
|
||||
std::optional<int> quality_from_view(const std::string_view &quality_type, const std::optional<int>(&original)) {
|
||||
#define _CONVERT_(x) \
|
||||
if (quality_type == #x##sv) return (int) T::x
|
||||
if (quality_type == #x##sv) \
|
||||
return (int) T::x
|
||||
_CONVERT_(balanced);
|
||||
_CONVERT_(quality);
|
||||
_CONVERT_(speed);
|
||||
@@ -190,11 +198,11 @@ namespace config {
|
||||
return original;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::optional<int>
|
||||
rc_from_view(const std::string_view &rc, const std::optional<int>(&original)) {
|
||||
template<class T>
|
||||
std::optional<int> rc_from_view(const std::string_view &rc, const std::optional<int>(&original)) {
|
||||
#define _CONVERT_(x) \
|
||||
if (rc == #x##sv) return (int) T::x
|
||||
if (rc == #x##sv) \
|
||||
return (int) T::x
|
||||
_CONVERT_(cbr);
|
||||
_CONVERT_(cqp);
|
||||
_CONVERT_(vbr_latency);
|
||||
@@ -203,11 +211,11 @@ namespace config {
|
||||
return original;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::optional<int>
|
||||
usage_from_view(const std::string_view &usage, const std::optional<int>(&original)) {
|
||||
template<class T>
|
||||
std::optional<int> usage_from_view(const std::string_view &usage, const std::optional<int>(&original)) {
|
||||
#define _CONVERT_(x) \
|
||||
if (usage == #x##sv) return (int) T::x
|
||||
if (usage == #x##sv) \
|
||||
return (int) T::x
|
||||
_CONVERT_(lowlatency);
|
||||
_CONVERT_(lowlatency_high_quality);
|
||||
_CONVERT_(transcoding);
|
||||
@@ -217,11 +225,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;
|
||||
}
|
||||
@@ -244,10 +257,10 @@ namespace config {
|
||||
disabled = false ///< Disabled
|
||||
};
|
||||
|
||||
std::optional<int>
|
||||
preset_from_view(const std::string_view &preset) {
|
||||
std::optional<int> preset_from_view(const std::string_view &preset) {
|
||||
#define _CONVERT_(x) \
|
||||
if (preset == #x##sv) return x
|
||||
if (preset == #x##sv) \
|
||||
return x
|
||||
_CONVERT_(veryslow);
|
||||
_CONVERT_(slower);
|
||||
_CONVERT_(slow);
|
||||
@@ -259,11 +272,16 @@ namespace config {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int>
|
||||
coder_from_view(const std::string_view &coder) {
|
||||
if (coder == "auto"sv) return _auto;
|
||||
if (coder == "cabac"sv || coder == "ac"sv) return disabled;
|
||||
if (coder == "cavlc"sv || coder == "vlc"sv) return enabled;
|
||||
std::optional<int> coder_from_view(const std::string_view &coder) {
|
||||
if (coder == "auto"sv) {
|
||||
return _auto;
|
||||
}
|
||||
if (coder == "cabac"sv || coder == "ac"sv) {
|
||||
return disabled;
|
||||
}
|
||||
if (coder == "cavlc"sv || coder == "vlc"sv) {
|
||||
return enabled;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -277,32 +295,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;
|
||||
}
|
||||
@@ -310,10 +336,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);
|
||||
@@ -329,10 +355,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);
|
||||
@@ -342,10 +368,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);
|
||||
@@ -355,10 +381,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);
|
||||
@@ -368,10 +394,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);
|
||||
@@ -380,9 +406,8 @@ namespace config {
|
||||
return video_t::dd_t::hdr_option_e::disabled; // Default to this if value is invalid
|
||||
}
|
||||
|
||||
video_t::dd_t::mode_remapping_t
|
||||
mode_remapping_from_view(const std::string_view value) {
|
||||
const auto parse_entry_list { [](const auto &entry_list, auto &output_field) {
|
||||
video_t::dd_t::mode_remapping_t mode_remapping_from_view(const std::string_view value) {
|
||||
const auto parse_entry_list {[](const auto &entry_list, auto &output_field) {
|
||||
for (auto &[_, entry] : entry_list) {
|
||||
auto requested_resolution = entry.template get_optional<std::string>("requested_resolution"s);
|
||||
auto requested_fps = entry.template get_optional<std::string>("requested_fps"s);
|
||||
@@ -393,9 +418,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;
|
||||
@@ -478,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
|
||||
@@ -515,13 +542,13 @@ namespace config {
|
||||
|
||||
input_t input {
|
||||
{
|
||||
{ 0x10, 0xA0 },
|
||||
{ 0x11, 0xA2 },
|
||||
{ 0x12, 0xA4 },
|
||||
{0x10, 0xA0},
|
||||
{0x11, 0xA2},
|
||||
{0x12, 0xA4},
|
||||
},
|
||||
-1ms, // back_button_timeout
|
||||
500ms, // key_repeat_delay
|
||||
std::chrono::duration<double> { 1 / 24.9 }, // key_repeat_period
|
||||
std::chrono::duration<double> {1 / 24.9}, // key_repeat_period
|
||||
|
||||
{
|
||||
platf::supported_gamepads(nullptr).front().name.data(),
|
||||
@@ -556,23 +583,19 @@ namespace config {
|
||||
{}, // prep 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, {
|
||||
@@ -587,9 +610,8 @@ namespace config {
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class It>
|
||||
It
|
||||
skip_list(It skipper, It end) {
|
||||
template<class It>
|
||||
It skip_list(It skipper, It end) {
|
||||
int stack = 1;
|
||||
while (skipper != end && stack) {
|
||||
if (*skipper == '[') {
|
||||
@@ -608,7 +630,7 @@ namespace config {
|
||||
std::pair<
|
||||
std::string_view::const_iterator,
|
||||
std::optional<std::pair<std::string, std::string>>>
|
||||
parse_option(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
|
||||
parse_option(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
|
||||
begin = std::find_if_not(begin, end, whitespace);
|
||||
auto endl = std::find_if(begin, end, endline);
|
||||
auto endc = std::find(begin, endl, '#');
|
||||
@@ -638,11 +660,11 @@ namespace config {
|
||||
|
||||
return std::make_pair(
|
||||
endl,
|
||||
std::make_pair(to_string(begin, end_name), to_string(begin_val, endl)));
|
||||
std::make_pair(to_string(begin, end_name), to_string(begin_val, endl))
|
||||
);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string>
|
||||
parse_config(const std::string_view &file_content) {
|
||||
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content) {
|
||||
std::unordered_map<std::string, std::string> vars;
|
||||
|
||||
auto pos = std::begin(file_content);
|
||||
@@ -667,8 +689,7 @@ namespace config {
|
||||
return vars;
|
||||
}
|
||||
|
||||
void
|
||||
string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
|
||||
void string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
|
||||
auto it = vars.find(name);
|
||||
if (it == std::end(vars)) {
|
||||
return;
|
||||
@@ -679,9 +700,8 @@ namespace config {
|
||||
vars.erase(it);
|
||||
}
|
||||
|
||||
template <typename T, typename F>
|
||||
void
|
||||
generic_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, T &input, F &&f) {
|
||||
template<typename T, typename F>
|
||||
void generic_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, T &input, F &&f) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
if (!tmp.empty()) {
|
||||
@@ -689,8 +709,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
string_restricted_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input, const std::vector<std::string_view> &allowed_vals) {
|
||||
void string_restricted_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input, const std::vector<std::string_view> &allowed_vals) {
|
||||
std::string temp;
|
||||
string_f(vars, name, temp);
|
||||
|
||||
@@ -702,8 +721,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
|
||||
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
|
||||
// appdata needs to be retrieved once only
|
||||
static auto appdata = platf::appdata();
|
||||
|
||||
@@ -727,8 +745,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
|
||||
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
|
||||
fs::path temp = input;
|
||||
|
||||
path_f(vars, name, temp);
|
||||
@@ -736,8 +753,7 @@ namespace config {
|
||||
input = temp.string();
|
||||
}
|
||||
|
||||
void
|
||||
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
|
||||
auto it = vars.find(name);
|
||||
|
||||
if (it == std::end(vars)) {
|
||||
@@ -754,16 +770,14 @@ namespace config {
|
||||
// If that integer is in hexadecimal
|
||||
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
|
||||
input = util::from_hex<int>(val.substr(2));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
input = util::from_view(val);
|
||||
}
|
||||
|
||||
vars.erase(it);
|
||||
}
|
||||
|
||||
void
|
||||
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
|
||||
auto it = vars.find(name);
|
||||
|
||||
if (it == std::end(vars)) {
|
||||
@@ -780,17 +794,15 @@ namespace config {
|
||||
// If that integer is in hexadecimal
|
||||
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
|
||||
input = util::from_hex<int>(val.substr(2));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
input = util::from_view(val);
|
||||
}
|
||||
|
||||
vars.erase(it);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void
|
||||
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
|
||||
template<class F>
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
if (!tmp.empty()) {
|
||||
@@ -798,9 +810,8 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void
|
||||
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
|
||||
template<class F>
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
if (!tmp.empty()) {
|
||||
@@ -808,8 +819,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &range) {
|
||||
void int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &range) {
|
||||
int temp = input;
|
||||
|
||||
int_f(vars, name, temp);
|
||||
@@ -820,9 +830,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 ||
|
||||
@@ -832,8 +843,7 @@ namespace config {
|
||||
(std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean));
|
||||
}
|
||||
|
||||
void
|
||||
bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
|
||||
void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
|
||||
@@ -844,8 +854,7 @@ namespace config {
|
||||
input = to_bool(tmp);
|
||||
}
|
||||
|
||||
void
|
||||
double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
|
||||
void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
|
||||
@@ -863,8 +872,7 @@ namespace config {
|
||||
input = val;
|
||||
}
|
||||
|
||||
void
|
||||
double_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input, const std::pair<double, double> &range) {
|
||||
void double_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input, const std::pair<double, double> &range) {
|
||||
double temp = input;
|
||||
|
||||
double_f(vars, name, temp);
|
||||
@@ -875,8 +883,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
|
||||
void list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
|
||||
std::string string;
|
||||
string_f(vars, name, string);
|
||||
|
||||
@@ -900,15 +907,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;
|
||||
}
|
||||
}
|
||||
@@ -918,8 +922,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
list_prep_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &input) {
|
||||
void list_prep_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &input) {
|
||||
std::string string;
|
||||
string_f(vars, name, string);
|
||||
|
||||
@@ -945,8 +948,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
|
||||
void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
|
||||
std::vector<std::string> list;
|
||||
list_string_f(vars, name, list);
|
||||
|
||||
@@ -972,16 +974,14 @@ namespace config {
|
||||
// If the integer is a hexadecimal
|
||||
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
|
||||
tmp = util::from_hex<int>(val.substr(2));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
tmp = util::from_view(val);
|
||||
}
|
||||
input.emplace_back(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
|
||||
void map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
|
||||
std::vector<int> list;
|
||||
list_int_f(vars, name, list);
|
||||
|
||||
@@ -1000,8 +1000,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
apply_flags(const char *line) {
|
||||
int apply_flags(const char *line) {
|
||||
int ret = 0;
|
||||
while (*line != '\0') {
|
||||
switch (*line) {
|
||||
@@ -1028,8 +1027,7 @@ namespace config {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string_view> &
|
||||
get_supported_gamepad_options() {
|
||||
std::vector<std::string_view> &get_supported_gamepad_options() {
|
||||
const auto options = platf::supported_gamepads(nullptr);
|
||||
static std::vector<std::string_view> opts {};
|
||||
opts.reserve(options.size());
|
||||
@@ -1039,8 +1037,7 @@ namespace config {
|
||||
return opts;
|
||||
}
|
||||
|
||||
void
|
||||
apply_config(std::unordered_map<std::string, std::string> &&vars) {
|
||||
void apply_config(std::unordered_map<std::string, std::string> &&vars) {
|
||||
if (!fs::exists(stream.file_apps.c_str())) {
|
||||
fs::copy_file(SUNSHINE_ASSETS_DIR "/apps.json", stream.file_apps);
|
||||
}
|
||||
@@ -1050,8 +1047,8 @@ namespace config {
|
||||
}
|
||||
|
||||
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()) {
|
||||
@@ -1059,8 +1056,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);
|
||||
@@ -1131,15 +1128,16 @@ namespace config {
|
||||
generic_f(vars, "dd_hdr_option", video.dd.hdr_option, dd::hdr_option_from_view);
|
||||
{
|
||||
int value = -1;
|
||||
int_between_f(vars, "dd_config_revert_delay", value, { 0, std::numeric_limits<int>::max() });
|
||||
int_between_f(vars, "dd_config_revert_delay", value, {0, std::numeric_limits<int>::max()});
|
||||
if (value >= 0) {
|
||||
video.dd.config_revert_delay = std::chrono::milliseconds { value };
|
||||
video.dd.config_revert_delay = std::chrono::milliseconds {value};
|
||||
}
|
||||
}
|
||||
bool_f(vars, "dd_config_revert_on_disconnect", video.dd.config_revert_on_disconnect);
|
||||
generic_f(vars, "dd_mode_remapping", video.dd.mode_remapping, dd::mode_remapping_from_view);
|
||||
bool_f(vars, "dd_wa_hdr_toggle", video.dd.wa.hdr_toggle);
|
||||
|
||||
int_between_f(vars, "min_fps_factor", video.min_fps_factor, { 1, 3 });
|
||||
int_between_f(vars, "min_fps_factor", video.min_fps_factor, {1, 3});
|
||||
|
||||
path_f(vars, "pkey", nvhttp.pkey);
|
||||
path_f(vars, "cert", nvhttp.cert);
|
||||
@@ -1158,19 +1156,19 @@ namespace config {
|
||||
string_f(vars, "virtual_sink", audio.virtual_sink);
|
||||
bool_f(vars, "install_steam_audio_drivers", audio.install_steam_drivers);
|
||||
|
||||
string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, { "pc"sv, "lan"sv, "wan"sv });
|
||||
string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, {"pc"sv, "lan"sv, "wan"sv});
|
||||
|
||||
int to = -1;
|
||||
int_between_f(vars, "ping_timeout", to, { -1, std::numeric_limits<int>::max() });
|
||||
int_between_f(vars, "ping_timeout", to, {-1, std::numeric_limits<int>::max()});
|
||||
if (to != -1) {
|
||||
stream.ping_timeout = std::chrono::milliseconds(to);
|
||||
}
|
||||
|
||||
int_between_f(vars, "lan_encryption_mode", stream.lan_encryption_mode, { 0, 2 });
|
||||
int_between_f(vars, "wan_encryption_mode", stream.wan_encryption_mode, { 0, 2 });
|
||||
int_between_f(vars, "lan_encryption_mode", stream.lan_encryption_mode, {0, 2});
|
||||
int_between_f(vars, "wan_encryption_mode", stream.wan_encryption_mode, {0, 2});
|
||||
|
||||
path_f(vars, "file_apps", stream.file_apps);
|
||||
int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 255 });
|
||||
int_between_f(vars, "fec_percentage", stream.fec_percentage, {1, 255});
|
||||
|
||||
map_int_int_f(vars, "keybindings"s, input.keybindings);
|
||||
|
||||
@@ -1187,20 +1185,20 @@ namespace config {
|
||||
int_f(vars, "back_button_timeout", to);
|
||||
|
||||
if (to > std::numeric_limits<int>::min()) {
|
||||
input.back_button_timeout = std::chrono::milliseconds { to };
|
||||
input.back_button_timeout = std::chrono::milliseconds {to};
|
||||
}
|
||||
|
||||
double repeat_frequency { 0 };
|
||||
double_between_f(vars, "key_repeat_frequency", repeat_frequency, { 0, std::numeric_limits<double>::max() });
|
||||
double repeat_frequency {0};
|
||||
double_between_f(vars, "key_repeat_frequency", repeat_frequency, {0, std::numeric_limits<double>::max()});
|
||||
|
||||
if (repeat_frequency > 0) {
|
||||
config::input.key_repeat_period = std::chrono::duration<double> { 1 / repeat_frequency };
|
||||
config::input.key_repeat_period = std::chrono::duration<double> {1 / repeat_frequency};
|
||||
}
|
||||
|
||||
to = -1;
|
||||
int_f(vars, "key_repeat_delay", to);
|
||||
if (to >= 0) {
|
||||
input.key_repeat_delay = std::chrono::milliseconds { to };
|
||||
input.key_repeat_delay = std::chrono::milliseconds {to};
|
||||
}
|
||||
|
||||
string_restricted_f(vars, "gamepad"s, input.gamepad, get_supported_gamepad_options());
|
||||
@@ -1220,10 +1218,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);
|
||||
@@ -1259,26 +1257,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') {
|
||||
@@ -1301,8 +1292,7 @@ namespace config {
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
parse(int argc, char *argv[]) {
|
||||
int parse(int argc, char *argv[]) {
|
||||
std::unordered_map<std::string, std::string> cmd_vars;
|
||||
#ifdef _WIN32
|
||||
bool shortcut_launch = false;
|
||||
@@ -1319,8 +1309,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
|
||||
@@ -1336,15 +1325,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);
|
||||
@@ -1370,7 +1357,7 @@ namespace config {
|
||||
|
||||
// Create empty config file if it does not exist
|
||||
if (!fs::exists(sunshine.config_file)) {
|
||||
std::ofstream { sunshine.config_file };
|
||||
std::ofstream {sunshine.config_file};
|
||||
}
|
||||
|
||||
// Read config file
|
||||
@@ -1385,11 +1372,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();
|
||||
}
|
||||
|
||||
@@ -1419,7 +1404,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];
|
||||
|
||||
24
src/config.h
24
src/config.h
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
@@ -11,6 +12,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// local includes
|
||||
#include "nvenc/nvenc_config.h"
|
||||
|
||||
namespace config {
|
||||
@@ -22,6 +24,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;
|
||||
@@ -129,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;
|
||||
@@ -204,17 +208,25 @@ 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(std::move(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)) {}
|
||||
do_cmd(std::move(do_cmd)),
|
||||
elevated(std::move(elevated)) {
|
||||
}
|
||||
|
||||
std::string do_cmd;
|
||||
std::string undo_cmd;
|
||||
bool elevated;
|
||||
};
|
||||
|
||||
struct sunshine_t {
|
||||
std::string locale;
|
||||
int min_log_level;
|
||||
@@ -248,8 +260,6 @@ namespace config {
|
||||
extern input_t input;
|
||||
extern sunshine_t sunshine;
|
||||
|
||||
int
|
||||
parse(int argc, char *argv[]);
|
||||
std::unordered_map<std::string, std::string>
|
||||
parse_config(const std::string_view &file_content);
|
||||
int parse(int argc, char *argv[]);
|
||||
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content);
|
||||
} // namespace config
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,34 +4,34 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
// standard includes
|
||||
#include <string>
|
||||
|
||||
// local includes
|
||||
#include "thread_safe.h"
|
||||
|
||||
#define WEB_DIR SUNSHINE_ASSETS_DIR "/web/"
|
||||
|
||||
namespace confighttp {
|
||||
constexpr auto PORT_HTTPS = 1;
|
||||
void
|
||||
start();
|
||||
void start();
|
||||
} // namespace confighttp
|
||||
|
||||
// mime types map
|
||||
const std::map<std::string, std::string> mime_types = {
|
||||
{ "css", "text/css" },
|
||||
{ "gif", "image/gif" },
|
||||
{ "htm", "text/html" },
|
||||
{ "html", "text/html" },
|
||||
{ "ico", "image/x-icon" },
|
||||
{ "jpeg", "image/jpeg" },
|
||||
{ "jpg", "image/jpeg" },
|
||||
{ "js", "application/javascript" },
|
||||
{ "json", "application/json" },
|
||||
{ "png", "image/png" },
|
||||
{ "svg", "image/svg+xml" },
|
||||
{ "ttf", "font/ttf" },
|
||||
{ "txt", "text/plain" },
|
||||
{ "woff2", "font/woff2" },
|
||||
{ "xml", "text/xml" },
|
||||
{"css", "text/css"},
|
||||
{"gif", "image/gif"},
|
||||
{"htm", "text/html"},
|
||||
{"html", "text/html"},
|
||||
{"ico", "image/x-icon"},
|
||||
{"jpeg", "image/jpeg"},
|
||||
{"jpg", "image/jpeg"},
|
||||
{"js", "application/javascript"},
|
||||
{"json", "application/json"},
|
||||
{"png", "image/png"},
|
||||
{"svg", "image/svg+xml"},
|
||||
{"ttf", "font/ttf"},
|
||||
{"txt", "text/plain"},
|
||||
{"woff2", "font/woff2"},
|
||||
{"xml", "text/xml"},
|
||||
};
|
||||
|
||||
140
src/crypto.cpp
140
src/crypto.cpp
@@ -2,29 +2,33 @@
|
||||
* @file src/crypto.cpp
|
||||
* @brief Definitions for cryptography functions.
|
||||
*/
|
||||
#include "crypto.h"
|
||||
// lib includes
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
// local includes
|
||||
#include "crypto.h"
|
||||
|
||||
namespace crypto {
|
||||
using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>;
|
||||
|
||||
cert_chain_t::cert_chain_t():
|
||||
_certs {}, _cert_ctx { X509_STORE_CTX_new() } {}
|
||||
void
|
||||
cert_chain_t::add(x509_t &&cert) {
|
||||
x509_store_t x509_store { X509_STORE_new() };
|
||||
_certs {},
|
||||
_cert_ctx {X509_STORE_CTX_new()} {
|
||||
}
|
||||
|
||||
void cert_chain_t::add(x509_t &&cert) {
|
||||
x509_store_t x509_store {X509_STORE_new()};
|
||||
|
||||
X509_STORE_add_cert(x509_store.get(), cert.get());
|
||||
_certs.emplace_back(std::make_pair(std::move(cert), 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 +56,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) {
|
||||
const char *cert_chain_t::verify(x509_t::element_type *cert) {
|
||||
int err_code = 0;
|
||||
for (auto &[_, x509_store] : _certs) {
|
||||
auto fg = util::fail_guard([this]() {
|
||||
@@ -86,8 +89,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) {
|
||||
@@ -110,8 +112,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
|
||||
@@ -131,8 +132,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
|
||||
@@ -145,8 +145,7 @@ namespace crypto {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
|
||||
int gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
|
||||
if (!decrypt_ctx && init_decrypt_gcm(decrypt_ctx, &key, iv, padding)) {
|
||||
return -1;
|
||||
}
|
||||
@@ -185,8 +184,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;
|
||||
}
|
||||
@@ -216,14 +214,12 @@ namespace crypto {
|
||||
return update_outlen + final_outlen;
|
||||
}
|
||||
|
||||
int
|
||||
gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
|
||||
int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
|
||||
// This overload handles the common case of [GCM tag][cipher text] buffer layout
|
||||
return encrypt(plaintext, tagged_cipher, tagged_cipher + tag_size, iv);
|
||||
}
|
||||
|
||||
int
|
||||
ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
|
||||
int ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(decrypt_ctx.get());
|
||||
});
|
||||
@@ -250,8 +246,7 @@ namespace crypto {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
|
||||
int ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(encrypt_ctx.get());
|
||||
});
|
||||
@@ -284,8 +279,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;
|
||||
}
|
||||
@@ -311,18 +305,20 @@ namespace crypto {
|
||||
}
|
||||
|
||||
ecb_t::ecb_t(const aes_t &key, bool padding):
|
||||
cipher_t { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding } {}
|
||||
cipher_t {EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding} {
|
||||
}
|
||||
|
||||
cbc_t::cbc_t(const aes_t &key, bool padding):
|
||||
cipher_t { nullptr, nullptr, key, padding } {}
|
||||
cipher_t {nullptr, nullptr, key, padding} {
|
||||
}
|
||||
|
||||
gcm_t::gcm_t(const crypto::aes_t &key, bool padding):
|
||||
cipher_t { nullptr, nullptr, key, padding } {}
|
||||
cipher_t {nullptr, nullptr, key, padding} {
|
||||
}
|
||||
|
||||
} // namespace cipher
|
||||
|
||||
aes_t
|
||||
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
|
||||
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
|
||||
aes_t key(16);
|
||||
|
||||
std::string salt_pin;
|
||||
@@ -338,16 +334,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());
|
||||
|
||||
@@ -357,9 +351,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());
|
||||
|
||||
@@ -369,40 +362,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);
|
||||
|
||||
@@ -411,9 +400,8 @@ namespace crypto {
|
||||
return r;
|
||||
}
|
||||
|
||||
std::vector<uint8_t>
|
||||
sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
std::vector<uint8_t> sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
|
||||
md_ctx_t ctx {EVP_MD_CTX_create()};
|
||||
|
||||
if (EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, (EVP_PKEY *) pkey.get()) != 1) {
|
||||
return {};
|
||||
@@ -436,10 +424,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());
|
||||
@@ -449,7 +436,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()));
|
||||
@@ -459,8 +446,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);
|
||||
@@ -472,26 +459,22 @@ namespace crypto {
|
||||
X509_set_pubkey(x509.get(), pkey.get());
|
||||
|
||||
auto name = X509_get_subject_name(x509.get());
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
|
||||
(const std::uint8_t *) cn.data(), cn.size(),
|
||||
-1, 0);
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const std::uint8_t *) cn.data(), cn.size(), -1, 0);
|
||||
|
||||
X509_set_issuer_name(x509.get(), name);
|
||||
X509_sign(x509.get(), pkey.get(), EVP_sha256());
|
||||
|
||||
return { pem(x509), pem(pkey) };
|
||||
return {pem(x509), pem(pkey)};
|
||||
}
|
||||
|
||||
std::vector<uint8_t>
|
||||
sign256(const pkey_t &pkey, const std::string_view &data) {
|
||||
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data) {
|
||||
return sign(pkey, data, EVP_sha256());
|
||||
}
|
||||
|
||||
bool
|
||||
verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
|
||||
bool verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
|
||||
auto pkey = X509_get0_pubkey(x509.get());
|
||||
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
md_ctx_t ctx {EVP_MD_CTX_create()};
|
||||
|
||||
if (EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
|
||||
return false;
|
||||
@@ -508,18 +491,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) {
|
||||
|
||||
85
src/crypto.h
85
src/crypto.h
@@ -4,12 +4,16 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <array>
|
||||
|
||||
// lib includes
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
// local includes
|
||||
#include "utility.h"
|
||||
|
||||
namespace crypto {
|
||||
@@ -18,8 +22,7 @@ namespace crypto {
|
||||
std::string pkey;
|
||||
};
|
||||
|
||||
void
|
||||
md_ctx_destroy(EVP_MD_CTX *);
|
||||
void md_ctx_destroy(EVP_MD_CTX *);
|
||||
|
||||
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
|
||||
|
||||
@@ -39,50 +42,33 @@ namespace crypto {
|
||||
* @param plaintext
|
||||
* @return The SHA-256 hash of the plaintext.
|
||||
*/
|
||||
sha256_t
|
||||
hash(const std::string_view &plaintext);
|
||||
sha256_t hash(const std::string_view &plaintext);
|
||||
|
||||
aes_t
|
||||
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
|
||||
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
|
||||
x509_t x509(const std::string_view &x);
|
||||
pkey_t pkey(const std::string_view &k);
|
||||
std::string pem(x509_t &x509);
|
||||
std::string pem(pkey_t &pkey);
|
||||
|
||||
x509_t
|
||||
x509(const std::string_view &x);
|
||||
pkey_t
|
||||
pkey(const std::string_view &k);
|
||||
std::string
|
||||
pem(x509_t &x509);
|
||||
std::string
|
||||
pem(pkey_t &pkey);
|
||||
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data);
|
||||
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
|
||||
|
||||
std::vector<uint8_t>
|
||||
sign256(const pkey_t &pkey, const std::string_view &data);
|
||||
bool
|
||||
verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
|
||||
creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits);
|
||||
|
||||
creds_t
|
||||
gen_creds(const std::string_view &cn, std::uint32_t key_bits);
|
||||
std::string_view signature(const x509_t &x);
|
||||
|
||||
std::string_view
|
||||
signature(const x509_t &x);
|
||||
|
||||
std::string
|
||||
rand(std::size_t bytes);
|
||||
std::string
|
||||
rand_alphabet(std::size_t bytes,
|
||||
const std::string_view &alphabet = std::string_view { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-" });
|
||||
std::string rand(std::size_t bytes);
|
||||
std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet = std::string_view {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-"});
|
||||
|
||||
class cert_chain_t {
|
||||
public:
|
||||
KITTY_DECL_CONSTR(cert_chain_t)
|
||||
|
||||
void
|
||||
add(x509_t &&cert);
|
||||
void add(x509_t &&cert);
|
||||
|
||||
void
|
||||
clear();
|
||||
void clear();
|
||||
|
||||
const char *
|
||||
verify(x509_t::element_type *cert);
|
||||
const char *verify(x509_t::element_type *cert);
|
||||
|
||||
private:
|
||||
std::vector<std::pair<x509_t, x509_store_t>> _certs;
|
||||
@@ -91,8 +77,8 @@ namespace crypto {
|
||||
|
||||
namespace cipher {
|
||||
constexpr std::size_t tag_size = 16;
|
||||
constexpr std::size_t
|
||||
round_to_pkcs7_padded(std::size_t size) {
|
||||
|
||||
constexpr std::size_t round_to_pkcs7_padded(std::size_t size) {
|
||||
return ((size + 15) / 16) * 16;
|
||||
}
|
||||
|
||||
@@ -110,23 +96,19 @@ namespace crypto {
|
||||
public:
|
||||
ecb_t() = default;
|
||||
ecb_t(ecb_t &&) noexcept = default;
|
||||
ecb_t &
|
||||
operator=(ecb_t &&) noexcept = default;
|
||||
ecb_t &operator=(ecb_t &&) noexcept = default;
|
||||
|
||||
ecb_t(const aes_t &key, bool padding = true);
|
||||
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
|
||||
int
|
||||
decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
|
||||
int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
|
||||
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
|
||||
};
|
||||
|
||||
class gcm_t: public cipher_t {
|
||||
public:
|
||||
gcm_t() = default;
|
||||
gcm_t(gcm_t &&) noexcept = default;
|
||||
gcm_t &
|
||||
operator=(gcm_t &&) noexcept = default;
|
||||
gcm_t &operator=(gcm_t &&) noexcept = default;
|
||||
|
||||
gcm_t(const crypto::aes_t &key, bool padding = true);
|
||||
|
||||
@@ -138,8 +120,7 @@ namespace crypto {
|
||||
* @param iv The initialization vector to be used for the encryption.
|
||||
* @return The total length of the ciphertext and GCM tag. Returns -1 in case of an error.
|
||||
*/
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv);
|
||||
int encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv);
|
||||
|
||||
/**
|
||||
* @brief Encrypts the plaintext using AES GCM mode.
|
||||
@@ -149,19 +130,16 @@ namespace crypto {
|
||||
* @param iv The initialization vector to be used for the encryption.
|
||||
* @return The total length of the ciphertext and GCM tag written into tagged_cipher. Returns -1 in case of an error.
|
||||
*/
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
|
||||
int encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
|
||||
|
||||
int
|
||||
decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
|
||||
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
|
||||
};
|
||||
|
||||
class cbc_t: public cipher_t {
|
||||
public:
|
||||
cbc_t() = default;
|
||||
cbc_t(cbc_t &&) noexcept = default;
|
||||
cbc_t &
|
||||
operator=(cbc_t &&) noexcept = default;
|
||||
cbc_t &operator=(cbc_t &&) noexcept = default;
|
||||
|
||||
cbc_t(const crypto::aes_t &key, bool padding = true);
|
||||
|
||||
@@ -173,8 +151,7 @@ namespace crypto {
|
||||
* @param iv The initialization vector to be used for the encryption.
|
||||
* @return The total length of the ciphertext written into cipher. Returns -1 in case of an error.
|
||||
*/
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
|
||||
int encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
|
||||
};
|
||||
} // namespace cipher
|
||||
} // namespace crypto
|
||||
|
||||
@@ -29,15 +29,15 @@
|
||||
|
||||
namespace display_device {
|
||||
namespace {
|
||||
constexpr std::chrono::milliseconds DEFAULT_RETRY_INTERVAL { 5000 };
|
||||
constexpr std::chrono::milliseconds DEFAULT_RETRY_INTERVAL {5000};
|
||||
|
||||
/**
|
||||
* @brief A global for the settings manager interface and other settings whose lifetime is managed by `display_device::init(...)`.
|
||||
*/
|
||||
struct {
|
||||
std::mutex mutex {};
|
||||
std::chrono::milliseconds config_revert_delay { 0 };
|
||||
std::unique_ptr<RetryScheduler<SettingsManagerInterface>> sm_instance { nullptr };
|
||||
std::chrono::milliseconds config_revert_delay {0};
|
||||
std::unique_ptr<RetryScheduler<SettingsManagerInterface>> sm_instance {nullptr};
|
||||
} DD_DATA;
|
||||
|
||||
/**
|
||||
@@ -49,8 +49,7 @@ namespace display_device {
|
||||
*/
|
||||
class sunshine_audio_context_t: public AudioContextInterface {
|
||||
public:
|
||||
[[nodiscard]] bool
|
||||
capture() override {
|
||||
[[nodiscard]] bool capture() override {
|
||||
return context_scheduler.execute([](auto &audio_context) {
|
||||
// Explicitly releasing the context first in case it was not release yet so that it can be potentially cleaned up.
|
||||
audio_context = boost::none;
|
||||
@@ -61,8 +60,7 @@ namespace display_device {
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isCaptured() const override {
|
||||
[[nodiscard]] bool isCaptured() const override {
|
||||
return context_scheduler.execute([](const auto &audio_context) {
|
||||
if (audio_context) {
|
||||
// In case we still have context we need to check whether it was released or not.
|
||||
@@ -74,8 +72,7 @@ namespace display_device {
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
release() override {
|
||||
void release() override {
|
||||
context_scheduler.schedule([](auto &audio_context, auto &stop_token) {
|
||||
if (audio_context) {
|
||||
audio_context->released = true;
|
||||
@@ -93,7 +90,7 @@ namespace display_device {
|
||||
audio_context = boost::none;
|
||||
stop_token.requestStop();
|
||||
},
|
||||
SchedulerOptions { .m_sleep_durations = { 2s } });
|
||||
SchedulerOptions {.m_sleep_durations = {2s}});
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -102,20 +99,20 @@ namespace display_device {
|
||||
* @brief A reference to the audio context that will automatically extend the audio session.
|
||||
* @note It is auto-initialized here for convenience.
|
||||
*/
|
||||
decltype(audio::get_audio_ctx_ref()) audio_ctx_ref { audio::get_audio_ctx_ref() };
|
||||
decltype(audio::get_audio_ctx_ref()) audio_ctx_ref {audio::get_audio_ctx_ref()};
|
||||
|
||||
/**
|
||||
* @brief Will be set to true if the capture was released, but we still have to keep the context around, because the device is not available.
|
||||
*/
|
||||
bool released { false };
|
||||
bool released {false};
|
||||
|
||||
/**
|
||||
* @brief How many times to check if the audio sink is available before giving up.
|
||||
*/
|
||||
int retry_counter { 15 };
|
||||
int retry_counter {15};
|
||||
};
|
||||
|
||||
RetryScheduler<boost::optional<audio_context_t>> context_scheduler { std::make_unique<boost::optional<audio_context_t>>(boost::none) };
|
||||
RetryScheduler<boost::optional<audio_context_t>> context_scheduler {std::make_unique<boost::optional<audio_context_t>>(boost::none)};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -124,9 +121,8 @@ namespace display_device {
|
||||
* @param value String to be converted
|
||||
* @return Parsed unsigned integer.
|
||||
*/
|
||||
unsigned int
|
||||
stou(const std::string &value) {
|
||||
unsigned long result { std::stoul(value) };
|
||||
unsigned int stou(const std::string &value) {
|
||||
unsigned long result {std::stoul(value)};
|
||||
if (result > std::numeric_limits<unsigned int>::max()) {
|
||||
throw std::out_of_range("stou");
|
||||
}
|
||||
@@ -151,10 +147,9 @@ namespace display_device {
|
||||
* }
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
parse_resolution_string(const std::string &input, std::optional<Resolution> &output) {
|
||||
const std::string trimmed_input { boost::algorithm::trim_copy(input) };
|
||||
const std::regex resolution_regex { R"(^(\d+)x(\d+)$)" };
|
||||
bool parse_resolution_string(const std::string &input, std::optional<Resolution> &output) {
|
||||
const std::string trimmed_input {boost::algorithm::trim_copy(input)};
|
||||
const std::regex resolution_regex {R"(^(\d+)x(\d+)$)"};
|
||||
|
||||
if (std::smatch match; std::regex_match(trimmed_input, match, resolution_regex)) {
|
||||
try {
|
||||
@@ -163,16 +158,13 @@ namespace display_device {
|
||||
stou(match[2].str())
|
||||
};
|
||||
return true;
|
||||
}
|
||||
catch (const std::out_of_range &) {
|
||||
} catch (const std::out_of_range &) {
|
||||
BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << " (number out of range).";
|
||||
}
|
||||
catch (const std::exception &err) {
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << ":\n"
|
||||
<< err.what();
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (trimmed_input.empty()) {
|
||||
output = std::nullopt;
|
||||
return true;
|
||||
@@ -203,16 +195,17 @@ namespace display_device {
|
||||
* }
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output, const bool allow_decimal_point = true) {
|
||||
static const auto is_zero { [](const auto &character) { return character == '0'; } };
|
||||
const std::string trimmed_input { boost::algorithm::trim_copy(input) };
|
||||
const std::regex refresh_rate_regex { allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)" };
|
||||
bool parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output, const bool allow_decimal_point = true) {
|
||||
static const auto is_zero {[](const auto &character) {
|
||||
return character == '0';
|
||||
}};
|
||||
const std::string trimmed_input {boost::algorithm::trim_copy(input)};
|
||||
const std::regex refresh_rate_regex {allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)"};
|
||||
|
||||
if (std::smatch match; std::regex_match(trimmed_input, match, refresh_rate_regex)) {
|
||||
try {
|
||||
// Here we are trimming zeros from the string to possibly reduce out of bounds case
|
||||
std::string trimmed_match_1 { boost::algorithm::trim_left_copy_if(match[1].str(), is_zero) };
|
||||
std::string trimmed_match_1 {boost::algorithm::trim_left_copy_if(match[1].str(), is_zero)};
|
||||
if (trimmed_match_1.empty()) {
|
||||
trimmed_match_1 = "0"s; // Just in case ALL the string is full of zeros, we want to leave one
|
||||
}
|
||||
@@ -230,33 +223,29 @@ namespace display_device {
|
||||
// denominator = 1000
|
||||
|
||||
// We are essentially removing the decimal point here: 59.995 -> 59995
|
||||
const std::string numerator_str { trimmed_match_1 + trimmed_match_2 };
|
||||
const auto numerator { stou(numerator_str) };
|
||||
const std::string numerator_str {trimmed_match_1 + trimmed_match_2};
|
||||
const auto numerator {stou(numerator_str)};
|
||||
|
||||
// Here we are counting decimal places and calculating denominator: 10^decimal_places
|
||||
const auto denominator { static_cast<unsigned int>(std::pow(10, trimmed_match_2.size())) };
|
||||
const auto denominator {static_cast<unsigned int>(std::pow(10, trimmed_match_2.size()))};
|
||||
|
||||
output = Rational { numerator, denominator };
|
||||
}
|
||||
else {
|
||||
output = Rational {numerator, denominator};
|
||||
} else {
|
||||
// We do not have a decimal point, just a valid number.
|
||||
// For example:
|
||||
// 60:
|
||||
// numerator = 60
|
||||
// denominator = 1
|
||||
output = Rational { stou(trimmed_match_1), 1 };
|
||||
output = Rational {stou(trimmed_match_1), 1};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (const std::out_of_range &) {
|
||||
} catch (const std::out_of_range &) {
|
||||
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << " (number out of range).";
|
||||
}
|
||||
catch (const std::exception &err) {
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << ":\n"
|
||||
<< err.what();
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (trimmed_input.empty()) {
|
||||
output = std::nullopt;
|
||||
return true;
|
||||
@@ -279,8 +268,7 @@ namespace display_device {
|
||||
* const auto device_prep_option = parse_device_prep_option(video_config);
|
||||
* @examples_end
|
||||
*/
|
||||
std::optional<SingleDisplayConfiguration::DevicePreparation>
|
||||
parse_device_prep_option(const config::video_t &video_config) {
|
||||
std::optional<SingleDisplayConfiguration::DevicePreparation> parse_device_prep_option(const config::video_t &video_config) {
|
||||
using enum config::video_t::dd_t::config_option_e;
|
||||
using enum SingleDisplayConfiguration::DevicePreparation;
|
||||
|
||||
@@ -315,44 +303,42 @@ namespace display_device {
|
||||
* const bool success = parse_resolution_option(video_config, *launch_session, config);
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
parse_resolution_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||
bool parse_resolution_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||
using resolution_option_e = config::video_t::dd_t::resolution_option_e;
|
||||
|
||||
switch (video_config.dd.resolution_option) {
|
||||
case resolution_option_e::automatic: {
|
||||
if (!session.enable_sops) {
|
||||
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution automatically, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
|
||||
}
|
||||
else if (session.width >= 0 && session.height >= 0) {
|
||||
config.m_resolution = Resolution {
|
||||
static_cast<unsigned int>(session.width),
|
||||
static_cast<unsigned int>(session.height)
|
||||
};
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case resolution_option_e::manual: {
|
||||
if (!session.enable_sops) {
|
||||
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution manually, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
|
||||
}
|
||||
else {
|
||||
if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) {
|
||||
BOOST_LOG(error) << "Failed to parse manual resolution string!";
|
||||
case resolution_option_e::automatic:
|
||||
{
|
||||
if (!session.enable_sops) {
|
||||
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution automatically, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
|
||||
} else if (session.width >= 0 && session.height >= 0) {
|
||||
config.m_resolution = Resolution {
|
||||
static_cast<unsigned int>(session.width),
|
||||
static_cast<unsigned int>(session.height)
|
||||
};
|
||||
} else {
|
||||
BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case resolution_option_e::manual:
|
||||
{
|
||||
if (!session.enable_sops) {
|
||||
BOOST_LOG(warning) << R"(Sunshine is configured to change resolution manually, but the "Optimize game settings" is not set in the client! Resolution will not be changed.)";
|
||||
} else {
|
||||
if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) {
|
||||
BOOST_LOG(error) << "Failed to parse manual resolution string!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.m_resolution) {
|
||||
BOOST_LOG(error) << "Manual resolution must be specified!";
|
||||
return false;
|
||||
if (!config.m_resolution) {
|
||||
BOOST_LOG(error) << "Manual resolution must be specified!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case resolution_option_e::disabled:
|
||||
break;
|
||||
}
|
||||
@@ -375,33 +361,33 @@ namespace display_device {
|
||||
* const bool success = parse_refresh_rate_option(video_config, *launch_session, config);
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
parse_refresh_rate_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||
bool parse_refresh_rate_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||
using refresh_rate_option_e = config::video_t::dd_t::refresh_rate_option_e;
|
||||
|
||||
switch (video_config.dd.refresh_rate_option) {
|
||||
case refresh_rate_option_e::automatic: {
|
||||
if (session.fps >= 0) {
|
||||
config.m_refresh_rate = Rational { static_cast<unsigned int>(session.fps), 1 };
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(error) << "FPS value provided by client session config is invalid: " << session.fps;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case refresh_rate_option_e::manual: {
|
||||
if (!parse_refresh_rate_string(video_config.dd.manual_refresh_rate, config.m_refresh_rate)) {
|
||||
BOOST_LOG(error) << "Failed to parse manual refresh rate string!";
|
||||
return false;
|
||||
case refresh_rate_option_e::automatic:
|
||||
{
|
||||
if (session.fps >= 0) {
|
||||
config.m_refresh_rate = Rational {static_cast<unsigned int>(session.fps), 1};
|
||||
} else {
|
||||
BOOST_LOG(error) << "FPS value provided by client session config is invalid: " << session.fps;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case refresh_rate_option_e::manual:
|
||||
{
|
||||
if (!parse_refresh_rate_string(video_config.dd.manual_refresh_rate, config.m_refresh_rate)) {
|
||||
BOOST_LOG(error) << "Failed to parse manual refresh rate string!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.m_refresh_rate) {
|
||||
BOOST_LOG(error) << "Manual refresh rate must be specified!";
|
||||
return false;
|
||||
if (!config.m_refresh_rate) {
|
||||
BOOST_LOG(error) << "Manual refresh rate must be specified!";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case refresh_rate_option_e::disabled:
|
||||
break;
|
||||
}
|
||||
@@ -422,8 +408,7 @@ namespace display_device {
|
||||
* const auto hdr_option = parse_hdr_option(video_config, *launch_session);
|
||||
* @examples_end
|
||||
*/
|
||||
std::optional<HdrState>
|
||||
parse_hdr_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
std::optional<HdrState> parse_hdr_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
using hdr_option_e = config::video_t::dd_t::hdr_option_e;
|
||||
|
||||
switch (video_config.dd.hdr_option) {
|
||||
@@ -450,11 +435,10 @@ namespace display_device {
|
||||
* @param video_config User's video related configuration.
|
||||
* @returns Enum value if remapping can be performed, null optional if remapping shall be skipped.
|
||||
*/
|
||||
std::optional<remapping_type_e>
|
||||
determine_remapping_type(const config::video_t &video_config) {
|
||||
std::optional<remapping_type_e> determine_remapping_type(const config::video_t &video_config) {
|
||||
using dd_t = config::video_t::dd_t;
|
||||
const bool auto_resolution { video_config.dd.resolution_option == dd_t::resolution_option_e::automatic };
|
||||
const bool auto_refresh_rate { video_config.dd.refresh_rate_option == dd_t::refresh_rate_option_e::automatic };
|
||||
const bool auto_resolution {video_config.dd.resolution_option == dd_t::resolution_option_e::automatic};
|
||||
const bool auto_refresh_rate {video_config.dd.refresh_rate_option == dd_t::refresh_rate_option_e::automatic};
|
||||
|
||||
if (auto_resolution && auto_refresh_rate) {
|
||||
return remapping_type_e::mixed;
|
||||
@@ -486,8 +470,7 @@ namespace display_device {
|
||||
* @param type Remapping type to check.
|
||||
* @returns True if resolution is to be mapped, false otherwise.
|
||||
*/
|
||||
bool
|
||||
is_resolution_mapped(const remapping_type_e type) {
|
||||
bool is_resolution_mapped(const remapping_type_e type) {
|
||||
return type == remapping_type_e::resolution_only || type == remapping_type_e::mixed;
|
||||
}
|
||||
|
||||
@@ -496,8 +479,7 @@ namespace display_device {
|
||||
* @param type Remapping type to check.
|
||||
* @returns True if FPS is to be mapped, false otherwise.
|
||||
*/
|
||||
bool
|
||||
is_fps_mapped(const remapping_type_e type) {
|
||||
bool is_fps_mapped(const remapping_type_e type) {
|
||||
return type == remapping_type_e::refresh_rate_only || type == remapping_type_e::mixed;
|
||||
}
|
||||
|
||||
@@ -507,17 +489,16 @@ namespace display_device {
|
||||
* @param type Specify which entry fields should be parsed.
|
||||
* @returns Parsed structure or null optional if a necessary field could not be parsed.
|
||||
*/
|
||||
std::optional<parsed_remapping_entry_t>
|
||||
parse_remapping_entry(const config::video_t::dd_t::mode_remapping_entry_t &entry, const remapping_type_e type) {
|
||||
std::optional<parsed_remapping_entry_t> parse_remapping_entry(const config::video_t::dd_t::mode_remapping_entry_t &entry, const remapping_type_e type) {
|
||||
parsed_remapping_entry_t result {};
|
||||
|
||||
if (is_resolution_mapped(type) && (!parse_resolution_string(entry.requested_resolution, result.requested_resolution) ||
|
||||
!parse_resolution_string(entry.final_resolution, result.final_resolution))) {
|
||||
!parse_resolution_string(entry.final_resolution, result.final_resolution))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (is_fps_mapped(type) && (!parse_refresh_rate_string(entry.requested_fps, result.requested_fps, false) ||
|
||||
!parse_refresh_rate_string(entry.final_refresh_rate, result.final_refresh_rate))) {
|
||||
!parse_refresh_rate_string(entry.final_refresh_rate, result.final_refresh_rate))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -539,14 +520,13 @@ namespace display_device {
|
||||
* const bool success = remap_display_mode_if_needed(video_config, *launch_session, config);
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
remap_display_mode_if_needed(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||
const auto remapping_type { determine_remapping_type(video_config) };
|
||||
bool remap_display_mode_if_needed(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||
const auto remapping_type {determine_remapping_type(video_config)};
|
||||
if (!remapping_type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto &remapping_list { [&]() {
|
||||
const auto &remapping_list {[&]() {
|
||||
using enum remapping_type_e;
|
||||
|
||||
switch (*remapping_type) {
|
||||
@@ -558,7 +538,7 @@ namespace display_device {
|
||||
default:
|
||||
return video_config.dd.mode_remapping.mixed;
|
||||
}
|
||||
}() };
|
||||
}()};
|
||||
|
||||
if (remapping_list.empty()) {
|
||||
BOOST_LOG(debug) << "No values are available for display mode remapping.";
|
||||
@@ -566,9 +546,9 @@ namespace display_device {
|
||||
}
|
||||
BOOST_LOG(debug) << "Trying to remap display modes...";
|
||||
|
||||
const auto entry_to_string { [type = *remapping_type](const config::video_t::dd_t::mode_remapping_entry_t &entry) {
|
||||
const bool mapping_resolution { is_resolution_mapped(type) };
|
||||
const bool mapping_fps { is_fps_mapped(type) };
|
||||
const auto entry_to_string {[type = *remapping_type](const config::video_t::dd_t::mode_remapping_entry_t &entry) {
|
||||
const bool mapping_resolution {is_resolution_mapped(type)};
|
||||
const bool mapping_fps {is_fps_mapped(type)};
|
||||
|
||||
// clang-format off
|
||||
return (mapping_resolution ? " - requested resolution: "s + entry.requested_resolution + "\n" : "") +
|
||||
@@ -576,10 +556,10 @@ namespace display_device {
|
||||
(mapping_resolution ? " - final resolution: "s + entry.final_resolution + "\n" : "") +
|
||||
(mapping_fps ? " - final refresh rate: "s + entry.final_refresh_rate : "");
|
||||
// clang-format on
|
||||
} };
|
||||
}};
|
||||
|
||||
for (const auto &entry : remapping_list) {
|
||||
const auto parsed_entry { parse_remapping_entry(entry, *remapping_type) };
|
||||
const auto parsed_entry {parse_remapping_entry(entry, *remapping_type)};
|
||||
if (!parsed_entry) {
|
||||
BOOST_LOG(error) << "Failed to parse remapping entry from:\n"
|
||||
<< entry_to_string(entry);
|
||||
@@ -632,16 +612,18 @@ namespace display_device {
|
||||
* @param video_config User's video related configuration.
|
||||
* @return An interface or nullptr if the OS does not support the interface.
|
||||
*/
|
||||
std::unique_ptr<SettingsManagerInterface>
|
||||
make_settings_manager([[maybe_unused]] const std::filesystem::path &persistence_filepath, [[maybe_unused]] const config::video_t &video_config) {
|
||||
std::unique_ptr<SettingsManagerInterface> make_settings_manager([[maybe_unused]] const std::filesystem::path &persistence_filepath, [[maybe_unused]] const config::video_t &video_config) {
|
||||
#ifdef _WIN32
|
||||
return std::make_unique<SettingsManager>(
|
||||
std::make_shared<WinDisplayDevice>(std::make_shared<WinApiLayer>()),
|
||||
std::make_shared<sunshine_audio_context_t>(),
|
||||
std::make_unique<PersistentState>(
|
||||
std::make_shared<FileSettingsPersistence>(persistence_filepath)),
|
||||
std::make_shared<FileSettingsPersistence>(persistence_filepath)
|
||||
),
|
||||
WinWorkarounds {
|
||||
.m_hdr_blank_delay = video_config.dd.wa.hdr_toggle ? std::make_optional(500ms) : std::nullopt });
|
||||
.m_hdr_blank_delay = video_config.dd.wa.hdr_toggle ? std::make_optional(500ms) : std::nullopt
|
||||
}
|
||||
);
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
@@ -660,17 +642,16 @@ namespace display_device {
|
||||
* @brief Reverts the configuration based on the provided option.
|
||||
* @note This is function does not lock mutex.
|
||||
*/
|
||||
void
|
||||
revert_configuration_unlocked(const revert_option_e option) {
|
||||
void revert_configuration_unlocked(const revert_option_e option) {
|
||||
if (!DD_DATA.sm_instance) {
|
||||
// Platform is not supported, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: by default the executor function is immediately executed in the calling thread. With delay, we want to avoid that.
|
||||
SchedulerOptions scheduler_option { .m_sleep_durations = { DEFAULT_RETRY_INTERVAL } };
|
||||
SchedulerOptions scheduler_option {.m_sleep_durations = {DEFAULT_RETRY_INTERVAL}};
|
||||
if (option == revert_option_e::try_indefinitely_with_delay && DD_DATA.config_revert_delay > std::chrono::milliseconds::zero()) {
|
||||
scheduler_option.m_sleep_durations = { DD_DATA.config_revert_delay, DEFAULT_RETRY_INTERVAL };
|
||||
scheduler_option.m_sleep_durations = {DD_DATA.config_revert_delay, DEFAULT_RETRY_INTERVAL};
|
||||
scheduler_option.m_execution = SchedulerOptions::Execution::ScheduledOnly;
|
||||
}
|
||||
|
||||
@@ -681,17 +662,21 @@ namespace display_device {
|
||||
return;
|
||||
}
|
||||
|
||||
auto available_devices { [&settings_iface]() {
|
||||
const auto devices { settings_iface.enumAvailableDevices() };
|
||||
auto available_devices {[&settings_iface]() {
|
||||
const auto devices {settings_iface.enumAvailableDevices()};
|
||||
std::set<std::string> parsed_devices;
|
||||
|
||||
std::transform(
|
||||
std::begin(devices), std::end(devices),
|
||||
std::begin(devices),
|
||||
std::end(devices),
|
||||
std::inserter(parsed_devices, std::end(parsed_devices)),
|
||||
[](const auto &device) { return device.m_device_id + " - " + device.m_friendly_name; });
|
||||
[](const auto &device) {
|
||||
return device.m_device_id + " - " + device.m_friendly_name;
|
||||
}
|
||||
);
|
||||
|
||||
return parsed_devices;
|
||||
}() };
|
||||
}()};
|
||||
if (available_devices == tried_out_devices) {
|
||||
BOOST_LOG(debug) << "Skipping reverting configuration, because no newly added/removed devices were detected since last check. Currently available devices:\n"
|
||||
<< toJson(available_devices);
|
||||
@@ -699,11 +684,10 @@ namespace display_device {
|
||||
}
|
||||
|
||||
using enum SettingsManagerInterface::RevertResult;
|
||||
if (const auto result { settings_iface.revertSettings() }; result == Ok) {
|
||||
if (const auto result {settings_iface.revertSettings()}; result == Ok) {
|
||||
stop_token.requestStop();
|
||||
return;
|
||||
}
|
||||
else if (result == ApiTemporarilyUnavailable) {
|
||||
} else if (result == ApiTemporarilyUnavailable) {
|
||||
// Do nothing and retry next time
|
||||
return;
|
||||
}
|
||||
@@ -713,13 +697,12 @@ namespace display_device {
|
||||
<< toJson(available_devices);
|
||||
tried_out_devices.swap(available_devices);
|
||||
},
|
||||
scheduler_option);
|
||||
scheduler_option);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<platf::deinit_t>
|
||||
init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config) {
|
||||
std::lock_guard lock { DD_DATA.mutex };
|
||||
std::unique_ptr<platf::deinit_t> init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config) {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
// We can support re-init without any issues, however we should make sure to clean up first!
|
||||
revert_configuration_unlocked(revert_option_e::try_once);
|
||||
DD_DATA.config_revert_delay = video_config.dd.config_revert_delay;
|
||||
@@ -727,10 +710,12 @@ namespace display_device {
|
||||
|
||||
// If we fail to create settings manager, this means platform is not supported, and
|
||||
// we will need to provided error-free pass-trough in other methods
|
||||
if (auto settings_manager { make_settings_manager(persistence_filepath, video_config) }) {
|
||||
if (auto settings_manager {make_settings_manager(persistence_filepath, video_config)}) {
|
||||
DD_DATA.sm_instance = std::make_unique<RetryScheduler<SettingsManagerInterface>>(std::move(settings_manager));
|
||||
|
||||
const auto available_devices { DD_DATA.sm_instance->execute([](auto &settings_iface) { return settings_iface.enumAvailableDevices(); }) };
|
||||
const auto available_devices {DD_DATA.sm_instance->execute([](auto &settings_iface) {
|
||||
return settings_iface.enumAvailableDevices();
|
||||
})};
|
||||
BOOST_LOG(info) << "Currently available display devices:\n"
|
||||
<< toJson(available_devices);
|
||||
|
||||
@@ -742,44 +727,44 @@ namespace display_device {
|
||||
class deinit_t: public platf::deinit_t {
|
||||
public:
|
||||
~deinit_t() override {
|
||||
std::lock_guard lock { DD_DATA.mutex };
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
try {
|
||||
// This may throw if used incorrectly. At the moment this will not happen, however
|
||||
// in case some unforeseen changes are made that could raise an exception,
|
||||
// we definitely don't want this to happen in destructor. Especially in the
|
||||
// deinit_t where the outcome does not really matter.
|
||||
revert_configuration_unlocked(revert_option_e::try_once);
|
||||
}
|
||||
catch (std::exception &err) {
|
||||
} catch (std::exception &err) {
|
||||
BOOST_LOG(fatal) << err.what();
|
||||
}
|
||||
|
||||
DD_DATA.sm_instance = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
std::string
|
||||
map_output_name(const std::string &output_name) {
|
||||
std::lock_guard lock { DD_DATA.mutex };
|
||||
std::string map_output_name(const std::string &output_name) {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
if (!DD_DATA.sm_instance) {
|
||||
// Fallback to giving back the output name if the platform is not supported.
|
||||
return output_name;
|
||||
}
|
||||
|
||||
return DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) { return settings_iface.getDisplayName(output_name); });
|
||||
return DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) {
|
||||
return settings_iface.getDisplayName(output_name);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
const auto result { parse_configuration(video_config, session) };
|
||||
if (const auto *parsed_config { std::get_if<SingleDisplayConfiguration>(&result) }; parsed_config) {
|
||||
void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
const auto result {parse_configuration(video_config, session)};
|
||||
if (const auto *parsed_config {std::get_if<SingleDisplayConfiguration>(&result)}; parsed_config) {
|
||||
configure_display(*parsed_config);
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto *disabled { std::get_if<configuration_disabled_tag_t>(&result) }; disabled) {
|
||||
if (const auto *disabled {std::get_if<configuration_disabled_tag_t>(&result)}; disabled) {
|
||||
revert_configuration();
|
||||
return;
|
||||
}
|
||||
@@ -788,9 +773,8 @@ namespace display_device {
|
||||
// want to revert active configuration in case we have any
|
||||
}
|
||||
|
||||
void
|
||||
configure_display(const SingleDisplayConfiguration &config) {
|
||||
std::lock_guard lock { DD_DATA.mutex };
|
||||
void configure_display(const SingleDisplayConfiguration &config) {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
if (!DD_DATA.sm_instance) {
|
||||
// Platform is not supported, nothing to do.
|
||||
return;
|
||||
@@ -803,18 +787,16 @@ namespace display_device {
|
||||
stop_token.requestStop();
|
||||
}
|
||||
},
|
||||
{ .m_sleep_durations = { DEFAULT_RETRY_INTERVAL } });
|
||||
{.m_sleep_durations = {DEFAULT_RETRY_INTERVAL}});
|
||||
}
|
||||
|
||||
void
|
||||
revert_configuration() {
|
||||
std::lock_guard lock { DD_DATA.mutex };
|
||||
void revert_configuration() {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
revert_configuration_unlocked(revert_option_e::try_indefinitely_with_delay);
|
||||
}
|
||||
|
||||
bool
|
||||
reset_persistence() {
|
||||
std::lock_guard lock { DD_DATA.mutex };
|
||||
bool reset_persistence() {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
if (!DD_DATA.sm_instance) {
|
||||
// Platform is not supported, assume success.
|
||||
return true;
|
||||
@@ -828,9 +810,20 @@ namespace display_device {
|
||||
});
|
||||
}
|
||||
|
||||
std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration>
|
||||
parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
const auto device_prep { parse_device_prep_option(video_config) };
|
||||
EnumeratedDeviceList enumerate_devices() {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
if (!DD_DATA.sm_instance) {
|
||||
// Platform is not supported.
|
||||
return {};
|
||||
}
|
||||
|
||||
return DD_DATA.sm_instance->execute([](auto &settings_iface) {
|
||||
return settings_iface.enumAvailableDevices();
|
||||
});
|
||||
}
|
||||
|
||||
std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
const auto device_prep {parse_device_prep_option(video_config)};
|
||||
if (!device_prep) {
|
||||
return configuration_disabled_tag_t {};
|
||||
}
|
||||
|
||||
@@ -4,18 +4,22 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <display_device/types.h>
|
||||
// standard includes
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
// lib includes
|
||||
#include <display_device/types.h>
|
||||
|
||||
// forward declarations
|
||||
namespace platf {
|
||||
class deinit_t;
|
||||
}
|
||||
|
||||
namespace config {
|
||||
struct video_t;
|
||||
}
|
||||
|
||||
namespace rtsp_stream {
|
||||
struct launch_session_t;
|
||||
}
|
||||
@@ -32,8 +36,7 @@ namespace display_device {
|
||||
* const auto init_guard { init("/my/persitence/file.state", video_config) };
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t>
|
||||
init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config);
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t> init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config);
|
||||
|
||||
/**
|
||||
* @brief Map the output name to a specific display.
|
||||
@@ -45,8 +48,7 @@ namespace display_device {
|
||||
* const auto mapped_name_custom { map_output_name("{some-device-id}") };
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] std::string
|
||||
map_output_name(const std::string &output_name);
|
||||
[[nodiscard]] std::string map_output_name(const std::string &output_name);
|
||||
|
||||
/**
|
||||
* @brief Configure the display device based on the user configuration and the session information.
|
||||
@@ -62,8 +64,7 @@ namespace display_device {
|
||||
* configure_display(video_config, *launch_session);
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
|
||||
void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
|
||||
|
||||
/**
|
||||
* @brief Configure the display device using the provided configuration.
|
||||
@@ -83,8 +84,7 @@ namespace display_device {
|
||||
* configure_display(valid_config);
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
configure_display(const SingleDisplayConfiguration &config);
|
||||
void configure_display(const SingleDisplayConfiguration &config);
|
||||
|
||||
/**
|
||||
* @brief Revert the display configuration and restore the previous state.
|
||||
@@ -96,8 +96,7 @@ namespace display_device {
|
||||
* revert_configuration();
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
revert_configuration();
|
||||
void revert_configuration();
|
||||
|
||||
/**
|
||||
* @brief Reset the persistence and currently held initial display state.
|
||||
@@ -111,16 +110,25 @@ namespace display_device {
|
||||
* The user then accepts that Sunshine is not able to restore the state and "agrees" to
|
||||
* do it manually.
|
||||
*
|
||||
* @return
|
||||
* @note Whether the function succeeds or fails, the any of the scheduled "retries" from
|
||||
* @return True if persistence was reset, false otherwise.
|
||||
* @note Whether the function succeeds or fails, any of the scheduled "retries" from
|
||||
* other methods will be stopped to not interfere with the user actions.
|
||||
*
|
||||
* @examples
|
||||
* const auto result = reset_persistence();
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
reset_persistence();
|
||||
[[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.
|
||||
@@ -150,6 +158,5 @@ namespace display_device {
|
||||
* }
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration>
|
||||
parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
|
||||
[[nodiscard]] std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
|
||||
} // namespace display_device
|
||||
|
||||
@@ -26,21 +26,18 @@ extern "C" {
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
void
|
||||
launch_ui() {
|
||||
void launch_ui() {
|
||||
std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS));
|
||||
platf::open_url(url);
|
||||
}
|
||||
|
||||
void
|
||||
launch_ui_with_path(std::string path) {
|
||||
void launch_ui_with_path(std::string path) {
|
||||
std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + path;
|
||||
platf::open_url(url);
|
||||
}
|
||||
|
||||
namespace args {
|
||||
int
|
||||
creds(const char *name, int argc, char *argv[]) {
|
||||
int creds(const char *name, int argc, char *argv[]) {
|
||||
if (argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) {
|
||||
help(name);
|
||||
}
|
||||
@@ -50,21 +47,18 @@ namespace args {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
help(const char *name) {
|
||||
int help(const char *name) {
|
||||
logging::print_help(name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
version() {
|
||||
int version() {
|
||||
// version was already logged at startup
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int
|
||||
restore_nvprefs_undo() {
|
||||
int restore_nvprefs_undo() {
|
||||
if (nvprefs_instance.load()) {
|
||||
nvprefs_instance.restore_from_and_delete_undo_file_if_exists();
|
||||
nvprefs_instance.unload();
|
||||
@@ -78,8 +72,7 @@ namespace lifetime {
|
||||
char **argv;
|
||||
std::atomic_int desired_exit_code;
|
||||
|
||||
void
|
||||
exit_sunshine(int exit_code, bool async) {
|
||||
void exit_sunshine(int exit_code, bool async) {
|
||||
// Store the exit code of the first exit_sunshine() call
|
||||
int zero = 0;
|
||||
desired_exit_code.compare_exchange_strong(zero, exit_code);
|
||||
@@ -94,8 +87,7 @@ namespace lifetime {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
debug_trap() {
|
||||
void debug_trap() {
|
||||
#ifdef _WIN32
|
||||
DebugBreak();
|
||||
#else
|
||||
@@ -103,22 +95,19 @@ namespace lifetime {
|
||||
#endif
|
||||
}
|
||||
|
||||
char **
|
||||
get_argv() {
|
||||
char **get_argv() {
|
||||
return argv;
|
||||
}
|
||||
} // namespace lifetime
|
||||
|
||||
void
|
||||
log_publisher_data() {
|
||||
void log_publisher_data() {
|
||||
BOOST_LOG(info) << "Package Publisher: "sv << SUNSHINE_PUBLISHER_NAME;
|
||||
BOOST_LOG(info) << "Publisher Website: "sv << SUNSHINE_PUBLISHER_WEBSITE;
|
||||
BOOST_LOG(info) << "Get support: "sv << SUNSHINE_PUBLISHER_ISSUE_URL;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
bool
|
||||
is_gamestream_enabled() {
|
||||
bool is_gamestream_enabled() {
|
||||
DWORD enabled;
|
||||
DWORD size = sizeof(enabled);
|
||||
return RegGetValueW(
|
||||
@@ -128,7 +117,8 @@ is_gamestream_enabled() {
|
||||
RRF_RT_REG_DWORD,
|
||||
nullptr,
|
||||
&enabled,
|
||||
&size) == ERROR_SUCCESS &&
|
||||
&size
|
||||
) == ERROR_SUCCESS &&
|
||||
enabled != 0;
|
||||
}
|
||||
|
||||
@@ -168,8 +158,7 @@ namespace service_ctrl {
|
||||
/**
|
||||
* @brief Asynchronously starts the Sunshine service.
|
||||
*/
|
||||
bool
|
||||
start_service() {
|
||||
bool start_service() {
|
||||
if (!service_handle) {
|
||||
return false;
|
||||
}
|
||||
@@ -189,8 +178,7 @@ namespace service_ctrl {
|
||||
* @brief Query the service status.
|
||||
* @param status The SERVICE_STATUS struct to populate.
|
||||
*/
|
||||
bool
|
||||
query_service_status(SERVICE_STATUS &status) {
|
||||
bool query_service_status(SERVICE_STATUS &status) {
|
||||
if (!service_handle) {
|
||||
return false;
|
||||
}
|
||||
@@ -209,9 +197,8 @@ namespace service_ctrl {
|
||||
SC_HANDLE service_handle = NULL;
|
||||
};
|
||||
|
||||
bool
|
||||
is_service_running() {
|
||||
service_controller sc { SERVICE_QUERY_STATUS };
|
||||
bool is_service_running() {
|
||||
service_controller sc {SERVICE_QUERY_STATUS};
|
||||
|
||||
SERVICE_STATUS status;
|
||||
if (!sc.query_service_status(status)) {
|
||||
@@ -221,9 +208,8 @@ namespace service_ctrl {
|
||||
return status.dwCurrentState == SERVICE_RUNNING;
|
||||
}
|
||||
|
||||
bool
|
||||
start_service() {
|
||||
service_controller sc { SERVICE_QUERY_STATUS | SERVICE_START };
|
||||
bool start_service() {
|
||||
service_controller sc {SERVICE_QUERY_STATUS | SERVICE_START};
|
||||
|
||||
std::cout << "Starting Sunshine..."sv;
|
||||
|
||||
@@ -247,8 +233,7 @@ namespace service_ctrl {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
wait_for_ui_ready() {
|
||||
bool wait_for_ui_ready() {
|
||||
std::cout << "Waiting for Web UI to be ready...";
|
||||
|
||||
// Wait up to 30 seconds for the web UI to start
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
* launch_ui();
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
launch_ui();
|
||||
void launch_ui();
|
||||
|
||||
/**
|
||||
* @brief Launch the Web UI at a specific endpoint.
|
||||
@@ -27,8 +26,7 @@ launch_ui();
|
||||
* launch_ui_with_path("/pin");
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
launch_ui_with_path(std::string path);
|
||||
void launch_ui_with_path(std::string path);
|
||||
|
||||
/**
|
||||
* @brief Functions for handling command line arguments.
|
||||
@@ -43,8 +41,7 @@ namespace args {
|
||||
* creds("sunshine", 2, {"new_username", "new_password"});
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
creds(const char *name, int argc, char *argv[]);
|
||||
int creds(const char *name, int argc, char *argv[]);
|
||||
|
||||
/**
|
||||
* @brief Print help to stdout, then exit.
|
||||
@@ -53,8 +50,7 @@ namespace args {
|
||||
* help("sunshine");
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
help(const char *name);
|
||||
int help(const char *name);
|
||||
|
||||
/**
|
||||
* @brief Print the version to stdout, then exit.
|
||||
@@ -62,8 +58,7 @@ namespace args {
|
||||
* version();
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
version();
|
||||
int version();
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
@@ -75,8 +70,7 @@ namespace args {
|
||||
* restore_nvprefs_undo();
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
restore_nvprefs_undo();
|
||||
int restore_nvprefs_undo();
|
||||
#endif
|
||||
} // namespace args
|
||||
|
||||
@@ -92,35 +86,30 @@ namespace lifetime {
|
||||
* @param exit_code The exit code to return from main().
|
||||
* @param async Specifies whether our termination will be non-blocking.
|
||||
*/
|
||||
void
|
||||
exit_sunshine(int exit_code, bool async);
|
||||
void exit_sunshine(int exit_code, bool async);
|
||||
|
||||
/**
|
||||
* @brief Breaks into the debugger or terminates Sunshine if no debugger is attached.
|
||||
*/
|
||||
void
|
||||
debug_trap();
|
||||
void debug_trap();
|
||||
|
||||
/**
|
||||
* @brief Get the argv array passed to main().
|
||||
*/
|
||||
char **
|
||||
get_argv();
|
||||
char **get_argv();
|
||||
} // namespace lifetime
|
||||
|
||||
/**
|
||||
* @brief Log the publisher metadata provided from CMake.
|
||||
*/
|
||||
void
|
||||
log_publisher_data();
|
||||
void log_publisher_data();
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* @brief Check if NVIDIA's GameStream software is running.
|
||||
* @return `true` if GameStream is enabled, `false` otherwise.
|
||||
*/
|
||||
bool
|
||||
is_gamestream_enabled();
|
||||
bool is_gamestream_enabled();
|
||||
|
||||
/**
|
||||
* @brief Namespace for controlling the Sunshine service model on Windows.
|
||||
@@ -132,8 +121,7 @@ namespace service_ctrl {
|
||||
* is_service_running();
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
is_service_running();
|
||||
bool is_service_running();
|
||||
|
||||
/**
|
||||
* @brief Start the service and wait for startup to complete.
|
||||
@@ -141,8 +129,7 @@ namespace service_ctrl {
|
||||
* start_service();
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
start_service();
|
||||
bool start_service();
|
||||
|
||||
/**
|
||||
* @brief Wait for the UI to be ready after Sunshine startup.
|
||||
@@ -150,7 +137,6 @@ namespace service_ctrl {
|
||||
* wait_for_ui_ready();
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
wait_for_ui_ready();
|
||||
bool wait_for_ui_ready();
|
||||
} // namespace service_ctrl
|
||||
#endif
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
#include "logging.h"
|
||||
|
||||
namespace file_handler {
|
||||
std::string
|
||||
get_parent_directory(const std::string &path) {
|
||||
std::string get_parent_directory(const std::string &path) {
|
||||
// remove any trailing path separators
|
||||
std::string trimmed_path = path;
|
||||
while (!trimmed_path.empty() && trimmed_path.back() == '/') {
|
||||
@@ -24,8 +23,7 @@ namespace file_handler {
|
||||
return p.parent_path().string();
|
||||
}
|
||||
|
||||
bool
|
||||
make_directory(const std::string &path) {
|
||||
bool make_directory(const std::string &path) {
|
||||
// first, check if the directory already exists
|
||||
if (std::filesystem::exists(path)) {
|
||||
return true;
|
||||
@@ -34,19 +32,17 @@ namespace file_handler {
|
||||
return std::filesystem::create_directories(path);
|
||||
}
|
||||
|
||||
std::string
|
||||
read_file(const char *path) {
|
||||
std::string read_file(const char *path) {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
BOOST_LOG(debug) << "Missing file: " << path;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::ifstream in(path);
|
||||
return std::string { (std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>() };
|
||||
return std::string {(std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()};
|
||||
}
|
||||
|
||||
int
|
||||
write_file(const char *path, const std::string_view &contents) {
|
||||
int write_file(const char *path, const std::string_view &contents) {
|
||||
std::ofstream out(path);
|
||||
|
||||
if (!out.is_open()) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
@@ -18,8 +19,7 @@ namespace file_handler {
|
||||
* std::string parent_dir = get_parent_directory("path/to/file");
|
||||
* @examples_end
|
||||
*/
|
||||
std::string
|
||||
get_parent_directory(const std::string &path);
|
||||
std::string get_parent_directory(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief Make a directory.
|
||||
@@ -29,8 +29,7 @@ namespace file_handler {
|
||||
* bool dir_created = make_directory("path/to/directory");
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
make_directory(const std::string &path);
|
||||
bool make_directory(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief Read a file to string.
|
||||
@@ -40,8 +39,7 @@ namespace file_handler {
|
||||
* std::string contents = read_file("path/to/file");
|
||||
* @examples_end
|
||||
*/
|
||||
std::string
|
||||
read_file(const char *path);
|
||||
std::string read_file(const char *path);
|
||||
|
||||
/**
|
||||
* @brief Writes a file.
|
||||
@@ -52,6 +50,5 @@ namespace file_handler {
|
||||
* int write_status = write_file("path/to/file", "file contents");
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
write_file(const char *path, const std::string_view &contents);
|
||||
int write_file(const char *path, const std::string_view &contents);
|
||||
} // namespace file_handler
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* @file globals.cpp
|
||||
* @brief Definitions for globally accessible variables and functions.
|
||||
*/
|
||||
// local includes
|
||||
#include "globals.h"
|
||||
|
||||
safe::mail_t mail::man;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// local includes
|
||||
#include "entry_handler.h"
|
||||
#include "thread_pool.h"
|
||||
|
||||
@@ -31,9 +32,9 @@ extern nvprefs::nvprefs_interface nvprefs_instance;
|
||||
* @brief Handles process-wide communication.
|
||||
*/
|
||||
namespace mail {
|
||||
#define MAIL(x) \
|
||||
#define MAIL(x) \
|
||||
constexpr auto x = std::string_view { \
|
||||
#x \
|
||||
#x \
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,22 +4,21 @@
|
||||
*/
|
||||
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
|
||||
|
||||
#include "process.h"
|
||||
|
||||
// standard includes
|
||||
#include <filesystem>
|
||||
#include <utility>
|
||||
|
||||
// lib includes
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/asio/ssl/context_base.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <Simple-Web-Server/server_http.hpp>
|
||||
#include <Simple-Web-Server/server_https.hpp>
|
||||
#include <boost/asio/ssl/context_base.hpp>
|
||||
#include <curl/curl.h>
|
||||
|
||||
// local includes
|
||||
#include "config.h"
|
||||
#include "crypto.h"
|
||||
#include "file_handler.h"
|
||||
@@ -28,6 +27,7 @@
|
||||
#include "network.h"
|
||||
#include "nvhttp.h"
|
||||
#include "platform/common.h"
|
||||
#include "process.h"
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
#include "uuid.h"
|
||||
@@ -37,16 +37,13 @@ namespace http {
|
||||
namespace fs = std::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
int
|
||||
reload_user_creds(const std::string &file);
|
||||
bool
|
||||
user_creds_exist(const std::string &file);
|
||||
int reload_user_creds(const std::string &file);
|
||||
bool user_creds_exist(const std::string &file);
|
||||
|
||||
std::string unique_id;
|
||||
net::net_e origin_web_ui_allowed;
|
||||
|
||||
int
|
||||
init() {
|
||||
int init() {
|
||||
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
|
||||
origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed);
|
||||
|
||||
@@ -57,29 +54,25 @@ namespace http {
|
||||
config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string();
|
||||
}
|
||||
|
||||
if (!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) {
|
||||
if (create_creds(config::nvhttp.pkey, config::nvhttp.cert)) {
|
||||
return -1;
|
||||
}
|
||||
if ((!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) &&
|
||||
create_creds(config::nvhttp.pkey, config::nvhttp.cert)) {
|
||||
return -1;
|
||||
}
|
||||
if (user_creds_exist(config::sunshine.credentials_file)) {
|
||||
if (reload_user_creds(config::sunshine.credentials_file)) return -1;
|
||||
}
|
||||
else {
|
||||
if (!user_creds_exist(config::sunshine.credentials_file)) {
|
||||
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
|
||||
} else if (reload_user_creds(config::sunshine.credentials_file)) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
|
||||
int save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
|
||||
pt::ptree outputTree;
|
||||
|
||||
if (fs::exists(file)) {
|
||||
try {
|
||||
pt::read_json(file, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what();
|
||||
return -1;
|
||||
}
|
||||
@@ -91,8 +84,7 @@ namespace http {
|
||||
outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string());
|
||||
try {
|
||||
pt::write_json(file, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "error writing to the credentials file, perhaps try this again as an administrator? Details: "sv << e.what();
|
||||
return -1;
|
||||
}
|
||||
@@ -101,8 +93,7 @@ namespace http {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
user_creds_exist(const std::string &file) {
|
||||
bool user_creds_exist(const std::string &file) {
|
||||
if (!fs::exists(file)) {
|
||||
return false;
|
||||
}
|
||||
@@ -113,32 +104,28 @@ namespace http {
|
||||
return inputTree.find("username") != inputTree.not_found() &&
|
||||
inputTree.find("password") != inputTree.not_found() &&
|
||||
inputTree.find("salt") != inputTree.not_found();
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "validating user credentials: "sv << e.what();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int
|
||||
reload_user_creds(const std::string &file) {
|
||||
int reload_user_creds(const std::string &file) {
|
||||
pt::ptree inputTree;
|
||||
try {
|
||||
pt::read_json(file, inputTree);
|
||||
config::sunshine.username = inputTree.get<std::string>("username");
|
||||
config::sunshine.password = inputTree.get<std::string>("password");
|
||||
config::sunshine.salt = inputTree.get<std::string>("salt");
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "loading user credentials: "sv << e.what();
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
create_creds(const std::string &pkey, const std::string &cert) {
|
||||
int create_creds(const std::string &pkey, const std::string &cert) {
|
||||
fs::path pkey_path = pkey;
|
||||
fs::path cert_path = cert;
|
||||
|
||||
@@ -172,18 +159,14 @@ namespace http {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fs::permissions(pkey_path,
|
||||
fs::perms::owner_read | fs::perms::owner_write,
|
||||
fs::perm_options::replace, err_code);
|
||||
fs::permissions(pkey_path, fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::replace, err_code);
|
||||
|
||||
if (err_code) {
|
||||
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message();
|
||||
return -1;
|
||||
}
|
||||
|
||||
fs::permissions(cert_path,
|
||||
fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write,
|
||||
fs::perm_options::replace, err_code);
|
||||
fs::permissions(cert_path, fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, fs::perm_options::replace, err_code);
|
||||
|
||||
if (err_code) {
|
||||
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message();
|
||||
@@ -193,21 +176,15 @@ namespace http {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
download_file(const std::string &url, const std::string &file) {
|
||||
CURL *curl = curl_easy_init();
|
||||
if (curl) {
|
||||
// sonar complains about weak ssl and tls versions
|
||||
// ideally, the setopts should go after the early returns; however sonar cannot detect the fix
|
||||
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
|
||||
}
|
||||
else {
|
||||
bool download_file(const std::string &url, const std::string &file, long ssl_version) {
|
||||
// sonar complains about weak ssl and tls versions; however sonar cannot detect the fix
|
||||
CURL *curl = curl_easy_init(); // NOSONAR
|
||||
if (!curl) {
|
||||
BOOST_LOG(error) << "Couldn't create CURL instance";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string file_dir = file_handler::get_parent_directory(file);
|
||||
if (!file_handler::make_directory(file_dir)) {
|
||||
if (std::string file_dir = file_handler::get_parent_directory(file); !file_handler::make_directory(file_dir)) {
|
||||
BOOST_LOG(error) << "Couldn't create directory ["sv << file_dir << ']';
|
||||
curl_easy_cleanup(curl);
|
||||
return false;
|
||||
@@ -220,6 +197,7 @@ namespace http {
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_SSLVERSION, ssl_version); // NOSONAR
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
||||
@@ -234,20 +212,16 @@ namespace http {
|
||||
return result == CURLE_OK;
|
||||
}
|
||||
|
||||
std::string
|
||||
url_escape(const std::string &url) {
|
||||
CURL *curl = curl_easy_init();
|
||||
char *string = curl_easy_escape(curl, url.c_str(), url.length());
|
||||
std::string url_escape(const std::string &url) {
|
||||
char *string = curl_easy_escape(nullptr, url.c_str(), static_cast<int>(url.length()));
|
||||
std::string result(string);
|
||||
curl_free(string);
|
||||
curl_easy_cleanup(curl);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string
|
||||
url_get_host(const std::string &url) {
|
||||
std::string url_get_host(const std::string &url) {
|
||||
CURLU *curlu = curl_url();
|
||||
curl_url_set(curlu, CURLUPART_URL, url.c_str(), url.length());
|
||||
curl_url_set(curlu, CURLUPART_URL, url.c_str(), static_cast<unsigned int>(url.length()));
|
||||
char *host;
|
||||
if (curl_url_get(curlu, CURLUPART_HOST, &host, 0) != CURLUE_OK) {
|
||||
curl_url_cleanup(curlu);
|
||||
@@ -258,5 +232,4 @@ namespace http {
|
||||
curl_url_cleanup(curlu);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace http
|
||||
|
||||
@@ -4,30 +4,28 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <curl/curl.h>
|
||||
|
||||
// local includes
|
||||
#include "network.h"
|
||||
#include "thread_safe.h"
|
||||
|
||||
namespace http {
|
||||
|
||||
int
|
||||
init();
|
||||
int
|
||||
create_creds(const std::string &pkey, const std::string &cert);
|
||||
int
|
||||
save_user_creds(
|
||||
int init();
|
||||
int create_creds(const std::string &pkey, const std::string &cert);
|
||||
int save_user_creds(
|
||||
const std::string &file,
|
||||
const std::string &username,
|
||||
const std::string &password,
|
||||
bool run_our_mouth = false);
|
||||
bool run_our_mouth = false
|
||||
);
|
||||
|
||||
int
|
||||
reload_user_creds(const std::string &file);
|
||||
bool
|
||||
download_file(const std::string &url, const std::string &file);
|
||||
std::string
|
||||
url_escape(const std::string &url);
|
||||
std::string
|
||||
url_get_host(const std::string &url);
|
||||
int reload_user_creds(const std::string &file);
|
||||
bool download_file(const std::string &url, const std::string &file, long ssl_version = CURL_SSLVERSION_TLSv1_3);
|
||||
std::string url_escape(const std::string &url);
|
||||
std::string url_get_host(const std::string &url);
|
||||
|
||||
extern std::string unique_id;
|
||||
extern net::net_e origin_web_ui_allowed;
|
||||
|
||||
323
src/input.cpp
323
src/input.cpp
@@ -2,13 +2,13 @@
|
||||
* @file src/input.cpp
|
||||
* @brief Definitions for gamepad, keyboard, and mouse input handling.
|
||||
*/
|
||||
// define uint32_t for <moonlight-common-c/src/Input.h>
|
||||
#include <cstdint>
|
||||
extern "C" {
|
||||
#include <moonlight-common-c/src/Input.h>
|
||||
#include <moonlight-common-c/src/Limelight.h>
|
||||
}
|
||||
|
||||
// standard includes
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
@@ -16,6 +16,10 @@ extern "C" {
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
// lib includes
|
||||
#include <boost/endian/buffers.hpp>
|
||||
|
||||
// local includes
|
||||
#include "config.h"
|
||||
#include "globals.h"
|
||||
#include "input.h"
|
||||
@@ -24,14 +28,13 @@ extern "C" {
|
||||
#include "thread_pool.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include <boost/endian/buffers.hpp>
|
||||
|
||||
// Win32 WHEEL_DELTA constant
|
||||
#ifndef WHEEL_DELTA
|
||||
#define WHEEL_DELTA 120
|
||||
#endif
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace input {
|
||||
|
||||
constexpr auto MAX_GAMEPADS = std::min((std::size_t) platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8);
|
||||
@@ -54,9 +57,8 @@ namespace input {
|
||||
UP ///< Button is up
|
||||
};
|
||||
|
||||
template <std::size_t N>
|
||||
int
|
||||
alloc_id(std::bitset<N> &gamepad_mask) {
|
||||
template<std::size_t N>
|
||||
int alloc_id(std::bitset<N> &gamepad_mask) {
|
||||
for (int x = 0; x < gamepad_mask.size(); ++x) {
|
||||
if (!gamepad_mask[x]) {
|
||||
gamepad_mask[x] = true;
|
||||
@@ -67,23 +69,22 @@ namespace input {
|
||||
return -1;
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
void
|
||||
free_id(std::bitset<N> &gamepad_mask, int id) {
|
||||
template<std::size_t N>
|
||||
void free_id(std::bitset<N> &gamepad_mask, int id) {
|
||||
gamepad_mask[id] = false;
|
||||
}
|
||||
|
||||
typedef uint32_t key_press_id_t;
|
||||
key_press_id_t
|
||||
make_kpid(uint16_t vk, uint8_t flags) {
|
||||
|
||||
key_press_id_t make_kpid(uint16_t vk, uint8_t flags) {
|
||||
return (key_press_id_t) vk << 8 | flags;
|
||||
}
|
||||
uint16_t
|
||||
vk_from_kpid(key_press_id_t kpid) {
|
||||
|
||||
uint16_t vk_from_kpid(key_press_id_t kpid) {
|
||||
return kpid >> 8;
|
||||
}
|
||||
uint8_t
|
||||
flags_from_kpid(key_press_id_t kpid) {
|
||||
|
||||
uint8_t flags_from_kpid(key_press_id_t kpid) {
|
||||
return kpid & 0xFF;
|
||||
}
|
||||
|
||||
@@ -92,8 +93,7 @@ namespace input {
|
||||
* @param f Netfloat value.
|
||||
* @return The native endianness float value.
|
||||
*/
|
||||
float
|
||||
from_netfloat(netfloat f) {
|
||||
float from_netfloat(netfloat f) {
|
||||
return boost::endian::endian_load<float, sizeof(float), boost::endian::order::little>(f);
|
||||
}
|
||||
|
||||
@@ -104,8 +104,7 @@ namespace input {
|
||||
* @param max The maximum value for clamping.
|
||||
* @return Clamped native endianess float value.
|
||||
*/
|
||||
float
|
||||
from_clamped_netfloat(netfloat f, float min, float max) {
|
||||
float from_clamped_netfloat(netfloat f, float min, float max) {
|
||||
return std::clamp(from_netfloat(f), min, max);
|
||||
}
|
||||
|
||||
@@ -116,16 +115,21 @@ namespace input {
|
||||
static platf::input_t platf_input;
|
||||
static std::bitset<platf::MAX_GAMEPADS> gamepadMask {};
|
||||
|
||||
void
|
||||
free_gamepad(platf::input_t &platf_input, int id) {
|
||||
void free_gamepad(platf::input_t &platf_input, int id) {
|
||||
platf::gamepad_update(platf_input, id, platf::gamepad_state_t {});
|
||||
platf::free_gamepad(platf_input, id);
|
||||
|
||||
free_id(gamepadMask, id);
|
||||
}
|
||||
|
||||
struct gamepad_t {
|
||||
gamepad_t():
|
||||
gamepad_state {}, back_timeout_id {}, id { -1 }, back_button_state { button_state_e::NONE } {}
|
||||
gamepad_state {},
|
||||
back_timeout_id {},
|
||||
id {-1},
|
||||
back_button_state {button_state_e::NONE} {
|
||||
}
|
||||
|
||||
~gamepad_t() {
|
||||
if (id >= 0) {
|
||||
task_pool.push([id = this->id]() {
|
||||
@@ -158,16 +162,18 @@ namespace input {
|
||||
|
||||
input_t(
|
||||
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event,
|
||||
platf::feedback_queue_t feedback_queue):
|
||||
platf::feedback_queue_t feedback_queue
|
||||
):
|
||||
shortcutFlags {},
|
||||
gamepads(MAX_GAMEPADS),
|
||||
client_context { platf::allocate_client_input_context(platf_input) },
|
||||
touch_port_event { std::move(touch_port_event) },
|
||||
feedback_queue { std::move(feedback_queue) },
|
||||
client_context {platf::allocate_client_input_context(platf_input)},
|
||||
touch_port_event {std::move(touch_port_event)},
|
||||
feedback_queue {std::move(feedback_queue)},
|
||||
mouse_left_button_timeout {},
|
||||
touch_port { { 0, 0, 0, 0 }, 0, 0, 1.0f },
|
||||
touch_port {{0, 0, 0, 0}, 0, 0, 1.0f},
|
||||
accumulated_vscroll_delta {},
|
||||
accumulated_hscroll_delta {} {}
|
||||
accumulated_hscroll_delta {} {
|
||||
}
|
||||
|
||||
// Keep track of alt+ctrl+shift key combo
|
||||
int shortcutFlags;
|
||||
@@ -194,8 +200,7 @@ namespace input {
|
||||
* @param keyCode The VKEY code
|
||||
* @return 0 if no shortcut applied, > 0 if shortcut applied.
|
||||
*/
|
||||
inline int
|
||||
apply_shortcut(short keyCode) {
|
||||
inline int apply_shortcut(short keyCode) {
|
||||
constexpr auto VK_F1 = 0x70;
|
||||
constexpr auto VK_F13 = 0x7C;
|
||||
|
||||
@@ -215,8 +220,7 @@ namespace input {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_REL_MOUSE_MOVE_PACKET packet) {
|
||||
void print(PNV_REL_MOUSE_MOVE_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin relative mouse move packet--"sv << std::endl
|
||||
<< "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl
|
||||
@@ -224,8 +228,7 @@ namespace input {
|
||||
<< "--end relative mouse move packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
|
||||
void print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin absolute mouse move packet--"sv << std::endl
|
||||
<< "x ["sv << util::endian::big(packet->x) << ']' << std::endl
|
||||
@@ -235,8 +238,7 @@ namespace input {
|
||||
<< "--end absolute mouse move packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
void print(PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin mouse button packet--"sv << std::endl
|
||||
<< "action ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl
|
||||
@@ -244,24 +246,21 @@ namespace input {
|
||||
<< "--end mouse button packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_SCROLL_PACKET packet) {
|
||||
void print(PNV_SCROLL_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin mouse scroll packet--"sv << std::endl
|
||||
<< "scrollAmt1 ["sv << util::endian::big(packet->scrollAmt1) << ']' << std::endl
|
||||
<< "--end mouse scroll packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PSS_HSCROLL_PACKET packet) {
|
||||
void print(PSS_HSCROLL_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin mouse hscroll packet--"sv << std::endl
|
||||
<< "scrollAmount ["sv << util::endian::big(packet->scrollAmount) << ']' << std::endl
|
||||
<< "--end mouse hscroll packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_KEYBOARD_PACKET packet) {
|
||||
void print(PNV_KEYBOARD_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin keyboard packet--"sv << std::endl
|
||||
<< "keyAction ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl
|
||||
@@ -271,8 +270,7 @@ namespace input {
|
||||
<< "--end keyboard packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_UNICODE_PACKET packet) {
|
||||
void print(PNV_UNICODE_PACKET packet) {
|
||||
std::string text(packet->text, util::endian::big(packet->header.size) - sizeof(packet->header.magic));
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin unicode packet--"sv << std::endl
|
||||
@@ -280,8 +278,7 @@ namespace input {
|
||||
<< "--end unicode packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
void print(PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
// Moonlight spams controller packet even when not necessary
|
||||
BOOST_LOG(verbose)
|
||||
<< "--begin controller packet--"sv << std::endl
|
||||
@@ -301,8 +298,7 @@ namespace input {
|
||||
* @brief Prints a touch packet.
|
||||
* @param packet The touch packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_TOUCH_PACKET packet) {
|
||||
void print(PSS_TOUCH_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin touch packet--"sv << std::endl
|
||||
<< "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl
|
||||
@@ -320,8 +316,7 @@ namespace input {
|
||||
* @brief Prints a pen packet.
|
||||
* @param packet The pen packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_PEN_PACKET packet) {
|
||||
void print(PSS_PEN_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin pen packet--"sv << std::endl
|
||||
<< "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl
|
||||
@@ -341,8 +336,7 @@ namespace input {
|
||||
* @brief Prints a controller arrival packet.
|
||||
* @param packet The controller arrival packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_CONTROLLER_ARRIVAL_PACKET packet) {
|
||||
void print(PSS_CONTROLLER_ARRIVAL_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin controller arrival packet--"sv << std::endl
|
||||
<< "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl
|
||||
@@ -356,8 +350,7 @@ namespace input {
|
||||
* @brief Prints a controller touch packet.
|
||||
* @param packet The controller touch packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_CONTROLLER_TOUCH_PACKET packet) {
|
||||
void print(PSS_CONTROLLER_TOUCH_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin controller touch packet--"sv << std::endl
|
||||
<< "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl
|
||||
@@ -373,8 +366,7 @@ namespace input {
|
||||
* @brief Prints a controller motion packet.
|
||||
* @param packet The controller motion packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_CONTROLLER_MOTION_PACKET packet) {
|
||||
void print(PSS_CONTROLLER_MOTION_PACKET packet) {
|
||||
BOOST_LOG(verbose)
|
||||
<< "--begin controller motion packet--"sv << std::endl
|
||||
<< "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl
|
||||
@@ -389,8 +381,7 @@ namespace input {
|
||||
* @brief Prints a controller battery packet.
|
||||
* @param packet The controller battery packet.
|
||||
*/
|
||||
void
|
||||
print(PSS_CONTROLLER_BATTERY_PACKET packet) {
|
||||
void print(PSS_CONTROLLER_BATTERY_PACKET packet) {
|
||||
BOOST_LOG(verbose)
|
||||
<< "--begin controller battery packet--"sv << std::endl
|
||||
<< "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl
|
||||
@@ -399,8 +390,7 @@ namespace input {
|
||||
<< "--end controller battery packet--"sv;
|
||||
}
|
||||
|
||||
void
|
||||
print(void *payload) {
|
||||
void print(void *payload) {
|
||||
auto header = (PNV_INPUT_HEADER) payload;
|
||||
|
||||
switch (util::endian::little(header->magic)) {
|
||||
@@ -451,8 +441,7 @@ namespace input {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
@@ -468,8 +457,7 @@ namespace input {
|
||||
* @param size The size of the client's surface containing the value.
|
||||
* @return The host-relative coordinate pair if a touchport is available.
|
||||
*/
|
||||
std::optional<std::pair<float, float>>
|
||||
client_to_touchport(std::shared_ptr<input_t> &input, const std::pair<float, float> &val, const std::pair<float, float> &size) {
|
||||
std::optional<std::pair<float, float>> client_to_touchport(std::shared_ptr<input_t> &input, const std::pair<float, float> &val, const std::pair<float, float> &size) {
|
||||
auto &touch_port_event = input->touch_port_event;
|
||||
auto &touch_port = input->touch_port;
|
||||
if (touch_port_event->peek()) {
|
||||
@@ -492,7 +480,7 @@ namespace input {
|
||||
x = std::clamp(x, offsetX, (size.first * scalarX) - offsetX);
|
||||
y = std::clamp(y, offsetY, (size.second * scalarY) - offsetY);
|
||||
|
||||
return std::pair { (x - offsetX) * touch_port.scalar_inv, (y - offsetY) * touch_port.scalar_inv };
|
||||
return std::pair {(x - offsetX) * touch_port.scalar_inv, (y - offsetY) * touch_port.scalar_inv};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -502,8 +490,7 @@ namespace input {
|
||||
* @param scalar The scalar cartesian coordinate pair.
|
||||
* @return The scaled radial coordinate.
|
||||
*/
|
||||
float
|
||||
multiply_polar_by_cartesian_scalar(float r, float angle, const std::pair<float, float> &scalar) {
|
||||
float multiply_polar_by_cartesian_scalar(float r, float angle, const std::pair<float, float> &scalar) {
|
||||
// Convert polar to cartesian coordinates
|
||||
float x = r * std::cos(angle);
|
||||
float y = r * std::sin(angle);
|
||||
@@ -516,8 +503,7 @@ namespace input {
|
||||
return std::sqrt(std::pow(x, 2) + std::pow(y, 2));
|
||||
}
|
||||
|
||||
std::pair<float, float>
|
||||
scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar) {
|
||||
std::pair<float, float> scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar) {
|
||||
// If the rotation is unknown, we'll just scale both axes equally by using
|
||||
// a 45-degree angle for our scaling calculations
|
||||
float angle = rotation == LI_ROT_UNKNOWN ? (M_PI / 4) : (rotation * (M_PI / 180));
|
||||
@@ -527,11 +513,10 @@ namespace input {
|
||||
float minor = val.second != 0.0f ? val.second : val.first;
|
||||
|
||||
// The minor axis is perpendicular to major axis so the angle must be rotated by 90 degrees
|
||||
return { multiply_polar_by_cartesian_scalar(major, angle, scalar), multiply_polar_by_cartesian_scalar(minor, angle + (M_PI / 2), scalar) };
|
||||
return {multiply_polar_by_cartesian_scalar(major, angle, scalar), multiply_polar_by_cartesian_scalar(minor, angle + (M_PI / 2), scalar)};
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
@@ -554,22 +539,23 @@ namespace input {
|
||||
auto width = (float) util::endian::big(packet->width);
|
||||
auto height = (float) util::endian::big(packet->height);
|
||||
|
||||
auto tpcoords = client_to_touchport(input, { x, y }, { width, height });
|
||||
auto tpcoords = client_to_touchport(input, {x, y}, {width, height});
|
||||
if (!tpcoords) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &touch_port = input->touch_port;
|
||||
platf::touch_port_t abs_port {
|
||||
touch_port.offset_x, touch_port.offset_y,
|
||||
touch_port.env_width, touch_port.env_height
|
||||
touch_port.offset_x,
|
||||
touch_port.offset_y,
|
||||
touch_port.env_width,
|
||||
touch_port.env_height
|
||||
};
|
||||
|
||||
platf::abs_mouse(platf_input, abs_port, tpcoords->first, tpcoords->second);
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
@@ -617,7 +603,8 @@ namespace input {
|
||||
}
|
||||
if (
|
||||
button == BUTTON_RIGHT && !release &&
|
||||
input->mouse_left_button_timeout > DISABLE_LEFT_BUTTON_DELAY) {
|
||||
input->mouse_left_button_timeout > DISABLE_LEFT_BUTTON_DELAY
|
||||
) {
|
||||
platf::button_mouse(platf_input, BUTTON_RIGHT, false);
|
||||
platf::button_mouse(platf_input, BUTTON_RIGHT, true);
|
||||
|
||||
@@ -629,8 +616,7 @@ namespace input {
|
||||
platf::button_mouse(platf_input, button, release);
|
||||
}
|
||||
|
||||
short
|
||||
map_keycode(short keycode) {
|
||||
short map_keycode(short keycode) {
|
||||
auto it = config::input.keybindings.find(keycode);
|
||||
if (it != std::end(config::input.keybindings)) {
|
||||
return it->second;
|
||||
@@ -642,16 +628,14 @@ namespace input {
|
||||
/**
|
||||
* @brief Update flags for keyboard shortcut combo's
|
||||
*/
|
||||
inline void
|
||||
update_shortcutFlags(int *flags, short keyCode, bool release) {
|
||||
inline void update_shortcutFlags(int *flags, short keyCode, bool release) {
|
||||
switch (keyCode) {
|
||||
case VKEY_SHIFT:
|
||||
case VKEY_LSHIFT:
|
||||
case VKEY_RSHIFT:
|
||||
if (release) {
|
||||
*flags &= ~input_t::SHIFT;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
*flags |= input_t::SHIFT;
|
||||
}
|
||||
break;
|
||||
@@ -660,8 +644,7 @@ namespace input {
|
||||
case VKEY_RCONTROL:
|
||||
if (release) {
|
||||
*flags &= ~input_t::CTRL;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
*flags |= input_t::CTRL;
|
||||
}
|
||||
break;
|
||||
@@ -670,16 +653,14 @@ namespace input {
|
||||
case VKEY_RMENU:
|
||||
if (release) {
|
||||
*flags &= ~input_t::ALT;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
*flags |= input_t::ALT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
is_modifier(uint16_t keyCode) {
|
||||
bool is_modifier(uint16_t keyCode) {
|
||||
switch (keyCode) {
|
||||
case VKEY_SHIFT:
|
||||
case VKEY_LSHIFT:
|
||||
@@ -696,8 +677,7 @@ namespace input {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
|
||||
void send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
|
||||
if (!release) {
|
||||
// Press any synthetic modifiers required for this key
|
||||
if (synthetic_modifiers & MODIFIER_SHIFT) {
|
||||
@@ -727,8 +707,7 @@ namespace input {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
|
||||
void repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
|
||||
// If key no longer pressed, stop repeating
|
||||
if (!key_press[make_kpid(key_code, flags)]) {
|
||||
key_press_repeat_id = nullptr;
|
||||
@@ -740,8 +719,7 @@ namespace input {
|
||||
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags, synthetic_modifiers).task_id;
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
|
||||
if (!config::input.keyboard) {
|
||||
return;
|
||||
}
|
||||
@@ -780,13 +758,11 @@ namespace input {
|
||||
if (config::input.key_repeat_delay.count() > 0) {
|
||||
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, keyCode, packet->flags, synthetic_modifiers).task_id;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Already released
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!release) {
|
||||
} else if (!release) {
|
||||
// Already pressed down key
|
||||
return;
|
||||
}
|
||||
@@ -803,16 +779,14 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The scroll packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_SCROLL_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_SCROLL_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config::input.high_resolution_scrolling) {
|
||||
platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
input->accumulated_vscroll_delta += util::endian::big(packet->scrollAmt1);
|
||||
auto full_ticks = input->accumulated_vscroll_delta / WHEEL_DELTA;
|
||||
if (full_ticks) {
|
||||
@@ -828,16 +802,14 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The scroll packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_HSCROLL_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_HSCROLL_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config::input.high_resolution_scrolling) {
|
||||
platf::hscroll(platf_input, util::endian::big(packet->scrollAmount));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
input->accumulated_hscroll_delta += util::endian::big(packet->scrollAmount);
|
||||
auto full_ticks = input->accumulated_hscroll_delta / WHEEL_DELTA;
|
||||
if (full_ticks) {
|
||||
@@ -848,8 +820,7 @@ namespace input {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(PNV_UNICODE_PACKET packet) {
|
||||
void passthrough(PNV_UNICODE_PACKET packet) {
|
||||
if (!config::input.keyboard) {
|
||||
return;
|
||||
}
|
||||
@@ -863,8 +834,7 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The controller arrival packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_ARRIVAL_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_ARRIVAL_PACKET packet) {
|
||||
if (!config::input.controller) {
|
||||
return;
|
||||
}
|
||||
@@ -891,7 +861,7 @@ namespace input {
|
||||
}
|
||||
|
||||
// Allocate a new gamepad
|
||||
if (platf::alloc_gamepad(platf_input, { id, packet->controllerNumber }, arrival, input->feedback_queue)) {
|
||||
if (platf::alloc_gamepad(platf_input, {id, packet->controllerNumber}, arrival, input->feedback_queue)) {
|
||||
free_id(gamepadMask, id);
|
||||
return;
|
||||
}
|
||||
@@ -904,25 +874,23 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The touch packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_TOUCH_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_TOUCH_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the client normalized coordinates to touchport coordinates
|
||||
auto coords = client_to_touchport(input,
|
||||
{ from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f },
|
||||
{ 65535.f, 65535.f });
|
||||
auto coords = client_to_touchport(input, {from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f, from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f}, {65535.f, 65535.f});
|
||||
if (!coords) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &touch_port = input->touch_port;
|
||||
platf::touch_port_t abs_port {
|
||||
touch_port.offset_x, touch_port.offset_y,
|
||||
touch_port.env_width, touch_port.env_height
|
||||
touch_port.offset_x,
|
||||
touch_port.offset_y,
|
||||
touch_port.env_width,
|
||||
touch_port.env_height
|
||||
};
|
||||
|
||||
// Renormalize the coordinates
|
||||
@@ -937,10 +905,11 @@ namespace input {
|
||||
|
||||
// Normalize the contact area based on the touchport
|
||||
auto contact_area = scale_client_contact_area(
|
||||
{ from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f },
|
||||
{from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f},
|
||||
rotation,
|
||||
{ abs_port.width / 65535.f, abs_port.height / 65535.f });
|
||||
{abs_port.width / 65535.f, abs_port.height / 65535.f}
|
||||
);
|
||||
|
||||
platf::touch_input_t touch {
|
||||
packet->eventType,
|
||||
@@ -961,25 +930,23 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The pen packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_PEN_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_PEN_PACKET packet) {
|
||||
if (!config::input.mouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the client normalized coordinates to touchport coordinates
|
||||
auto coords = client_to_touchport(input,
|
||||
{ from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f },
|
||||
{ 65535.f, 65535.f });
|
||||
auto coords = client_to_touchport(input, {from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f, from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f}, {65535.f, 65535.f});
|
||||
if (!coords) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &touch_port = input->touch_port;
|
||||
platf::touch_port_t abs_port {
|
||||
touch_port.offset_x, touch_port.offset_y,
|
||||
touch_port.env_width, touch_port.env_height
|
||||
touch_port.offset_x,
|
||||
touch_port.offset_y,
|
||||
touch_port.env_width,
|
||||
touch_port.env_height
|
||||
};
|
||||
|
||||
// Renormalize the coordinates
|
||||
@@ -994,10 +961,11 @@ namespace input {
|
||||
|
||||
// Normalize the contact area based on the touchport
|
||||
auto contact_area = scale_client_contact_area(
|
||||
{ from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f },
|
||||
{from_clamped_netfloat(packet->contactAreaMajor, 0.0f, 1.0f) * 65535.f,
|
||||
from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f},
|
||||
rotation,
|
||||
{ abs_port.width / 65535.f, abs_port.height / 65535.f });
|
||||
{abs_port.width / 65535.f, abs_port.height / 65535.f}
|
||||
);
|
||||
|
||||
platf::pen_input_t pen {
|
||||
packet->eventType,
|
||||
@@ -1020,8 +988,7 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The controller touch packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_TOUCH_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_TOUCH_PACKET packet) {
|
||||
if (!config::input.controller) {
|
||||
return;
|
||||
}
|
||||
@@ -1038,7 +1005,7 @@ namespace input {
|
||||
}
|
||||
|
||||
platf::gamepad_touch_t touch {
|
||||
{ gamepad.id, packet->controllerNumber },
|
||||
{gamepad.id, packet->controllerNumber},
|
||||
packet->eventType,
|
||||
util::endian::little(packet->pointerId),
|
||||
from_clamped_netfloat(packet->x, 0.0f, 1.0f),
|
||||
@@ -1054,8 +1021,7 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The controller motion packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_MOTION_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_MOTION_PACKET packet) {
|
||||
if (!config::input.controller) {
|
||||
return;
|
||||
}
|
||||
@@ -1072,7 +1038,7 @@ namespace input {
|
||||
}
|
||||
|
||||
platf::gamepad_motion_t motion {
|
||||
{ gamepad.id, packet->controllerNumber },
|
||||
{gamepad.id, packet->controllerNumber},
|
||||
packet->motionType,
|
||||
from_netfloat(packet->x),
|
||||
from_netfloat(packet->y),
|
||||
@@ -1087,8 +1053,7 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param packet The controller battery packet.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_BATTERY_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_BATTERY_PACKET packet) {
|
||||
if (!config::input.controller) {
|
||||
return;
|
||||
}
|
||||
@@ -1105,7 +1070,7 @@ namespace input {
|
||||
}
|
||||
|
||||
platf::gamepad_battery_t battery {
|
||||
{ gamepad.id, packet->controllerNumber },
|
||||
{gamepad.id, packet->controllerNumber},
|
||||
packet->batteryState,
|
||||
packet->batteryPercentage
|
||||
};
|
||||
@@ -1113,8 +1078,7 @@ namespace input {
|
||||
platf::gamepad_battery(platf_input, battery);
|
||||
}
|
||||
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
if (!config::input.controller) {
|
||||
return;
|
||||
}
|
||||
@@ -1135,14 +1099,13 @@ namespace input {
|
||||
return;
|
||||
}
|
||||
|
||||
if (platf::alloc_gamepad(platf_input, { id, (uint8_t) packet->controllerNumber }, {}, input->feedback_queue)) {
|
||||
if (platf::alloc_gamepad(platf_input, {id, (uint8_t) packet->controllerNumber}, {}, input->feedback_queue)) {
|
||||
free_id(gamepadMask, id);
|
||||
return;
|
||||
}
|
||||
|
||||
gamepad.id = id;
|
||||
}
|
||||
else if (!(packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id >= 0) {
|
||||
} else if (!(packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id >= 0) {
|
||||
// If this is the final event for a gamepad being removed, free the gamepad and return.
|
||||
free_gamepad(platf_input, gamepad.id);
|
||||
gamepad.id = -1;
|
||||
@@ -1219,8 +1182,7 @@ namespace input {
|
||||
|
||||
gamepad.back_timeout_id = task_pool.pushDelayed(std::move(f), config::input.back_button_timeout).task_id;
|
||||
}
|
||||
}
|
||||
else if (gamepad.back_timeout_id) {
|
||||
} else if (gamepad.back_timeout_id) {
|
||||
task_pool.cancel(gamepad.back_timeout_id);
|
||||
gamepad.back_timeout_id = nullptr;
|
||||
}
|
||||
@@ -1243,8 +1205,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_REL_MOUSE_MOVE_PACKET dest, PNV_REL_MOUSE_MOVE_PACKET src) {
|
||||
batch_result_e batch(PNV_REL_MOUSE_MOVE_PACKET dest, PNV_REL_MOUSE_MOVE_PACKET src) {
|
||||
short deltaX, deltaY;
|
||||
|
||||
// Batching is safe as long as the result doesn't overflow a 16-bit integer
|
||||
@@ -1267,8 +1228,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_ABS_MOUSE_MOVE_PACKET dest, PNV_ABS_MOUSE_MOVE_PACKET src) {
|
||||
batch_result_e batch(PNV_ABS_MOUSE_MOVE_PACKET dest, PNV_ABS_MOUSE_MOVE_PACKET src) {
|
||||
// Batching must only happen if the reference width and height don't change
|
||||
if (dest->width != src->width || dest->height != src->height) {
|
||||
return batch_result_e::terminate_batch;
|
||||
@@ -1285,8 +1245,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_SCROLL_PACKET dest, PNV_SCROLL_PACKET src) {
|
||||
batch_result_e batch(PNV_SCROLL_PACKET dest, PNV_SCROLL_PACKET src) {
|
||||
short scrollAmt;
|
||||
|
||||
// Batching is safe as long as the result doesn't overflow a 16-bit integer
|
||||
@@ -1306,8 +1265,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_HSCROLL_PACKET dest, PSS_HSCROLL_PACKET src) {
|
||||
batch_result_e batch(PSS_HSCROLL_PACKET dest, PSS_HSCROLL_PACKET src) {
|
||||
short scrollAmt;
|
||||
|
||||
// Batching is safe as long as the result doesn't overflow a 16-bit integer
|
||||
@@ -1326,8 +1284,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_MULTI_CONTROLLER_PACKET dest, PNV_MULTI_CONTROLLER_PACKET src) {
|
||||
batch_result_e batch(PNV_MULTI_CONTROLLER_PACKET dest, PNV_MULTI_CONTROLLER_PACKET src) {
|
||||
// Do not allow batching if the active controllers change
|
||||
if (dest->activeGamepadMask != src->activeGamepadMask) {
|
||||
return batch_result_e::terminate_batch;
|
||||
@@ -1355,8 +1312,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_TOUCH_PACKET dest, PSS_TOUCH_PACKET src) {
|
||||
batch_result_e batch(PSS_TOUCH_PACKET dest, PSS_TOUCH_PACKET src) {
|
||||
// Only batch hover or move events
|
||||
if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
|
||||
dest->eventType != LI_TOUCH_EVENT_HOVER) {
|
||||
@@ -1390,8 +1346,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_PEN_PACKET dest, PSS_PEN_PACKET src) {
|
||||
batch_result_e batch(PSS_PEN_PACKET dest, PSS_PEN_PACKET src) {
|
||||
// Only batch hover or move events
|
||||
if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
|
||||
dest->eventType != LI_TOUCH_EVENT_HOVER) {
|
||||
@@ -1424,8 +1379,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_CONTROLLER_TOUCH_PACKET dest, PSS_CONTROLLER_TOUCH_PACKET src) {
|
||||
batch_result_e batch(PSS_CONTROLLER_TOUCH_PACKET dest, PSS_CONTROLLER_TOUCH_PACKET src) {
|
||||
// Only batch hover or move events
|
||||
if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
|
||||
dest->eventType != LI_TOUCH_EVENT_HOVER) {
|
||||
@@ -1465,8 +1419,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_CONTROLLER_MOTION_PACKET dest, PSS_CONTROLLER_MOTION_PACKET src) {
|
||||
batch_result_e batch(PSS_CONTROLLER_MOTION_PACKET dest, PSS_CONTROLLER_MOTION_PACKET src) {
|
||||
// We can only batch entries for the same controller, but allow batching attempts to continue
|
||||
// in case we have more packets for this controller later in the queue.
|
||||
if (dest->controllerNumber != src->controllerNumber) {
|
||||
@@ -1489,8 +1442,7 @@ namespace input {
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_INPUT_HEADER dest, PNV_INPUT_HEADER src) {
|
||||
batch_result_e batch(PNV_INPUT_HEADER dest, PNV_INPUT_HEADER src) {
|
||||
// We can only batch if the packet types are the same
|
||||
if (dest->magic != src->magic) {
|
||||
return batch_result_e::terminate_batch;
|
||||
@@ -1526,8 +1478,7 @@ namespace input {
|
||||
* @brief Called on a thread pool thread to process an input message.
|
||||
* @param input The input context pointer.
|
||||
*/
|
||||
void
|
||||
passthrough_next_message(std::shared_ptr<input_t> input) {
|
||||
void passthrough_next_message(std::shared_ptr<input_t> input) {
|
||||
// 'entry' backs the 'payload' pointer, so they must remain in scope together
|
||||
std::vector<uint8_t> entry;
|
||||
PNV_INPUT_HEADER payload;
|
||||
@@ -1558,12 +1509,10 @@ namespace input {
|
||||
if (batch_result == batch_result_e::terminate_batch) {
|
||||
// Stop batching
|
||||
break;
|
||||
}
|
||||
else if (batch_result == batch_result_e::batched) {
|
||||
} else if (batch_result == batch_result_e::batched) {
|
||||
// Erase this entry since it was batched
|
||||
i = input->input_queue.erase(i);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// We couldn't batch this entry, but try to batch later entries.
|
||||
i++;
|
||||
}
|
||||
@@ -1627,8 +1576,7 @@ namespace input {
|
||||
* @param input The input context pointer.
|
||||
* @param input_data The input message.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data) {
|
||||
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(input->input_queue_lock);
|
||||
input->input_queue.push_back(std::move(input_data));
|
||||
@@ -1636,8 +1584,7 @@ namespace input {
|
||||
task_pool.push(passthrough_next_message, input);
|
||||
}
|
||||
|
||||
void
|
||||
reset(std::shared_ptr<input_t> &input) {
|
||||
void reset(std::shared_ptr<input_t> &input) {
|
||||
task_pool.cancel(key_press_repeat_id);
|
||||
task_pool.cancel(input->mouse_left_button_timeout);
|
||||
|
||||
@@ -1668,15 +1615,13 @@ namespace input {
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t>
|
||||
init() {
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t> init() {
|
||||
platf_input = platf::input();
|
||||
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
bool
|
||||
probe_gamepads() {
|
||||
bool probe_gamepads() {
|
||||
auto input = static_cast<platf::input_t *>(platf_input.get());
|
||||
const auto gamepads = platf::supported_gamepads(input);
|
||||
for (auto &gamepad : gamepads) {
|
||||
@@ -1687,18 +1632,18 @@ namespace input {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<input_t>
|
||||
alloc(safe::mail_t mail) {
|
||||
std::shared_ptr<input_t> alloc(safe::mail_t mail) {
|
||||
auto input = std::make_shared<input_t>(
|
||||
mail->event<input::touch_port_t>(mail::touch_port),
|
||||
mail->queue<platf::gamepad_feedback_msg_t>(mail::gamepad_feedback));
|
||||
mail->queue<platf::gamepad_feedback_msg_t>(mail::gamepad_feedback)
|
||||
);
|
||||
|
||||
// Workaround to ensure new frames will be captured when a client connects
|
||||
task_pool.pushDelayed([]() {
|
||||
platf::move_mouse(platf_input, 1, 1);
|
||||
platf::move_mouse(platf_input, -1, -1);
|
||||
},
|
||||
100ms);
|
||||
100ms);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
26
src/input.h
26
src/input.h
@@ -4,29 +4,25 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <functional>
|
||||
|
||||
// local includes
|
||||
#include "platform/common.h"
|
||||
#include "thread_safe.h"
|
||||
|
||||
namespace input {
|
||||
struct input_t;
|
||||
|
||||
void
|
||||
print(void *input);
|
||||
void
|
||||
reset(std::shared_ptr<input_t> &input);
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
|
||||
void print(void *input);
|
||||
void reset(std::shared_ptr<input_t> &input);
|
||||
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
|
||||
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t>
|
||||
init();
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t> init();
|
||||
|
||||
bool
|
||||
probe_gamepads();
|
||||
bool probe_gamepads();
|
||||
|
||||
std::shared_ptr<input_t>
|
||||
alloc(safe::mail_t mail);
|
||||
std::shared_ptr<input_t> alloc(safe::mail_t mail);
|
||||
|
||||
struct touch_port_t: public platf::touch_port_t {
|
||||
int env_width, env_height;
|
||||
@@ -36,8 +32,7 @@ namespace input {
|
||||
|
||||
float scalar_inv;
|
||||
|
||||
explicit
|
||||
operator bool() const {
|
||||
explicit operator bool() const {
|
||||
return width != 0 && height != 0 && env_width != 0 && env_height != 0;
|
||||
}
|
||||
};
|
||||
@@ -49,6 +44,5 @@ namespace input {
|
||||
* @param scalar The scalar cartesian coordinate pair.
|
||||
* @return The major and minor axis pair.
|
||||
*/
|
||||
std::pair<float, float>
|
||||
scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar);
|
||||
std::pair<float, float> scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar);
|
||||
} // namespace input
|
||||
|
||||
@@ -47,15 +47,13 @@ namespace logging {
|
||||
deinit();
|
||||
}
|
||||
|
||||
void
|
||||
deinit() {
|
||||
void deinit() {
|
||||
log_flush();
|
||||
bl::core::get()->remove_sink(sink);
|
||||
sink.reset();
|
||||
}
|
||||
|
||||
void
|
||||
formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os) {
|
||||
void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os) {
|
||||
constexpr const char *message = "Message";
|
||||
constexpr const char *severity = "Severity";
|
||||
|
||||
@@ -90,7 +88,8 @@ namespace logging {
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - std::chrono::time_point_cast<std::chrono::seconds>(now));
|
||||
now - std::chrono::time_point_cast<std::chrono::seconds>(now)
|
||||
);
|
||||
|
||||
auto t = std::chrono::system_clock::to_time_t(now);
|
||||
auto lt = *std::localtime(&t);
|
||||
@@ -99,8 +98,7 @@ namespace logging {
|
||||
<< log_type << view.attribute_values()[message].extract<std::string>();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<deinit_t>
|
||||
init(int min_log_level, const std::string &log_file) {
|
||||
[[nodiscard]] std::unique_ptr<deinit_t> init(int min_log_level, const std::string &log_file) {
|
||||
if (sink) {
|
||||
// Deinitialize the logging system before reinitializing it. This can probably only ever be hit in tests.
|
||||
deinit();
|
||||
@@ -112,7 +110,7 @@ namespace logging {
|
||||
sink = boost::make_shared<text_sink>();
|
||||
|
||||
#ifndef SUNSHINE_TESTS
|
||||
boost::shared_ptr<std::ostream> stream { &std::cout, boost::null_deleter() };
|
||||
boost::shared_ptr<std::ostream> stream {&std::cout, boost::null_deleter()};
|
||||
sink->locked_backend()->add_stream(stream);
|
||||
#endif
|
||||
sink->locked_backend()->add_stream(boost::make_shared<std::ofstream>(log_file));
|
||||
@@ -127,12 +125,10 @@ namespace logging {
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
void
|
||||
setup_av_logging(int min_log_level) {
|
||||
void setup_av_logging(int min_log_level) {
|
||||
if (min_log_level >= 1) {
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
av_log_set_level(AV_LOG_DEBUG);
|
||||
}
|
||||
av_log_set_callback([](void *ptr, int level, const char *fmt, va_list vl) {
|
||||
@@ -144,28 +140,23 @@ namespace logging {
|
||||
// We print AV_LOG_FATAL at the error level. FFmpeg prints things as fatal that
|
||||
// are expected in some cases, such as lack of codec support or similar things.
|
||||
BOOST_LOG(error) << buffer;
|
||||
}
|
||||
else if (level <= AV_LOG_WARNING) {
|
||||
} else if (level <= AV_LOG_WARNING) {
|
||||
BOOST_LOG(warning) << buffer;
|
||||
}
|
||||
else if (level <= AV_LOG_INFO) {
|
||||
} else if (level <= AV_LOG_INFO) {
|
||||
BOOST_LOG(info) << buffer;
|
||||
}
|
||||
else if (level <= AV_LOG_VERBOSE) {
|
||||
} else if (level <= AV_LOG_VERBOSE) {
|
||||
// AV_LOG_VERBOSE is less verbose than AV_LOG_DEBUG
|
||||
BOOST_LOG(debug) << buffer;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(verbose) << buffer;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
setup_libdisplaydevice_logging(int min_log_level) {
|
||||
constexpr int min_level { static_cast<int>(display_device::Logger::LogLevel::verbose) };
|
||||
constexpr int max_level { static_cast<int>(display_device::Logger::LogLevel::fatal) };
|
||||
const auto log_level { static_cast<display_device::Logger::LogLevel>(std::min(std::max(min_level, min_log_level), max_level)) };
|
||||
void setup_libdisplaydevice_logging(int min_log_level) {
|
||||
constexpr int min_level {static_cast<int>(display_device::Logger::LogLevel::verbose)};
|
||||
constexpr int max_level {static_cast<int>(display_device::Logger::LogLevel::fatal)};
|
||||
const auto log_level {static_cast<display_device::Logger::LogLevel>(std::min(std::max(min_level, min_log_level), max_level))};
|
||||
|
||||
display_device::Logger::get().setLogLevel(log_level);
|
||||
display_device::Logger::get().setCustomCallback([](const display_device::Logger::LogLevel level, const std::string &message) {
|
||||
@@ -192,15 +183,13 @@ namespace logging {
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
log_flush() {
|
||||
void log_flush() {
|
||||
if (sink) {
|
||||
sink->flush();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
print_help(const char *name) {
|
||||
void print_help(const char *name) {
|
||||
std::cout
|
||||
<< "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl
|
||||
<< " Any configurable option can be overwritten with: \"name=value\""sv << std::endl
|
||||
@@ -220,13 +209,11 @@ namespace logging {
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
std::string
|
||||
bracket(const std::string &input) {
|
||||
std::string bracket(const std::string &input) {
|
||||
return "["s + input + "]"s;
|
||||
}
|
||||
|
||||
std::wstring
|
||||
bracket(const std::wstring &input) {
|
||||
std::wstring bracket(const std::wstring &input) {
|
||||
return L"["s + input + L"]"s;
|
||||
}
|
||||
|
||||
|
||||
103
src/logging.h
103
src/logging.h
@@ -41,11 +41,9 @@ namespace logging {
|
||||
* deinit();
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
deinit();
|
||||
void deinit();
|
||||
|
||||
void
|
||||
formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os);
|
||||
void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os);
|
||||
|
||||
/**
|
||||
* @brief Initialize the logging system.
|
||||
@@ -56,22 +54,19 @@ namespace logging {
|
||||
* log_init(2, "sunshine.log");
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] std::unique_ptr<deinit_t>
|
||||
init(int min_log_level, const std::string &log_file);
|
||||
[[nodiscard]] std::unique_ptr<deinit_t> init(int min_log_level, const std::string &log_file);
|
||||
|
||||
/**
|
||||
* @brief Setup AV logging.
|
||||
* @param min_log_level The log level.
|
||||
*/
|
||||
void
|
||||
setup_av_logging(int min_log_level);
|
||||
void setup_av_logging(int min_log_level);
|
||||
|
||||
/**
|
||||
* @brief Setup logging for libdisplaydevice.
|
||||
* @param min_log_level The log level.
|
||||
*/
|
||||
void
|
||||
setup_libdisplaydevice_logging(int min_log_level);
|
||||
void setup_libdisplaydevice_logging(int min_log_level);
|
||||
|
||||
/**
|
||||
* @brief Flush the log.
|
||||
@@ -79,8 +74,7 @@ namespace logging {
|
||||
* log_flush();
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
log_flush();
|
||||
void log_flush();
|
||||
|
||||
/**
|
||||
* @brief Print help to stdout.
|
||||
@@ -89,8 +83,7 @@ namespace logging {
|
||||
* print_help("sunshine");
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
print_help(const char *name);
|
||||
void print_help(const char *name);
|
||||
|
||||
/**
|
||||
* @brief A helper class for tracking and logging numerical values across a period of time
|
||||
@@ -105,28 +98,24 @@ namespace logging {
|
||||
* // [2024:01:01:12:00:00]: Debug: Test time value (min/max/avg): 1ms/3ms/2.00ms
|
||||
* @examples_end
|
||||
*/
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
class min_max_avg_periodic_logger {
|
||||
public:
|
||||
min_max_avg_periodic_logger(boost::log::sources::severity_logger<int> &severity,
|
||||
std::string_view message,
|
||||
std::string_view units,
|
||||
std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
|
||||
min_max_avg_periodic_logger(boost::log::sources::severity_logger<int> &severity, std::string_view message, std::string_view units, std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
|
||||
severity(severity),
|
||||
message(message),
|
||||
units(units),
|
||||
interval(interval_in_seconds),
|
||||
enabled(config::sunshine.min_log_level <= severity.default_severity()) {}
|
||||
enabled(config::sunshine.min_log_level <= severity.default_severity()) {
|
||||
}
|
||||
|
||||
void
|
||||
collect_and_log(const T &value) {
|
||||
void collect_and_log(const T &value) {
|
||||
if (enabled) {
|
||||
auto print_info = [&](const T &min_value, const T &max_value, double avg_value) {
|
||||
auto f = stat_trackers::two_digits_after_decimal();
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
BOOST_LOG(severity.get()) << message << " (min/max/avg): " << f % min_value << units << "/" << f % max_value << units << "/" << f % avg_value << units;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(severity.get()) << message << " (min/max/avg): " << min_value << units << "/" << max_value << units << "/" << f % avg_value << units;
|
||||
}
|
||||
};
|
||||
@@ -134,18 +123,19 @@ namespace logging {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
collect_and_log(std::function<T()> func) {
|
||||
if (enabled) collect_and_log(func());
|
||||
void collect_and_log(std::function<T()> func) {
|
||||
if (enabled) {
|
||||
collect_and_log(func());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
reset() {
|
||||
if (enabled) tracker.reset();
|
||||
void reset() {
|
||||
if (enabled) {
|
||||
tracker.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
is_enabled() const {
|
||||
bool is_enabled() const {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@@ -175,40 +165,41 @@ namespace logging {
|
||||
*/
|
||||
class time_delta_periodic_logger {
|
||||
public:
|
||||
time_delta_periodic_logger(boost::log::sources::severity_logger<int> &severity,
|
||||
std::string_view message,
|
||||
std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
|
||||
logger(severity, message, "ms", interval_in_seconds) {}
|
||||
|
||||
void
|
||||
first_point(const std::chrono::steady_clock::time_point &point) {
|
||||
if (logger.is_enabled()) point1 = point;
|
||||
time_delta_periodic_logger(boost::log::sources::severity_logger<int> &severity, std::string_view message, std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
|
||||
logger(severity, message, "ms", interval_in_seconds) {
|
||||
}
|
||||
|
||||
void
|
||||
first_point_now() {
|
||||
if (logger.is_enabled()) first_point(std::chrono::steady_clock::now());
|
||||
void first_point(const std::chrono::steady_clock::time_point &point) {
|
||||
if (logger.is_enabled()) {
|
||||
point1 = point;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
second_point_and_log(const std::chrono::steady_clock::time_point &point) {
|
||||
void first_point_now() {
|
||||
if (logger.is_enabled()) {
|
||||
first_point(std::chrono::steady_clock::now());
|
||||
}
|
||||
}
|
||||
|
||||
void second_point_and_log(const std::chrono::steady_clock::time_point &point) {
|
||||
if (logger.is_enabled()) {
|
||||
logger.collect_and_log(std::chrono::duration<double, std::milli>(point - point1).count());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
second_point_now_and_log() {
|
||||
if (logger.is_enabled()) second_point_and_log(std::chrono::steady_clock::now());
|
||||
void second_point_now_and_log() {
|
||||
if (logger.is_enabled()) {
|
||||
second_point_and_log(std::chrono::steady_clock::now());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
reset() {
|
||||
if (logger.is_enabled()) logger.reset();
|
||||
void reset() {
|
||||
if (logger.is_enabled()) {
|
||||
logger.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
is_enabled() const {
|
||||
bool is_enabled() const {
|
||||
return logger.is_enabled();
|
||||
}
|
||||
|
||||
@@ -222,15 +213,13 @@ namespace logging {
|
||||
* @param input Input string.
|
||||
* @return Enclosed string.
|
||||
*/
|
||||
std::string
|
||||
bracket(const std::string &input);
|
||||
std::string bracket(const std::string &input);
|
||||
|
||||
/**
|
||||
* @brief Enclose string in square brackets.
|
||||
* @param input Input string.
|
||||
* @return Enclosed string.
|
||||
*/
|
||||
std::wstring
|
||||
bracket(const std::wstring &input);
|
||||
std::wstring bracket(const std::wstring &input);
|
||||
|
||||
} // namespace logging
|
||||
|
||||
60
src/main.cpp
60
src/main.cpp
@@ -30,31 +30,37 @@ extern "C" {
|
||||
using namespace std::literals;
|
||||
|
||||
std::map<int, std::function<void()>> signal_handlers;
|
||||
void
|
||||
on_signal_forwarder(int sig) {
|
||||
|
||||
void on_signal_forwarder(int sig) {
|
||||
signal_handlers.at(sig)();
|
||||
}
|
||||
|
||||
template <class FN>
|
||||
void
|
||||
on_signal(int sig, FN &&fn) {
|
||||
template<class FN>
|
||||
void on_signal(int sig, FN &&fn) {
|
||||
signal_handlers.emplace(sig, std::forward<FN>(fn));
|
||||
|
||||
std::signal(sig, on_signal_forwarder);
|
||||
}
|
||||
|
||||
std::map<std::string_view, std::function<int(const char *name, int argc, char **argv)>> cmd_to_func {
|
||||
{ "creds"sv, [](const char *name, int argc, char **argv) { return args::creds(name, argc, argv); } },
|
||||
{ "help"sv, [](const char *name, int argc, char **argv) { return args::help(name); } },
|
||||
{ "version"sv, [](const char *name, int argc, char **argv) { return args::version(); } },
|
||||
{"creds"sv, [](const char *name, int argc, char **argv) {
|
||||
return args::creds(name, argc, argv);
|
||||
}},
|
||||
{"help"sv, [](const char *name, int argc, char **argv) {
|
||||
return args::help(name);
|
||||
}},
|
||||
{"version"sv, [](const char *name, int argc, char **argv) {
|
||||
return args::version();
|
||||
}},
|
||||
#ifdef _WIN32
|
||||
{ "restore-nvprefs-undo"sv, [](const char *name, int argc, char **argv) { return args::restore_nvprefs_undo(); } },
|
||||
{"restore-nvprefs-undo"sv, [](const char *name, int argc, char **argv) {
|
||||
return args::restore_nvprefs_undo();
|
||||
}},
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
LRESULT CALLBACK
|
||||
SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
LRESULT CALLBACK SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
switch (uMsg) {
|
||||
case WM_CLOSE:
|
||||
DestroyWindow(hwnd);
|
||||
@@ -62,19 +68,19 @@ SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
case WM_ENDSESSION: {
|
||||
// Terminate ourselves with a blocking exit call
|
||||
std::cout << "Received WM_ENDSESSION"sv << std::endl;
|
||||
lifetime::exit_sunshine(0, false);
|
||||
return 0;
|
||||
}
|
||||
case WM_ENDSESSION:
|
||||
{
|
||||
// Terminate ourselves with a blocking exit call
|
||||
std::cout << "Received WM_ENDSESSION"sv << std::endl;
|
||||
lifetime::exit_sunshine(0, false);
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
WINAPI BOOL
|
||||
ConsoleCtrlHandler(DWORD type) {
|
||||
WINAPI BOOL ConsoleCtrlHandler(DWORD type) {
|
||||
if (type == CTRL_CLOSE_EVENT) {
|
||||
BOOST_LOG(info) << "Console closed handler called";
|
||||
lifetime::exit_sunshine(0, false);
|
||||
@@ -83,8 +89,7 @@ ConsoleCtrlHandler(DWORD type) {
|
||||
}
|
||||
#endif
|
||||
|
||||
int
|
||||
main(int argc, char *argv[]) {
|
||||
int main(int argc, char *argv[]) {
|
||||
lifetime::argv = argv;
|
||||
|
||||
task_pool_util::TaskPool::task_id_t force_shutdown = nullptr;
|
||||
@@ -188,7 +193,8 @@ main(int argc, char *argv[]) {
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
nullptr
|
||||
);
|
||||
|
||||
session_monitor_hwnd_promise.set_value(wnd);
|
||||
|
||||
@@ -216,12 +222,10 @@ main(int argc, char *argv[]) {
|
||||
if (session_monitor_join_thread_future.wait_for(1s) == std::future_status::ready) {
|
||||
session_monitor_thread.join();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "session_monitor_join_thread_future reached timeout";
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "session_monitor_hwnd_future reached timeout";
|
||||
}
|
||||
|
||||
@@ -324,8 +328,8 @@ main(int argc, char *argv[]) {
|
||||
return lifetime::desired_exit_code;
|
||||
}
|
||||
|
||||
std::thread httpThread { nvhttp::start };
|
||||
std::thread configThread { confighttp::start };
|
||||
std::thread httpThread {nvhttp::start};
|
||||
std::thread configThread {confighttp::start};
|
||||
|
||||
#ifdef _WIN32
|
||||
// If we're using the default port and GameStream is enabled, warn the user
|
||||
|
||||
@@ -12,5 +12,4 @@
|
||||
* main(1, const char* args[] = {"sunshine", nullptr});
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
main(int argc, char *argv[]);
|
||||
int main(int argc, char *argv[]);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
@@ -14,7 +15,7 @@ namespace move_by_copy_util {
|
||||
* When a copy is made, it moves the object
|
||||
* This allows you to move an object when a move can't be done.
|
||||
*/
|
||||
template <class T>
|
||||
template<class T>
|
||||
class MoveByCopy {
|
||||
public:
|
||||
typedef T move_type;
|
||||
@@ -24,7 +25,8 @@ namespace move_by_copy_util {
|
||||
|
||||
public:
|
||||
explicit MoveByCopy(move_type &&to_move):
|
||||
_to_move(std::move(to_move)) {}
|
||||
_to_move(std::move(to_move)) {
|
||||
}
|
||||
|
||||
MoveByCopy(MoveByCopy &&other) = default;
|
||||
|
||||
@@ -32,11 +34,9 @@ namespace move_by_copy_util {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
MoveByCopy &
|
||||
operator=(MoveByCopy &&other) = default;
|
||||
MoveByCopy &operator=(MoveByCopy &&other) = default;
|
||||
|
||||
MoveByCopy &
|
||||
operator=(const MoveByCopy &other) {
|
||||
MoveByCopy &operator=(const MoveByCopy &other) {
|
||||
this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move);
|
||||
|
||||
return *this;
|
||||
@@ -47,16 +47,14 @@ namespace move_by_copy_util {
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
MoveByCopy<T>
|
||||
cmove(T &movable) {
|
||||
template<class T>
|
||||
MoveByCopy<T> cmove(T &movable) {
|
||||
return MoveByCopy<T>(std::move(movable));
|
||||
}
|
||||
|
||||
// Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
|
||||
template <class T>
|
||||
MoveByCopy<T>
|
||||
const_cmove(const T &movable) {
|
||||
template<class T>
|
||||
MoveByCopy<T> const_cmove(const T &movable) {
|
||||
return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
|
||||
}
|
||||
} // namespace move_by_copy_util
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
* @file src/network.cpp
|
||||
* @brief Definitions for networking related functions.
|
||||
*/
|
||||
#include "network.h"
|
||||
#include "config.h"
|
||||
#include "logging.h"
|
||||
#include "utility.h"
|
||||
// standard includes
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
// local includes
|
||||
#include "config.h"
|
||||
#include "logging.h"
|
||||
#include "network.h"
|
||||
#include "utility.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace ip = boost::asio::ip;
|
||||
@@ -33,8 +36,7 @@ namespace net {
|
||||
ip::make_network_v6("fe80::/64"sv),
|
||||
};
|
||||
|
||||
net_e
|
||||
from_enum_string(const std::string_view &view) {
|
||||
net_e from_enum_string(const std::string_view &view) {
|
||||
if (view == "wan") {
|
||||
return WAN;
|
||||
}
|
||||
@@ -45,8 +47,7 @@ namespace net {
|
||||
return PC;
|
||||
}
|
||||
|
||||
net_e
|
||||
from_address(const std::string_view &view) {
|
||||
net_e from_address(const std::string_view &view) {
|
||||
auto addr = normalize_address(ip::make_address(view));
|
||||
|
||||
if (addr.is_v6()) {
|
||||
@@ -61,8 +62,7 @@ namespace net {
|
||||
return LAN;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
for (auto &range : pc_ips_v4) {
|
||||
if (range.hosts().find(addr.to_v4()) != range.hosts().end()) {
|
||||
return PC;
|
||||
@@ -79,8 +79,7 @@ namespace net {
|
||||
return WAN;
|
||||
}
|
||||
|
||||
std::string_view
|
||||
to_enum_string(net_e net) {
|
||||
std::string_view to_enum_string(net_e net) {
|
||||
switch (net) {
|
||||
case PC:
|
||||
return "pc"sv;
|
||||
@@ -94,8 +93,7 @@ namespace net {
|
||||
return "wan"sv;
|
||||
}
|
||||
|
||||
af_e
|
||||
af_from_enum_string(const std::string_view &view) {
|
||||
af_e af_from_enum_string(const std::string_view &view) {
|
||||
if (view == "ipv4") {
|
||||
return IPV4;
|
||||
}
|
||||
@@ -107,8 +105,7 @@ namespace net {
|
||||
return BOTH;
|
||||
}
|
||||
|
||||
std::string_view
|
||||
af_to_any_address_string(af_e af) {
|
||||
std::string_view af_to_any_address_string(af_e af) {
|
||||
switch (af) {
|
||||
case IPV4:
|
||||
return "0.0.0.0"sv;
|
||||
@@ -120,8 +117,7 @@ namespace net {
|
||||
return "::"sv;
|
||||
}
|
||||
|
||||
boost::asio::ip::address
|
||||
normalize_address(boost::asio::ip::address address) {
|
||||
boost::asio::ip::address normalize_address(boost::asio::ip::address address) {
|
||||
// Convert IPv6-mapped IPv4 addresses into regular IPv4 addresses
|
||||
if (address.is_v6()) {
|
||||
auto v6 = address.to_v6();
|
||||
@@ -133,37 +129,31 @@ namespace net {
|
||||
return address;
|
||||
}
|
||||
|
||||
std::string
|
||||
addr_to_normalized_string(boost::asio::ip::address address) {
|
||||
std::string addr_to_normalized_string(boost::asio::ip::address address) {
|
||||
return normalize_address(address).to_string();
|
||||
}
|
||||
|
||||
std::string
|
||||
addr_to_url_escaped_string(boost::asio::ip::address address) {
|
||||
std::string addr_to_url_escaped_string(boost::asio::ip::address address) {
|
||||
address = normalize_address(address);
|
||||
if (address.is_v6()) {
|
||||
std::stringstream ss;
|
||||
ss << '[' << address.to_string() << ']';
|
||||
return ss.str();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return address.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
encryption_mode_for_address(boost::asio::ip::address address) {
|
||||
int encryption_mode_for_address(boost::asio::ip::address address) {
|
||||
auto nettype = net::from_address(address.to_string());
|
||||
if (nettype == net::net_e::PC || nettype == net::net_e::LAN) {
|
||||
return config::stream.lan_encryption_mode;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return config::stream.wan_encryption_mode;
|
||||
}
|
||||
}
|
||||
|
||||
host_t
|
||||
host_create(af_e af, ENetAddress &addr, std::uint16_t port) {
|
||||
host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port) {
|
||||
static std::once_flag enet_init_flag;
|
||||
std::call_once(enet_init_flag, []() {
|
||||
enet_initialize();
|
||||
@@ -174,7 +164,7 @@ namespace net {
|
||||
enet_address_set_port(&addr, port);
|
||||
|
||||
// Maximum of 128 clients, which should be enough for anyone
|
||||
auto host = host_t { enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, 128, 0, 0, 0) };
|
||||
auto host = host_t {enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, 128, 0, 0, 0)};
|
||||
|
||||
// Enable opportunistic QoS tagging (automatically disables if the network appears to drop tagged packets)
|
||||
enet_socket_set_option(host->socket, ENET_SOCKOPT_QOS, 1);
|
||||
@@ -182,8 +172,7 @@ namespace net {
|
||||
return host;
|
||||
}
|
||||
|
||||
void
|
||||
free_host(ENetHost *host) {
|
||||
void free_host(ENetHost *host) {
|
||||
std::for_each(host->peers, host->peers + host->peerCount, [](ENetPeer &peer_ref) {
|
||||
ENetPeer *peer = &peer_ref;
|
||||
|
||||
@@ -195,8 +184,7 @@ namespace net {
|
||||
enet_host_destroy(host);
|
||||
}
|
||||
|
||||
std::uint16_t
|
||||
map_port(int port) {
|
||||
std::uint16_t map_port(int port) {
|
||||
// calculate the port from the config port
|
||||
auto mapped_port = (std::uint16_t)((int) config::sunshine.port + port);
|
||||
|
||||
@@ -213,10 +201,9 @@ namespace net {
|
||||
* @param hostname The hostname to use for instance name generation.
|
||||
* @return Hostname-based instance name or "Sunshine" if hostname is invalid.
|
||||
*/
|
||||
std::string
|
||||
mdns_instance_name(const std::string_view &hostname) {
|
||||
std::string mdns_instance_name(const std::string_view &hostname) {
|
||||
// Start with the unmodified hostname
|
||||
std::string instancename { hostname.data(), hostname.size() };
|
||||
std::string instancename {hostname.data(), hostname.size()};
|
||||
|
||||
// Truncate to 63 characters per RFC 6763 section 7.2.
|
||||
if (instancename.size() > 63) {
|
||||
@@ -227,8 +214,7 @@ namespace net {
|
||||
// Replace any spaces with dashes
|
||||
if (instancename[i] == ' ') {
|
||||
instancename[i] = '-';
|
||||
}
|
||||
else if (!std::isalnum(instancename[i]) && instancename[i] != '-') {
|
||||
} else if (!std::isalnum(instancename[i]) && instancename[i] != '-') {
|
||||
// Stop at the first invalid character
|
||||
instancename.resize(i);
|
||||
break;
|
||||
|
||||
@@ -4,18 +4,19 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
// lib includes
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include <enet/enet.h>
|
||||
|
||||
// local includes
|
||||
#include "utility.h"
|
||||
|
||||
namespace net {
|
||||
void
|
||||
free_host(ENetHost *host);
|
||||
void free_host(ENetHost *host);
|
||||
|
||||
/**
|
||||
* @brief Map a specified port based on the base port.
|
||||
@@ -26,8 +27,7 @@ namespace net {
|
||||
* @examples_end
|
||||
* @todo Ensure port is not already in use by another application.
|
||||
*/
|
||||
std::uint16_t
|
||||
map_port(int port);
|
||||
std::uint16_t map_port(int port);
|
||||
|
||||
using host_t = util::safe_ptr<ENetHost, free_host>;
|
||||
using peer_t = ENetPeer *;
|
||||
@@ -44,32 +44,26 @@ namespace net {
|
||||
BOTH ///< IPv4 and IPv6
|
||||
};
|
||||
|
||||
net_e
|
||||
from_enum_string(const std::string_view &view);
|
||||
std::string_view
|
||||
to_enum_string(net_e net);
|
||||
net_e from_enum_string(const std::string_view &view);
|
||||
std::string_view to_enum_string(net_e net);
|
||||
|
||||
net_e
|
||||
from_address(const std::string_view &view);
|
||||
net_e from_address(const std::string_view &view);
|
||||
|
||||
host_t
|
||||
host_create(af_e af, ENetAddress &addr, std::uint16_t port);
|
||||
host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port);
|
||||
|
||||
/**
|
||||
* @brief Get the address family enum value from a string.
|
||||
* @param view The config option value.
|
||||
* @return The address family enum value.
|
||||
*/
|
||||
af_e
|
||||
af_from_enum_string(const std::string_view &view);
|
||||
af_e af_from_enum_string(const std::string_view &view);
|
||||
|
||||
/**
|
||||
* @brief Get the wildcard binding address for a given address family.
|
||||
* @param af Address family.
|
||||
* @return Normalized address.
|
||||
*/
|
||||
std::string_view
|
||||
af_to_any_address_string(af_e af);
|
||||
std::string_view af_to_any_address_string(af_e af);
|
||||
|
||||
/**
|
||||
* @brief Convert an address to a normalized form.
|
||||
@@ -77,8 +71,7 @@ namespace net {
|
||||
* @param address The address to normalize.
|
||||
* @return Normalized address.
|
||||
*/
|
||||
boost::asio::ip::address
|
||||
normalize_address(boost::asio::ip::address address);
|
||||
boost::asio::ip::address normalize_address(boost::asio::ip::address address);
|
||||
|
||||
/**
|
||||
* @brief Get the given address in normalized string form.
|
||||
@@ -86,8 +79,7 @@ namespace net {
|
||||
* @param address The address to normalize.
|
||||
* @return Normalized address in string form.
|
||||
*/
|
||||
std::string
|
||||
addr_to_normalized_string(boost::asio::ip::address address);
|
||||
std::string addr_to_normalized_string(boost::asio::ip::address address);
|
||||
|
||||
/**
|
||||
* @brief Get the given address in a normalized form for the host portion of a URL.
|
||||
@@ -95,22 +87,19 @@ namespace net {
|
||||
* @param address The address to normalize and escape.
|
||||
* @return Normalized address in URL-escaped string.
|
||||
*/
|
||||
std::string
|
||||
addr_to_url_escaped_string(boost::asio::ip::address address);
|
||||
std::string addr_to_url_escaped_string(boost::asio::ip::address address);
|
||||
|
||||
/**
|
||||
* @brief Get the encryption mode for the given remote endpoint address.
|
||||
* @param address The address used to look up the desired encryption mode.
|
||||
* @return The WAN or LAN encryption mode, based on the provided address.
|
||||
*/
|
||||
int
|
||||
encryption_mode_for_address(boost::asio::ip::address address);
|
||||
int encryption_mode_for_address(boost::asio::ip::address address);
|
||||
|
||||
/**
|
||||
* @brief Returns a string for use as the instance name for mDNS.
|
||||
* @param hostname The hostname to use for instance name generation.
|
||||
* @return Hostname-based instance name or "Sunshine" if hostname is invalid.
|
||||
*/
|
||||
std::string
|
||||
mdns_instance_name(const std::string_view &hostname);
|
||||
std::string mdns_instance_name(const std::string_view &hostname);
|
||||
} // namespace net
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,161 +1,155 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_base.h
|
||||
* @brief Declarations for abstract platform-agnostic base of standalone NVENC encoder.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "nvenc_colorspace.h"
|
||||
#include "nvenc_config.h"
|
||||
#include "nvenc_encoded_frame.h"
|
||||
|
||||
#include "src/logging.h"
|
||||
#include "src/video.h"
|
||||
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
/**
|
||||
* @brief Standalone NVENC encoder
|
||||
*/
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief Abstract platform-agnostic base of standalone NVENC encoder.
|
||||
* Derived classes perform platform-specific operations.
|
||||
*/
|
||||
class nvenc_base {
|
||||
public:
|
||||
/**
|
||||
* @param device_type Underlying device type used by derived class.
|
||||
*/
|
||||
explicit nvenc_base(NV_ENC_DEVICE_TYPE device_type);
|
||||
virtual ~nvenc_base();
|
||||
|
||||
nvenc_base(const nvenc_base &) = delete;
|
||||
nvenc_base &
|
||||
operator=(const nvenc_base &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Create the encoder.
|
||||
* @param config NVENC encoder configuration.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
* @param colorspace YUV colorspace.
|
||||
* @param buffer_format Platform-agnostic input surface format.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
bool
|
||||
create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format);
|
||||
|
||||
/**
|
||||
* @brief Destroy the encoder.
|
||||
* Derived classes classes call it in the destructor.
|
||||
*/
|
||||
void
|
||||
destroy_encoder();
|
||||
|
||||
/**
|
||||
* @brief Encode the next frame using platform-specific input surface.
|
||||
* @param frame_index Frame index that uniquely identifies the frame.
|
||||
* Afterwards serves as parameter for `invalidate_ref_frames()`.
|
||||
* No restrictions on the first frame index, but later frame indexes must be subsequent.
|
||||
* @param force_idr Whether to encode frame as forced IDR.
|
||||
* @return Encoded frame.
|
||||
*/
|
||||
nvenc_encoded_frame
|
||||
encode_frame(uint64_t frame_index, bool force_idr);
|
||||
|
||||
/**
|
||||
* @brief Perform reference frame invalidation (RFI) procedure.
|
||||
* @param first_frame First frame index of the invalidation range.
|
||||
* @param last_frame Last frame index of the invalidation range.
|
||||
* @return `true` on success, `false` on error.
|
||||
* After error next frame must be encoded with `force_idr = true`.
|
||||
*/
|
||||
bool
|
||||
invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Required. Used for loading NvEnc library and setting `nvenc` variable with `NvEncodeAPICreateInstance()`.
|
||||
* Called during `create_encoder()` if `nvenc` variable is not initialized.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual bool
|
||||
init_library() = 0;
|
||||
|
||||
/**
|
||||
* @brief Required. Used for creating outside-facing input surface,
|
||||
* registering this surface with `nvenc->nvEncRegisterResource()` and setting `registered_input_buffer` variable.
|
||||
* Called during `create_encoder()`.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual bool
|
||||
create_and_register_input_buffer() = 0;
|
||||
|
||||
/**
|
||||
* @brief Optional. Override if you must perform additional operations on the registered input surface in the beginning of `encode_frame()`.
|
||||
* Typically used for interop copy.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual bool
|
||||
synchronize_input_buffer() { return true; }
|
||||
|
||||
/**
|
||||
* @brief Optional. Override if you want to create encoder in async mode.
|
||||
* In this case must also set `async_event_handle` variable.
|
||||
* @param timeout_ms Wait timeout in milliseconds
|
||||
* @return `true` on success, `false` on timeout or error
|
||||
*/
|
||||
virtual bool
|
||||
wait_for_async_event(uint32_t timeout_ms) { return false; }
|
||||
|
||||
bool
|
||||
nvenc_failed(NVENCSTATUS status);
|
||||
|
||||
/**
|
||||
* @brief This function returns the corresponding struct version for the minimum API required by the codec.
|
||||
* @details Reducing the struct versions maximizes driver compatibility by avoiding needless API breaks.
|
||||
* @param version The raw structure version from `NVENCAPI_STRUCT_VERSION()`.
|
||||
* @param v11_struct_version Optionally specifies the struct version to use with v11 SDK major versions.
|
||||
* @param v12_struct_version Optionally specifies the struct version to use with v12 SDK major versions.
|
||||
* @return A suitable struct version for the active codec.
|
||||
*/
|
||||
uint32_t
|
||||
min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0);
|
||||
|
||||
const NV_ENC_DEVICE_TYPE device_type;
|
||||
|
||||
void *encoder = nullptr;
|
||||
|
||||
struct {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED;
|
||||
uint32_t ref_frames_in_dpb = 0;
|
||||
bool rfi = false;
|
||||
} encoder_params;
|
||||
|
||||
std::string last_nvenc_error_string;
|
||||
|
||||
// Derived classes set these variables
|
||||
void *device = nullptr; ///< Platform-specific handle of encoding device.
|
||||
///< Should be set in constructor or `init_library()`.
|
||||
std::shared_ptr<NV_ENCODE_API_FUNCTION_LIST> nvenc; ///< Function pointers list produced by `NvEncodeAPICreateInstance()`.
|
||||
///< Should be set in `init_library()`.
|
||||
NV_ENC_REGISTERED_PTR registered_input_buffer = nullptr; ///< Platform-specific input surface registered with `NvEncRegisterResource()`.
|
||||
///< Should be set in `create_and_register_input_buffer()`.
|
||||
void *async_event_handle = nullptr; ///< (optional) Platform-specific handle of event object event.
|
||||
///< Can be set in constructor or `init_library()`, must override `wait_for_async_event()`.
|
||||
|
||||
private:
|
||||
NV_ENC_OUTPUT_PTR output_bitstream = nullptr;
|
||||
uint32_t minimum_api_version = 0;
|
||||
|
||||
struct {
|
||||
uint64_t last_encoded_frame_index = 0;
|
||||
bool rfi_needs_confirmation = false;
|
||||
std::pair<uint64_t, uint64_t> last_rfi_range;
|
||||
logging::min_max_avg_periodic_logger<double> frame_size_logger = { debug, "NvEnc: encoded frame sizes in kB", "" };
|
||||
} encoder_state;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
/**
|
||||
* @file src/nvenc/nvenc_base.h
|
||||
* @brief Declarations for abstract platform-agnostic base of standalone NVENC encoder.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_colorspace.h"
|
||||
#include "nvenc_config.h"
|
||||
#include "nvenc_encoded_frame.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/video.h"
|
||||
|
||||
/**
|
||||
* @brief Standalone NVENC encoder
|
||||
*/
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief Abstract platform-agnostic base of standalone NVENC encoder.
|
||||
* Derived classes perform platform-specific operations.
|
||||
*/
|
||||
class nvenc_base {
|
||||
public:
|
||||
/**
|
||||
* @param device_type Underlying device type used by derived class.
|
||||
*/
|
||||
explicit nvenc_base(NV_ENC_DEVICE_TYPE device_type);
|
||||
virtual ~nvenc_base();
|
||||
|
||||
nvenc_base(const nvenc_base &) = delete;
|
||||
nvenc_base &operator=(const nvenc_base &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Create the encoder.
|
||||
* @param config NVENC encoder configuration.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
* @param colorspace YUV colorspace.
|
||||
* @param buffer_format Platform-agnostic input surface format.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
bool create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format);
|
||||
|
||||
/**
|
||||
* @brief Destroy the encoder.
|
||||
* Derived classes classes call it in the destructor.
|
||||
*/
|
||||
void destroy_encoder();
|
||||
|
||||
/**
|
||||
* @brief Encode the next frame using platform-specific input surface.
|
||||
* @param frame_index Frame index that uniquely identifies the frame.
|
||||
* Afterwards serves as parameter for `invalidate_ref_frames()`.
|
||||
* No restrictions on the first frame index, but later frame indexes must be subsequent.
|
||||
* @param force_idr Whether to encode frame as forced IDR.
|
||||
* @return Encoded frame.
|
||||
*/
|
||||
nvenc_encoded_frame encode_frame(uint64_t frame_index, bool force_idr);
|
||||
|
||||
/**
|
||||
* @brief Perform reference frame invalidation (RFI) procedure.
|
||||
* @param first_frame First frame index of the invalidation range.
|
||||
* @param last_frame Last frame index of the invalidation range.
|
||||
* @return `true` on success, `false` on error.
|
||||
* After error next frame must be encoded with `force_idr = true`.
|
||||
*/
|
||||
bool invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Required. Used for loading NvEnc library and setting `nvenc` variable with `NvEncodeAPICreateInstance()`.
|
||||
* Called during `create_encoder()` if `nvenc` variable is not initialized.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual bool init_library() = 0;
|
||||
|
||||
/**
|
||||
* @brief Required. Used for creating outside-facing input surface,
|
||||
* registering this surface with `nvenc->nvEncRegisterResource()` and setting `registered_input_buffer` variable.
|
||||
* Called during `create_encoder()`.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual bool create_and_register_input_buffer() = 0;
|
||||
|
||||
/**
|
||||
* @brief Optional. Override if you must perform additional operations on the registered input surface in the beginning of `encode_frame()`.
|
||||
* Typically used for interop copy.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual bool synchronize_input_buffer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Optional. Override if you want to create encoder in async mode.
|
||||
* In this case must also set `async_event_handle` variable.
|
||||
* @param timeout_ms Wait timeout in milliseconds
|
||||
* @return `true` on success, `false` on timeout or error
|
||||
*/
|
||||
virtual bool wait_for_async_event(uint32_t timeout_ms) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool nvenc_failed(NVENCSTATUS status);
|
||||
|
||||
/**
|
||||
* @brief This function returns the corresponding struct version for the minimum API required by the codec.
|
||||
* @details Reducing the struct versions maximizes driver compatibility by avoiding needless API breaks.
|
||||
* @param version The raw structure version from `NVENCAPI_STRUCT_VERSION()`.
|
||||
* @param v11_struct_version Optionally specifies the struct version to use with v11 SDK major versions.
|
||||
* @param v12_struct_version Optionally specifies the struct version to use with v12 SDK major versions.
|
||||
* @return A suitable struct version for the active codec.
|
||||
*/
|
||||
uint32_t min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0);
|
||||
|
||||
const NV_ENC_DEVICE_TYPE device_type;
|
||||
|
||||
void *encoder = nullptr;
|
||||
|
||||
struct {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED;
|
||||
uint32_t ref_frames_in_dpb = 0;
|
||||
bool rfi = false;
|
||||
} encoder_params;
|
||||
|
||||
std::string last_nvenc_error_string;
|
||||
|
||||
// Derived classes set these variables
|
||||
void *device = nullptr; ///< Platform-specific handle of encoding device.
|
||||
///< Should be set in constructor or `init_library()`.
|
||||
std::shared_ptr<NV_ENCODE_API_FUNCTION_LIST> nvenc; ///< Function pointers list produced by `NvEncodeAPICreateInstance()`.
|
||||
///< Should be set in `init_library()`.
|
||||
NV_ENC_REGISTERED_PTR registered_input_buffer = nullptr; ///< Platform-specific input surface registered with `NvEncRegisterResource()`.
|
||||
///< Should be set in `create_and_register_input_buffer()`.
|
||||
void *async_event_handle = nullptr; ///< (optional) Platform-specific handle of event object event.
|
||||
///< Can be set in constructor or `init_library()`, must override `wait_for_async_event()`.
|
||||
|
||||
private:
|
||||
NV_ENC_OUTPUT_PTR output_bitstream = nullptr;
|
||||
uint32_t minimum_api_version = 0;
|
||||
|
||||
struct {
|
||||
uint64_t last_encoded_frame_index = 0;
|
||||
bool rfi_needs_confirmation = false;
|
||||
std::pair<uint64_t, uint64_t> last_rfi_range;
|
||||
logging::min_max_avg_periodic_logger<double> frame_size_logger = {debug, "NvEnc: encoded frame sizes in kB", ""};
|
||||
} encoder_state;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_colorspace.h
|
||||
* @brief Declarations for NVENC YUV colorspace.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief YUV colorspace and color range.
|
||||
*/
|
||||
struct nvenc_colorspace_t {
|
||||
NV_ENC_VUI_COLOR_PRIMARIES primaries;
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC tranfer_function;
|
||||
NV_ENC_VUI_MATRIX_COEFFS matrix;
|
||||
bool full_range;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
/**
|
||||
* @file src/nvenc/nvenc_colorspace.h
|
||||
* @brief Declarations for NVENC YUV colorspace.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief YUV colorspace and color range.
|
||||
*/
|
||||
struct nvenc_colorspace_t {
|
||||
NV_ENC_VUI_COLOR_PRIMARIES primaries;
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC tranfer_function;
|
||||
NV_ENC_VUI_MATRIX_COEFFS matrix;
|
||||
bool full_range;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_config.h
|
||||
* @brief Declarations for NVENC encoder configuration.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
enum class nvenc_two_pass {
|
||||
disabled, ///< Single pass, the fastest and no extra vram
|
||||
quarter_resolution, ///< Larger motion vectors being caught, faster and uses less extra vram
|
||||
full_resolution, ///< Better overall statistics, slower and uses more extra vram
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief NVENC encoder configuration.
|
||||
*/
|
||||
struct nvenc_config {
|
||||
// Quality preset from 1 to 7, higher is slower
|
||||
int quality_preset = 1;
|
||||
|
||||
// Use optional preliminary pass for better motion vectors, bitrate distribution and stricter VBV(HRD), uses CUDA cores
|
||||
nvenc_two_pass two_pass = nvenc_two_pass::quarter_resolution;
|
||||
|
||||
// Percentage increase of VBV/HRD from the default single frame, allows low-latency variable bitrate
|
||||
int vbv_percentage_increase = 0;
|
||||
|
||||
// Improves fades compression, uses CUDA cores
|
||||
bool weighted_prediction = false;
|
||||
|
||||
// Allocate more bitrate to flat regions since they're visually more perceptible, uses CUDA cores
|
||||
bool adaptive_quantization = false;
|
||||
|
||||
// Don't use QP below certain value, limits peak image quality to save bitrate
|
||||
bool enable_min_qp = false;
|
||||
|
||||
// Min QP value for H.264 when enable_min_qp is selected
|
||||
unsigned min_qp_h264 = 19;
|
||||
|
||||
// Min QP value for HEVC when enable_min_qp is selected
|
||||
unsigned min_qp_hevc = 23;
|
||||
|
||||
// Min QP value for AV1 when enable_min_qp is selected
|
||||
unsigned min_qp_av1 = 23;
|
||||
|
||||
// Use CAVLC entropy coding in H.264 instead of CABAC, not relevant and here for historical reasons
|
||||
bool h264_cavlc = false;
|
||||
|
||||
// Add filler data to encoded frames to stay at target bitrate, mainly for testing
|
||||
bool insert_filler_data = false;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
/**
|
||||
* @file src/nvenc/nvenc_config.h
|
||||
* @brief Declarations for NVENC encoder configuration.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
enum class nvenc_two_pass {
|
||||
disabled, ///< Single pass, the fastest and no extra vram
|
||||
quarter_resolution, ///< Larger motion vectors being caught, faster and uses less extra vram
|
||||
full_resolution, ///< Better overall statistics, slower and uses more extra vram
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief NVENC encoder configuration.
|
||||
*/
|
||||
struct nvenc_config {
|
||||
// Quality preset from 1 to 7, higher is slower
|
||||
int quality_preset = 1;
|
||||
|
||||
// Use optional preliminary pass for better motion vectors, bitrate distribution and stricter VBV(HRD), uses CUDA cores
|
||||
nvenc_two_pass two_pass = nvenc_two_pass::quarter_resolution;
|
||||
|
||||
// Percentage increase of VBV/HRD from the default single frame, allows low-latency variable bitrate
|
||||
int vbv_percentage_increase = 0;
|
||||
|
||||
// Improves fades compression, uses CUDA cores
|
||||
bool weighted_prediction = false;
|
||||
|
||||
// Allocate more bitrate to flat regions since they're visually more perceptible, uses CUDA cores
|
||||
bool adaptive_quantization = false;
|
||||
|
||||
// Don't use QP below certain value, limits peak image quality to save bitrate
|
||||
bool enable_min_qp = false;
|
||||
|
||||
// Min QP value for H.264 when enable_min_qp is selected
|
||||
unsigned min_qp_h264 = 19;
|
||||
|
||||
// Min QP value for HEVC when enable_min_qp is selected
|
||||
unsigned min_qp_hevc = 23;
|
||||
|
||||
// Min QP value for AV1 when enable_min_qp is selected
|
||||
unsigned min_qp_av1 = 23;
|
||||
|
||||
// Use CAVLC entropy coding in H.264 instead of CABAC, not relevant and here for historical reasons
|
||||
bool h264_cavlc = false;
|
||||
|
||||
// Add filler data to encoded frames to stay at target bitrate, mainly for testing
|
||||
bool insert_filler_data = false;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
|
||||
@@ -1,58 +1,57 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11.cpp
|
||||
* @brief Definitions for abstract Direct3D11 NVENC encoder.
|
||||
*/
|
||||
#include "src/logging.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "nvenc_d3d11.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
nvenc_d3d11::~nvenc_d3d11() {
|
||||
if (dll) {
|
||||
FreeLibrary(dll);
|
||||
dll = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
nvenc_d3d11::init_library() {
|
||||
if (dll) return true;
|
||||
|
||||
#ifdef _WIN64
|
||||
constexpr auto dll_name = "nvEncodeAPI64.dll";
|
||||
#else
|
||||
constexpr auto dll_name = "nvEncodeAPI.dll";
|
||||
#endif
|
||||
|
||||
if ((dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
|
||||
if (auto create_instance = (decltype(NvEncodeAPICreateInstance) *) GetProcAddress(dll, "NvEncodeAPICreateInstance")) {
|
||||
auto new_nvenc = std::make_unique<NV_ENCODE_API_FUNCTION_LIST>();
|
||||
new_nvenc->version = min_struct_version(NV_ENCODE_API_FUNCTION_LIST_VER);
|
||||
if (nvenc_failed(create_instance(new_nvenc.get()))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncodeAPICreateInstance() failed: " << last_nvenc_error_string;
|
||||
}
|
||||
else {
|
||||
nvenc = std::move(new_nvenc);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(error) << "NvEnc: No NvEncodeAPICreateInstance() in " << dll_name;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << dll_name;
|
||||
}
|
||||
|
||||
if (dll) {
|
||||
FreeLibrary(dll);
|
||||
dll = NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11.cpp
|
||||
* @brief Definitions for abstract Direct3D11 NVENC encoder.
|
||||
*/
|
||||
// local includes
|
||||
#include "src/logging.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "nvenc_d3d11.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
nvenc_d3d11::~nvenc_d3d11() {
|
||||
if (dll) {
|
||||
FreeLibrary(dll);
|
||||
dll = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool nvenc_d3d11::init_library() {
|
||||
if (dll) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
constexpr auto dll_name = "nvEncodeAPI64.dll";
|
||||
#else
|
||||
constexpr auto dll_name = "nvEncodeAPI.dll";
|
||||
#endif
|
||||
|
||||
if ((dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
|
||||
if (auto create_instance = (decltype(NvEncodeAPICreateInstance) *) GetProcAddress(dll, "NvEncodeAPICreateInstance")) {
|
||||
auto new_nvenc = std::make_unique<NV_ENCODE_API_FUNCTION_LIST>();
|
||||
new_nvenc->version = min_struct_version(NV_ENCODE_API_FUNCTION_LIST_VER);
|
||||
if (nvenc_failed(create_instance(new_nvenc.get()))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncodeAPICreateInstance() failed: " << last_nvenc_error_string;
|
||||
} else {
|
||||
nvenc = std::move(new_nvenc);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG(error) << "NvEnc: No NvEncodeAPICreateInstance() in " << dll_name;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << dll_name;
|
||||
}
|
||||
|
||||
if (dll) {
|
||||
FreeLibrary(dll);
|
||||
dll = NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
|
||||
@@ -1,47 +1,48 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11.h
|
||||
* @brief Declarations for abstract Direct3D11 NVENC encoder.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <comdef.h>
|
||||
#include <d3d11.h>
|
||||
|
||||
#include "nvenc_base.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
_COM_SMARTPTR_TYPEDEF(ID3D11Device, IID_ID3D11Device);
|
||||
_COM_SMARTPTR_TYPEDEF(ID3D11Texture2D, IID_ID3D11Texture2D);
|
||||
_COM_SMARTPTR_TYPEDEF(IDXGIDevice, IID_IDXGIDevice);
|
||||
_COM_SMARTPTR_TYPEDEF(IDXGIAdapter, IID_IDXGIAdapter);
|
||||
|
||||
/**
|
||||
* @brief Abstract Direct3D11 NVENC encoder.
|
||||
* Encapsulates common code used by native and interop implementations.
|
||||
*/
|
||||
class nvenc_d3d11: public nvenc_base {
|
||||
public:
|
||||
explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type):
|
||||
nvenc_base(device_type) {}
|
||||
|
||||
~nvenc_d3d11();
|
||||
|
||||
/**
|
||||
* @brief Get input surface texture.
|
||||
* @return Input surface texture.
|
||||
*/
|
||||
virtual ID3D11Texture2D *
|
||||
get_input_texture() = 0;
|
||||
|
||||
protected:
|
||||
bool
|
||||
init_library() override;
|
||||
|
||||
private:
|
||||
HMODULE dll = NULL;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11.h
|
||||
* @brief Declarations for abstract Direct3D11 NVENC encoder.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
|
||||
// standard includes
|
||||
#include <comdef.h>
|
||||
#include <d3d11.h>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_base.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
_COM_SMARTPTR_TYPEDEF(ID3D11Device, IID_ID3D11Device);
|
||||
_COM_SMARTPTR_TYPEDEF(ID3D11Texture2D, IID_ID3D11Texture2D);
|
||||
_COM_SMARTPTR_TYPEDEF(IDXGIDevice, IID_IDXGIDevice);
|
||||
_COM_SMARTPTR_TYPEDEF(IDXGIAdapter, IID_IDXGIAdapter);
|
||||
|
||||
/**
|
||||
* @brief Abstract Direct3D11 NVENC encoder.
|
||||
* Encapsulates common code used by native and interop implementations.
|
||||
*/
|
||||
class nvenc_d3d11: public nvenc_base {
|
||||
public:
|
||||
explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type):
|
||||
nvenc_base(device_type) {
|
||||
}
|
||||
|
||||
~nvenc_d3d11();
|
||||
|
||||
/**
|
||||
* @brief Get input surface texture.
|
||||
* @return Input surface texture.
|
||||
*/
|
||||
virtual ID3D11Texture2D *get_input_texture() = 0;
|
||||
|
||||
protected:
|
||||
bool init_library() override;
|
||||
|
||||
private:
|
||||
HMODULE dll = NULL;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
|
||||
@@ -1,71 +1,74 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11_native.cpp
|
||||
* @brief Definitions for native Direct3D11 NVENC encoder.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#include "nvenc_d3d11_native.h"
|
||||
|
||||
#include "nvenc_utils.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
nvenc_d3d11_native::nvenc_d3d11_native(ID3D11Device *d3d_device):
|
||||
nvenc_d3d11(NV_ENC_DEVICE_TYPE_DIRECTX),
|
||||
d3d_device(d3d_device) {
|
||||
device = d3d_device;
|
||||
}
|
||||
|
||||
nvenc_d3d11_native::~nvenc_d3d11_native() {
|
||||
if (encoder) destroy_encoder();
|
||||
}
|
||||
|
||||
ID3D11Texture2D *
|
||||
nvenc_d3d11_native::get_input_texture() {
|
||||
return d3d_input_texture.GetInterfacePtr();
|
||||
}
|
||||
|
||||
bool
|
||||
nvenc_d3d11_native::create_and_register_input_buffer() {
|
||||
if (encoder_params.buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
|
||||
BOOST_LOG(error) << "NvEnc: 10-bit 4:4:4 encoding is incompatible with D3D11 surface formats, use CUDA interop";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!d3d_input_texture) {
|
||||
D3D11_TEXTURE2D_DESC desc = {};
|
||||
desc.Width = encoder_params.width;
|
||||
desc.Height = encoder_params.height;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = dxgi_format_from_nvenc_format(encoder_params.buffer_format);
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
if (d3d_device->CreateTexture2D(&desc, nullptr, &d3d_input_texture) != S_OK) {
|
||||
BOOST_LOG(error) << "NvEnc: couldn't create input texture";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!registered_input_buffer) {
|
||||
NV_ENC_REGISTER_RESOURCE register_resource = { min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4) };
|
||||
register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX;
|
||||
register_resource.width = encoder_params.width;
|
||||
register_resource.height = encoder_params.height;
|
||||
register_resource.resourceToRegister = d3d_input_texture.GetInterfacePtr();
|
||||
register_resource.bufferFormat = encoder_params.buffer_format;
|
||||
register_resource.bufferUsage = NV_ENC_INPUT_IMAGE;
|
||||
|
||||
if (nvenc_failed(nvenc->nvEncRegisterResource(encoder, ®ister_resource))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncRegisterResource() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
}
|
||||
|
||||
registered_input_buffer = register_resource.registeredResource;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11_native.cpp
|
||||
* @brief Definitions for native Direct3D11 NVENC encoder.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
// this include
|
||||
#include "nvenc_d3d11_native.h"
|
||||
|
||||
// local includes
|
||||
#include "nvenc_utils.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
nvenc_d3d11_native::nvenc_d3d11_native(ID3D11Device *d3d_device):
|
||||
nvenc_d3d11(NV_ENC_DEVICE_TYPE_DIRECTX),
|
||||
d3d_device(d3d_device) {
|
||||
device = d3d_device;
|
||||
}
|
||||
|
||||
nvenc_d3d11_native::~nvenc_d3d11_native() {
|
||||
if (encoder) {
|
||||
destroy_encoder();
|
||||
}
|
||||
}
|
||||
|
||||
ID3D11Texture2D *
|
||||
nvenc_d3d11_native::get_input_texture() {
|
||||
return d3d_input_texture.GetInterfacePtr();
|
||||
}
|
||||
|
||||
bool nvenc_d3d11_native::create_and_register_input_buffer() {
|
||||
if (encoder_params.buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
|
||||
BOOST_LOG(error) << "NvEnc: 10-bit 4:4:4 encoding is incompatible with D3D11 surface formats, use CUDA interop";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!d3d_input_texture) {
|
||||
D3D11_TEXTURE2D_DESC desc = {};
|
||||
desc.Width = encoder_params.width;
|
||||
desc.Height = encoder_params.height;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = dxgi_format_from_nvenc_format(encoder_params.buffer_format);
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
if (d3d_device->CreateTexture2D(&desc, nullptr, &d3d_input_texture) != S_OK) {
|
||||
BOOST_LOG(error) << "NvEnc: couldn't create input texture";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!registered_input_buffer) {
|
||||
NV_ENC_REGISTER_RESOURCE register_resource = {min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4)};
|
||||
register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX;
|
||||
register_resource.width = encoder_params.width;
|
||||
register_resource.height = encoder_params.height;
|
||||
register_resource.resourceToRegister = d3d_input_texture.GetInterfacePtr();
|
||||
register_resource.bufferFormat = encoder_params.buffer_format;
|
||||
register_resource.bufferUsage = NV_ENC_INPUT_IMAGE;
|
||||
|
||||
if (nvenc_failed(nvenc->nvEncRegisterResource(encoder, ®ister_resource))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncRegisterResource() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
}
|
||||
|
||||
registered_input_buffer = register_resource.registeredResource;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
|
||||
@@ -1,38 +1,37 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11_native.h
|
||||
* @brief Declarations for native Direct3D11 NVENC encoder.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <comdef.h>
|
||||
#include <d3d11.h>
|
||||
|
||||
#include "nvenc_d3d11.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief Native Direct3D11 NVENC encoder.
|
||||
*/
|
||||
class nvenc_d3d11_native final: public nvenc_d3d11 {
|
||||
public:
|
||||
/**
|
||||
* @param d3d_device Direct3D11 device used for encoding.
|
||||
*/
|
||||
explicit nvenc_d3d11_native(ID3D11Device *d3d_device);
|
||||
~nvenc_d3d11_native();
|
||||
|
||||
ID3D11Texture2D *
|
||||
get_input_texture() override;
|
||||
|
||||
private:
|
||||
bool
|
||||
create_and_register_input_buffer() override;
|
||||
|
||||
const ID3D11DevicePtr d3d_device;
|
||||
ID3D11Texture2DPtr d3d_input_texture;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11_native.h
|
||||
* @brief Declarations for native Direct3D11 NVENC encoder.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
// standard includes
|
||||
#include <comdef.h>
|
||||
#include <d3d11.h>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_d3d11.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief Native Direct3D11 NVENC encoder.
|
||||
*/
|
||||
class nvenc_d3d11_native final: public nvenc_d3d11 {
|
||||
public:
|
||||
/**
|
||||
* @param d3d_device Direct3D11 device used for encoding.
|
||||
*/
|
||||
explicit nvenc_d3d11_native(ID3D11Device *d3d_device);
|
||||
~nvenc_d3d11_native();
|
||||
|
||||
ID3D11Texture2D *get_input_texture() override;
|
||||
|
||||
private:
|
||||
bool create_and_register_input_buffer() override;
|
||||
|
||||
const ID3D11DevicePtr d3d_device;
|
||||
ID3D11Texture2DPtr d3d_input_texture;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
|
||||
@@ -1,267 +1,269 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11_on_cuda.cpp
|
||||
* @brief Definitions for CUDA NVENC encoder with Direct3D11 input surfaces.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#include "nvenc_d3d11_on_cuda.h"
|
||||
|
||||
#include "nvenc_utils.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
nvenc_d3d11_on_cuda::nvenc_d3d11_on_cuda(ID3D11Device *d3d_device):
|
||||
nvenc_d3d11(NV_ENC_DEVICE_TYPE_CUDA),
|
||||
d3d_device(d3d_device) {
|
||||
}
|
||||
|
||||
nvenc_d3d11_on_cuda::~nvenc_d3d11_on_cuda() {
|
||||
if (encoder) destroy_encoder();
|
||||
|
||||
if (cuda_context) {
|
||||
{
|
||||
auto autopop_context = push_context();
|
||||
|
||||
if (cuda_d3d_input_texture) {
|
||||
if (cuda_failed(cuda_functions.cuGraphicsUnregisterResource(cuda_d3d_input_texture))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuGraphicsUnregisterResource() failed: error " << last_cuda_error;
|
||||
}
|
||||
cuda_d3d_input_texture = nullptr;
|
||||
}
|
||||
|
||||
if (cuda_surface) {
|
||||
if (cuda_failed(cuda_functions.cuMemFree(cuda_surface))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuMemFree() failed: error " << last_cuda_error;
|
||||
}
|
||||
cuda_surface = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (cuda_failed(cuda_functions.cuCtxDestroy(cuda_context))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuCtxDestroy() failed: error " << last_cuda_error;
|
||||
}
|
||||
cuda_context = nullptr;
|
||||
}
|
||||
|
||||
if (cuda_functions.dll) {
|
||||
FreeLibrary(cuda_functions.dll);
|
||||
cuda_functions = {};
|
||||
}
|
||||
}
|
||||
|
||||
ID3D11Texture2D *
|
||||
nvenc_d3d11_on_cuda::get_input_texture() {
|
||||
return d3d_input_texture.GetInterfacePtr();
|
||||
}
|
||||
|
||||
bool
|
||||
nvenc_d3d11_on_cuda::init_library() {
|
||||
if (!nvenc_d3d11::init_library()) return false;
|
||||
|
||||
constexpr auto dll_name = "nvcuda.dll";
|
||||
|
||||
if ((cuda_functions.dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
|
||||
auto load_function = [&]<typename T>(T &location, auto symbol) -> bool {
|
||||
location = (T) GetProcAddress(cuda_functions.dll, symbol);
|
||||
return location != nullptr;
|
||||
};
|
||||
if (!load_function(cuda_functions.cuInit, "cuInit") ||
|
||||
!load_function(cuda_functions.cuD3D11GetDevice, "cuD3D11GetDevice") ||
|
||||
!load_function(cuda_functions.cuCtxCreate, "cuCtxCreate_v2") ||
|
||||
!load_function(cuda_functions.cuCtxDestroy, "cuCtxDestroy_v2") ||
|
||||
!load_function(cuda_functions.cuCtxPushCurrent, "cuCtxPushCurrent_v2") ||
|
||||
!load_function(cuda_functions.cuCtxPopCurrent, "cuCtxPopCurrent_v2") ||
|
||||
!load_function(cuda_functions.cuMemAllocPitch, "cuMemAllocPitch_v2") ||
|
||||
!load_function(cuda_functions.cuMemFree, "cuMemFree_v2") ||
|
||||
!load_function(cuda_functions.cuGraphicsD3D11RegisterResource, "cuGraphicsD3D11RegisterResource") ||
|
||||
!load_function(cuda_functions.cuGraphicsUnregisterResource, "cuGraphicsUnregisterResource") ||
|
||||
!load_function(cuda_functions.cuGraphicsMapResources, "cuGraphicsMapResources") ||
|
||||
!load_function(cuda_functions.cuGraphicsUnmapResources, "cuGraphicsUnmapResources") ||
|
||||
!load_function(cuda_functions.cuGraphicsSubResourceGetMappedArray, "cuGraphicsSubResourceGetMappedArray") ||
|
||||
!load_function(cuda_functions.cuMemcpy2D, "cuMemcpy2D_v2")) {
|
||||
BOOST_LOG(error) << "NvEnc: missing CUDA functions in " << dll_name;
|
||||
FreeLibrary(cuda_functions.dll);
|
||||
cuda_functions = {};
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(debug) << "NvEnc: couldn't load CUDA dynamic library " << dll_name;
|
||||
}
|
||||
|
||||
if (cuda_functions.dll) {
|
||||
IDXGIDevicePtr dxgi_device;
|
||||
IDXGIAdapterPtr dxgi_adapter;
|
||||
if (d3d_device &&
|
||||
SUCCEEDED(d3d_device->QueryInterface(IID_PPV_ARGS(&dxgi_device))) &&
|
||||
SUCCEEDED(dxgi_device->GetAdapter(&dxgi_adapter))) {
|
||||
CUdevice cuda_device;
|
||||
if (cuda_succeeded(cuda_functions.cuInit(0)) &&
|
||||
cuda_succeeded(cuda_functions.cuD3D11GetDevice(&cuda_device, dxgi_adapter)) &&
|
||||
cuda_succeeded(cuda_functions.cuCtxCreate(&cuda_context, CU_CTX_SCHED_BLOCKING_SYNC, cuda_device)) &&
|
||||
cuda_succeeded(cuda_functions.cuCtxPopCurrent(&cuda_context))) {
|
||||
device = cuda_context;
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(error) << "NvEnc: couldn't create CUDA interop context: error " << last_cuda_error;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(error) << "NvEnc: couldn't get DXGI adapter for CUDA interop";
|
||||
}
|
||||
}
|
||||
|
||||
return device != nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
nvenc_d3d11_on_cuda::create_and_register_input_buffer() {
|
||||
if (encoder_params.buffer_format != NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
|
||||
BOOST_LOG(error) << "NvEnc: CUDA interop is expected to be used only for 10-bit 4:4:4 encoding";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!d3d_input_texture) {
|
||||
D3D11_TEXTURE2D_DESC desc = {};
|
||||
desc.Width = encoder_params.width;
|
||||
desc.Height = encoder_params.height * 3; // Planar YUV
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = dxgi_format_from_nvenc_format(encoder_params.buffer_format);
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
|
||||
if (d3d_device->CreateTexture2D(&desc, nullptr, &d3d_input_texture) != S_OK) {
|
||||
BOOST_LOG(error) << "NvEnc: couldn't create input texture";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto autopop_context = push_context();
|
||||
if (!autopop_context) return false;
|
||||
|
||||
if (!cuda_d3d_input_texture) {
|
||||
if (cuda_failed(cuda_functions.cuGraphicsD3D11RegisterResource(
|
||||
&cuda_d3d_input_texture,
|
||||
d3d_input_texture,
|
||||
CU_GRAPHICS_REGISTER_FLAGS_NONE))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuGraphicsD3D11RegisterResource() failed: error " << last_cuda_error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cuda_surface) {
|
||||
if (cuda_failed(cuda_functions.cuMemAllocPitch(
|
||||
&cuda_surface,
|
||||
&cuda_surface_pitch,
|
||||
// Planar 16-bit YUV
|
||||
encoder_params.width * 2,
|
||||
encoder_params.height * 3, 16))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuMemAllocPitch() failed: error " << last_cuda_error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!registered_input_buffer) {
|
||||
NV_ENC_REGISTER_RESOURCE register_resource = { min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4) };
|
||||
register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR;
|
||||
register_resource.width = encoder_params.width;
|
||||
register_resource.height = encoder_params.height;
|
||||
register_resource.pitch = cuda_surface_pitch;
|
||||
register_resource.resourceToRegister = (void *) cuda_surface;
|
||||
register_resource.bufferFormat = encoder_params.buffer_format;
|
||||
register_resource.bufferUsage = NV_ENC_INPUT_IMAGE;
|
||||
|
||||
if (nvenc_failed(nvenc->nvEncRegisterResource(encoder, ®ister_resource))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncRegisterResource() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
}
|
||||
|
||||
registered_input_buffer = register_resource.registeredResource;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
nvenc_d3d11_on_cuda::synchronize_input_buffer() {
|
||||
auto autopop_context = push_context();
|
||||
if (!autopop_context) return false;
|
||||
|
||||
if (cuda_failed(cuda_functions.cuGraphicsMapResources(1, &cuda_d3d_input_texture, 0))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuGraphicsMapResources() failed: error " << last_cuda_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto unmap = [&]() -> bool {
|
||||
if (cuda_failed(cuda_functions.cuGraphicsUnmapResources(1, &cuda_d3d_input_texture, 0))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuGraphicsUnmapResources() failed: error " << last_cuda_error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
auto unmap_guard = util::fail_guard(unmap);
|
||||
|
||||
CUarray input_texture_array;
|
||||
if (cuda_failed(cuda_functions.cuGraphicsSubResourceGetMappedArray(&input_texture_array, cuda_d3d_input_texture, 0, 0))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuGraphicsSubResourceGetMappedArray() failed: error " << last_cuda_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
CUDA_MEMCPY2D copy_params = {};
|
||||
copy_params.srcMemoryType = CU_MEMORYTYPE_ARRAY;
|
||||
copy_params.srcArray = input_texture_array;
|
||||
copy_params.dstMemoryType = CU_MEMORYTYPE_DEVICE;
|
||||
copy_params.dstDevice = cuda_surface;
|
||||
copy_params.dstPitch = cuda_surface_pitch;
|
||||
// Planar 16-bit YUV
|
||||
copy_params.WidthInBytes = encoder_params.width * 2;
|
||||
copy_params.Height = encoder_params.height * 3;
|
||||
|
||||
if (cuda_failed(cuda_functions.cuMemcpy2D(©_params))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuMemcpy2D() failed: error " << last_cuda_error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unmap_guard.disable();
|
||||
return unmap();
|
||||
}
|
||||
|
||||
bool
|
||||
nvenc_d3d11_on_cuda::cuda_succeeded(CUresult result) {
|
||||
last_cuda_error = result;
|
||||
return result == CUDA_SUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
nvenc_d3d11_on_cuda::cuda_failed(CUresult result) {
|
||||
last_cuda_error = result;
|
||||
return result != CUDA_SUCCESS;
|
||||
}
|
||||
|
||||
nvenc_d3d11_on_cuda::autopop_context::~autopop_context() {
|
||||
if (pushed_context) {
|
||||
CUcontext popped_context;
|
||||
if (parent.cuda_failed(parent.cuda_functions.cuCtxPopCurrent(&popped_context))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuCtxPopCurrent() failed: error " << parent.last_cuda_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nvenc_d3d11_on_cuda::autopop_context
|
||||
nvenc_d3d11_on_cuda::push_context() {
|
||||
if (cuda_context &&
|
||||
cuda_succeeded(cuda_functions.cuCtxPushCurrent(cuda_context))) {
|
||||
return { *this, cuda_context };
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(error) << "NvEnc: cuCtxPushCurrent() failed: error " << last_cuda_error;
|
||||
return { *this, nullptr };
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11_on_cuda.cpp
|
||||
* @brief Definitions for CUDA NVENC encoder with Direct3D11 input surfaces.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
// this include
|
||||
#include "nvenc_d3d11_on_cuda.h"
|
||||
|
||||
// local includes
|
||||
#include "nvenc_utils.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
nvenc_d3d11_on_cuda::nvenc_d3d11_on_cuda(ID3D11Device *d3d_device):
|
||||
nvenc_d3d11(NV_ENC_DEVICE_TYPE_CUDA),
|
||||
d3d_device(d3d_device) {
|
||||
}
|
||||
|
||||
nvenc_d3d11_on_cuda::~nvenc_d3d11_on_cuda() {
|
||||
if (encoder) {
|
||||
destroy_encoder();
|
||||
}
|
||||
|
||||
if (cuda_context) {
|
||||
{
|
||||
auto autopop_context = push_context();
|
||||
|
||||
if (cuda_d3d_input_texture) {
|
||||
if (cuda_failed(cuda_functions.cuGraphicsUnregisterResource(cuda_d3d_input_texture))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuGraphicsUnregisterResource() failed: error " << last_cuda_error;
|
||||
}
|
||||
cuda_d3d_input_texture = nullptr;
|
||||
}
|
||||
|
||||
if (cuda_surface) {
|
||||
if (cuda_failed(cuda_functions.cuMemFree(cuda_surface))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuMemFree() failed: error " << last_cuda_error;
|
||||
}
|
||||
cuda_surface = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (cuda_failed(cuda_functions.cuCtxDestroy(cuda_context))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuCtxDestroy() failed: error " << last_cuda_error;
|
||||
}
|
||||
cuda_context = nullptr;
|
||||
}
|
||||
|
||||
if (cuda_functions.dll) {
|
||||
FreeLibrary(cuda_functions.dll);
|
||||
cuda_functions = {};
|
||||
}
|
||||
}
|
||||
|
||||
ID3D11Texture2D *nvenc_d3d11_on_cuda::get_input_texture() {
|
||||
return d3d_input_texture.GetInterfacePtr();
|
||||
}
|
||||
|
||||
bool nvenc_d3d11_on_cuda::init_library() {
|
||||
if (!nvenc_d3d11::init_library()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr auto dll_name = "nvcuda.dll";
|
||||
|
||||
if ((cuda_functions.dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
|
||||
auto load_function = [&]<typename T>(T &location, auto symbol) -> bool {
|
||||
location = (T) GetProcAddress(cuda_functions.dll, symbol);
|
||||
return location != nullptr;
|
||||
};
|
||||
if (!load_function(cuda_functions.cuInit, "cuInit") ||
|
||||
!load_function(cuda_functions.cuD3D11GetDevice, "cuD3D11GetDevice") ||
|
||||
!load_function(cuda_functions.cuCtxCreate, "cuCtxCreate_v2") ||
|
||||
!load_function(cuda_functions.cuCtxDestroy, "cuCtxDestroy_v2") ||
|
||||
!load_function(cuda_functions.cuCtxPushCurrent, "cuCtxPushCurrent_v2") ||
|
||||
!load_function(cuda_functions.cuCtxPopCurrent, "cuCtxPopCurrent_v2") ||
|
||||
!load_function(cuda_functions.cuMemAllocPitch, "cuMemAllocPitch_v2") ||
|
||||
!load_function(cuda_functions.cuMemFree, "cuMemFree_v2") ||
|
||||
!load_function(cuda_functions.cuGraphicsD3D11RegisterResource, "cuGraphicsD3D11RegisterResource") ||
|
||||
!load_function(cuda_functions.cuGraphicsUnregisterResource, "cuGraphicsUnregisterResource") ||
|
||||
!load_function(cuda_functions.cuGraphicsMapResources, "cuGraphicsMapResources") ||
|
||||
!load_function(cuda_functions.cuGraphicsUnmapResources, "cuGraphicsUnmapResources") ||
|
||||
!load_function(cuda_functions.cuGraphicsSubResourceGetMappedArray, "cuGraphicsSubResourceGetMappedArray") ||
|
||||
!load_function(cuda_functions.cuMemcpy2D, "cuMemcpy2D_v2")) {
|
||||
BOOST_LOG(error) << "NvEnc: missing CUDA functions in " << dll_name;
|
||||
FreeLibrary(cuda_functions.dll);
|
||||
cuda_functions = {};
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG(debug) << "NvEnc: couldn't load CUDA dynamic library " << dll_name;
|
||||
}
|
||||
|
||||
if (cuda_functions.dll) {
|
||||
IDXGIDevicePtr dxgi_device;
|
||||
IDXGIAdapterPtr dxgi_adapter;
|
||||
if (d3d_device &&
|
||||
SUCCEEDED(d3d_device->QueryInterface(IID_PPV_ARGS(&dxgi_device))) &&
|
||||
SUCCEEDED(dxgi_device->GetAdapter(&dxgi_adapter))) {
|
||||
CUdevice cuda_device;
|
||||
if (cuda_succeeded(cuda_functions.cuInit(0)) &&
|
||||
cuda_succeeded(cuda_functions.cuD3D11GetDevice(&cuda_device, dxgi_adapter)) &&
|
||||
cuda_succeeded(cuda_functions.cuCtxCreate(&cuda_context, CU_CTX_SCHED_BLOCKING_SYNC, cuda_device)) &&
|
||||
cuda_succeeded(cuda_functions.cuCtxPopCurrent(&cuda_context))) {
|
||||
device = cuda_context;
|
||||
} else {
|
||||
BOOST_LOG(error) << "NvEnc: couldn't create CUDA interop context: error " << last_cuda_error;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG(error) << "NvEnc: couldn't get DXGI adapter for CUDA interop";
|
||||
}
|
||||
}
|
||||
|
||||
return device != nullptr;
|
||||
}
|
||||
|
||||
bool nvenc_d3d11_on_cuda::create_and_register_input_buffer() {
|
||||
if (encoder_params.buffer_format != NV_ENC_BUFFER_FORMAT_YUV444_10BIT) {
|
||||
BOOST_LOG(error) << "NvEnc: CUDA interop is expected to be used only for 10-bit 4:4:4 encoding";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!d3d_input_texture) {
|
||||
D3D11_TEXTURE2D_DESC desc = {};
|
||||
desc.Width = encoder_params.width;
|
||||
desc.Height = encoder_params.height * 3; // Planar YUV
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = dxgi_format_from_nvenc_format(encoder_params.buffer_format);
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
|
||||
if (d3d_device->CreateTexture2D(&desc, nullptr, &d3d_input_texture) != S_OK) {
|
||||
BOOST_LOG(error) << "NvEnc: couldn't create input texture";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto autopop_context = push_context();
|
||||
if (!autopop_context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!cuda_d3d_input_texture) {
|
||||
if (cuda_failed(cuda_functions.cuGraphicsD3D11RegisterResource(
|
||||
&cuda_d3d_input_texture,
|
||||
d3d_input_texture,
|
||||
CU_GRAPHICS_REGISTER_FLAGS_NONE
|
||||
))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuGraphicsD3D11RegisterResource() failed: error " << last_cuda_error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cuda_surface) {
|
||||
if (cuda_failed(cuda_functions.cuMemAllocPitch(
|
||||
&cuda_surface,
|
||||
&cuda_surface_pitch,
|
||||
// Planar 16-bit YUV
|
||||
encoder_params.width * 2,
|
||||
encoder_params.height * 3,
|
||||
16
|
||||
))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuMemAllocPitch() failed: error " << last_cuda_error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!registered_input_buffer) {
|
||||
NV_ENC_REGISTER_RESOURCE register_resource = {min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4)};
|
||||
register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR;
|
||||
register_resource.width = encoder_params.width;
|
||||
register_resource.height = encoder_params.height;
|
||||
register_resource.pitch = cuda_surface_pitch;
|
||||
register_resource.resourceToRegister = (void *) cuda_surface;
|
||||
register_resource.bufferFormat = encoder_params.buffer_format;
|
||||
register_resource.bufferUsage = NV_ENC_INPUT_IMAGE;
|
||||
|
||||
if (nvenc_failed(nvenc->nvEncRegisterResource(encoder, ®ister_resource))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncRegisterResource() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
}
|
||||
|
||||
registered_input_buffer = register_resource.registeredResource;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nvenc_d3d11_on_cuda::synchronize_input_buffer() {
|
||||
auto autopop_context = push_context();
|
||||
if (!autopop_context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cuda_failed(cuda_functions.cuGraphicsMapResources(1, &cuda_d3d_input_texture, 0))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuGraphicsMapResources() failed: error " << last_cuda_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto unmap = [&]() -> bool {
|
||||
if (cuda_failed(cuda_functions.cuGraphicsUnmapResources(1, &cuda_d3d_input_texture, 0))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuGraphicsUnmapResources() failed: error " << last_cuda_error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
auto unmap_guard = util::fail_guard(unmap);
|
||||
|
||||
CUarray input_texture_array;
|
||||
if (cuda_failed(cuda_functions.cuGraphicsSubResourceGetMappedArray(&input_texture_array, cuda_d3d_input_texture, 0, 0))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuGraphicsSubResourceGetMappedArray() failed: error " << last_cuda_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
CUDA_MEMCPY2D copy_params = {};
|
||||
copy_params.srcMemoryType = CU_MEMORYTYPE_ARRAY;
|
||||
copy_params.srcArray = input_texture_array;
|
||||
copy_params.dstMemoryType = CU_MEMORYTYPE_DEVICE;
|
||||
copy_params.dstDevice = cuda_surface;
|
||||
copy_params.dstPitch = cuda_surface_pitch;
|
||||
// Planar 16-bit YUV
|
||||
copy_params.WidthInBytes = encoder_params.width * 2;
|
||||
copy_params.Height = encoder_params.height * 3;
|
||||
|
||||
if (cuda_failed(cuda_functions.cuMemcpy2D(©_params))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuMemcpy2D() failed: error " << last_cuda_error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unmap_guard.disable();
|
||||
return unmap();
|
||||
}
|
||||
|
||||
bool nvenc_d3d11_on_cuda::cuda_succeeded(CUresult result) {
|
||||
last_cuda_error = result;
|
||||
return result == CUDA_SUCCESS;
|
||||
}
|
||||
|
||||
bool nvenc_d3d11_on_cuda::cuda_failed(CUresult result) {
|
||||
last_cuda_error = result;
|
||||
return result != CUDA_SUCCESS;
|
||||
}
|
||||
|
||||
nvenc_d3d11_on_cuda::autopop_context::~autopop_context() {
|
||||
if (pushed_context) {
|
||||
CUcontext popped_context;
|
||||
if (parent.cuda_failed(parent.cuda_functions.cuCtxPopCurrent(&popped_context))) {
|
||||
BOOST_LOG(error) << "NvEnc: cuCtxPopCurrent() failed: error " << parent.last_cuda_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nvenc_d3d11_on_cuda::autopop_context nvenc_d3d11_on_cuda::push_context() {
|
||||
if (cuda_context &&
|
||||
cuda_succeeded(cuda_functions.cuCtxPushCurrent(cuda_context))) {
|
||||
return {*this, cuda_context};
|
||||
} else {
|
||||
BOOST_LOG(error) << "NvEnc: cuCtxPushCurrent() failed: error " << last_cuda_error;
|
||||
return {*this, nullptr};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
|
||||
@@ -1,96 +1,89 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11_on_cuda.h
|
||||
* @brief Declarations for CUDA NVENC encoder with Direct3D11 input surfaces.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "nvenc_d3d11.h"
|
||||
|
||||
#include <ffnvcodec/dynlink_cuda.h>
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief Interop Direct3D11 on CUDA NVENC encoder.
|
||||
* Input surface is Direct3D11, encoding is performed by CUDA.
|
||||
*/
|
||||
class nvenc_d3d11_on_cuda final: public nvenc_d3d11 {
|
||||
public:
|
||||
/**
|
||||
* @param d3d_device Direct3D11 device that will create input surface texture.
|
||||
* CUDA encoding device will be derived from it.
|
||||
*/
|
||||
explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device);
|
||||
~nvenc_d3d11_on_cuda();
|
||||
|
||||
ID3D11Texture2D *
|
||||
get_input_texture() override;
|
||||
|
||||
private:
|
||||
bool
|
||||
init_library() override;
|
||||
|
||||
bool
|
||||
create_and_register_input_buffer() override;
|
||||
|
||||
bool
|
||||
synchronize_input_buffer() override;
|
||||
|
||||
bool
|
||||
cuda_succeeded(CUresult result);
|
||||
|
||||
bool
|
||||
cuda_failed(CUresult result);
|
||||
|
||||
struct autopop_context {
|
||||
autopop_context(nvenc_d3d11_on_cuda &parent, CUcontext pushed_context):
|
||||
parent(parent),
|
||||
pushed_context(pushed_context) {
|
||||
}
|
||||
|
||||
~autopop_context();
|
||||
|
||||
explicit
|
||||
operator bool() const {
|
||||
return pushed_context != nullptr;
|
||||
}
|
||||
|
||||
nvenc_d3d11_on_cuda &parent;
|
||||
CUcontext pushed_context = nullptr;
|
||||
};
|
||||
|
||||
autopop_context
|
||||
push_context();
|
||||
|
||||
HMODULE dll = NULL;
|
||||
const ID3D11DevicePtr d3d_device;
|
||||
ID3D11Texture2DPtr d3d_input_texture;
|
||||
|
||||
struct {
|
||||
tcuInit *cuInit;
|
||||
tcuD3D11GetDevice *cuD3D11GetDevice;
|
||||
tcuCtxCreate_v2 *cuCtxCreate;
|
||||
tcuCtxDestroy_v2 *cuCtxDestroy;
|
||||
tcuCtxPushCurrent_v2 *cuCtxPushCurrent;
|
||||
tcuCtxPopCurrent_v2 *cuCtxPopCurrent;
|
||||
tcuMemAllocPitch_v2 *cuMemAllocPitch;
|
||||
tcuMemFree_v2 *cuMemFree;
|
||||
tcuGraphicsD3D11RegisterResource *cuGraphicsD3D11RegisterResource;
|
||||
tcuGraphicsUnregisterResource *cuGraphicsUnregisterResource;
|
||||
tcuGraphicsMapResources *cuGraphicsMapResources;
|
||||
tcuGraphicsUnmapResources *cuGraphicsUnmapResources;
|
||||
tcuGraphicsSubResourceGetMappedArray *cuGraphicsSubResourceGetMappedArray;
|
||||
tcuMemcpy2D_v2 *cuMemcpy2D;
|
||||
HMODULE dll;
|
||||
} cuda_functions = {};
|
||||
|
||||
CUresult last_cuda_error = CUDA_SUCCESS;
|
||||
CUcontext cuda_context = nullptr;
|
||||
CUgraphicsResource cuda_d3d_input_texture = nullptr;
|
||||
CUdeviceptr cuda_surface = 0;
|
||||
size_t cuda_surface_pitch = 0;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11_on_cuda.h
|
||||
* @brief Declarations for CUDA NVENC encoder with Direct3D11 input surfaces.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
// lib includes
|
||||
#include <ffnvcodec/dynlink_cuda.h>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_d3d11.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief Interop Direct3D11 on CUDA NVENC encoder.
|
||||
* Input surface is Direct3D11, encoding is performed by CUDA.
|
||||
*/
|
||||
class nvenc_d3d11_on_cuda final: public nvenc_d3d11 {
|
||||
public:
|
||||
/**
|
||||
* @param d3d_device Direct3D11 device that will create input surface texture.
|
||||
* CUDA encoding device will be derived from it.
|
||||
*/
|
||||
explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device);
|
||||
~nvenc_d3d11_on_cuda();
|
||||
|
||||
ID3D11Texture2D *get_input_texture() override;
|
||||
|
||||
private:
|
||||
bool init_library() override;
|
||||
|
||||
bool create_and_register_input_buffer() override;
|
||||
|
||||
bool synchronize_input_buffer() override;
|
||||
|
||||
bool cuda_succeeded(CUresult result);
|
||||
|
||||
bool cuda_failed(CUresult result);
|
||||
|
||||
struct autopop_context {
|
||||
autopop_context(nvenc_d3d11_on_cuda &parent, CUcontext pushed_context):
|
||||
parent(parent),
|
||||
pushed_context(pushed_context) {
|
||||
}
|
||||
|
||||
~autopop_context();
|
||||
|
||||
explicit operator bool() const {
|
||||
return pushed_context != nullptr;
|
||||
}
|
||||
|
||||
nvenc_d3d11_on_cuda &parent;
|
||||
CUcontext pushed_context = nullptr;
|
||||
};
|
||||
|
||||
autopop_context push_context();
|
||||
|
||||
HMODULE dll = NULL;
|
||||
const ID3D11DevicePtr d3d_device;
|
||||
ID3D11Texture2DPtr d3d_input_texture;
|
||||
|
||||
struct {
|
||||
tcuInit *cuInit;
|
||||
tcuD3D11GetDevice *cuD3D11GetDevice;
|
||||
tcuCtxCreate_v2 *cuCtxCreate;
|
||||
tcuCtxDestroy_v2 *cuCtxDestroy;
|
||||
tcuCtxPushCurrent_v2 *cuCtxPushCurrent;
|
||||
tcuCtxPopCurrent_v2 *cuCtxPopCurrent;
|
||||
tcuMemAllocPitch_v2 *cuMemAllocPitch;
|
||||
tcuMemFree_v2 *cuMemFree;
|
||||
tcuGraphicsD3D11RegisterResource *cuGraphicsD3D11RegisterResource;
|
||||
tcuGraphicsUnregisterResource *cuGraphicsUnregisterResource;
|
||||
tcuGraphicsMapResources *cuGraphicsMapResources;
|
||||
tcuGraphicsUnmapResources *cuGraphicsUnmapResources;
|
||||
tcuGraphicsSubResourceGetMappedArray *cuGraphicsSubResourceGetMappedArray;
|
||||
tcuMemcpy2D_v2 *cuMemcpy2D;
|
||||
HMODULE dll;
|
||||
} cuda_functions = {};
|
||||
|
||||
CUresult last_cuda_error = CUDA_SUCCESS;
|
||||
CUcontext cuda_context = nullptr;
|
||||
CUgraphicsResource cuda_d3d_input_texture = nullptr;
|
||||
CUdeviceptr cuda_surface = 0;
|
||||
size_t cuda_surface_pitch = 0;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_encoded_frame.h
|
||||
* @brief Declarations for NVENC encoded frame.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief Encoded frame.
|
||||
*/
|
||||
struct nvenc_encoded_frame {
|
||||
std::vector<uint8_t> data;
|
||||
uint64_t frame_index = 0;
|
||||
bool idr = false;
|
||||
bool after_ref_frame_invalidation = false;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
/**
|
||||
* @file src/nvenc/nvenc_encoded_frame.h
|
||||
* @brief Declarations for NVENC encoded frame.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief Encoded frame.
|
||||
*/
|
||||
struct nvenc_encoded_frame {
|
||||
std::vector<uint8_t> data;
|
||||
uint64_t frame_index = 0;
|
||||
bool idr = false;
|
||||
bool after_ref_frame_invalidation = false;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
|
||||
@@ -1,94 +1,93 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_utils.cpp
|
||||
* @brief Definitions for NVENC utilities.
|
||||
*/
|
||||
#include <cassert>
|
||||
|
||||
#include "nvenc_utils.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
#ifdef _WIN32
|
||||
DXGI_FORMAT
|
||||
dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format) {
|
||||
switch (format) {
|
||||
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
|
||||
return DXGI_FORMAT_P010;
|
||||
|
||||
case NV_ENC_BUFFER_FORMAT_NV12:
|
||||
return DXGI_FORMAT_NV12;
|
||||
|
||||
case NV_ENC_BUFFER_FORMAT_AYUV:
|
||||
return DXGI_FORMAT_AYUV;
|
||||
|
||||
case NV_ENC_BUFFER_FORMAT_YUV444_10BIT:
|
||||
return DXGI_FORMAT_R16_UINT;
|
||||
|
||||
default:
|
||||
return DXGI_FORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
NV_ENC_BUFFER_FORMAT
|
||||
nvenc_format_from_sunshine_format(platf::pix_fmt_e format) {
|
||||
switch (format) {
|
||||
case platf::pix_fmt_e::nv12:
|
||||
return NV_ENC_BUFFER_FORMAT_NV12;
|
||||
|
||||
case platf::pix_fmt_e::p010:
|
||||
return NV_ENC_BUFFER_FORMAT_YUV420_10BIT;
|
||||
|
||||
case platf::pix_fmt_e::ayuv:
|
||||
return NV_ENC_BUFFER_FORMAT_AYUV;
|
||||
|
||||
case platf::pix_fmt_e::yuv444p16:
|
||||
return NV_ENC_BUFFER_FORMAT_YUV444_10BIT;
|
||||
|
||||
default:
|
||||
return NV_ENC_BUFFER_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
nvenc_colorspace_t
|
||||
nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace) {
|
||||
nvenc_colorspace_t colorspace;
|
||||
|
||||
switch (sunshine_colorspace.colorspace) {
|
||||
case video::colorspace_e::rec601:
|
||||
// Rec. 601
|
||||
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_SMPTE170M;
|
||||
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE170M;
|
||||
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_SMPTE170M;
|
||||
break;
|
||||
|
||||
case video::colorspace_e::rec709:
|
||||
// Rec. 709
|
||||
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_BT709;
|
||||
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT709;
|
||||
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_BT709;
|
||||
break;
|
||||
|
||||
case video::colorspace_e::bt2020sdr:
|
||||
// Rec. 2020
|
||||
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_BT2020;
|
||||
assert(sunshine_colorspace.bit_depth == 10);
|
||||
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT2020_10;
|
||||
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_BT2020_NCL;
|
||||
break;
|
||||
|
||||
case video::colorspace_e::bt2020:
|
||||
// Rec. 2020 with ST 2084 perceptual quantizer
|
||||
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_BT2020;
|
||||
assert(sunshine_colorspace.bit_depth == 10);
|
||||
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE2084;
|
||||
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_BT2020_NCL;
|
||||
break;
|
||||
}
|
||||
|
||||
colorspace.full_range = sunshine_colorspace.full_range;
|
||||
|
||||
return colorspace;
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
/**
|
||||
* @file src/nvenc/nvenc_utils.cpp
|
||||
* @brief Definitions for NVENC utilities.
|
||||
*/
|
||||
// standard includes
|
||||
#include <cassert>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_utils.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
#ifdef _WIN32
|
||||
DXGI_FORMAT dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format) {
|
||||
switch (format) {
|
||||
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
|
||||
return DXGI_FORMAT_P010;
|
||||
|
||||
case NV_ENC_BUFFER_FORMAT_NV12:
|
||||
return DXGI_FORMAT_NV12;
|
||||
|
||||
case NV_ENC_BUFFER_FORMAT_AYUV:
|
||||
return DXGI_FORMAT_AYUV;
|
||||
|
||||
case NV_ENC_BUFFER_FORMAT_YUV444_10BIT:
|
||||
return DXGI_FORMAT_R16_UINT;
|
||||
|
||||
default:
|
||||
return DXGI_FORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
NV_ENC_BUFFER_FORMAT nvenc_format_from_sunshine_format(platf::pix_fmt_e format) {
|
||||
switch (format) {
|
||||
case platf::pix_fmt_e::nv12:
|
||||
return NV_ENC_BUFFER_FORMAT_NV12;
|
||||
|
||||
case platf::pix_fmt_e::p010:
|
||||
return NV_ENC_BUFFER_FORMAT_YUV420_10BIT;
|
||||
|
||||
case platf::pix_fmt_e::ayuv:
|
||||
return NV_ENC_BUFFER_FORMAT_AYUV;
|
||||
|
||||
case platf::pix_fmt_e::yuv444p16:
|
||||
return NV_ENC_BUFFER_FORMAT_YUV444_10BIT;
|
||||
|
||||
default:
|
||||
return NV_ENC_BUFFER_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace) {
|
||||
nvenc_colorspace_t colorspace;
|
||||
|
||||
switch (sunshine_colorspace.colorspace) {
|
||||
case video::colorspace_e::rec601:
|
||||
// Rec. 601
|
||||
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_SMPTE170M;
|
||||
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE170M;
|
||||
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_SMPTE170M;
|
||||
break;
|
||||
|
||||
case video::colorspace_e::rec709:
|
||||
// Rec. 709
|
||||
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_BT709;
|
||||
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT709;
|
||||
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_BT709;
|
||||
break;
|
||||
|
||||
case video::colorspace_e::bt2020sdr:
|
||||
// Rec. 2020
|
||||
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_BT2020;
|
||||
assert(sunshine_colorspace.bit_depth == 10);
|
||||
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT2020_10;
|
||||
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_BT2020_NCL;
|
||||
break;
|
||||
|
||||
case video::colorspace_e::bt2020:
|
||||
// Rec. 2020 with ST 2084 perceptual quantizer
|
||||
colorspace.primaries = NV_ENC_VUI_COLOR_PRIMARIES_BT2020;
|
||||
assert(sunshine_colorspace.bit_depth == 10);
|
||||
colorspace.tranfer_function = NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE2084;
|
||||
colorspace.matrix = NV_ENC_VUI_MATRIX_COEFFS_BT2020_NCL;
|
||||
break;
|
||||
}
|
||||
|
||||
colorspace.full_range = sunshine_colorspace.full_range;
|
||||
|
||||
return colorspace;
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_utils.h
|
||||
* @brief Declarations for NVENC utilities.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <dxgiformat.h>
|
||||
#endif
|
||||
|
||||
#include "nvenc_colorspace.h"
|
||||
|
||||
#include "src/platform/common.h"
|
||||
#include "src/video_colorspace.h"
|
||||
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
#ifdef _WIN32
|
||||
DXGI_FORMAT
|
||||
dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format);
|
||||
#endif
|
||||
|
||||
NV_ENC_BUFFER_FORMAT
|
||||
nvenc_format_from_sunshine_format(platf::pix_fmt_e format);
|
||||
|
||||
nvenc_colorspace_t
|
||||
nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace);
|
||||
|
||||
} // namespace nvenc
|
||||
/**
|
||||
* @file src/nvenc/nvenc_utils.h
|
||||
* @brief Declarations for NVENC utilities.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// plafform includes
|
||||
#ifdef _WIN32
|
||||
#include <dxgiformat.h>
|
||||
#endif
|
||||
|
||||
// lib includes
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_colorspace.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/video_colorspace.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
#ifdef _WIN32
|
||||
DXGI_FORMAT dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format);
|
||||
#endif
|
||||
|
||||
NV_ENC_BUFFER_FORMAT nvenc_format_from_sunshine_format(platf::pix_fmt_e format);
|
||||
|
||||
nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace);
|
||||
|
||||
} // namespace nvenc
|
||||
|
||||
263
src/nvhttp.cpp
263
src/nvhttp.cpp
@@ -7,16 +7,16 @@
|
||||
|
||||
// standard includes
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
// lib includes
|
||||
#include <Simple-Web-Server/server_http.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/asio/ssl/context_base.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
#include <string>
|
||||
#include <Simple-Web-Server/server_http.hpp>
|
||||
|
||||
// local includes
|
||||
#include "config.h"
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "video.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace nvhttp {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
@@ -61,8 +62,7 @@ namespace nvhttp {
|
||||
protected:
|
||||
boost::asio::ssl::context context;
|
||||
|
||||
void
|
||||
after_bind() override {
|
||||
void after_bind() override {
|
||||
if (verify) {
|
||||
context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once);
|
||||
context.set_verify_callback([](int verified, boost::asio::ssl::verify_context &ctx) {
|
||||
@@ -73,17 +73,18 @@ namespace nvhttp {
|
||||
}
|
||||
|
||||
// This is Server<HTTPS>::accept() with SSL validation support added
|
||||
void
|
||||
accept() override {
|
||||
void accept() override {
|
||||
auto connection = create_connection(*io_service, context);
|
||||
|
||||
acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const SimpleWeb::error_code &ec) {
|
||||
auto lock = connection->handler_runner->continue_lock();
|
||||
if (!lock)
|
||||
if (!lock) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ec != SimpleWeb::error::operation_aborted)
|
||||
if (ec != SimpleWeb::error::operation_aborted) {
|
||||
this->accept();
|
||||
}
|
||||
|
||||
auto session = std::make_shared<Session>(config.max_request_streambuf_size, connection);
|
||||
|
||||
@@ -96,20 +97,22 @@ namespace nvhttp {
|
||||
session->connection->socket->async_handshake(boost::asio::ssl::stream_base::server, [this, session](const SimpleWeb::error_code &ec) {
|
||||
session->connection->cancel_timeout();
|
||||
auto lock = session->connection->handler_runner->continue_lock();
|
||||
if (!lock)
|
||||
if (!lock) {
|
||||
return;
|
||||
if (!ec) {
|
||||
if (verify && !verify(session->connection->socket->native_handle()))
|
||||
this->write(session, on_verify_failed);
|
||||
else
|
||||
this->read(session);
|
||||
}
|
||||
else if (this->on_error)
|
||||
if (!ec) {
|
||||
if (verify && !verify(session->connection->socket->native_handle())) {
|
||||
this->write(session, on_verify_failed);
|
||||
} else {
|
||||
this->read(session);
|
||||
}
|
||||
} else if (this->on_error) {
|
||||
this->on_error(session->request, ec);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (this->on_error)
|
||||
} else if (this->on_error) {
|
||||
this->on_error(session->request, ec);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -148,8 +151,7 @@ namespace nvhttp {
|
||||
REMOVE ///< Remove certificate
|
||||
};
|
||||
|
||||
std::string
|
||||
get_arg(const args_t &args, const char *name, const char *default_value = nullptr) {
|
||||
std::string get_arg(const args_t &args, const char *name, const char *default_value = nullptr) {
|
||||
auto it = args.find(name);
|
||||
if (it == std::end(args)) {
|
||||
if (default_value != NULL) {
|
||||
@@ -161,15 +163,13 @@ namespace nvhttp {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void
|
||||
save_state() {
|
||||
void save_state() {
|
||||
pt::ptree root;
|
||||
|
||||
if (fs::exists(config::nvhttp.file_state)) {
|
||||
try {
|
||||
pt::read_json(config::nvhttp.file_state, root);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
|
||||
return;
|
||||
}
|
||||
@@ -193,15 +193,13 @@ namespace nvhttp {
|
||||
|
||||
try {
|
||||
pt::write_json(config::nvhttp.file_state, root);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "Couldn't write "sv << config::nvhttp.file_state << ": "sv << e.what();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
load_state() {
|
||||
void load_state() {
|
||||
if (!fs::exists(config::nvhttp.file_state)) {
|
||||
BOOST_LOG(info) << "File "sv << config::nvhttp.file_state << " doesn't exist"sv;
|
||||
http::unique_id = uuid_util::uuid_t::generate().string();
|
||||
@@ -211,8 +209,7 @@ namespace nvhttp {
|
||||
pt::ptree tree;
|
||||
try {
|
||||
pt::read_json(config::nvhttp.file_state, tree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
|
||||
|
||||
return;
|
||||
@@ -266,8 +263,7 @@ namespace nvhttp {
|
||||
client_root = client;
|
||||
}
|
||||
|
||||
void
|
||||
add_authorized_client(const std::string &name, std::string &&cert) {
|
||||
void add_authorized_client(const std::string &name, std::string &&cert) {
|
||||
client_t &client = client_root;
|
||||
named_cert_t named_cert;
|
||||
named_cert.name = name;
|
||||
@@ -280,8 +276,7 @@ namespace nvhttp {
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<rtsp_stream::launch_session_t>
|
||||
make_launch_session(bool host_audio, const args_t &args) {
|
||||
std::shared_ptr<rtsp_stream::launch_session_t> make_launch_session(bool host_audio, const args_t &args) {
|
||||
auto launch_session = std::make_shared<rtsp_stream::launch_session_t>();
|
||||
|
||||
launch_session->id = ++session_id_counter;
|
||||
@@ -295,9 +290,15 @@ namespace nvhttp {
|
||||
int x = 0;
|
||||
std::string segment;
|
||||
while (std::getline(mode, segment, 'x')) {
|
||||
if (x == 0) launch_session->width = atoi(segment.c_str());
|
||||
if (x == 1) launch_session->height = atoi(segment.c_str());
|
||||
if (x == 2) launch_session->fps = atoi(segment.c_str());
|
||||
if (x == 0) {
|
||||
launch_session->width = atoi(segment.c_str());
|
||||
}
|
||||
if (x == 1) {
|
||||
launch_session->height = atoi(segment.c_str());
|
||||
}
|
||||
if (x == 2) {
|
||||
launch_session->fps = atoi(segment.c_str());
|
||||
}
|
||||
x++;
|
||||
}
|
||||
launch_session->unique_id = (get_arg(args, "uniqueid", "unknown"));
|
||||
@@ -312,7 +313,8 @@ namespace nvhttp {
|
||||
auto corever = util::from_view(get_arg(args, "corever", "0"));
|
||||
if (corever >= 1) {
|
||||
launch_session->rtsp_cipher = crypto::cipher::gcm_t {
|
||||
launch_session->gcm_key, false
|
||||
launch_session->gcm_key,
|
||||
false
|
||||
};
|
||||
launch_session->rtsp_iv_counter = 0;
|
||||
}
|
||||
@@ -331,21 +333,18 @@ namespace nvhttp {
|
||||
return launch_session;
|
||||
}
|
||||
|
||||
void
|
||||
remove_session(const pair_session_t &sess) {
|
||||
void remove_session(const pair_session_t &sess) {
|
||||
map_id_sess.erase(sess.client.uniqueID);
|
||||
}
|
||||
|
||||
void
|
||||
fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
|
||||
void fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
|
||||
tree.put("root.paired", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 400);
|
||||
tree.put("root.<xmlattr>.status_message", status_msg);
|
||||
remove_session(sess); // Security measure, delete the session when something went wrong and force a re-pair
|
||||
}
|
||||
|
||||
void
|
||||
getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
|
||||
void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
|
||||
if (sess.last_phase != PAIR_PHASE::NONE) {
|
||||
fail_pair(sess, tree, "Out of order call to getservercert");
|
||||
return;
|
||||
@@ -357,7 +356,7 @@ namespace nvhttp {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string_view salt_view { sess.async_insert_pin.salt.data(), 32 };
|
||||
std::string_view salt_view {sess.async_insert_pin.salt.data(), 32};
|
||||
|
||||
auto salt = util::from_hex<std::array<uint8_t, 16>>(salt_view, true);
|
||||
|
||||
@@ -369,8 +368,7 @@ namespace nvhttp {
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
}
|
||||
|
||||
void
|
||||
clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
|
||||
void clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
|
||||
if (sess.last_phase != PAIR_PHASE::GETSERVERCERT) {
|
||||
fail_pair(sess, tree, "Out of order call to clientchallenge");
|
||||
return;
|
||||
@@ -393,7 +391,7 @@ namespace nvhttp {
|
||||
decrypted.insert(std::end(decrypted), std::begin(sign), std::end(sign));
|
||||
decrypted.insert(std::end(decrypted), std::begin(serversecret), std::end(serversecret));
|
||||
|
||||
auto hash = crypto::hash({ (char *) decrypted.data(), decrypted.size() });
|
||||
auto hash = crypto::hash({(char *) decrypted.data(), decrypted.size()});
|
||||
auto serverchallenge = crypto::rand(16);
|
||||
|
||||
std::string plaintext;
|
||||
@@ -413,8 +411,7 @@ namespace nvhttp {
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
}
|
||||
|
||||
void
|
||||
serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
|
||||
void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
|
||||
if (sess.last_phase != PAIR_PHASE::CLIENTCHALLENGE) {
|
||||
fail_pair(sess, tree, "Out of order call to serverchallengeresp");
|
||||
return;
|
||||
@@ -443,8 +440,7 @@ namespace nvhttp {
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
}
|
||||
|
||||
void
|
||||
clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, pt::ptree &tree, const std::string &client_pairing_secret) {
|
||||
void clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, pt::ptree &tree, const std::string &client_pairing_secret) {
|
||||
if (sess.last_phase != PAIR_PHASE::SERVERCHALLENGERESP) {
|
||||
fail_pair(sess, tree, "Out of order call to clientpairingsecret");
|
||||
return;
|
||||
@@ -458,8 +454,8 @@ namespace nvhttp {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string_view secret { client_pairing_secret.data(), 16 };
|
||||
std::string_view sign { client_pairing_secret.data() + secret.size(), client_pairing_secret.size() - secret.size() };
|
||||
std::string_view secret {client_pairing_secret.data(), 16};
|
||||
std::string_view sign {client_pairing_secret.data() + secret.size(), client_pairing_secret.size() - secret.size()};
|
||||
|
||||
auto x509 = crypto::x509(client.cert);
|
||||
if (!x509) {
|
||||
@@ -486,8 +482,7 @@ namespace nvhttp {
|
||||
|
||||
// The client is now successfully paired and will be authorized to connect
|
||||
add_authorized_client(client.name, std::move(client.cert));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
tree.put("root.paired", 0);
|
||||
}
|
||||
|
||||
@@ -495,22 +490,21 @@ namespace nvhttp {
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template<class T>
|
||||
struct tunnel;
|
||||
|
||||
template <>
|
||||
template<>
|
||||
struct tunnel<SunshineHTTPS> {
|
||||
static auto constexpr to_string = "HTTPS"sv;
|
||||
};
|
||||
|
||||
template <>
|
||||
template<>
|
||||
struct tunnel<SimpleWeb::HTTP> {
|
||||
static auto constexpr to_string = "NONE"sv;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
void
|
||||
print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
template<class T>
|
||||
void print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string;
|
||||
|
||||
BOOST_LOG(debug) << "METHOD :: "sv << request->method;
|
||||
@@ -529,9 +523,8 @@ namespace nvhttp {
|
||||
BOOST_LOG(debug) << " [--] "sv;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
template<class T>
|
||||
void not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
|
||||
pt::ptree tree;
|
||||
@@ -549,9 +542,8 @@ namespace nvhttp {
|
||||
response->close_connection_after_response = true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
template<class T>
|
||||
void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
|
||||
pt::ptree tree;
|
||||
@@ -572,7 +564,7 @@ namespace nvhttp {
|
||||
return;
|
||||
}
|
||||
|
||||
auto uniqID { get_arg(args, "uniqueid") };
|
||||
auto uniqID {get_arg(args, "uniqueid")};
|
||||
|
||||
args_t::const_iterator it;
|
||||
if (it = args.find("phrase"); it != std::end(args)) {
|
||||
@@ -593,8 +585,7 @@ namespace nvhttp {
|
||||
std::getline(std::cin, pin);
|
||||
|
||||
getservercert(ptr->second, tree, pin);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
|
||||
system_tray::update_tray_require_pin();
|
||||
#endif
|
||||
@@ -603,8 +594,7 @@ namespace nvhttp {
|
||||
fg.disable();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (it->second == "pairchallenge"sv) {
|
||||
} else if (it->second == "pairchallenge"sv) {
|
||||
tree.put("root.paired", 1);
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
return;
|
||||
@@ -622,23 +612,19 @@ namespace nvhttp {
|
||||
if (it = args.find("clientchallenge"); it != std::end(args)) {
|
||||
auto challenge = util::from_hex_vec(it->second, true);
|
||||
clientchallenge(sess_it->second, tree, challenge);
|
||||
}
|
||||
else if (it = args.find("serverchallengeresp"); it != std::end(args)) {
|
||||
} else if (it = args.find("serverchallengeresp"); it != std::end(args)) {
|
||||
auto encrypted_response = util::from_hex_vec(it->second, true);
|
||||
serverchallengeresp(sess_it->second, tree, encrypted_response);
|
||||
}
|
||||
else if (it = args.find("clientpairingsecret"); it != std::end(args)) {
|
||||
} else if (it = args.find("clientpairingsecret"); it != std::end(args)) {
|
||||
auto pairingsecret = util::from_hex_vec(it->second, true);
|
||||
clientpairingsecret(sess_it->second, add_cert, tree, pairingsecret);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
tree.put("root.<xmlattr>.status_code", 404);
|
||||
tree.put("root.<xmlattr>.status_message", "Invalid pairing request");
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
pin(std::string pin, std::string name) {
|
||||
bool pin(std::string pin, std::string name) {
|
||||
pt::ptree tree;
|
||||
if (map_id_sess.empty()) {
|
||||
return false;
|
||||
@@ -649,7 +635,9 @@ namespace nvhttp {
|
||||
tree.put("root.paired", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 400);
|
||||
tree.put(
|
||||
"root.<xmlattr>.status_message", "Pin must be 4 digits, " + std::to_string(pin.size()) + " provided");
|
||||
"root.<xmlattr>.status_message",
|
||||
"Pin must be 4 digits, " + std::to_string(pin.size()) + " provided"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -672,11 +660,9 @@ namespace nvhttp {
|
||||
auto &async_response = sess.async_insert_pin.response;
|
||||
if (async_response.has_left() && async_response.left()) {
|
||||
async_response.left()->write(data.str());
|
||||
}
|
||||
else if (async_response.has_right() && async_response.right()) {
|
||||
} else if (async_response.has_right() && async_response.right()) {
|
||||
async_response.right()->write(data.str());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -686,9 +672,8 @@ namespace nvhttp {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
template<class T>
|
||||
void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
|
||||
int pair_status = 0;
|
||||
@@ -719,8 +704,7 @@ namespace nvhttp {
|
||||
// For HTTP requests, use a placeholder MAC address that Moonlight knows to ignore.
|
||||
if constexpr (std::is_same_v<SunshineHTTPS, T>) {
|
||||
tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address())));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
tree.put("root.mac", "00:00:00:00:00:00");
|
||||
}
|
||||
|
||||
@@ -735,8 +719,7 @@ namespace nvhttp {
|
||||
// support know to ignore this bogus address.
|
||||
if (local_endpoint.address().is_v6() && !local_endpoint.address().to_v6().is_v4_mapped()) {
|
||||
tree.put("root.LocalIP", "127.0.0.1");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
tree.put("root.LocalIP", net::addr_to_normalized_string(local_endpoint.address()));
|
||||
}
|
||||
|
||||
@@ -782,22 +765,20 @@ 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;
|
||||
}
|
||||
|
||||
void
|
||||
applist(resp_https_t response, req_https_t request) {
|
||||
void applist(resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
pt::ptree tree;
|
||||
@@ -825,12 +806,11 @@ namespace nvhttp {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
launch(bool &host_audio, resp_https_t response, req_https_t request) {
|
||||
void launch(bool &host_audio, resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
pt::ptree tree;
|
||||
bool revert_display_configuration { false };
|
||||
bool revert_display_configuration {false};
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
@@ -848,7 +828,8 @@ namespace nvhttp {
|
||||
args.find("rikey"s) == std::end(args) ||
|
||||
args.find("rikeyid"s) == std::end(args) ||
|
||||
args.find("localAudioPlayMode"s) == std::end(args) ||
|
||||
args.find("appid"s) == std::end(args)) {
|
||||
args.find("appid"s) == std::end(args)
|
||||
) {
|
||||
tree.put("root.resume", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 400);
|
||||
tree.put("root.<xmlattr>.status_message", "Missing a required launch parameter");
|
||||
@@ -871,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,
|
||||
@@ -915,9 +896,7 @@ namespace nvhttp {
|
||||
}
|
||||
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme +
|
||||
net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' +
|
||||
std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
|
||||
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
|
||||
tree.put("root.gamesession", 1);
|
||||
|
||||
rtsp_stream::launch_session_raise(launch_session);
|
||||
@@ -926,8 +905,7 @@ namespace nvhttp {
|
||||
revert_display_configuration = false;
|
||||
}
|
||||
|
||||
void
|
||||
resume(bool &host_audio, resp_https_t response, req_https_t request) {
|
||||
void resume(bool &host_audio, resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
pt::ptree tree;
|
||||
@@ -951,7 +929,8 @@ namespace nvhttp {
|
||||
auto args = request->parse_query_string();
|
||||
if (
|
||||
args.find("rikey"s) == std::end(args) ||
|
||||
args.find("rikeyid"s) == std::end(args)) {
|
||||
args.find("rikeyid"s) == std::end(args)
|
||||
) {
|
||||
tree.put("root.resume", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 400);
|
||||
tree.put("root.<xmlattr>.status_message", "Missing a required resume parameter");
|
||||
@@ -962,7 +941,7 @@ namespace nvhttp {
|
||||
// Newer Moonlight clients send localAudioPlayMode on /resume too,
|
||||
// so we should use it if it's present in the args and there are
|
||||
// no active sessions we could be interfering with.
|
||||
const bool no_active_sessions { rtsp_stream::session_count() == 0 };
|
||||
const bool no_active_sessions {rtsp_stream::session_count() == 0};
|
||||
if (no_active_sessions && args.find("localAudioPlayMode"s) != std::end(args)) {
|
||||
host_audio = util::from_view(get_arg(args, "localAudioPlayMode"));
|
||||
}
|
||||
@@ -999,16 +978,13 @@ namespace nvhttp {
|
||||
}
|
||||
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme +
|
||||
net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' +
|
||||
std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
|
||||
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
|
||||
tree.put("root.resume", 1);
|
||||
|
||||
rtsp_stream::launch_session_raise(launch_session);
|
||||
}
|
||||
|
||||
void
|
||||
cancel(resp_https_t response, req_https_t request) {
|
||||
void cancel(resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
pt::ptree tree;
|
||||
@@ -1033,8 +1009,7 @@ namespace nvhttp {
|
||||
display_device::revert_configuration();
|
||||
}
|
||||
|
||||
void
|
||||
appasset(resp_https_t response, req_https_t request) {
|
||||
void appasset(resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
@@ -1047,14 +1022,12 @@ namespace nvhttp {
|
||||
response->close_connection_after_response = true;
|
||||
}
|
||||
|
||||
void
|
||||
setup(const std::string &pkey, const std::string &cert) {
|
||||
void setup(const std::string &pkey, const std::string &cert) {
|
||||
conf_intern.pkey = pkey;
|
||||
conf_intern.servercert = cert;
|
||||
}
|
||||
|
||||
void
|
||||
start() {
|
||||
void start() {
|
||||
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
||||
|
||||
auto port_http = net::map_port(PORT_HTTP);
|
||||
@@ -1077,7 +1050,7 @@ namespace nvhttp {
|
||||
// launch will store it in host_audio
|
||||
bool host_audio {};
|
||||
|
||||
https_server_t https_server { config::nvhttp.cert, config::nvhttp.pkey };
|
||||
https_server_t https_server {config::nvhttp.cert, config::nvhttp.pkey};
|
||||
http_server_t http_server;
|
||||
|
||||
// Verify certificates after establishing connection
|
||||
@@ -1143,11 +1116,17 @@ namespace nvhttp {
|
||||
|
||||
https_server.default_resource["GET"] = not_found<SunshineHTTPS>;
|
||||
https_server.resource["^/serverinfo$"]["GET"] = serverinfo<SunshineHTTPS>;
|
||||
https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SunshineHTTPS>(add_cert, resp, req); };
|
||||
https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) {
|
||||
pair<SunshineHTTPS>(add_cert, resp, req);
|
||||
};
|
||||
https_server.resource["^/applist$"]["GET"] = applist;
|
||||
https_server.resource["^/appasset$"]["GET"] = appasset;
|
||||
https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) { launch(host_audio, resp, req); };
|
||||
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) { resume(host_audio, resp, req); };
|
||||
https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) {
|
||||
launch(host_audio, resp, req);
|
||||
};
|
||||
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) {
|
||||
resume(host_audio, resp, req);
|
||||
};
|
||||
https_server.resource["^/cancel$"]["GET"] = cancel;
|
||||
|
||||
https_server.config.reuse_address = true;
|
||||
@@ -1156,7 +1135,9 @@ namespace nvhttp {
|
||||
|
||||
http_server.default_resource["GET"] = not_found<SimpleWeb::HTTP>;
|
||||
http_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTP>;
|
||||
http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTP>(add_cert, resp, req); };
|
||||
http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) {
|
||||
pair<SimpleWeb::HTTP>(add_cert, resp, req);
|
||||
};
|
||||
|
||||
http_server.config.reuse_address = true;
|
||||
http_server.config.address = net::af_to_any_address_string(address_family);
|
||||
@@ -1165,8 +1146,7 @@ namespace nvhttp {
|
||||
auto accept_and_run = [&](auto *http_server) {
|
||||
try {
|
||||
http_server->start();
|
||||
}
|
||||
catch (boost::system::system_error &err) {
|
||||
} catch (boost::system::system_error &err) {
|
||||
// It's possible the exception gets thrown after calling http_server->stop() from a different thread
|
||||
if (shutdown_event->peek()) {
|
||||
return;
|
||||
@@ -1177,8 +1157,8 @@ namespace nvhttp {
|
||||
return;
|
||||
}
|
||||
};
|
||||
std::thread ssl { accept_and_run, &https_server };
|
||||
std::thread tcp { accept_and_run, &http_server };
|
||||
std::thread ssl {accept_and_run, &https_server};
|
||||
std::thread tcp {accept_and_run, &http_server};
|
||||
|
||||
// Wait for any event
|
||||
shutdown_event->view();
|
||||
@@ -1190,24 +1170,21 @@ namespace nvhttp {
|
||||
tcp.join();
|
||||
}
|
||||
|
||||
void
|
||||
erase_all_clients() {
|
||||
void erase_all_clients() {
|
||||
client_t client;
|
||||
client_root = client;
|
||||
cert_chain.clear();
|
||||
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++;
|
||||
}
|
||||
else {
|
||||
removed = true;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
42
src/nvhttp.h
42
src/nvhttp.h
@@ -9,8 +9,9 @@
|
||||
#include <string>
|
||||
|
||||
// lib includes
|
||||
#include <Simple-Web-Server/server_https.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <Simple-Web-Server/server_https.hpp>
|
||||
|
||||
// local includes
|
||||
#include "crypto.h"
|
||||
@@ -49,21 +50,20 @@ namespace nvhttp {
|
||||
* nvhttp::start();
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
start();
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief Setup the nvhttp server.
|
||||
* @param pkey
|
||||
* @param cert
|
||||
*/
|
||||
void
|
||||
setup(const std::string &pkey, const std::string &cert);
|
||||
void setup(const std::string &pkey, const std::string &cert);
|
||||
|
||||
class SunshineHTTPS: public SimpleWeb::HTTPS {
|
||||
public:
|
||||
SunshineHTTPS(boost::asio::io_context &io_context, boost::asio::ssl::context &ctx):
|
||||
SimpleWeb::HTTPS(io_context, ctx) {}
|
||||
SimpleWeb::HTTPS(io_context, ctx) {
|
||||
}
|
||||
|
||||
virtual ~SunshineHTTPS() {
|
||||
// Gracefully shutdown the TLS connection
|
||||
@@ -111,8 +111,7 @@ namespace nvhttp {
|
||||
* @brief removes the temporary pairing session
|
||||
* @param sess
|
||||
*/
|
||||
void
|
||||
remove_session(const pair_session_t &sess);
|
||||
void remove_session(const pair_session_t &sess);
|
||||
|
||||
/**
|
||||
* @brief Pair, phase 1
|
||||
@@ -124,8 +123,7 @@ namespace nvhttp {
|
||||
*
|
||||
* At this stage we only have to send back our public certificate.
|
||||
*/
|
||||
void
|
||||
getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin);
|
||||
void getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin);
|
||||
|
||||
/**
|
||||
* @brief Pair, phase 2
|
||||
@@ -139,8 +137,7 @@ namespace nvhttp {
|
||||
*
|
||||
* The hash + server_challenge will then be AES encrypted and sent as the `challengeresponse` in the returned XML
|
||||
*/
|
||||
void
|
||||
clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge);
|
||||
void clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge);
|
||||
|
||||
/**
|
||||
* @brief Pair, phase 3
|
||||
@@ -149,8 +146,7 @@ namespace nvhttp {
|
||||
* we have to send back the `pairingsecret`:
|
||||
* using our private key we have to sign the certificate_signature + server_secret (generated in phase 2)
|
||||
*/
|
||||
void
|
||||
serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response);
|
||||
void serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response);
|
||||
|
||||
/**
|
||||
* @brief Pair, phase 4 (final)
|
||||
@@ -166,8 +162,7 @@ namespace nvhttp {
|
||||
* Then using the client certificate public key we should be able to verify that
|
||||
* the client secret has been signed by Moonlight
|
||||
*/
|
||||
void
|
||||
clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, boost::property_tree::ptree &tree, const std::string &client_pairing_secret);
|
||||
void clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, boost::property_tree::ptree &tree, const std::string &client_pairing_secret);
|
||||
|
||||
/**
|
||||
* @brief Compare the user supplied pin to the Moonlight pin.
|
||||
@@ -178,27 +173,25 @@ namespace nvhttp {
|
||||
* bool pin_status = nvhttp::pin("1234", "laptop");
|
||||
* @examples_end
|
||||
*/
|
||||
bool
|
||||
pin(std::string pin, std::string name);
|
||||
bool pin(std::string pin, std::string name);
|
||||
|
||||
/**
|
||||
* @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.
|
||||
@@ -206,6 +199,5 @@ namespace nvhttp {
|
||||
* nvhttp::erase_all_clients();
|
||||
* @examples_end
|
||||
*/
|
||||
void
|
||||
erase_all_clients();
|
||||
void erase_all_clients();
|
||||
} // namespace nvhttp
|
||||
|
||||
@@ -4,18 +4,21 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <bitset>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
// lib includes
|
||||
#include <boost/core/noncopyable.hpp>
|
||||
#ifndef _WIN32
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/process.hpp>
|
||||
#endif
|
||||
|
||||
// local includes
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/thread_safe.h"
|
||||
@@ -44,13 +47,15 @@ namespace boost {
|
||||
class address;
|
||||
} // namespace ip
|
||||
} // namespace asio
|
||||
|
||||
namespace filesystem {
|
||||
class path;
|
||||
}
|
||||
|
||||
namespace process::inline v1 {
|
||||
class child;
|
||||
class group;
|
||||
template <typename Char>
|
||||
template<typename Char>
|
||||
class basic_environment;
|
||||
typedef basic_environment<char> environment;
|
||||
} // namespace process::inline v1
|
||||
@@ -59,6 +64,7 @@ namespace boost {
|
||||
namespace video {
|
||||
struct config_t;
|
||||
} // namespace video
|
||||
|
||||
namespace nvenc {
|
||||
class nvenc_base;
|
||||
}
|
||||
@@ -103,26 +109,23 @@ namespace platf {
|
||||
};
|
||||
|
||||
struct gamepad_feedback_msg_t {
|
||||
static gamepad_feedback_msg_t
|
||||
make_rumble(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) {
|
||||
static gamepad_feedback_msg_t make_rumble(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) {
|
||||
gamepad_feedback_msg_t msg;
|
||||
msg.type = gamepad_feedback_e::rumble;
|
||||
msg.id = id;
|
||||
msg.data.rumble = { lowfreq, highfreq };
|
||||
msg.data.rumble = {lowfreq, highfreq};
|
||||
return msg;
|
||||
}
|
||||
|
||||
static gamepad_feedback_msg_t
|
||||
make_rumble_triggers(std::uint16_t id, std::uint16_t left, std::uint16_t right) {
|
||||
static gamepad_feedback_msg_t make_rumble_triggers(std::uint16_t id, std::uint16_t left, std::uint16_t right) {
|
||||
gamepad_feedback_msg_t msg;
|
||||
msg.type = gamepad_feedback_e::rumble_triggers;
|
||||
msg.id = id;
|
||||
msg.data.rumble_triggers = { left, right };
|
||||
msg.data.rumble_triggers = {left, right};
|
||||
return msg;
|
||||
}
|
||||
|
||||
static gamepad_feedback_msg_t
|
||||
make_motion_event_state(std::uint16_t id, std::uint8_t motion_type, std::uint16_t report_rate) {
|
||||
static gamepad_feedback_msg_t make_motion_event_state(std::uint16_t id, std::uint8_t motion_type, std::uint16_t report_rate) {
|
||||
gamepad_feedback_msg_t msg;
|
||||
msg.type = gamepad_feedback_e::set_motion_event_state;
|
||||
msg.id = id;
|
||||
@@ -131,30 +134,33 @@ namespace platf {
|
||||
return msg;
|
||||
}
|
||||
|
||||
static gamepad_feedback_msg_t
|
||||
make_rgb_led(std::uint16_t id, std::uint8_t r, std::uint8_t g, std::uint8_t b) {
|
||||
static gamepad_feedback_msg_t make_rgb_led(std::uint16_t id, std::uint8_t r, std::uint8_t g, std::uint8_t b) {
|
||||
gamepad_feedback_msg_t msg;
|
||||
msg.type = gamepad_feedback_e::set_rgb_led;
|
||||
msg.id = id;
|
||||
msg.data.rgb_led = { r, g, b };
|
||||
msg.data.rgb_led = {r, g, b};
|
||||
return msg;
|
||||
}
|
||||
|
||||
gamepad_feedback_e type;
|
||||
std::uint16_t id;
|
||||
|
||||
union {
|
||||
struct {
|
||||
std::uint16_t lowfreq;
|
||||
std::uint16_t highfreq;
|
||||
} rumble;
|
||||
|
||||
struct {
|
||||
std::uint16_t left_trigger;
|
||||
std::uint16_t right_trigger;
|
||||
} rumble_triggers;
|
||||
|
||||
struct {
|
||||
std::uint16_t report_rate;
|
||||
std::uint8_t motion_type;
|
||||
} motion_event_state;
|
||||
|
||||
struct {
|
||||
std::uint8_t r;
|
||||
std::uint8_t g;
|
||||
@@ -179,7 +185,8 @@ namespace platf {
|
||||
};
|
||||
|
||||
constexpr std::uint8_t map_stereo[] {
|
||||
FRONT_LEFT, FRONT_RIGHT
|
||||
FRONT_LEFT,
|
||||
FRONT_RIGHT
|
||||
};
|
||||
constexpr std::uint8_t map_surround51[] {
|
||||
FRONT_LEFT,
|
||||
@@ -221,10 +228,9 @@ namespace platf {
|
||||
unknown ///< Unknown
|
||||
};
|
||||
|
||||
inline std::string_view
|
||||
from_pix_fmt(pix_fmt_e pix_fmt) {
|
||||
inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) {
|
||||
using namespace std::literals;
|
||||
#define _CONVERT(x) \
|
||||
#define _CONVERT(x) \
|
||||
case pix_fmt_e::x: \
|
||||
return #x##sv
|
||||
switch (pix_fmt) {
|
||||
@@ -344,10 +350,8 @@ namespace platf {
|
||||
|
||||
img_t(img_t &&) = delete;
|
||||
img_t(const img_t &) = delete;
|
||||
img_t &
|
||||
operator=(img_t &&) = delete;
|
||||
img_t &
|
||||
operator=(const img_t &) = delete;
|
||||
img_t &operator=(img_t &&) = delete;
|
||||
img_t &operator=(const img_t &) = delete;
|
||||
|
||||
std::uint8_t *data {};
|
||||
std::int32_t width {};
|
||||
@@ -371,14 +375,14 @@ namespace platf {
|
||||
std::string surround51;
|
||||
std::string surround71;
|
||||
};
|
||||
|
||||
std::optional<null_t> null;
|
||||
};
|
||||
|
||||
struct encode_device_t {
|
||||
virtual ~encode_device_t() = default;
|
||||
|
||||
virtual int
|
||||
convert(platf::img_t &img) = 0;
|
||||
virtual int convert(platf::img_t &img) = 0;
|
||||
|
||||
video::sunshine_colorspace_t colorspace;
|
||||
};
|
||||
@@ -387,21 +391,18 @@ namespace platf {
|
||||
void *data {};
|
||||
AVFrame *frame {};
|
||||
|
||||
int
|
||||
convert(platf::img_t &img) override {
|
||||
int convert(platf::img_t &img) override {
|
||||
return -1;
|
||||
}
|
||||
|
||||
virtual void
|
||||
apply_colorspace() {
|
||||
virtual void apply_colorspace() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the frame to be encoded.
|
||||
* @note Implementations must take ownership of 'frame'.
|
||||
*/
|
||||
virtual int
|
||||
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
|
||||
virtual int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
|
||||
BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?";
|
||||
return -1;
|
||||
};
|
||||
@@ -410,29 +411,25 @@ namespace platf {
|
||||
* @brief Initialize the hwframes context.
|
||||
* @note Implementations may set parameters during initialization of the hwframes context.
|
||||
*/
|
||||
virtual void
|
||||
init_hwframes(AVHWFramesContext *frames) {};
|
||||
virtual void init_hwframes(AVHWFramesContext *frames) {};
|
||||
|
||||
/**
|
||||
* @brief Provides a hook for allow platform-specific code to adjust codec options.
|
||||
* @note Implementations may set or modify codec options prior to codec initialization.
|
||||
*/
|
||||
virtual void
|
||||
init_codec_options(AVCodecContext *ctx, AVDictionary **options) {};
|
||||
virtual void init_codec_options(AVCodecContext *ctx, AVDictionary **options) {};
|
||||
|
||||
/**
|
||||
* @brief Prepare to derive a context.
|
||||
* @note Implementations may make modifications required before context derivation
|
||||
*/
|
||||
virtual int
|
||||
prepare_to_derive_context(int hw_device_type) {
|
||||
virtual int prepare_to_derive_context(int hw_device_type) {
|
||||
return 0;
|
||||
};
|
||||
};
|
||||
|
||||
struct nvenc_encode_device_t: encode_device_t {
|
||||
virtual bool
|
||||
init_encoder(const video::config_t &client_config, const video::sunshine_colorspace_t &colorspace) = 0;
|
||||
virtual bool init_encoder(const video::config_t &client_config, const video::sunshine_colorspace_t &colorspace) = 0;
|
||||
|
||||
nvenc::nvenc_base *nvenc = nullptr;
|
||||
};
|
||||
@@ -466,7 +463,9 @@ namespace platf {
|
||||
using pull_free_image_cb_t = std::function<bool(std::shared_ptr<img_t> &img_out)>;
|
||||
|
||||
display_t() noexcept:
|
||||
offset_x { 0 }, offset_y { 0 } {}
|
||||
offset_x {0},
|
||||
offset_y {0} {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Capture a frame.
|
||||
@@ -480,32 +479,25 @@ namespace platf {
|
||||
* @retval capture_e::error On error
|
||||
* @retval capture_e::reinit When need of reinitialization
|
||||
*/
|
||||
virtual capture_e
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) = 0;
|
||||
virtual capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) = 0;
|
||||
|
||||
virtual std::shared_ptr<img_t>
|
||||
alloc_img() = 0;
|
||||
virtual std::shared_ptr<img_t> alloc_img() = 0;
|
||||
|
||||
virtual int
|
||||
dummy_img(img_t *img) = 0;
|
||||
virtual int dummy_img(img_t *img) = 0;
|
||||
|
||||
virtual std::unique_ptr<avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(pix_fmt_e pix_fmt) {
|
||||
virtual std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual std::unique_ptr<nvenc_encode_device_t>
|
||||
make_nvenc_encode_device(pix_fmt_e pix_fmt) {
|
||||
virtual std::unique_ptr<nvenc_encode_device_t> make_nvenc_encode_device(pix_fmt_e pix_fmt) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
is_hdr() {
|
||||
virtual bool is_hdr() {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
get_hdr_metadata(SS_HDR_METADATA &metadata) {
|
||||
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) {
|
||||
std::memset(&metadata, 0, sizeof(metadata));
|
||||
return false;
|
||||
}
|
||||
@@ -516,8 +508,7 @@ namespace platf {
|
||||
* @param config The codec configuration.
|
||||
* @return `true` if supported, `false` otherwise.
|
||||
*/
|
||||
virtual bool
|
||||
is_codec_supported(std::string_view name, const ::video::config_t &config) {
|
||||
virtual bool is_codec_supported(std::string_view name, const ::video::config_t &config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -531,57 +522,46 @@ namespace platf {
|
||||
|
||||
protected:
|
||||
// collect capture timing data (at loglevel debug)
|
||||
logging::time_delta_periodic_logger sleep_overshoot_logger = { debug, "Frame capture sleep overshoot" };
|
||||
logging::time_delta_periodic_logger sleep_overshoot_logger = {debug, "Frame capture sleep overshoot"};
|
||||
};
|
||||
|
||||
class mic_t {
|
||||
public:
|
||||
virtual capture_e
|
||||
sample(std::vector<float> &frame_buffer) = 0;
|
||||
virtual capture_e sample(std::vector<float> &frame_buffer) = 0;
|
||||
|
||||
virtual ~mic_t() = default;
|
||||
};
|
||||
|
||||
class audio_control_t {
|
||||
public:
|
||||
virtual int
|
||||
set_sink(const std::string &sink) = 0;
|
||||
virtual int set_sink(const std::string &sink) = 0;
|
||||
|
||||
virtual std::unique_ptr<mic_t>
|
||||
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0;
|
||||
virtual std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if the audio sink is available in the system.
|
||||
* @param sink Sink to be checked.
|
||||
* @returns True if available, false otherwise.
|
||||
*/
|
||||
virtual bool
|
||||
is_sink_available(const std::string &sink) = 0;
|
||||
virtual bool is_sink_available(const std::string &sink) = 0;
|
||||
|
||||
virtual std::optional<sink_t>
|
||||
sink_info() = 0;
|
||||
virtual std::optional<sink_t> sink_info() = 0;
|
||||
|
||||
virtual ~audio_control_t() = default;
|
||||
};
|
||||
|
||||
void
|
||||
freeInput(void *);
|
||||
void freeInput(void *);
|
||||
|
||||
using input_t = util::safe_ptr<void, freeInput>;
|
||||
|
||||
std::filesystem::path
|
||||
appdata();
|
||||
std::filesystem::path appdata();
|
||||
|
||||
std::string
|
||||
get_mac_address(const std::string_view &address);
|
||||
std::string get_mac_address(const std::string_view &address);
|
||||
|
||||
std::string
|
||||
from_sockaddr(const sockaddr *const);
|
||||
std::pair<std::uint16_t, std::string>
|
||||
from_sockaddr_ex(const sockaddr *const);
|
||||
std::string from_sockaddr(const sockaddr *const);
|
||||
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const);
|
||||
|
||||
std::unique_ptr<audio_control_t>
|
||||
audio_control();
|
||||
std::unique_ptr<audio_control_t> audio_control();
|
||||
|
||||
/**
|
||||
* @brief Get the display_t instance for the given hwdevice_type.
|
||||
@@ -591,22 +571,18 @@ namespace platf {
|
||||
* @param config Stream configuration
|
||||
* @return The display_t instance based on hwdevice_type.
|
||||
*/
|
||||
std::shared_ptr<display_t>
|
||||
display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
|
||||
// A list of names of displays accepted as display_name with the mem_type_e
|
||||
std::vector<std::string>
|
||||
display_names(mem_type_e hwdevice_type);
|
||||
std::vector<std::string> display_names(mem_type_e hwdevice_type);
|
||||
|
||||
/**
|
||||
* @brief Check if GPUs/drivers have changed since the last call to this function.
|
||||
* @return `true` if a change has occurred or if it is unknown whether a change occurred.
|
||||
*/
|
||||
bool
|
||||
needs_encoder_reenumeration();
|
||||
bool needs_encoder_reenumeration();
|
||||
|
||||
boost::process::v1::child
|
||||
run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const boost::process::v1::environment &env, FILE *file, std::error_code &ec, boost::process::v1::group *group);
|
||||
boost::process::v1::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const boost::process::v1::environment &env, FILE *file, std::error_code &ec, boost::process::v1::group *group);
|
||||
|
||||
enum class thread_priority_e : int {
|
||||
low, ///< Low priority
|
||||
@@ -614,17 +590,13 @@ namespace platf {
|
||||
high, ///< High priority
|
||||
critical ///< Critical priority
|
||||
};
|
||||
void
|
||||
adjust_thread_priority(thread_priority_e priority);
|
||||
void adjust_thread_priority(thread_priority_e priority);
|
||||
|
||||
// Allow OS-specific actions to be taken to prepare for streaming
|
||||
void
|
||||
streaming_will_start();
|
||||
void
|
||||
streaming_will_stop();
|
||||
void streaming_will_start();
|
||||
void streaming_will_stop();
|
||||
|
||||
void
|
||||
restart();
|
||||
void restart();
|
||||
|
||||
/**
|
||||
* @brief Set an environment variable.
|
||||
@@ -632,16 +604,14 @@ namespace platf {
|
||||
* @param value The value to set the environment variable to.
|
||||
* @return 0 on success, non-zero on failure.
|
||||
*/
|
||||
int
|
||||
set_env(const std::string &name, const std::string &value);
|
||||
int set_env(const std::string &name, const std::string &value);
|
||||
|
||||
/**
|
||||
* @brief Unset an environment variable.
|
||||
* @param name The name of the environment variable.
|
||||
* @return 0 on success, non-zero on failure.
|
||||
*/
|
||||
int
|
||||
unset_env(const std::string &name);
|
||||
int unset_env(const std::string &name);
|
||||
|
||||
struct buffer_descriptor_t {
|
||||
const char *buffer;
|
||||
@@ -649,9 +619,14 @@ namespace platf {
|
||||
|
||||
// Constructors required for emplace_back() prior to C++20
|
||||
buffer_descriptor_t(const char *buffer, size_t size):
|
||||
buffer(buffer), size(size) {}
|
||||
buffer(buffer),
|
||||
size(size) {
|
||||
}
|
||||
|
||||
buffer_descriptor_t():
|
||||
buffer(nullptr), size(0) {}
|
||||
buffer(nullptr),
|
||||
size(0) {
|
||||
}
|
||||
};
|
||||
|
||||
struct batched_send_info_t {
|
||||
@@ -682,24 +657,22 @@ namespace platf {
|
||||
* @param offset The offset in the total payload data (bytes).
|
||||
* @return Buffer descriptor describing the region at the given offset.
|
||||
*/
|
||||
buffer_descriptor_t
|
||||
buffer_for_payload_offset(ptrdiff_t offset) {
|
||||
buffer_descriptor_t buffer_for_payload_offset(ptrdiff_t offset) {
|
||||
for (const auto &desc : payload_buffers) {
|
||||
if (offset < desc.size) {
|
||||
return {
|
||||
desc.buffer + offset,
|
||||
desc.size - offset,
|
||||
};
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
offset -= desc.size;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
bool
|
||||
send_batch(batched_send_info_t &send_info);
|
||||
|
||||
bool send_batch(batched_send_info_t &send_info);
|
||||
|
||||
struct send_info_t {
|
||||
const char *header;
|
||||
@@ -712,8 +685,8 @@ namespace platf {
|
||||
uint16_t target_port;
|
||||
boost::asio::ip::address &source_address;
|
||||
};
|
||||
bool
|
||||
send(send_info_t &send_info);
|
||||
|
||||
bool send(send_info_t &send_info);
|
||||
|
||||
enum class qos_data_type_e : int {
|
||||
audio, ///< Audio
|
||||
@@ -728,34 +701,29 @@ namespace platf {
|
||||
* @param data_type The type of traffic sent on this socket.
|
||||
* @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic.
|
||||
*/
|
||||
std::unique_ptr<deinit_t>
|
||||
enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging);
|
||||
std::unique_ptr<deinit_t> enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging);
|
||||
|
||||
/**
|
||||
* @brief Open a url in the default web browser.
|
||||
* @param url The url to open.
|
||||
*/
|
||||
void
|
||||
open_url(const std::string &url);
|
||||
void open_url(const std::string &url);
|
||||
|
||||
/**
|
||||
* @brief Attempt to gracefully terminate a process group.
|
||||
* @param native_handle The native handle of the process group.
|
||||
* @return `true` if termination was successfully requested.
|
||||
*/
|
||||
bool
|
||||
request_process_group_exit(std::uintptr_t native_handle);
|
||||
bool request_process_group_exit(std::uintptr_t native_handle);
|
||||
|
||||
/**
|
||||
* @brief Check if a process group still has running children.
|
||||
* @param native_handle The native handle of the process group.
|
||||
* @return `true` if processes are still running.
|
||||
*/
|
||||
bool
|
||||
process_group_running(std::uintptr_t native_handle);
|
||||
bool process_group_running(std::uintptr_t native_handle);
|
||||
|
||||
input_t
|
||||
input();
|
||||
input_t input();
|
||||
/**
|
||||
* @brief Get the current mouse position on screen
|
||||
* @param input The input_t instance to use.
|
||||
@@ -764,24 +732,15 @@ namespace platf {
|
||||
* auto [x, y] = get_mouse_loc(input);
|
||||
* @examples_end
|
||||
*/
|
||||
util::point_t
|
||||
get_mouse_loc(input_t &input);
|
||||
void
|
||||
move_mouse(input_t &input, int deltaX, int deltaY);
|
||||
void
|
||||
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
|
||||
void
|
||||
button_mouse(input_t &input, int button, bool release);
|
||||
void
|
||||
scroll(input_t &input, int distance);
|
||||
void
|
||||
hscroll(input_t &input, int distance);
|
||||
void
|
||||
keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags);
|
||||
void
|
||||
gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state);
|
||||
void
|
||||
unicode(input_t &input, char *utf8, int size);
|
||||
util::point_t get_mouse_loc(input_t &input);
|
||||
void move_mouse(input_t &input, int deltaX, int deltaY);
|
||||
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
|
||||
void button_mouse(input_t &input, int button, bool release);
|
||||
void scroll(input_t &input, int distance);
|
||||
void hscroll(input_t &input, int distance);
|
||||
void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags);
|
||||
void gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state);
|
||||
void unicode(input_t &input, char *utf8, int size);
|
||||
|
||||
typedef deinit_t client_input_t;
|
||||
|
||||
@@ -790,8 +749,7 @@ namespace platf {
|
||||
* @param input The global input context.
|
||||
* @return A unique pointer to a per-client input data context.
|
||||
*/
|
||||
std::unique_ptr<client_input_t>
|
||||
allocate_client_input_context(input_t &input);
|
||||
std::unique_ptr<client_input_t> allocate_client_input_context(input_t &input);
|
||||
|
||||
/**
|
||||
* @brief Send a touch event to the OS.
|
||||
@@ -799,8 +757,7 @@ namespace platf {
|
||||
* @param touch_port The current viewport for translating to screen coordinates.
|
||||
* @param touch The touch event.
|
||||
*/
|
||||
void
|
||||
touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch);
|
||||
void touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch);
|
||||
|
||||
/**
|
||||
* @brief Send a pen event to the OS.
|
||||
@@ -808,32 +765,28 @@ namespace platf {
|
||||
* @param touch_port The current viewport for translating to screen coordinates.
|
||||
* @param pen The pen event.
|
||||
*/
|
||||
void
|
||||
pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen);
|
||||
void pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen);
|
||||
|
||||
/**
|
||||
* @brief Send a gamepad touch event to the OS.
|
||||
* @param input The global input context.
|
||||
* @param touch The touch event.
|
||||
*/
|
||||
void
|
||||
gamepad_touch(input_t &input, const gamepad_touch_t &touch);
|
||||
void gamepad_touch(input_t &input, const gamepad_touch_t &touch);
|
||||
|
||||
/**
|
||||
* @brief Send a gamepad motion event to the OS.
|
||||
* @param input The global input context.
|
||||
* @param motion The motion event.
|
||||
*/
|
||||
void
|
||||
gamepad_motion(input_t &input, const gamepad_motion_t &motion);
|
||||
void gamepad_motion(input_t &input, const gamepad_motion_t &motion);
|
||||
|
||||
/**
|
||||
* @brief Send a gamepad battery event to the OS.
|
||||
* @param input The global input context.
|
||||
* @param battery The battery event.
|
||||
*/
|
||||
void
|
||||
gamepad_battery(input_t &input, const gamepad_battery_t &battery);
|
||||
void gamepad_battery(input_t &input, const gamepad_battery_t &battery);
|
||||
|
||||
/**
|
||||
* @brief Create a new virtual gamepad.
|
||||
@@ -843,35 +796,29 @@ namespace platf {
|
||||
* @param feedback_queue The queue for posting messages back to the client.
|
||||
* @return 0 on success.
|
||||
*/
|
||||
int
|
||||
alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
|
||||
void
|
||||
free_gamepad(input_t &input, int nr);
|
||||
int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
|
||||
void free_gamepad(input_t &input, int nr);
|
||||
|
||||
/**
|
||||
* @brief Get the supported platform capabilities to advertise to the client.
|
||||
* @return Capability flags.
|
||||
*/
|
||||
platform_caps::caps_t
|
||||
get_capabilities();
|
||||
platform_caps::caps_t get_capabilities();
|
||||
|
||||
#define SERVICE_NAME "Sunshine"
|
||||
#define SERVICE_TYPE "_nvstream._tcp"
|
||||
|
||||
namespace publish {
|
||||
[[nodiscard]] std::unique_ptr<deinit_t>
|
||||
start();
|
||||
[[nodiscard]] std::unique_ptr<deinit_t> start();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<deinit_t>
|
||||
init();
|
||||
[[nodiscard]] std::unique_ptr<deinit_t> init();
|
||||
|
||||
/**
|
||||
* @brief Returns the current computer name in UTF-8.
|
||||
* @return Computer name or a placeholder upon failure.
|
||||
*/
|
||||
std::string
|
||||
get_host_name();
|
||||
std::string get_host_name();
|
||||
|
||||
/**
|
||||
* @brief Gets the supported gamepads for this platform backend.
|
||||
@@ -879,8 +826,7 @@ namespace platf {
|
||||
* @param input Pointer to the platform's `input_t` or `nullptr`.
|
||||
* @return Vector of gamepad options and status.
|
||||
*/
|
||||
std::vector<supported_gamepad_t> &
|
||||
supported_gamepads(input_t *input);
|
||||
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input);
|
||||
|
||||
struct high_precision_timer: private boost::noncopyable {
|
||||
virtual ~high_precision_timer() = default;
|
||||
@@ -889,22 +835,19 @@ namespace platf {
|
||||
* @brief Sleep for the duration
|
||||
* @param duration Sleep duration
|
||||
*/
|
||||
virtual void
|
||||
sleep_for(const std::chrono::nanoseconds &duration) = 0;
|
||||
virtual void sleep_for(const std::chrono::nanoseconds &duration) = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if platform-specific timer backend has been initialized successfully
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
virtual
|
||||
operator bool() = 0;
|
||||
virtual operator bool() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create platform-specific timer capable of high-precision sleep
|
||||
* @return A unique pointer to timer
|
||||
*/
|
||||
std::unique_ptr<high_precision_timer>
|
||||
create_high_precision_timer();
|
||||
std::unique_ptr<high_precision_timer> create_high_precision_timer();
|
||||
|
||||
} // namespace platf
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
* @file src/platform/linux/audio.cpp
|
||||
* @brief Definitions for audio control on Linux.
|
||||
*/
|
||||
// standard includes
|
||||
#include <bitset>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
// lib includes
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
#include <pulse/error.h>
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include <pulse/simple.h>
|
||||
|
||||
#include "src/platform/common.h"
|
||||
|
||||
// local includes
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/thread_safe.h"
|
||||
|
||||
namespace platf {
|
||||
@@ -32,8 +33,7 @@ namespace platf {
|
||||
PA_CHANNEL_POSITION_SIDE_RIGHT,
|
||||
};
|
||||
|
||||
std::string
|
||||
to_string(const char *name, const std::uint8_t *mapping, int channels) {
|
||||
std::string to_string(const char *name, const std::uint8_t *mapping, int channels) {
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "rate=48000 sink_name="sv << name << " format=float channels="sv << channels << " channel_map="sv;
|
||||
@@ -53,8 +53,7 @@ namespace platf {
|
||||
struct mic_attr_t: public mic_t {
|
||||
util::safe_ptr<pa_simple, pa_simple_free> mic;
|
||||
|
||||
capture_e
|
||||
sample(std::vector<float> &sample_buf) override {
|
||||
capture_e sample(std::vector<float> &sample_buf) override {
|
||||
auto sample_size = sample_buf.size();
|
||||
|
||||
auto buf = sample_buf.data();
|
||||
@@ -69,11 +68,10 @@ namespace platf {
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<mic_t>
|
||||
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
|
||||
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
|
||||
auto mic = std::make_unique<mic_attr_t>();
|
||||
|
||||
pa_sample_spec ss { PA_SAMPLE_FLOAT32, sample_rate, (std::uint8_t) channels };
|
||||
pa_sample_spec ss {PA_SAMPLE_FLOAT32, sample_rate, (std::uint8_t) channels};
|
||||
pa_channel_map pa_map;
|
||||
|
||||
pa_map.channels = channels;
|
||||
@@ -92,9 +90,8 @@ namespace platf {
|
||||
int status;
|
||||
|
||||
mic->mic.reset(
|
||||
pa_simple_new(nullptr, "sunshine",
|
||||
pa_stream_direction_t::PA_STREAM_RECORD, source_name.c_str(),
|
||||
"sunshine-record", &ss, &pa_map, &pa_attr, &status));
|
||||
pa_simple_new(nullptr, "sunshine", pa_stream_direction_t::PA_STREAM_RECORD, source_name.c_str(), "sunshine-record", &ss, &pa_map, &pa_attr, &status)
|
||||
);
|
||||
|
||||
if (!mic->mic) {
|
||||
auto err_str = pa_strerror(status);
|
||||
@@ -106,38 +103,37 @@ namespace platf {
|
||||
}
|
||||
|
||||
namespace pa {
|
||||
template <bool B, class T>
|
||||
template<bool B, class T>
|
||||
struct add_const_helper;
|
||||
|
||||
template <class T>
|
||||
template<class T>
|
||||
struct add_const_helper<true, T> {
|
||||
using type = const std::remove_pointer_t<T> *;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
template<class T>
|
||||
struct add_const_helper<false, T> {
|
||||
using type = const T *;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
template<class T>
|
||||
using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type;
|
||||
|
||||
template <class T>
|
||||
void
|
||||
pa_free(T *p) {
|
||||
template<class T>
|
||||
void pa_free(T *p) {
|
||||
pa_xfree(p);
|
||||
}
|
||||
|
||||
using ctx_t = util::safe_ptr<pa_context, pa_context_unref>;
|
||||
using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>;
|
||||
using op_t = util::safe_ptr<pa_operation, pa_operation_unref>;
|
||||
using string_t = util::safe_ptr<char, pa_free<char>>;
|
||||
|
||||
template <class T>
|
||||
template<class T>
|
||||
using cb_simple_t = std::function<void(ctx_t::pointer, add_const_t<T> i)>;
|
||||
|
||||
template <class T>
|
||||
void
|
||||
cb(ctx_t::pointer ctx, add_const_t<T> i, void *userdata) {
|
||||
template<class T>
|
||||
void cb(ctx_t::pointer ctx, add_const_t<T> i, void *userdata) {
|
||||
auto &f = *(cb_simple_t<T> *) userdata;
|
||||
|
||||
// Cannot similarly filter on eol here. Unless reported otherwise assume
|
||||
@@ -145,12 +141,11 @@ namespace platf {
|
||||
f(ctx, i);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template<class T>
|
||||
using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>;
|
||||
|
||||
template <class T>
|
||||
void
|
||||
cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
|
||||
template<class T>
|
||||
void cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
|
||||
auto &f = *(cb_t<T> *) userdata;
|
||||
|
||||
// For some reason, pulseaudio calls this callback after disconnecting
|
||||
@@ -161,22 +156,19 @@ namespace platf {
|
||||
f(ctx, i, eol);
|
||||
}
|
||||
|
||||
void
|
||||
cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
|
||||
void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
|
||||
auto alarm = (safe::alarm_raw_t<int> *) userdata;
|
||||
|
||||
alarm->ring(i);
|
||||
}
|
||||
|
||||
void
|
||||
ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
|
||||
void ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
|
||||
auto &f = *(std::function<void(ctx_t::pointer)> *) userdata;
|
||||
|
||||
f(ctx);
|
||||
}
|
||||
|
||||
void
|
||||
success_cb(ctx_t::pointer ctx, int status, void *userdata) {
|
||||
void success_cb(ctx_t::pointer ctx, int status, void *userdata) {
|
||||
assert(userdata != nullptr);
|
||||
|
||||
auto alarm = (safe::alarm_raw_t<int> *) userdata;
|
||||
@@ -205,8 +197,8 @@ namespace platf {
|
||||
std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb;
|
||||
|
||||
std::thread worker;
|
||||
int
|
||||
init() {
|
||||
|
||||
int init() {
|
||||
events = std::make_unique<safe::event_t<ctx_event_e>>();
|
||||
loop.reset(pa_mainloop_new());
|
||||
ctx.reset(pa_context_new(pa_mainloop_get_api(loop.get()), "sunshine"));
|
||||
@@ -262,8 +254,7 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
|
||||
int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
|
||||
auto alarm = safe::make_alarm<int>();
|
||||
|
||||
op_t op {
|
||||
@@ -272,15 +263,15 @@ namespace platf {
|
||||
"module-null-sink",
|
||||
to_string(name, channel_mapping, channels).c_str(),
|
||||
cb_i,
|
||||
alarm.get()),
|
||||
alarm.get()
|
||||
),
|
||||
};
|
||||
|
||||
alarm->wait();
|
||||
return *alarm->status();
|
||||
}
|
||||
|
||||
int
|
||||
unload_null(std::uint32_t i) {
|
||||
int unload_null(std::uint32_t i) {
|
||||
if (i == PA_INVALID_INDEX) {
|
||||
return 0;
|
||||
}
|
||||
@@ -301,8 +292,7 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::optional<sink_t>
|
||||
sink_info() override {
|
||||
std::optional<sink_t> sink_info() override {
|
||||
constexpr auto stereo = "sink-sunshine-stereo";
|
||||
constexpr auto surround51 = "sink-sunshine-surround51";
|
||||
constexpr auto surround71 = "sink-sunshine-surround71";
|
||||
@@ -331,20 +321,18 @@ namespace platf {
|
||||
index.stereo = sink_info->owner_module;
|
||||
|
||||
++nullcount;
|
||||
}
|
||||
else if (!std::strcmp(sink_info->name, surround51)) {
|
||||
} else if (!std::strcmp(sink_info->name, surround51)) {
|
||||
index.surround51 = sink_info->owner_module;
|
||||
|
||||
++nullcount;
|
||||
}
|
||||
else if (!std::strcmp(sink_info->name, surround71)) {
|
||||
} else if (!std::strcmp(sink_info->name, surround71)) {
|
||||
index.surround71 = sink_info->owner_module;
|
||||
|
||||
++nullcount;
|
||||
}
|
||||
};
|
||||
|
||||
op_t op { pa_context_get_sink_info_list(ctx.get(), cb<pa_sink_info *>, &f) };
|
||||
op_t op {pa_context_get_sink_info_list(ctx.get(), cb<pa_sink_info *>, &f)};
|
||||
|
||||
if (!op) {
|
||||
BOOST_LOG(error) << "Couldn't create card info operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
|
||||
@@ -365,8 +353,7 @@ namespace platf {
|
||||
index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo));
|
||||
if (index.stereo == PA_INVALID_INDEX) {
|
||||
BOOST_LOG(warning) << "Couldn't create virtual sink for stereo: "sv << pa_strerror(pa_context_errno(ctx.get()));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
++nullcount;
|
||||
}
|
||||
}
|
||||
@@ -375,8 +362,7 @@ namespace platf {
|
||||
index.surround51 = load_null(surround51, speaker::map_surround51, sizeof(speaker::map_surround51));
|
||||
if (index.surround51 == PA_INVALID_INDEX) {
|
||||
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-51: "sv << pa_strerror(pa_context_errno(ctx.get()));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
++nullcount;
|
||||
}
|
||||
}
|
||||
@@ -385,8 +371,7 @@ namespace platf {
|
||||
index.surround71 = load_null(surround71, speaker::map_surround71, sizeof(speaker::map_surround71));
|
||||
if (index.surround71 == PA_INVALID_INDEX) {
|
||||
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-71: "sv << pa_strerror(pa_context_errno(ctx.get()));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
++nullcount;
|
||||
}
|
||||
}
|
||||
@@ -396,14 +381,13 @@ namespace platf {
|
||||
}
|
||||
|
||||
if (nullcount == 3) {
|
||||
sink.null = std::make_optional(sink_t::null_t { stereo, surround51, surround71 });
|
||||
sink.null = std::make_optional(sink_t::null_t {stereo, surround51, surround71});
|
||||
}
|
||||
|
||||
return std::make_optional(std::move(sink));
|
||||
}
|
||||
|
||||
std::string
|
||||
get_default_sink_name() {
|
||||
std::string get_default_sink_name() {
|
||||
std::string sink_name;
|
||||
auto alarm = safe::make_alarm<int>();
|
||||
|
||||
@@ -419,14 +403,13 @@ namespace platf {
|
||||
alarm->ring(0);
|
||||
};
|
||||
|
||||
op_t server_op { pa_context_get_server_info(ctx.get(), cb<pa_server_info *>, &server_f) };
|
||||
op_t server_op {pa_context_get_server_info(ctx.get(), cb<pa_server_info *>, &server_f)};
|
||||
alarm->wait();
|
||||
// No need to check status. If it failed just return default name.
|
||||
return sink_name;
|
||||
}
|
||||
|
||||
std::string
|
||||
get_monitor_name(const std::string &sink_name) {
|
||||
std::string get_monitor_name(const std::string &sink_name) {
|
||||
std::string monitor_name;
|
||||
auto alarm = safe::make_alarm<int>();
|
||||
|
||||
@@ -449,7 +432,7 @@ namespace platf {
|
||||
monitor_name = sink_info->monitor_source_name;
|
||||
};
|
||||
|
||||
op_t sink_op { pa_context_get_sink_info_by_name(ctx.get(), sink_name.c_str(), cb<pa_sink_info *>, &sink_f) };
|
||||
op_t sink_op {pa_context_get_sink_info_by_name(ctx.get(), sink_name.c_str(), cb<pa_sink_info *>, &sink_f)};
|
||||
|
||||
alarm->wait();
|
||||
// No need to check status. If it failed just return default name.
|
||||
@@ -457,8 +440,7 @@ namespace platf {
|
||||
return monitor_name;
|
||||
}
|
||||
|
||||
std::unique_ptr<mic_t>
|
||||
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
|
||||
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
|
||||
// Sink choice priority:
|
||||
// 1. Config sink
|
||||
// 2. Last sink swapped to (Usually virtual in this case)
|
||||
@@ -467,26 +449,32 @@ namespace platf {
|
||||
// but this happens right after the swap so the default returned by PA was not
|
||||
// the new one just set!
|
||||
auto sink_name = config::audio.sink;
|
||||
if (sink_name.empty()) sink_name = requested_sink;
|
||||
if (sink_name.empty()) sink_name = get_default_sink_name();
|
||||
if (sink_name.empty()) {
|
||||
sink_name = requested_sink;
|
||||
}
|
||||
if (sink_name.empty()) {
|
||||
sink_name = get_default_sink_name();
|
||||
}
|
||||
|
||||
return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name));
|
||||
}
|
||||
|
||||
bool
|
||||
is_sink_available(const std::string &sink) override {
|
||||
bool is_sink_available(const std::string &sink) override {
|
||||
BOOST_LOG(warning) << "audio_control_t::is_sink_available() unimplemented: "sv << sink;
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
set_sink(const std::string &sink) override {
|
||||
int set_sink(const std::string &sink) override {
|
||||
auto alarm = safe::make_alarm<int>();
|
||||
|
||||
BOOST_LOG(info) << "Setting default sink to: ["sv << sink << "]"sv;
|
||||
op_t op {
|
||||
pa_context_set_default_sink(
|
||||
ctx.get(), sink.c_str(), success_cb, alarm.get()),
|
||||
ctx.get(),
|
||||
sink.c_str(),
|
||||
success_cb,
|
||||
alarm.get()
|
||||
),
|
||||
};
|
||||
|
||||
if (!op) {
|
||||
@@ -525,8 +513,7 @@ namespace platf {
|
||||
};
|
||||
} // namespace pa
|
||||
|
||||
std::unique_ptr<audio_control_t>
|
||||
audio_control() {
|
||||
std::unique_ptr<audio_control_t> audio_control() {
|
||||
auto audio = std::make_unique<pa::server_t>();
|
||||
|
||||
if (audio->init()) {
|
||||
@@ -535,4 +522,4 @@ namespace platf {
|
||||
|
||||
return audio;
|
||||
}
|
||||
} // namespace platf
|
||||
} // namespace platf
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
* @file src/platform/linux/cuda.cpp
|
||||
* @brief Definitions for CUDA encoding.
|
||||
*/
|
||||
// standard includes
|
||||
#include <bitset>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
|
||||
#include <NvFBC.h>
|
||||
// lib includes
|
||||
#include <ffnvcodec/dynlink_loader.h>
|
||||
#include <NvFBC.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
@@ -16,6 +18,7 @@ extern "C" {
|
||||
#include <libavutil/imgutils.h>
|
||||
}
|
||||
|
||||
// local includes
|
||||
#include "cuda.h"
|
||||
#include "graphics.h"
|
||||
#include "src/logging.h"
|
||||
@@ -27,7 +30,8 @@ extern "C" {
|
||||
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
|
||||
|
||||
#define CU_CHECK(x, y) \
|
||||
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) return -1
|
||||
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
|
||||
return -1
|
||||
|
||||
#define CU_CHECK_IGNORE(x, y) \
|
||||
check((x), SUNSHINE_STRINGVIEW(y ": "))
|
||||
@@ -35,17 +39,16 @@ extern "C" {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace cuda {
|
||||
constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1;
|
||||
constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39;
|
||||
|
||||
void
|
||||
pass_error(const std::string_view &sv, const char *name, const char *description) {
|
||||
void pass_error(const std::string_view &sv, const char *name, const char *description) {
|
||||
BOOST_LOG(error) << sv << name << ':' << description;
|
||||
}
|
||||
|
||||
void
|
||||
cff(CudaFunctions *cf) {
|
||||
void cff(CudaFunctions *cf) {
|
||||
cuda_free_functions(&cf);
|
||||
}
|
||||
|
||||
@@ -53,8 +56,7 @@ namespace cuda {
|
||||
|
||||
static cdf_t cdf;
|
||||
|
||||
inline static int
|
||||
check(CUresult result, const std::string_view &sv) {
|
||||
inline static int check(CUresult result, const std::string_view &sv) {
|
||||
if (result != CUDA_SUCCESS) {
|
||||
const char *name;
|
||||
const char *description;
|
||||
@@ -69,13 +71,11 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
freeStream(CUstream stream) {
|
||||
void freeStream(CUstream stream) {
|
||||
CU_CHECK_IGNORE(cdf->cuStreamDestroy(stream), "Couldn't destroy cuda stream");
|
||||
}
|
||||
|
||||
void
|
||||
unregisterResource(CUgraphicsResource resource) {
|
||||
void unregisterResource(CUgraphicsResource resource) {
|
||||
CU_CHECK_IGNORE(cdf->cuGraphicsUnregisterResource(resource), "Couldn't unregister resource");
|
||||
}
|
||||
|
||||
@@ -86,8 +86,7 @@ namespace cuda {
|
||||
tex_t tex;
|
||||
};
|
||||
|
||||
int
|
||||
init() {
|
||||
int init() {
|
||||
auto status = cuda_load_functions(&cdf, nullptr);
|
||||
if (status) {
|
||||
BOOST_LOG(error) << "Couldn't load cuda: "sv << status;
|
||||
@@ -102,8 +101,7 @@ namespace cuda {
|
||||
|
||||
class cuda_t: public platf::avcodec_encode_device_t {
|
||||
public:
|
||||
int
|
||||
init(int in_width, int in_height) {
|
||||
int init(int in_width, int in_height) {
|
||||
if (!cdf) {
|
||||
BOOST_LOG(warning) << "cuda not initialized"sv;
|
||||
return -1;
|
||||
@@ -117,8 +115,7 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
|
||||
this->hwframe.reset(frame);
|
||||
this->frame = frame;
|
||||
|
||||
@@ -156,8 +153,7 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
apply_colorspace() override {
|
||||
void apply_colorspace() override {
|
||||
sws.apply_colorspace(colorspace);
|
||||
|
||||
auto tex = tex_t::make(height, width * 4);
|
||||
@@ -182,11 +178,10 @@ namespace cuda {
|
||||
return;
|
||||
}
|
||||
|
||||
sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex->texture.linear, stream.get(), { frame->width, frame->height, 0, 0 });
|
||||
sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex->texture.linear, stream.get(), {frame->width, frame->height, 0, 0});
|
||||
}
|
||||
|
||||
cudaTextureObject_t
|
||||
tex_obj(const tex_t &tex) const {
|
||||
cudaTextureObject_t tex_obj(const tex_t &tex) const {
|
||||
return linear_interpolation ? tex.texture.linear : tex.texture.point;
|
||||
}
|
||||
|
||||
@@ -203,13 +198,11 @@ namespace cuda {
|
||||
|
||||
class cuda_ram_t: public cuda_t {
|
||||
public:
|
||||
int
|
||||
convert(platf::img_t &img) override {
|
||||
int convert(platf::img_t &img) override {
|
||||
return sws.load_ram(img, tex.array) || sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(tex), stream.get());
|
||||
}
|
||||
|
||||
int
|
||||
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
|
||||
if (cuda_t::set_frame(frame, hw_frames_ctx)) {
|
||||
return -1;
|
||||
}
|
||||
@@ -229,8 +222,7 @@ namespace cuda {
|
||||
|
||||
class cuda_vram_t: public cuda_t {
|
||||
public:
|
||||
int
|
||||
convert(platf::img_t &img) override {
|
||||
int convert(platf::img_t &img) override {
|
||||
return sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(((img_t *) &img)->tex), stream.get());
|
||||
}
|
||||
};
|
||||
@@ -240,8 +232,7 @@ namespace cuda {
|
||||
* @param index CUDA device index to open.
|
||||
* @return File descriptor or -1 on failure.
|
||||
*/
|
||||
file_t
|
||||
open_drm_fd_for_cuda_device(int index) {
|
||||
file_t open_drm_fd_for_cuda_device(int index) {
|
||||
CUdevice device;
|
||||
CU_CHECK(cdf->cuDeviceGet(&device, index), "Couldn't get CUDA device");
|
||||
|
||||
@@ -252,29 +243,29 @@ namespace cuda {
|
||||
BOOST_LOG(debug) << "Found CUDA device with PCI bus ID: "sv << pci_bus_id.data();
|
||||
|
||||
// Linux uses lowercase hexadecimal while CUDA uses uppercase
|
||||
std::transform(pci_bus_id.begin(), pci_bus_id.end(), pci_bus_id.begin(),
|
||||
[](char c) { return std::tolower(c); });
|
||||
std::transform(pci_bus_id.begin(), pci_bus_id.end(), pci_bus_id.begin(), [](char c) {
|
||||
return std::tolower(c);
|
||||
});
|
||||
|
||||
// Look for the name of the primary node in sysfs
|
||||
try {
|
||||
char sysfs_path[PATH_MAX];
|
||||
std::snprintf(sysfs_path, sizeof(sysfs_path), "/sys/bus/pci/devices/%s/drm", pci_bus_id.data());
|
||||
fs::path sysfs_dir { sysfs_path };
|
||||
for (auto &entry : fs::directory_iterator { sysfs_dir }) {
|
||||
fs::path sysfs_dir {sysfs_path};
|
||||
for (auto &entry : fs::directory_iterator {sysfs_dir}) {
|
||||
auto file = entry.path().filename();
|
||||
auto filestring = file.generic_string();
|
||||
if (std::string_view { filestring }.substr(0, 4) != "card"sv) {
|
||||
if (std::string_view {filestring}.substr(0, 4) != "card"sv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << "Found DRM primary node: "sv << filestring;
|
||||
|
||||
fs::path dri_path { "/dev/dri"sv };
|
||||
fs::path dri_path {"/dev/dri"sv};
|
||||
auto device_path = dri_path / file;
|
||||
return open(device_path.c_str(), O_RDWR);
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error &err) {
|
||||
} catch (const std::filesystem::filesystem_error &err) {
|
||||
BOOST_LOG(error) << "Failed to read sysfs: "sv << err.what();
|
||||
}
|
||||
|
||||
@@ -292,8 +283,7 @@ namespace cuda {
|
||||
* @param offset_y Offset of content in captured frame.
|
||||
* @return 0 on success or -1 on failure.
|
||||
*/
|
||||
int
|
||||
init(int in_width, int in_height, int offset_x, int offset_y) {
|
||||
int init(int in_width, int in_height, int offset_x, int offset_y) {
|
||||
// This must be non-zero to tell the video core that it's a hardware encoding device.
|
||||
data = (void *) 0x1;
|
||||
|
||||
@@ -340,8 +330,7 @@ namespace cuda {
|
||||
* @param hw_frames_ctx_buf FFmpeg hardware frame context.
|
||||
* @return 0 on success or -1 on failure.
|
||||
*/
|
||||
int
|
||||
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
|
||||
this->hwframe.reset(frame);
|
||||
this->frame = frame;
|
||||
|
||||
@@ -377,10 +366,8 @@ namespace cuda {
|
||||
|
||||
cuda_ctx->stream = stream.get();
|
||||
|
||||
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&y_res, nv12->tex[0], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY),
|
||||
"Couldn't register Y plane texture");
|
||||
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&uv_res, nv12->tex[1], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY),
|
||||
"Couldn't register UV plane texture");
|
||||
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&y_res, nv12->tex[0], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), "Couldn't register Y plane texture");
|
||||
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&uv_res, nv12->tex[1], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), "Couldn't register UV plane texture");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -390,15 +377,13 @@ namespace cuda {
|
||||
* @param img Captured screen image.
|
||||
* @return 0 on success or -1 on failure.
|
||||
*/
|
||||
int
|
||||
convert(platf::img_t &img) override {
|
||||
int convert(platf::img_t &img) override {
|
||||
auto &descriptor = (egl::img_descriptor_t &) img;
|
||||
|
||||
if (descriptor.sequence == 0) {
|
||||
// For dummy images, use a blank RGB texture instead of importing a DMA-BUF
|
||||
rgb = egl::create_blank(img);
|
||||
}
|
||||
else if (descriptor.sequence > sequence) {
|
||||
} else if (descriptor.sequence > sequence) {
|
||||
sequence = descriptor.sequence;
|
||||
|
||||
rgb = egl::rgb_t {};
|
||||
@@ -419,7 +404,7 @@ namespace cuda {
|
||||
auto fmt_desc = av_pix_fmt_desc_get(sw_format);
|
||||
|
||||
// Map the GL textures to read for CUDA
|
||||
CUgraphicsResource resources[2] = { y_res.get(), uv_res.get() };
|
||||
CUgraphicsResource resources[2] = {y_res.get(), uv_res.get()};
|
||||
CU_CHECK(cdf->cuGraphicsMapResources(2, resources, stream.get()), "Couldn't map GL textures in CUDA");
|
||||
|
||||
// Copy from the GL textures to the target CUDA frame
|
||||
@@ -445,8 +430,7 @@ namespace cuda {
|
||||
/**
|
||||
* @brief Configures shader parameters for the specified colorspace.
|
||||
*/
|
||||
void
|
||||
apply_colorspace() override {
|
||||
void apply_colorspace() override {
|
||||
sws.apply_colorspace(colorspace);
|
||||
}
|
||||
|
||||
@@ -474,8 +458,7 @@ namespace cuda {
|
||||
int offset_x, offset_y;
|
||||
};
|
||||
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(int width, int height, bool vram) {
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram) {
|
||||
if (init()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -484,8 +467,7 @@ namespace cuda {
|
||||
|
||||
if (vram) {
|
||||
cuda = std::make_unique<cuda_vram_t>();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
cuda = std::make_unique<cuda_ram_t>();
|
||||
}
|
||||
|
||||
@@ -504,8 +486,7 @@ namespace cuda {
|
||||
* @param offset_y Offset of content in captured frame.
|
||||
* @return FFmpeg encoding device context.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y) {
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y) {
|
||||
if (init()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -521,29 +502,30 @@ namespace cuda {
|
||||
|
||||
namespace nvfbc {
|
||||
static PNVFBCCREATEINSTANCE createInstance {};
|
||||
static NVFBC_API_FUNCTION_LIST func { NVFBC_VERSION };
|
||||
static NVFBC_API_FUNCTION_LIST func {NVFBC_VERSION};
|
||||
|
||||
static constexpr inline NVFBC_BOOL
|
||||
nv_bool(bool b) {
|
||||
static constexpr inline NVFBC_BOOL nv_bool(bool b) {
|
||||
return b ? NVFBC_TRUE : NVFBC_FALSE;
|
||||
}
|
||||
|
||||
static void *handle { nullptr };
|
||||
int
|
||||
init() {
|
||||
static void *handle {nullptr};
|
||||
|
||||
int init() {
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
if (funcs_loaded) return 0;
|
||||
if (funcs_loaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!handle) {
|
||||
handle = dyn::handle({ "libnvidia-fbc.so.1", "libnvidia-fbc.so" });
|
||||
handle = dyn::handle({"libnvidia-fbc.so.1", "libnvidia-fbc.so"});
|
||||
if (!handle) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
|
||||
{ (dyn::apiproc *) &createInstance, "NvFBCCreateInstance" },
|
||||
{(dyn::apiproc *) &createInstance, "NvFBCCreateInstance"},
|
||||
};
|
||||
|
||||
if (dyn::load(handle, funcs)) {
|
||||
@@ -569,7 +551,7 @@ namespace cuda {
|
||||
class ctx_t {
|
||||
public:
|
||||
ctx_t(NVFBC_SESSION_HANDLE handle) {
|
||||
NVFBC_BIND_CONTEXT_PARAMS params { NVFBC_BIND_CONTEXT_PARAMS_VER };
|
||||
NVFBC_BIND_CONTEXT_PARAMS params {NVFBC_BIND_CONTEXT_PARAMS_VER};
|
||||
|
||||
if (func.nvFBCBindContext(handle, ¶ms)) {
|
||||
BOOST_LOG(error) << "Couldn't bind NvFBC context to current thread: " << func.nvFBCGetLastErrorStr(handle);
|
||||
@@ -579,7 +561,7 @@ namespace cuda {
|
||||
}
|
||||
|
||||
~ctx_t() {
|
||||
NVFBC_RELEASE_CONTEXT_PARAMS params { NVFBC_RELEASE_CONTEXT_PARAMS_VER };
|
||||
NVFBC_RELEASE_CONTEXT_PARAMS params {NVFBC_RELEASE_CONTEXT_PARAMS_VER};
|
||||
if (func.nvFBCReleaseContext(handle, ¶ms)) {
|
||||
BOOST_LOG(error) << "Couldn't release NvFBC context from current thread: " << func.nvFBCGetLastErrorStr(handle);
|
||||
}
|
||||
@@ -597,26 +579,26 @@ namespace cuda {
|
||||
|
||||
public:
|
||||
handle_t() = default;
|
||||
|
||||
handle_t(handle_t &&other):
|
||||
handle_flags { other.handle_flags }, handle { other.handle } {
|
||||
handle_flags {other.handle_flags},
|
||||
handle {other.handle} {
|
||||
other.handle_flags.reset();
|
||||
}
|
||||
|
||||
handle_t &
|
||||
operator=(handle_t &&other) {
|
||||
handle_t &operator=(handle_t &&other) {
|
||||
std::swap(handle_flags, other.handle_flags);
|
||||
std::swap(handle, other.handle);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
static std::optional<handle_t>
|
||||
make() {
|
||||
NVFBC_CREATE_HANDLE_PARAMS params { NVFBC_CREATE_HANDLE_PARAMS_VER };
|
||||
static std::optional<handle_t> make() {
|
||||
NVFBC_CREATE_HANDLE_PARAMS params {NVFBC_CREATE_HANDLE_PARAMS_VER};
|
||||
|
||||
// Set privateData to allow NvFBC on consumer NVIDIA GPUs.
|
||||
// Based on https://github.com/keylase/nvidia-patch/blob/3193b4b1cea91527bf09ea9b8db5aade6a3f3c0a/win/nvfbcwrp/nvfbcwrp_main.cpp#L23-L25 .
|
||||
const unsigned int MAGIC_PRIVATE_DATA[4] = { 0xAEF57AC5, 0x401D1A39, 0x1B856BBE, 0x9ED0CEBA };
|
||||
const unsigned int MAGIC_PRIVATE_DATA[4] = {0xAEF57AC5, 0x401D1A39, 0x1B856BBE, 0x9ED0CEBA};
|
||||
params.privateData = MAGIC_PRIVATE_DATA;
|
||||
params.privateDataSize = sizeof(MAGIC_PRIVATE_DATA);
|
||||
|
||||
@@ -633,14 +615,12 @@ namespace cuda {
|
||||
return handle;
|
||||
}
|
||||
|
||||
const char *
|
||||
last_error() {
|
||||
const char *last_error() {
|
||||
return func.nvFBCGetLastErrorStr(handle);
|
||||
}
|
||||
|
||||
std::optional<NVFBC_GET_STATUS_PARAMS>
|
||||
status() {
|
||||
NVFBC_GET_STATUS_PARAMS params { NVFBC_GET_STATUS_PARAMS_VER };
|
||||
std::optional<NVFBC_GET_STATUS_PARAMS> status() {
|
||||
NVFBC_GET_STATUS_PARAMS params {NVFBC_GET_STATUS_PARAMS_VER};
|
||||
|
||||
auto status = func.nvFBCGetStatus(handle, ¶ms);
|
||||
if (status) {
|
||||
@@ -652,8 +632,7 @@ namespace cuda {
|
||||
return params;
|
||||
}
|
||||
|
||||
int
|
||||
capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
|
||||
int capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
|
||||
if (func.nvFBCCreateCaptureSession(handle, &capture_params)) {
|
||||
BOOST_LOG(error) << "Failed to start capture session: "sv << last_error();
|
||||
return -1;
|
||||
@@ -673,13 +652,12 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
stop() {
|
||||
int stop() {
|
||||
if (!handle_flags[SESSION_CAPTURE]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS params { NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER };
|
||||
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS params {NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER};
|
||||
|
||||
if (func.nvFBCDestroyCaptureSession(handle, ¶ms)) {
|
||||
BOOST_LOG(error) << "Couldn't destroy capture session: "sv << last_error();
|
||||
@@ -692,17 +670,16 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
reset() {
|
||||
int reset() {
|
||||
if (!handle_flags[SESSION_HANDLE]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
stop();
|
||||
|
||||
NVFBC_DESTROY_HANDLE_PARAMS params { NVFBC_DESTROY_HANDLE_PARAMS_VER };
|
||||
NVFBC_DESTROY_HANDLE_PARAMS params {NVFBC_DESTROY_HANDLE_PARAMS_VER};
|
||||
|
||||
ctx_t ctx { handle };
|
||||
ctx_t ctx {handle};
|
||||
if (func.nvFBCDestroyHandle(handle, ¶ms)) {
|
||||
BOOST_LOG(error) << "Couldn't destroy session handle: "sv << func.nvFBCGetLastErrorStr(handle);
|
||||
}
|
||||
@@ -723,14 +700,13 @@ namespace cuda {
|
||||
|
||||
class display_t: public platf::display_t {
|
||||
public:
|
||||
int
|
||||
init(const std::string_view &display_name, const ::video::config_t &config) {
|
||||
int init(const std::string_view &display_name, const ::video::config_t &config) {
|
||||
auto handle = handle_t::make();
|
||||
if (!handle) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ctx_t ctx { handle->handle };
|
||||
ctx_t ctx {handle->handle};
|
||||
|
||||
auto status_params = handle->status();
|
||||
if (!status_params) {
|
||||
@@ -744,19 +720,17 @@ namespace cuda {
|
||||
|
||||
if (monitor_nr < 0 || monitor_nr >= status_params->dwOutputNum) {
|
||||
BOOST_LOG(warning) << "Can't stream monitor ["sv << monitor_nr << "], it needs to be between [0] and ["sv << status_params->dwOutputNum - 1 << "], defaulting to virtual desktop"sv;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
streamedMonitor = monitor_nr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "XrandR not available, streaming entire virtual desktop"sv;
|
||||
}
|
||||
}
|
||||
|
||||
delay = std::chrono::nanoseconds { 1s } / config.framerate;
|
||||
delay = std::chrono::nanoseconds {1s} / config.framerate;
|
||||
|
||||
capture_params = NVFBC_CREATE_CAPTURE_SESSION_PARAMS { NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER };
|
||||
capture_params = NVFBC_CREATE_CAPTURE_SESSION_PARAMS {NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER};
|
||||
|
||||
capture_params.eCaptureType = NVFBC_CAPTURE_SHARED_CUDA;
|
||||
capture_params.bDisableAutoModesetRecovery = nv_bool(true);
|
||||
@@ -773,8 +747,7 @@ namespace cuda {
|
||||
|
||||
capture_params.eTrackingType = NVFBC_TRACKING_OUTPUT;
|
||||
capture_params.dwOutputId = output.dwId;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
capture_params.eTrackingType = NVFBC_TRACKING_SCREEN;
|
||||
|
||||
width = status_params->screenSize.w;
|
||||
@@ -788,8 +761,7 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
platf::capture_e
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
{
|
||||
@@ -802,7 +774,7 @@ namespace cuda {
|
||||
// Force display_t::capture to initialize handle_t::capture
|
||||
cursor_visible = !*cursor;
|
||||
|
||||
ctx_t ctx { handle.handle };
|
||||
ctx_t ctx {handle.handle};
|
||||
auto fg = util::fail_guard([&]() {
|
||||
handle.reset();
|
||||
});
|
||||
@@ -849,8 +821,7 @@ namespace cuda {
|
||||
}
|
||||
|
||||
// Reinitialize the capture session.
|
||||
platf::capture_e
|
||||
reinit(bool cursor) {
|
||||
platf::capture_e reinit(bool cursor) {
|
||||
if (handle.stop()) {
|
||||
return platf::capture_e::error;
|
||||
}
|
||||
@@ -860,8 +831,7 @@ namespace cuda {
|
||||
capture_params.bPushModel = nv_bool(false);
|
||||
capture_params.bWithCursor = nv_bool(true);
|
||||
capture_params.bAllowDirectCapture = nv_bool(false);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
capture_params.bPushModel = nv_bool(true);
|
||||
capture_params.bWithCursor = nv_bool(false);
|
||||
capture_params.bAllowDirectCapture = nv_bool(true);
|
||||
@@ -919,8 +889,7 @@ namespace cuda {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
platf::capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
if (cursor != cursor_visible) {
|
||||
auto status = reinit(cursor);
|
||||
if (status != platf::capture_e::ok) {
|
||||
@@ -960,13 +929,11 @@ namespace cuda {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) {
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) {
|
||||
return ::cuda::make_avcodec_encode_device(width, height, true);
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::img_t>
|
||||
alloc_img() override {
|
||||
std::shared_ptr<platf::img_t> alloc_img() override {
|
||||
auto img = std::make_shared<cuda::img_t>();
|
||||
|
||||
img->data = nullptr;
|
||||
@@ -985,8 +952,7 @@ namespace cuda {
|
||||
return img;
|
||||
};
|
||||
|
||||
int
|
||||
dummy_img(platf::img_t *) override {
|
||||
int dummy_img(platf::img_t *) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1001,8 +967,7 @@ namespace cuda {
|
||||
} // namespace cuda
|
||||
|
||||
namespace platf {
|
||||
std::shared_ptr<display_t>
|
||||
nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
if (hwdevice_type != mem_type_e::cuda) {
|
||||
BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv;
|
||||
return nullptr;
|
||||
@@ -1017,8 +982,7 @@ namespace platf {
|
||||
return display;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
nvfbc_display_names() {
|
||||
std::vector<std::string> nvfbc_display_names() {
|
||||
if (cuda::init() || cuda::nvfbc::init()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
* @file src/platform/linux/cuda.cu
|
||||
* @brief CUDA implementation for Linux.
|
||||
*/
|
||||
// #include <algorithm>
|
||||
#include <helper_math.h>
|
||||
// standard includes
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
// platform includes
|
||||
#include <helper_math.h>
|
||||
|
||||
// local includes
|
||||
#include "cuda.h"
|
||||
|
||||
using namespace std::literals;
|
||||
@@ -18,16 +21,20 @@ using namespace std::literals;
|
||||
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
|
||||
|
||||
#define CU_CHECK(x, y) \
|
||||
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return -1
|
||||
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
|
||||
return -1
|
||||
|
||||
#define CU_CHECK_VOID(x, y) \
|
||||
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return;
|
||||
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
|
||||
return;
|
||||
|
||||
#define CU_CHECK_PTR(x, y) \
|
||||
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return nullptr;
|
||||
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
|
||||
return nullptr;
|
||||
|
||||
#define CU_CHECK_OPT(x, y) \
|
||||
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return std::nullopt;
|
||||
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
|
||||
return std::nullopt;
|
||||
|
||||
#define CU_CHECK_IGNORE(x, y) \
|
||||
check((x), SUNSHINE_STRINGVIEW(y ": "))
|
||||
@@ -42,277 +49,293 @@ using namespace std::literals;
|
||||
* Not pretty and extremely error-prone, fix at earliest convenience.
|
||||
*/
|
||||
namespace platf {
|
||||
struct img_t: std::enable_shared_from_this<img_t> {
|
||||
public:
|
||||
std::uint8_t *data {};
|
||||
std::int32_t width {};
|
||||
std::int32_t height {};
|
||||
std::int32_t pixel_pitch {};
|
||||
std::int32_t row_pitch {};
|
||||
struct img_t: std::enable_shared_from_this<img_t> {
|
||||
public:
|
||||
std::uint8_t *data {};
|
||||
std::int32_t width {};
|
||||
std::int32_t height {};
|
||||
std::int32_t pixel_pitch {};
|
||||
std::int32_t row_pitch {};
|
||||
|
||||
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
|
||||
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
|
||||
|
||||
virtual ~img_t() = default;
|
||||
};
|
||||
} // namespace platf
|
||||
virtual ~img_t() = default;
|
||||
};
|
||||
} // namespace platf
|
||||
|
||||
// End special declarations
|
||||
|
||||
namespace cuda {
|
||||
|
||||
struct alignas(16) cuda_color_t {
|
||||
float4 color_vec_y;
|
||||
float4 color_vec_u;
|
||||
float4 color_vec_v;
|
||||
float2 range_y;
|
||||
float2 range_uv;
|
||||
};
|
||||
struct alignas(16) cuda_color_t {
|
||||
float4 color_vec_y;
|
||||
float4 color_vec_u;
|
||||
float4 color_vec_v;
|
||||
float2 range_y;
|
||||
float2 range_uv;
|
||||
};
|
||||
|
||||
static_assert(sizeof(video::color_t) == sizeof(cuda::cuda_color_t), "color matrix struct mismatch");
|
||||
static_assert(sizeof(video::color_t) == sizeof(cuda::cuda_color_t), "color matrix struct mismatch");
|
||||
|
||||
auto constexpr INVALID_TEXTURE = std::numeric_limits<cudaTextureObject_t>::max();
|
||||
auto constexpr INVALID_TEXTURE = std::numeric_limits<cudaTextureObject_t>::max();
|
||||
|
||||
template<class T>
|
||||
inline T div_align(T l, T r) {
|
||||
return (l + r - 1) / r;
|
||||
}
|
||||
|
||||
void pass_error(const std::string_view &sv, const char *name, const char *description);
|
||||
inline static int check(cudaError_t result, const std::string_view &sv) {
|
||||
if(result) {
|
||||
auto name = cudaGetErrorName(result);
|
||||
auto description = cudaGetErrorString(result);
|
||||
|
||||
pass_error(sv, name, description);
|
||||
return -1;
|
||||
template<class T>
|
||||
inline T div_align(T l, T r) {
|
||||
return (l + r - 1) / r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
void pass_error(const std::string_view &sv, const char *name, const char *description);
|
||||
|
||||
template<class T>
|
||||
ptr_t make_ptr() {
|
||||
void *p;
|
||||
CU_CHECK_PTR(cudaMalloc(&p, sizeof(T)), "Couldn't allocate color matrix");
|
||||
inline static int check(cudaError_t result, const std::string_view &sv) {
|
||||
if (result) {
|
||||
auto name = cudaGetErrorName(result);
|
||||
auto description = cudaGetErrorString(result);
|
||||
|
||||
ptr_t ptr { p };
|
||||
pass_error(sv, name, description);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void freeCudaPtr_t::operator()(void *ptr) {
|
||||
CU_CHECK_IGNORE(cudaFree(ptr), "Couldn't free cuda device pointer");
|
||||
}
|
||||
|
||||
void freeCudaStream_t::operator()(cudaStream_t ptr) {
|
||||
CU_CHECK_IGNORE(cudaStreamDestroy(ptr), "Couldn't free cuda stream");
|
||||
}
|
||||
|
||||
stream_t make_stream(int flags) {
|
||||
cudaStream_t stream;
|
||||
|
||||
if(!flags) {
|
||||
CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream");
|
||||
}
|
||||
else {
|
||||
CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return stream_t { stream };
|
||||
}
|
||||
template<class T>
|
||||
ptr_t make_ptr() {
|
||||
void *p;
|
||||
CU_CHECK_PTR(cudaMalloc(&p, sizeof(T)), "Couldn't allocate color matrix");
|
||||
|
||||
inline __device__ float3 bgra_to_rgb(uchar4 vec) {
|
||||
return make_float3((float)vec.z, (float)vec.y, (float)vec.x);
|
||||
}
|
||||
ptr_t ptr {p};
|
||||
|
||||
inline __device__ float3 bgra_to_rgb(float4 vec) {
|
||||
return make_float3(vec.z, vec.y, vec.x);
|
||||
}
|
||||
|
||||
inline __device__ float2 calcUV(float3 pixel, const cuda_color_t *const color_matrix) {
|
||||
float4 vec_u = color_matrix->color_vec_u;
|
||||
float4 vec_v = color_matrix->color_vec_v;
|
||||
|
||||
float u = dot(pixel, make_float3(vec_u)) + vec_u.w;
|
||||
float v = dot(pixel, make_float3(vec_v)) + vec_v.w;
|
||||
|
||||
u = u * color_matrix->range_uv.x + color_matrix->range_uv.y;
|
||||
v = v * color_matrix->range_uv.x + color_matrix->range_uv.y;
|
||||
|
||||
return make_float2(u, v);
|
||||
}
|
||||
|
||||
inline __device__ float calcY(float3 pixel, const cuda_color_t *const color_matrix) {
|
||||
float4 vec_y = color_matrix->color_vec_y;
|
||||
|
||||
return (dot(pixel, make_float3(vec_y)) + vec_y.w) * color_matrix->range_y.x + color_matrix->range_y.y;
|
||||
}
|
||||
|
||||
__global__ void RGBA_to_NV12(
|
||||
cudaTextureObject_t srcImage, std::uint8_t *dstY, std::uint8_t *dstUV,
|
||||
std::uint32_t dstPitchY, std::uint32_t dstPitchUV,
|
||||
float scale, const viewport_t viewport, const cuda_color_t *const color_matrix) {
|
||||
|
||||
int idX = (threadIdx.x + blockDim.x * blockIdx.x) * 2;
|
||||
int idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2;
|
||||
|
||||
if(idX >= viewport.width) return;
|
||||
if(idY >= viewport.height) return;
|
||||
|
||||
float x = idX * scale;
|
||||
float y = idY * scale;
|
||||
|
||||
idX += viewport.offsetX;
|
||||
idY += viewport.offsetY;
|
||||
|
||||
uint8_t *dstY0 = dstY + idX + idY * dstPitchY;
|
||||
uint8_t *dstY1 = dstY + idX + (idY + 1) * dstPitchY;
|
||||
dstUV = dstUV + idX + (idY / 2 * dstPitchUV);
|
||||
|
||||
float3 rgb_lt = bgra_to_rgb(tex2D<float4>(srcImage, x, y));
|
||||
float3 rgb_rt = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y));
|
||||
float3 rgb_lb = bgra_to_rgb(tex2D<float4>(srcImage, x, y + scale));
|
||||
float3 rgb_rb = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y + scale));
|
||||
|
||||
float2 uv_lt = calcUV(rgb_lt, color_matrix) * 256.0f;
|
||||
float2 uv_rt = calcUV(rgb_rt, color_matrix) * 256.0f;
|
||||
float2 uv_lb = calcUV(rgb_lb, color_matrix) * 256.0f;
|
||||
float2 uv_rb = calcUV(rgb_rb, color_matrix) * 256.0f;
|
||||
|
||||
float2 uv = (uv_lt + uv_lb + uv_rt + uv_rb) * 0.25f;
|
||||
|
||||
dstUV[0] = uv.x;
|
||||
dstUV[1] = uv.y;
|
||||
dstY0[0] = calcY(rgb_lt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
|
||||
dstY0[1] = calcY(rgb_rt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
|
||||
dstY1[0] = calcY(rgb_lb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
|
||||
dstY1[1] = calcY(rgb_rb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
|
||||
}
|
||||
|
||||
int tex_t::copy(std::uint8_t *src, int height, int pitch) {
|
||||
CU_CHECK(cudaMemcpy2DToArray(array, 0, 0, src, pitch, pitch, height, cudaMemcpyDeviceToDevice), "Couldn't copy to cuda array from deviceptr");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::optional<tex_t> tex_t::make(int height, int pitch) {
|
||||
tex_t tex;
|
||||
|
||||
auto format = cudaCreateChannelDesc<uchar4>();
|
||||
CU_CHECK_OPT(cudaMallocArray(&tex.array, &format, pitch, height, cudaArrayDefault), "Couldn't allocate cuda array");
|
||||
|
||||
cudaResourceDesc res {};
|
||||
res.resType = cudaResourceTypeArray;
|
||||
res.res.array.array = tex.array;
|
||||
|
||||
cudaTextureDesc desc {};
|
||||
|
||||
desc.readMode = cudaReadModeNormalizedFloat;
|
||||
desc.filterMode = cudaFilterModePoint;
|
||||
desc.normalizedCoords = false;
|
||||
|
||||
std::fill_n(std::begin(desc.addressMode), 2, cudaAddressModeClamp);
|
||||
|
||||
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.point, &res, &desc, nullptr), "Couldn't create cuda texture that uses point interpolation");
|
||||
|
||||
desc.filterMode = cudaFilterModeLinear;
|
||||
|
||||
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.linear, &res, &desc, nullptr), "Couldn't create cuda texture that uses linear interpolation");
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
tex_t::tex_t() : array {}, texture { INVALID_TEXTURE, INVALID_TEXTURE } {}
|
||||
tex_t::tex_t(tex_t &&other) : array { other.array }, texture { other.texture } {
|
||||
other.array = 0;
|
||||
other.texture.point = INVALID_TEXTURE;
|
||||
other.texture.linear = INVALID_TEXTURE;
|
||||
}
|
||||
|
||||
tex_t &tex_t::operator=(tex_t &&other) {
|
||||
std::swap(array, other.array);
|
||||
std::swap(texture, other.texture);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
tex_t::~tex_t() {
|
||||
if(texture.point != INVALID_TEXTURE) {
|
||||
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.point), "Couldn't deallocate cuda texture that uses point interpolation");
|
||||
|
||||
texture.point = INVALID_TEXTURE;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
if(texture.linear != INVALID_TEXTURE) {
|
||||
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.linear), "Couldn't deallocate cuda texture that uses linear interpolation");
|
||||
|
||||
texture.linear = INVALID_TEXTURE;
|
||||
void freeCudaPtr_t::operator()(void *ptr) {
|
||||
CU_CHECK_IGNORE(cudaFree(ptr), "Couldn't free cuda device pointer");
|
||||
}
|
||||
|
||||
if(array) {
|
||||
CU_CHECK_IGNORE(cudaFreeArray(array), "Couldn't deallocate cuda array");
|
||||
|
||||
array = cudaArray_t {};
|
||||
}
|
||||
}
|
||||
|
||||
sws_t::sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix)
|
||||
: threadsPerBlock { threadsPerBlock }, color_matrix { std::move(color_matrix) } {
|
||||
// Ensure aspect ratio is maintained
|
||||
auto scalar = std::fminf(out_width / (float)in_width, out_height / (float)in_height);
|
||||
auto out_width_f = in_width * scalar;
|
||||
auto out_height_f = in_height * scalar;
|
||||
|
||||
// result is always positive
|
||||
auto offsetX_f = (out_width - out_width_f) / 2;
|
||||
auto offsetY_f = (out_height - out_height_f) / 2;
|
||||
|
||||
viewport.width = out_width_f;
|
||||
viewport.height = out_height_f;
|
||||
|
||||
viewport.offsetX = offsetX_f;
|
||||
viewport.offsetY = offsetY_f;
|
||||
|
||||
scale = 1.0f / scalar;
|
||||
}
|
||||
|
||||
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, int pitch) {
|
||||
cudaDeviceProp props;
|
||||
int device;
|
||||
CU_CHECK_OPT(cudaGetDevice(&device), "Couldn't get cuda device");
|
||||
CU_CHECK_OPT(cudaGetDeviceProperties(&props, device), "Couldn't get cuda device properties");
|
||||
|
||||
auto ptr = make_ptr<cuda_color_t>();
|
||||
if(!ptr) {
|
||||
return std::nullopt;
|
||||
void freeCudaStream_t::operator()(cudaStream_t ptr) {
|
||||
CU_CHECK_IGNORE(cudaStreamDestroy(ptr), "Couldn't free cuda stream");
|
||||
}
|
||||
|
||||
return std::make_optional<sws_t>(in_width, in_height, out_width, out_height, pitch, props.maxThreadsPerMultiProcessor / props.maxBlocksPerMultiProcessor, std::move(ptr));
|
||||
}
|
||||
stream_t make_stream(int flags) {
|
||||
cudaStream_t stream;
|
||||
|
||||
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream) {
|
||||
return convert(Y, UV, pitchY, pitchUV, texture, stream, viewport);
|
||||
}
|
||||
if (!flags) {
|
||||
CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream");
|
||||
} else {
|
||||
CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags");
|
||||
}
|
||||
|
||||
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) {
|
||||
int threadsX = viewport.width / 2;
|
||||
int threadsY = viewport.height / 2;
|
||||
return stream_t {stream};
|
||||
}
|
||||
|
||||
dim3 block(threadsPerBlock);
|
||||
dim3 grid(div_align(threadsX, threadsPerBlock), threadsY);
|
||||
inline __device__ float3 bgra_to_rgb(uchar4 vec) {
|
||||
return make_float3((float) vec.z, (float) vec.y, (float) vec.x);
|
||||
}
|
||||
|
||||
RGBA_to_NV12<<<grid, block, 0, stream>>>(texture, Y, UV, pitchY, pitchUV, scale, viewport, (cuda_color_t *)color_matrix.get());
|
||||
inline __device__ float3 bgra_to_rgb(float4 vec) {
|
||||
return make_float3(vec.z, vec.y, vec.x);
|
||||
}
|
||||
|
||||
return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_NV12 failed");
|
||||
}
|
||||
inline __device__ float2 calcUV(float3 pixel, const cuda_color_t *const color_matrix) {
|
||||
float4 vec_u = color_matrix->color_vec_u;
|
||||
float4 vec_v = color_matrix->color_vec_v;
|
||||
|
||||
void sws_t::apply_colorspace(const video::sunshine_colorspace_t& colorspace) {
|
||||
auto color_p = video::color_vectors_from_colorspace(colorspace);
|
||||
CU_CHECK_IGNORE(cudaMemcpy(color_matrix.get(), color_p, sizeof(video::color_t), cudaMemcpyHostToDevice), "Couldn't copy color matrix to cuda");
|
||||
}
|
||||
float u = dot(pixel, make_float3(vec_u)) + vec_u.w;
|
||||
float v = dot(pixel, make_float3(vec_v)) + vec_v.w;
|
||||
|
||||
int sws_t::load_ram(platf::img_t &img, cudaArray_t array) {
|
||||
return CU_CHECK_IGNORE(cudaMemcpy2DToArray(array, 0, 0, img.data, img.row_pitch, img.width * img.pixel_pitch, img.height, cudaMemcpyHostToDevice), "Couldn't copy to cuda array");
|
||||
}
|
||||
u = u * color_matrix->range_uv.x + color_matrix->range_uv.y;
|
||||
v = v * color_matrix->range_uv.x + color_matrix->range_uv.y;
|
||||
|
||||
} // namespace cuda
|
||||
return make_float2(u, v);
|
||||
}
|
||||
|
||||
inline __device__ float calcY(float3 pixel, const cuda_color_t *const color_matrix) {
|
||||
float4 vec_y = color_matrix->color_vec_y;
|
||||
|
||||
return (dot(pixel, make_float3(vec_y)) + vec_y.w) * color_matrix->range_y.x + color_matrix->range_y.y;
|
||||
}
|
||||
|
||||
__global__ void RGBA_to_NV12(
|
||||
cudaTextureObject_t srcImage,
|
||||
std::uint8_t *dstY,
|
||||
std::uint8_t *dstUV,
|
||||
std::uint32_t dstPitchY,
|
||||
std::uint32_t dstPitchUV,
|
||||
float scale,
|
||||
const viewport_t viewport,
|
||||
const cuda_color_t *const color_matrix
|
||||
) {
|
||||
int idX = (threadIdx.x + blockDim.x * blockIdx.x) * 2;
|
||||
int idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2;
|
||||
|
||||
if (idX >= viewport.width) {
|
||||
return;
|
||||
}
|
||||
if (idY >= viewport.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
float x = idX * scale;
|
||||
float y = idY * scale;
|
||||
|
||||
idX += viewport.offsetX;
|
||||
idY += viewport.offsetY;
|
||||
|
||||
uint8_t *dstY0 = dstY + idX + idY * dstPitchY;
|
||||
uint8_t *dstY1 = dstY + idX + (idY + 1) * dstPitchY;
|
||||
dstUV = dstUV + idX + (idY / 2 * dstPitchUV);
|
||||
|
||||
float3 rgb_lt = bgra_to_rgb(tex2D<float4>(srcImage, x, y));
|
||||
float3 rgb_rt = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y));
|
||||
float3 rgb_lb = bgra_to_rgb(tex2D<float4>(srcImage, x, y + scale));
|
||||
float3 rgb_rb = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y + scale));
|
||||
|
||||
float2 uv_lt = calcUV(rgb_lt, color_matrix) * 256.0f;
|
||||
float2 uv_rt = calcUV(rgb_rt, color_matrix) * 256.0f;
|
||||
float2 uv_lb = calcUV(rgb_lb, color_matrix) * 256.0f;
|
||||
float2 uv_rb = calcUV(rgb_rb, color_matrix) * 256.0f;
|
||||
|
||||
float2 uv = (uv_lt + uv_lb + uv_rt + uv_rb) * 0.25f;
|
||||
|
||||
dstUV[0] = uv.x;
|
||||
dstUV[1] = uv.y;
|
||||
dstY0[0] = calcY(rgb_lt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
|
||||
dstY0[1] = calcY(rgb_rt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
|
||||
dstY1[0] = calcY(rgb_lb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
|
||||
dstY1[1] = calcY(rgb_rb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
|
||||
}
|
||||
|
||||
int tex_t::copy(std::uint8_t *src, int height, int pitch) {
|
||||
CU_CHECK(cudaMemcpy2DToArray(array, 0, 0, src, pitch, pitch, height, cudaMemcpyDeviceToDevice), "Couldn't copy to cuda array from deviceptr");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::optional<tex_t> tex_t::make(int height, int pitch) {
|
||||
tex_t tex;
|
||||
|
||||
auto format = cudaCreateChannelDesc<uchar4>();
|
||||
CU_CHECK_OPT(cudaMallocArray(&tex.array, &format, pitch, height, cudaArrayDefault), "Couldn't allocate cuda array");
|
||||
|
||||
cudaResourceDesc res {};
|
||||
res.resType = cudaResourceTypeArray;
|
||||
res.res.array.array = tex.array;
|
||||
|
||||
cudaTextureDesc desc {};
|
||||
|
||||
desc.readMode = cudaReadModeNormalizedFloat;
|
||||
desc.filterMode = cudaFilterModePoint;
|
||||
desc.normalizedCoords = false;
|
||||
|
||||
std::fill_n(std::begin(desc.addressMode), 2, cudaAddressModeClamp);
|
||||
|
||||
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.point, &res, &desc, nullptr), "Couldn't create cuda texture that uses point interpolation");
|
||||
|
||||
desc.filterMode = cudaFilterModeLinear;
|
||||
|
||||
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.linear, &res, &desc, nullptr), "Couldn't create cuda texture that uses linear interpolation");
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
tex_t::tex_t():
|
||||
array {},
|
||||
texture {INVALID_TEXTURE, INVALID_TEXTURE} {
|
||||
}
|
||||
|
||||
tex_t::tex_t(tex_t &&other):
|
||||
array {other.array},
|
||||
texture {other.texture} {
|
||||
other.array = 0;
|
||||
other.texture.point = INVALID_TEXTURE;
|
||||
other.texture.linear = INVALID_TEXTURE;
|
||||
}
|
||||
|
||||
tex_t &tex_t::operator=(tex_t &&other) {
|
||||
std::swap(array, other.array);
|
||||
std::swap(texture, other.texture);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
tex_t::~tex_t() {
|
||||
if (texture.point != INVALID_TEXTURE) {
|
||||
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.point), "Couldn't deallocate cuda texture that uses point interpolation");
|
||||
|
||||
texture.point = INVALID_TEXTURE;
|
||||
}
|
||||
|
||||
if (texture.linear != INVALID_TEXTURE) {
|
||||
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.linear), "Couldn't deallocate cuda texture that uses linear interpolation");
|
||||
|
||||
texture.linear = INVALID_TEXTURE;
|
||||
}
|
||||
|
||||
if (array) {
|
||||
CU_CHECK_IGNORE(cudaFreeArray(array), "Couldn't deallocate cuda array");
|
||||
|
||||
array = cudaArray_t {};
|
||||
}
|
||||
}
|
||||
|
||||
sws_t::sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix):
|
||||
threadsPerBlock {threadsPerBlock},
|
||||
color_matrix {std::move(color_matrix)} {
|
||||
// Ensure aspect ratio is maintained
|
||||
auto scalar = std::fminf(out_width / (float) in_width, out_height / (float) in_height);
|
||||
auto out_width_f = in_width * scalar;
|
||||
auto out_height_f = in_height * scalar;
|
||||
|
||||
// result is always positive
|
||||
auto offsetX_f = (out_width - out_width_f) / 2;
|
||||
auto offsetY_f = (out_height - out_height_f) / 2;
|
||||
|
||||
viewport.width = out_width_f;
|
||||
viewport.height = out_height_f;
|
||||
|
||||
viewport.offsetX = offsetX_f;
|
||||
viewport.offsetY = offsetY_f;
|
||||
|
||||
scale = 1.0f / scalar;
|
||||
}
|
||||
|
||||
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, int pitch) {
|
||||
cudaDeviceProp props;
|
||||
int device;
|
||||
CU_CHECK_OPT(cudaGetDevice(&device), "Couldn't get cuda device");
|
||||
CU_CHECK_OPT(cudaGetDeviceProperties(&props, device), "Couldn't get cuda device properties");
|
||||
|
||||
auto ptr = make_ptr<cuda_color_t>();
|
||||
if (!ptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::make_optional<sws_t>(in_width, in_height, out_width, out_height, pitch, props.maxThreadsPerMultiProcessor / props.maxBlocksPerMultiProcessor, std::move(ptr));
|
||||
}
|
||||
|
||||
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream) {
|
||||
return convert(Y, UV, pitchY, pitchUV, texture, stream, viewport);
|
||||
}
|
||||
|
||||
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) {
|
||||
int threadsX = viewport.width / 2;
|
||||
int threadsY = viewport.height / 2;
|
||||
|
||||
dim3 block(threadsPerBlock);
|
||||
dim3 grid(div_align(threadsX, threadsPerBlock), threadsY);
|
||||
|
||||
RGBA_to_NV12<<<grid, block, 0, stream>>>(texture, Y, UV, pitchY, pitchUV, scale, viewport, (cuda_color_t *) color_matrix.get());
|
||||
|
||||
return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_NV12 failed");
|
||||
}
|
||||
|
||||
void sws_t::apply_colorspace(const video::sunshine_colorspace_t &colorspace) {
|
||||
auto color_p = video::color_vectors_from_colorspace(colorspace);
|
||||
CU_CHECK_IGNORE(cudaMemcpy(color_matrix.get(), color_p, sizeof(video::color_t), cudaMemcpyHostToDevice), "Couldn't copy color matrix to cuda");
|
||||
}
|
||||
|
||||
int sws_t::load_ram(platf::img_t &img, cudaArray_t array) {
|
||||
return CU_CHECK_IGNORE(cudaMemcpy2DToArray(array, 0, 0, img.data, img.row_pitch, img.width * img.pixel_pitch, img.height, cudaMemcpyHostToDevice), "Couldn't copy to cuda array");
|
||||
}
|
||||
|
||||
} // namespace cuda
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(SUNSHINE_BUILD_CUDA)
|
||||
|
||||
#include "src/video_colorspace.h"
|
||||
|
||||
// standard includes
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// local includes
|
||||
#include "src/video_colorspace.h"
|
||||
|
||||
namespace platf {
|
||||
class avcodec_encode_device_t;
|
||||
class img_t;
|
||||
@@ -22,11 +23,10 @@ namespace platf {
|
||||
namespace cuda {
|
||||
|
||||
namespace nvfbc {
|
||||
std::vector<std::string>
|
||||
display_names();
|
||||
std::vector<std::string> display_names();
|
||||
}
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(int width, int height, bool vram);
|
||||
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram);
|
||||
|
||||
/**
|
||||
* @brief Create a GL->CUDA encoding device for consuming captured dmabufs.
|
||||
@@ -36,11 +36,9 @@ namespace cuda {
|
||||
* @param offset_y Offset of content in captured frame.
|
||||
* @return FFmpeg encoding device context.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y);
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y);
|
||||
|
||||
int
|
||||
init();
|
||||
int init();
|
||||
} // namespace cuda
|
||||
|
||||
typedef struct cudaArray *cudaArray_t;
|
||||
@@ -57,21 +55,18 @@ namespace cuda {
|
||||
|
||||
class freeCudaPtr_t {
|
||||
public:
|
||||
void
|
||||
operator()(void *ptr);
|
||||
void operator()(void *ptr);
|
||||
};
|
||||
|
||||
class freeCudaStream_t {
|
||||
public:
|
||||
void
|
||||
operator()(cudaStream_t ptr);
|
||||
void operator()(cudaStream_t ptr);
|
||||
};
|
||||
|
||||
using ptr_t = std::unique_ptr<void, freeCudaPtr_t>;
|
||||
using stream_t = std::unique_ptr<CUstream_st, freeCudaStream_t>;
|
||||
|
||||
stream_t
|
||||
make_stream(int flags = 0);
|
||||
stream_t make_stream(int flags = 0);
|
||||
|
||||
struct viewport_t {
|
||||
int width, height;
|
||||
@@ -80,19 +75,16 @@ namespace cuda {
|
||||
|
||||
class tex_t {
|
||||
public:
|
||||
static std::optional<tex_t>
|
||||
make(int height, int pitch);
|
||||
static std::optional<tex_t> make(int height, int pitch);
|
||||
|
||||
tex_t();
|
||||
tex_t(tex_t &&);
|
||||
|
||||
tex_t &
|
||||
operator=(tex_t &&other);
|
||||
tex_t &operator=(tex_t &&other);
|
||||
|
||||
~tex_t();
|
||||
|
||||
int
|
||||
copy(std::uint8_t *src, int height, int pitch);
|
||||
int copy(std::uint8_t *src, int height, int pitch);
|
||||
|
||||
cudaArray_t array;
|
||||
|
||||
@@ -113,20 +105,15 @@ namespace cuda {
|
||||
*
|
||||
* pitch -- The size of a single row of pixels in bytes
|
||||
*/
|
||||
static std::optional<sws_t>
|
||||
make(int in_width, int in_height, int out_width, int out_height, int pitch);
|
||||
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, int pitch);
|
||||
|
||||
// Converts loaded image into a CUDevicePtr
|
||||
int
|
||||
convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream);
|
||||
int
|
||||
convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport);
|
||||
int convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream);
|
||||
int convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport);
|
||||
|
||||
void
|
||||
apply_colorspace(const video::sunshine_colorspace_t &colorspace);
|
||||
void apply_colorspace(const video::sunshine_colorspace_t &colorspace);
|
||||
|
||||
int
|
||||
load_ram(platf::img_t &img, cudaArray_t array);
|
||||
int load_ram(platf::img_t &img, cudaArray_t array);
|
||||
|
||||
ptr_t color_matrix;
|
||||
|
||||
@@ -138,4 +125,4 @@ namespace cuda {
|
||||
};
|
||||
} // namespace cuda
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
* @file src/platform/linux/graphics.cpp
|
||||
* @brief Definitions for graphics related functions.
|
||||
*/
|
||||
// standard includes
|
||||
#include <fcntl.h>
|
||||
|
||||
// local includes
|
||||
#include "graphics.h"
|
||||
#include "src/file_handler.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/video.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/pixdesc.h>
|
||||
}
|
||||
@@ -17,8 +19,7 @@ extern "C" {
|
||||
// There aren't that many DRM_FORMAT I need to use, so define them here
|
||||
//
|
||||
// They aren't likely to change any time soon.
|
||||
#define fourcc_code(a, b, c, d) ((std::uint32_t)(a) | ((std::uint32_t)(b) << 8) | \
|
||||
((std::uint32_t)(c) << 16) | ((std::uint32_t)(d) << 24))
|
||||
#define fourcc_code(a, b, c, d) ((std::uint32_t)(a) | ((std::uint32_t)(b) << 8) | ((std::uint32_t)(c) << 16) | ((std::uint32_t)(d) << 24))
|
||||
#define fourcc_mod_code(vendor, val) ((((uint64_t) vendor) << 56) | ((val) & 0x00ffffffffffffffULL))
|
||||
#define DRM_FORMAT_MOD_INVALID fourcc_mod_code(0, ((1ULL << 56) - 1))
|
||||
|
||||
@@ -27,11 +28,11 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace gl {
|
||||
GladGLContext ctx;
|
||||
|
||||
void
|
||||
drain_errors(const std::string_view &prefix) {
|
||||
void drain_errors(const std::string_view &prefix) {
|
||||
GLenum err;
|
||||
while ((err = ctx.GetError()) != GL_NO_ERROR) {
|
||||
BOOST_LOG(error) << "GL: "sv << prefix << ": ["sv << util::hex(err).to_string_view() << ']';
|
||||
@@ -44,13 +45,12 @@ namespace gl {
|
||||
}
|
||||
}
|
||||
|
||||
tex_t
|
||||
tex_t::make(std::size_t count) {
|
||||
tex_t textures { count };
|
||||
tex_t tex_t::make(std::size_t count) {
|
||||
tex_t textures {count};
|
||||
|
||||
ctx.GenTextures(textures.size(), textures.begin());
|
||||
|
||||
float color[] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
float color[] = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
|
||||
for (auto tex : textures) {
|
||||
gl::ctx.BindTexture(GL_TEXTURE_2D, tex);
|
||||
@@ -70,25 +70,22 @@ namespace gl {
|
||||
}
|
||||
}
|
||||
|
||||
frame_buf_t
|
||||
frame_buf_t::make(std::size_t count) {
|
||||
frame_buf_t frame_buf { count };
|
||||
frame_buf_t frame_buf_t::make(std::size_t count) {
|
||||
frame_buf_t frame_buf {count};
|
||||
|
||||
ctx.GenFramebuffers(frame_buf.size(), frame_buf.begin());
|
||||
|
||||
return frame_buf;
|
||||
}
|
||||
|
||||
void
|
||||
frame_buf_t::copy(int id, int texture, int offset_x, int offset_y, int width, int height) {
|
||||
void frame_buf_t::copy(int id, int texture, int offset_x, int offset_y, int width, int height) {
|
||||
gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, (*this)[id]);
|
||||
gl::ctx.ReadBuffer(GL_COLOR_ATTACHMENT0 + id);
|
||||
gl::ctx.BindTexture(GL_TEXTURE_2D, texture);
|
||||
gl::ctx.CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, offset_x, offset_y, width, height);
|
||||
}
|
||||
|
||||
std::string
|
||||
shader_t::err_str() {
|
||||
std::string shader_t::err_str() {
|
||||
int length;
|
||||
ctx.GetShaderiv(handle(), GL_INFO_LOG_LENGTH, &length);
|
||||
|
||||
@@ -102,8 +99,7 @@ namespace gl {
|
||||
return string;
|
||||
}
|
||||
|
||||
util::Either<shader_t, std::string>
|
||||
shader_t::compile(const std::string_view &source, GLenum type) {
|
||||
util::Either<shader_t, std::string> shader_t::compile(const std::string_view &source, GLenum type) {
|
||||
shader_t shader;
|
||||
|
||||
auto data = source.data();
|
||||
@@ -123,13 +119,11 @@ namespace gl {
|
||||
return shader;
|
||||
}
|
||||
|
||||
GLuint
|
||||
shader_t::handle() const {
|
||||
GLuint shader_t::handle() const {
|
||||
return _shader.el;
|
||||
}
|
||||
|
||||
buffer_t
|
||||
buffer_t::make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data) {
|
||||
buffer_t buffer_t::make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data) {
|
||||
buffer_t buffer;
|
||||
buffer._block = block;
|
||||
buffer._size = data.size();
|
||||
@@ -142,25 +136,21 @@ namespace gl {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
GLuint
|
||||
buffer_t::handle() const {
|
||||
GLuint buffer_t::handle() const {
|
||||
return _buffer.el;
|
||||
}
|
||||
|
||||
const char *
|
||||
buffer_t::block() const {
|
||||
const char *buffer_t::block() const {
|
||||
return _block;
|
||||
}
|
||||
|
||||
void
|
||||
buffer_t::update(const std::string_view &view, std::size_t offset) {
|
||||
void buffer_t::update(const std::string_view &view, std::size_t offset) {
|
||||
ctx.BindBuffer(GL_UNIFORM_BUFFER, handle());
|
||||
ctx.BufferSubData(GL_UNIFORM_BUFFER, offset, view.size(), (const void *) view.data());
|
||||
}
|
||||
|
||||
void
|
||||
buffer_t::update(std::string_view *members, std::size_t count, std::size_t offset) {
|
||||
util::buffer_t<std::uint8_t> buffer { _size };
|
||||
void buffer_t::update(std::string_view *members, std::size_t count, std::size_t offset) {
|
||||
util::buffer_t<std::uint8_t> buffer {_size};
|
||||
|
||||
for (int x = 0; x < count; ++x) {
|
||||
auto val = members[x];
|
||||
@@ -171,8 +161,7 @@ namespace gl {
|
||||
update(util::view(buffer.begin(), buffer.end()), offset);
|
||||
}
|
||||
|
||||
std::string
|
||||
program_t::err_str() {
|
||||
std::string program_t::err_str() {
|
||||
int length;
|
||||
ctx.GetProgramiv(handle(), GL_INFO_LOG_LENGTH, &length);
|
||||
|
||||
@@ -186,8 +175,7 @@ namespace gl {
|
||||
return string;
|
||||
}
|
||||
|
||||
util::Either<program_t, std::string>
|
||||
program_t::link(const shader_t &vert, const shader_t &frag) {
|
||||
util::Either<program_t, std::string> program_t::link(const shader_t &vert, const shader_t &frag) {
|
||||
program_t program;
|
||||
|
||||
program._program.el = ctx.CreateProgram();
|
||||
@@ -214,16 +202,14 @@ namespace gl {
|
||||
return program;
|
||||
}
|
||||
|
||||
void
|
||||
program_t::bind(const buffer_t &buffer) {
|
||||
void program_t::bind(const buffer_t &buffer) {
|
||||
ctx.UseProgram(handle());
|
||||
auto i = ctx.GetUniformBlockIndex(handle(), buffer.block());
|
||||
|
||||
ctx.BindBufferBase(GL_UNIFORM_BUFFER, i, buffer.handle());
|
||||
}
|
||||
|
||||
std::optional<buffer_t>
|
||||
program_t::uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count) {
|
||||
std::optional<buffer_t> program_t::uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count) {
|
||||
auto i = ctx.GetUniformBlockIndex(handle(), block);
|
||||
if (i == GL_INVALID_INDEX) {
|
||||
BOOST_LOG(error) << "Couldn't find index of ["sv << block << ']';
|
||||
@@ -235,7 +221,7 @@ namespace gl {
|
||||
|
||||
bool error_flag = false;
|
||||
|
||||
util::buffer_t<GLint> offsets { count };
|
||||
util::buffer_t<GLint> offsets {count};
|
||||
auto indices = (std::uint32_t *) alloca(count * sizeof(std::uint32_t));
|
||||
auto names = (const char **) alloca(count * sizeof(const char *));
|
||||
auto names_p = names;
|
||||
@@ -260,7 +246,7 @@ namespace gl {
|
||||
}
|
||||
|
||||
ctx.GetActiveUniformsiv(handle(), count, indices, GL_UNIFORM_OFFSET, offsets.begin());
|
||||
util::buffer_t<std::uint8_t> buffer { (std::size_t) size };
|
||||
util::buffer_t<std::uint8_t> buffer {(std::size_t) size};
|
||||
|
||||
for (int x = 0; x < count; ++x) {
|
||||
auto val = std::get<1>(members[x]);
|
||||
@@ -268,11 +254,10 @@ namespace gl {
|
||||
std::copy_n((const std::uint8_t *) val.data(), val.size(), &buffer[offsets[x]]);
|
||||
}
|
||||
|
||||
return buffer_t::make(std::move(offsets), block, std::string_view { (char *) buffer.begin(), buffer.size() });
|
||||
return buffer_t::make(std::move(offsets), block, std::string_view {(char *) buffer.begin(), buffer.size()});
|
||||
}
|
||||
|
||||
GLuint
|
||||
program_t::handle() const {
|
||||
GLuint program_t::handle() const {
|
||||
return _program.el;
|
||||
}
|
||||
|
||||
@@ -282,23 +267,24 @@ namespace gbm {
|
||||
device_destroy_fn device_destroy;
|
||||
create_device_fn create_device;
|
||||
|
||||
int
|
||||
init() {
|
||||
static void *handle { nullptr };
|
||||
int init() {
|
||||
static void *handle {nullptr};
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
if (funcs_loaded) return 0;
|
||||
if (funcs_loaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!handle) {
|
||||
handle = dyn::handle({ "libgbm.so.1", "libgbm.so" });
|
||||
handle = dyn::handle({"libgbm.so.1", "libgbm.so"});
|
||||
if (!handle) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<GLADapiproc *, const char *>> funcs {
|
||||
{ (GLADapiproc *) &device_destroy, "gbm_device_destroy" },
|
||||
{ (GLADapiproc *) &create_device, "gbm_create_device" },
|
||||
{(GLADapiproc *) &device_destroy, "gbm_device_destroy"},
|
||||
{(GLADapiproc *) &create_device, "gbm_create_device"},
|
||||
};
|
||||
|
||||
if (dyn::load(handle, funcs)) {
|
||||
@@ -334,16 +320,14 @@ namespace egl {
|
||||
constexpr auto EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT = 0x3449;
|
||||
constexpr auto EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT = 0x344A;
|
||||
|
||||
bool
|
||||
fail() {
|
||||
bool fail() {
|
||||
return eglGetError() != EGL_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof egl::display_t
|
||||
*/
|
||||
display_t
|
||||
make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display) {
|
||||
display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display) {
|
||||
constexpr auto EGL_PLATFORM_GBM_MESA = 0x31D7;
|
||||
constexpr auto EGL_PLATFORM_WAYLAND_KHR = 0x31D8;
|
||||
constexpr auto EGL_PLATFORM_X11_KHR = 0x31D5;
|
||||
@@ -408,10 +392,11 @@ namespace egl {
|
||||
return display;
|
||||
}
|
||||
|
||||
std::optional<ctx_t>
|
||||
make_ctx(display_t::pointer display) {
|
||||
std::optional<ctx_t> make_ctx(display_t::pointer display) {
|
||||
constexpr int conf_attr[] {
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE
|
||||
EGL_RENDERABLE_TYPE,
|
||||
EGL_OPENGL_BIT,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
int count;
|
||||
@@ -427,10 +412,12 @@ namespace egl {
|
||||
}
|
||||
|
||||
constexpr int attr[] {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE
|
||||
EGL_CONTEXT_CLIENT_VERSION,
|
||||
3,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
ctx_t ctx { display, eglCreateContext(display, conf, EGL_NO_CONTEXT, attr) };
|
||||
ctx_t ctx {display, eglCreateContext(display, conf, EGL_NO_CONTEXT, attr)};
|
||||
if (fail()) {
|
||||
BOOST_LOG(error) << "Couldn't create EGL context: ["sv << util::hex(eglGetError()).to_string_view() << ']';
|
||||
return std::nullopt;
|
||||
@@ -465,8 +452,7 @@ namespace egl {
|
||||
EGLAttrib hi;
|
||||
};
|
||||
|
||||
inline plane_attr_t
|
||||
get_plane(std::uint32_t plane_indice) {
|
||||
inline plane_attr_t get_plane(std::uint32_t plane_indice) {
|
||||
switch (plane_indice) {
|
||||
case 0:
|
||||
return {
|
||||
@@ -511,8 +497,7 @@ namespace egl {
|
||||
* @param surface The surface descriptor.
|
||||
* @return Vector of EGL attributes.
|
||||
*/
|
||||
std::vector<EGLAttrib>
|
||||
surface_descriptor_to_egl_attribs(const surface_descriptor_t &surface) {
|
||||
std::vector<EGLAttrib> surface_descriptor_to_egl_attribs(const surface_descriptor_t &surface) {
|
||||
std::vector<EGLAttrib> attribs;
|
||||
|
||||
attribs.emplace_back(EGL_WIDTH);
|
||||
@@ -549,8 +534,7 @@ namespace egl {
|
||||
return attribs;
|
||||
}
|
||||
|
||||
std::optional<rgb_t>
|
||||
import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) {
|
||||
std::optional<rgb_t> import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) {
|
||||
auto attribs = surface_descriptor_to_egl_attribs(xrgb);
|
||||
|
||||
rgb_t rgb {
|
||||
@@ -580,8 +564,7 @@ namespace egl {
|
||||
* @param img The image to use for texture sizing.
|
||||
* @return The new RGB texture.
|
||||
*/
|
||||
rgb_t
|
||||
create_blank(platf::img_t &img) {
|
||||
rgb_t create_blank(platf::img_t &img) {
|
||||
rgb_t rgb {
|
||||
EGL_NO_DISPLAY,
|
||||
EGL_NO_IMAGE,
|
||||
@@ -597,7 +580,7 @@ namespace egl {
|
||||
|
||||
GLenum attachment = GL_COLOR_ATTACHMENT0;
|
||||
gl::ctx.DrawBuffers(1, &attachment);
|
||||
const GLuint rgb_black[] = { 0, 0, 0, 0 };
|
||||
const GLuint rgb_black[] = {0, 0, 0, 0};
|
||||
gl::ctx.ClearBufferuiv(GL_COLOR, 0, rgb_black);
|
||||
|
||||
gl_drain_errors;
|
||||
@@ -605,8 +588,7 @@ namespace egl {
|
||||
return rgb;
|
||||
}
|
||||
|
||||
std::optional<nv12_t>
|
||||
import_target(display_t::pointer egl_display, std::array<file_t, nv12_img_t::num_fds> &&fds, const surface_descriptor_t &y, const surface_descriptor_t &uv) {
|
||||
std::optional<nv12_t> import_target(display_t::pointer egl_display, std::array<file_t, nv12_img_t::num_fds> &&fds, const surface_descriptor_t &y, const surface_descriptor_t &uv) {
|
||||
auto y_attribs = surface_descriptor_to_egl_attribs(y);
|
||||
auto uv_attribs = surface_descriptor_to_egl_attribs(uv);
|
||||
|
||||
@@ -642,8 +624,8 @@ namespace egl {
|
||||
gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]);
|
||||
gl::ctx.DrawBuffers(1, &attachments[x]);
|
||||
|
||||
const float y_black[] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
const float uv_black[] = { 0.5f, 0.5f, 0.5f, 0.5f };
|
||||
const float y_black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
const float uv_black[] = {0.5f, 0.5f, 0.5f, 0.5f};
|
||||
gl::ctx.ClearBufferfv(GL_COLOR, 0, x == 0 ? y_black : uv_black);
|
||||
}
|
||||
|
||||
@@ -661,8 +643,7 @@ namespace egl {
|
||||
* @param format Format of the target frame.
|
||||
* @return The new RGB texture.
|
||||
*/
|
||||
std::optional<nv12_t>
|
||||
create_target(int width, int height, AVPixelFormat format) {
|
||||
std::optional<nv12_t> create_target(int width, int height, AVPixelFormat format) {
|
||||
nv12_t nv12 {
|
||||
EGL_NO_DISPLAY,
|
||||
EGL_NO_IMAGE,
|
||||
@@ -679,12 +660,10 @@ namespace egl {
|
||||
if (fmt_desc->comp[0].depth <= 8) {
|
||||
y_format = GL_R8;
|
||||
uv_format = GL_RG8;
|
||||
}
|
||||
else if (fmt_desc->comp[0].depth <= 16) {
|
||||
} else if (fmt_desc->comp[0].depth <= 16) {
|
||||
y_format = GL_R16;
|
||||
uv_format = GL_RG16;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(error) << "Unsupported target pixel format: "sv << format;
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -693,8 +672,7 @@ namespace egl {
|
||||
gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, y_format, width, height);
|
||||
|
||||
gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[1]);
|
||||
gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, uv_format,
|
||||
width >> fmt_desc->log2_chroma_w, height >> fmt_desc->log2_chroma_h);
|
||||
gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, uv_format, width >> fmt_desc->log2_chroma_w, height >> fmt_desc->log2_chroma_h);
|
||||
|
||||
nv12->buf.bind(std::begin(nv12->tex), std::end(nv12->tex));
|
||||
|
||||
@@ -707,8 +685,8 @@ namespace egl {
|
||||
gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]);
|
||||
gl::ctx.DrawBuffers(1, &attachments[x]);
|
||||
|
||||
const float y_black[] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
const float uv_black[] = { 0.5f, 0.5f, 0.5f, 0.5f };
|
||||
const float y_black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
const float uv_black[] = {0.5f, 0.5f, 0.5f, 0.5f};
|
||||
gl::ctx.ClearBufferfv(GL_COLOR, 0, x == 0 ? y_black : uv_black);
|
||||
}
|
||||
|
||||
@@ -719,8 +697,7 @@ namespace egl {
|
||||
return nv12;
|
||||
}
|
||||
|
||||
void
|
||||
sws_t::apply_colorspace(const video::sunshine_colorspace_t &colorspace) {
|
||||
void sws_t::apply_colorspace(const video::sunshine_colorspace_t &colorspace) {
|
||||
auto color_p = video::color_vectors_from_colorspace(colorspace);
|
||||
|
||||
std::string_view members[] {
|
||||
@@ -737,8 +714,7 @@ namespace egl {
|
||||
program[1].bind(color_matrix);
|
||||
}
|
||||
|
||||
std::optional<sws_t>
|
||||
sws_t::make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex) {
|
||||
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex) {
|
||||
sws_t sws;
|
||||
|
||||
sws.serial = std::numeric_limits<std::uint64_t>::max();
|
||||
@@ -866,8 +842,7 @@ namespace egl {
|
||||
return sws;
|
||||
}
|
||||
|
||||
int
|
||||
sws_t::blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height) {
|
||||
int sws_t::blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height) {
|
||||
auto f = [&]() {
|
||||
std::swap(offsetX, this->offsetX);
|
||||
std::swap(offsetY, this->offsetY);
|
||||
@@ -881,8 +856,7 @@ namespace egl {
|
||||
return convert(fb);
|
||||
}
|
||||
|
||||
std::optional<sws_t>
|
||||
sws_t::make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format) {
|
||||
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format) {
|
||||
GLint gl_format;
|
||||
|
||||
// Decide the bit depth format of the backing texture based the target frame format
|
||||
@@ -916,16 +890,14 @@ namespace egl {
|
||||
return make(in_width, in_height, out_width, out_height, std::move(tex));
|
||||
}
|
||||
|
||||
void
|
||||
sws_t::load_ram(platf::img_t &img) {
|
||||
void sws_t::load_ram(platf::img_t &img) {
|
||||
loaded_texture = tex[0];
|
||||
|
||||
gl::ctx.BindTexture(GL_TEXTURE_2D, loaded_texture);
|
||||
gl::ctx.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, img.width, img.height, GL_BGRA, GL_UNSIGNED_BYTE, img.data);
|
||||
}
|
||||
|
||||
void
|
||||
sws_t::load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture) {
|
||||
void sws_t::load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture) {
|
||||
// When only a sub-part of the image must be encoded...
|
||||
const bool copy = offset_x || offset_y || img.sd.width != in_width || img.sd.height != in_height;
|
||||
if (copy) {
|
||||
@@ -934,8 +906,7 @@ namespace egl {
|
||||
|
||||
loaded_texture = tex[0];
|
||||
framebuf.copy(0, loaded_texture, offset_x, offset_y, in_width, in_height);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
loaded_texture = texture;
|
||||
}
|
||||
|
||||
@@ -985,8 +956,7 @@ namespace egl {
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
sws_t::convert(gl::frame_buf_t &fb) {
|
||||
int sws_t::convert(gl::frame_buf_t &fb) {
|
||||
gl::ctx.BindTexture(GL_TEXTURE_2D, loaded_texture);
|
||||
|
||||
GLenum attachments[] {
|
||||
@@ -1019,7 +989,6 @@ namespace egl {
|
||||
}
|
||||
} // namespace egl
|
||||
|
||||
void
|
||||
free_frame(AVFrame *frame) {
|
||||
void free_frame(AVFrame *frame) {
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
// lib includes
|
||||
#include <glad/egl.h>
|
||||
#include <glad/gl.h>
|
||||
|
||||
// local includes
|
||||
#include "misc.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
@@ -21,35 +24,30 @@
|
||||
#define gl_drain_errors_helper(x) gl::drain_errors(x)
|
||||
#define gl_drain_errors gl_drain_errors_helper(__FILE__ ":" SUNSHINE_STRINGIFY(__LINE__))
|
||||
|
||||
extern "C" int
|
||||
close(int __fd);
|
||||
extern "C" int close(int __fd);
|
||||
|
||||
// X11 Display
|
||||
extern "C" struct _XDisplay;
|
||||
|
||||
struct AVFrame;
|
||||
void
|
||||
free_frame(AVFrame *frame);
|
||||
void free_frame(AVFrame *frame);
|
||||
|
||||
using frame_t = util::safe_ptr<AVFrame, free_frame>;
|
||||
|
||||
namespace gl {
|
||||
extern GladGLContext ctx;
|
||||
void
|
||||
drain_errors(const std::string_view &prefix);
|
||||
void drain_errors(const std::string_view &prefix);
|
||||
|
||||
class tex_t: public util::buffer_t<GLuint> {
|
||||
using util::buffer_t<GLuint>::buffer_t;
|
||||
|
||||
public:
|
||||
tex_t(tex_t &&) = default;
|
||||
tex_t &
|
||||
operator=(tex_t &&) = default;
|
||||
tex_t &operator=(tex_t &&) = default;
|
||||
|
||||
~tex_t();
|
||||
|
||||
static tex_t
|
||||
make(std::size_t count);
|
||||
static tex_t make(std::size_t count);
|
||||
};
|
||||
|
||||
class frame_buf_t: public util::buffer_t<GLuint> {
|
||||
@@ -57,16 +55,13 @@ namespace gl {
|
||||
|
||||
public:
|
||||
frame_buf_t(frame_buf_t &&) = default;
|
||||
frame_buf_t &
|
||||
operator=(frame_buf_t &&) = default;
|
||||
frame_buf_t &operator=(frame_buf_t &&) = default;
|
||||
|
||||
~frame_buf_t();
|
||||
|
||||
static frame_buf_t
|
||||
make(std::size_t count);
|
||||
static frame_buf_t make(std::size_t count);
|
||||
|
||||
inline void
|
||||
bind(std::nullptr_t, std::nullptr_t) {
|
||||
inline void bind(std::nullptr_t, std::nullptr_t) {
|
||||
int x = 0;
|
||||
for (auto fb : (*this)) {
|
||||
ctx.BindFramebuffer(GL_FRAMEBUFFER, fb);
|
||||
@@ -77,9 +72,8 @@ namespace gl {
|
||||
return;
|
||||
}
|
||||
|
||||
template <class It>
|
||||
void
|
||||
bind(It it_begin, It it_end) {
|
||||
template<class It>
|
||||
void bind(It it_begin, It it_end) {
|
||||
using namespace std::literals;
|
||||
if (std::distance(it_begin, it_end) > size()) {
|
||||
BOOST_LOG(warning) << "To many elements to bind"sv;
|
||||
@@ -100,8 +94,7 @@ namespace gl {
|
||||
/**
|
||||
* Copies a part of the framebuffer to texture
|
||||
*/
|
||||
void
|
||||
copy(int id, int texture, int offset_x, int offset_y, int width, int height);
|
||||
void copy(int id, int texture, int offset_x, int offset_y, int width, int height);
|
||||
};
|
||||
|
||||
class shader_t {
|
||||
@@ -112,14 +105,11 @@ namespace gl {
|
||||
});
|
||||
|
||||
public:
|
||||
std::string
|
||||
err_str();
|
||||
std::string err_str();
|
||||
|
||||
static util::Either<shader_t, std::string>
|
||||
compile(const std::string_view &source, GLenum type);
|
||||
static util::Either<shader_t, std::string> compile(const std::string_view &source, GLenum type);
|
||||
|
||||
GLuint
|
||||
handle() const;
|
||||
GLuint handle() const;
|
||||
|
||||
private:
|
||||
shader_internal_t _shader;
|
||||
@@ -133,19 +123,14 @@ namespace gl {
|
||||
});
|
||||
|
||||
public:
|
||||
static buffer_t
|
||||
make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data);
|
||||
static buffer_t make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data);
|
||||
|
||||
GLuint
|
||||
handle() const;
|
||||
GLuint handle() const;
|
||||
|
||||
const char *
|
||||
block() const;
|
||||
const char *block() const;
|
||||
|
||||
void
|
||||
update(const std::string_view &view, std::size_t offset = 0);
|
||||
void
|
||||
update(std::string_view *members, std::size_t count, std::size_t offset = 0);
|
||||
void update(const std::string_view &view, std::size_t offset = 0);
|
||||
void update(std::string_view *members, std::size_t count, std::size_t offset = 0);
|
||||
|
||||
private:
|
||||
const char *_block;
|
||||
@@ -165,20 +150,15 @@ namespace gl {
|
||||
});
|
||||
|
||||
public:
|
||||
std::string
|
||||
err_str();
|
||||
std::string err_str();
|
||||
|
||||
static util::Either<program_t, std::string>
|
||||
link(const shader_t &vert, const shader_t &frag);
|
||||
static util::Either<program_t, std::string> link(const shader_t &vert, const shader_t &frag);
|
||||
|
||||
void
|
||||
bind(const buffer_t &buffer);
|
||||
void bind(const buffer_t &buffer);
|
||||
|
||||
std::optional<buffer_t>
|
||||
uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count);
|
||||
std::optional<buffer_t> uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count);
|
||||
|
||||
GLuint
|
||||
handle() const;
|
||||
GLuint handle() const;
|
||||
|
||||
private:
|
||||
program_internal_t _program;
|
||||
@@ -195,8 +175,7 @@ namespace gbm {
|
||||
|
||||
using gbm_t = util::dyn_safe_ptr<device, &device_destroy>;
|
||||
|
||||
int
|
||||
init();
|
||||
int init();
|
||||
|
||||
} // namespace gbm
|
||||
|
||||
@@ -258,24 +237,23 @@ namespace egl {
|
||||
std::uint32_t offsets[4];
|
||||
};
|
||||
|
||||
display_t
|
||||
make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display);
|
||||
std::optional<ctx_t>
|
||||
make_ctx(display_t::pointer display);
|
||||
display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display);
|
||||
std::optional<ctx_t> make_ctx(display_t::pointer display);
|
||||
|
||||
std::optional<rgb_t>
|
||||
import_source(
|
||||
display_t::pointer egl_display,
|
||||
const surface_descriptor_t &xrgb);
|
||||
import_source(
|
||||
display_t::pointer egl_display,
|
||||
const surface_descriptor_t &xrgb
|
||||
);
|
||||
|
||||
rgb_t
|
||||
create_blank(platf::img_t &img);
|
||||
rgb_t create_blank(platf::img_t &img);
|
||||
|
||||
std::optional<nv12_t>
|
||||
import_target(
|
||||
std::optional<nv12_t> import_target(
|
||||
display_t::pointer egl_display,
|
||||
std::array<file_t, nv12_img_t::num_fds> &&fds,
|
||||
const surface_descriptor_t &y, const surface_descriptor_t &uv);
|
||||
const surface_descriptor_t &y,
|
||||
const surface_descriptor_t &uv
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Creates biplanar YUV textures to render into.
|
||||
@@ -284,8 +262,7 @@ namespace egl {
|
||||
* @param format Format of the target frame.
|
||||
* @return The new RGB texture.
|
||||
*/
|
||||
std::optional<nv12_t>
|
||||
create_target(int width, int height, AVPixelFormat format);
|
||||
std::optional<nv12_t> create_target(int width, int height, AVPixelFormat format);
|
||||
|
||||
class cursor_t: public platf::img_t {
|
||||
public:
|
||||
@@ -304,8 +281,7 @@ namespace egl {
|
||||
reset();
|
||||
}
|
||||
|
||||
void
|
||||
reset() {
|
||||
void reset() {
|
||||
for (auto x = 0; x < 4; ++x) {
|
||||
if (sd.fds[x] >= 0) {
|
||||
close(sd.fds[x]);
|
||||
@@ -323,26 +299,19 @@ namespace egl {
|
||||
|
||||
class sws_t {
|
||||
public:
|
||||
static std::optional<sws_t>
|
||||
make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex);
|
||||
static std::optional<sws_t>
|
||||
make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format);
|
||||
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex);
|
||||
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format);
|
||||
|
||||
// Convert the loaded image into the first two framebuffers
|
||||
int
|
||||
convert(gl::frame_buf_t &fb);
|
||||
int convert(gl::frame_buf_t &fb);
|
||||
|
||||
// Make an area of the image black
|
||||
int
|
||||
blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height);
|
||||
int blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height);
|
||||
|
||||
void
|
||||
load_ram(platf::img_t &img);
|
||||
void
|
||||
load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture);
|
||||
void load_ram(platf::img_t &img);
|
||||
void load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture);
|
||||
|
||||
void
|
||||
apply_colorspace(const video::sunshine_colorspace_t &colorspace);
|
||||
void apply_colorspace(const video::sunshine_colorspace_t &colorspace);
|
||||
|
||||
// The first texture is the monitor image.
|
||||
// The second texture is the cursor image
|
||||
@@ -367,6 +336,5 @@ namespace egl {
|
||||
std::uint64_t serial;
|
||||
};
|
||||
|
||||
bool
|
||||
fail();
|
||||
bool fail();
|
||||
} // namespace egl
|
||||
|
||||
@@ -2,132 +2,114 @@
|
||||
* @file src/platform/linux/input/inputtino.cpp
|
||||
* @brief Definitions for the inputtino Linux input handling.
|
||||
*/
|
||||
// lib includes
|
||||
#include <inputtino/input.hpp>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
#include "src/config.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
// local includes
|
||||
#include "inputtino_common.h"
|
||||
#include "inputtino_gamepad.h"
|
||||
#include "inputtino_keyboard.h"
|
||||
#include "inputtino_mouse.h"
|
||||
#include "inputtino_pen.h"
|
||||
#include "inputtino_touch.h"
|
||||
#include "src/config.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf {
|
||||
|
||||
input_t
|
||||
input() {
|
||||
return { new input_raw_t() };
|
||||
input_t input() {
|
||||
return {new input_raw_t()};
|
||||
}
|
||||
|
||||
std::unique_ptr<client_input_t>
|
||||
allocate_client_input_context(input_t &input) {
|
||||
std::unique_ptr<client_input_t> allocate_client_input_context(input_t &input) {
|
||||
return std::make_unique<client_input_raw_t>(input);
|
||||
}
|
||||
|
||||
void
|
||||
freeInput(void *p) {
|
||||
void freeInput(void *p) {
|
||||
auto *input = (input_raw_t *) p;
|
||||
delete input;
|
||||
}
|
||||
|
||||
void
|
||||
move_mouse(input_t &input, int deltaX, int deltaY) {
|
||||
void move_mouse(input_t &input, int deltaX, int deltaY) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::mouse::move(raw, deltaX, deltaY);
|
||||
}
|
||||
|
||||
void
|
||||
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
|
||||
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::mouse::move_abs(raw, touch_port, x, y);
|
||||
}
|
||||
|
||||
void
|
||||
button_mouse(input_t &input, int button, bool release) {
|
||||
void button_mouse(input_t &input, int button, bool release) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::mouse::button(raw, button, release);
|
||||
}
|
||||
|
||||
void
|
||||
scroll(input_t &input, int high_res_distance) {
|
||||
void scroll(input_t &input, int high_res_distance) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::mouse::scroll(raw, high_res_distance);
|
||||
}
|
||||
|
||||
void
|
||||
hscroll(input_t &input, int high_res_distance) {
|
||||
void hscroll(input_t &input, int high_res_distance) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::mouse::hscroll(raw, high_res_distance);
|
||||
}
|
||||
|
||||
void
|
||||
keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
|
||||
void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::keyboard::update(raw, modcode, release, flags);
|
||||
}
|
||||
|
||||
void
|
||||
unicode(input_t &input, char *utf8, int size) {
|
||||
void unicode(input_t &input, char *utf8, int size) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::keyboard::unicode(raw, utf8, size);
|
||||
}
|
||||
|
||||
void
|
||||
touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
|
||||
void touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
|
||||
auto raw = (client_input_raw_t *) input;
|
||||
platf::touch::update(raw, touch_port, touch);
|
||||
}
|
||||
|
||||
void
|
||||
pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
|
||||
void pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
|
||||
auto raw = (client_input_raw_t *) input;
|
||||
platf::pen::update(raw, touch_port, pen);
|
||||
}
|
||||
|
||||
int
|
||||
alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
|
||||
int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
return platf::gamepad::alloc(raw, id, metadata, feedback_queue);
|
||||
}
|
||||
|
||||
void
|
||||
free_gamepad(input_t &input, int nr) {
|
||||
void free_gamepad(input_t &input, int nr) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::gamepad::free(raw, nr);
|
||||
}
|
||||
|
||||
void
|
||||
gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
|
||||
void gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::gamepad::update(raw, nr, gamepad_state);
|
||||
}
|
||||
|
||||
void
|
||||
gamepad_touch(input_t &input, const gamepad_touch_t &touch) {
|
||||
void gamepad_touch(input_t &input, const gamepad_touch_t &touch) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::gamepad::touch(raw, touch);
|
||||
}
|
||||
|
||||
void
|
||||
gamepad_motion(input_t &input, const gamepad_motion_t &motion) {
|
||||
void gamepad_motion(input_t &input, const gamepad_motion_t &motion) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::gamepad::motion(raw, motion);
|
||||
}
|
||||
|
||||
void
|
||||
gamepad_battery(input_t &input, const gamepad_battery_t &battery) {
|
||||
void gamepad_battery(input_t &input, const gamepad_battery_t &battery) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::gamepad::battery(raw, battery);
|
||||
}
|
||||
|
||||
platform_caps::caps_t
|
||||
get_capabilities() {
|
||||
platform_caps::caps_t get_capabilities() {
|
||||
platform_caps::caps_t caps = 0;
|
||||
// TODO: if has_uinput
|
||||
caps |= platform_caps::pen_touch;
|
||||
@@ -140,14 +122,12 @@ namespace platf {
|
||||
return caps;
|
||||
}
|
||||
|
||||
util::point_t
|
||||
get_mouse_loc(input_t &input) {
|
||||
util::point_t get_mouse_loc(input_t &input) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
return platf::mouse::get_location(raw);
|
||||
}
|
||||
|
||||
std::vector<supported_gamepad_t> &
|
||||
supported_gamepads(input_t *input) {
|
||||
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
|
||||
return platf::gamepad::supported_gamepads(input);
|
||||
}
|
||||
} // namespace platf
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <boost/locale.hpp>
|
||||
#include <inputtino/input.hpp>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
// local includes
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
@@ -94,8 +96,7 @@ namespace platf {
|
||||
inputtino::Result<inputtino::PenTablet> pen;
|
||||
};
|
||||
|
||||
inline float
|
||||
deg2rad(float degree) {
|
||||
inline float deg2rad(float degree) {
|
||||
return degree * (M_PI / 180.f);
|
||||
}
|
||||
} // namespace platf
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
* @file src/platform/linux/input/inputtino_gamepad.cpp
|
||||
* @brief Definitions for inputtino gamepad input handling.
|
||||
*/
|
||||
// lib includes
|
||||
#include <boost/locale.hpp>
|
||||
#include <inputtino/input.hpp>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
// local includes
|
||||
#include "inputtino_common.h"
|
||||
#include "inputtino_gamepad.h"
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
#include "inputtino_common.h"
|
||||
#include "inputtino_gamepad.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::gamepad {
|
||||
@@ -25,69 +26,54 @@ namespace platf::gamepad {
|
||||
GAMEPAD_STATUS ///< Helper to indicate the number of status
|
||||
};
|
||||
|
||||
auto
|
||||
create_xbox_one() {
|
||||
return inputtino::XboxOneJoypad::create({ .name = "Sunshine X-Box One (virtual) pad",
|
||||
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
|
||||
.vendor_id = 0x045E,
|
||||
.product_id = 0x02EA,
|
||||
.version = 0x0408 });
|
||||
auto create_xbox_one() {
|
||||
return inputtino::XboxOneJoypad::create({.name = "Sunshine X-Box One (virtual) pad",
|
||||
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
|
||||
.vendor_id = 0x045E,
|
||||
.product_id = 0x02EA,
|
||||
.version = 0x0408});
|
||||
}
|
||||
|
||||
auto
|
||||
create_switch() {
|
||||
return inputtino::SwitchJoypad::create({ .name = "Sunshine Nintendo (virtual) pad",
|
||||
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
|
||||
.vendor_id = 0x057e,
|
||||
.product_id = 0x2009,
|
||||
.version = 0x8111 });
|
||||
auto create_switch() {
|
||||
return inputtino::SwitchJoypad::create({.name = "Sunshine Nintendo (virtual) pad",
|
||||
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
|
||||
.vendor_id = 0x057e,
|
||||
.product_id = 0x2009,
|
||||
.version = 0x8111});
|
||||
}
|
||||
|
||||
auto
|
||||
create_ds5() {
|
||||
return inputtino::PS5Joypad::create({ .name = "Sunshine DualSense (virtual) pad",
|
||||
.vendor_id = 0x054C,
|
||||
.product_id = 0x0CE6,
|
||||
.version = 0x8111 });
|
||||
auto create_ds5() {
|
||||
return inputtino::PS5Joypad::create({.name = "Sunshine DualSense (virtual) pad", .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111});
|
||||
}
|
||||
|
||||
int
|
||||
alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
|
||||
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
|
||||
ControllerType selectedGamepadType;
|
||||
|
||||
if (config::input.gamepad == "xone"sv) {
|
||||
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (manual selection)"sv;
|
||||
selectedGamepadType = XboxOneWired;
|
||||
}
|
||||
else if (config::input.gamepad == "ds5"sv) {
|
||||
} else if (config::input.gamepad == "ds5"sv) {
|
||||
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualSense 5 controller (manual selection)"sv;
|
||||
selectedGamepadType = DualSenseWired;
|
||||
}
|
||||
else if (config::input.gamepad == "switch"sv) {
|
||||
} else if (config::input.gamepad == "switch"sv) {
|
||||
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (manual selection)"sv;
|
||||
selectedGamepadType = SwitchProWired;
|
||||
}
|
||||
else if (metadata.type == LI_CTYPE_XBOX) {
|
||||
} else if (metadata.type == LI_CTYPE_XBOX) {
|
||||
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (auto-selected by client-reported type)"sv;
|
||||
selectedGamepadType = XboxOneWired;
|
||||
}
|
||||
else if (metadata.type == LI_CTYPE_PS) {
|
||||
} else if (metadata.type == LI_CTYPE_PS) {
|
||||
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by client-reported type)"sv;
|
||||
selectedGamepadType = DualSenseWired;
|
||||
}
|
||||
else if (metadata.type == LI_CTYPE_NINTENDO) {
|
||||
} else if (metadata.type == LI_CTYPE_NINTENDO) {
|
||||
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (auto-selected by client-reported type)"sv;
|
||||
selectedGamepadType = SwitchProWired;
|
||||
}
|
||||
else if (config::input.motion_as_ds4 && (metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
|
||||
} else if (config::input.motion_as_ds4 && (metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
|
||||
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by motion sensor presence)"sv;
|
||||
selectedGamepadType = DualSenseWired;
|
||||
}
|
||||
else if (config::input.touchpad_as_ds4 && (metadata.capabilities & LI_CCAP_TOUCHPAD)) {
|
||||
} else if (config::input.touchpad_as_ds4 && (metadata.capabilities & LI_CCAP_TOUCHPAD)) {
|
||||
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by touchpad presence)"sv;
|
||||
selectedGamepadType = DualSenseWired;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (default)"sv;
|
||||
selectedGamepadType = XboxOneWired;
|
||||
}
|
||||
@@ -102,8 +88,7 @@ namespace platf::gamepad {
|
||||
if (metadata.capabilities & LI_CCAP_RGB_LED) {
|
||||
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " has an RGB LED, but it is not usable when emulating a joypad different from DS5"sv;
|
||||
}
|
||||
}
|
||||
else if (selectedGamepadType == DualSenseWired) {
|
||||
} else if (selectedGamepadType == DualSenseWired) {
|
||||
if (!(metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
|
||||
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " is emulating a DualShock 5 controller, but the client gamepad doesn't have motion sensors active"sv;
|
||||
}
|
||||
@@ -125,73 +110,71 @@ namespace platf::gamepad {
|
||||
};
|
||||
|
||||
switch (selectedGamepadType) {
|
||||
case XboxOneWired: {
|
||||
auto xOne = create_xbox_one();
|
||||
if (xOne) {
|
||||
(*xOne).set_on_rumble(on_rumble_fn);
|
||||
gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne));
|
||||
raw->gamepads[id.globalIndex] = std::move(gamepad);
|
||||
return 0;
|
||||
case XboxOneWired:
|
||||
{
|
||||
auto xOne = create_xbox_one();
|
||||
if (xOne) {
|
||||
(*xOne).set_on_rumble(on_rumble_fn);
|
||||
gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne));
|
||||
raw->gamepads[id.globalIndex] = std::move(gamepad);
|
||||
return 0;
|
||||
} else {
|
||||
BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage();
|
||||
return -1;
|
||||
case SwitchProWired:
|
||||
{
|
||||
auto switchPro = create_switch();
|
||||
if (switchPro) {
|
||||
(*switchPro).set_on_rumble(on_rumble_fn);
|
||||
gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro));
|
||||
raw->gamepads[id.globalIndex] = std::move(gamepad);
|
||||
return 0;
|
||||
} else {
|
||||
BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
case SwitchProWired: {
|
||||
auto switchPro = create_switch();
|
||||
if (switchPro) {
|
||||
(*switchPro).set_on_rumble(on_rumble_fn);
|
||||
gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro));
|
||||
raw->gamepads[id.globalIndex] = std::move(gamepad);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
case DualSenseWired: {
|
||||
auto ds5 = create_ds5();
|
||||
if (ds5) {
|
||||
(*ds5).set_on_rumble(on_rumble_fn);
|
||||
(*ds5).set_on_led([feedback_queue, idx = id.clientRelativeIndex, gamepad](int r, int g, int b) {
|
||||
// Don't resend duplicate LED data
|
||||
if (gamepad->last_rgb_led.type == platf::gamepad_feedback_e::set_rgb_led && gamepad->last_rgb_led.data.rgb_led.r == r && gamepad->last_rgb_led.data.rgb_led.g == g && gamepad->last_rgb_led.data.rgb_led.b == b) {
|
||||
return;
|
||||
}
|
||||
case DualSenseWired:
|
||||
{
|
||||
auto ds5 = create_ds5();
|
||||
if (ds5) {
|
||||
(*ds5).set_on_rumble(on_rumble_fn);
|
||||
(*ds5).set_on_led([feedback_queue, idx = id.clientRelativeIndex, gamepad](int r, int g, int b) {
|
||||
// Don't resend duplicate LED data
|
||||
if (gamepad->last_rgb_led.type == platf::gamepad_feedback_e::set_rgb_led && gamepad->last_rgb_led.data.rgb_led.r == r && gamepad->last_rgb_led.data.rgb_led.g == g && gamepad->last_rgb_led.data.rgb_led.b == b) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto msg = gamepad_feedback_msg_t::make_rgb_led(idx, r, g, b);
|
||||
feedback_queue->raise(msg);
|
||||
gamepad->last_rgb_led = msg;
|
||||
});
|
||||
auto msg = gamepad_feedback_msg_t::make_rgb_led(idx, r, g, b);
|
||||
feedback_queue->raise(msg);
|
||||
gamepad->last_rgb_led = msg;
|
||||
});
|
||||
|
||||
// Activate the motion sensors
|
||||
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_ACCEL, 100));
|
||||
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_GYRO, 100));
|
||||
// Activate the motion sensors
|
||||
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_ACCEL, 100));
|
||||
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_GYRO, 100));
|
||||
|
||||
gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5));
|
||||
raw->gamepads[id.globalIndex] = std::move(gamepad);
|
||||
return 0;
|
||||
gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5));
|
||||
raw->gamepads[id.globalIndex] = std::move(gamepad);
|
||||
return 0;
|
||||
} else {
|
||||
BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
free(input_raw_t *raw, int nr) {
|
||||
void free(input_raw_t *raw, int nr) {
|
||||
// This will call the destructor which in turn will stop the background threads for rumble and LED (and ultimately remove the joypad device)
|
||||
raw->gamepads[nr]->joypad.reset();
|
||||
raw->gamepads[nr].reset();
|
||||
}
|
||||
|
||||
void
|
||||
update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
|
||||
void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
|
||||
auto gamepad = raw->gamepads[nr];
|
||||
if (!gamepad) {
|
||||
return;
|
||||
@@ -203,11 +186,10 @@ namespace platf::gamepad {
|
||||
gc.set_stick(inputtino::Joypad::RS, gamepad_state.rsX, gamepad_state.rsY);
|
||||
gc.set_triggers(gamepad_state.lt, gamepad_state.rt);
|
||||
},
|
||||
*gamepad->joypad);
|
||||
*gamepad->joypad);
|
||||
}
|
||||
|
||||
void
|
||||
touch(input_raw_t *raw, const gamepad_touch_t &touch) {
|
||||
void touch(input_raw_t *raw, const gamepad_touch_t &touch) {
|
||||
auto gamepad = raw->gamepads[touch.id.globalIndex];
|
||||
if (!gamepad) {
|
||||
return;
|
||||
@@ -216,15 +198,13 @@ namespace platf::gamepad {
|
||||
if (std::holds_alternative<inputtino::PS5Joypad>(*gamepad->joypad)) {
|
||||
if (touch.pressure > 0.5) {
|
||||
std::get<inputtino::PS5Joypad>(*gamepad->joypad).place_finger(touch.pointerId, touch.x * inputtino::PS5Joypad::touchpad_width, touch.y * inputtino::PS5Joypad::touchpad_height);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
std::get<inputtino::PS5Joypad>(*gamepad->joypad).release_finger(touch.pointerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
motion(input_raw_t *raw, const gamepad_motion_t &motion) {
|
||||
void motion(input_raw_t *raw, const gamepad_motion_t &motion) {
|
||||
auto gamepad = raw->gamepads[motion.id.globalIndex];
|
||||
if (!gamepad) {
|
||||
return;
|
||||
@@ -242,8 +222,7 @@ namespace platf::gamepad {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
battery(input_raw_t *raw, const gamepad_battery_t &battery) {
|
||||
void battery(input_raw_t *raw, const gamepad_battery_t &battery) {
|
||||
auto gamepad = raw->gamepads[battery.id.globalIndex];
|
||||
if (!gamepad) {
|
||||
return;
|
||||
@@ -272,14 +251,13 @@ namespace platf::gamepad {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<supported_gamepad_t> &
|
||||
supported_gamepads(input_t *input) {
|
||||
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
|
||||
if (!input) {
|
||||
static std::vector gps {
|
||||
supported_gamepad_t { "auto", true, "" },
|
||||
supported_gamepad_t { "xone", false, "" },
|
||||
supported_gamepad_t { "ds5", false, "" },
|
||||
supported_gamepad_t { "switch", false, "" },
|
||||
supported_gamepad_t {"auto", true, ""},
|
||||
supported_gamepad_t {"xone", false, ""},
|
||||
supported_gamepad_t {"ds5", false, ""},
|
||||
supported_gamepad_t {"switch", false, ""},
|
||||
};
|
||||
|
||||
return gps;
|
||||
@@ -290,10 +268,10 @@ namespace platf::gamepad {
|
||||
auto xOne = create_xbox_one();
|
||||
|
||||
static std::vector gps {
|
||||
supported_gamepad_t { "auto", true, "" },
|
||||
supported_gamepad_t { "xone", static_cast<bool>(xOne), !xOne ? xOne.getErrorMessage() : "" },
|
||||
supported_gamepad_t { "ds5", static_cast<bool>(ds5), !ds5 ? ds5.getErrorMessage() : "" },
|
||||
supported_gamepad_t { "switch", static_cast<bool>(switchPro), !switchPro ? switchPro.getErrorMessage() : "" },
|
||||
supported_gamepad_t {"auto", true, ""},
|
||||
supported_gamepad_t {"xone", static_cast<bool>(xOne), !xOne ? xOne.getErrorMessage() : ""},
|
||||
supported_gamepad_t {"ds5", static_cast<bool>(ds5), !ds5 ? ds5.getErrorMessage() : ""},
|
||||
supported_gamepad_t {"switch", static_cast<bool>(switchPro), !switchPro ? switchPro.getErrorMessage() : ""},
|
||||
};
|
||||
|
||||
for (auto &[name, is_enabled, reason_disabled] : gps) {
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <boost/locale.hpp>
|
||||
#include <inputtino/input.hpp>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
#include "src/platform/common.h"
|
||||
|
||||
// local includes
|
||||
#include "inputtino_common.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
@@ -22,24 +23,17 @@ namespace platf::gamepad {
|
||||
SwitchProWired ///< Switch Pro Wired Controller
|
||||
};
|
||||
|
||||
int
|
||||
alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
|
||||
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
|
||||
|
||||
void
|
||||
free(input_raw_t *raw, int nr);
|
||||
void free(input_raw_t *raw, int nr);
|
||||
|
||||
void
|
||||
update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state);
|
||||
void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state);
|
||||
|
||||
void
|
||||
touch(input_raw_t *raw, const gamepad_touch_t &touch);
|
||||
void touch(input_raw_t *raw, const gamepad_touch_t &touch);
|
||||
|
||||
void
|
||||
motion(input_raw_t *raw, const gamepad_motion_t &motion);
|
||||
void motion(input_raw_t *raw, const gamepad_motion_t &motion);
|
||||
|
||||
void
|
||||
battery(input_raw_t *raw, const gamepad_battery_t &battery);
|
||||
void battery(input_raw_t *raw, const gamepad_battery_t &battery);
|
||||
|
||||
std::vector<supported_gamepad_t> &
|
||||
supported_gamepads(input_t *input);
|
||||
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input);
|
||||
} // namespace platf::gamepad
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
* @file src/platform/linux/input/inputtino_keyboard.cpp
|
||||
* @brief Definitions for inputtino keyboard input handling.
|
||||
*/
|
||||
// lib includes
|
||||
#include <boost/locale.hpp>
|
||||
#include <inputtino/input.hpp>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
// local includes
|
||||
#include "inputtino_common.h"
|
||||
#include "inputtino_keyboard.h"
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
#include "inputtino_common.h"
|
||||
#include "inputtino_keyboard.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::keyboard {
|
||||
@@ -25,8 +26,7 @@ namespace platf::keyboard {
|
||||
*
|
||||
* adapted from: https://stackoverflow.com/a/7639754
|
||||
*/
|
||||
std::string
|
||||
to_hex(const std::basic_string<char32_t> &str) {
|
||||
std::string to_hex(const std::basic_string<char32_t> &str) {
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::setfill('0');
|
||||
for (const auto &ch : str) {
|
||||
@@ -42,48 +42,123 @@ namespace platf::keyboard {
|
||||
* A map of linux scan code -> Moonlight keyboard code
|
||||
*/
|
||||
static const std::map<short, short> key_mappings = {
|
||||
{ KEY_BACKSPACE, 0x08 }, { KEY_TAB, 0x09 }, { KEY_ENTER, 0x0D }, { KEY_LEFTSHIFT, 0x10 },
|
||||
{ KEY_LEFTCTRL, 0x11 }, { KEY_CAPSLOCK, 0x14 }, { KEY_ESC, 0x1B }, { KEY_SPACE, 0x20 },
|
||||
{ KEY_PAGEUP, 0x21 }, { KEY_PAGEDOWN, 0x22 }, { KEY_END, 0x23 }, { KEY_HOME, 0x24 },
|
||||
{ KEY_LEFT, 0x25 }, { KEY_UP, 0x26 }, { KEY_RIGHT, 0x27 }, { KEY_DOWN, 0x28 },
|
||||
{ KEY_SYSRQ, 0x2C }, { KEY_INSERT, 0x2D }, { KEY_DELETE, 0x2E }, { KEY_0, 0x30 },
|
||||
{ KEY_1, 0x31 }, { KEY_2, 0x32 }, { KEY_3, 0x33 }, { KEY_4, 0x34 },
|
||||
{ KEY_5, 0x35 }, { KEY_6, 0x36 }, { KEY_7, 0x37 }, { KEY_8, 0x38 },
|
||||
{ KEY_9, 0x39 }, { KEY_A, 0x41 }, { KEY_B, 0x42 }, { KEY_C, 0x43 },
|
||||
{ KEY_D, 0x44 }, { KEY_E, 0x45 }, { KEY_F, 0x46 }, { KEY_G, 0x47 },
|
||||
{ KEY_H, 0x48 }, { KEY_I, 0x49 }, { KEY_J, 0x4A }, { KEY_K, 0x4B },
|
||||
{ KEY_L, 0x4C }, { KEY_M, 0x4D }, { KEY_N, 0x4E }, { KEY_O, 0x4F },
|
||||
{ KEY_P, 0x50 }, { KEY_Q, 0x51 }, { KEY_R, 0x52 }, { KEY_S, 0x53 },
|
||||
{ KEY_T, 0x54 }, { KEY_U, 0x55 }, { KEY_V, 0x56 }, { KEY_W, 0x57 },
|
||||
{ KEY_X, 0x58 }, { KEY_Y, 0x59 }, { KEY_Z, 0x5A }, { KEY_LEFTMETA, 0x5B },
|
||||
{ KEY_RIGHTMETA, 0x5C }, { KEY_KP0, 0x60 }, { KEY_KP1, 0x61 }, { KEY_KP2, 0x62 },
|
||||
{ KEY_KP3, 0x63 }, { KEY_KP4, 0x64 }, { KEY_KP5, 0x65 }, { KEY_KP6, 0x66 },
|
||||
{ KEY_KP7, 0x67 }, { KEY_KP8, 0x68 }, { KEY_KP9, 0x69 }, { KEY_KPASTERISK, 0x6A },
|
||||
{ KEY_KPPLUS, 0x6B }, { KEY_KPMINUS, 0x6D }, { KEY_KPDOT, 0x6E }, { KEY_KPSLASH, 0x6F },
|
||||
{ KEY_F1, 0x70 }, { KEY_F2, 0x71 }, { KEY_F3, 0x72 }, { KEY_F4, 0x73 },
|
||||
{ KEY_F5, 0x74 }, { KEY_F6, 0x75 }, { KEY_F7, 0x76 }, { KEY_F8, 0x77 },
|
||||
{ KEY_F9, 0x78 }, { KEY_F10, 0x79 }, { KEY_F11, 0x7A }, { KEY_F12, 0x7B },
|
||||
{ KEY_NUMLOCK, 0x90 }, { KEY_SCROLLLOCK, 0x91 }, { KEY_LEFTSHIFT, 0xA0 }, { KEY_RIGHTSHIFT, 0xA1 },
|
||||
{ KEY_LEFTCTRL, 0xA2 }, { KEY_RIGHTCTRL, 0xA3 }, { KEY_LEFTALT, 0xA4 }, { KEY_RIGHTALT, 0xA5 },
|
||||
{ KEY_SEMICOLON, 0xBA }, { KEY_EQUAL, 0xBB }, { KEY_COMMA, 0xBC }, { KEY_MINUS, 0xBD },
|
||||
{ KEY_DOT, 0xBE }, { KEY_SLASH, 0xBF }, { KEY_GRAVE, 0xC0 }, { KEY_LEFTBRACE, 0xDB },
|
||||
{ KEY_BACKSLASH, 0xDC }, { KEY_RIGHTBRACE, 0xDD }, { KEY_APOSTROPHE, 0xDE }, { KEY_102ND, 0xE2 }
|
||||
{KEY_BACKSPACE, 0x08},
|
||||
{KEY_TAB, 0x09},
|
||||
{KEY_ENTER, 0x0D},
|
||||
{KEY_LEFTSHIFT, 0x10},
|
||||
{KEY_LEFTCTRL, 0x11},
|
||||
{KEY_CAPSLOCK, 0x14},
|
||||
{KEY_ESC, 0x1B},
|
||||
{KEY_SPACE, 0x20},
|
||||
{KEY_PAGEUP, 0x21},
|
||||
{KEY_PAGEDOWN, 0x22},
|
||||
{KEY_END, 0x23},
|
||||
{KEY_HOME, 0x24},
|
||||
{KEY_LEFT, 0x25},
|
||||
{KEY_UP, 0x26},
|
||||
{KEY_RIGHT, 0x27},
|
||||
{KEY_DOWN, 0x28},
|
||||
{KEY_SYSRQ, 0x2C},
|
||||
{KEY_INSERT, 0x2D},
|
||||
{KEY_DELETE, 0x2E},
|
||||
{KEY_0, 0x30},
|
||||
{KEY_1, 0x31},
|
||||
{KEY_2, 0x32},
|
||||
{KEY_3, 0x33},
|
||||
{KEY_4, 0x34},
|
||||
{KEY_5, 0x35},
|
||||
{KEY_6, 0x36},
|
||||
{KEY_7, 0x37},
|
||||
{KEY_8, 0x38},
|
||||
{KEY_9, 0x39},
|
||||
{KEY_A, 0x41},
|
||||
{KEY_B, 0x42},
|
||||
{KEY_C, 0x43},
|
||||
{KEY_D, 0x44},
|
||||
{KEY_E, 0x45},
|
||||
{KEY_F, 0x46},
|
||||
{KEY_G, 0x47},
|
||||
{KEY_H, 0x48},
|
||||
{KEY_I, 0x49},
|
||||
{KEY_J, 0x4A},
|
||||
{KEY_K, 0x4B},
|
||||
{KEY_L, 0x4C},
|
||||
{KEY_M, 0x4D},
|
||||
{KEY_N, 0x4E},
|
||||
{KEY_O, 0x4F},
|
||||
{KEY_P, 0x50},
|
||||
{KEY_Q, 0x51},
|
||||
{KEY_R, 0x52},
|
||||
{KEY_S, 0x53},
|
||||
{KEY_T, 0x54},
|
||||
{KEY_U, 0x55},
|
||||
{KEY_V, 0x56},
|
||||
{KEY_W, 0x57},
|
||||
{KEY_X, 0x58},
|
||||
{KEY_Y, 0x59},
|
||||
{KEY_Z, 0x5A},
|
||||
{KEY_LEFTMETA, 0x5B},
|
||||
{KEY_RIGHTMETA, 0x5C},
|
||||
{KEY_KP0, 0x60},
|
||||
{KEY_KP1, 0x61},
|
||||
{KEY_KP2, 0x62},
|
||||
{KEY_KP3, 0x63},
|
||||
{KEY_KP4, 0x64},
|
||||
{KEY_KP5, 0x65},
|
||||
{KEY_KP6, 0x66},
|
||||
{KEY_KP7, 0x67},
|
||||
{KEY_KP8, 0x68},
|
||||
{KEY_KP9, 0x69},
|
||||
{KEY_KPASTERISK, 0x6A},
|
||||
{KEY_KPPLUS, 0x6B},
|
||||
{KEY_KPMINUS, 0x6D},
|
||||
{KEY_KPDOT, 0x6E},
|
||||
{KEY_KPSLASH, 0x6F},
|
||||
{KEY_F1, 0x70},
|
||||
{KEY_F2, 0x71},
|
||||
{KEY_F3, 0x72},
|
||||
{KEY_F4, 0x73},
|
||||
{KEY_F5, 0x74},
|
||||
{KEY_F6, 0x75},
|
||||
{KEY_F7, 0x76},
|
||||
{KEY_F8, 0x77},
|
||||
{KEY_F9, 0x78},
|
||||
{KEY_F10, 0x79},
|
||||
{KEY_F11, 0x7A},
|
||||
{KEY_F12, 0x7B},
|
||||
{KEY_NUMLOCK, 0x90},
|
||||
{KEY_SCROLLLOCK, 0x91},
|
||||
{KEY_LEFTSHIFT, 0xA0},
|
||||
{KEY_RIGHTSHIFT, 0xA1},
|
||||
{KEY_LEFTCTRL, 0xA2},
|
||||
{KEY_RIGHTCTRL, 0xA3},
|
||||
{KEY_LEFTALT, 0xA4},
|
||||
{KEY_RIGHTALT, 0xA5},
|
||||
{KEY_SEMICOLON, 0xBA},
|
||||
{KEY_EQUAL, 0xBB},
|
||||
{KEY_COMMA, 0xBC},
|
||||
{KEY_MINUS, 0xBD},
|
||||
{KEY_DOT, 0xBE},
|
||||
{KEY_SLASH, 0xBF},
|
||||
{KEY_GRAVE, 0xC0},
|
||||
{KEY_LEFTBRACE, 0xDB},
|
||||
{KEY_BACKSLASH, 0xDC},
|
||||
{KEY_RIGHTBRACE, 0xDD},
|
||||
{KEY_APOSTROPHE, 0xDE},
|
||||
{KEY_102ND, 0xE2}
|
||||
};
|
||||
|
||||
void
|
||||
update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) {
|
||||
void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) {
|
||||
if (raw->keyboard) {
|
||||
if (release) {
|
||||
(*raw->keyboard).release(modcode);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
(*raw->keyboard).press(modcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
unicode(input_raw_t *raw, char *utf8, int size) {
|
||||
void unicode(input_raw_t *raw, char *utf8, int size) {
|
||||
if (raw->keyboard) {
|
||||
/* Reading input text as UTF-8 */
|
||||
auto utf8_str = boost::locale::conv::to_utf<wchar_t>(utf8, utf8 + size, "UTF-8");
|
||||
@@ -106,8 +181,7 @@ namespace platf::keyboard {
|
||||
auto wincode = key_mappings.find(keycode);
|
||||
if (keycode == -1 || wincode == key_mappings.end()) {
|
||||
BOOST_LOG(warning) << "Unicode, unable to find keycode for: "sv << ch;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
(*raw->keyboard).press(wincode->second);
|
||||
(*raw->keyboard).release(wincode->second);
|
||||
}
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <boost/locale.hpp>
|
||||
#include <inputtino/input.hpp>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
// local includes
|
||||
#include "inputtino_common.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::keyboard {
|
||||
void
|
||||
update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags);
|
||||
void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags);
|
||||
|
||||
void
|
||||
unicode(input_raw_t *raw, char *utf8, int size);
|
||||
void unicode(input_raw_t *raw, char *utf8, int size);
|
||||
} // namespace platf::keyboard
|
||||
|
||||
@@ -2,38 +2,36 @@
|
||||
* @file src/platform/linux/input/inputtino_mouse.cpp
|
||||
* @brief Definitions for inputtino mouse input handling.
|
||||
*/
|
||||
// lib includes
|
||||
#include <boost/locale.hpp>
|
||||
#include <inputtino/input.hpp>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
// local includes
|
||||
#include "inputtino_common.h"
|
||||
#include "inputtino_mouse.h"
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
#include "inputtino_common.h"
|
||||
#include "inputtino_mouse.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::mouse {
|
||||
|
||||
void
|
||||
move(input_raw_t *raw, int deltaX, int deltaY) {
|
||||
void move(input_raw_t *raw, int deltaX, int deltaY) {
|
||||
if (raw->mouse) {
|
||||
(*raw->mouse).move(deltaX, deltaY);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y) {
|
||||
void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y) {
|
||||
if (raw->mouse) {
|
||||
(*raw->mouse).move_abs(x, y, touch_port.width, touch_port.height);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
button(input_raw_t *raw, int button, bool release) {
|
||||
void button(input_raw_t *raw, int button, bool release) {
|
||||
if (raw->mouse) {
|
||||
inputtino::Mouse::MOUSE_BUTTON btn_type;
|
||||
switch (button) {
|
||||
@@ -58,35 +56,31 @@ namespace platf::mouse {
|
||||
}
|
||||
if (release) {
|
||||
(*raw->mouse).release(btn_type);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
(*raw->mouse).press(btn_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
scroll(input_raw_t *raw, int high_res_distance) {
|
||||
void scroll(input_raw_t *raw, int high_res_distance) {
|
||||
if (raw->mouse) {
|
||||
(*raw->mouse).vertical_scroll(high_res_distance);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
hscroll(input_raw_t *raw, int high_res_distance) {
|
||||
void hscroll(input_raw_t *raw, int high_res_distance) {
|
||||
if (raw->mouse) {
|
||||
(*raw->mouse).horizontal_scroll(high_res_distance);
|
||||
}
|
||||
}
|
||||
|
||||
util::point_t
|
||||
get_location(input_raw_t *raw) {
|
||||
util::point_t get_location(input_raw_t *raw) {
|
||||
if (raw->mouse) {
|
||||
// TODO: decide what to do after https://github.com/games-on-whales/inputtino/issues/6 is resolved.
|
||||
// TODO: auto x = (*raw->mouse).get_absolute_x();
|
||||
// TODO: auto y = (*raw->mouse).get_absolute_y();
|
||||
return { 0, 0 };
|
||||
return {0, 0};
|
||||
}
|
||||
return { 0, 0 };
|
||||
return {0, 0};
|
||||
}
|
||||
} // namespace platf::mouse
|
||||
|
||||
@@ -3,33 +3,27 @@
|
||||
* @brief Declarations for inputtino mouse input handling.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <boost/locale.hpp>
|
||||
#include <inputtino/input.hpp>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
#include "src/platform/common.h"
|
||||
|
||||
// local includes
|
||||
#include "inputtino_common.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::mouse {
|
||||
void
|
||||
move(input_raw_t *raw, int deltaX, int deltaY);
|
||||
void move(input_raw_t *raw, int deltaX, int deltaY);
|
||||
|
||||
void
|
||||
move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y);
|
||||
void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y);
|
||||
|
||||
void
|
||||
button(input_raw_t *raw, int button, bool release);
|
||||
void button(input_raw_t *raw, int button, bool release);
|
||||
|
||||
void
|
||||
scroll(input_raw_t *raw, int high_res_distance);
|
||||
void scroll(input_raw_t *raw, int high_res_distance);
|
||||
|
||||
void
|
||||
hscroll(input_raw_t *raw, int high_res_distance);
|
||||
void hscroll(input_raw_t *raw, int high_res_distance);
|
||||
|
||||
util::point_t
|
||||
get_location(input_raw_t *raw);
|
||||
util::point_t get_location(input_raw_t *raw);
|
||||
} // namespace platf::mouse
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
* @file src/platform/linux/input/inputtino_pen.cpp
|
||||
* @brief Definitions for inputtino pen input handling.
|
||||
*/
|
||||
// lib includes
|
||||
#include <boost/locale.hpp>
|
||||
#include <inputtino/input.hpp>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
// local includes
|
||||
#include "inputtino_common.h"
|
||||
#include "inputtino_pen.h"
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
#include "inputtino_common.h"
|
||||
#include "inputtino_pen.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::pen {
|
||||
void
|
||||
update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen) {
|
||||
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen) {
|
||||
if (raw->pen) {
|
||||
// First set the buttons
|
||||
(*raw->pen).set_btn(inputtino::PenTablet::PRIMARY, pen.penButtons & LI_PEN_BUTTON_PRIMARY);
|
||||
@@ -63,13 +63,7 @@ namespace platf::pen {
|
||||
|
||||
bool is_touching = pen.eventType == LI_TOUCH_EVENT_DOWN || pen.eventType == LI_TOUCH_EVENT_MOVE;
|
||||
|
||||
(*raw->pen).place_tool(tool,
|
||||
pen.x,
|
||||
pen.y,
|
||||
is_touching ? pen.pressureOrDistance : -1,
|
||||
is_touching ? -1 : pen.pressureOrDistance,
|
||||
tilt_x,
|
||||
tilt_y);
|
||||
(*raw->pen).place_tool(tool, pen.x, pen.y, is_touching ? pen.pressureOrDistance : -1, is_touching ? -1 : pen.pressureOrDistance, tilt_x, tilt_y);
|
||||
}
|
||||
}
|
||||
} // namespace platf::pen
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <boost/locale.hpp>
|
||||
#include <inputtino/input.hpp>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
#include "src/platform/common.h"
|
||||
|
||||
// local includes
|
||||
#include "inputtino_common.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::pen {
|
||||
void
|
||||
update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen);
|
||||
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen);
|
||||
}
|
||||
|
||||
@@ -2,52 +2,53 @@
|
||||
* @file src/platform/linux/input/inputtino_touch.cpp
|
||||
* @brief Definitions for inputtino touch input handling.
|
||||
*/
|
||||
// lib includes
|
||||
#include <boost/locale.hpp>
|
||||
#include <inputtino/input.hpp>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
// local includes
|
||||
#include "inputtino_common.h"
|
||||
#include "inputtino_touch.h"
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
#include "inputtino_common.h"
|
||||
#include "inputtino_touch.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::touch {
|
||||
void
|
||||
update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch) {
|
||||
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch) {
|
||||
if (raw->touch) {
|
||||
switch (touch.eventType) {
|
||||
case LI_TOUCH_EVENT_HOVER:
|
||||
case LI_TOUCH_EVENT_DOWN:
|
||||
case LI_TOUCH_EVENT_MOVE: {
|
||||
// Convert our 0..360 range to -90..90 relative to Y axis
|
||||
int adjusted_angle = touch.rotation;
|
||||
case LI_TOUCH_EVENT_MOVE:
|
||||
{
|
||||
// Convert our 0..360 range to -90..90 relative to Y axis
|
||||
int adjusted_angle = touch.rotation;
|
||||
|
||||
if (adjusted_angle > 90 && adjusted_angle < 270) {
|
||||
// Lower hemisphere
|
||||
adjusted_angle = 180 - adjusted_angle;
|
||||
}
|
||||
if (adjusted_angle > 90 && adjusted_angle < 270) {
|
||||
// Lower hemisphere
|
||||
adjusted_angle = 180 - adjusted_angle;
|
||||
}
|
||||
|
||||
// Wrap the value if it's out of range
|
||||
if (adjusted_angle > 90) {
|
||||
adjusted_angle -= 360;
|
||||
// Wrap the value if it's out of range
|
||||
if (adjusted_angle > 90) {
|
||||
adjusted_angle -= 360;
|
||||
} else if (adjusted_angle < -90) {
|
||||
adjusted_angle += 360;
|
||||
}
|
||||
(*raw->touch).place_finger(touch.pointerId, touch.x, touch.y, touch.pressureOrDistance, adjusted_angle);
|
||||
break;
|
||||
}
|
||||
else if (adjusted_angle < -90) {
|
||||
adjusted_angle += 360;
|
||||
}
|
||||
(*raw->touch).place_finger(touch.pointerId, touch.x, touch.y, touch.pressureOrDistance, adjusted_angle);
|
||||
break;
|
||||
}
|
||||
case LI_TOUCH_EVENT_CANCEL:
|
||||
case LI_TOUCH_EVENT_UP:
|
||||
case LI_TOUCH_EVENT_HOVER_LEAVE: {
|
||||
(*raw->touch).release_finger(touch.pointerId);
|
||||
break;
|
||||
}
|
||||
case LI_TOUCH_EVENT_HOVER_LEAVE:
|
||||
{
|
||||
(*raw->touch).release_finger(touch.pointerId);
|
||||
break;
|
||||
}
|
||||
// TODO: LI_TOUCH_EVENT_CANCEL_ALL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <boost/locale.hpp>
|
||||
#include <inputtino/input.hpp>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
#include "src/platform/common.h"
|
||||
|
||||
// local includes
|
||||
#include "inputtino_common.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::touch {
|
||||
void
|
||||
update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch);
|
||||
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user