mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-03-14 07:20:54 +00:00
style: adjust clang-format rules (#2186)
Co-authored-by: Vithorio Polten <reach@vithor.io>
This commit is contained in:
parent
f57aee9025
commit
c2420427b1
@ -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
|
||||
|
@ -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
|
||||
|
363
src/config.cpp
363
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;
|
||||
@ -515,13 +541,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 +582,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 +609,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 +629,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 +659,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 +688,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 +699,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 +708,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 +720,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 +744,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 +752,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 +769,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 +793,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 +809,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 +818,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 +829,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 +842,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 +853,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 +871,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 +882,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 +906,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 +921,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 +947,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 +973,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 +999,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 +1026,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 +1036,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 +1046,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 +1055,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 +1127,15 @@ 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};
|
||||
}
|
||||
}
|
||||
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 +1154,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 +1183,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 +1216,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 +1255,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 +1290,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 +1307,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 +1323,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 +1355,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 +1370,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 +1402,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];
|
||||
|
23
src/config.h
23
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;
|
||||
@ -204,17 +207,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 +259,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
|
||||
|
@ -6,25 +6,22 @@
|
||||
*/
|
||||
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
|
||||
|
||||
#include "process.h"
|
||||
|
||||
// standard includes
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
|
||||
// lib includes
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/asio/ssl/context_base.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <Simple-Web-Server/crypto.hpp>
|
||||
#include <Simple-Web-Server/server_https.hpp>
|
||||
#include <boost/asio/ssl/context_base.hpp>
|
||||
|
||||
// local includes
|
||||
#include "config.h"
|
||||
#include "confighttp.h"
|
||||
#include "crypto.h"
|
||||
@ -36,6 +33,7 @@
|
||||
#include "network.h"
|
||||
#include "nvhttp.h"
|
||||
#include "platform/common.h"
|
||||
#include "process.h"
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
#include "uuid.h"
|
||||
@ -62,8 +60,7 @@ namespace confighttp {
|
||||
* @brief Log the request details.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
print_req(const req_https_t &request) {
|
||||
void print_req(const req_https_t &request) {
|
||||
BOOST_LOG(debug) << "METHOD :: "sv << request->method;
|
||||
BOOST_LOG(debug) << "DESTINATION :: "sv << request->path;
|
||||
|
||||
@ -85,8 +82,7 @@ namespace confighttp {
|
||||
* @param response The HTTP response object.
|
||||
* @param output_tree The JSON tree to send.
|
||||
*/
|
||||
void
|
||||
send_response(resp_https_t response, const pt::ptree &output_tree) {
|
||||
void send_response(resp_https_t response, const pt::ptree &output_tree) {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, output_tree);
|
||||
response->write(data.str());
|
||||
@ -97,12 +93,11 @@ namespace confighttp {
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
send_unauthorized(resp_https_t response, req_https_t request) {
|
||||
void send_unauthorized(resp_https_t response, req_https_t request) {
|
||||
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
|
||||
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
|
||||
const SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{ "WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")" }
|
||||
{"WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")"}
|
||||
};
|
||||
response->write(SimpleWeb::StatusCode::client_error_unauthorized, headers);
|
||||
}
|
||||
@ -113,12 +108,11 @@ namespace confighttp {
|
||||
* @param request The HTTP request object.
|
||||
* @param path The path to redirect to.
|
||||
*/
|
||||
void
|
||||
send_redirect(resp_https_t response, req_https_t request, const char *path) {
|
||||
void send_redirect(resp_https_t response, req_https_t request, const char *path) {
|
||||
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
|
||||
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
|
||||
const SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{ "Location", path }
|
||||
{"Location", path}
|
||||
};
|
||||
response->write(SimpleWeb::StatusCode::redirection_temporary_redirect, headers);
|
||||
}
|
||||
@ -129,8 +123,7 @@ namespace confighttp {
|
||||
* @param request The HTTP request object.
|
||||
* @return True if the user is authenticated, false otherwise.
|
||||
*/
|
||||
bool
|
||||
authenticate(resp_https_t response, req_https_t request) {
|
||||
bool authenticate(resp_https_t response, req_https_t request) {
|
||||
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
|
||||
auto ip_type = net::from_address(address);
|
||||
|
||||
@ -180,8 +173,7 @@ namespace confighttp {
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
not_found(resp_https_t response, [[maybe_unused]] req_https_t request) {
|
||||
void not_found(resp_https_t response, [[maybe_unused]] req_https_t request) {
|
||||
constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_not_found;
|
||||
|
||||
pt::ptree tree;
|
||||
@ -203,8 +195,7 @@ namespace confighttp {
|
||||
* @param request The HTTP request object.
|
||||
* @param error_message The error message to include in the response.
|
||||
*/
|
||||
void
|
||||
bad_request(resp_https_t response, [[maybe_unused]] req_https_t request, const std::string &error_message = "Bad Request") {
|
||||
void bad_request(resp_https_t response, [[maybe_unused]] req_https_t request, const std::string &error_message = "Bad Request") {
|
||||
constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_bad_request;
|
||||
|
||||
pt::ptree tree;
|
||||
@ -227,9 +218,10 @@ namespace confighttp {
|
||||
* @param request The HTTP request object.
|
||||
* @todo combine these functions into a single function that accepts the page, i.e "index", "pin", "apps"
|
||||
*/
|
||||
void
|
||||
getIndexPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void getIndexPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -244,9 +236,10 @@ namespace confighttp {
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
getPinPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void getPinPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -261,9 +254,10 @@ namespace confighttp {
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
getAppsPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void getAppsPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -279,9 +273,10 @@ namespace confighttp {
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
getClientsPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void getClientsPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -296,9 +291,10 @@ namespace confighttp {
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
getConfigPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void getConfigPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -313,9 +309,10 @@ namespace confighttp {
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
getPasswordPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void getPasswordPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -330,8 +327,7 @@ namespace confighttp {
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
getWelcomePage(resp_https_t response, req_https_t request) {
|
||||
void getWelcomePage(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
if (!config::sunshine.username.empty()) {
|
||||
send_redirect(response, request, "/");
|
||||
@ -348,9 +344,10 @@ namespace confighttp {
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
getTroubleshootingPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void getTroubleshootingPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -367,8 +364,7 @@ namespace confighttp {
|
||||
* @todo combine function with getSunshineLogoImage and possibly getNodeModules
|
||||
* @todo use mime_types map
|
||||
*/
|
||||
void
|
||||
getFaviconImage(resp_https_t response, req_https_t request) {
|
||||
void getFaviconImage(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
|
||||
std::ifstream in(WEB_DIR "images/sunshine.ico", std::ios::binary);
|
||||
@ -384,8 +380,7 @@ namespace confighttp {
|
||||
* @todo combine function with getFaviconImage and possibly getNodeModules
|
||||
* @todo use mime_types map
|
||||
*/
|
||||
void
|
||||
getSunshineLogoImage(resp_https_t response, req_https_t request) {
|
||||
void getSunshineLogoImage(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
|
||||
std::ifstream in(WEB_DIR "images/logo-sunshine-45.png", std::ios::binary);
|
||||
@ -400,8 +395,7 @@ namespace confighttp {
|
||||
* @param query The path to check.
|
||||
* @return True if the path is a child of the base path, false otherwise.
|
||||
*/
|
||||
bool
|
||||
isChildPath(fs::path const &base, fs::path const &query) {
|
||||
bool isChildPath(fs::path const &base, fs::path const &query) {
|
||||
auto relPath = fs::relative(base, query);
|
||||
return *(relPath.begin()) != fs::path("..");
|
||||
}
|
||||
@ -411,8 +405,7 @@ namespace confighttp {
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
getNodeModules(resp_https_t response, req_https_t request) {
|
||||
void getNodeModules(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
fs::path webDirPath(WEB_DIR);
|
||||
fs::path nodeModulesPath(webDirPath / "assets");
|
||||
@ -455,9 +448,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/apps| GET| null}
|
||||
*/
|
||||
void
|
||||
getApps(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void getApps(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -474,9 +468,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/logs| GET| null}
|
||||
*/
|
||||
void
|
||||
getLogs(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void getLogs(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -518,9 +513,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/apps| POST| {"name":"Hello, World!","index":-1}}
|
||||
*/
|
||||
void
|
||||
saveApp(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void saveApp(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -551,16 +547,14 @@ namespace confighttp {
|
||||
|
||||
if (index == -1) {
|
||||
apps_node.push_back(std::make_pair("", inputTree));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick
|
||||
pt::ptree newApps;
|
||||
int i = 0;
|
||||
for (const auto &[k, v] : apps_node) {
|
||||
if (i == index) {
|
||||
newApps.push_back(std::make_pair("", inputTree));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
newApps.push_back(std::make_pair("", v));
|
||||
}
|
||||
i++;
|
||||
@ -590,8 +584,7 @@ namespace confighttp {
|
||||
|
||||
outputTree.put("status", true);
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "SaveApp: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
@ -604,9 +597,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/apps/9999| DELETE| null}
|
||||
*/
|
||||
void
|
||||
deleteApp(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void deleteApp(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -622,8 +616,7 @@ namespace confighttp {
|
||||
std::string error;
|
||||
if (const int max_index = static_cast<int>(apps_node.size()) - 1; max_index < 0) {
|
||||
error = "No applications to delete";
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error = "'index' out of range, max index is "s + std::to_string(max_index);
|
||||
}
|
||||
bad_request(response, request, error);
|
||||
@ -646,8 +639,7 @@ namespace confighttp {
|
||||
outputTree.put("status", true);
|
||||
outputTree.put("result", "application "s + std::to_string(index) + " deleted");
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "DeleteApp: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
@ -667,9 +659,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/covers/upload| POST| {"key":"igdb_1234","url":"https://images.igdb.com/igdb/image/upload/t_cover_big_2x/abc123.png"}}
|
||||
*/
|
||||
void
|
||||
uploadCover(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void uploadCover(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
std::stringstream configStream;
|
||||
@ -678,8 +671,7 @@ namespace confighttp {
|
||||
pt::ptree inputTree;
|
||||
try {
|
||||
pt::read_json(ss, inputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "UploadCover: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
return;
|
||||
@ -705,8 +697,7 @@ namespace confighttp {
|
||||
bad_request(response, request, "Failed to download cover");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
auto data = SimpleWeb::Crypto::Base64::decode(inputTree.get<std::string>("data"));
|
||||
|
||||
std::ofstream imgfile(path);
|
||||
@ -724,9 +715,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/config| GET| null}
|
||||
*/
|
||||
void
|
||||
getConfig(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void getConfig(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -751,8 +743,7 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/configLocale| GET| null}
|
||||
*/
|
||||
void
|
||||
getLocale(resp_https_t response, req_https_t request) {
|
||||
void getLocale(resp_https_t response, req_https_t request) {
|
||||
// we need to return the locale whether authenticated or not
|
||||
|
||||
print_req(request);
|
||||
@ -778,9 +769,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/config| POST| {"key":"value"}}
|
||||
*/
|
||||
void
|
||||
saveConfig(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void saveConfig(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -794,15 +786,16 @@ namespace confighttp {
|
||||
pt::read_json(ss, inputTree);
|
||||
for (const auto &[k, v] : inputTree) {
|
||||
std::string value = inputTree.get<std::string>(k);
|
||||
if (value.length() == 0 || value.compare("null") == 0) continue;
|
||||
if (value.length() == 0 || value.compare("null") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
configStream << k << " = " << value << std::endl;
|
||||
}
|
||||
file_handler::write_file(config::sunshine.config_file.c_str(), configStream.str());
|
||||
outputTree.put("status", true);
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "SaveConfig: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
@ -815,9 +808,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/restart| POST| null}
|
||||
*/
|
||||
void
|
||||
restart(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void restart(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -832,9 +826,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/reset-display-device-persistence| POST| null}
|
||||
*/
|
||||
void
|
||||
resetDisplayDevicePersistence(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void resetDisplayDevicePersistence(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -860,9 +855,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/password| POST| {"currentUsername":"admin","currentPassword":"admin","newUsername":"admin","newPassword":"admin","confirmNewPassword":"admin"}}
|
||||
*/
|
||||
void
|
||||
savePassword(resp_https_t response, req_https_t request) {
|
||||
if (!config::sunshine.username.empty() && !authenticate(response, request)) return;
|
||||
void savePassword(resp_https_t response, req_https_t request) {
|
||||
if (!config::sunshine.username.empty() && !authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -881,40 +877,37 @@ namespace confighttp {
|
||||
auto password = inputTree.count("currentPassword") > 0 ? inputTree.get<std::string>("currentPassword") : "";
|
||||
auto newPassword = inputTree.count("newPassword") > 0 ? inputTree.get<std::string>("newPassword") : "";
|
||||
auto confirmPassword = inputTree.count("confirmNewPassword") > 0 ? inputTree.get<std::string>("confirmNewPassword") : "";
|
||||
if (newUsername.length() == 0) newUsername = username;
|
||||
if (newUsername.length() == 0) {
|
||||
newUsername = username;
|
||||
}
|
||||
if (newUsername.length() == 0) {
|
||||
errors.emplace_back("Invalid Username");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
|
||||
if (config::sunshine.username.empty() || (boost::iequals(username, config::sunshine.username) && hash == config::sunshine.password)) {
|
||||
if (newPassword.empty() || newPassword != confirmPassword) {
|
||||
errors.emplace_back("Password Mismatch");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword);
|
||||
http::reload_user_creds(config::sunshine.credentials_file);
|
||||
outputTree.put("status", true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
errors.emplace_back("Invalid Current Credentials");
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.empty()) {
|
||||
// join the errors array
|
||||
std::string error = std::accumulate(errors.begin(), errors.end(), std::string(),
|
||||
[](const std::string &a, const std::string &b) {
|
||||
return a.empty() ? b : a + ", " + b;
|
||||
});
|
||||
std::string error = std::accumulate(errors.begin(), errors.end(), std::string(), [](const std::string &a, const std::string &b) {
|
||||
return a.empty() ? b : a + ", " + b;
|
||||
});
|
||||
bad_request(response, request, error);
|
||||
return;
|
||||
}
|
||||
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "SavePassword: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
@ -934,9 +927,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/pin| POST| {"pin":"1234","name":"My PC"}}
|
||||
*/
|
||||
void
|
||||
savePin(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void savePin(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -952,8 +946,7 @@ namespace confighttp {
|
||||
std::string name = inputTree.get<std::string>("name");
|
||||
outputTree.put("status", nvhttp::pin(pin, name));
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "SavePin: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
@ -966,9 +959,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/clients/unpair-all| POST| null}
|
||||
*/
|
||||
void
|
||||
unpairAll(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void unpairAll(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -993,9 +987,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/unpair| POST| {"uuid":"1234"}}
|
||||
*/
|
||||
void
|
||||
unpair(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void unpair(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -1010,8 +1005,7 @@ namespace confighttp {
|
||||
std::string uuid = inputTree.get<std::string>("uuid");
|
||||
outputTree.put("status", nvhttp::unpair_client(uuid));
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "Unpair: "sv << e.what();
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
@ -1024,9 +1018,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/clients/list| GET| null}
|
||||
*/
|
||||
void
|
||||
listClients(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void listClients(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -1046,9 +1041,10 @@ namespace confighttp {
|
||||
*
|
||||
* @api_examples{/api/apps/close| POST| null}
|
||||
*/
|
||||
void
|
||||
closeApp(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
void closeApp(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_req(request);
|
||||
|
||||
@ -1059,14 +1055,13 @@ namespace confighttp {
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
|
||||
void
|
||||
start() {
|
||||
void start() {
|
||||
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
||||
|
||||
auto port_https = net::map_port(PORT_HTTPS);
|
||||
auto address_family = net::af_from_enum_string(config::sunshine.address_family);
|
||||
|
||||
https_server_t server { config::nvhttp.cert, config::nvhttp.pkey };
|
||||
https_server_t server {config::nvhttp.cert, config::nvhttp.pkey};
|
||||
server.default_resource["DELETE"] = [](resp_https_t response, req_https_t request) {
|
||||
bad_request(response, request);
|
||||
};
|
||||
@ -1116,8 +1111,7 @@ namespace confighttp {
|
||||
server->start([](unsigned short port) {
|
||||
BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << port << "]";
|
||||
});
|
||||
}
|
||||
catch (boost::system::system_error &err) {
|
||||
} catch (boost::system::system_error &err) {
|
||||
// It's possible the exception gets thrown after calling server->stop() from a different thread
|
||||
if (shutdown_event->peek()) {
|
||||
return;
|
||||
@ -1128,7 +1122,7 @@ namespace confighttp {
|
||||
return;
|
||||
}
|
||||
};
|
||||
std::thread tcp { accept_and_run, &server };
|
||||
std::thread tcp {accept_and_run, &server};
|
||||
|
||||
// Wait for any event
|
||||
shutdown_event->view();
|
||||
|
@ -4,34 +4,35 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <functional>
|
||||
#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,8 @@ 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) };
|
||||
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.
|
||||
@ -119,8 +118,7 @@ namespace display_device {
|
||||
* const auto result = reset_persistence();
|
||||
* @examples_end
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
reset_persistence();
|
||||
[[nodiscard]] bool reset_persistence();
|
||||
|
||||
/**
|
||||
* @brief A tag structure indicating that configuration parsing has failed.
|
||||
@ -150,6 +148,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);
|
||||
|
||||
@ -63,23 +60,22 @@ namespace http {
|
||||
}
|
||||
}
|
||||
if (user_creds_exist(config::sunshine.credentials_file)) {
|
||||
if (reload_user_creds(config::sunshine.credentials_file)) return -1;
|
||||
}
|
||||
else {
|
||||
if (reload_user_creds(config::sunshine.credentials_file)) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
|
||||
}
|
||||
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 +87,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 +96,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 +107,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 +162,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,15 +179,13 @@ namespace http {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
download_file(const std::string &url, const std::string &file) {
|
||||
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 {
|
||||
} else {
|
||||
BOOST_LOG(error) << "Couldn't create CURL instance";
|
||||
return false;
|
||||
}
|
||||
@ -234,8 +218,7 @@ namespace http {
|
||||
return result == CURLE_OK;
|
||||
}
|
||||
|
||||
std::string
|
||||
url_escape(const std::string &url) {
|
||||
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 result(string);
|
||||
@ -244,8 +227,7 @@ namespace http {
|
||||
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());
|
||||
char *host;
|
||||
|
@ -4,30 +4,25 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// 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);
|
||||
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
|
||||
|
243
src/nvhttp.cpp
243
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,8 +765,7 @@ namespace nvhttp {
|
||||
response->close_connection_after_response = true;
|
||||
}
|
||||
|
||||
pt::ptree
|
||||
get_all_clients() {
|
||||
pt::ptree get_all_clients() {
|
||||
pt::ptree named_cert_nodes;
|
||||
client_t &client = client_root;
|
||||
for (auto &named_cert : client.named_devices) {
|
||||
@ -796,8 +778,7 @@ namespace nvhttp {
|
||||
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");
|
||||
@ -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 unpair_client(std::string uuid) {
|
||||
int removed = 0;
|
||||
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 {
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
38
src/nvhttp.h
38
src/nvhttp.h
@ -9,8 +9,8 @@
|
||||
#include <string>
|
||||
|
||||
// lib includes
|
||||
#include <Simple-Web-Server/server_https.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <Simple-Web-Server/server_https.hpp>
|
||||
|
||||
// local includes
|
||||
#include "crypto.h"
|
||||
@ -49,21 +49,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 +110,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 +122,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 +136,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 +145,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 +161,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,8 +172,7 @@ 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.
|
||||
@ -187,8 +180,7 @@ namespace nvhttp {
|
||||
* nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1");
|
||||
* @examples_end
|
||||
*/
|
||||
int
|
||||
unpair_client(std::string uniqueid);
|
||||
int unpair_client(std::string uniqueid);
|
||||
|
||||
/**
|
||||
* @brief Get all paired clients.
|
||||
@ -197,8 +189,7 @@ namespace nvhttp {
|
||||
* boost::property_tree::ptree clients = nvhttp::get_all_clients();
|
||||
* @examples_end
|
||||
*/
|
||||
boost::property_tree::ptree
|
||||
get_all_clients();
|
||||
boost::property_tree::ptree get_all_clients();
|
||||
|
||||
/**
|
||||
* @brief Remove all paired clients.
|
||||
@ -206,6 +197,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);
|
||||
}
|
||||
|
@ -2,28 +2,30 @@
|
||||
* @file src/platform/linux/kmsgrab.cpp
|
||||
* @brief Definitions for KMS screen capture.
|
||||
*/
|
||||
#include <drm_fourcc.h>
|
||||
// standard includes
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
// platform includes
|
||||
#include <drm_fourcc.h>
|
||||
#include <linux/dma-buf.h>
|
||||
#include <sys/capability.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <xf86drm.h>
|
||||
#include <xf86drmMode.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
|
||||
// local includes
|
||||
#include "cuda.h"
|
||||
#include "graphics.h"
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/round_robin.h"
|
||||
#include "src/utility.h"
|
||||
#include "src/video.h"
|
||||
|
||||
#include "cuda.h"
|
||||
#include "graphics.h"
|
||||
#include "vaapi.h"
|
||||
#include "wayland.h"
|
||||
|
||||
@ -59,7 +61,10 @@ namespace platf {
|
||||
class wrapper_fb {
|
||||
public:
|
||||
wrapper_fb(drmModeFB *fb):
|
||||
fb { fb }, fb_id { fb->fb_id }, width { fb->width }, height { fb->height } {
|
||||
fb {fb},
|
||||
fb_id {fb->fb_id},
|
||||
width {fb->width},
|
||||
height {fb->height} {
|
||||
pixel_format = DRM_FORMAT_XRGB8888;
|
||||
modifier = DRM_FORMAT_MOD_INVALID;
|
||||
std::fill_n(handles, 4, 0);
|
||||
@ -70,7 +75,10 @@ namespace platf {
|
||||
}
|
||||
|
||||
wrapper_fb(drmModeFB2 *fb2):
|
||||
fb2 { fb2 }, fb_id { fb2->fb_id }, width { fb2->width }, height { fb2->height } {
|
||||
fb2 {fb2},
|
||||
fb_id {fb2->fb_id},
|
||||
width {fb2->width},
|
||||
height {fb2->height} {
|
||||
pixel_format = fb2->pixel_format;
|
||||
modifier = (fb2->flags & DRM_MODE_FB_MODIFIERS) ? fb2->modifier : DRM_FORMAT_MOD_INVALID;
|
||||
|
||||
@ -82,8 +90,7 @@ namespace platf {
|
||||
~wrapper_fb() {
|
||||
if (fb) {
|
||||
drmModeFreeFB(fb);
|
||||
}
|
||||
else if (fb2) {
|
||||
} else if (fb2) {
|
||||
drmModeFreeFB2(fb2);
|
||||
}
|
||||
}
|
||||
@ -116,8 +123,7 @@ namespace platf {
|
||||
static int env_width;
|
||||
static int env_height;
|
||||
|
||||
std::string_view
|
||||
plane_type(std::uint64_t val) {
|
||||
std::string_view plane_type(std::uint64_t val) {
|
||||
switch (val) {
|
||||
case DRM_PLANE_TYPE_OVERLAY:
|
||||
return "DRM_PLANE_TYPE_OVERLAY"sv;
|
||||
@ -165,10 +171,10 @@ namespace platf {
|
||||
|
||||
static std::vector<card_descriptor_t> card_descriptors;
|
||||
|
||||
static std::uint32_t
|
||||
from_view(const std::string_view &string) {
|
||||
static std::uint32_t from_view(const std::string_view &string) {
|
||||
#define _CONVERT(x, y) \
|
||||
if (string == x) return DRM_MODE_CONNECTOR_##y
|
||||
if (string == x) \
|
||||
return DRM_MODE_CONNECTOR_##y
|
||||
|
||||
// This list was created from the following sources:
|
||||
// https://gitlab.freedesktop.org/mesa/drm/-/blob/main/xf86drmMode.c (drmModeGetConnectorTypeName)
|
||||
@ -212,7 +218,7 @@ namespace platf {
|
||||
// value appended to the string. Let's try to read it.
|
||||
if (string.find("Unknown"sv) == 0) {
|
||||
std::uint32_t type;
|
||||
std::string null_terminated_string { string };
|
||||
std::string null_terminated_string {string};
|
||||
if (std::sscanf(null_terminated_string.c_str(), "Unknown%u", &type) == 1) {
|
||||
return type;
|
||||
}
|
||||
@ -225,15 +231,19 @@ namespace platf {
|
||||
class plane_it_t: public round_robin_util::it_wrap_t<plane_t::element_type, plane_it_t> {
|
||||
public:
|
||||
plane_it_t(int fd, std::uint32_t *plane_p, std::uint32_t *end):
|
||||
fd { fd }, plane_p { plane_p }, end { end } {
|
||||
fd {fd},
|
||||
plane_p {plane_p},
|
||||
end {end} {
|
||||
load_next_valid_plane();
|
||||
}
|
||||
|
||||
plane_it_t(int fd, std::uint32_t *end):
|
||||
fd { fd }, plane_p { end }, end { end } {}
|
||||
fd {fd},
|
||||
plane_p {end},
|
||||
end {end} {
|
||||
}
|
||||
|
||||
void
|
||||
load_next_valid_plane() {
|
||||
void load_next_valid_plane() {
|
||||
this->plane.reset();
|
||||
|
||||
for (; plane_p != end; ++plane_p) {
|
||||
@ -248,19 +258,16 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
inc() {
|
||||
void inc() {
|
||||
++plane_p;
|
||||
load_next_valid_plane();
|
||||
}
|
||||
|
||||
bool
|
||||
eq(const plane_it_t &other) const {
|
||||
bool eq(const plane_it_t &other) const {
|
||||
return plane_p == other.plane_p;
|
||||
}
|
||||
|
||||
plane_t::pointer
|
||||
get() {
|
||||
plane_t::pointer get() {
|
||||
return plane.get();
|
||||
}
|
||||
|
||||
@ -289,8 +296,7 @@ namespace platf {
|
||||
public:
|
||||
using connector_interal_t = util::safe_ptr<drmModeConnector, drmModeFreeConnector>;
|
||||
|
||||
int
|
||||
init(const char *path) {
|
||||
int init(const char *path) {
|
||||
cap_sys_admin admin;
|
||||
fd.el = open(path, O_RDWR);
|
||||
|
||||
@ -299,7 +305,7 @@ namespace platf {
|
||||
return -1;
|
||||
}
|
||||
|
||||
version_t ver { drmGetVersion(fd.el) };
|
||||
version_t ver {drmGetVersion(fd.el)};
|
||||
BOOST_LOG(info) << path << " -> "sv << ((ver && ver->name) ? ver->name : "UNKNOWN");
|
||||
|
||||
// Open the render node for this card to share with libva.
|
||||
@ -313,8 +319,7 @@ namespace platf {
|
||||
render_fd.el = dup(fd.el);
|
||||
}
|
||||
free(rendernode_path);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "No render device name for: "sv << path;
|
||||
render_fd.el = dup(fd.el);
|
||||
}
|
||||
@ -346,8 +351,7 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
fb_t
|
||||
fb(plane_t::pointer plane) {
|
||||
fb_t fb(plane_t::pointer plane) {
|
||||
cap_sys_admin admin;
|
||||
|
||||
auto fb2 = drmModeGetFB2(fd.el, plane->fb_id);
|
||||
@ -363,36 +367,30 @@ namespace platf {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
crtc_t
|
||||
crtc(std::uint32_t id) {
|
||||
crtc_t crtc(std::uint32_t id) {
|
||||
return drmModeGetCrtc(fd.el, id);
|
||||
}
|
||||
|
||||
encoder_t
|
||||
encoder(std::uint32_t id) {
|
||||
encoder_t encoder(std::uint32_t id) {
|
||||
return drmModeGetEncoder(fd.el, id);
|
||||
}
|
||||
|
||||
res_t
|
||||
res() {
|
||||
res_t res() {
|
||||
return drmModeGetResources(fd.el);
|
||||
}
|
||||
|
||||
bool
|
||||
is_nvidia() {
|
||||
version_t ver { drmGetVersion(fd.el) };
|
||||
bool is_nvidia() {
|
||||
version_t ver {drmGetVersion(fd.el)};
|
||||
return ver && ver->name && strncmp(ver->name, "nvidia-drm", 10) == 0;
|
||||
}
|
||||
|
||||
bool
|
||||
is_cursor(std::uint32_t plane_id) {
|
||||
bool is_cursor(std::uint32_t plane_id) {
|
||||
auto props = plane_props(plane_id);
|
||||
for (auto &[prop, val] : props) {
|
||||
if (prop->name == "type"sv) {
|
||||
if (val == DRM_PLANE_TYPE_CURSOR) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -401,8 +399,7 @@ namespace platf {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<std::uint64_t>
|
||||
prop_value_by_name(const std::vector<std::pair<prop_t, std::uint64_t>> &props, std::string_view name) {
|
||||
std::optional<std::uint64_t> prop_value_by_name(const std::vector<std::pair<prop_t, std::uint64_t>> &props, std::string_view name) {
|
||||
for (auto &[prop, val] : props) {
|
||||
if (prop->name == name) {
|
||||
return val;
|
||||
@ -411,8 +408,7 @@ namespace platf {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
get_panel_orientation(std::uint32_t plane_id) {
|
||||
std::uint32_t get_panel_orientation(std::uint32_t plane_id) {
|
||||
auto props = plane_props(plane_id);
|
||||
auto value = prop_value_by_name(props, "rotation"sv);
|
||||
if (value) {
|
||||
@ -423,8 +419,7 @@ namespace platf {
|
||||
return DRM_MODE_ROTATE_0;
|
||||
}
|
||||
|
||||
int
|
||||
get_crtc_index_by_id(std::uint32_t crtc_id) {
|
||||
int get_crtc_index_by_id(std::uint32_t crtc_id) {
|
||||
auto resources = res();
|
||||
for (int i = 0; i < resources->count_crtcs; i++) {
|
||||
if (resources->crtcs[i] == crtc_id) {
|
||||
@ -434,13 +429,11 @@ namespace platf {
|
||||
return -1;
|
||||
}
|
||||
|
||||
connector_interal_t
|
||||
connector(std::uint32_t id) {
|
||||
connector_interal_t connector(std::uint32_t id) {
|
||||
return drmModeGetConnector(fd.el, id);
|
||||
}
|
||||
|
||||
std::vector<connector_t>
|
||||
monitors(conn_type_count_t &conn_type_count) {
|
||||
std::vector<connector_t> monitors(conn_type_count_t &conn_type_count) {
|
||||
auto resources = res();
|
||||
if (!resources) {
|
||||
BOOST_LOG(error) << "Couldn't get connector resources"sv;
|
||||
@ -474,8 +467,7 @@ namespace platf {
|
||||
return monitors;
|
||||
}
|
||||
|
||||
file_t
|
||||
handleFD(std::uint32_t handle) {
|
||||
file_t handleFD(std::uint32_t handle) {
|
||||
file_t fb_fd;
|
||||
|
||||
auto status = drmPrimeHandleToFD(fd.el, handle, 0 /* flags */, &fb_fd.el);
|
||||
@ -486,8 +478,7 @@ namespace platf {
|
||||
return fb_fd;
|
||||
}
|
||||
|
||||
std::vector<std::pair<prop_t, std::uint64_t>>
|
||||
props(std::uint32_t id, std::uint32_t type) {
|
||||
std::vector<std::pair<prop_t, std::uint64_t>> props(std::uint32_t id, std::uint32_t type) {
|
||||
obj_prop_t obj_prop = drmModeObjectGetProperties(fd.el, id, type);
|
||||
if (!obj_prop) {
|
||||
return {};
|
||||
@ -503,39 +494,32 @@ namespace platf {
|
||||
return props;
|
||||
}
|
||||
|
||||
std::vector<std::pair<prop_t, std::uint64_t>>
|
||||
plane_props(std::uint32_t id) {
|
||||
std::vector<std::pair<prop_t, std::uint64_t>> plane_props(std::uint32_t id) {
|
||||
return props(id, DRM_MODE_OBJECT_PLANE);
|
||||
}
|
||||
|
||||
std::vector<std::pair<prop_t, std::uint64_t>>
|
||||
crtc_props(std::uint32_t id) {
|
||||
std::vector<std::pair<prop_t, std::uint64_t>> crtc_props(std::uint32_t id) {
|
||||
return props(id, DRM_MODE_OBJECT_CRTC);
|
||||
}
|
||||
|
||||
std::vector<std::pair<prop_t, std::uint64_t>>
|
||||
connector_props(std::uint32_t id) {
|
||||
std::vector<std::pair<prop_t, std::uint64_t>> connector_props(std::uint32_t id) {
|
||||
return props(id, DRM_MODE_OBJECT_CONNECTOR);
|
||||
}
|
||||
|
||||
plane_t
|
||||
operator[](std::uint32_t index) {
|
||||
plane_t operator[](std::uint32_t index) {
|
||||
return drmModeGetPlane(fd.el, plane_res->planes[index]);
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
count() {
|
||||
std::uint32_t count() {
|
||||
return plane_res->count_planes;
|
||||
}
|
||||
|
||||
plane_it_t
|
||||
begin() const {
|
||||
return plane_it_t { fd.el, plane_res->planes, plane_res->planes + plane_res->count_planes };
|
||||
plane_it_t begin() const {
|
||||
return plane_it_t {fd.el, plane_res->planes, plane_res->planes + plane_res->count_planes};
|
||||
}
|
||||
|
||||
plane_it_t
|
||||
end() const {
|
||||
return plane_it_t { fd.el, plane_res->planes + plane_res->count_planes };
|
||||
plane_it_t end() const {
|
||||
return plane_it_t {fd.el, plane_res->planes + plane_res->count_planes};
|
||||
}
|
||||
|
||||
file_t fd;
|
||||
@ -543,16 +527,14 @@ namespace platf {
|
||||
plane_res_t plane_res;
|
||||
};
|
||||
|
||||
std::map<std::uint32_t, monitor_t>
|
||||
map_crtc_to_monitor(const std::vector<connector_t> &connectors) {
|
||||
std::map<std::uint32_t, monitor_t> map_crtc_to_monitor(const std::vector<connector_t> &connectors) {
|
||||
std::map<std::uint32_t, monitor_t> result;
|
||||
|
||||
for (auto &connector : connectors) {
|
||||
result.emplace(connector.crtc_id,
|
||||
monitor_t {
|
||||
connector.type,
|
||||
connector.index,
|
||||
});
|
||||
result.emplace(connector.crtc_id, monitor_t {
|
||||
connector.type,
|
||||
connector.index,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -565,8 +547,7 @@ namespace platf {
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
print(plane_t::pointer plane, fb_t::pointer fb, crtc_t::pointer crtc) {
|
||||
void print(plane_t::pointer plane, fb_t::pointer fb, crtc_t::pointer crtc) {
|
||||
if (crtc) {
|
||||
BOOST_LOG(debug) << "crtc("sv << crtc->x << ", "sv << crtc->y << ')';
|
||||
BOOST_LOG(debug) << "crtc("sv << crtc->width << ", "sv << crtc->height << ')';
|
||||
@ -601,21 +582,22 @@ namespace platf {
|
||||
class display_t: public platf::display_t {
|
||||
public:
|
||||
display_t(mem_type_e mem_type):
|
||||
platf::display_t(), mem_type { mem_type } {}
|
||||
platf::display_t(),
|
||||
mem_type {mem_type} {
|
||||
}
|
||||
|
||||
int
|
||||
init(const std::string &display_name, const ::video::config_t &config) {
|
||||
delay = std::chrono::nanoseconds { 1s } / config.framerate;
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
delay = std::chrono::nanoseconds {1s} / config.framerate;
|
||||
|
||||
int monitor_index = util::from_view(display_name);
|
||||
int monitor = 0;
|
||||
|
||||
fs::path card_dir { "/dev/dri"sv };
|
||||
for (auto &entry : fs::directory_iterator { card_dir }) {
|
||||
fs::path card_dir {"/dev/dri"sv};
|
||||
for (auto &entry : fs::directory_iterator {card_dir}) {
|
||||
auto file = entry.path().filename();
|
||||
|
||||
auto filestring = file.generic_string();
|
||||
if (filestring.size() < 4 || std::string_view { filestring }.substr(0, 4) != "card"sv) {
|
||||
if (filestring.size() < 4 || std::string_view {filestring}.substr(0, 4) != "card"sv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -779,8 +761,7 @@ namespace platf {
|
||||
if (!(plane->possible_crtcs & (1 << crtc_index))) {
|
||||
// Skip cursor planes for other CRTCs
|
||||
continue;
|
||||
}
|
||||
else if (plane->possible_crtcs != (1 << crtc_index)) {
|
||||
} else if (plane->possible_crtcs != (1 << crtc_index)) {
|
||||
// We assume a 1:1 mapping between cursor planes and CRTCs, which seems to
|
||||
// match the behavior of drivers in the real world. If it's violated, we'll
|
||||
// proceed anyway but print a warning in the log.
|
||||
@ -799,8 +780,7 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
is_hdr() {
|
||||
bool is_hdr() {
|
||||
if (!hdr_metadata_blob_id || *hdr_metadata_blob_id == 0) {
|
||||
return false;
|
||||
}
|
||||
@ -846,8 +826,7 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
get_hdr_metadata(SS_HDR_METADATA &metadata) {
|
||||
bool get_hdr_metadata(SS_HDR_METADATA &metadata) {
|
||||
// This performs all the metadata validation
|
||||
if (!is_hdr()) {
|
||||
return false;
|
||||
@ -876,8 +855,7 @@ namespace platf {
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
update_cursor() {
|
||||
void update_cursor() {
|
||||
if (cursor_plane_id < 0) {
|
||||
return;
|
||||
}
|
||||
@ -898,26 +876,19 @@ namespace platf {
|
||||
for (auto &[prop, val] : props) {
|
||||
if (prop->name == "CRTC_X"sv) {
|
||||
prop_crtc_x = val;
|
||||
}
|
||||
else if (prop->name == "CRTC_Y"sv) {
|
||||
} else if (prop->name == "CRTC_Y"sv) {
|
||||
prop_crtc_y = val;
|
||||
}
|
||||
else if (prop->name == "CRTC_W"sv) {
|
||||
} else if (prop->name == "CRTC_W"sv) {
|
||||
prop_crtc_w = val;
|
||||
}
|
||||
else if (prop->name == "CRTC_H"sv) {
|
||||
} else if (prop->name == "CRTC_H"sv) {
|
||||
prop_crtc_h = val;
|
||||
}
|
||||
else if (prop->name == "SRC_X"sv) {
|
||||
} else if (prop->name == "SRC_X"sv) {
|
||||
prop_src_x = val;
|
||||
}
|
||||
else if (prop->name == "SRC_Y"sv) {
|
||||
} else if (prop->name == "SRC_Y"sv) {
|
||||
prop_src_y = val;
|
||||
}
|
||||
else if (prop->name == "SRC_W"sv) {
|
||||
} else if (prop->name == "SRC_W"sv) {
|
||||
prop_src_w = val;
|
||||
}
|
||||
else if (prop->name == "SRC_H"sv) {
|
||||
} else if (prop->name == "SRC_H"sv) {
|
||||
prop_src_h = val;
|
||||
}
|
||||
}
|
||||
@ -951,15 +922,13 @@ namespace platf {
|
||||
if (!plane->fb_id) {
|
||||
captured_cursor.visible = false;
|
||||
captured_cursor.fb_id = 0;
|
||||
}
|
||||
else if (plane->fb_id != captured_cursor.fb_id) {
|
||||
} else if (plane->fb_id != captured_cursor.fb_id) {
|
||||
BOOST_LOG(debug) << "Refreshing cursor image after FB changed"sv;
|
||||
cursor_dirty = true;
|
||||
}
|
||||
else if (*prop_src_x != captured_cursor.prop_src_x ||
|
||||
*prop_src_y != captured_cursor.prop_src_y ||
|
||||
*prop_src_w != captured_cursor.prop_src_w ||
|
||||
*prop_src_h != captured_cursor.prop_src_h) {
|
||||
} else if (*prop_src_x != captured_cursor.prop_src_x ||
|
||||
*prop_src_y != captured_cursor.prop_src_y ||
|
||||
*prop_src_w != captured_cursor.prop_src_w ||
|
||||
*prop_src_h != captured_cursor.prop_src_h) {
|
||||
BOOST_LOG(debug) << "Refreshing cursor image after source dimensions changed"sv;
|
||||
cursor_dirty = true;
|
||||
}
|
||||
@ -1041,8 +1010,7 @@ namespace platf {
|
||||
// If the image is tightly packed, copy it in one shot
|
||||
if (fb->pitches[0] == src_w * 4 && src_x == 0) {
|
||||
memcpy(captured_cursor.pixels.data(), &((std::uint8_t *) mapped_data)[src_y * fb->pitches[0]], src_h * fb->pitches[0]);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Copy row by row to deal with mismatched pitch or an X offset
|
||||
auto pixel_dst = captured_cursor.pixels.data();
|
||||
for (int y = 0; y < src_h; y++) {
|
||||
@ -1068,8 +1036,7 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
inline capture_e
|
||||
refresh(file_t *file, egl::surface_descriptor_t *sd, std::optional<std::chrono::steady_clock::time_point> &frame_timestamp) {
|
||||
inline capture_e refresh(file_t *file, egl::surface_descriptor_t *sd, std::optional<std::chrono::steady_clock::time_point> &frame_timestamp) {
|
||||
// Check for a change in HDR metadata
|
||||
if (connector_id) {
|
||||
auto connector_props = card.connector_props(*connector_id);
|
||||
@ -1123,7 +1090,8 @@ namespace platf {
|
||||
|
||||
if (
|
||||
fb->width != img_width ||
|
||||
fb->height != img_height) {
|
||||
fb->height != img_height
|
||||
) {
|
||||
return capture_e::reinit;
|
||||
}
|
||||
|
||||
@ -1155,10 +1123,10 @@ namespace platf {
|
||||
class display_ram_t: public display_t {
|
||||
public:
|
||||
display_ram_t(mem_type_e mem_type):
|
||||
display_t(mem_type) {}
|
||||
display_t(mem_type) {
|
||||
}
|
||||
|
||||
int
|
||||
init(const std::string &display_name, const ::video::config_t &config) {
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
if (!gbm::create_device) {
|
||||
BOOST_LOG(warning) << "libgbm not initialized"sv;
|
||||
return -1;
|
||||
@ -1189,8 +1157,7 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
capture_e
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
sleep_overshoot_logger.reset();
|
||||
@ -1235,8 +1202,7 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
std::unique_ptr<avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
|
||||
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
|
||||
#ifdef SUNSHINE_BUILD_VAAPI
|
||||
if (mem_type == mem_type_e::vaapi) {
|
||||
return va::make_avcodec_encode_device(width, height, false);
|
||||
@ -1252,8 +1218,7 @@ namespace platf {
|
||||
return std::make_unique<avcodec_encode_device_t>();
|
||||
}
|
||||
|
||||
void
|
||||
blend_cursor(img_t &img) {
|
||||
void blend_cursor(img_t &img) {
|
||||
// TODO: Cursor scaling is not supported in this codepath.
|
||||
// We always draw the cursor at the source size.
|
||||
auto pixels = (int *) img.data;
|
||||
@ -1290,8 +1255,7 @@ namespace platf {
|
||||
auto alpha = (*(uint *) &cursor_pixel) >> 24u;
|
||||
if (alpha == 255) {
|
||||
*pixels_begin = cursor_pixel;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
auto colors_out = (uint8_t *) &cursor_pixel;
|
||||
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
|
||||
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
|
||||
@ -1302,8 +1266,7 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
file_t fb_fd[4];
|
||||
|
||||
egl::surface_descriptor_t sd;
|
||||
@ -1345,8 +1308,7 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
std::shared_ptr<img_t>
|
||||
alloc_img() override {
|
||||
std::shared_ptr<img_t> alloc_img() override {
|
||||
auto img = std::make_shared<kms_img_t>();
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
@ -1357,8 +1319,7 @@ namespace platf {
|
||||
return img;
|
||||
}
|
||||
|
||||
int
|
||||
dummy_img(platf::img_t *img) override {
|
||||
int dummy_img(platf::img_t *img) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1370,10 +1331,10 @@ namespace platf {
|
||||
class display_vram_t: public display_t {
|
||||
public:
|
||||
display_vram_t(mem_type_e mem_type):
|
||||
display_t(mem_type) {}
|
||||
display_t(mem_type) {
|
||||
}
|
||||
|
||||
std::unique_ptr<avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
|
||||
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
|
||||
#ifdef SUNSHINE_BUILD_VAAPI
|
||||
if (mem_type == mem_type_e::vaapi) {
|
||||
return va::make_avcodec_encode_device(width, height, dup(card.render_fd.el), img_offset_x, img_offset_y, true);
|
||||
@ -1390,8 +1351,7 @@ namespace platf {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<img_t>
|
||||
alloc_img() override {
|
||||
std::shared_ptr<img_t> alloc_img() override {
|
||||
auto img = std::make_shared<egl::img_descriptor_t>();
|
||||
|
||||
img->width = width;
|
||||
@ -1406,14 +1366,12 @@ namespace platf {
|
||||
return img;
|
||||
}
|
||||
|
||||
int
|
||||
dummy_img(platf::img_t *img) override {
|
||||
int dummy_img(platf::img_t *img) override {
|
||||
// Empty images are recognized as dummies by the zero sequence number
|
||||
return 0;
|
||||
}
|
||||
|
||||
capture_e
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) {
|
||||
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
sleep_overshoot_logger.reset();
|
||||
@ -1458,8 +1416,7 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds /* timeout */, bool cursor) {
|
||||
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds /* timeout */, bool cursor) {
|
||||
file_t fb_fd[4];
|
||||
|
||||
if (!pull_free_image_cb(img_out)) {
|
||||
@ -1491,8 +1448,7 @@ namespace platf {
|
||||
img->pixel_pitch = 4;
|
||||
img->row_pitch = img->pixel_pitch * img->width;
|
||||
img->data = img->buffer.data();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
img->data = nullptr;
|
||||
}
|
||||
|
||||
@ -1502,8 +1458,7 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
int
|
||||
init(const std::string &display_name, const ::video::config_t &config) {
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
if (display_t::init(display_name, config)) {
|
||||
return -1;
|
||||
}
|
||||
@ -1530,8 +1485,7 @@ namespace platf {
|
||||
|
||||
} // namespace kms
|
||||
|
||||
std::shared_ptr<display_t>
|
||||
kms_display(mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
std::shared_ptr<display_t> kms_display(mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
if (hwdevice_type == mem_type_e::vaapi || hwdevice_type == mem_type_e::cuda) {
|
||||
auto disp = std::make_shared<kms::display_vram_t>(hwdevice_type);
|
||||
|
||||
@ -1561,8 +1515,7 @@ namespace platf {
|
||||
*
|
||||
* This is an ugly hack :(
|
||||
*/
|
||||
void
|
||||
correlate_to_wayland(std::vector<kms::card_descriptor_t> &cds) {
|
||||
void correlate_to_wayland(std::vector<kms::card_descriptor_t> &cds) {
|
||||
auto monitors = wl::monitors();
|
||||
|
||||
BOOST_LOG(info) << "-------- Start of KMS monitor list --------"sv;
|
||||
@ -1578,8 +1531,7 @@ namespace platf {
|
||||
std::uint32_t index;
|
||||
if (index_begin == std::string_view::npos) {
|
||||
index = 1;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
index = std::max<int64_t>(1, util::from_view(name.substr(index_begin + 1)));
|
||||
}
|
||||
|
||||
@ -1594,7 +1546,8 @@ namespace platf {
|
||||
// A sanity check, it's guesswork after all.
|
||||
if (
|
||||
monitor_descriptor.viewport.width != monitor->viewport.width ||
|
||||
monitor_descriptor.viewport.height != monitor->viewport.height) {
|
||||
monitor_descriptor.viewport.height != monitor->viewport.height
|
||||
) {
|
||||
BOOST_LOG(warning)
|
||||
<< "Mismatch on expected Resolution compared to actual resolution: "sv
|
||||
<< monitor_descriptor.viewport.width << 'x' << monitor_descriptor.viewport.height
|
||||
@ -1616,8 +1569,7 @@ namespace platf {
|
||||
}
|
||||
|
||||
// A list of names of displays accepted as display_name
|
||||
std::vector<std::string>
|
||||
kms_display_names(mem_type_e hwdevice_type) {
|
||||
std::vector<std::string> kms_display_names(mem_type_e hwdevice_type) {
|
||||
int count = 0;
|
||||
|
||||
if (!fs::exists("/dev/dri")) {
|
||||
@ -1635,12 +1587,12 @@ namespace platf {
|
||||
std::vector<kms::card_descriptor_t> cds;
|
||||
std::vector<std::string> display_names;
|
||||
|
||||
fs::path card_dir { "/dev/dri"sv };
|
||||
for (auto &entry : fs::directory_iterator { card_dir }) {
|
||||
fs::path card_dir {"/dev/dri"sv};
|
||||
for (auto &entry : fs::directory_iterator {card_dir}) {
|
||||
auto file = entry.path().filename();
|
||||
|
||||
auto filestring = file.generic_string();
|
||||
if (std::string_view { filestring }.substr(0, 4) != "card"sv) {
|
||||
if (std::string_view {filestring}.substr(0, 4) != "card"sv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1655,8 +1607,7 @@ namespace platf {
|
||||
BOOST_LOG(debug) << file << " is not a CUDA device"sv;
|
||||
if (config::video.encoder == "nvenc") {
|
||||
BOOST_LOG(warning) << "Using NVENC with your display connected to a different GPU may not work properly!"sv;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -12,16 +12,18 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
// lib includes
|
||||
// platform includes
|
||||
#include <arpa/inet.h>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/host_name.hpp>
|
||||
#include <boost/process/v1.hpp>
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netinet/udp.h>
|
||||
#include <pwd.h>
|
||||
|
||||
// lib includes
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/host_name.hpp>
|
||||
#include <boost/process/v1.hpp>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// local includes
|
||||
@ -46,8 +48,7 @@ namespace bp = boost::process;
|
||||
window_system_e window_system;
|
||||
|
||||
namespace dyn {
|
||||
void *
|
||||
handle(const std::vector<const char *> &libs) {
|
||||
void *handle(const std::vector<const char *> &libs) {
|
||||
void *handle;
|
||||
|
||||
for (auto lib : libs) {
|
||||
@ -70,8 +71,7 @@ namespace dyn {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int
|
||||
load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
|
||||
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
|
||||
int err = 0;
|
||||
for (auto &func : funcs) {
|
||||
TUPLE_2D_REF(fn, name, func);
|
||||
@ -88,16 +88,16 @@ namespace dyn {
|
||||
return err;
|
||||
}
|
||||
} // namespace dyn
|
||||
|
||||
namespace platf {
|
||||
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
|
||||
|
||||
ifaddr_t
|
||||
get_ifaddrs() {
|
||||
ifaddrs *p { nullptr };
|
||||
ifaddr_t get_ifaddrs() {
|
||||
ifaddrs *p {nullptr};
|
||||
|
||||
getifaddrs(&p);
|
||||
|
||||
return ifaddr_t { p };
|
||||
return ifaddr_t {p};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,8 +105,7 @@ namespace platf {
|
||||
* @details This is used for the log directory, so it cannot invoke Boost logging!
|
||||
* @return The path of the appdata directory that should be used.
|
||||
*/
|
||||
fs::path
|
||||
appdata() {
|
||||
fs::path appdata() {
|
||||
static std::once_flag migration_flag;
|
||||
static fs::path config_path;
|
||||
|
||||
@ -172,8 +171,7 @@ namespace platf {
|
||||
std::cerr << "Migration failed: " << ec.message() << std::endl;
|
||||
config_path = old_config_path;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// We cannot use Boost logging because it hasn't been initialized yet!
|
||||
std::cerr << "Config exists in both "sv << old_config_path << " and "sv << config_path << ". Using "sv << config_path << " for config" << std::endl;
|
||||
std::cerr << "It is recommended to remove "sv << old_config_path << std::endl;
|
||||
@ -185,45 +183,36 @@ namespace platf {
|
||||
return config_path;
|
||||
}
|
||||
|
||||
std::string
|
||||
from_sockaddr(const sockaddr *const ip_addr) {
|
||||
std::string from_sockaddr(const sockaddr *const ip_addr) {
|
||||
char data[INET6_ADDRSTRLEN] = {};
|
||||
|
||||
auto family = ip_addr->sa_family;
|
||||
if (family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data,
|
||||
INET6_ADDRSTRLEN);
|
||||
}
|
||||
else if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data,
|
||||
INET_ADDRSTRLEN);
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
|
||||
} else if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
|
||||
}
|
||||
|
||||
return std::string { data };
|
||||
return std::string {data};
|
||||
}
|
||||
|
||||
std::pair<std::uint16_t, std::string>
|
||||
from_sockaddr_ex(const sockaddr *const ip_addr) {
|
||||
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
|
||||
char data[INET6_ADDRSTRLEN] = {};
|
||||
|
||||
auto family = ip_addr->sa_family;
|
||||
std::uint16_t port = 0;
|
||||
if (family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data,
|
||||
INET6_ADDRSTRLEN);
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
|
||||
port = ((sockaddr_in6 *) ip_addr)->sin6_port;
|
||||
}
|
||||
else if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data,
|
||||
INET_ADDRSTRLEN);
|
||||
} else if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
|
||||
port = ((sockaddr_in *) ip_addr)->sin_port;
|
||||
}
|
||||
|
||||
return { port, std::string { data } };
|
||||
return {port, std::string {data}};
|
||||
}
|
||||
|
||||
std::string
|
||||
get_mac_address(const std::string_view &address) {
|
||||
std::string get_mac_address(const std::string_view &address) {
|
||||
auto ifaddrs = get_ifaddrs();
|
||||
for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
|
||||
if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) {
|
||||
@ -240,8 +229,7 @@ namespace platf {
|
||||
return "00:00:00:00:00:00"s;
|
||||
}
|
||||
|
||||
bp::child
|
||||
run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
|
||||
bp::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
|
||||
// clang-format off
|
||||
if (!group) {
|
||||
if (!file) {
|
||||
@ -266,8 +254,7 @@ namespace platf {
|
||||
* @brief Open a url in the default web browser.
|
||||
* @param url The url to open.
|
||||
*/
|
||||
void
|
||||
open_url(const std::string &url) {
|
||||
void open_url(const std::string &url) {
|
||||
// set working dir to user home directory
|
||||
auto working_dir = boost::filesystem::path(std::getenv("HOME"));
|
||||
std::string cmd = R"(xdg-open ")" + url + R"(")";
|
||||
@ -277,30 +264,25 @@ namespace platf {
|
||||
auto child = run_command(false, false, cmd, working_dir, _env, nullptr, ec, nullptr);
|
||||
if (ec) {
|
||||
BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(info) << "Opened url ["sv << url << "]"sv;
|
||||
child.detach();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
adjust_thread_priority(thread_priority_e priority) {
|
||||
void adjust_thread_priority(thread_priority_e priority) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
void
|
||||
streaming_will_start() {
|
||||
void streaming_will_start() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
void
|
||||
streaming_will_stop() {
|
||||
void streaming_will_stop() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
void
|
||||
restart_on_exit() {
|
||||
void restart_on_exit() {
|
||||
char executable[PATH_MAX];
|
||||
ssize_t len = readlink("/proc/self/exe", executable, PATH_MAX - 1);
|
||||
if (len == -1) {
|
||||
@ -322,42 +304,35 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
restart() {
|
||||
void restart() {
|
||||
// Gracefully clean up and restart ourselves instead of exiting
|
||||
atexit(restart_on_exit);
|
||||
lifetime::exit_sunshine(0, true);
|
||||
}
|
||||
|
||||
int
|
||||
set_env(const std::string &name, const std::string &value) {
|
||||
int set_env(const std::string &name, const std::string &value) {
|
||||
return setenv(name.c_str(), value.c_str(), 1);
|
||||
}
|
||||
|
||||
int
|
||||
unset_env(const std::string &name) {
|
||||
int unset_env(const std::string &name) {
|
||||
return unsetenv(name.c_str());
|
||||
}
|
||||
|
||||
bool
|
||||
request_process_group_exit(std::uintptr_t native_handle) {
|
||||
bool request_process_group_exit(std::uintptr_t native_handle) {
|
||||
if (kill(-((pid_t) native_handle), SIGTERM) == 0 || errno == ESRCH) {
|
||||
BOOST_LOG(debug) << "Successfully sent SIGTERM to process group: "sv << native_handle;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "Unable to send SIGTERM to process group ["sv << native_handle << "]: "sv << errno;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
process_group_running(std::uintptr_t native_handle) {
|
||||
bool process_group_running(std::uintptr_t native_handle) {
|
||||
return waitpid(-((pid_t) native_handle), nullptr, WNOHANG) >= 0;
|
||||
}
|
||||
|
||||
struct sockaddr_in
|
||||
to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
|
||||
struct sockaddr_in to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
|
||||
struct sockaddr_in saddr_v4 = {};
|
||||
|
||||
saddr_v4.sin_family = AF_INET;
|
||||
@ -369,8 +344,7 @@ namespace platf {
|
||||
return saddr_v4;
|
||||
}
|
||||
|
||||
struct sockaddr_in6
|
||||
to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
|
||||
struct sockaddr_in6 to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
|
||||
struct sockaddr_in6 saddr_v6 = {};
|
||||
|
||||
saddr_v6.sin6_family = AF_INET6;
|
||||
@ -383,8 +357,7 @@ namespace platf {
|
||||
return saddr_v6;
|
||||
}
|
||||
|
||||
bool
|
||||
send_batch(batched_send_info_t &send_info) {
|
||||
bool send_batch(batched_send_info_t &send_info) {
|
||||
auto sockfd = (int) send_info.native_socket;
|
||||
struct msghdr msg = {};
|
||||
|
||||
@ -396,8 +369,7 @@ namespace platf {
|
||||
|
||||
msg.msg_name = (struct sockaddr *) &taddr_v6;
|
||||
msg.msg_namelen = sizeof(taddr_v6);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
|
||||
|
||||
msg.msg_name = (struct sockaddr *) &taddr_v4;
|
||||
@ -405,10 +377,10 @@ namespace platf {
|
||||
}
|
||||
|
||||
union {
|
||||
char buf[CMSG_SPACE(sizeof(uint16_t)) +
|
||||
std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
|
||||
char buf[CMSG_SPACE(sizeof(uint16_t)) + std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
|
||||
struct cmsghdr alignment;
|
||||
} cmbuf = {}; // Must be zeroed for CMSG_NXTHDR()
|
||||
|
||||
socklen_t cmbuflen = 0;
|
||||
|
||||
msg.msg_control = cmbuf.buf;
|
||||
@ -430,8 +402,7 @@ namespace platf {
|
||||
pktinfo_cm->cmsg_type = IPV6_PKTINFO;
|
||||
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo));
|
||||
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
struct in_pktinfo pktInfo;
|
||||
|
||||
struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0);
|
||||
@ -469,8 +440,7 @@ namespace platf {
|
||||
iovs[iovlen].iov_len = send_info.payload_size;
|
||||
iovlen++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Translate buffer descriptors into iovs
|
||||
auto payload_offset = (send_info.block_offset + seg_index) * send_info.payload_size;
|
||||
auto payload_length = payload_offset + (segs_in_batch * send_info.payload_size);
|
||||
@ -496,8 +466,7 @@ namespace platf {
|
||||
cm->cmsg_type = UDP_SEGMENT;
|
||||
cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
|
||||
*((uint16_t *) CMSG_DATA(cm)) = msg_size;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
msg.msg_controllen = cmbuflen;
|
||||
}
|
||||
|
||||
@ -593,8 +562,7 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
send(send_info_t &send_info) {
|
||||
bool send(send_info_t &send_info) {
|
||||
auto sockfd = (int) send_info.native_socket;
|
||||
struct msghdr msg = {};
|
||||
|
||||
@ -606,8 +574,7 @@ namespace platf {
|
||||
|
||||
msg.msg_name = (struct sockaddr *) &taddr_v6;
|
||||
msg.msg_namelen = sizeof(taddr_v6);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
|
||||
|
||||
msg.msg_name = (struct sockaddr *) &taddr_v4;
|
||||
@ -618,6 +585,7 @@ namespace platf {
|
||||
char buf[std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
|
||||
struct cmsghdr alignment;
|
||||
} cmbuf;
|
||||
|
||||
socklen_t cmbuflen = 0;
|
||||
|
||||
msg.msg_control = cmbuf.buf;
|
||||
@ -637,8 +605,7 @@ namespace platf {
|
||||
pktinfo_cm->cmsg_type = IPV6_PKTINFO;
|
||||
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo));
|
||||
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
struct in_pktinfo pktInfo;
|
||||
|
||||
struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0);
|
||||
@ -703,7 +670,8 @@ namespace platf {
|
||||
class qos_t: public deinit_t {
|
||||
public:
|
||||
qos_t(int sockfd, std::vector<std::tuple<int, int, int>> options):
|
||||
sockfd(sockfd), options(options) {
|
||||
sockfd(sockfd),
|
||||
options(options) {
|
||||
qos_ref_count++;
|
||||
}
|
||||
|
||||
@ -731,8 +699,7 @@ 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) {
|
||||
int sockfd = (int) native_socket;
|
||||
std::vector<std::tuple<int, int, int>> reset_options;
|
||||
|
||||
@ -745,8 +712,7 @@ namespace platf {
|
||||
if (address.is_v6() && !address.to_v6().is_v4_mapped()) {
|
||||
level = SOL_IPV6;
|
||||
option = IPV6_TCLASS;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
level = SOL_IP;
|
||||
option = IP_TOS;
|
||||
}
|
||||
@ -773,8 +739,7 @@ namespace platf {
|
||||
if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) == 0) {
|
||||
// Reset TOS to -1 when QoS is disabled
|
||||
reset_options.emplace_back(std::make_tuple(level, option, -1));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(error) << "Failed to set TOS/TCLASS: "sv << errno;
|
||||
}
|
||||
}
|
||||
@ -790,20 +755,17 @@ namespace platf {
|
||||
if (setsockopt(sockfd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)) == 0) {
|
||||
// Reset SO_PRIORITY to 0 when QoS is disabled
|
||||
reset_options.emplace_back(std::make_tuple(SOL_SOCKET, SO_PRIORITY, 0));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(error) << "Failed to set SO_PRIORITY: "sv << errno;
|
||||
}
|
||||
|
||||
return std::make_unique<qos_t>(sockfd, reset_options);
|
||||
}
|
||||
|
||||
std::string
|
||||
get_host_name() {
|
||||
std::string get_host_name() {
|
||||
try {
|
||||
return boost::asio::ip::host_name();
|
||||
}
|
||||
catch (boost::system::system_error &err) {
|
||||
} catch (boost::system::system_error &err) {
|
||||
BOOST_LOG(error) << "Failed to get hostname: "sv << err.what();
|
||||
return "Sunshine"s;
|
||||
}
|
||||
@ -830,67 +792,62 @@ namespace platf {
|
||||
static std::bitset<source::MAX_FLAGS> sources;
|
||||
|
||||
#ifdef SUNSHINE_BUILD_CUDA
|
||||
std::vector<std::string>
|
||||
nvfbc_display_names();
|
||||
std::shared_ptr<display_t>
|
||||
nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
std::vector<std::string> nvfbc_display_names();
|
||||
std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
|
||||
bool
|
||||
verify_nvfbc() {
|
||||
bool verify_nvfbc() {
|
||||
return !nvfbc_display_names().empty();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SUNSHINE_BUILD_WAYLAND
|
||||
std::vector<std::string>
|
||||
wl_display_names();
|
||||
std::shared_ptr<display_t>
|
||||
wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
std::vector<std::string> wl_display_names();
|
||||
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
|
||||
bool
|
||||
verify_wl() {
|
||||
bool verify_wl() {
|
||||
return window_system == window_system_e::WAYLAND && !wl_display_names().empty();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SUNSHINE_BUILD_DRM
|
||||
std::vector<std::string>
|
||||
kms_display_names(mem_type_e hwdevice_type);
|
||||
std::shared_ptr<display_t>
|
||||
kms_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
std::vector<std::string> kms_display_names(mem_type_e hwdevice_type);
|
||||
std::shared_ptr<display_t> kms_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
|
||||
bool
|
||||
verify_kms() {
|
||||
bool verify_kms() {
|
||||
return !kms_display_names(mem_type_e::unknown).empty();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
std::vector<std::string>
|
||||
x11_display_names();
|
||||
std::shared_ptr<display_t>
|
||||
x11_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
std::vector<std::string> x11_display_names();
|
||||
std::shared_ptr<display_t> x11_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
|
||||
bool
|
||||
verify_x11() {
|
||||
bool verify_x11() {
|
||||
return window_system == window_system_e::X11 && !x11_display_names().empty();
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<std::string>
|
||||
display_names(mem_type_e hwdevice_type) {
|
||||
std::vector<std::string> display_names(mem_type_e hwdevice_type) {
|
||||
#ifdef SUNSHINE_BUILD_CUDA
|
||||
// display using NvFBC only supports mem_type_e::cuda
|
||||
if (sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) return nvfbc_display_names();
|
||||
if (sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) {
|
||||
return nvfbc_display_names();
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_WAYLAND
|
||||
if (sources[source::WAYLAND]) return wl_display_names();
|
||||
if (sources[source::WAYLAND]) {
|
||||
return wl_display_names();
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_DRM
|
||||
if (sources[source::KMS]) return kms_display_names(hwdevice_type);
|
||||
if (sources[source::KMS]) {
|
||||
return kms_display_names(hwdevice_type);
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
if (sources[source::X11]) return x11_display_names();
|
||||
if (sources[source::X11]) {
|
||||
return x11_display_names();
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
@ -899,14 +856,12 @@ namespace platf {
|
||||
* @brief Returns if GPUs/drivers have changed since the last call to this function.
|
||||
* @return `true` if a change has occurred or if it is unknown whether a change occurred.
|
||||
*/
|
||||
bool
|
||||
needs_encoder_reenumeration() {
|
||||
bool needs_encoder_reenumeration() {
|
||||
// We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on Linux.
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<display_t>
|
||||
display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
#ifdef SUNSHINE_BUILD_CUDA
|
||||
if (sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) {
|
||||
BOOST_LOG(info) << "Screencasting with NvFBC"sv;
|
||||
@ -935,8 +890,7 @@ namespace platf {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<deinit_t>
|
||||
init() {
|
||||
std::unique_ptr<deinit_t> init() {
|
||||
// enable low latency mode for AMD
|
||||
// https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/30039
|
||||
set_env("AMD_DEBUG", "lowlatencyenc");
|
||||
@ -1005,8 +959,7 @@ namespace platf {
|
||||
|
||||
class linux_high_precision_timer: public high_precision_timer {
|
||||
public:
|
||||
void
|
||||
sleep_for(const std::chrono::nanoseconds &duration) override {
|
||||
void sleep_for(const std::chrono::nanoseconds &duration) override {
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
|
||||
@ -1015,8 +968,7 @@ namespace platf {
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<high_precision_timer>
|
||||
create_high_precision_timer() {
|
||||
std::unique_ptr<high_precision_timer> create_high_precision_timer() {
|
||||
return std::make_unique<linux_high_precision_timer>();
|
||||
}
|
||||
} // namespace platf
|
||||
|
@ -4,9 +4,11 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
// local includes
|
||||
#include "src/utility.h"
|
||||
|
||||
KITTY_USING_MOVE_T(file_t, int, -1, {
|
||||
@ -26,9 +28,7 @@ extern window_system_e window_system;
|
||||
namespace dyn {
|
||||
typedef void (*apiproc)(void);
|
||||
|
||||
int
|
||||
load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
|
||||
void *
|
||||
handle(const std::vector<const char *> &libs);
|
||||
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
|
||||
void *handle(const std::vector<const char *> &libs);
|
||||
|
||||
} // namespace dyn
|
||||
|
@ -3,8 +3,10 @@
|
||||
* @brief Definitions for publishing services on Linux.
|
||||
* @note Adapted from https://www.avahi.org/doxygen/html/client-publish-service_8c-example.html
|
||||
*/
|
||||
// standard includes
|
||||
#include <thread>
|
||||
|
||||
// local includes
|
||||
#include "misc.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/network.h"
|
||||
@ -84,6 +86,7 @@ namespace avahi {
|
||||
};
|
||||
|
||||
constexpr auto IF_UNSPEC = -1;
|
||||
|
||||
enum proto {
|
||||
PROTO_INET = 0, ///< IPv4
|
||||
PROTO_INET6 = 1, ///< IPv6
|
||||
@ -164,7 +167,8 @@ namespace avahi {
|
||||
const char *domain,
|
||||
const char *host,
|
||||
uint16_t port,
|
||||
...);
|
||||
...
|
||||
);
|
||||
|
||||
typedef int (*entry_group_is_empty_fn)(EntryGroup *);
|
||||
typedef int (*entry_group_reset_fn)(EntryGroup *);
|
||||
@ -199,30 +203,31 @@ namespace avahi {
|
||||
simple_poll_new_fn simple_poll_new;
|
||||
simple_poll_free_fn simple_poll_free;
|
||||
|
||||
int
|
||||
init_common() {
|
||||
static void *handle { nullptr };
|
||||
int init_common() {
|
||||
static void *handle {nullptr};
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
if (funcs_loaded) return 0;
|
||||
if (funcs_loaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!handle) {
|
||||
handle = dyn::handle({ "libavahi-common.so.3", "libavahi-common.so" });
|
||||
handle = dyn::handle({"libavahi-common.so.3", "libavahi-common.so"});
|
||||
if (!handle) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
|
||||
{ (dyn::apiproc *) &alternative_service_name, "avahi_alternative_service_name" },
|
||||
{ (dyn::apiproc *) &free, "avahi_free" },
|
||||
{ (dyn::apiproc *) &strdup, "avahi_strdup" },
|
||||
{ (dyn::apiproc *) &strerror, "avahi_strerror" },
|
||||
{ (dyn::apiproc *) &simple_poll_get, "avahi_simple_poll_get" },
|
||||
{ (dyn::apiproc *) &simple_poll_loop, "avahi_simple_poll_loop" },
|
||||
{ (dyn::apiproc *) &simple_poll_quit, "avahi_simple_poll_quit" },
|
||||
{ (dyn::apiproc *) &simple_poll_new, "avahi_simple_poll_new" },
|
||||
{ (dyn::apiproc *) &simple_poll_free, "avahi_simple_poll_free" },
|
||||
{(dyn::apiproc *) &alternative_service_name, "avahi_alternative_service_name"},
|
||||
{(dyn::apiproc *) &free, "avahi_free"},
|
||||
{(dyn::apiproc *) &strdup, "avahi_strdup"},
|
||||
{(dyn::apiproc *) &strerror, "avahi_strerror"},
|
||||
{(dyn::apiproc *) &simple_poll_get, "avahi_simple_poll_get"},
|
||||
{(dyn::apiproc *) &simple_poll_loop, "avahi_simple_poll_loop"},
|
||||
{(dyn::apiproc *) &simple_poll_quit, "avahi_simple_poll_quit"},
|
||||
{(dyn::apiproc *) &simple_poll_new, "avahi_simple_poll_new"},
|
||||
{(dyn::apiproc *) &simple_poll_free, "avahi_simple_poll_free"},
|
||||
};
|
||||
|
||||
if (dyn::load(handle, funcs)) {
|
||||
@ -233,34 +238,35 @@ namespace avahi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
init_client() {
|
||||
int init_client() {
|
||||
if (init_common()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void *handle { nullptr };
|
||||
static void *handle {nullptr};
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
if (funcs_loaded) return 0;
|
||||
if (funcs_loaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!handle) {
|
||||
handle = dyn::handle({ "libavahi-client.so.3", "libavahi-client.so" });
|
||||
handle = dyn::handle({"libavahi-client.so.3", "libavahi-client.so"});
|
||||
if (!handle) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
|
||||
{ (dyn::apiproc *) &client_new, "avahi_client_new" },
|
||||
{ (dyn::apiproc *) &client_free, "avahi_client_free" },
|
||||
{ (dyn::apiproc *) &entry_group_get_client, "avahi_entry_group_get_client" },
|
||||
{ (dyn::apiproc *) &entry_group_new, "avahi_entry_group_new" },
|
||||
{ (dyn::apiproc *) &entry_group_add_service, "avahi_entry_group_add_service" },
|
||||
{ (dyn::apiproc *) &entry_group_is_empty, "avahi_entry_group_is_empty" },
|
||||
{ (dyn::apiproc *) &entry_group_reset, "avahi_entry_group_reset" },
|
||||
{ (dyn::apiproc *) &entry_group_commit, "avahi_entry_group_commit" },
|
||||
{ (dyn::apiproc *) &client_errno, "avahi_client_errno" },
|
||||
{(dyn::apiproc *) &client_new, "avahi_client_new"},
|
||||
{(dyn::apiproc *) &client_free, "avahi_client_free"},
|
||||
{(dyn::apiproc *) &entry_group_get_client, "avahi_entry_group_get_client"},
|
||||
{(dyn::apiproc *) &entry_group_new, "avahi_entry_group_new"},
|
||||
{(dyn::apiproc *) &entry_group_add_service, "avahi_entry_group_add_service"},
|
||||
{(dyn::apiproc *) &entry_group_is_empty, "avahi_entry_group_is_empty"},
|
||||
{(dyn::apiproc *) &entry_group_reset, "avahi_entry_group_reset"},
|
||||
{(dyn::apiproc *) &entry_group_commit, "avahi_entry_group_commit"},
|
||||
{(dyn::apiproc *) &client_errno, "avahi_client_errno"},
|
||||
};
|
||||
|
||||
if (dyn::load(handle, funcs)) {
|
||||
@ -274,13 +280,12 @@ namespace avahi {
|
||||
|
||||
namespace platf::publish {
|
||||
|
||||
template <class T>
|
||||
void
|
||||
free(T *p) {
|
||||
template<class T>
|
||||
void free(T *p) {
|
||||
avahi::free(p);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template<class T>
|
||||
using ptr_t = util::safe_ptr<T, free<T>>;
|
||||
using client_t = util::dyn_safe_ptr<avahi::Client, &avahi::client_free>;
|
||||
using poll_t = util::dyn_safe_ptr<avahi::SimplePoll, &avahi::simple_poll_free>;
|
||||
@ -292,11 +297,9 @@ namespace platf::publish {
|
||||
|
||||
ptr_t<char> name;
|
||||
|
||||
void
|
||||
create_services(avahi::Client *c);
|
||||
void create_services(avahi::Client *c);
|
||||
|
||||
void
|
||||
entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
|
||||
void entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
|
||||
group = g;
|
||||
|
||||
switch (state) {
|
||||
@ -319,8 +322,7 @@ namespace platf::publish {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
create_services(avahi::Client *c) {
|
||||
void create_services(avahi::Client *c) {
|
||||
int ret;
|
||||
|
||||
auto fg = util::fail_guard([]() {
|
||||
@ -339,13 +341,16 @@ namespace platf::publish {
|
||||
|
||||
ret = avahi::entry_group_add_service(
|
||||
group,
|
||||
avahi::IF_UNSPEC, avahi::PROTO_UNSPEC,
|
||||
avahi::IF_UNSPEC,
|
||||
avahi::PROTO_UNSPEC,
|
||||
avahi::PublishFlags(0),
|
||||
name.get(),
|
||||
SERVICE_TYPE,
|
||||
nullptr, nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
net::map_port(nvhttp::PORT_HTTP),
|
||||
nullptr);
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (ret < 0) {
|
||||
if (ret == avahi::ERR_COLLISION) {
|
||||
@ -375,8 +380,7 @@ namespace platf::publish {
|
||||
fg.disable();
|
||||
}
|
||||
|
||||
void
|
||||
client_callback(avahi::Client *c, avahi::ClientState state, void *) {
|
||||
void client_callback(avahi::Client *c, avahi::ClientState state, void *) {
|
||||
switch (state) {
|
||||
case avahi::CLIENT_S_RUNNING:
|
||||
create_services(c);
|
||||
@ -387,8 +391,9 @@ namespace platf::publish {
|
||||
break;
|
||||
case avahi::CLIENT_S_COLLISION:
|
||||
case avahi::CLIENT_S_REGISTERING:
|
||||
if (group)
|
||||
if (group) {
|
||||
avahi::entry_group_reset(group);
|
||||
}
|
||||
break;
|
||||
case avahi::CLIENT_CONNECTING:;
|
||||
}
|
||||
@ -399,7 +404,8 @@ namespace platf::publish {
|
||||
std::thread poll_thread;
|
||||
|
||||
deinit_t(std::thread poll_thread):
|
||||
poll_thread { std::move(poll_thread) } {}
|
||||
poll_thread {std::move(poll_thread)} {
|
||||
}
|
||||
|
||||
~deinit_t() override {
|
||||
if (avahi::simple_poll_quit && poll) {
|
||||
@ -412,8 +418,7 @@ namespace platf::publish {
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<::platf::deinit_t>
|
||||
start() {
|
||||
[[nodiscard]] std::unique_ptr<::platf::deinit_t> start() {
|
||||
if (avahi::init_client()) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -430,13 +435,14 @@ namespace platf::publish {
|
||||
name.reset(avahi::strdup(instance_name.c_str()));
|
||||
|
||||
client.reset(
|
||||
avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error));
|
||||
avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error)
|
||||
);
|
||||
|
||||
if (!client) {
|
||||
BOOST_LOG(error) << "Failed to create client: "sv << avahi::strerror(avhi_error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<deinit_t>(std::thread { avahi::simple_poll_loop, poll.get() });
|
||||
return std::make_unique<deinit_t>(std::thread {avahi::simple_poll_loop, poll.get()});
|
||||
}
|
||||
} // namespace platf::publish
|
||||
|
@ -2,28 +2,30 @@
|
||||
* @file src/platform/linux/vaapi.cpp
|
||||
* @brief Definitions for VA-API hardware accelerated capture.
|
||||
*/
|
||||
// standard includes
|
||||
#include <fcntl.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <va/va.h>
|
||||
#include <va/va_drm.h>
|
||||
#if !VA_CHECK_VERSION(1, 9, 0)
|
||||
// vaSyncBuffer stub allows Sunshine built against libva <2.9.0 to link against ffmpeg on libva 2.9.0 or later
|
||||
VAStatus
|
||||
vaSyncBuffer(
|
||||
VADisplay dpy,
|
||||
VABufferID buf_id,
|
||||
uint64_t timeout_ns) {
|
||||
return VA_STATUS_ERROR_UNIMPLEMENTED;
|
||||
}
|
||||
// vaSyncBuffer stub allows Sunshine built against libva <2.9.0 to link against ffmpeg on libva 2.9.0 or later
|
||||
VAStatus
|
||||
vaSyncBuffer(
|
||||
VADisplay dpy,
|
||||
VABufferID buf_id,
|
||||
uint64_t timeout_ns
|
||||
) {
|
||||
return VA_STATUS_ERROR_UNIMPLEMENTED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// local includes
|
||||
#include "graphics.h"
|
||||
#include "misc.h"
|
||||
#include "src/config.h"
|
||||
@ -69,6 +71,7 @@ namespace va {
|
||||
|
||||
// Number of layers making up the surface.
|
||||
uint32_t num_layers;
|
||||
|
||||
struct {
|
||||
// DRM format fourcc of this layer (DRM_FOURCC_*).
|
||||
uint32_t drm_format;
|
||||
@ -89,13 +92,11 @@ namespace va {
|
||||
|
||||
using display_t = util::safe_ptr_v2<void, VAStatus, vaTerminate>;
|
||||
|
||||
int
|
||||
vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *encode_device, AVBufferRef **hw_device_buf);
|
||||
int vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *encode_device, AVBufferRef **hw_device_buf);
|
||||
|
||||
class va_t: public platf::avcodec_encode_device_t {
|
||||
public:
|
||||
int
|
||||
init(int in_width, int in_height, file_t &&render_device) {
|
||||
int init(int in_width, int in_height, file_t &&render_device) {
|
||||
file = std::move(render_device);
|
||||
|
||||
if (!gbm::create_device) {
|
||||
@ -135,8 +136,7 @@ namespace va {
|
||||
* @param profile The profile to match.
|
||||
* @return A valid encoding entrypoint or 0 on failure.
|
||||
*/
|
||||
VAEntrypoint
|
||||
select_va_entrypoint(VAProfile profile) {
|
||||
VAEntrypoint select_va_entrypoint(VAProfile profile) {
|
||||
std::vector<VAEntrypoint> entrypoints(vaMaxNumEntrypoints(va_display));
|
||||
int num_eps;
|
||||
auto status = vaQueryConfigEntrypoints(va_display, profile, entrypoints.data(), &num_eps);
|
||||
@ -166,8 +166,7 @@ namespace va {
|
||||
* @param profile The profile to match.
|
||||
* @return Boolean value indicating if the profile is supported.
|
||||
*/
|
||||
bool
|
||||
is_va_profile_supported(VAProfile profile) {
|
||||
bool is_va_profile_supported(VAProfile profile) {
|
||||
std::vector<VAProfile> profiles(vaMaxNumProfiles(va_display));
|
||||
int num_profs;
|
||||
auto status = vaQueryConfigProfiles(va_display, profiles.data(), &num_profs);
|
||||
@ -185,13 +184,11 @@ namespace va {
|
||||
* @param ctx The FFmpeg codec context.
|
||||
* @return The matching VA profile or `VAProfileNone` on failure.
|
||||
*/
|
||||
VAProfile
|
||||
get_va_profile(AVCodecContext *ctx) {
|
||||
VAProfile get_va_profile(AVCodecContext *ctx) {
|
||||
if (ctx->codec_id == AV_CODEC_ID_H264) {
|
||||
// There's no VAAPI profile for H.264 4:4:4
|
||||
return VAProfileH264High;
|
||||
}
|
||||
else if (ctx->codec_id == AV_CODEC_ID_HEVC) {
|
||||
} else if (ctx->codec_id == AV_CODEC_ID_HEVC) {
|
||||
switch (ctx->profile) {
|
||||
case FF_PROFILE_HEVC_REXT:
|
||||
switch (av_pix_fmt_desc_get(ctx->sw_pix_fmt)->comp[0].depth) {
|
||||
@ -206,8 +203,7 @@ namespace va {
|
||||
case FF_PROFILE_HEVC_MAIN:
|
||||
return VAProfileHEVCMain;
|
||||
}
|
||||
}
|
||||
else if (ctx->codec_id == AV_CODEC_ID_AV1) {
|
||||
} else if (ctx->codec_id == AV_CODEC_ID_AV1) {
|
||||
switch (ctx->profile) {
|
||||
case FF_PROFILE_AV1_HIGH:
|
||||
return VAProfileAV1Profile1;
|
||||
@ -220,8 +216,7 @@ namespace va {
|
||||
return VAProfileNone;
|
||||
}
|
||||
|
||||
void
|
||||
init_codec_options(AVCodecContext *ctx, AVDictionary **options) override {
|
||||
void init_codec_options(AVCodecContext *ctx, AVDictionary **options) override {
|
||||
auto va_profile = get_va_profile(ctx);
|
||||
if (va_profile == VAProfileNone || !is_va_profile_supported(va_profile)) {
|
||||
// Don't bother doing anything if the profile isn't supported
|
||||
@ -239,19 +234,18 @@ namespace va {
|
||||
if (va_entrypoint == VAEntrypointEncSliceLP) {
|
||||
BOOST_LOG(info) << "Using LP encoding mode"sv;
|
||||
av_dict_set_int(options, "low_power", 1, 0);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(info) << "Using normal encoding mode"sv;
|
||||
}
|
||||
|
||||
VAConfigAttrib rc_attr = { VAConfigAttribRateControl };
|
||||
VAConfigAttrib rc_attr = {VAConfigAttribRateControl};
|
||||
auto status = vaGetConfigAttributes(va_display, va_profile, va_entrypoint, &rc_attr, 1);
|
||||
if (status != VA_STATUS_SUCCESS) {
|
||||
// Stick to the default rate control (CQP)
|
||||
rc_attr.value = 0;
|
||||
}
|
||||
|
||||
VAConfigAttrib slice_attr = { VAConfigAttribEncMaxSlices };
|
||||
VAConfigAttrib slice_attr = {VAConfigAttribEncMaxSlices};
|
||||
status = vaGetConfigAttributes(va_display, va_profile, va_entrypoint, &slice_attr, 1);
|
||||
if (status != VA_STATUS_SUCCESS) {
|
||||
// Assume only a single slice is supported
|
||||
@ -281,27 +275,22 @@ namespace va {
|
||||
if (rc_attr.value & VA_RC_VBR) {
|
||||
BOOST_LOG(info) << "Using VBR with single frame VBV size"sv;
|
||||
av_dict_set(options, "rc_mode", "VBR", 0);
|
||||
}
|
||||
else if (rc_attr.value & VA_RC_CBR) {
|
||||
} else if (rc_attr.value & VA_RC_CBR) {
|
||||
BOOST_LOG(info) << "Using CBR with single frame VBV size"sv;
|
||||
av_dict_set(options, "rc_mode", "CBR", 0);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "Using CQP with single frame VBV size"sv;
|
||||
av_dict_set_int(options, "qp", config::video.qp, 0);
|
||||
}
|
||||
}
|
||||
else if (!(rc_attr.value & (VA_RC_CBR | VA_RC_VBR))) {
|
||||
} else if (!(rc_attr.value & (VA_RC_CBR | VA_RC_VBR))) {
|
||||
BOOST_LOG(warning) << "Using CQP rate control"sv;
|
||||
av_dict_set_int(options, "qp", config::video.qp, 0);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(info) << "Using default rate control"sv;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
|
||||
this->hwframe.reset(frame);
|
||||
this->frame = frame;
|
||||
|
||||
@ -321,7 +310,8 @@ namespace va {
|
||||
surface,
|
||||
va::SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
|
||||
va::EXPORT_SURFACE_WRITE_ONLY | va::EXPORT_SURFACE_SEPARATE_LAYERS,
|
||||
&prime);
|
||||
&prime
|
||||
);
|
||||
if (status) {
|
||||
BOOST_LOG(error) << "Couldn't export va surface handle: ["sv << (int) surface << "]: "sv << vaErrorStr(status);
|
||||
|
||||
@ -377,8 +367,7 @@ namespace va {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
apply_colorspace() override {
|
||||
void apply_colorspace() override {
|
||||
sws.apply_colorspace(colorspace);
|
||||
}
|
||||
|
||||
@ -401,8 +390,7 @@ namespace va {
|
||||
|
||||
class va_ram_t: public va_t {
|
||||
public:
|
||||
int
|
||||
convert(platf::img_t &img) override {
|
||||
int convert(platf::img_t &img) override {
|
||||
sws.load_ram(img);
|
||||
|
||||
sws.convert(nv12->buf);
|
||||
@ -412,15 +400,13 @@ namespace va {
|
||||
|
||||
class va_vram_t: public va_t {
|
||||
public:
|
||||
int
|
||||
convert(platf::img_t &img) override {
|
||||
int convert(platf::img_t &img) override {
|
||||
auto &descriptor = (egl::img_descriptor_t &) img;
|
||||
|
||||
if (descriptor.sequence == 0) {
|
||||
// For dummy images, use a blank RGB texture instead of importing a DMA-BUF
|
||||
rgb = egl::create_blank(img);
|
||||
}
|
||||
else if (descriptor.sequence > sequence) {
|
||||
} else if (descriptor.sequence > sequence) {
|
||||
sequence = descriptor.sequence;
|
||||
|
||||
rgb = egl::rgb_t {};
|
||||
@ -440,8 +426,7 @@ namespace va {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
init(int in_width, int in_height, file_t &&render_device, int offset_x, int offset_y) {
|
||||
int init(int in_width, int in_height, file_t &&render_device, int offset_x, int offset_y) {
|
||||
if (va_t::init(in_width, in_height, std::move(render_device))) {
|
||||
return -1;
|
||||
}
|
||||
@ -471,6 +456,7 @@ namespace va {
|
||||
void *xdisplay;
|
||||
int fd;
|
||||
} drm;
|
||||
|
||||
int drm_fd;
|
||||
} VAAPIDevicePriv;
|
||||
|
||||
@ -494,13 +480,11 @@ namespace va {
|
||||
unsigned int driver_quirks;
|
||||
} AVVAAPIDeviceContext;
|
||||
|
||||
static void
|
||||
__log(void *level, const char *msg) {
|
||||
static void __log(void *level, const char *msg) {
|
||||
BOOST_LOG(*(boost::log::sources::severity_logger<int> *) level) << msg;
|
||||
}
|
||||
|
||||
static void
|
||||
vaapi_hwdevice_ctx_free(AVHWDeviceContext *ctx) {
|
||||
static void vaapi_hwdevice_ctx_free(AVHWDeviceContext *ctx) {
|
||||
auto hwctx = (AVVAAPIDeviceContext *) ctx->hwctx;
|
||||
auto priv = (VAAPIDevicePriv *) ctx->user_opaque;
|
||||
|
||||
@ -509,8 +493,7 @@ namespace va {
|
||||
av_freep(&priv);
|
||||
}
|
||||
|
||||
int
|
||||
vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *base, AVBufferRef **hw_device_buf) {
|
||||
int vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *base, AVBufferRef **hw_device_buf) {
|
||||
auto va = (va::va_t *) base;
|
||||
auto fd = dup(va->file.el);
|
||||
|
||||
@ -522,7 +505,7 @@ namespace va {
|
||||
av_free(priv);
|
||||
});
|
||||
|
||||
va::display_t display { vaGetDisplayDRM(fd) };
|
||||
va::display_t display {vaGetDisplayDRM(fd)};
|
||||
if (!display) {
|
||||
auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str();
|
||||
|
||||
@ -556,7 +539,7 @@ namespace va {
|
||||
|
||||
auto err = av_hwdevice_ctx_init(*hw_device_buf);
|
||||
if (err) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
|
||||
BOOST_LOG(error) << "Failed to create FFMpeg hardware device context: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return err;
|
||||
@ -565,8 +548,7 @@ namespace va {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
query(display_t::pointer display, VAProfile profile) {
|
||||
static bool query(display_t::pointer display, VAProfile profile) {
|
||||
std::vector<VAEntrypoint> entrypoints;
|
||||
entrypoints.resize(vaMaxNumEntrypoints(display));
|
||||
|
||||
@ -587,15 +569,14 @@ namespace va {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
validate(int fd) {
|
||||
va::display_t display { vaGetDisplayDRM(fd) };
|
||||
bool validate(int fd) {
|
||||
va::display_t display {vaGetDisplayDRM(fd)};
|
||||
if (!display) {
|
||||
char string[1024];
|
||||
|
||||
auto bytes = readlink(("/proc/self/fd/" + std::to_string(fd)).c_str(), string, sizeof(string));
|
||||
|
||||
std::string_view render_device { string, (std::size_t) bytes };
|
||||
std::string_view render_device {string, (std::size_t) bytes};
|
||||
|
||||
BOOST_LOG(error) << "Couldn't open a va display from DRM with device: "sv << render_device;
|
||||
return false;
|
||||
@ -623,8 +604,7 @@ namespace va {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram) {
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram) {
|
||||
if (vram) {
|
||||
auto egl = std::make_unique<va::va_vram_t>();
|
||||
if (egl->init(width, height, std::move(card), offset_x, offset_y)) {
|
||||
@ -644,8 +624,7 @@ namespace va {
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram) {
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram) {
|
||||
auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str();
|
||||
|
||||
file_t file = open(render_device, O_RDWR);
|
||||
@ -659,8 +638,7 @@ namespace va {
|
||||
return make_avcodec_encode_device(width, height, std::move(file), offset_x, offset_y, vram);
|
||||
}
|
||||
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(int width, int height, bool vram) {
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram) {
|
||||
return make_avcodec_encode_device(width, height, 0, 0, vram);
|
||||
}
|
||||
} // namespace va
|
||||
|
@ -4,12 +4,14 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// local includes
|
||||
#include "misc.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
namespace egl {
|
||||
struct surface_descriptor_t;
|
||||
}
|
||||
|
||||
namespace va {
|
||||
/**
|
||||
* Width --> Width of the image
|
||||
@ -18,14 +20,10 @@ namespace va {
|
||||
* offset_y --> Vertical offset of the image in the texture
|
||||
* file_t card --> The file descriptor of the render device used for encoding
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(int width, int height, bool vram);
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram);
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram);
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram);
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram);
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram);
|
||||
|
||||
// Ensure the render device pointed to by fd is capable of encoding h264 with the hevc_mode configured
|
||||
bool
|
||||
validate(int fd);
|
||||
bool validate(int fd);
|
||||
} // namespace va
|
||||
|
@ -2,12 +2,15 @@
|
||||
* @file src/platform/linux/wayland.cpp
|
||||
* @brief Definitions for Wayland capture.
|
||||
*/
|
||||
// standard includes
|
||||
#include <cstdlib>
|
||||
|
||||
// platform includes
|
||||
#include <poll.h>
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-util.h>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
// local includes
|
||||
#include "graphics.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
@ -27,16 +30,14 @@ using namespace std::literals;
|
||||
namespace wl {
|
||||
|
||||
// Helper to call C++ method from wayland C callback
|
||||
template <class T, class Method, Method m, class... Params>
|
||||
static auto
|
||||
classCall(void *data, Params... params) -> decltype(((*reinterpret_cast<T *>(data)).*m)(params...)) {
|
||||
template<class T, class Method, Method m, class... Params>
|
||||
static auto classCall(void *data, Params... params) -> decltype(((*reinterpret_cast<T *>(data)).*m)(params...)) {
|
||||
return ((*reinterpret_cast<T *>(data)).*m)(params...);
|
||||
}
|
||||
|
||||
#define CLASS_CALL(c, m) classCall<c, decltype(&c::m), &c::m>
|
||||
|
||||
int
|
||||
display_t::init(const char *display_name) {
|
||||
int display_t::init(const char *display_name) {
|
||||
if (!display_name) {
|
||||
display_name = std::getenv("WAYLAND_DISPLAY");
|
||||
}
|
||||
@ -57,8 +58,7 @@ namespace wl {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
display_t::roundtrip() {
|
||||
void display_t::roundtrip() {
|
||||
wl_display_roundtrip(display_internal.get());
|
||||
}
|
||||
|
||||
@ -67,8 +67,7 @@ namespace wl {
|
||||
* @param timeout The timeout in milliseconds.
|
||||
* @return `true` if new events were dispatched or `false` if the timeout expired.
|
||||
*/
|
||||
bool
|
||||
display_t::dispatch(std::chrono::milliseconds timeout) {
|
||||
bool display_t::dispatch(std::chrono::milliseconds timeout) {
|
||||
// Check if any events are queued already. If not, flush
|
||||
// outgoing events, and prepare to wait for readability.
|
||||
if (wl_display_prepare_read(display_internal.get()) == 0) {
|
||||
@ -81,8 +80,7 @@ namespace wl {
|
||||
if (poll(&pfd, 1, timeout.count()) == 1 && (pfd.revents & POLLIN)) {
|
||||
// Read the new event(s)
|
||||
wl_display_read_events(display_internal.get());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// We timed out, so unlock the queue now
|
||||
wl_display_cancel_read(display_internal.get());
|
||||
return false;
|
||||
@ -94,13 +92,12 @@ namespace wl {
|
||||
return true;
|
||||
}
|
||||
|
||||
wl_registry *
|
||||
display_t::registry() {
|
||||
wl_registry *display_t::registry() {
|
||||
return wl_display_get_registry(display_internal.get());
|
||||
}
|
||||
|
||||
inline monitor_t::monitor_t(wl_output *output):
|
||||
output { output },
|
||||
output {output},
|
||||
wl_listener {
|
||||
&CLASS_CALL(monitor_t, wl_geometry),
|
||||
&CLASS_CALL(monitor_t, wl_mode),
|
||||
@ -113,46 +110,40 @@ namespace wl {
|
||||
&CLASS_CALL(monitor_t, xdg_done),
|
||||
&CLASS_CALL(monitor_t, xdg_name),
|
||||
&CLASS_CALL(monitor_t, xdg_description)
|
||||
} {}
|
||||
} {
|
||||
}
|
||||
|
||||
inline void
|
||||
monitor_t::xdg_name(zxdg_output_v1 *, const char *name) {
|
||||
inline void monitor_t::xdg_name(zxdg_output_v1 *, const char *name) {
|
||||
this->name = name;
|
||||
|
||||
BOOST_LOG(info) << "Name: "sv << this->name;
|
||||
}
|
||||
|
||||
void
|
||||
monitor_t::xdg_description(zxdg_output_v1 *, const char *description) {
|
||||
void monitor_t::xdg_description(zxdg_output_v1 *, const char *description) {
|
||||
this->description = description;
|
||||
|
||||
BOOST_LOG(info) << "Found monitor: "sv << this->description;
|
||||
}
|
||||
|
||||
void
|
||||
monitor_t::xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y) {
|
||||
void monitor_t::xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y) {
|
||||
viewport.offset_x = x;
|
||||
viewport.offset_y = y;
|
||||
|
||||
BOOST_LOG(info) << "Offset: "sv << x << 'x' << y;
|
||||
}
|
||||
|
||||
void
|
||||
monitor_t::xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height) {
|
||||
void monitor_t::xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height) {
|
||||
BOOST_LOG(info) << "Logical size: "sv << width << 'x' << height;
|
||||
}
|
||||
|
||||
void
|
||||
monitor_t::wl_mode(wl_output *wl_output, std::uint32_t flags,
|
||||
std::int32_t width, std::int32_t height, std::int32_t refresh) {
|
||||
void monitor_t::wl_mode(wl_output *wl_output, std::uint32_t flags, std::int32_t width, std::int32_t height, std::int32_t refresh) {
|
||||
viewport.width = width;
|
||||
viewport.height = height;
|
||||
|
||||
BOOST_LOG(info) << "Resolution: "sv << width << 'x' << height;
|
||||
}
|
||||
|
||||
void
|
||||
monitor_t::listen(zxdg_output_manager_v1 *output_manager) {
|
||||
void monitor_t::listen(zxdg_output_manager_v1 *output_manager) {
|
||||
auto xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, output);
|
||||
zxdg_output_v1_add_listener(xdg_output, &xdg_listener, this);
|
||||
wl_output_add_listener(output, &wl_listener, this);
|
||||
@ -160,34 +151,33 @@ namespace wl {
|
||||
|
||||
interface_t::interface_t() noexcept
|
||||
:
|
||||
output_manager { nullptr },
|
||||
output_manager {nullptr},
|
||||
listener {
|
||||
&CLASS_CALL(interface_t, add_interface),
|
||||
&CLASS_CALL(interface_t, del_interface)
|
||||
} {}
|
||||
} {
|
||||
}
|
||||
|
||||
void
|
||||
interface_t::listen(wl_registry *registry) {
|
||||
void interface_t::listen(wl_registry *registry) {
|
||||
wl_registry_add_listener(registry, &listener, this);
|
||||
}
|
||||
|
||||
void
|
||||
interface_t::add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version) {
|
||||
void interface_t::add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version) {
|
||||
BOOST_LOG(debug) << "Available interface: "sv << interface << '(' << id << ") version "sv << version;
|
||||
|
||||
if (!std::strcmp(interface, wl_output_interface.name)) {
|
||||
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
|
||||
monitors.emplace_back(
|
||||
std::make_unique<monitor_t>(
|
||||
(wl_output *) wl_registry_bind(registry, id, &wl_output_interface, 2)));
|
||||
}
|
||||
else if (!std::strcmp(interface, zxdg_output_manager_v1_interface.name)) {
|
||||
(wl_output *) wl_registry_bind(registry, id, &wl_output_interface, 2)
|
||||
)
|
||||
);
|
||||
} else if (!std::strcmp(interface, zxdg_output_manager_v1_interface.name)) {
|
||||
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
|
||||
output_manager = (zxdg_output_manager_v1 *) wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, version);
|
||||
|
||||
this->interface[XDG_OUTPUT] = true;
|
||||
}
|
||||
else if (!std::strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) {
|
||||
} else if (!std::strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) {
|
||||
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
|
||||
dmabuf_manager = (zwlr_export_dmabuf_manager_v1 *) wl_registry_bind(registry, id, &zwlr_export_dmabuf_manager_v1_interface, version);
|
||||
|
||||
@ -195,13 +185,15 @@ namespace wl {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
interface_t::del_interface(wl_registry *registry, uint32_t id) {
|
||||
void interface_t::del_interface(wl_registry *registry, uint32_t id) {
|
||||
BOOST_LOG(info) << "Delete: "sv << id;
|
||||
}
|
||||
|
||||
dmabuf_t::dmabuf_t():
|
||||
status { READY }, frames {}, current_frame { &frames[0] }, listener {
|
||||
status {READY},
|
||||
frames {},
|
||||
current_frame {&frames[0]},
|
||||
listener {
|
||||
&CLASS_CALL(dmabuf_t, frame),
|
||||
&CLASS_CALL(dmabuf_t, object),
|
||||
&CLASS_CALL(dmabuf_t, ready),
|
||||
@ -209,8 +201,7 @@ namespace wl {
|
||||
} {
|
||||
}
|
||||
|
||||
void
|
||||
dmabuf_t::listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor) {
|
||||
void dmabuf_t::listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor) {
|
||||
auto frame = zwlr_export_dmabuf_manager_v1_capture_output(dmabuf_manager, blend_cursor, output);
|
||||
zwlr_export_dmabuf_frame_v1_add_listener(frame, &listener, this);
|
||||
|
||||
@ -223,15 +214,19 @@ namespace wl {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
dmabuf_t::frame(
|
||||
void dmabuf_t::frame(
|
||||
zwlr_export_dmabuf_frame_v1 *frame,
|
||||
std::uint32_t width, std::uint32_t height,
|
||||
std::uint32_t x, std::uint32_t y,
|
||||
std::uint32_t buffer_flags, std::uint32_t flags,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
std::uint32_t x,
|
||||
std::uint32_t y,
|
||||
std::uint32_t buffer_flags,
|
||||
std::uint32_t flags,
|
||||
std::uint32_t format,
|
||||
std::uint32_t high, std::uint32_t low,
|
||||
std::uint32_t obj_count) {
|
||||
std::uint32_t high,
|
||||
std::uint32_t low,
|
||||
std::uint32_t obj_count
|
||||
) {
|
||||
auto next_frame = get_next_frame();
|
||||
|
||||
next_frame->sd.fourcc = format;
|
||||
@ -240,15 +235,15 @@ namespace wl {
|
||||
next_frame->sd.modifier = (((std::uint64_t) high) << 32) | low;
|
||||
}
|
||||
|
||||
void
|
||||
dmabuf_t::object(
|
||||
void dmabuf_t::object(
|
||||
zwlr_export_dmabuf_frame_v1 *frame,
|
||||
std::uint32_t index,
|
||||
std::int32_t fd,
|
||||
std::uint32_t size,
|
||||
std::uint32_t offset,
|
||||
std::uint32_t stride,
|
||||
std::uint32_t plane_index) {
|
||||
std::uint32_t plane_index
|
||||
) {
|
||||
auto next_frame = get_next_frame();
|
||||
|
||||
next_frame->sd.fds[plane_index] = fd;
|
||||
@ -256,10 +251,12 @@ namespace wl {
|
||||
next_frame->sd.offsets[plane_index] = offset;
|
||||
}
|
||||
|
||||
void
|
||||
dmabuf_t::ready(
|
||||
void dmabuf_t::ready(
|
||||
zwlr_export_dmabuf_frame_v1 *frame,
|
||||
std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec) {
|
||||
std::uint32_t tv_sec_hi,
|
||||
std::uint32_t tv_sec_lo,
|
||||
std::uint32_t tv_nsec
|
||||
) {
|
||||
zwlr_export_dmabuf_frame_v1_destroy(frame);
|
||||
|
||||
current_frame->destroy();
|
||||
@ -268,10 +265,10 @@ namespace wl {
|
||||
status = READY;
|
||||
}
|
||||
|
||||
void
|
||||
dmabuf_t::cancel(
|
||||
void dmabuf_t::cancel(
|
||||
zwlr_export_dmabuf_frame_v1 *frame,
|
||||
std::uint32_t reason) {
|
||||
std::uint32_t reason
|
||||
) {
|
||||
zwlr_export_dmabuf_frame_v1_destroy(frame);
|
||||
|
||||
auto next_frame = get_next_frame();
|
||||
@ -280,8 +277,7 @@ namespace wl {
|
||||
status = REINIT;
|
||||
}
|
||||
|
||||
void
|
||||
frame_t::destroy() {
|
||||
void frame_t::destroy() {
|
||||
for (auto x = 0; x < 4; ++x) {
|
||||
if (sd.fds[x] >= 0) {
|
||||
close(sd.fds[x]);
|
||||
@ -296,8 +292,7 @@ namespace wl {
|
||||
std::fill_n(sd.fds, 4, -1);
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<monitor_t>>
|
||||
monitors(const char *display_name) {
|
||||
std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name) {
|
||||
display_t display;
|
||||
|
||||
if (display.init(display_name)) {
|
||||
@ -323,15 +318,13 @@ namespace wl {
|
||||
return std::move(interface.monitors);
|
||||
}
|
||||
|
||||
static bool
|
||||
validate() {
|
||||
static bool validate() {
|
||||
display_t display;
|
||||
|
||||
return display.init() == 0;
|
||||
}
|
||||
|
||||
int
|
||||
init() {
|
||||
int init() {
|
||||
static bool validated = validate();
|
||||
|
||||
return !validated;
|
||||
@ -339,4 +332,4 @@ namespace wl {
|
||||
|
||||
} // namespace wl
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
|
@ -4,6 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <bitset>
|
||||
|
||||
#ifdef SUNSHINE_BUILD_WAYLAND
|
||||
@ -11,6 +12,7 @@
|
||||
#include <xdg-output-unstable-v1.h>
|
||||
#endif
|
||||
|
||||
// local includes
|
||||
#include "graphics.h"
|
||||
|
||||
/**
|
||||
@ -27,8 +29,7 @@ namespace wl {
|
||||
frame_t();
|
||||
egl::surface_descriptor_t sd;
|
||||
|
||||
void
|
||||
destroy();
|
||||
void destroy();
|
||||
};
|
||||
|
||||
class dmabuf_t {
|
||||
@ -42,50 +43,52 @@ namespace wl {
|
||||
dmabuf_t(dmabuf_t &&) = delete;
|
||||
dmabuf_t(const dmabuf_t &) = delete;
|
||||
|
||||
dmabuf_t &
|
||||
operator=(const dmabuf_t &) = delete;
|
||||
dmabuf_t &
|
||||
operator=(dmabuf_t &&) = delete;
|
||||
dmabuf_t &operator=(const dmabuf_t &) = delete;
|
||||
dmabuf_t &operator=(dmabuf_t &&) = delete;
|
||||
|
||||
dmabuf_t();
|
||||
|
||||
void
|
||||
listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor = false);
|
||||
void listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor = false);
|
||||
|
||||
~dmabuf_t();
|
||||
|
||||
void
|
||||
frame(
|
||||
void frame(
|
||||
zwlr_export_dmabuf_frame_v1 *frame,
|
||||
std::uint32_t width, std::uint32_t height,
|
||||
std::uint32_t x, std::uint32_t y,
|
||||
std::uint32_t buffer_flags, std::uint32_t flags,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
std::uint32_t x,
|
||||
std::uint32_t y,
|
||||
std::uint32_t buffer_flags,
|
||||
std::uint32_t flags,
|
||||
std::uint32_t format,
|
||||
std::uint32_t high, std::uint32_t low,
|
||||
std::uint32_t obj_count);
|
||||
std::uint32_t high,
|
||||
std::uint32_t low,
|
||||
std::uint32_t obj_count
|
||||
);
|
||||
|
||||
void
|
||||
object(
|
||||
void object(
|
||||
zwlr_export_dmabuf_frame_v1 *frame,
|
||||
std::uint32_t index,
|
||||
std::int32_t fd,
|
||||
std::uint32_t size,
|
||||
std::uint32_t offset,
|
||||
std::uint32_t stride,
|
||||
std::uint32_t plane_index);
|
||||
std::uint32_t plane_index
|
||||
);
|
||||
|
||||
void
|
||||
ready(
|
||||
void ready(
|
||||
zwlr_export_dmabuf_frame_v1 *frame,
|
||||
std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec);
|
||||
std::uint32_t tv_sec_hi,
|
||||
std::uint32_t tv_sec_lo,
|
||||
std::uint32_t tv_nsec
|
||||
);
|
||||
|
||||
void
|
||||
cancel(
|
||||
void cancel(
|
||||
zwlr_export_dmabuf_frame_v1 *frame,
|
||||
std::uint32_t reason);
|
||||
std::uint32_t reason
|
||||
);
|
||||
|
||||
inline frame_t *
|
||||
get_next_frame() {
|
||||
inline frame_t *get_next_frame() {
|
||||
return current_frame == &frames[0] ? &frames[1] : &frames[0];
|
||||
}
|
||||
|
||||
@ -102,38 +105,31 @@ namespace wl {
|
||||
monitor_t(monitor_t &&) = delete;
|
||||
monitor_t(const monitor_t &) = delete;
|
||||
|
||||
monitor_t &
|
||||
operator=(const monitor_t &) = delete;
|
||||
monitor_t &
|
||||
operator=(monitor_t &&) = delete;
|
||||
monitor_t &operator=(const monitor_t &) = delete;
|
||||
monitor_t &operator=(monitor_t &&) = delete;
|
||||
|
||||
monitor_t(wl_output *output);
|
||||
|
||||
void
|
||||
xdg_name(zxdg_output_v1 *, const char *name);
|
||||
void
|
||||
xdg_description(zxdg_output_v1 *, const char *description);
|
||||
void
|
||||
xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y);
|
||||
void
|
||||
xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height);
|
||||
void
|
||||
xdg_done(zxdg_output_v1 *) {}
|
||||
void xdg_name(zxdg_output_v1 *, const char *name);
|
||||
void xdg_description(zxdg_output_v1 *, const char *description);
|
||||
void xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y);
|
||||
void xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height);
|
||||
|
||||
void
|
||||
wl_geometry(wl_output *wl_output, std::int32_t x, std::int32_t y,
|
||||
std::int32_t physical_width, std::int32_t physical_height, std::int32_t subpixel,
|
||||
const char *make, const char *model, std::int32_t transform) {}
|
||||
void
|
||||
wl_mode(wl_output *wl_output, std::uint32_t flags,
|
||||
std::int32_t width, std::int32_t height, std::int32_t refresh);
|
||||
void
|
||||
wl_done(wl_output *wl_output) {}
|
||||
void
|
||||
wl_scale(wl_output *wl_output, std::int32_t factor) {}
|
||||
void xdg_done(zxdg_output_v1 *) {
|
||||
}
|
||||
|
||||
void
|
||||
listen(zxdg_output_manager_v1 *output_manager);
|
||||
void wl_geometry(wl_output *wl_output, std::int32_t x, std::int32_t y, std::int32_t physical_width, std::int32_t physical_height, std::int32_t subpixel, const char *make, const char *model, std::int32_t transform) {
|
||||
}
|
||||
|
||||
void wl_mode(wl_output *wl_output, std::uint32_t flags, std::int32_t width, std::int32_t height, std::int32_t refresh);
|
||||
|
||||
void wl_done(wl_output *wl_output) {
|
||||
}
|
||||
|
||||
void wl_scale(wl_output *wl_output, std::int32_t factor) {
|
||||
}
|
||||
|
||||
void listen(zxdg_output_manager_v1 *output_manager);
|
||||
|
||||
wl_output *output;
|
||||
|
||||
@ -162,31 +158,25 @@ namespace wl {
|
||||
interface_t(interface_t &&) = delete;
|
||||
interface_t(const interface_t &) = delete;
|
||||
|
||||
interface_t &
|
||||
operator=(const interface_t &) = delete;
|
||||
interface_t &
|
||||
operator=(interface_t &&) = delete;
|
||||
interface_t &operator=(const interface_t &) = delete;
|
||||
interface_t &operator=(interface_t &&) = delete;
|
||||
|
||||
interface_t() noexcept;
|
||||
|
||||
void
|
||||
listen(wl_registry *registry);
|
||||
void listen(wl_registry *registry);
|
||||
|
||||
std::vector<std::unique_ptr<monitor_t>> monitors;
|
||||
|
||||
zwlr_export_dmabuf_manager_v1 *dmabuf_manager;
|
||||
zxdg_output_manager_v1 *output_manager;
|
||||
|
||||
bool
|
||||
operator[](interface_e bit) const {
|
||||
bool operator[](interface_e bit) const {
|
||||
return interface[bit];
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version);
|
||||
void
|
||||
del_interface(wl_registry *registry, uint32_t id);
|
||||
void add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version);
|
||||
void del_interface(wl_registry *registry, uint32_t id);
|
||||
|
||||
std::bitset<MAX_INTERFACES> interface;
|
||||
|
||||
@ -201,24 +191,19 @@ namespace wl {
|
||||
* @param display_name The name of the display.
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
int
|
||||
init(const char *display_name = nullptr);
|
||||
int init(const char *display_name = nullptr);
|
||||
|
||||
// Roundtrip with Wayland connection
|
||||
void
|
||||
roundtrip();
|
||||
void roundtrip();
|
||||
|
||||
// Wait up to the timeout to read and dispatch new events
|
||||
bool
|
||||
dispatch(std::chrono::milliseconds timeout);
|
||||
bool dispatch(std::chrono::milliseconds timeout);
|
||||
|
||||
// Get the registry associated with the display
|
||||
// No need to manually free the registry
|
||||
wl_registry *
|
||||
registry();
|
||||
wl_registry *registry();
|
||||
|
||||
inline display_internal_t::pointer
|
||||
get() {
|
||||
inline display_internal_t::pointer get() {
|
||||
return display_internal.get();
|
||||
}
|
||||
|
||||
@ -226,11 +211,9 @@ namespace wl {
|
||||
display_internal_t display_internal;
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<monitor_t>>
|
||||
monitors(const char *display_name = nullptr);
|
||||
std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name = nullptr);
|
||||
|
||||
int
|
||||
init();
|
||||
int init();
|
||||
} // namespace wl
|
||||
#else
|
||||
|
||||
@ -243,15 +226,12 @@ namespace wl {
|
||||
monitor_t(monitor_t &&) = delete;
|
||||
monitor_t(const monitor_t &) = delete;
|
||||
|
||||
monitor_t &
|
||||
operator=(const monitor_t &) = delete;
|
||||
monitor_t &
|
||||
operator=(monitor_t &&) = delete;
|
||||
monitor_t &operator=(const monitor_t &) = delete;
|
||||
monitor_t &operator=(monitor_t &&) = delete;
|
||||
|
||||
monitor_t(wl_output *output);
|
||||
|
||||
void
|
||||
listen(zxdg_output_manager_v1 *output_manager);
|
||||
void listen(zxdg_output_manager_v1 *output_manager);
|
||||
|
||||
wl_output *output;
|
||||
|
||||
@ -261,10 +241,12 @@ namespace wl {
|
||||
platf::touch_port_t viewport;
|
||||
};
|
||||
|
||||
inline std::vector<std::unique_ptr<monitor_t>>
|
||||
monitors(const char *display_name = nullptr) { return {}; }
|
||||
inline std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name = nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
inline int
|
||||
init() { return -1; }
|
||||
inline int init() {
|
||||
return -1;
|
||||
}
|
||||
} // namespace wl
|
||||
#endif
|
||||
|
@ -2,18 +2,19 @@
|
||||
* @file src/platform/linux/wlgrab.cpp
|
||||
* @brief Definitions for wlgrab capture.
|
||||
*/
|
||||
// standard includes
|
||||
#include <thread>
|
||||
|
||||
#include "src/platform/common.h"
|
||||
|
||||
#include "src/logging.h"
|
||||
#include "src/video.h"
|
||||
|
||||
// local includes
|
||||
#include "cuda.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/video.h"
|
||||
#include "vaapi.h"
|
||||
#include "wayland.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace wl {
|
||||
static int env_width;
|
||||
static int env_height;
|
||||
@ -27,9 +28,8 @@ namespace wl {
|
||||
|
||||
class wlr_t: public platf::display_t {
|
||||
public:
|
||||
int
|
||||
init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
delay = std::chrono::nanoseconds { 1s } / config.framerate;
|
||||
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
delay = std::chrono::nanoseconds {1s} / config.framerate;
|
||||
mem_type = hwdevice_type;
|
||||
|
||||
if (display.init()) {
|
||||
@ -82,13 +82,11 @@ namespace wl {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
dummy_img(platf::img_t *img) override {
|
||||
int dummy_img(platf::img_t *img) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline platf::capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
inline platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
auto to = std::chrono::steady_clock::now() + timeout;
|
||||
|
||||
// Dispatch events until we get a new frame or the timeout expires
|
||||
@ -105,7 +103,8 @@ namespace wl {
|
||||
if (
|
||||
dmabuf.status == dmabuf_t::REINIT ||
|
||||
current_frame->sd.width != width ||
|
||||
current_frame->sd.height != height) {
|
||||
current_frame->sd.height != height
|
||||
) {
|
||||
return platf::capture_e::reinit;
|
||||
}
|
||||
|
||||
@ -125,8 +124,7 @@ namespace wl {
|
||||
|
||||
class wlr_ram_t: public wlr_t {
|
||||
public:
|
||||
platf::capture_e
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
sleep_overshoot_logger.reset();
|
||||
@ -171,8 +169,7 @@ namespace wl {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
platf::capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor);
|
||||
if (status != platf::capture_e::ok) {
|
||||
return status;
|
||||
@ -204,8 +201,7 @@ namespace wl {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
int
|
||||
init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
if (wlr_t::init(hwdevice_type, display_name, config)) {
|
||||
return -1;
|
||||
}
|
||||
@ -225,8 +221,7 @@ namespace wl {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
|
||||
#ifdef SUNSHINE_BUILD_VAAPI
|
||||
if (mem_type == platf::mem_type_e::vaapi) {
|
||||
return va::make_avcodec_encode_device(width, height, false);
|
||||
@ -242,8 +237,7 @@ namespace wl {
|
||||
return std::make_unique<platf::avcodec_encode_device_t>();
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::img_t>
|
||||
alloc_img() override {
|
||||
std::shared_ptr<platf::img_t> alloc_img() override {
|
||||
auto img = std::make_shared<img_t>();
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
@ -260,8 +254,7 @@ namespace wl {
|
||||
|
||||
class wlr_vram_t: public wlr_t {
|
||||
public:
|
||||
platf::capture_e
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
sleep_overshoot_logger.reset();
|
||||
@ -306,8 +299,7 @@ namespace wl {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
platf::capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor);
|
||||
if (status != platf::capture_e::ok) {
|
||||
return status;
|
||||
@ -332,8 +324,7 @@ namespace wl {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::img_t>
|
||||
alloc_img() override {
|
||||
std::shared_ptr<platf::img_t> alloc_img() override {
|
||||
auto img = std::make_shared<egl::img_descriptor_t>();
|
||||
|
||||
img->width = width;
|
||||
@ -348,8 +339,7 @@ namespace wl {
|
||||
return img;
|
||||
}
|
||||
|
||||
std::unique_ptr<platf::avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
|
||||
#ifdef SUNSHINE_BUILD_VAAPI
|
||||
if (mem_type == platf::mem_type_e::vaapi) {
|
||||
return va::make_avcodec_encode_device(width, height, 0, 0, true);
|
||||
@ -365,8 +355,7 @@ namespace wl {
|
||||
return std::make_unique<platf::avcodec_encode_device_t>();
|
||||
}
|
||||
|
||||
int
|
||||
dummy_img(platf::img_t *img) override {
|
||||
int dummy_img(platf::img_t *img) override {
|
||||
// Empty images are recognized as dummies by the zero sequence number
|
||||
return 0;
|
||||
}
|
||||
@ -377,8 +366,7 @@ namespace wl {
|
||||
} // namespace wl
|
||||
|
||||
namespace platf {
|
||||
std::shared_ptr<display_t>
|
||||
wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
|
||||
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
|
||||
return nullptr;
|
||||
@ -401,8 +389,7 @@ namespace platf {
|
||||
return wlr;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
wl_display_names() {
|
||||
std::vector<std::string> wl_display_names() {
|
||||
std::vector<std::string> display_names;
|
||||
|
||||
wl::display_t display;
|
||||
|
@ -2,61 +2,49 @@
|
||||
* @file src/platform/linux/x11grab.cpp
|
||||
* @brief Definitions for x11 capture.
|
||||
*/
|
||||
#include "src/platform/common.h"
|
||||
|
||||
// standard includes
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
// plaform includes
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
#include <X11/X.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include <xcb/shm.h>
|
||||
#include <xcb/xfixes.h>
|
||||
|
||||
#include "src/config.h"
|
||||
#include "src/globals.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/task_pool.h"
|
||||
#include "src/video.h"
|
||||
|
||||
// local includes
|
||||
#include "cuda.h"
|
||||
#include "graphics.h"
|
||||
#include "misc.h"
|
||||
#include "src/config.h"
|
||||
#include "src/globals.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/task_pool.h"
|
||||
#include "src/video.h"
|
||||
#include "vaapi.h"
|
||||
#include "x11grab.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf {
|
||||
int
|
||||
load_xcb();
|
||||
int
|
||||
load_x11();
|
||||
int load_xcb();
|
||||
int load_x11();
|
||||
|
||||
namespace x11 {
|
||||
#define _FN(x, ret, args) \
|
||||
#define _FN(x, ret, args) \
|
||||
typedef ret(*x##_fn) args; \
|
||||
static x##_fn x
|
||||
|
||||
_FN(GetImage, XImage *,
|
||||
(
|
||||
Display * display,
|
||||
Drawable d,
|
||||
int x, int y,
|
||||
unsigned int width, unsigned int height,
|
||||
unsigned long plane_mask,
|
||||
int format));
|
||||
_FN(GetImage, XImage *, (Display * display, Drawable d, int x, int y, unsigned int width, unsigned int height, unsigned long plane_mask, int format));
|
||||
|
||||
_FN(OpenDisplay, Display *, (_Xconst char *display_name));
|
||||
_FN(GetWindowAttributes, Status,
|
||||
(
|
||||
Display * display,
|
||||
Window w,
|
||||
XWindowAttributes *window_attributes_return));
|
||||
_FN(GetWindowAttributes, Status, (Display * display, Window w, XWindowAttributes *window_attributes_return));
|
||||
|
||||
_FN(CloseDisplay, int, (Display * display));
|
||||
_FN(Free, int, (void *data));
|
||||
@ -70,27 +58,28 @@ namespace platf {
|
||||
_FN(FreeOutputInfo, void, (XRROutputInfo * outputInfo));
|
||||
_FN(FreeCrtcInfo, void, (XRRCrtcInfo * crtcInfo));
|
||||
|
||||
static int
|
||||
init() {
|
||||
static void *handle { nullptr };
|
||||
static int init() {
|
||||
static void *handle {nullptr};
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
if (funcs_loaded) return 0;
|
||||
if (funcs_loaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!handle) {
|
||||
handle = dyn::handle({ "libXrandr.so.2", "libXrandr.so" });
|
||||
handle = dyn::handle({"libXrandr.so.2", "libXrandr.so"});
|
||||
if (!handle) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
|
||||
{ (dyn::apiproc *) &GetScreenResources, "XRRGetScreenResources" },
|
||||
{ (dyn::apiproc *) &GetOutputInfo, "XRRGetOutputInfo" },
|
||||
{ (dyn::apiproc *) &GetCrtcInfo, "XRRGetCrtcInfo" },
|
||||
{ (dyn::apiproc *) &FreeScreenResources, "XRRFreeScreenResources" },
|
||||
{ (dyn::apiproc *) &FreeOutputInfo, "XRRFreeOutputInfo" },
|
||||
{ (dyn::apiproc *) &FreeCrtcInfo, "XRRFreeCrtcInfo" },
|
||||
{(dyn::apiproc *) &GetScreenResources, "XRRGetScreenResources"},
|
||||
{(dyn::apiproc *) &GetOutputInfo, "XRRGetOutputInfo"},
|
||||
{(dyn::apiproc *) &GetCrtcInfo, "XRRGetCrtcInfo"},
|
||||
{(dyn::apiproc *) &FreeScreenResources, "XRRFreeScreenResources"},
|
||||
{(dyn::apiproc *) &FreeOutputInfo, "XRRFreeOutputInfo"},
|
||||
{(dyn::apiproc *) &FreeCrtcInfo, "XRRFreeCrtcInfo"},
|
||||
};
|
||||
|
||||
if (dyn::load(handle, funcs)) {
|
||||
@ -102,25 +91,27 @@ namespace platf {
|
||||
}
|
||||
|
||||
} // namespace rr
|
||||
|
||||
namespace fix {
|
||||
_FN(GetCursorImage, XFixesCursorImage *, (Display * dpy));
|
||||
|
||||
static int
|
||||
init() {
|
||||
static void *handle { nullptr };
|
||||
static int init() {
|
||||
static void *handle {nullptr};
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
if (funcs_loaded) return 0;
|
||||
if (funcs_loaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!handle) {
|
||||
handle = dyn::handle({ "libXfixes.so.3", "libXfixes.so" });
|
||||
handle = dyn::handle({"libXfixes.so.3", "libXfixes.so"});
|
||||
if (!handle) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
|
||||
{ (dyn::apiproc *) &GetCursorImage, "XFixesGetCursorImage" },
|
||||
{(dyn::apiproc *) &GetCursorImage, "XFixesGetCursorImage"},
|
||||
};
|
||||
|
||||
if (dyn::load(handle, funcs)) {
|
||||
@ -132,27 +123,28 @@ namespace platf {
|
||||
}
|
||||
} // namespace fix
|
||||
|
||||
static int
|
||||
init() {
|
||||
static void *handle { nullptr };
|
||||
static int init() {
|
||||
static void *handle {nullptr};
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
if (funcs_loaded) return 0;
|
||||
if (funcs_loaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!handle) {
|
||||
handle = dyn::handle({ "libX11.so.6", "libX11.so" });
|
||||
handle = dyn::handle({"libX11.so.6", "libX11.so"});
|
||||
if (!handle) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
|
||||
{ (dyn::apiproc *) &GetImage, "XGetImage" },
|
||||
{ (dyn::apiproc *) &OpenDisplay, "XOpenDisplay" },
|
||||
{ (dyn::apiproc *) &GetWindowAttributes, "XGetWindowAttributes" },
|
||||
{ (dyn::apiproc *) &Free, "XFree" },
|
||||
{ (dyn::apiproc *) &CloseDisplay, "XCloseDisplay" },
|
||||
{ (dyn::apiproc *) &InitThreads, "XInitThreads" },
|
||||
{(dyn::apiproc *) &GetImage, "XGetImage"},
|
||||
{(dyn::apiproc *) &OpenDisplay, "XOpenDisplay"},
|
||||
{(dyn::apiproc *) &GetWindowAttributes, "XGetWindowAttributes"},
|
||||
{(dyn::apiproc *) &Free, "XFree"},
|
||||
{(dyn::apiproc *) &CloseDisplay, "XCloseDisplay"},
|
||||
{(dyn::apiproc *) &InitThreads, "XInitThreads"},
|
||||
};
|
||||
|
||||
if (dyn::load(handle, funcs)) {
|
||||
@ -167,31 +159,13 @@ namespace platf {
|
||||
namespace xcb {
|
||||
static xcb_extension_t *shm_id;
|
||||
|
||||
_FN(shm_get_image_reply, xcb_shm_get_image_reply_t *,
|
||||
(
|
||||
xcb_connection_t * c,
|
||||
xcb_shm_get_image_cookie_t cookie,
|
||||
xcb_generic_error_t **e));
|
||||
_FN(shm_get_image_reply, xcb_shm_get_image_reply_t *, (xcb_connection_t * c, xcb_shm_get_image_cookie_t cookie, xcb_generic_error_t **e));
|
||||
|
||||
_FN(shm_get_image_unchecked, xcb_shm_get_image_cookie_t,
|
||||
(
|
||||
xcb_connection_t * c,
|
||||
xcb_drawable_t drawable,
|
||||
int16_t x, int16_t y,
|
||||
uint16_t width, uint16_t height,
|
||||
uint32_t plane_mask,
|
||||
uint8_t format,
|
||||
xcb_shm_seg_t shmseg,
|
||||
uint32_t offset));
|
||||
_FN(shm_get_image_unchecked, xcb_shm_get_image_cookie_t, (xcb_connection_t * c, xcb_drawable_t drawable, int16_t x, int16_t y, uint16_t width, uint16_t height, uint32_t plane_mask, uint8_t format, xcb_shm_seg_t shmseg, uint32_t offset));
|
||||
|
||||
_FN(shm_attach, xcb_void_cookie_t,
|
||||
(xcb_connection_t * c,
|
||||
xcb_shm_seg_t shmseg,
|
||||
uint32_t shmid,
|
||||
uint8_t read_only));
|
||||
_FN(shm_attach, xcb_void_cookie_t, (xcb_connection_t * c, xcb_shm_seg_t shmseg, uint32_t shmid, uint8_t read_only));
|
||||
|
||||
_FN(get_extension_data, xcb_query_extension_reply_t *,
|
||||
(xcb_connection_t * c, xcb_extension_t *ext));
|
||||
_FN(get_extension_data, xcb_query_extension_reply_t *, (xcb_connection_t * c, xcb_extension_t *ext));
|
||||
|
||||
_FN(get_setup, xcb_setup_t *, (xcb_connection_t * c));
|
||||
_FN(disconnect, void, (xcb_connection_t * c));
|
||||
@ -200,25 +174,26 @@ namespace platf {
|
||||
_FN(setup_roots_iterator, xcb_screen_iterator_t, (const xcb_setup_t *R));
|
||||
_FN(generate_id, std::uint32_t, (xcb_connection_t * c));
|
||||
|
||||
int
|
||||
init_shm() {
|
||||
static void *handle { nullptr };
|
||||
int init_shm() {
|
||||
static void *handle {nullptr};
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
if (funcs_loaded) return 0;
|
||||
if (funcs_loaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!handle) {
|
||||
handle = dyn::handle({ "libxcb-shm.so.0", "libxcb-shm.so" });
|
||||
handle = dyn::handle({"libxcb-shm.so.0", "libxcb-shm.so"});
|
||||
if (!handle) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
|
||||
{ (dyn::apiproc *) &shm_id, "xcb_shm_id" },
|
||||
{ (dyn::apiproc *) &shm_get_image_reply, "xcb_shm_get_image_reply" },
|
||||
{ (dyn::apiproc *) &shm_get_image_unchecked, "xcb_shm_get_image_unchecked" },
|
||||
{ (dyn::apiproc *) &shm_attach, "xcb_shm_attach" },
|
||||
{(dyn::apiproc *) &shm_id, "xcb_shm_id"},
|
||||
{(dyn::apiproc *) &shm_get_image_reply, "xcb_shm_get_image_reply"},
|
||||
{(dyn::apiproc *) &shm_get_image_unchecked, "xcb_shm_get_image_unchecked"},
|
||||
{(dyn::apiproc *) &shm_attach, "xcb_shm_attach"},
|
||||
};
|
||||
|
||||
if (dyn::load(handle, funcs)) {
|
||||
@ -229,28 +204,29 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
init() {
|
||||
static void *handle { nullptr };
|
||||
int init() {
|
||||
static void *handle {nullptr};
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
if (funcs_loaded) return 0;
|
||||
if (funcs_loaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!handle) {
|
||||
handle = dyn::handle({ "libxcb.so.1", "libxcb.so" });
|
||||
handle = dyn::handle({"libxcb.so.1", "libxcb.so"});
|
||||
if (!handle) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
|
||||
{ (dyn::apiproc *) &get_extension_data, "xcb_get_extension_data" },
|
||||
{ (dyn::apiproc *) &get_setup, "xcb_get_setup" },
|
||||
{ (dyn::apiproc *) &disconnect, "xcb_disconnect" },
|
||||
{ (dyn::apiproc *) &connection_has_error, "xcb_connection_has_error" },
|
||||
{ (dyn::apiproc *) &connect, "xcb_connect" },
|
||||
{ (dyn::apiproc *) &setup_roots_iterator, "xcb_setup_roots_iterator" },
|
||||
{ (dyn::apiproc *) &generate_id, "xcb_generate_id" },
|
||||
{(dyn::apiproc *) &get_extension_data, "xcb_get_extension_data"},
|
||||
{(dyn::apiproc *) &get_setup, "xcb_get_setup"},
|
||||
{(dyn::apiproc *) &disconnect, "xcb_disconnect"},
|
||||
{(dyn::apiproc *) &connection_has_error, "xcb_connection_has_error"},
|
||||
{(dyn::apiproc *) &connect, "xcb_connect"},
|
||||
{(dyn::apiproc *) &setup_roots_iterator, "xcb_setup_roots_iterator"},
|
||||
{(dyn::apiproc *) &generate_id, "xcb_generate_id"},
|
||||
};
|
||||
|
||||
if (dyn::load(handle, funcs)) {
|
||||
@ -264,10 +240,8 @@ namespace platf {
|
||||
#undef _FN
|
||||
} // namespace xcb
|
||||
|
||||
void
|
||||
freeImage(XImage *);
|
||||
void
|
||||
freeX(XFixesCursorImage *);
|
||||
void freeImage(XImage *);
|
||||
void freeX(XFixesCursorImage *);
|
||||
|
||||
using xcb_connect_t = util::dyn_safe_ptr<xcb_connection_t, &xcb::disconnect>;
|
||||
using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;
|
||||
@ -282,9 +256,13 @@ namespace platf {
|
||||
class shm_id_t {
|
||||
public:
|
||||
shm_id_t():
|
||||
id { -1 } {}
|
||||
id {-1} {
|
||||
}
|
||||
|
||||
shm_id_t(int id):
|
||||
id { id } {}
|
||||
id {id} {
|
||||
}
|
||||
|
||||
shm_id_t(shm_id_t &&other) noexcept:
|
||||
id(other.id) {
|
||||
other.id = -1;
|
||||
@ -296,15 +274,19 @@ namespace platf {
|
||||
id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
int id;
|
||||
};
|
||||
|
||||
class shm_data_t {
|
||||
public:
|
||||
shm_data_t():
|
||||
data { (void *) -1 } {}
|
||||
data {(void *) -1} {
|
||||
}
|
||||
|
||||
shm_data_t(void *data):
|
||||
data { data } {}
|
||||
data {data} {
|
||||
}
|
||||
|
||||
shm_data_t(shm_data_t &&other) noexcept:
|
||||
data(other.data) {
|
||||
@ -331,9 +313,8 @@ namespace platf {
|
||||
}
|
||||
};
|
||||
|
||||
static void
|
||||
blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
|
||||
xcursor_t overlay { x11::fix::GetCursorImage(display) };
|
||||
static void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
|
||||
xcursor_t overlay {x11::fix::GetCursorImage(display)};
|
||||
|
||||
if (!overlay) {
|
||||
BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv;
|
||||
@ -370,8 +351,7 @@ namespace platf {
|
||||
auto alpha = (*(uint *) pixel_p) >> 24u;
|
||||
if (alpha == 255) {
|
||||
*pixels_begin = *pixel_p;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
auto colors_out = (uint8_t *) pixel_p;
|
||||
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
|
||||
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
|
||||
@ -398,18 +378,20 @@ namespace platf {
|
||||
// int env_width, env_height;
|
||||
|
||||
x11_attr_t(mem_type_e mem_type):
|
||||
xdisplay { x11::OpenDisplay(nullptr) }, xwindow {}, xattr {}, mem_type { mem_type } {
|
||||
xdisplay {x11::OpenDisplay(nullptr)},
|
||||
xwindow {},
|
||||
xattr {},
|
||||
mem_type {mem_type} {
|
||||
x11::InitThreads();
|
||||
}
|
||||
|
||||
int
|
||||
init(const std::string &display_name, const ::video::config_t &config) {
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
if (!xdisplay) {
|
||||
BOOST_LOG(error) << "Could not open X11 display"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
delay = std::chrono::nanoseconds { 1s } / config.framerate;
|
||||
delay = std::chrono::nanoseconds {1s} / config.framerate;
|
||||
|
||||
xwindow = DefaultRootWindow(xdisplay.get());
|
||||
|
||||
@ -422,13 +404,13 @@ namespace platf {
|
||||
|
||||
if (streamedMonitor != -1) {
|
||||
BOOST_LOG(info) << "Configuring selected display ("sv << streamedMonitor << ") to stream"sv;
|
||||
screen_res_t screenr { x11::rr::GetScreenResources(xdisplay.get(), xwindow) };
|
||||
screen_res_t screenr {x11::rr::GetScreenResources(xdisplay.get(), xwindow)};
|
||||
int output = screenr->noutput;
|
||||
|
||||
output_info_t result;
|
||||
int monitor = 0;
|
||||
for (int x = 0; x < output; ++x) {
|
||||
output_info_t out_info { x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) };
|
||||
output_info_t out_info {x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x])};
|
||||
if (out_info) {
|
||||
if (monitor++ == streamedMonitor) {
|
||||
result = std::move(out_info);
|
||||
@ -443,7 +425,7 @@ namespace platf {
|
||||
}
|
||||
|
||||
if (result->crtc) {
|
||||
crtc_info_t crt_info { x11::rr::GetCrtcInfo(xdisplay.get(), screenr.get(), result->crtc) };
|
||||
crtc_info_t crt_info {x11::rr::GetCrtcInfo(xdisplay.get(), screenr.get(), result->crtc)};
|
||||
BOOST_LOG(info)
|
||||
<< "Streaming display: "sv << result->name << " with res "sv << crt_info->width << 'x' << crt_info->height << " offset by "sv << crt_info->x << 'x' << crt_info->y;
|
||||
|
||||
@ -451,14 +433,12 @@ namespace platf {
|
||||
height = crt_info->height;
|
||||
offset_x = crt_info->x;
|
||||
offset_y = crt_info->y;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "Couldn't get requested display info, defaulting to recording entire virtual desktop"sv;
|
||||
width = xattr.width;
|
||||
height = xattr.height;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
width = xattr.width;
|
||||
height = xattr.height;
|
||||
}
|
||||
@ -472,13 +452,11 @@ namespace platf {
|
||||
/**
|
||||
* Called when the display attributes should change.
|
||||
*/
|
||||
void
|
||||
refresh() {
|
||||
void refresh() {
|
||||
x11::GetWindowAttributes(xdisplay.get(), xwindow, &xattr); // Update xattr's
|
||||
}
|
||||
|
||||
capture_e
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
sleep_overshoot_logger.reset();
|
||||
@ -523,8 +501,7 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
refresh();
|
||||
|
||||
// The whole X server changed, so we must reinit everything
|
||||
@ -538,7 +515,7 @@ namespace platf {
|
||||
}
|
||||
auto img = (x11_img_t *) img_out.get();
|
||||
|
||||
XImage *x_img { x11::GetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) };
|
||||
XImage *x_img {x11::GetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap)};
|
||||
img->frame_timestamp = std::chrono::steady_clock::now();
|
||||
|
||||
img->width = x_img->width;
|
||||
@ -555,13 +532,11 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
std::shared_ptr<img_t>
|
||||
alloc_img() override {
|
||||
std::shared_ptr<img_t> alloc_img() override {
|
||||
return std::make_shared<x11_img_t>();
|
||||
}
|
||||
|
||||
std::unique_ptr<avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
|
||||
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
|
||||
#ifdef SUNSHINE_BUILD_VAAPI
|
||||
if (mem_type == mem_type_e::vaapi) {
|
||||
return va::make_avcodec_encode_device(width, height, false);
|
||||
@ -577,8 +552,7 @@ namespace platf {
|
||||
return std::make_unique<avcodec_encode_device_t>();
|
||||
}
|
||||
|
||||
int
|
||||
dummy_img(img_t *img) override {
|
||||
int dummy_img(img_t *img) override {
|
||||
// TODO: stop cheating and give black image
|
||||
if (!img) {
|
||||
return -1;
|
||||
@ -605,15 +579,15 @@ namespace platf {
|
||||
|
||||
task_pool_util::TaskPool::task_id_t refresh_task_id;
|
||||
|
||||
void
|
||||
delayed_refresh() {
|
||||
void delayed_refresh() {
|
||||
refresh();
|
||||
|
||||
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
|
||||
}
|
||||
|
||||
shm_attr_t(mem_type_e mem_type):
|
||||
x11_attr_t(mem_type), shm_xdisplay { x11::OpenDisplay(nullptr) } {
|
||||
x11_attr_t(mem_type),
|
||||
shm_xdisplay {x11::OpenDisplay(nullptr)} {
|
||||
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
|
||||
}
|
||||
|
||||
@ -621,8 +595,7 @@ namespace platf {
|
||||
while (!task_pool.cancel(refresh_task_id));
|
||||
}
|
||||
|
||||
capture_e
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
sleep_overshoot_logger.reset();
|
||||
@ -667,18 +640,16 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
// The whole X server changed, so we must reinit everything
|
||||
if (xattr.width != env_width || xattr.height != env_height) {
|
||||
BOOST_LOG(warning) << "X dimensions changed in SHM mode, request reinit"sv;
|
||||
return capture_e::reinit;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
auto img_cookie = xcb::shm_get_image_unchecked(xcb.get(), display->root, offset_x, offset_y, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0);
|
||||
auto frame_timestamp = std::chrono::steady_clock::now();
|
||||
|
||||
xcb_img_t img_reply { xcb::shm_get_image_reply(xcb.get(), img_cookie, nullptr) };
|
||||
xcb_img_t img_reply {xcb::shm_get_image_reply(xcb.get(), img_cookie, nullptr)};
|
||||
if (!img_reply) {
|
||||
BOOST_LOG(error) << "Could not get image reply"sv;
|
||||
return capture_e::reinit;
|
||||
@ -699,8 +670,7 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<img_t>
|
||||
alloc_img() override {
|
||||
std::shared_ptr<img_t> alloc_img() override {
|
||||
auto img = std::make_shared<shm_img_t>();
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
@ -711,13 +681,11 @@ namespace platf {
|
||||
return img;
|
||||
}
|
||||
|
||||
int
|
||||
dummy_img(platf::img_t *img) override {
|
||||
int dummy_img(platf::img_t *img) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
init(const std::string &display_name, const ::video::config_t &config) {
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
if (x11_attr_t::init(display_name, config)) {
|
||||
return 1;
|
||||
}
|
||||
@ -756,14 +724,12 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
frame_size() {
|
||||
std::uint32_t frame_size() {
|
||||
return width * height * 4;
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<display_t>
|
||||
x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
std::shared_ptr<display_t> x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
|
||||
BOOST_LOG(error) << "Could not initialize x11 display with the given hw device type"sv;
|
||||
return nullptr;
|
||||
@ -797,8 +763,7 @@ namespace platf {
|
||||
return x11_disp;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
x11_display_names() {
|
||||
std::vector<std::string> x11_display_names() {
|
||||
if (load_x11() || load_xcb()) {
|
||||
BOOST_LOG(error) << "Couldn't init x11 libraries"sv;
|
||||
|
||||
@ -807,18 +772,18 @@ namespace platf {
|
||||
|
||||
BOOST_LOG(info) << "Detecting displays"sv;
|
||||
|
||||
x11::xdisplay_t xdisplay { x11::OpenDisplay(nullptr) };
|
||||
x11::xdisplay_t xdisplay {x11::OpenDisplay(nullptr)};
|
||||
if (!xdisplay) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto xwindow = DefaultRootWindow(xdisplay.get());
|
||||
screen_res_t screenr { x11::rr::GetScreenResources(xdisplay.get(), xwindow) };
|
||||
screen_res_t screenr {x11::rr::GetScreenResources(xdisplay.get(), xwindow)};
|
||||
int output = screenr->noutput;
|
||||
|
||||
int monitor = 0;
|
||||
for (int x = 0; x < output; ++x) {
|
||||
output_info_t out_info { x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) };
|
||||
output_info_t out_info {x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x])};
|
||||
if (out_info) {
|
||||
BOOST_LOG(info) << "Detected display: "sv << out_info->name << " (id: "sv << monitor << ")"sv << out_info->name << " connected: "sv << (out_info->connection == RR_Connected);
|
||||
++monitor;
|
||||
@ -835,25 +800,22 @@ namespace platf {
|
||||
return names;
|
||||
}
|
||||
|
||||
void
|
||||
freeImage(XImage *p) {
|
||||
void freeImage(XImage *p) {
|
||||
XDestroyImage(p);
|
||||
}
|
||||
void
|
||||
freeX(XFixesCursorImage *p) {
|
||||
|
||||
void freeX(XFixesCursorImage *p) {
|
||||
x11::Free(p);
|
||||
}
|
||||
|
||||
int
|
||||
load_xcb() {
|
||||
int load_xcb() {
|
||||
// This will be called once only
|
||||
static int xcb_status = xcb::init_shm() || xcb::init();
|
||||
|
||||
return xcb_status;
|
||||
}
|
||||
|
||||
int
|
||||
load_x11() {
|
||||
int load_x11() {
|
||||
// This will be called once only
|
||||
static int x11_status =
|
||||
window_system == window_system_e::NONE ||
|
||||
@ -863,8 +825,7 @@ namespace platf {
|
||||
}
|
||||
|
||||
namespace x11 {
|
||||
std::optional<cursor_t>
|
||||
cursor_t::make() {
|
||||
std::optional<cursor_t> cursor_t::make() {
|
||||
if (load_x11()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -876,8 +837,7 @@ namespace platf {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void
|
||||
cursor_t::capture(egl::cursor_t &img) {
|
||||
void cursor_t::capture(egl::cursor_t &img) {
|
||||
auto display = (xdisplay_t::pointer) ctx.get();
|
||||
|
||||
xcursor_t xcursor = fix::GetCursorImage(display);
|
||||
@ -904,23 +864,19 @@ namespace platf {
|
||||
img.serial = xcursor->cursor_serial;
|
||||
}
|
||||
|
||||
void
|
||||
cursor_t::blend(img_t &img, int offsetX, int offsetY) {
|
||||
void cursor_t::blend(img_t &img, int offsetX, int offsetY) {
|
||||
blend_cursor((xdisplay_t::pointer) ctx.get(), img, offsetX, offsetY);
|
||||
}
|
||||
|
||||
xdisplay_t
|
||||
make_display() {
|
||||
xdisplay_t make_display() {
|
||||
return OpenDisplay(nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
freeDisplay(_XDisplay *xdisplay) {
|
||||
void freeDisplay(_XDisplay *xdisplay) {
|
||||
CloseDisplay(xdisplay);
|
||||
}
|
||||
|
||||
void
|
||||
freeCursorCtx(cursor_ctx_t::pointer ctx) {
|
||||
void freeCursorCtx(cursor_ctx_t::pointer ctx) {
|
||||
CloseDisplay((xdisplay_t::pointer) ctx);
|
||||
}
|
||||
} // namespace x11
|
||||
|
@ -4,8 +4,10 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <optional>
|
||||
|
||||
// local includes
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
@ -18,21 +20,17 @@ namespace egl {
|
||||
|
||||
namespace platf::x11 {
|
||||
struct cursor_ctx_raw_t;
|
||||
void
|
||||
freeCursorCtx(cursor_ctx_raw_t *ctx);
|
||||
void
|
||||
freeDisplay(_XDisplay *xdisplay);
|
||||
void freeCursorCtx(cursor_ctx_raw_t *ctx);
|
||||
void freeDisplay(_XDisplay *xdisplay);
|
||||
|
||||
using cursor_ctx_t = util::safe_ptr<cursor_ctx_raw_t, freeCursorCtx>;
|
||||
using xdisplay_t = util::safe_ptr<_XDisplay, freeDisplay>;
|
||||
|
||||
class cursor_t {
|
||||
public:
|
||||
static std::optional<cursor_t>
|
||||
make();
|
||||
static std::optional<cursor_t> make();
|
||||
|
||||
void
|
||||
capture(egl::cursor_t &img);
|
||||
void capture(egl::cursor_t &img);
|
||||
|
||||
/**
|
||||
* Capture and blend the cursor into the image
|
||||
@ -40,12 +38,10 @@ namespace platf::x11 {
|
||||
* img <-- destination image
|
||||
* offsetX, offsetY <--- Top left corner of the virtual screen
|
||||
*/
|
||||
void
|
||||
blend(img_t &img, int offsetX, int offsetY);
|
||||
void blend(img_t &img, int offsetX, int offsetY);
|
||||
|
||||
cursor_ctx_t ctx;
|
||||
};
|
||||
|
||||
xdisplay_t
|
||||
make_display();
|
||||
xdisplay_t make_display();
|
||||
} // namespace platf::x11
|
||||
|
@ -4,8 +4,10 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// platform includes
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
// lib includes
|
||||
#include "third-party/TPCircularBuffer/TPCircularBuffer.h"
|
||||
|
||||
#define kBufferLength 4096
|
||||
|
@ -2,12 +2,13 @@
|
||||
* @file src/platform/macos/av_audio.m
|
||||
* @brief Definitions for audio capture on macOS.
|
||||
*/
|
||||
// local includes
|
||||
#import "av_audio.h"
|
||||
|
||||
@implementation AVAudio
|
||||
|
||||
+ (NSArray<AVCaptureDevice *> *)microphones {
|
||||
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) { 10, 15, 0 })]) {
|
||||
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) {10, 15, 0})]) {
|
||||
// This will generate a warning about AVCaptureDeviceDiscoverySession being
|
||||
// unavailable before macOS 10.15, but we have a guard to prevent it from
|
||||
// being called on those earlier systems.
|
||||
@ -16,14 +17,12 @@
|
||||
// a different method.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
|
||||
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInMicrophone,
|
||||
AVCaptureDeviceTypeExternalUnknown]
|
||||
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInMicrophone, AVCaptureDeviceTypeExternalUnknown]
|
||||
mediaType:AVMediaTypeAudio
|
||||
position:AVCaptureDevicePositionUnspecified];
|
||||
return discoverySession.devices;
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// We're intentionally using a deprecated API here specifically for versions
|
||||
// of macOS where it's not deprecated, so we can ignore any deprecation
|
||||
// warnings:
|
||||
@ -75,8 +74,7 @@
|
||||
|
||||
if ([self.audioCaptureSession canAddInput:audioInput]) {
|
||||
[self.audioCaptureSession addInput:audioInput];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
[audioInput dealloc];
|
||||
return -1;
|
||||
}
|
||||
@ -92,17 +90,14 @@
|
||||
(NSString *) AVLinearPCMIsNonInterleaved: @NO
|
||||
}];
|
||||
|
||||
dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,
|
||||
QOS_CLASS_USER_INITIATED,
|
||||
DISPATCH_QUEUE_PRIORITY_HIGH);
|
||||
dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INITIATED, DISPATCH_QUEUE_PRIORITY_HIGH);
|
||||
dispatch_queue_t recordingQueue = dispatch_queue_create("audioSamplingQueue", qos);
|
||||
|
||||
[audioOutput setSampleBufferDelegate:self queue:recordingQueue];
|
||||
|
||||
if ([self.audioCaptureSession canAddOutput:audioOutput]) {
|
||||
[self.audioCaptureSession addOutput:audioOutput];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
[audioInput release];
|
||||
[audioOutput release];
|
||||
return -1;
|
||||
|
@ -4,17 +4,20 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "src/platform/common.h"
|
||||
|
||||
// platform includes
|
||||
#include <CoreMedia/CoreMedia.h>
|
||||
#include <CoreVideo/CoreVideo.h>
|
||||
|
||||
// local includes
|
||||
#include "src/platform/common.h"
|
||||
|
||||
namespace platf {
|
||||
struct av_sample_buf_t {
|
||||
CMSampleBufferRef buf;
|
||||
|
||||
explicit av_sample_buf_t(CMSampleBufferRef buf):
|
||||
buf((CMSampleBufferRef) CFRetain(buf)) {}
|
||||
buf((CMSampleBufferRef) CFRetain(buf)) {
|
||||
}
|
||||
|
||||
~av_sample_buf_t() {
|
||||
if (buf != nullptr) {
|
||||
@ -29,12 +32,12 @@ namespace platf {
|
||||
// Constructor
|
||||
explicit av_pixel_buf_t(CMSampleBufferRef sb):
|
||||
buf(
|
||||
CMSampleBufferGetImageBuffer(sb)) {
|
||||
CMSampleBufferGetImageBuffer(sb)
|
||||
) {
|
||||
CVPixelBufferLockBaseAddress(buf, kCVPixelBufferLock_ReadOnly);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint8_t *
|
||||
data() const {
|
||||
[[nodiscard]] uint8_t *data() const {
|
||||
return static_cast<uint8_t *>(CVPixelBufferGetBaseAddress(buf));
|
||||
}
|
||||
|
||||
@ -59,8 +62,11 @@ namespace platf {
|
||||
temp_retain_av_img_t(
|
||||
std::shared_ptr<av_sample_buf_t> sb,
|
||||
std::shared_ptr<av_pixel_buf_t> pb,
|
||||
uint8_t *dt):
|
||||
uint8_t *dt
|
||||
):
|
||||
sample_buffer(std::move(sb)),
|
||||
pixel_buffer(std::move(pb)), data(dt) {}
|
||||
pixel_buffer(std::move(pb)),
|
||||
data(dt) {
|
||||
}
|
||||
};
|
||||
} // namespace platf
|
||||
|
@ -4,8 +4,9 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
// platform includes
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
struct CaptureSession {
|
||||
AVCaptureVideoDataOutput *output;
|
||||
|
@ -2,6 +2,7 @@
|
||||
* @file src/platform/macos/av_video.m
|
||||
* @brief Definitions for video capture on macOS.
|
||||
*/
|
||||
// local includes
|
||||
#import "av_video.h"
|
||||
|
||||
@implementation AVVideo
|
||||
@ -62,8 +63,7 @@
|
||||
|
||||
if ([self.session canAddInput:screenInput]) {
|
||||
[self.session addInput:screenInput];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
[screenInput release];
|
||||
return nil;
|
||||
}
|
||||
@ -97,9 +97,7 @@
|
||||
(NSString *) AVVideoScalingModeKey: AVVideoScalingModeResizeAspect,
|
||||
}];
|
||||
|
||||
dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL,
|
||||
QOS_CLASS_USER_INITIATED,
|
||||
DISPATCH_QUEUE_PRIORITY_HIGH);
|
||||
dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, DISPATCH_QUEUE_PRIORITY_HIGH);
|
||||
dispatch_queue_t recordingQueue = dispatch_queue_create("videoCaptureQueue", qos);
|
||||
[videoOutput setSampleBufferDelegate:self queue:recordingQueue];
|
||||
|
||||
@ -107,8 +105,7 @@
|
||||
|
||||
if ([self.session canAddOutput:videoOutput]) {
|
||||
[self.session addOutput:videoOutput];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
[videoOutput release];
|
||||
return nil;
|
||||
}
|
||||
|
@ -2,15 +2,15 @@
|
||||
* @file src/platform/macos/display.mm
|
||||
* @brief Definitions for display capture on macOS.
|
||||
*/
|
||||
// local includes
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/platform/macos/av_img_t.h"
|
||||
#include "src/platform/macos/av_video.h"
|
||||
#include "src/platform/macos/misc.h"
|
||||
#include "src/platform/macos/nv12_zero_device.h"
|
||||
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
|
||||
// Avoid conflict between AVFoundation and libavutil both defining AVMediaType
|
||||
#define AVMediaType AVMediaType_FFmpeg
|
||||
#include "src/video.h"
|
||||
@ -29,8 +29,7 @@ namespace platf {
|
||||
[av_capture release];
|
||||
}
|
||||
|
||||
capture_e
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) {
|
||||
auto new_sample_buffer = std::make_shared<av_sample_buf_t>(sampleBuffer);
|
||||
auto new_pixel_buffer = std::make_shared<av_pixel_buf_t>(new_sample_buffer->buf);
|
||||
@ -46,7 +45,8 @@ namespace platf {
|
||||
auto old_data_retainer = std::make_shared<temp_retain_av_img_t>(
|
||||
av_img->sample_buffer,
|
||||
av_img->pixel_buffer,
|
||||
img_out->data);
|
||||
img_out->data
|
||||
);
|
||||
|
||||
av_img->sample_buffer = new_sample_buffer;
|
||||
av_img->pixel_buffer = new_pixel_buffer;
|
||||
@ -74,33 +74,28 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
std::shared_ptr<img_t>
|
||||
alloc_img() override {
|
||||
std::shared_ptr<img_t> alloc_img() override {
|
||||
return std::make_shared<av_img_t>();
|
||||
}
|
||||
|
||||
std::unique_ptr<avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
|
||||
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
|
||||
if (pix_fmt == pix_fmt_e::yuv420p) {
|
||||
av_capture.pixelFormat = kCVPixelFormatType_32BGRA;
|
||||
|
||||
return std::make_unique<avcodec_encode_device_t>();
|
||||
}
|
||||
else if (pix_fmt == pix_fmt_e::nv12 || pix_fmt == pix_fmt_e::p010) {
|
||||
} else if (pix_fmt == pix_fmt_e::nv12 || pix_fmt == pix_fmt_e::p010) {
|
||||
auto device = std::make_unique<nv12_zero_device>();
|
||||
|
||||
device->init(static_cast<void *>(av_capture), pix_fmt, setResolution, setPixelFormat);
|
||||
|
||||
return device;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(error) << "Unsupported Pixel Format."sv;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
dummy_img(img_t *img) override {
|
||||
int dummy_img(img_t *img) override {
|
||||
if (!platf::is_screen_capture_allowed()) {
|
||||
// If we don't have the screen capture permission, this function will hang
|
||||
// indefinitely without doing anything useful. Exit instead to avoid this.
|
||||
@ -117,7 +112,8 @@ namespace platf {
|
||||
auto old_data_retainer = std::make_shared<temp_retain_av_img_t>(
|
||||
av_img->sample_buffer,
|
||||
av_img->pixel_buffer,
|
||||
img->data);
|
||||
img->data
|
||||
);
|
||||
|
||||
av_img->sample_buffer = new_sample_buffer;
|
||||
av_img->pixel_buffer = new_pixel_buffer;
|
||||
@ -146,19 +142,16 @@ namespace platf {
|
||||
* width --> the intended capture width
|
||||
* height --> the intended capture height
|
||||
*/
|
||||
static void
|
||||
setResolution(void *display, int width, int height) {
|
||||
static void setResolution(void *display, int width, int height) {
|
||||
[static_cast<AVVideo *>(display) setFrameWidth:width frameHeight:height];
|
||||
}
|
||||
|
||||
static void
|
||||
setPixelFormat(void *display, OSType pixelFormat) {
|
||||
static void setPixelFormat(void *display, OSType pixelFormat) {
|
||||
static_cast<AVVideo *>(display).pixelFormat = pixelFormat;
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<display_t>
|
||||
display(platf::mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
std::shared_ptr<display_t> display(platf::mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::videotoolbox) {
|
||||
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
|
||||
return nullptr;
|
||||
@ -200,8 +193,7 @@ namespace platf {
|
||||
return display;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
display_names(mem_type_e hwdevice_type) {
|
||||
std::vector<std::string> display_names(mem_type_e hwdevice_type) {
|
||||
__block std::vector<std::string> display_names;
|
||||
|
||||
auto display_array = [AVVideo displayNames];
|
||||
@ -219,8 +211,7 @@ namespace platf {
|
||||
* @brief Returns if GPUs/drivers have changed since the last call to this function.
|
||||
* @return `true` if a change has occurred or if it is unknown whether a change occurred.
|
||||
*/
|
||||
bool
|
||||
needs_encoder_reenumeration() {
|
||||
bool needs_encoder_reenumeration() {
|
||||
// We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on macOS.
|
||||
return true;
|
||||
}
|
||||
|
@ -2,22 +2,24 @@
|
||||
* @file src/platform/macos/input.cpp
|
||||
* @brief Definitions for macOS input handling.
|
||||
*/
|
||||
#include "src/input.h"
|
||||
|
||||
#import <Carbon/Carbon.h>
|
||||
// standard includes
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
// platform includes
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#import <Carbon/Carbon.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
// local includes
|
||||
#include "src/display_device.h"
|
||||
#include "src/input.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
/**
|
||||
* @brief Delay for a double click, in milliseconds.
|
||||
* @todo Make this configurable.
|
||||
@ -50,8 +52,7 @@ namespace platf {
|
||||
};
|
||||
|
||||
// Customized less operator for using std::lower_bound() on a KeyCodeMap array.
|
||||
bool
|
||||
operator<(const KeyCodeMap &a, const KeyCodeMap &b) {
|
||||
bool operator<(const KeyCodeMap &a, const KeyCodeMap &b) {
|
||||
return a.win_keycode < b.win_keycode;
|
||||
}
|
||||
|
||||
@ -227,13 +228,15 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
int
|
||||
keysym(int keycode) {
|
||||
int keysym(int keycode) {
|
||||
KeyCodeMap key_map {};
|
||||
|
||||
key_map.win_keycode = keycode;
|
||||
const KeyCodeMap *temp_map = std::lower_bound(
|
||||
kKeyCodesMap, kKeyCodesMap + sizeof(kKeyCodesMap) / sizeof(kKeyCodesMap[0]), key_map);
|
||||
kKeyCodesMap,
|
||||
kKeyCodesMap + sizeof(kKeyCodesMap) / sizeof(kKeyCodesMap[0]),
|
||||
key_map
|
||||
);
|
||||
|
||||
if (temp_map >= kKeyCodesMap + sizeof(kKeyCodesMap) / sizeof(kKeyCodesMap[0]) ||
|
||||
temp_map->win_keycode != keycode || temp_map->mac_keycode == -1) {
|
||||
@ -243,8 +246,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
return temp_map->mac_keycode;
|
||||
}
|
||||
|
||||
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 key = keysym(modcode);
|
||||
|
||||
BOOST_LOG(debug) << "got keycode: 0x"sv << std::hex << modcode << ", translated to: 0x" << std::hex << key << ", release:" << release;
|
||||
@ -284,8 +286,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
macos_input->kb_flags = release ? macos_input->kb_flags & ~mask : macos_input->kb_flags | mask;
|
||||
CGEventSetType(event, kCGEventFlagsChanged);
|
||||
CGEventSetFlags(event, macos_input->kb_flags);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
CGEventSetIntegerValueField(event, kCGKeyboardEventKeycode, key);
|
||||
CGEventSetType(event, release ? kCGEventKeyUp : kCGEventKeyDown);
|
||||
}
|
||||
@ -293,30 +294,25 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
CGEventPost(kCGHIDEventTap, event);
|
||||
}
|
||||
|
||||
void
|
||||
unicode(input_t &input, char *utf8, int size) {
|
||||
void unicode(input_t &input, char *utf8, int size) {
|
||||
BOOST_LOG(info) << "unicode: Unicode input not yet implemented for MacOS."sv;
|
||||
}
|
||||
|
||||
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) {
|
||||
BOOST_LOG(info) << "alloc_gamepad: Gamepad not yet implemented for MacOS."sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
free_gamepad(input_t &input, int nr) {
|
||||
void free_gamepad(input_t &input, int nr) {
|
||||
BOOST_LOG(info) << "free_gamepad: Gamepad not yet implemented for MacOS."sv;
|
||||
}
|
||||
|
||||
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) {
|
||||
BOOST_LOG(info) << "gamepad: Gamepad not yet implemented for MacOS."sv;
|
||||
}
|
||||
|
||||
// returns current mouse location:
|
||||
util::point_t
|
||||
get_mouse_loc(input_t &input) {
|
||||
util::point_t get_mouse_loc(input_t &input) {
|
||||
// Creating a new event every time to avoid any reuse risk
|
||||
const auto macos_input = static_cast<macos_input_t *>(input.get());
|
||||
const auto snapshot_event = CGEventCreate(macos_input->source);
|
||||
@ -328,14 +324,14 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
post_mouse(
|
||||
void post_mouse(
|
||||
input_t &input,
|
||||
const CGMouseButton button,
|
||||
const CGEventType type,
|
||||
const util::point_t raw_location,
|
||||
const util::point_t previous_location,
|
||||
const int click_count) {
|
||||
const int click_count
|
||||
) {
|
||||
BOOST_LOG(debug) << "mouse_event: "sv << button << ", type: "sv << type << ", location:"sv << raw_location.x << ":"sv << raw_location.y << " click_count: "sv << click_count;
|
||||
|
||||
const auto macos_input = static_cast<macos_input_t *>(input.get());
|
||||
@ -368,8 +364,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
CGWarpMouseCursorPosition(location);
|
||||
}
|
||||
|
||||
inline CGEventType
|
||||
event_type_mouse(input_t &input) {
|
||||
inline CGEventType event_type_mouse(input_t &input) {
|
||||
const auto macos_input = static_cast<macos_input_t *>(input.get());
|
||||
|
||||
if (macos_input->mouse_down[0]) {
|
||||
@ -384,28 +379,28 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
return kCGEventMouseMoved;
|
||||
}
|
||||
|
||||
void
|
||||
move_mouse(
|
||||
void move_mouse(
|
||||
input_t &input,
|
||||
const int deltaX,
|
||||
const int deltaY) {
|
||||
const int deltaY
|
||||
) {
|
||||
const auto current = get_mouse_loc(input);
|
||||
|
||||
const auto location = util::point_t { current.x + deltaX, current.y + deltaY };
|
||||
const auto location = util::point_t {current.x + deltaX, current.y + deltaY};
|
||||
post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, current, 0);
|
||||
}
|
||||
|
||||
void
|
||||
abs_mouse(
|
||||
void abs_mouse(
|
||||
input_t &input,
|
||||
const touch_port_t &touch_port,
|
||||
const float x,
|
||||
const float y) {
|
||||
const float y
|
||||
) {
|
||||
const auto macos_input = static_cast<macos_input_t *>(input.get());
|
||||
const auto scaling = macos_input->displayScaling;
|
||||
const auto display = macos_input->display;
|
||||
|
||||
auto location = util::point_t { x * scaling, y * scaling };
|
||||
auto location = util::point_t {x * scaling, y * scaling};
|
||||
CGRect display_bounds = CGDisplayBounds(display);
|
||||
// in order to get the correct mouse location for capturing display , we need to add the display bounds to the location
|
||||
location.x += display_bounds.origin.x;
|
||||
@ -414,8 +409,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, get_mouse_loc(input), 0);
|
||||
}
|
||||
|
||||
void
|
||||
button_mouse(input_t &input, const int button, const bool release) {
|
||||
void button_mouse(input_t &input, const int button, const bool release) {
|
||||
CGMouseButton mac_button;
|
||||
CGEventType event;
|
||||
|
||||
@ -447,26 +441,26 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
|
||||
if (now < macos_input->last_mouse_event[mac_button][release] + MULTICLICK_DELAY_MS) {
|
||||
post_mouse(input, mac_button, event, mouse_position, mouse_position, 2);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
post_mouse(input, mac_button, event, mouse_position, mouse_position, 1);
|
||||
}
|
||||
|
||||
macos_input->last_mouse_event[mac_button][release] = now;
|
||||
}
|
||||
|
||||
void
|
||||
scroll(input_t &input, const int high_res_distance) {
|
||||
void scroll(input_t &input, const int high_res_distance) {
|
||||
CGEventRef upEvent = CGEventCreateScrollWheelEvent(
|
||||
nullptr,
|
||||
kCGScrollEventUnitLine,
|
||||
2, high_res_distance > 0 ? 1 : -1, high_res_distance);
|
||||
2,
|
||||
high_res_distance > 0 ? 1 : -1,
|
||||
high_res_distance
|
||||
);
|
||||
CGEventPost(kCGHIDEventTap, upEvent);
|
||||
CFRelease(upEvent);
|
||||
}
|
||||
|
||||
void
|
||||
hscroll(input_t &input, int high_res_distance) {
|
||||
void hscroll(input_t &input, int high_res_distance) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
@ -475,8 +469,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
* @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) {
|
||||
// Unused
|
||||
return nullptr;
|
||||
}
|
||||
@ -487,8 +480,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
* @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) {
|
||||
// Unimplemented feature - platform_caps::pen_touch
|
||||
}
|
||||
|
||||
@ -498,8 +490,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
* @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) {
|
||||
// Unimplemented feature - platform_caps::pen_touch
|
||||
}
|
||||
|
||||
@ -508,8 +499,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
* @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) {
|
||||
// Unimplemented feature - platform_caps::controller_touch
|
||||
}
|
||||
|
||||
@ -518,8 +508,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
* @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) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
@ -528,14 +517,12 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
* @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) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
input_t
|
||||
input() {
|
||||
input_t result { new macos_input_t() };
|
||||
input_t input() {
|
||||
input_t result {new macos_input_t()};
|
||||
|
||||
const auto macos_input = static_cast<macos_input_t *>(result.get());
|
||||
|
||||
@ -550,8 +537,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
CGDirectDisplayID displays[max_display];
|
||||
if (CGGetActiveDisplayList(max_display, displays, &display_count) != kCGErrorSuccess) {
|
||||
BOOST_LOG(error) << "Unable to get active display list , error: "sv << std::endl;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
for (int i = 0; i < display_count; i++) {
|
||||
CGDirectDisplayID display_id = displays[i];
|
||||
if (display_id == std::atoi(output_name.c_str())) {
|
||||
@ -581,8 +567,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
freeInput(void *p) {
|
||||
void freeInput(void *p) {
|
||||
const auto *input = static_cast<macos_input_t *>(p);
|
||||
|
||||
CFRelease(input->source);
|
||||
@ -592,10 +577,9 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
delete input;
|
||||
}
|
||||
|
||||
std::vector<supported_gamepad_t> &
|
||||
supported_gamepads(input_t *input) {
|
||||
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
|
||||
static std::vector gamepads {
|
||||
supported_gamepad_t { "", false, "gamepads.macos_not_implemented" }
|
||||
supported_gamepad_t {"", false, "gamepads.macos_not_implemented"}
|
||||
};
|
||||
|
||||
return gamepads;
|
||||
@ -605,8 +589,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
* @brief Returns the supported platform capabilities to advertise to the client.
|
||||
* @return Capability flags.
|
||||
*/
|
||||
platform_caps::caps_t
|
||||
get_capabilities() {
|
||||
platform_caps::caps_t get_capabilities() {
|
||||
return 0;
|
||||
}
|
||||
} // namespace platf
|
||||
|
@ -2,11 +2,11 @@
|
||||
* @file src/platform/macos/microphone.mm
|
||||
* @brief Definitions for microphone capture on macOS.
|
||||
*/
|
||||
#include "src/platform/common.h"
|
||||
#include "src/platform/macos/av_audio.h"
|
||||
|
||||
// local includes
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/platform/macos/av_audio.h"
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
@ -18,8 +18,7 @@ namespace platf {
|
||||
[av_audio_capture release];
|
||||
}
|
||||
|
||||
capture_e
|
||||
sample(std::vector<float> &sample_in) override {
|
||||
capture_e sample(std::vector<float> &sample_in) override {
|
||||
auto sample_size = sample_in.size();
|
||||
|
||||
uint32_t length = 0;
|
||||
@ -45,14 +44,12 @@ namespace platf {
|
||||
AVCaptureDevice *audio_capture_device {};
|
||||
|
||||
public:
|
||||
int
|
||||
set_sink(const std::string &sink) override {
|
||||
int set_sink(const std::string &sink) override {
|
||||
BOOST_LOG(warning) << "audio_control_t::set_sink() unimplemented: "sv << sink;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 {
|
||||
auto mic = std::make_unique<av_mic_t>();
|
||||
const char *audio_sink = "";
|
||||
|
||||
@ -81,22 +78,19 @@ namespace platf {
|
||||
return mic;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::optional<sink_t>
|
||||
sink_info() override {
|
||||
std::optional<sink_t> sink_info() override {
|
||||
sink_t sink;
|
||||
|
||||
return sink;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<audio_control_t>
|
||||
audio_control() {
|
||||
std::unique_ptr<audio_control_t> audio_control() {
|
||||
return std::make_unique<macos_audio_control_t>();
|
||||
}
|
||||
} // namespace platf
|
||||
|
@ -4,21 +4,20 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <vector>
|
||||
|
||||
// platform includes
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
namespace platf {
|
||||
bool
|
||||
is_screen_capture_allowed();
|
||||
bool is_screen_capture_allowed();
|
||||
}
|
||||
|
||||
namespace dyn {
|
||||
typedef void (*apiproc)();
|
||||
|
||||
int
|
||||
load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
|
||||
void *
|
||||
handle(const std::vector<const char *> &libs);
|
||||
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
|
||||
void *handle(const std::vector<const char *> &libs);
|
||||
|
||||
} // namespace dyn
|
||||
|
@ -8,24 +8,29 @@
|
||||
#define __APPLE_USE_RFC_3542 1
|
||||
#endif
|
||||
|
||||
#include <Foundation/Foundation.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <dlfcn.h>
|
||||
// standard includes
|
||||
#include <fcntl.h>
|
||||
#include <ifaddrs.h>
|
||||
|
||||
// platform includes
|
||||
#include <arpa/inet.h>
|
||||
#include <dlfcn.h>
|
||||
#include <Foundation/Foundation.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <pwd.h>
|
||||
|
||||
// lib includes
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/host_name.hpp>
|
||||
#include <boost/process/v1.hpp>
|
||||
|
||||
// local includes
|
||||
#include "misc.h"
|
||||
#include "src/entry_handler.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/host_name.hpp>
|
||||
#include <boost/process/v1.hpp>
|
||||
|
||||
using namespace std::literals;
|
||||
namespace fs = std::filesystem;
|
||||
namespace bp = boost::process;
|
||||
@ -37,24 +42,20 @@ namespace platf {
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 110000 // __MAC_11_0
|
||||
// If they're not in the SDK then we can use our own function definitions.
|
||||
// Need to use weak import so that this will link in macOS 10.14 and earlier
|
||||
extern "C" bool
|
||||
CGPreflightScreenCaptureAccess(void) __attribute__((weak_import));
|
||||
extern "C" bool
|
||||
CGRequestScreenCaptureAccess(void) __attribute__((weak_import));
|
||||
extern "C" bool CGPreflightScreenCaptureAccess(void) __attribute__((weak_import));
|
||||
extern "C" bool CGRequestScreenCaptureAccess(void) __attribute__((weak_import));
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
auto screen_capture_allowed = std::atomic<bool> { false };
|
||||
auto screen_capture_allowed = std::atomic<bool> {false};
|
||||
} // namespace
|
||||
|
||||
// Return whether screen capture is allowed for this process.
|
||||
bool
|
||||
is_screen_capture_allowed() {
|
||||
bool is_screen_capture_allowed() {
|
||||
return screen_capture_allowed;
|
||||
}
|
||||
|
||||
std::unique_ptr<deinit_t>
|
||||
init() {
|
||||
std::unique_ptr<deinit_t> init() {
|
||||
// This will generate a warning about CGPreflightScreenCaptureAccess and
|
||||
// CGRequestScreenCaptureAccess being unavailable before macOS 10.15, but
|
||||
// we have a guard to prevent it from being called on those earlier systems.
|
||||
@ -69,7 +70,7 @@ namespace platf {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
|
||||
#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
|
||||
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) { 10, 15, 0 })] &&
|
||||
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) {10, 15, 0})] &&
|
||||
// Double check that these weakly-linked symbols have been loaded:
|
||||
CGPreflightScreenCaptureAccess != nullptr && CGRequestScreenCaptureAccess != nullptr &&
|
||||
!CGPreflightScreenCaptureAccess()) {
|
||||
@ -84,66 +85,55 @@ namespace platf {
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
fs::path
|
||||
appdata() {
|
||||
fs::path appdata() {
|
||||
const char *homedir;
|
||||
if ((homedir = getenv("HOME")) == nullptr) {
|
||||
homedir = getpwuid(geteuid())->pw_dir;
|
||||
}
|
||||
|
||||
return fs::path { homedir } / ".config/sunshine"sv;
|
||||
return fs::path {homedir} / ".config/sunshine"sv;
|
||||
}
|
||||
|
||||
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
|
||||
|
||||
ifaddr_t
|
||||
get_ifaddrs() {
|
||||
ifaddrs *p { nullptr };
|
||||
ifaddr_t get_ifaddrs() {
|
||||
ifaddrs *p {nullptr};
|
||||
|
||||
getifaddrs(&p);
|
||||
|
||||
return ifaddr_t { p };
|
||||
return ifaddr_t {p};
|
||||
}
|
||||
|
||||
std::string
|
||||
from_sockaddr(const sockaddr *const ip_addr) {
|
||||
std::string from_sockaddr(const sockaddr *const ip_addr) {
|
||||
char data[INET6_ADDRSTRLEN] = {};
|
||||
|
||||
auto family = ip_addr->sa_family;
|
||||
if (family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data,
|
||||
INET6_ADDRSTRLEN);
|
||||
}
|
||||
else if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data,
|
||||
INET_ADDRSTRLEN);
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
|
||||
} else if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
|
||||
}
|
||||
|
||||
return std::string { data };
|
||||
return std::string {data};
|
||||
}
|
||||
|
||||
std::pair<std::uint16_t, std::string>
|
||||
from_sockaddr_ex(const sockaddr *const ip_addr) {
|
||||
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
|
||||
char data[INET6_ADDRSTRLEN] = {};
|
||||
|
||||
auto family = ip_addr->sa_family;
|
||||
std::uint16_t port = 0;
|
||||
if (family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data,
|
||||
INET6_ADDRSTRLEN);
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
|
||||
port = ((sockaddr_in6 *) ip_addr)->sin6_port;
|
||||
}
|
||||
else if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data,
|
||||
INET_ADDRSTRLEN);
|
||||
} else if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
|
||||
port = ((sockaddr_in *) ip_addr)->sin_port;
|
||||
}
|
||||
|
||||
return { port, std::string { data } };
|
||||
return {port, std::string {data}};
|
||||
}
|
||||
|
||||
std::string
|
||||
get_mac_address(const std::string_view &address) {
|
||||
std::string get_mac_address(const std::string_view &address) {
|
||||
auto ifaddrs = get_ifaddrs();
|
||||
|
||||
for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
|
||||
@ -160,8 +150,7 @@ namespace platf {
|
||||
ptr = (unsigned char *) LLADDR((struct sockaddr_dl *) (ifaptr)->ifa_addr);
|
||||
char buff[100];
|
||||
|
||||
snprintf(buff, sizeof(buff), "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
*ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4), *(ptr + 5));
|
||||
snprintf(buff, sizeof(buff), "%02x:%02x:%02x:%02x:%02x:%02x", *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4), *(ptr + 5));
|
||||
mac_address = buff;
|
||||
break;
|
||||
}
|
||||
@ -181,8 +170,7 @@ namespace platf {
|
||||
return "00:00:00:00:00:00"s;
|
||||
}
|
||||
|
||||
bp::child
|
||||
run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
|
||||
bp::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
|
||||
// clang-format off
|
||||
if (!group) {
|
||||
if (!file) {
|
||||
@ -207,8 +195,7 @@ namespace platf {
|
||||
* @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) {
|
||||
boost::filesystem::path working_dir;
|
||||
std::string cmd = R"(open ")" + url + R"(")";
|
||||
|
||||
@ -217,30 +204,25 @@ namespace platf {
|
||||
auto child = run_command(false, false, cmd, working_dir, _env, nullptr, ec, nullptr);
|
||||
if (ec) {
|
||||
BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(info) << "Opened url ["sv << url << "]"sv;
|
||||
child.detach();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
adjust_thread_priority(thread_priority_e priority) {
|
||||
void adjust_thread_priority(thread_priority_e priority) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
void
|
||||
streaming_will_start() {
|
||||
void streaming_will_start() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
void
|
||||
streaming_will_stop() {
|
||||
void streaming_will_stop() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
void
|
||||
restart_on_exit() {
|
||||
void restart_on_exit() {
|
||||
char executable[2048];
|
||||
uint32_t size = sizeof(executable);
|
||||
if (_NSGetExecutablePath(executable, &size) < 0) {
|
||||
@ -261,42 +243,35 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
restart() {
|
||||
void restart() {
|
||||
// Gracefully clean up and restart ourselves instead of exiting
|
||||
atexit(restart_on_exit);
|
||||
lifetime::exit_sunshine(0, true);
|
||||
}
|
||||
|
||||
int
|
||||
set_env(const std::string &name, const std::string &value) {
|
||||
int set_env(const std::string &name, const std::string &value) {
|
||||
return setenv(name.c_str(), value.c_str(), 1);
|
||||
}
|
||||
|
||||
int
|
||||
unset_env(const std::string &name) {
|
||||
int unset_env(const std::string &name) {
|
||||
return unsetenv(name.c_str());
|
||||
}
|
||||
|
||||
bool
|
||||
request_process_group_exit(std::uintptr_t native_handle) {
|
||||
bool request_process_group_exit(std::uintptr_t native_handle) {
|
||||
if (killpg((pid_t) native_handle, SIGTERM) == 0 || errno == ESRCH) {
|
||||
BOOST_LOG(debug) << "Successfully sent SIGTERM to process group: "sv << native_handle;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "Unable to send SIGTERM to process group ["sv << native_handle << "]: "sv << errno;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
process_group_running(std::uintptr_t native_handle) {
|
||||
bool process_group_running(std::uintptr_t native_handle) {
|
||||
return waitpid(-((pid_t) native_handle), nullptr, WNOHANG) >= 0;
|
||||
}
|
||||
|
||||
struct sockaddr_in
|
||||
to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
|
||||
struct sockaddr_in to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
|
||||
struct sockaddr_in saddr_v4 = {};
|
||||
|
||||
saddr_v4.sin_family = AF_INET;
|
||||
@ -308,8 +283,7 @@ namespace platf {
|
||||
return saddr_v4;
|
||||
}
|
||||
|
||||
struct sockaddr_in6
|
||||
to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
|
||||
struct sockaddr_in6 to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
|
||||
struct sockaddr_in6 saddr_v6 = {};
|
||||
|
||||
saddr_v6.sin6_family = AF_INET6;
|
||||
@ -322,14 +296,12 @@ namespace platf {
|
||||
return saddr_v6;
|
||||
}
|
||||
|
||||
bool
|
||||
send_batch(batched_send_info_t &send_info) {
|
||||
bool send_batch(batched_send_info_t &send_info) {
|
||||
// Fall back to unbatched send calls
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
send(send_info_t &send_info) {
|
||||
bool send(send_info_t &send_info) {
|
||||
auto sockfd = (int) send_info.native_socket;
|
||||
struct msghdr msg = {};
|
||||
|
||||
@ -341,8 +313,7 @@ namespace platf {
|
||||
|
||||
msg.msg_name = (struct sockaddr *) &taddr_v6;
|
||||
msg.msg_namelen = sizeof(taddr_v6);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
|
||||
|
||||
msg.msg_name = (struct sockaddr *) &taddr_v4;
|
||||
@ -353,6 +324,7 @@ namespace platf {
|
||||
char buf[std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
|
||||
struct cmsghdr alignment;
|
||||
} cmbuf {};
|
||||
|
||||
socklen_t cmbuflen = 0;
|
||||
|
||||
msg.msg_control = cmbuf.buf;
|
||||
@ -372,8 +344,7 @@ namespace platf {
|
||||
pktinfo_cm->cmsg_type = IPV6_PKTINFO;
|
||||
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo));
|
||||
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
struct in_pktinfo pktInfo {};
|
||||
|
||||
struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0);
|
||||
@ -438,7 +409,8 @@ namespace platf {
|
||||
class qos_t: public deinit_t {
|
||||
public:
|
||||
qos_t(int sockfd, std::vector<std::tuple<int, int, int>> options):
|
||||
sockfd(sockfd), options(options) {
|
||||
sockfd(sockfd),
|
||||
options(options) {
|
||||
qos_ref_count++;
|
||||
}
|
||||
|
||||
@ -466,8 +438,7 @@ 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) {
|
||||
int sockfd = (int) native_socket;
|
||||
std::vector<std::tuple<int, int, int>> reset_options;
|
||||
|
||||
@ -489,8 +460,7 @@ namespace platf {
|
||||
if (setsockopt(sockfd, SOL_SOCKET, SO_NET_SERVICE_TYPE, &service_type, sizeof(service_type)) == 0) {
|
||||
// Reset SO_NET_SERVICE_TYPE to best-effort when QoS is disabled
|
||||
reset_options.emplace_back(std::make_tuple(SOL_SOCKET, SO_NET_SERVICE_TYPE, NET_SERVICE_TYPE_BE));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(error) << "Failed to set SO_NET_SERVICE_TYPE: "sv << errno;
|
||||
}
|
||||
}
|
||||
@ -501,8 +471,7 @@ namespace platf {
|
||||
if (address.is_v6()) {
|
||||
level = IPPROTO_IPV6;
|
||||
option = IPV6_TCLASS;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
level = IPPROTO_IP;
|
||||
option = IP_TOS;
|
||||
}
|
||||
@ -529,8 +498,7 @@ namespace platf {
|
||||
if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) == 0) {
|
||||
// Reset TOS to -1 when QoS is disabled
|
||||
reset_options.emplace_back(std::make_tuple(level, option, -1));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(error) << "Failed to set TOS/TCLASS: "sv << errno;
|
||||
}
|
||||
}
|
||||
@ -539,12 +507,10 @@ namespace platf {
|
||||
return std::make_unique<qos_t>(sockfd, reset_options);
|
||||
}
|
||||
|
||||
std::string
|
||||
get_host_name() {
|
||||
std::string get_host_name() {
|
||||
try {
|
||||
return boost::asio::ip::host_name();
|
||||
}
|
||||
catch (boost::system::system_error &err) {
|
||||
} catch (boost::system::system_error &err) {
|
||||
BOOST_LOG(error) << "Failed to get hostname: "sv << err.what();
|
||||
return "Sunshine"s;
|
||||
}
|
||||
@ -552,8 +518,7 @@ namespace platf {
|
||||
|
||||
class macos_high_precision_timer: public high_precision_timer {
|
||||
public:
|
||||
void
|
||||
sleep_for(const std::chrono::nanoseconds &duration) override {
|
||||
void sleep_for(const std::chrono::nanoseconds &duration) override {
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
|
||||
@ -562,15 +527,13 @@ namespace platf {
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<high_precision_timer>
|
||||
create_high_precision_timer() {
|
||||
std::unique_ptr<high_precision_timer> create_high_precision_timer() {
|
||||
return std::make_unique<macos_high_precision_timer>();
|
||||
}
|
||||
} // namespace platf
|
||||
|
||||
namespace dyn {
|
||||
void *
|
||||
handle(const std::vector<const char *> &libs) {
|
||||
void *handle(const std::vector<const char *> &libs) {
|
||||
void *handle;
|
||||
|
||||
for (auto lib : libs) {
|
||||
@ -593,8 +556,7 @@ namespace dyn {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int
|
||||
load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
|
||||
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
|
||||
int err = 0;
|
||||
for (auto &func : funcs) {
|
||||
TUPLE_2D_REF(fn, name, func);
|
||||
|
@ -2,11 +2,12 @@
|
||||
* @file src/platform/macos/nv12_zero_device.cpp
|
||||
* @brief Definitions for NV12 zero copy device on macOS.
|
||||
*/
|
||||
// standard includes
|
||||
#include <utility>
|
||||
|
||||
// local includes
|
||||
#include "src/platform/macos/av_img_t.h"
|
||||
#include "src/platform/macos/nv12_zero_device.h"
|
||||
|
||||
#include "src/video.h"
|
||||
|
||||
extern "C" {
|
||||
@ -15,20 +16,17 @@ extern "C" {
|
||||
|
||||
namespace platf {
|
||||
|
||||
void
|
||||
free_frame(AVFrame *frame) {
|
||||
void free_frame(AVFrame *frame) {
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
void
|
||||
free_buffer(void *opaque, uint8_t *data) {
|
||||
void free_buffer(void *opaque, uint8_t *data) {
|
||||
CVPixelBufferRelease((CVPixelBufferRef) data);
|
||||
}
|
||||
|
||||
util::safe_ptr<AVFrame, free_frame> av_frame;
|
||||
|
||||
int
|
||||
nv12_zero_device::convert(platf::img_t &img) {
|
||||
int nv12_zero_device::convert(platf::img_t &img) {
|
||||
auto *av_img = (av_img_t *) &img;
|
||||
|
||||
// Release any existing CVPixelBuffer previously retained for encoding
|
||||
@ -47,8 +45,7 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
nv12_zero_device::set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
|
||||
int nv12_zero_device::set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
|
||||
this->frame = frame;
|
||||
|
||||
av_frame.reset(frame);
|
||||
@ -58,11 +55,8 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
nv12_zero_device::init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, const pixel_format_fn_t &pixel_format_fn) {
|
||||
pixel_format_fn(display, pix_fmt == pix_fmt_e::nv12 ?
|
||||
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange :
|
||||
kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange);
|
||||
int nv12_zero_device::init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, const pixel_format_fn_t &pixel_format_fn) {
|
||||
pixel_format_fn(display, pix_fmt == pix_fmt_e::nv12 ? kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange : kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange);
|
||||
|
||||
this->display = display;
|
||||
this->resolution_fn = std::move(resolution_fn);
|
||||
|
@ -4,13 +4,13 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// local includes
|
||||
#include "src/platform/common.h"
|
||||
|
||||
struct AVFrame;
|
||||
|
||||
namespace platf {
|
||||
void
|
||||
free_frame(AVFrame *frame);
|
||||
void free_frame(AVFrame *frame);
|
||||
|
||||
class nv12_zero_device: public avcodec_encode_device_t {
|
||||
// display holds a pointer to an av_video object. Since the namespaces of AVFoundation
|
||||
@ -24,13 +24,10 @@ namespace platf {
|
||||
resolution_fn_t resolution_fn;
|
||||
using pixel_format_fn_t = std::function<void(void *display, int pixelFormat)>;
|
||||
|
||||
int
|
||||
init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, const pixel_format_fn_t &pixel_format_fn);
|
||||
int init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, const pixel_format_fn_t &pixel_format_fn);
|
||||
|
||||
int
|
||||
convert(img_t &img) override;
|
||||
int
|
||||
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override;
|
||||
int convert(img_t &img) override;
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override;
|
||||
|
||||
private:
|
||||
util::safe_ptr<AVFrame, free_frame> av_frame;
|
||||
|
@ -2,9 +2,13 @@
|
||||
* @file src/platform/macos/publish.cpp
|
||||
* @brief Definitions for publishing services on macOS.
|
||||
*/
|
||||
#include <dns_sd.h>
|
||||
// standard includes
|
||||
#include <thread>
|
||||
|
||||
// platform includes
|
||||
#include <dns_sd.h>
|
||||
|
||||
// local includes
|
||||
#include "src/logging.h"
|
||||
#include "src/network.h"
|
||||
#include "src/nvhttp.h"
|
||||
@ -17,12 +21,13 @@ namespace platf::publish {
|
||||
/** @brief Custom deleter intended to be used for `std::unique_ptr<DNSServiceRef>`. */
|
||||
struct ServiceRefDeleter {
|
||||
typedef DNSServiceRef pointer; ///< Type of object to be deleted.
|
||||
void
|
||||
operator()(pointer serviceRef) {
|
||||
|
||||
void operator()(pointer serviceRef) {
|
||||
DNSServiceRefDeallocate(serviceRef);
|
||||
BOOST_LOG(info) << "Deregistered DNS service."sv;
|
||||
}
|
||||
};
|
||||
|
||||
/** @brief This class encapsulates the polling and deinitialization of our connection with
|
||||
* the mDNS service. Implements the `::platf::deinit_t` interface.
|
||||
*/
|
||||
@ -37,25 +42,25 @@ namespace platf::publish {
|
||||
*/
|
||||
deinit_t(DNSServiceRef serviceRef):
|
||||
unique_ptr(serviceRef) {
|
||||
_thread = std::thread { [serviceRef, &_stopRequested = std::as_const(_stopRequested)]() {
|
||||
_thread = std::thread {[serviceRef, &_stopRequested = std::as_const(_stopRequested)]() {
|
||||
const auto socket = DNSServiceRefSockFD(serviceRef);
|
||||
while (!_stopRequested) {
|
||||
auto fdset = fd_set {};
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(socket, &fdset);
|
||||
auto timeout = timeval { .tv_sec = 3, .tv_usec = 0 }; // 3 second timeout
|
||||
auto timeout = timeval {.tv_sec = 3, .tv_usec = 0}; // 3 second timeout
|
||||
const auto ready = select(socket + 1, &fdset, nullptr, nullptr, &timeout);
|
||||
if (ready == -1) {
|
||||
BOOST_LOG(error) << "Failed to obtain response from DNS service."sv;
|
||||
break;
|
||||
}
|
||||
else if (ready != 0) {
|
||||
} else if (ready != 0) {
|
||||
DNSServiceProcessResult(serviceRef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} };
|
||||
}};
|
||||
}
|
||||
|
||||
/** @brief Ensure that we gracefully finish polling the mDNS service before freeing our
|
||||
* connection to it.
|
||||
*/
|
||||
@ -63,9 +68,9 @@ namespace platf::publish {
|
||||
_stopRequested = true;
|
||||
_thread.join();
|
||||
}
|
||||
|
||||
deinit_t(const deinit_t &) = delete;
|
||||
deinit_t &
|
||||
operator=(const deinit_t &) = delete;
|
||||
deinit_t &operator=(const deinit_t &) = delete;
|
||||
|
||||
private:
|
||||
std::thread _thread; ///< Thread for polling the mDNS service for a response.
|
||||
@ -75,10 +80,7 @@ namespace platf::publish {
|
||||
/** @brief Callback that will be invoked when the mDNS service finishes registering our service.
|
||||
* @param errorCode Describes whether the registration was successful.
|
||||
*/
|
||||
void
|
||||
registrationCallback(DNSServiceRef /*serviceRef*/, DNSServiceFlags /*flags*/,
|
||||
DNSServiceErrorType errorCode, const char * /*name*/,
|
||||
const char * /*regtype*/, const char * /*domain*/, void * /*context*/) {
|
||||
void registrationCallback(DNSServiceRef /*serviceRef*/, DNSServiceFlags /*flags*/, DNSServiceErrorType errorCode, const char * /*name*/, const char * /*regtype*/, const char * /*domain*/, void * /*context*/) {
|
||||
if (errorCode != kDNSServiceErr_NoError) {
|
||||
BOOST_LOG(error) << "Failed to register DNS service: Error "sv << errorCode;
|
||||
return;
|
||||
@ -98,8 +100,7 @@ namespace platf::publish {
|
||||
* which will manage polling for a response from the mDNS service, and then, when
|
||||
* deconstructed, will deregister the service.
|
||||
*/
|
||||
[[nodiscard]] std::unique_ptr<::platf::deinit_t>
|
||||
start() {
|
||||
[[nodiscard]] std::unique_ptr<::platf::deinit_t> start() {
|
||||
auto serviceRef = DNSServiceRef {};
|
||||
const auto status = DNSServiceRegister(
|
||||
&serviceRef,
|
||||
|
@ -8,14 +8,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
// platform includes
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#undef DEFINE_GUID
|
||||
#ifdef __cplusplus
|
||||
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
|
||||
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}}
|
||||
#else
|
||||
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
|
||||
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}}
|
||||
#endif
|
||||
|
||||
DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8);
|
||||
@ -40,64 +41,69 @@ class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
|
||||
// ----------------------------------------------------------------------------
|
||||
interface IPolicyConfig: public IUnknown {
|
||||
public:
|
||||
virtual HRESULT
|
||||
GetMixFormat(
|
||||
virtual HRESULT GetMixFormat(
|
||||
PCWSTR,
|
||||
WAVEFORMATEX **);
|
||||
WAVEFORMATEX **
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
GetDeviceFormat(
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
|
||||
PCWSTR,
|
||||
INT,
|
||||
WAVEFORMATEX **);
|
||||
WAVEFORMATEX **
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
|
||||
PCWSTR);
|
||||
PCWSTR
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
SetDeviceFormat(
|
||||
PCWSTR,
|
||||
WAVEFORMATEX *,
|
||||
WAVEFORMATEX *);
|
||||
SetDeviceFormat(
|
||||
PCWSTR,
|
||||
WAVEFORMATEX *,
|
||||
WAVEFORMATEX *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
|
||||
PCWSTR,
|
||||
INT,
|
||||
PINT64,
|
||||
PINT64);
|
||||
PINT64
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
|
||||
PCWSTR,
|
||||
PINT64);
|
||||
PINT64
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
GetShareMode(
|
||||
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
|
||||
PCWSTR,
|
||||
struct DeviceShareMode *);
|
||||
struct DeviceShareMode *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
SetShareMode(
|
||||
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
|
||||
PCWSTR,
|
||||
struct DeviceShareMode *);
|
||||
struct DeviceShareMode *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
GetPropertyValue(
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
|
||||
PCWSTR,
|
||||
const PROPERTYKEY &,
|
||||
PROPVARIANT *);
|
||||
PROPVARIANT *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
SetPropertyValue(
|
||||
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
|
||||
PCWSTR,
|
||||
const PROPERTYKEY &,
|
||||
PROPVARIANT *);
|
||||
PROPVARIANT *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
SetDefaultEndpoint(
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
|
||||
PCWSTR wszDeviceId,
|
||||
ERole eRole);
|
||||
ERole eRole
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
|
||||
PCWSTR,
|
||||
INT);
|
||||
INT
|
||||
);
|
||||
};
|
||||
|
@ -3,22 +3,21 @@
|
||||
* @brief Definitions for Windows audio capture.
|
||||
*/
|
||||
#define INITGUID
|
||||
#include <audioclient.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <roapi.h>
|
||||
|
||||
// platform includes
|
||||
#include <audioclient.h>
|
||||
#include <avrt.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <newdev.h>
|
||||
#include <roapi.h>
|
||||
#include <synchapi.h>
|
||||
|
||||
#include <newdev.h>
|
||||
|
||||
#include <avrt.h>
|
||||
|
||||
// local includes
|
||||
#include "misc.h"
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
// Must be the last included file
|
||||
// clang-format off
|
||||
#include "PolicyConfig.h"
|
||||
@ -65,8 +64,7 @@ namespace {
|
||||
_size,
|
||||
};
|
||||
|
||||
constexpr WAVEFORMATEXTENSIBLE
|
||||
create_waveformat(sample_format_e sample_format, WORD channel_count, DWORD channel_mask) {
|
||||
constexpr WAVEFORMATEXTENSIBLE create_waveformat(sample_format_e sample_format, WORD channel_count, DWORD channel_mask) {
|
||||
WAVEFORMATEXTENSIBLE waveformat = {};
|
||||
|
||||
switch (sample_format) {
|
||||
@ -119,9 +117,8 @@ namespace {
|
||||
|
||||
using virtual_sink_waveformats_t = std::vector<WAVEFORMATEXTENSIBLE>;
|
||||
|
||||
template <WORD channel_count>
|
||||
virtual_sink_waveformats_t
|
||||
create_virtual_sink_waveformats() {
|
||||
template<WORD channel_count>
|
||||
virtual_sink_waveformats_t create_virtual_sink_waveformats() {
|
||||
if constexpr (channel_count == 2) {
|
||||
auto channel_mask = waveformat_mask_stereo;
|
||||
// only choose 24 or 16-bit formats to avoid clobbering existing Dolby/DTS spatial audio settings
|
||||
@ -130,8 +127,7 @@ namespace {
|
||||
create_waveformat(sample_format_e::s24, channel_count, channel_mask),
|
||||
create_waveformat(sample_format_e::s16, channel_count, channel_mask),
|
||||
};
|
||||
}
|
||||
else if (channel_count == 6) {
|
||||
} else if (channel_count == 6) {
|
||||
auto channel_mask1 = waveformat_mask_surround51_with_backspeakers;
|
||||
auto channel_mask2 = waveformat_mask_surround51_with_sidespeakers;
|
||||
return {
|
||||
@ -146,8 +142,7 @@ namespace {
|
||||
create_waveformat(sample_format_e::s16, channel_count, channel_mask1),
|
||||
create_waveformat(sample_format_e::s16, channel_count, channel_mask2),
|
||||
};
|
||||
}
|
||||
else if (channel_count == 8) {
|
||||
} else if (channel_count == 8) {
|
||||
auto channel_mask = waveformat_mask_surround71;
|
||||
return {
|
||||
create_waveformat(sample_format_e::f32, channel_count, channel_mask),
|
||||
@ -159,8 +154,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
waveformat_to_pretty_string(const WAVEFORMATEXTENSIBLE &waveformat) {
|
||||
std::string waveformat_to_pretty_string(const WAVEFORMATEXTENSIBLE &waveformat) {
|
||||
std::string result = waveformat.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ? "F" :
|
||||
waveformat.SubFormat == KSDATAFORMAT_SUBTYPE_PCM ? "S" :
|
||||
"UNKNOWN";
|
||||
@ -196,16 +190,15 @@ namespace {
|
||||
} // namespace
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::audio {
|
||||
template <class T>
|
||||
void
|
||||
Release(T *p) {
|
||||
template<class T>
|
||||
void Release(T *p) {
|
||||
p->Release();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
co_task_free(T *p) {
|
||||
template<class T>
|
||||
void co_task_free(T *p) {
|
||||
CoTaskMemFree((LPVOID) p);
|
||||
}
|
||||
|
||||
@ -272,14 +265,14 @@ namespace platf::audio {
|
||||
},
|
||||
};
|
||||
|
||||
audio_client_t
|
||||
make_audio_client(device_t &device, const format_t &format) {
|
||||
audio_client_t make_audio_client(device_t &device, const format_t &format) {
|
||||
audio_client_t audio_client;
|
||||
auto status = device->Activate(
|
||||
IID_IAudioClient,
|
||||
CLSCTX_ALL,
|
||||
nullptr,
|
||||
(void **) &audio_client);
|
||||
(void **) &audio_client
|
||||
);
|
||||
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
@ -311,9 +304,11 @@ namespace platf::audio {
|
||||
AUDCLNT_SHAREMODE_SHARED,
|
||||
AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
|
||||
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, // Enable automatic resampling to 48 KHz
|
||||
0, 0,
|
||||
0,
|
||||
0,
|
||||
(LPWAVEFORMATEX) &capture_waveformat,
|
||||
nullptr);
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (status) {
|
||||
BOOST_LOG(error) << "Couldn't initialize audio client for ["sv << format.name << "]: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
@ -325,14 +320,14 @@ namespace platf::audio {
|
||||
return audio_client;
|
||||
}
|
||||
|
||||
device_t
|
||||
default_device(device_enum_t &device_enum) {
|
||||
device_t default_device(device_enum_t &device_enum) {
|
||||
device_t device;
|
||||
HRESULT status;
|
||||
status = device_enum->GetDefaultAudioEndpoint(
|
||||
eRender,
|
||||
eConsole,
|
||||
&device);
|
||||
&device
|
||||
);
|
||||
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't get default audio endpoint [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
@ -345,68 +340,68 @@ namespace platf::audio {
|
||||
|
||||
class audio_notification_t: public ::IMMNotificationClient {
|
||||
public:
|
||||
audio_notification_t() {}
|
||||
audio_notification_t() {
|
||||
}
|
||||
|
||||
// IUnknown implementation (unused by IMMDeviceEnumerator)
|
||||
ULONG STDMETHODCALLTYPE
|
||||
AddRef() {
|
||||
ULONG STDMETHODCALLTYPE AddRef() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE
|
||||
Release() {
|
||||
ULONG STDMETHODCALLTYPE Release() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
QueryInterface(REFIID riid, VOID **ppvInterface) {
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) {
|
||||
if (IID_IUnknown == riid) {
|
||||
AddRef();
|
||||
*ppvInterface = (IUnknown *) this;
|
||||
return S_OK;
|
||||
}
|
||||
else if (__uuidof(IMMNotificationClient) == riid) {
|
||||
} else if (__uuidof(IMMNotificationClient) == riid) {
|
||||
AddRef();
|
||||
*ppvInterface = (IMMNotificationClient *) this;
|
||||
return S_OK;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
*ppvInterface = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
// IMMNotificationClient
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) {
|
||||
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) {
|
||||
if (flow == eRender) {
|
||||
default_render_device_changed_flag.store(true);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
OnDeviceAdded(LPCWSTR pwstrDeviceId) { return S_OK; }
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
OnDeviceRemoved(LPCWSTR pwstrDeviceId) { return S_OK; }
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
OnDeviceStateChanged(
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(
|
||||
LPCWSTR pwstrDeviceId,
|
||||
DWORD dwNewState) { return S_OK; }
|
||||
DWORD dwNewState
|
||||
) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
OnPropertyValueChanged(
|
||||
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(
|
||||
LPCWSTR pwstrDeviceId,
|
||||
const PROPERTYKEY key) { return S_OK; }
|
||||
const PROPERTYKEY key
|
||||
) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the default rendering device changed and resets the change flag
|
||||
* @return `true` if the device changed since last call
|
||||
*/
|
||||
bool
|
||||
check_default_render_device_changed() {
|
||||
bool check_default_render_device_changed() {
|
||||
return default_render_device_changed_flag.exchange(false);
|
||||
}
|
||||
|
||||
@ -416,8 +411,7 @@ namespace platf::audio {
|
||||
|
||||
class mic_wasapi_t: public mic_t {
|
||||
public:
|
||||
capture_e
|
||||
sample(std::vector<float> &sample_out) override {
|
||||
capture_e sample(std::vector<float> &sample_out) override {
|
||||
auto sample_size = sample_out.size();
|
||||
|
||||
// Refill the sample buffer if needed
|
||||
@ -438,8 +432,7 @@ namespace platf::audio {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
int
|
||||
init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out) {
|
||||
int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out) {
|
||||
audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr));
|
||||
if (!audio_event) {
|
||||
BOOST_LOG(error) << "Couldn't create Event handle"sv;
|
||||
@ -454,7 +447,8 @@ namespace platf::audio {
|
||||
nullptr,
|
||||
CLSCTX_ALL,
|
||||
IID_IMMDeviceEnumerator,
|
||||
(void **) &device_enum);
|
||||
(void **) &device_enum
|
||||
);
|
||||
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
@ -509,7 +503,7 @@ namespace platf::audio {
|
||||
}
|
||||
|
||||
// *2 --> needs to fit double
|
||||
sample_buf = util::buffer_t<float> { std::max(frames, frame_size) * 2 * channels_out };
|
||||
sample_buf = util::buffer_t<float> {std::max(frames, frame_size) * 2 * channels_out};
|
||||
sample_buf_pos = std::begin(sample_buf);
|
||||
|
||||
status = audio_client->GetService(IID_IAudioCaptureClient, (void **) &audio_capture);
|
||||
@ -559,8 +553,7 @@ namespace platf::audio {
|
||||
}
|
||||
|
||||
private:
|
||||
capture_e
|
||||
_fill_buffer() {
|
||||
capture_e _fill_buffer() {
|
||||
HRESULT status;
|
||||
|
||||
// Total number of samples
|
||||
@ -600,13 +593,16 @@ namespace platf::audio {
|
||||
for (
|
||||
status = audio_capture->GetNextPacketSize(&packet_size);
|
||||
SUCCEEDED(status) && packet_size > 0;
|
||||
status = audio_capture->GetNextPacketSize(&packet_size)) {
|
||||
status = audio_capture->GetNextPacketSize(&packet_size)
|
||||
) {
|
||||
DWORD buffer_flags;
|
||||
status = audio_capture->GetBuffer(
|
||||
(BYTE **) &sample_aligned.samples,
|
||||
&block_aligned.audio_sample_size,
|
||||
&buffer_flags,
|
||||
nullptr, nullptr);
|
||||
nullptr,
|
||||
nullptr
|
||||
);
|
||||
|
||||
switch (status) {
|
||||
case S_OK:
|
||||
@ -631,8 +627,7 @@ namespace platf::audio {
|
||||
|
||||
if (buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) {
|
||||
std::fill_n(sample_buf_pos, n, 0);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
std::copy_n(sample_aligned.samples, n, sample_buf_pos);
|
||||
}
|
||||
|
||||
@ -674,8 +669,7 @@ namespace platf::audio {
|
||||
|
||||
class audio_control_t: public ::platf::audio_control_t {
|
||||
public:
|
||||
std::optional<sink_t>
|
||||
sink_info() override {
|
||||
std::optional<sink_t> sink_info() override {
|
||||
sink_t sink;
|
||||
|
||||
// Fill host sink name with the device_id of the current default audio device.
|
||||
@ -697,8 +691,7 @@ namespace platf::audio {
|
||||
match_fields_list_t match_list;
|
||||
if (config::audio.virtual_sink.empty()) {
|
||||
match_list = match_steam_speakers();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
match_list = match_all_fields(from_utf8(config::audio.virtual_sink));
|
||||
}
|
||||
|
||||
@ -714,16 +707,14 @@ namespace platf::audio {
|
||||
"virtual-"s + formats[1].name + device_id,
|
||||
"virtual-"s + formats[2].name + device_id,
|
||||
});
|
||||
}
|
||||
else if (!config::audio.virtual_sink.empty()) {
|
||||
} else if (!config::audio.virtual_sink.empty()) {
|
||||
BOOST_LOG(warning) << "Couldn't find the specified virtual audio sink " << config::audio.virtual_sink;
|
||||
}
|
||||
|
||||
return sink;
|
||||
}
|
||||
|
||||
bool
|
||||
is_sink_available(const std::string &sink) override {
|
||||
bool is_sink_available(const std::string &sink) override {
|
||||
const auto match_list = match_all_fields(from_utf8(sink));
|
||||
const auto matched = find_device_id(match_list);
|
||||
return static_cast<bool>(matched);
|
||||
@ -735,8 +726,7 @@ namespace platf::audio {
|
||||
* @return A pair of device_id and format reference if the sink name matches
|
||||
* our naming scheme for virtual audio sinks, `std::nullopt` otherwise.
|
||||
*/
|
||||
std::optional<std::pair<std::wstring, std::reference_wrapper<const format_t>>>
|
||||
extract_virtual_sink_info(const std::string &sink) {
|
||||
std::optional<std::pair<std::wstring, std::reference_wrapper<const format_t>>> extract_virtual_sink_info(const std::string &sink) {
|
||||
// Encoding format:
|
||||
// [virtual-(format name)]device_id
|
||||
std::string current = sink;
|
||||
@ -756,8 +746,7 @@ namespace platf::audio {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
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 {
|
||||
auto mic = std::make_unique<mic_wasapi_t>();
|
||||
|
||||
if (mic->init(sample_rate, frame_size, channels)) {
|
||||
@ -784,8 +773,7 @@ namespace platf::audio {
|
||||
* virtual-(format name)
|
||||
* If it doesn't contain that prefix, then the format will not be changed
|
||||
*/
|
||||
std::optional<std::wstring>
|
||||
set_format(const std::string &sink) {
|
||||
std::optional<std::wstring> set_format(const std::string &sink) {
|
||||
if (sink.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -799,8 +787,7 @@ namespace platf::audio {
|
||||
auto matched = find_device_id(match_all_fields(from_utf8(sink)));
|
||||
if (matched) {
|
||||
return matched->second;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(error) << "Couldn't find audio sink " << sink;
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -826,8 +813,7 @@ namespace platf::audio {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int
|
||||
set_sink(const std::string &sink) override {
|
||||
int set_sink(const std::string &sink) override {
|
||||
auto device_id = set_format(sink);
|
||||
if (!device_id) {
|
||||
return -1;
|
||||
@ -840,8 +826,7 @@ namespace platf::audio {
|
||||
// Depending on the format of the string, we could get either of these errors
|
||||
if (status == HRESULT_FROM_WIN32(ERROR_NOT_FOUND) || status == E_INVALIDARG) {
|
||||
BOOST_LOG(warning) << "Audio sink not found: "sv << sink;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << "]: 0x"sv << util::hex(status).to_string_view();
|
||||
}
|
||||
|
||||
@ -868,20 +853,18 @@ namespace platf::audio {
|
||||
using match_fields_list_t = std::vector<std::pair<match_field_e, std::wstring>>;
|
||||
using matched_field_t = std::pair<match_field_e, std::wstring>;
|
||||
|
||||
audio_control_t::match_fields_list_t
|
||||
match_steam_speakers() {
|
||||
audio_control_t::match_fields_list_t match_steam_speakers() {
|
||||
return {
|
||||
{ match_field_e::adapter_friendly_name, L"Steam Streaming Speakers" }
|
||||
{match_field_e::adapter_friendly_name, L"Steam Streaming Speakers"}
|
||||
};
|
||||
}
|
||||
|
||||
audio_control_t::match_fields_list_t
|
||||
match_all_fields(const std::wstring &name) {
|
||||
audio_control_t::match_fields_list_t match_all_fields(const std::wstring &name) {
|
||||
return {
|
||||
{ match_field_e::device_id, name }, // {0.0.0.00000000}.{29dd7668-45b2-4846-882d-950f55bf7eb8}
|
||||
{ match_field_e::device_friendly_name, name }, // Digital Audio (S/PDIF) (High Definition Audio Device)
|
||||
{ match_field_e::device_description, name }, // Digital Audio (S/PDIF)
|
||||
{ match_field_e::adapter_friendly_name, name }, // High Definition Audio Device
|
||||
{match_field_e::device_id, name}, // {0.0.0.00000000}.{29dd7668-45b2-4846-882d-950f55bf7eb8}
|
||||
{match_field_e::device_friendly_name, name}, // Digital Audio (S/PDIF) (High Definition Audio Device)
|
||||
{match_field_e::device_description, name}, // Digital Audio (S/PDIF)
|
||||
{match_field_e::adapter_friendly_name, name}, // High Definition Audio Device
|
||||
};
|
||||
}
|
||||
|
||||
@ -890,8 +873,7 @@ namespace platf::audio {
|
||||
* @param match_list Pairs of match fields and values
|
||||
* @return Optional pair of matched field and device_id
|
||||
*/
|
||||
std::optional<matched_field_t>
|
||||
find_device_id(const match_fields_list_t &match_list) {
|
||||
std::optional<matched_field_t> find_device_id(const match_fields_list_t &match_list) {
|
||||
if (match_list.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -965,8 +947,7 @@ namespace platf::audio {
|
||||
/**
|
||||
* @brief Resets the default audio device from Steam Streaming Speakers.
|
||||
*/
|
||||
void
|
||||
reset_default_device() {
|
||||
void reset_default_device() {
|
||||
auto matched_steam = find_device_id(match_steam_speakers());
|
||||
if (!matched_steam) {
|
||||
return;
|
||||
@ -1027,8 +1008,7 @@ namespace platf::audio {
|
||||
* @brief Installs the Steam Streaming Speakers driver, if present.
|
||||
* @return `true` if installation was successful.
|
||||
*/
|
||||
bool
|
||||
install_steam_audio_drivers() {
|
||||
bool install_steam_audio_drivers() {
|
||||
#ifdef STEAM_DRIVER_SUBDIR
|
||||
// MinGW's libnewdev.a is missing DiInstallDriverW() even though the headers have it,
|
||||
// so we have to load it at runtime. It's Vista or later, so it will always be available.
|
||||
@ -1072,8 +1052,7 @@ namespace platf::audio {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
auto err = GetLastError();
|
||||
switch (err) {
|
||||
case ERROR_ACCESS_DENIED:
|
||||
@ -1096,14 +1075,14 @@ namespace platf::audio {
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
init() {
|
||||
int init() {
|
||||
auto status = CoCreateInstance(
|
||||
CLSID_CPolicyConfigClient,
|
||||
nullptr,
|
||||
CLSCTX_ALL,
|
||||
IID_IPolicyConfig,
|
||||
(void **) &policy);
|
||||
(void **) &policy
|
||||
);
|
||||
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
@ -1116,7 +1095,8 @@ namespace platf::audio {
|
||||
nullptr,
|
||||
CLSCTX_ALL,
|
||||
IID_IMMDeviceEnumerator,
|
||||
(void **) &device_enum);
|
||||
(void **) &device_enum
|
||||
);
|
||||
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
@ -1126,7 +1106,8 @@ namespace platf::audio {
|
||||
return 0;
|
||||
}
|
||||
|
||||
~audio_control_t() override {}
|
||||
~audio_control_t() override {
|
||||
}
|
||||
|
||||
policy_t policy;
|
||||
audio::device_enum_t device_enum;
|
||||
@ -1138,12 +1119,10 @@ namespace platf {
|
||||
|
||||
// It's not big enough to justify it's own source file :/
|
||||
namespace dxgi {
|
||||
int
|
||||
init();
|
||||
int init();
|
||||
}
|
||||
|
||||
std::unique_ptr<audio_control_t>
|
||||
audio_control() {
|
||||
std::unique_ptr<audio_control_t> audio_control() {
|
||||
auto control = std::make_unique<audio::audio_control_t>();
|
||||
|
||||
if (control->init()) {
|
||||
@ -1160,8 +1139,7 @@ namespace platf {
|
||||
return control;
|
||||
}
|
||||
|
||||
std::unique_ptr<deinit_t>
|
||||
init() {
|
||||
std::unique_ptr<deinit_t> init() {
|
||||
if (dxgi::init()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -4,16 +4,17 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// platform includes
|
||||
#include <d3d11.h>
|
||||
#include <d3d11_4.h>
|
||||
#include <d3dcommon.h>
|
||||
#include <dwmapi.h>
|
||||
#include <dxgi.h>
|
||||
#include <dxgi1_6.h>
|
||||
|
||||
#include <Unknwn.h>
|
||||
#include <winrt/Windows.Graphics.Capture.h>
|
||||
|
||||
// local includes
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
#include "src/video.h"
|
||||
@ -25,9 +26,8 @@ namespace platf::dxgi {
|
||||
// You should have a debugger like WinDbg attached to receive debug messages.
|
||||
auto constexpr D3D11_CREATE_DEVICE_FLAGS = 0;
|
||||
|
||||
template <class T>
|
||||
void
|
||||
Release(T *dxgi) {
|
||||
template<class T>
|
||||
void Release(T *dxgi) {
|
||||
dxgi->Release();
|
||||
}
|
||||
|
||||
@ -72,6 +72,7 @@ namespace platf::dxgi {
|
||||
} // namespace video
|
||||
|
||||
class hwdevice_t;
|
||||
|
||||
struct cursor_t {
|
||||
std::vector<std::uint8_t> img_data;
|
||||
|
||||
@ -83,10 +84,9 @@ namespace platf::dxgi {
|
||||
class gpu_cursor_t {
|
||||
public:
|
||||
gpu_cursor_t():
|
||||
cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {};
|
||||
cursor_view {0, 0, 0, 0, 0.0f, 1.0f} {};
|
||||
|
||||
void
|
||||
set_pos(LONG topleft_x, LONG topleft_y, LONG display_width, LONG display_height, DXGI_MODE_ROTATION display_rotation, bool visible) {
|
||||
void set_pos(LONG topleft_x, LONG topleft_y, LONG display_width, LONG display_height, DXGI_MODE_ROTATION display_rotation, bool visible) {
|
||||
this->topleft_x = topleft_x;
|
||||
this->topleft_y = topleft_y;
|
||||
this->display_width = display_width;
|
||||
@ -96,16 +96,14 @@ namespace platf::dxgi {
|
||||
update_viewport();
|
||||
}
|
||||
|
||||
void
|
||||
set_texture(LONG texture_width, LONG texture_height, texture2d_t &&texture) {
|
||||
void set_texture(LONG texture_width, LONG texture_height, texture2d_t &&texture) {
|
||||
this->texture = std::move(texture);
|
||||
this->texture_width = texture_width;
|
||||
this->texture_height = texture_height;
|
||||
update_viewport();
|
||||
}
|
||||
|
||||
void
|
||||
update_viewport() {
|
||||
void update_viewport() {
|
||||
switch (display_rotation) {
|
||||
case DXGI_MODE_ROTATION_UNSPECIFIED:
|
||||
case DXGI_MODE_ROTATION_IDENTITY:
|
||||
@ -158,11 +156,9 @@ namespace platf::dxgi {
|
||||
|
||||
class display_base_t: public display_t {
|
||||
public:
|
||||
int
|
||||
init(const ::video::config_t &config, const std::string &display_name);
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
|
||||
capture_e
|
||||
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override;
|
||||
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override;
|
||||
|
||||
factory1_t factory;
|
||||
adapter_t adapter;
|
||||
@ -209,6 +205,7 @@ namespace platf::dxgi {
|
||||
UINT IndependentVidPnVSyncControl : 1;
|
||||
UINT Reserved : 28;
|
||||
};
|
||||
|
||||
UINT Value;
|
||||
};
|
||||
} D3DKMT_WDDM_2_7_CAPS;
|
||||
@ -231,30 +228,21 @@ namespace platf::dxgi {
|
||||
typedef NTSTATUS(WINAPI *PD3DKMTQueryAdapterInfo)(D3DKMT_QUERYADAPTERINFO *);
|
||||
typedef NTSTATUS(WINAPI *PD3DKMTCloseAdapter)(D3DKMT_CLOSEADAPTER *);
|
||||
|
||||
virtual bool
|
||||
is_hdr() override;
|
||||
virtual bool
|
||||
get_hdr_metadata(SS_HDR_METADATA &metadata) override;
|
||||
virtual bool is_hdr() override;
|
||||
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) override;
|
||||
|
||||
const char *
|
||||
dxgi_format_to_string(DXGI_FORMAT format);
|
||||
const char *
|
||||
colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
|
||||
virtual std::vector<DXGI_FORMAT>
|
||||
get_supported_capture_formats() = 0;
|
||||
const char *dxgi_format_to_string(DXGI_FORMAT format);
|
||||
const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
|
||||
virtual std::vector<DXGI_FORMAT> get_supported_capture_formats() = 0;
|
||||
|
||||
protected:
|
||||
int
|
||||
get_pixel_pitch() {
|
||||
int get_pixel_pitch() {
|
||||
return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4;
|
||||
}
|
||||
|
||||
virtual 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_visible) = 0;
|
||||
virtual capture_e
|
||||
release_snapshot() = 0;
|
||||
virtual int
|
||||
complete_img(img_t *img, bool dummy) = 0;
|
||||
virtual 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_visible) = 0;
|
||||
virtual capture_e release_snapshot() = 0;
|
||||
virtual int complete_img(img_t *img, bool dummy) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -262,17 +250,12 @@ namespace platf::dxgi {
|
||||
*/
|
||||
class display_ram_t: public display_base_t {
|
||||
public:
|
||||
std::shared_ptr<img_t>
|
||||
alloc_img() override;
|
||||
int
|
||||
dummy_img(img_t *img) override;
|
||||
int
|
||||
complete_img(img_t *img, bool dummy) override;
|
||||
std::vector<DXGI_FORMAT>
|
||||
get_supported_capture_formats() override;
|
||||
std::shared_ptr<img_t> alloc_img() override;
|
||||
int dummy_img(img_t *img) override;
|
||||
int complete_img(img_t *img, bool dummy) override;
|
||||
std::vector<DXGI_FORMAT> get_supported_capture_formats() override;
|
||||
|
||||
std::unique_ptr<avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
|
||||
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE img_info;
|
||||
texture2d_t texture;
|
||||
@ -283,23 +266,16 @@ namespace platf::dxgi {
|
||||
*/
|
||||
class display_vram_t: public display_base_t, public std::enable_shared_from_this<display_vram_t> {
|
||||
public:
|
||||
std::shared_ptr<img_t>
|
||||
alloc_img() override;
|
||||
int
|
||||
dummy_img(img_t *img_base) override;
|
||||
int
|
||||
complete_img(img_t *img_base, bool dummy) override;
|
||||
std::vector<DXGI_FORMAT>
|
||||
get_supported_capture_formats() override;
|
||||
std::shared_ptr<img_t> alloc_img() override;
|
||||
int dummy_img(img_t *img_base) override;
|
||||
int complete_img(img_t *img_base, bool dummy) override;
|
||||
std::vector<DXGI_FORMAT> get_supported_capture_formats() override;
|
||||
|
||||
bool
|
||||
is_codec_supported(std::string_view name, const ::video::config_t &config) override;
|
||||
bool is_codec_supported(std::string_view name, const ::video::config_t &config) override;
|
||||
|
||||
std::unique_ptr<avcodec_encode_device_t>
|
||||
make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
|
||||
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
|
||||
|
||||
std::unique_ptr<nvenc_encode_device_t>
|
||||
make_nvenc_encode_device(pix_fmt_e pix_fmt) override;
|
||||
std::unique_ptr<nvenc_encode_device_t> make_nvenc_encode_device(pix_fmt_e pix_fmt) override;
|
||||
|
||||
std::atomic<uint32_t> next_image_id;
|
||||
};
|
||||
@ -313,14 +289,10 @@ namespace platf::dxgi {
|
||||
bool has_frame {};
|
||||
std::chrono::steady_clock::time_point last_protected_content_warning_time {};
|
||||
|
||||
int
|
||||
init(display_base_t *display, const ::video::config_t &config);
|
||||
capture_e
|
||||
next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p);
|
||||
capture_e
|
||||
reset(dup_t::pointer dup_p = dup_t::pointer());
|
||||
capture_e
|
||||
release_frame();
|
||||
int init(display_base_t *display, const ::video::config_t &config);
|
||||
capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p);
|
||||
capture_e reset(dup_t::pointer dup_p = dup_t::pointer());
|
||||
capture_e release_frame();
|
||||
|
||||
~duplication_t();
|
||||
};
|
||||
@ -330,12 +302,9 @@ namespace platf::dxgi {
|
||||
*/
|
||||
class display_ddup_ram_t: public display_ram_t {
|
||||
public:
|
||||
int
|
||||
init(const ::video::config_t &config, const std::string &display_name);
|
||||
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_visible) override;
|
||||
capture_e
|
||||
release_snapshot() override;
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
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_visible) override;
|
||||
capture_e release_snapshot() override;
|
||||
|
||||
duplication_t dup;
|
||||
cursor_t cursor;
|
||||
@ -346,12 +315,9 @@ namespace platf::dxgi {
|
||||
*/
|
||||
class display_ddup_vram_t: public display_vram_t {
|
||||
public:
|
||||
int
|
||||
init(const ::video::config_t &config, const std::string &display_name);
|
||||
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_visible) override;
|
||||
capture_e
|
||||
release_snapshot() override;
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
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_visible) override;
|
||||
capture_e release_snapshot() override;
|
||||
|
||||
duplication_t dup;
|
||||
sampler_state_t sampler_linear;
|
||||
@ -375,29 +341,24 @@ namespace platf::dxgi {
|
||||
* Display duplicator that uses the Windows.Graphics.Capture API.
|
||||
*/
|
||||
class wgc_capture_t {
|
||||
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice uwp_device { nullptr };
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item { nullptr };
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool frame_pool { nullptr };
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureSession capture_session { nullptr };
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame produced_frame { nullptr }, consumed_frame { nullptr };
|
||||
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice uwp_device {nullptr};
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item {nullptr};
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool frame_pool {nullptr};
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureSession capture_session {nullptr};
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame produced_frame {nullptr}, consumed_frame {nullptr};
|
||||
SRWLOCK frame_lock = SRWLOCK_INIT;
|
||||
CONDITION_VARIABLE frame_present_cv;
|
||||
|
||||
void
|
||||
on_frame_arrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, winrt::Windows::Foundation::IInspectable const &);
|
||||
void on_frame_arrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, winrt::Windows::Foundation::IInspectable const &);
|
||||
|
||||
public:
|
||||
wgc_capture_t();
|
||||
~wgc_capture_t();
|
||||
|
||||
int
|
||||
init(display_base_t *display, const ::video::config_t &config);
|
||||
capture_e
|
||||
next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time);
|
||||
capture_e
|
||||
release_frame();
|
||||
int
|
||||
set_cursor_visible(bool);
|
||||
int init(display_base_t *display, const ::video::config_t &config);
|
||||
capture_e next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time);
|
||||
capture_e release_frame();
|
||||
int set_cursor_visible(bool);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -407,12 +368,9 @@ namespace platf::dxgi {
|
||||
wgc_capture_t dup;
|
||||
|
||||
public:
|
||||
int
|
||||
init(const ::video::config_t &config, const std::string &display_name);
|
||||
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_visible) override;
|
||||
capture_e
|
||||
release_snapshot() override;
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
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_visible) override;
|
||||
capture_e release_snapshot() override;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -422,11 +380,8 @@ namespace platf::dxgi {
|
||||
wgc_capture_t dup;
|
||||
|
||||
public:
|
||||
int
|
||||
init(const ::video::config_t &config, const std::string &display_name);
|
||||
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_visible) override;
|
||||
capture_e
|
||||
release_snapshot() override;
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
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_visible) override;
|
||||
capture_e release_snapshot() override;
|
||||
};
|
||||
} // namespace platf::dxgi
|
||||
|
@ -2,13 +2,16 @@
|
||||
* @file src/platform/windows/display_base.cpp
|
||||
* @brief Definitions for the Windows display base code.
|
||||
*/
|
||||
// standard includes
|
||||
#include <cmath>
|
||||
#include <initguid.h>
|
||||
#include <thread>
|
||||
|
||||
// platform includes
|
||||
#include <initguid.h>
|
||||
|
||||
// lib includes
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/process/v1.hpp>
|
||||
|
||||
#include <MinHook.h>
|
||||
|
||||
// We have to include boost/process/v1.hpp before display.h due to WinSock.h,
|
||||
@ -36,14 +39,14 @@ typedef enum _D3DKMT_GPU_PREFERENCE_QUERY_STATE: DWORD {
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
}
|
||||
|
||||
namespace platf::dxgi {
|
||||
namespace bp = boost::process;
|
||||
|
||||
/**
|
||||
* DDAPI-specific initialization goes here.
|
||||
*/
|
||||
int
|
||||
duplication_t::init(display_base_t *display, const ::video::config_t &config) {
|
||||
int duplication_t::init(display_base_t *display, const ::video::config_t &config) {
|
||||
HRESULT status;
|
||||
|
||||
// Capture format will be determined from the first call to AcquireNextFrame()
|
||||
@ -81,8 +84,7 @@ namespace platf::dxgi {
|
||||
BOOST_LOG(warning) << "DuplicateOutput1 Failed [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "IDXGIOutput5 is not supported by your OS. Capture performance may be reduced."sv;
|
||||
|
||||
dxgi::output1_t output1 {};
|
||||
@ -124,8 +126,7 @@ namespace platf::dxgi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
capture_e
|
||||
duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p) {
|
||||
capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p) {
|
||||
auto capture_status = release_frame();
|
||||
if (capture_status != capture_e::ok) {
|
||||
return capture_status;
|
||||
@ -157,8 +158,7 @@ namespace platf::dxgi {
|
||||
}
|
||||
}
|
||||
|
||||
capture_e
|
||||
duplication_t::reset(dup_t::pointer dup_p) {
|
||||
capture_e duplication_t::reset(dup_t::pointer dup_p) {
|
||||
auto capture_status = release_frame();
|
||||
|
||||
dup.reset(dup_p);
|
||||
@ -166,8 +166,7 @@ namespace platf::dxgi {
|
||||
return capture_status;
|
||||
}
|
||||
|
||||
capture_e
|
||||
duplication_t::release_frame() {
|
||||
capture_e duplication_t::release_frame() {
|
||||
if (!has_frame) {
|
||||
return capture_e::ok;
|
||||
}
|
||||
@ -195,16 +194,14 @@ namespace platf::dxgi {
|
||||
release_frame();
|
||||
}
|
||||
|
||||
capture_e
|
||||
display_base_t::capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) {
|
||||
capture_e display_base_t::capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) {
|
||||
auto adjust_client_frame_rate = [&]() -> DXGI_RATIONAL {
|
||||
// Adjust capture frame interval when display refresh rate is not integral but very close to requested fps.
|
||||
if (display_refresh_rate.Denominator > 1) {
|
||||
DXGI_RATIONAL candidate = display_refresh_rate;
|
||||
if (client_frame_rate % display_refresh_rate_rounded == 0) {
|
||||
candidate.Numerator *= client_frame_rate / display_refresh_rate_rounded;
|
||||
}
|
||||
else if (display_refresh_rate_rounded % client_frame_rate == 0) {
|
||||
} else if (display_refresh_rate_rounded % client_frame_rate == 0) {
|
||||
candidate.Denominator *= display_refresh_rate_rounded / client_frame_rate;
|
||||
}
|
||||
double candidate_rate = (double) candidate.Numerator / candidate.Denominator;
|
||||
@ -215,7 +212,7 @@ namespace platf::dxgi {
|
||||
}
|
||||
}
|
||||
|
||||
return { (uint32_t) client_frame_rate, 1 };
|
||||
return {(uint32_t) client_frame_rate, 1};
|
||||
};
|
||||
|
||||
DXGI_RATIONAL client_frame_rate_adjusted = adjust_client_frame_rate();
|
||||
@ -258,8 +255,7 @@ namespace platf::dxgi {
|
||||
frame_pacing_group_start = std::nullopt;
|
||||
frame_pacing_group_frames = 0;
|
||||
status = capture_e::timeout;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
timer->sleep_for(sleep_period);
|
||||
sleep_overshoot_logger.first_point(sleep_target);
|
||||
sleep_overshoot_logger.second_point_now_and_log();
|
||||
@ -268,8 +264,7 @@ namespace platf::dxgi {
|
||||
|
||||
if (status == capture_e::ok && img_out) {
|
||||
frame_pacing_group_frames += 1;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
frame_pacing_group_start = std::nullopt;
|
||||
frame_pacing_group_frames = 0;
|
||||
}
|
||||
@ -289,8 +284,7 @@ namespace platf::dxgi {
|
||||
}
|
||||
|
||||
frame_pacing_group_frames = 1;
|
||||
}
|
||||
else if (status == platf::capture_e::timeout) {
|
||||
} else if (status == platf::capture_e::timeout) {
|
||||
// The D3D11 device is protected by an unfair lock that is held the entire time that
|
||||
// IDXGIOutputDuplication::AcquireNextFrame() is running. This is normally harmless,
|
||||
// however sometimes the encoding thread needs to interact with our ID3D11Device to
|
||||
@ -348,8 +342,7 @@ namespace platf::dxgi {
|
||||
* @param output The DXGI output to capture.
|
||||
* @param enumeration_only Specifies whether this test is occurring for display enumeration.
|
||||
*/
|
||||
bool
|
||||
test_dxgi_duplication(adapter_t &adapter, output_t &output, bool enumeration_only) {
|
||||
bool test_dxgi_duplication(adapter_t &adapter, output_t &output, bool enumeration_only) {
|
||||
D3D_FEATURE_LEVEL featureLevels[] {
|
||||
D3D_FEATURE_LEVEL_11_1,
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
@ -366,11 +359,13 @@ namespace platf::dxgi {
|
||||
D3D_DRIVER_TYPE_UNKNOWN,
|
||||
nullptr,
|
||||
D3D11_CREATE_DEVICE_FLAGS,
|
||||
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
|
||||
featureLevels,
|
||||
sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
|
||||
D3D11_SDK_VERSION,
|
||||
&device,
|
||||
nullptr,
|
||||
nullptr);
|
||||
nullptr
|
||||
);
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return false;
|
||||
@ -403,8 +398,7 @@ namespace platf::dxgi {
|
||||
// capture the current desktop, just bail immediately. Retrying won't help.
|
||||
if (enumeration_only && status == E_ACCESSDENIED) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
std::this_thread::sleep_for(200ms);
|
||||
}
|
||||
}
|
||||
@ -418,8 +412,7 @@ namespace platf::dxgi {
|
||||
* @param gpuPreference A pointer to the location where the preference will be written.
|
||||
* @return Always STATUS_SUCCESS if valid arguments are provided.
|
||||
*/
|
||||
NTSTATUS
|
||||
__stdcall NtGdiDdDDIGetCachedHybridQueryValueHook(D3DKMT_GPU_PREFERENCE_QUERY_STATE *gpuPreference) {
|
||||
NTSTATUS __stdcall NtGdiDdDDIGetCachedHybridQueryValueHook(D3DKMT_GPU_PREFERENCE_QUERY_STATE *gpuPreference) {
|
||||
// By faking a cached GPU preference state of D3DKMT_GPU_PREFERENCE_STATE_UNSPECIFIED, this will
|
||||
// prevent DXGI from performing the normal GPU preference resolution that looks at the registry,
|
||||
// power settings, and the hybrid adapter DDI interface to pick a GPU. Instead, we will not be
|
||||
@ -428,14 +421,12 @@ namespace platf::dxgi {
|
||||
if (gpuPreference) {
|
||||
*gpuPreference = D3DKMT_GPU_PREFERENCE_STATE_UNSPECIFIED;
|
||||
return 0; // STATUS_SUCCESS
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
display_base_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
int display_base_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
std::once_flag windows_cpp_once_flag;
|
||||
|
||||
std::call_once(windows_cpp_once_flag, []() {
|
||||
@ -479,7 +470,7 @@ namespace platf::dxgi {
|
||||
adapter_t::pointer adapter_p;
|
||||
for (int tries = 0; tries < 2; ++tries) {
|
||||
for (int x = 0; factory->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) {
|
||||
dxgi::adapter_t adapter_tmp { adapter_p };
|
||||
dxgi::adapter_t adapter_tmp {adapter_p};
|
||||
|
||||
DXGI_ADAPTER_DESC1 adapter_desc;
|
||||
adapter_tmp->GetDesc1(&adapter_desc);
|
||||
@ -490,7 +481,7 @@ namespace platf::dxgi {
|
||||
|
||||
dxgi::output_t::pointer output_p;
|
||||
for (int y = 0; adapter_tmp->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
|
||||
dxgi::output_t output_tmp { output_p };
|
||||
dxgi::output_t output_tmp {output_p};
|
||||
|
||||
DXGI_OUTPUT_DESC desc;
|
||||
output_tmp->GetDesc(&desc);
|
||||
@ -512,8 +503,7 @@ namespace platf::dxgi {
|
||||
display_rotation == DXGI_MODE_ROTATION_ROTATE270) {
|
||||
width_before_rotation = height;
|
||||
height_before_rotation = width;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
width_before_rotation = width;
|
||||
height_before_rotation = height;
|
||||
}
|
||||
@ -570,11 +560,13 @@ namespace platf::dxgi {
|
||||
D3D_DRIVER_TYPE_UNKNOWN,
|
||||
nullptr,
|
||||
D3D11_CREATE_DEVICE_FLAGS,
|
||||
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
|
||||
featureLevels,
|
||||
sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
|
||||
D3D11_SDK_VERSION,
|
||||
&device,
|
||||
&feature_level,
|
||||
&device_ctx);
|
||||
&device_ctx
|
||||
);
|
||||
|
||||
adapter_p->Release();
|
||||
|
||||
@ -632,7 +624,7 @@ namespace platf::dxgi {
|
||||
return false;
|
||||
}
|
||||
|
||||
D3DKMT_OPENADAPTERFROMLUID d3dkmt_adapter = { adapter };
|
||||
D3DKMT_OPENADAPTERFROMLUID d3dkmt_adapter = {adapter};
|
||||
if (FAILED(d3dkmt_open_adapter(&d3dkmt_adapter))) {
|
||||
BOOST_LOG(error) << "D3DKMTOpenAdapterFromLuid() failed while trying to determine GPU HAGS status";
|
||||
return false;
|
||||
@ -649,13 +641,12 @@ namespace platf::dxgi {
|
||||
|
||||
if (SUCCEEDED(d3dkmt_query_adapter_info(&d3dkmt_adapter_info))) {
|
||||
result = d3dkmt_adapter_caps.HwSchEnabled;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(warning) << "D3DKMTQueryAdapterInfo() failed while trying to determine GPU HAGS status";
|
||||
result = false;
|
||||
}
|
||||
|
||||
D3DKMT_CLOSEADAPTER d3dkmt_close_adapter_wrap = { d3dkmt_adapter.hAdapter };
|
||||
D3DKMT_CLOSEADAPTER d3dkmt_close_adapter_wrap = {d3dkmt_adapter.hAdapter};
|
||||
if (FAILED(d3dkmt_close_adapter(&d3dkmt_close_adapter_wrap))) {
|
||||
BOOST_LOG(error) << "D3DKMTCloseAdapter() failed while trying to determine GPU HAGS status";
|
||||
}
|
||||
@ -671,15 +662,16 @@ namespace platf::dxgi {
|
||||
// As of 2023.07, NVIDIA driver has unfixed bug(s) where "realtime" can cause unrecoverable encoding freeze or outright driver crash
|
||||
// This issue happens more frequently with HAGS, in DX12 games or when VRAM is filled close to max capacity
|
||||
// Track OBS to see if they find better workaround or NVIDIA fixes it on their end, they seem to be in communication
|
||||
if (hags_enabled && !config::video.nv_realtime_hags) priority = D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH;
|
||||
if (hags_enabled && !config::video.nv_realtime_hags) {
|
||||
priority = D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH;
|
||||
}
|
||||
}
|
||||
BOOST_LOG(info) << "Active GPU has HAGS " << (hags_enabled ? "enabled" : "disabled");
|
||||
BOOST_LOG(info) << "Using " << (priority == D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH ? "high" : "realtime") << " GPU priority";
|
||||
if (FAILED(d3dkmt_set_process_priority(GetCurrentProcess(), priority))) {
|
||||
BOOST_LOG(warning) << "Failed to adjust GPU priority. Please run application as administrator for optimal performance.";
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(error) << "Couldn't load D3DKMTSetProcessSchedulingPriorityClass function from gdi32.dll to adjust GPU priority";
|
||||
}
|
||||
}
|
||||
@ -740,8 +732,7 @@ namespace platf::dxgi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
display_base_t::is_hdr() {
|
||||
bool display_base_t::is_hdr() {
|
||||
dxgi::output6_t output6 {};
|
||||
|
||||
auto status = output->QueryInterface(IID_IDXGIOutput6, (void **) &output6);
|
||||
@ -756,8 +747,7 @@ namespace platf::dxgi {
|
||||
return desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
|
||||
}
|
||||
|
||||
bool
|
||||
display_base_t::get_hdr_metadata(SS_HDR_METADATA &metadata) {
|
||||
bool display_base_t::get_hdr_metadata(SS_HDR_METADATA &metadata) {
|
||||
dxgi::output6_t output6 {};
|
||||
|
||||
std::memset(&metadata, 0, sizeof(metadata));
|
||||
@ -928,20 +918,31 @@ namespace platf::dxgi {
|
||||
"DXGI_FORMAT_A8P8",
|
||||
"DXGI_FORMAT_B4G4R4A4_UNORM",
|
||||
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
|
||||
"DXGI_FORMAT_P208",
|
||||
"DXGI_FORMAT_V208",
|
||||
"DXGI_FORMAT_V408"
|
||||
};
|
||||
|
||||
const char *
|
||||
display_base_t::dxgi_format_to_string(DXGI_FORMAT format) {
|
||||
const char *display_base_t::dxgi_format_to_string(DXGI_FORMAT format) {
|
||||
return format_str[format];
|
||||
}
|
||||
|
||||
const char *
|
||||
display_base_t::colorspace_to_string(DXGI_COLOR_SPACE_TYPE type) {
|
||||
const char *display_base_t::colorspace_to_string(DXGI_COLOR_SPACE_TYPE type) {
|
||||
const char *type_str[] = {
|
||||
"DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709",
|
||||
"DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709",
|
||||
@ -972,8 +973,7 @@ namespace platf::dxgi {
|
||||
|
||||
if (type < ARRAYSIZE(type_str)) {
|
||||
return type_str[type];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
@ -985,8 +985,7 @@ namespace platf {
|
||||
* Pick a display adapter and capture method.
|
||||
* @param hwdevice_type enables possible use of hardware encoder
|
||||
*/
|
||||
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) {
|
||||
if (config::video.capture == "ddx" || config::video.capture.empty()) {
|
||||
if (hwdevice_type == mem_type_e::dxgi) {
|
||||
auto disp = std::make_shared<dxgi::display_ddup_vram_t>();
|
||||
@ -994,8 +993,7 @@ namespace platf {
|
||||
if (!disp->init(config, display_name)) {
|
||||
return disp;
|
||||
}
|
||||
}
|
||||
else if (hwdevice_type == mem_type_e::system) {
|
||||
} else if (hwdevice_type == mem_type_e::system) {
|
||||
auto disp = std::make_shared<dxgi::display_ddup_ram_t>();
|
||||
|
||||
if (!disp->init(config, display_name)) {
|
||||
@ -1011,8 +1009,7 @@ namespace platf {
|
||||
if (!disp->init(config, display_name)) {
|
||||
return disp;
|
||||
}
|
||||
}
|
||||
else if (hwdevice_type == mem_type_e::system) {
|
||||
} else if (hwdevice_type == mem_type_e::system) {
|
||||
auto disp = std::make_shared<dxgi::display_wgc_ram_t>();
|
||||
|
||||
if (!disp->init(config, display_name)) {
|
||||
@ -1025,8 +1022,7 @@ namespace platf {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
display_names(mem_type_e) {
|
||||
std::vector<std::string> display_names(mem_type_e) {
|
||||
std::vector<std::string> display_names;
|
||||
|
||||
HRESULT status;
|
||||
@ -1066,7 +1062,7 @@ namespace platf {
|
||||
|
||||
dxgi::output_t::pointer output_p {};
|
||||
for (int y = 0; adapter->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
|
||||
dxgi::output_t output { output_p };
|
||||
dxgi::output_t output {output_p};
|
||||
|
||||
DXGI_OUTPUT_DESC desc;
|
||||
output->GetDesc(&desc);
|
||||
@ -1096,8 +1092,7 @@ namespace platf {
|
||||
* @brief Returns if GPUs/drivers have changed since the last call to this function.
|
||||
* @return `true` if a change has occurred or if it is unknown whether a change occurred.
|
||||
*/
|
||||
bool
|
||||
needs_encoder_reenumeration() {
|
||||
bool needs_encoder_reenumeration() {
|
||||
// Serialize access to the static DXGI factory
|
||||
static std::mutex reenumeration_state_lock;
|
||||
auto lg = std::lock_guard(reenumeration_state_lock);
|
||||
@ -1117,8 +1112,7 @@ namespace platf {
|
||||
// can deal with any initialization races that may occur when the system is booting.
|
||||
BOOST_LOG(info) << "Encoder reenumeration is required"sv;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// The DXGI factory from last time is still current, so no encoder changes have occurred.
|
||||
return false;
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @file src/platform/windows/display_ram.cpp
|
||||
* @brief Definitions for handling ram.
|
||||
*/
|
||||
// local includes
|
||||
#include "display.h"
|
||||
|
||||
#include "misc.h"
|
||||
#include "src/logging.h"
|
||||
|
||||
@ -19,8 +19,7 @@ namespace platf::dxgi {
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
|
||||
void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
|
||||
int height = cursor.shape_info.Height / 2;
|
||||
int width = cursor.shape_info.Width;
|
||||
int pitch = cursor.shape_info.Pitch;
|
||||
@ -82,8 +81,7 @@ namespace platf::dxgi {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
|
||||
void apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
|
||||
auto colors_out = (std::uint8_t *) &cursor_pixel;
|
||||
auto colors_in = (std::uint8_t *) img_pixel_p;
|
||||
|
||||
@ -91,28 +89,24 @@ namespace platf::dxgi {
|
||||
auto alpha = colors_out[3];
|
||||
if (alpha == 255) {
|
||||
*img_pixel_p = cursor_pixel;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
|
||||
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
|
||||
colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
apply_color_masked(int *img_pixel_p, int cursor_pixel) {
|
||||
void apply_color_masked(int *img_pixel_p, int cursor_pixel) {
|
||||
// TODO: When use of IDXGIOutput5 is implemented, support different color formats
|
||||
auto alpha = ((std::uint8_t *) &cursor_pixel)[3];
|
||||
if (alpha == 0xFF) {
|
||||
*img_pixel_p ^= cursor_pixel;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
*img_pixel_p = cursor_pixel;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
|
||||
void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
|
||||
int height = cursor.shape_info.Height;
|
||||
int width = cursor.shape_info.Width;
|
||||
int pitch = cursor.shape_info.Pitch;
|
||||
@ -150,8 +144,7 @@ namespace platf::dxgi {
|
||||
std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) {
|
||||
if (masked) {
|
||||
apply_color_masked(img_pixel_p, cursor_pixel);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
apply_color_alpha(img_pixel_p, cursor_pixel);
|
||||
}
|
||||
++img_pixel_p;
|
||||
@ -159,8 +152,7 @@ namespace platf::dxgi {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
blend_cursor(const cursor_t &cursor, img_t &img) {
|
||||
void blend_cursor(const cursor_t &cursor, img_t &img) {
|
||||
switch (cursor.shape_info.Type) {
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
|
||||
blend_cursor_color(cursor, img, false);
|
||||
@ -176,14 +168,13 @@ namespace platf::dxgi {
|
||||
}
|
||||
}
|
||||
|
||||
capture_e
|
||||
display_ddup_ram_t::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_visible) {
|
||||
capture_e display_ddup_ram_t::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_visible) {
|
||||
HRESULT status;
|
||||
DXGI_OUTDUPL_FRAME_INFO frame_info;
|
||||
|
||||
resource_t::pointer res_p {};
|
||||
auto capture_status = dup.next_frame(frame_info, timeout, &res_p);
|
||||
resource_t res { res_p };
|
||||
resource_t res {res_p};
|
||||
|
||||
if (capture_status != capture_e::ok) {
|
||||
return capture_status;
|
||||
@ -290,8 +281,7 @@ namespace platf::dxgi {
|
||||
if (dummy_img(img)) {
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Map the staging texture for CPU access (making it inaccessible for the GPU)
|
||||
status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info);
|
||||
if (FAILED(status)) {
|
||||
@ -325,13 +315,11 @@ namespace platf::dxgi {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
display_ddup_ram_t::release_snapshot() {
|
||||
capture_e display_ddup_ram_t::release_snapshot() {
|
||||
return dup.release_frame();
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::img_t>
|
||||
display_ram_t::alloc_img() {
|
||||
std::shared_ptr<platf::img_t> display_ram_t::alloc_img() {
|
||||
auto img = std::make_shared<img_t>();
|
||||
|
||||
// Initialize fields that are format-independent
|
||||
@ -341,8 +329,7 @@ namespace platf::dxgi {
|
||||
return img;
|
||||
}
|
||||
|
||||
int
|
||||
display_ram_t::complete_img(platf::img_t *img, bool dummy) {
|
||||
int display_ram_t::complete_img(platf::img_t *img, bool dummy) {
|
||||
// If this is not a dummy image, we must know the format by now
|
||||
if (!dummy && capture_format == DXGI_FORMAT_UNKNOWN) {
|
||||
BOOST_LOG(error) << "display_ram_t::complete_img() called with unknown capture format!";
|
||||
@ -373,8 +360,7 @@ namespace platf::dxgi {
|
||||
/**
|
||||
* @memberof platf::dxgi::display_ram_t
|
||||
*/
|
||||
int
|
||||
display_ram_t::dummy_img(platf::img_t *img) {
|
||||
int display_ram_t::dummy_img(platf::img_t *img) {
|
||||
if (complete_img(img, true)) {
|
||||
return -1;
|
||||
}
|
||||
@ -383,13 +369,11 @@ namespace platf::dxgi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<DXGI_FORMAT>
|
||||
display_ram_t::get_supported_capture_formats() {
|
||||
return { DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8X8_UNORM };
|
||||
std::vector<DXGI_FORMAT> display_ram_t::get_supported_capture_formats() {
|
||||
return {DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8X8_UNORM};
|
||||
}
|
||||
|
||||
int
|
||||
display_ddup_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
int display_ddup_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
if (display_base_t::init(config, display_name) || dup.init(this, config)) {
|
||||
return -1;
|
||||
}
|
||||
@ -397,8 +381,7 @@ namespace platf::dxgi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<avcodec_encode_device_t>
|
||||
display_ram_t::make_avcodec_encode_device(pix_fmt_e pix_fmt) {
|
||||
std::unique_ptr<avcodec_encode_device_t> display_ram_t::make_avcodec_encode_device(pix_fmt_e pix_fmt) {
|
||||
return std::make_unique<avcodec_encode_device_t>();
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,336 +1,335 @@
|
||||
/**
|
||||
* @file src/platform/windows/display_wgc.cpp
|
||||
* @brief Definitions for WinRT Windows.Graphics.Capture API
|
||||
*/
|
||||
#include <dxgi1_2.h>
|
||||
|
||||
#include "display.h"
|
||||
|
||||
#include "misc.h"
|
||||
#include "src/logging.h"
|
||||
|
||||
// Gross hack to work around MINGW-packages#22160
|
||||
#define ____FIReference_1_boolean_INTERFACE_DEFINED__
|
||||
|
||||
#include <windows.graphics.capture.interop.h>
|
||||
#include <winrt/windows.foundation.h>
|
||||
#include <winrt/windows.foundation.metadata.h>
|
||||
#include <winrt/windows.graphics.directx.direct3d11.h>
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
}
|
||||
|
||||
namespace winrt {
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Foundation::Metadata;
|
||||
using namespace Windows::Graphics::Capture;
|
||||
using namespace Windows::Graphics::DirectX::Direct3D11;
|
||||
|
||||
extern "C" {
|
||||
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows structures sometimes have compile-time GUIDs. GCC supports this, but in a roundabout way.
|
||||
* If WINRT_IMPL_HAS_DECLSPEC_UUID is true, then the compiler supports adding this attribute to a struct. For example, Visual Studio.
|
||||
* If not, then MinGW GCC has a workaround to assign a GUID to a structure.
|
||||
*/
|
||||
struct
|
||||
#if WINRT_IMPL_HAS_DECLSPEC_UUID
|
||||
__declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"))
|
||||
#endif
|
||||
IDirect3DDxgiInterfaceAccess: ::IUnknown {
|
||||
virtual HRESULT __stdcall GetInterface(REFIID id, void **object) = 0;
|
||||
};
|
||||
} // namespace winrt
|
||||
#if !WINRT_IMPL_HAS_DECLSPEC_UUID
|
||||
static constexpr GUID GUID__IDirect3DDxgiInterfaceAccess = {
|
||||
0xA9B3D012, 0x3DF2, 0x4EE3, { 0xB8, 0xD1, 0x86, 0x95, 0xF4, 0x57, 0xD3, 0xC1 }
|
||||
// compare with __declspec(uuid(...)) for the struct above.
|
||||
};
|
||||
template <>
|
||||
constexpr auto
|
||||
__mingw_uuidof<winrt::IDirect3DDxgiInterfaceAccess>() -> GUID const & {
|
||||
return GUID__IDirect3DDxgiInterfaceAccess;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace platf::dxgi {
|
||||
wgc_capture_t::wgc_capture_t() {
|
||||
InitializeConditionVariable(&frame_present_cv);
|
||||
}
|
||||
|
||||
wgc_capture_t::~wgc_capture_t() {
|
||||
if (capture_session)
|
||||
capture_session.Close();
|
||||
if (frame_pool)
|
||||
frame_pool.Close();
|
||||
item = nullptr;
|
||||
capture_session = nullptr;
|
||||
frame_pool = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the Windows.Graphics.Capture backend.
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
int
|
||||
wgc_capture_t::init(display_base_t *display, const ::video::config_t &config) {
|
||||
HRESULT status;
|
||||
dxgi::dxgi_t dxgi;
|
||||
winrt::com_ptr<::IInspectable> d3d_comhandle;
|
||||
try {
|
||||
if (!winrt::GraphicsCaptureSession::IsSupported()) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows!"sv;
|
||||
return -1;
|
||||
}
|
||||
if (FAILED(status = display->device->QueryInterface(IID_IDXGIDevice, (void **) &dxgi))) {
|
||||
BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
if (FAILED(status = winrt::CreateDirect3D11DeviceFromDXGIDevice(*&dxgi, d3d_comhandle.put()))) {
|
||||
BOOST_LOG(error) << "Failed to query WinRT DirectX interface from device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to acquire device: [0x"sv << util::hex(e.code()).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
DXGI_OUTPUT_DESC output_desc;
|
||||
uwp_device = d3d_comhandle.as<winrt::IDirect3DDevice>();
|
||||
display->output->GetDesc(&output_desc);
|
||||
|
||||
auto monitor_factory = winrt::get_activation_factory<winrt::GraphicsCaptureItem, IGraphicsCaptureItemInterop>();
|
||||
if (monitor_factory == nullptr ||
|
||||
FAILED(status = monitor_factory->CreateForMonitor(output_desc.Monitor, winrt::guid_of<winrt::IGraphicsCaptureItem>(), winrt::put_abi(item)))) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to acquire display: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (config.dynamicRange)
|
||||
display->capture_format = DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
else
|
||||
display->capture_format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
|
||||
try {
|
||||
frame_pool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(uwp_device, static_cast<winrt::Windows::Graphics::DirectX::DirectXPixelFormat>(display->capture_format), 2, item.Size());
|
||||
capture_session = frame_pool.CreateCaptureSession(item);
|
||||
frame_pool.FrameArrived({ this, &wgc_capture_t::on_frame_arrived });
|
||||
}
|
||||
catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to create capture session: [0x"sv << util::hex(e.code()).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
if (winrt::ApiInformation::IsPropertyPresent(L"Windows.Graphics.Capture.GraphicsCaptureSession", L"IsBorderRequired")) {
|
||||
capture_session.IsBorderRequired(false);
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(warning) << "Can't disable colored border around capture area on this version of Windows";
|
||||
}
|
||||
}
|
||||
catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(warning) << "Screen capture may not be fully supported on this device for this release of Windows: failed to disable border around capture area: [0x"sv << util::hex(e.code()).to_string_view() << ']';
|
||||
}
|
||||
try {
|
||||
capture_session.StartCapture();
|
||||
}
|
||||
catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to start capture: [0x"sv << util::hex(e.code()).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function runs in a separate thread spawned by the frame pool and is a producer of frames.
|
||||
* To maintain parity with the original display interface, this frame will be consumed by the capture thread.
|
||||
* Acquire a read-write lock, make the produced frame available to the capture thread, then wake the capture thread.
|
||||
*/
|
||||
void
|
||||
wgc_capture_t::on_frame_arrived(winrt::Direct3D11CaptureFramePool const &sender, winrt::IInspectable const &) {
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame frame { nullptr };
|
||||
try {
|
||||
frame = sender.TryGetNextFrame();
|
||||
}
|
||||
catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(warning) << "Failed to capture frame: "sv << e.code();
|
||||
return;
|
||||
}
|
||||
if (frame != nullptr) {
|
||||
AcquireSRWLockExclusive(&frame_lock);
|
||||
if (produced_frame)
|
||||
produced_frame.Close();
|
||||
|
||||
produced_frame = frame;
|
||||
ReleaseSRWLockExclusive(&frame_lock);
|
||||
WakeConditionVariable(&frame_present_cv);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the next frame from the producer thread.
|
||||
* If not available, the capture thread blocks until one is, or the wait times out.
|
||||
* @param timeout how long to wait for the next frame
|
||||
* @param out a texture containing the frame just captured
|
||||
* @param out_time the timestamp of the frame just captured
|
||||
*/
|
||||
capture_e
|
||||
wgc_capture_t::next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time) {
|
||||
// this CONSUMER runs in the capture thread
|
||||
release_frame();
|
||||
|
||||
AcquireSRWLockExclusive(&frame_lock);
|
||||
if (produced_frame == nullptr && SleepConditionVariableSRW(&frame_present_cv, &frame_lock, timeout.count(), 0) == 0) {
|
||||
ReleaseSRWLockExclusive(&frame_lock);
|
||||
if (GetLastError() == ERROR_TIMEOUT)
|
||||
return capture_e::timeout;
|
||||
else
|
||||
return capture_e::error;
|
||||
}
|
||||
if (produced_frame) {
|
||||
consumed_frame = produced_frame;
|
||||
produced_frame = nullptr;
|
||||
}
|
||||
ReleaseSRWLockExclusive(&frame_lock);
|
||||
if (consumed_frame == nullptr) // spurious wakeup
|
||||
return capture_e::timeout;
|
||||
|
||||
auto capture_access = consumed_frame.Surface().as<winrt::IDirect3DDxgiInterfaceAccess>();
|
||||
if (capture_access == nullptr)
|
||||
return capture_e::error;
|
||||
capture_access->GetInterface(IID_ID3D11Texture2D, (void **) out);
|
||||
out_time = consumed_frame.SystemRelativeTime().count(); // raw ticks from query performance counter
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
wgc_capture_t::release_frame() {
|
||||
if (consumed_frame != nullptr) {
|
||||
consumed_frame.Close();
|
||||
consumed_frame = nullptr;
|
||||
}
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
int
|
||||
wgc_capture_t::set_cursor_visible(bool x) {
|
||||
try {
|
||||
if (capture_session.IsCursorCaptureEnabled() != x)
|
||||
capture_session.IsCursorCaptureEnabled(x);
|
||||
return 0;
|
||||
}
|
||||
catch (winrt::hresult_error &) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
display_wgc_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
if (display_base_t::init(config, display_name) || dup.init(this, config))
|
||||
return -1;
|
||||
|
||||
texture.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the next frame from the Windows.Graphics.Capture API and copy it into a new snapshot texture.
|
||||
* @param pull_free_image_cb call this to get a new free image from the video subsystem.
|
||||
* @param img_out the captured frame is returned here
|
||||
* @param timeout how long to wait for the next frame
|
||||
* @param cursor_visible whether to capture the cursor
|
||||
*/
|
||||
capture_e
|
||||
display_wgc_ram_t::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_visible) {
|
||||
HRESULT status;
|
||||
texture2d_t src;
|
||||
uint64_t frame_qpc;
|
||||
dup.set_cursor_visible(cursor_visible);
|
||||
auto capture_status = dup.next_frame(timeout, &src, frame_qpc);
|
||||
if (capture_status != capture_e::ok)
|
||||
return capture_status;
|
||||
|
||||
auto frame_timestamp = std::chrono::steady_clock::now() - qpc_time_difference(qpc_counter(), frame_qpc);
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
src->GetDesc(&desc);
|
||||
|
||||
// Create the staging texture if it doesn't exist. It should match the source in size and format.
|
||||
if (texture == nullptr) {
|
||||
capture_format = desc.Format;
|
||||
BOOST_LOG(info) << "Capture format ["sv << dxgi_format_to_string(capture_format) << ']';
|
||||
|
||||
D3D11_TEXTURE2D_DESC t {};
|
||||
t.Width = width;
|
||||
t.Height = height;
|
||||
t.MipLevels = 1;
|
||||
t.ArraySize = 1;
|
||||
t.SampleDesc.Count = 1;
|
||||
t.Usage = D3D11_USAGE_STAGING;
|
||||
t.Format = capture_format;
|
||||
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
|
||||
auto status = device->CreateTexture2D(&t, nullptr, &texture);
|
||||
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
|
||||
// It's possible for our display enumeration to race with mode changes and result in
|
||||
// mismatched image pool and desktop texture sizes. If this happens, just reinit again.
|
||||
if (desc.Width != width || desc.Height != height) {
|
||||
BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']';
|
||||
return capture_e::reinit;
|
||||
}
|
||||
// It's also possible for the capture format to change on the fly. If that happens,
|
||||
// reinitialize capture to try format detection again and create new images.
|
||||
if (capture_format != desc.Format) {
|
||||
BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']';
|
||||
return capture_e::reinit;
|
||||
}
|
||||
|
||||
// Copy from GPU to CPU
|
||||
device_ctx->CopyResource(texture.get(), src.get());
|
||||
|
||||
if (!pull_free_image_cb(img_out)) {
|
||||
return capture_e::interrupted;
|
||||
}
|
||||
auto img = (img_t *) img_out.get();
|
||||
|
||||
// Map the staging texture for CPU access (making it inaccessible for the GPU)
|
||||
if (FAILED(status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info))) {
|
||||
BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
// Now that we know the capture format, we can finish creating the image
|
||||
if (complete_img(img, false)) {
|
||||
device_ctx->Unmap(texture.get(), 0);
|
||||
img_info.pData = nullptr;
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
std::copy_n((std::uint8_t *) img_info.pData, height * img_info.RowPitch, (std::uint8_t *) img->data);
|
||||
|
||||
// Unmap the staging texture to allow GPU access again
|
||||
device_ctx->Unmap(texture.get(), 0);
|
||||
img_info.pData = nullptr;
|
||||
|
||||
if (img) {
|
||||
img->frame_timestamp = frame_timestamp;
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e
|
||||
display_wgc_ram_t::release_snapshot() {
|
||||
return dup.release_frame();
|
||||
}
|
||||
} // namespace platf::dxgi
|
||||
/**
|
||||
* @file src/platform/windows/display_wgc.cpp
|
||||
* @brief Definitions for WinRT Windows.Graphics.Capture API
|
||||
*/
|
||||
// platform includes
|
||||
#include <dxgi1_2.h>
|
||||
|
||||
// local includes
|
||||
#include "display.h"
|
||||
#include "misc.h"
|
||||
#include "src/logging.h"
|
||||
|
||||
// Gross hack to work around MINGW-packages#22160
|
||||
#define ____FIReference_1_boolean_INTERFACE_DEFINED__
|
||||
|
||||
#include <windows.graphics.capture.interop.h>
|
||||
#include <winrt/windows.foundation.h>
|
||||
#include <winrt/windows.foundation.metadata.h>
|
||||
#include <winrt/windows.graphics.directx.direct3d11.h>
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
}
|
||||
|
||||
namespace winrt {
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Foundation::Metadata;
|
||||
using namespace Windows::Graphics::Capture;
|
||||
using namespace Windows::Graphics::DirectX::Direct3D11;
|
||||
|
||||
extern "C" {
|
||||
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows structures sometimes have compile-time GUIDs. GCC supports this, but in a roundabout way.
|
||||
* If WINRT_IMPL_HAS_DECLSPEC_UUID is true, then the compiler supports adding this attribute to a struct. For example, Visual Studio.
|
||||
* If not, then MinGW GCC has a workaround to assign a GUID to a structure.
|
||||
*/
|
||||
struct
|
||||
#if WINRT_IMPL_HAS_DECLSPEC_UUID
|
||||
__declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"))
|
||||
#endif
|
||||
IDirect3DDxgiInterfaceAccess: ::IUnknown {
|
||||
virtual HRESULT __stdcall GetInterface(REFIID id, void **object) = 0;
|
||||
};
|
||||
} // namespace winrt
|
||||
#if !WINRT_IMPL_HAS_DECLSPEC_UUID
|
||||
static constexpr GUID GUID__IDirect3DDxgiInterfaceAccess = {
|
||||
0xA9B3D012,
|
||||
0x3DF2,
|
||||
0x4EE3,
|
||||
{0xB8, 0xD1, 0x86, 0x95, 0xF4, 0x57, 0xD3, 0xC1}
|
||||
// compare with __declspec(uuid(...)) for the struct above.
|
||||
};
|
||||
|
||||
template<>
|
||||
constexpr auto __mingw_uuidof<winrt::IDirect3DDxgiInterfaceAccess>() -> GUID const & {
|
||||
return GUID__IDirect3DDxgiInterfaceAccess;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace platf::dxgi {
|
||||
wgc_capture_t::wgc_capture_t() {
|
||||
InitializeConditionVariable(&frame_present_cv);
|
||||
}
|
||||
|
||||
wgc_capture_t::~wgc_capture_t() {
|
||||
if (capture_session) {
|
||||
capture_session.Close();
|
||||
}
|
||||
if (frame_pool) {
|
||||
frame_pool.Close();
|
||||
}
|
||||
item = nullptr;
|
||||
capture_session = nullptr;
|
||||
frame_pool = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the Windows.Graphics.Capture backend.
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
int wgc_capture_t::init(display_base_t *display, const ::video::config_t &config) {
|
||||
HRESULT status;
|
||||
dxgi::dxgi_t dxgi;
|
||||
winrt::com_ptr<::IInspectable> d3d_comhandle;
|
||||
try {
|
||||
if (!winrt::GraphicsCaptureSession::IsSupported()) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows!"sv;
|
||||
return -1;
|
||||
}
|
||||
if (FAILED(status = display->device->QueryInterface(IID_IDXGIDevice, (void **) &dxgi))) {
|
||||
BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
if (FAILED(status = winrt::CreateDirect3D11DeviceFromDXGIDevice(*&dxgi, d3d_comhandle.put()))) {
|
||||
BOOST_LOG(error) << "Failed to query WinRT DirectX interface from device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
} catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to acquire device: [0x"sv << util::hex(e.code()).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
DXGI_OUTPUT_DESC output_desc;
|
||||
uwp_device = d3d_comhandle.as<winrt::IDirect3DDevice>();
|
||||
display->output->GetDesc(&output_desc);
|
||||
|
||||
auto monitor_factory = winrt::get_activation_factory<winrt::GraphicsCaptureItem, IGraphicsCaptureItemInterop>();
|
||||
if (monitor_factory == nullptr ||
|
||||
FAILED(status = monitor_factory->CreateForMonitor(output_desc.Monitor, winrt::guid_of<winrt::IGraphicsCaptureItem>(), winrt::put_abi(item)))) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to acquire display: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (config.dynamicRange) {
|
||||
display->capture_format = DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
} else {
|
||||
display->capture_format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
}
|
||||
|
||||
try {
|
||||
frame_pool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(uwp_device, static_cast<winrt::Windows::Graphics::DirectX::DirectXPixelFormat>(display->capture_format), 2, item.Size());
|
||||
capture_session = frame_pool.CreateCaptureSession(item);
|
||||
frame_pool.FrameArrived({this, &wgc_capture_t::on_frame_arrived});
|
||||
} catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to create capture session: [0x"sv << util::hex(e.code()).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
if (winrt::ApiInformation::IsPropertyPresent(L"Windows.Graphics.Capture.GraphicsCaptureSession", L"IsBorderRequired")) {
|
||||
capture_session.IsBorderRequired(false);
|
||||
} else {
|
||||
BOOST_LOG(warning) << "Can't disable colored border around capture area on this version of Windows";
|
||||
}
|
||||
} catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(warning) << "Screen capture may not be fully supported on this device for this release of Windows: failed to disable border around capture area: [0x"sv << util::hex(e.code()).to_string_view() << ']';
|
||||
}
|
||||
try {
|
||||
capture_session.StartCapture();
|
||||
} catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to start capture: [0x"sv << util::hex(e.code()).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function runs in a separate thread spawned by the frame pool and is a producer of frames.
|
||||
* To maintain parity with the original display interface, this frame will be consumed by the capture thread.
|
||||
* Acquire a read-write lock, make the produced frame available to the capture thread, then wake the capture thread.
|
||||
*/
|
||||
void wgc_capture_t::on_frame_arrived(winrt::Direct3D11CaptureFramePool const &sender, winrt::IInspectable const &) {
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame frame {nullptr};
|
||||
try {
|
||||
frame = sender.TryGetNextFrame();
|
||||
} catch (winrt::hresult_error &e) {
|
||||
BOOST_LOG(warning) << "Failed to capture frame: "sv << e.code();
|
||||
return;
|
||||
}
|
||||
if (frame != nullptr) {
|
||||
AcquireSRWLockExclusive(&frame_lock);
|
||||
if (produced_frame) {
|
||||
produced_frame.Close();
|
||||
}
|
||||
|
||||
produced_frame = frame;
|
||||
ReleaseSRWLockExclusive(&frame_lock);
|
||||
WakeConditionVariable(&frame_present_cv);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the next frame from the producer thread.
|
||||
* If not available, the capture thread blocks until one is, or the wait times out.
|
||||
* @param timeout how long to wait for the next frame
|
||||
* @param out a texture containing the frame just captured
|
||||
* @param out_time the timestamp of the frame just captured
|
||||
*/
|
||||
capture_e wgc_capture_t::next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time) {
|
||||
// this CONSUMER runs in the capture thread
|
||||
release_frame();
|
||||
|
||||
AcquireSRWLockExclusive(&frame_lock);
|
||||
if (produced_frame == nullptr && SleepConditionVariableSRW(&frame_present_cv, &frame_lock, timeout.count(), 0) == 0) {
|
||||
ReleaseSRWLockExclusive(&frame_lock);
|
||||
if (GetLastError() == ERROR_TIMEOUT) {
|
||||
return capture_e::timeout;
|
||||
} else {
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
if (produced_frame) {
|
||||
consumed_frame = produced_frame;
|
||||
produced_frame = nullptr;
|
||||
}
|
||||
ReleaseSRWLockExclusive(&frame_lock);
|
||||
if (consumed_frame == nullptr) { // spurious wakeup
|
||||
return capture_e::timeout;
|
||||
}
|
||||
|
||||
auto capture_access = consumed_frame.Surface().as<winrt::IDirect3DDxgiInterfaceAccess>();
|
||||
if (capture_access == nullptr) {
|
||||
return capture_e::error;
|
||||
}
|
||||
capture_access->GetInterface(IID_ID3D11Texture2D, (void **) out);
|
||||
out_time = consumed_frame.SystemRelativeTime().count(); // raw ticks from query performance counter
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e wgc_capture_t::release_frame() {
|
||||
if (consumed_frame != nullptr) {
|
||||
consumed_frame.Close();
|
||||
consumed_frame = nullptr;
|
||||
}
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
int wgc_capture_t::set_cursor_visible(bool x) {
|
||||
try {
|
||||
if (capture_session.IsCursorCaptureEnabled() != x) {
|
||||
capture_session.IsCursorCaptureEnabled(x);
|
||||
}
|
||||
return 0;
|
||||
} catch (winrt::hresult_error &) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int display_wgc_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
if (display_base_t::init(config, display_name) || dup.init(this, config)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
texture.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the next frame from the Windows.Graphics.Capture API and copy it into a new snapshot texture.
|
||||
* @param pull_free_image_cb call this to get a new free image from the video subsystem.
|
||||
* @param img_out the captured frame is returned here
|
||||
* @param timeout how long to wait for the next frame
|
||||
* @param cursor_visible whether to capture the cursor
|
||||
*/
|
||||
capture_e display_wgc_ram_t::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_visible) {
|
||||
HRESULT status;
|
||||
texture2d_t src;
|
||||
uint64_t frame_qpc;
|
||||
dup.set_cursor_visible(cursor_visible);
|
||||
auto capture_status = dup.next_frame(timeout, &src, frame_qpc);
|
||||
if (capture_status != capture_e::ok) {
|
||||
return capture_status;
|
||||
}
|
||||
|
||||
auto frame_timestamp = std::chrono::steady_clock::now() - qpc_time_difference(qpc_counter(), frame_qpc);
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
src->GetDesc(&desc);
|
||||
|
||||
// Create the staging texture if it doesn't exist. It should match the source in size and format.
|
||||
if (texture == nullptr) {
|
||||
capture_format = desc.Format;
|
||||
BOOST_LOG(info) << "Capture format ["sv << dxgi_format_to_string(capture_format) << ']';
|
||||
|
||||
D3D11_TEXTURE2D_DESC t {};
|
||||
t.Width = width;
|
||||
t.Height = height;
|
||||
t.MipLevels = 1;
|
||||
t.ArraySize = 1;
|
||||
t.SampleDesc.Count = 1;
|
||||
t.Usage = D3D11_USAGE_STAGING;
|
||||
t.Format = capture_format;
|
||||
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
|
||||
auto status = device->CreateTexture2D(&t, nullptr, &texture);
|
||||
|
||||
if (FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
|
||||
// It's possible for our display enumeration to race with mode changes and result in
|
||||
// mismatched image pool and desktop texture sizes. If this happens, just reinit again.
|
||||
if (desc.Width != width || desc.Height != height) {
|
||||
BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']';
|
||||
return capture_e::reinit;
|
||||
}
|
||||
// It's also possible for the capture format to change on the fly. If that happens,
|
||||
// reinitialize capture to try format detection again and create new images.
|
||||
if (capture_format != desc.Format) {
|
||||
BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']';
|
||||
return capture_e::reinit;
|
||||
}
|
||||
|
||||
// Copy from GPU to CPU
|
||||
device_ctx->CopyResource(texture.get(), src.get());
|
||||
|
||||
if (!pull_free_image_cb(img_out)) {
|
||||
return capture_e::interrupted;
|
||||
}
|
||||
auto img = (img_t *) img_out.get();
|
||||
|
||||
// Map the staging texture for CPU access (making it inaccessible for the GPU)
|
||||
if (FAILED(status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info))) {
|
||||
BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
// Now that we know the capture format, we can finish creating the image
|
||||
if (complete_img(img, false)) {
|
||||
device_ctx->Unmap(texture.get(), 0);
|
||||
img_info.pData = nullptr;
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
std::copy_n((std::uint8_t *) img_info.pData, height * img_info.RowPitch, (std::uint8_t *) img->data);
|
||||
|
||||
// Unmap the staging texture to allow GPU access again
|
||||
device_ctx->Unmap(texture.get(), 0);
|
||||
img_info.pData = nullptr;
|
||||
|
||||
if (img) {
|
||||
img->frame_timestamp = frame_timestamp;
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e display_wgc_ram_t::release_snapshot() {
|
||||
return dup.release_frame();
|
||||
}
|
||||
} // namespace platf::dxgi
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
|
@ -2,12 +2,15 @@
|
||||
* @file src/platform/windows/misc.cpp
|
||||
* @brief Miscellaneous definitions for Windows.
|
||||
*/
|
||||
// standard includes
|
||||
#include <csignal>
|
||||
#include <filesystem>
|
||||
#include <iomanip>
|
||||
#include <iterator>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
// lib includes
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/process/v1.hpp>
|
||||
@ -34,16 +37,14 @@
|
||||
#define NTDDI_VERSION NTDDI_WIN10
|
||||
#include <Shlwapi.h>
|
||||
|
||||
// local includes
|
||||
#include "misc.h"
|
||||
|
||||
#include "nvprefs/nvprefs_interface.h"
|
||||
#include "src/entry_handler.h"
|
||||
#include "src/globals.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
#include <iterator>
|
||||
|
||||
#include "nvprefs/nvprefs_interface.h"
|
||||
|
||||
// UDP_SEND_MSG_SIZE was added in the Windows 10 20H1 SDK
|
||||
#ifndef UDP_SEND_MSG_SIZE
|
||||
@ -63,16 +64,14 @@
|
||||
|
||||
#include <winternl.h>
|
||||
extern "C" {
|
||||
NTSTATUS NTAPI
|
||||
NtSetTimerResolution(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution);
|
||||
NTSTATUS NTAPI NtSetTimerResolution(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::atomic<bool> used_nt_set_timer_resolution = false;
|
||||
|
||||
bool
|
||||
nt_set_timer_resolution_max() {
|
||||
bool nt_set_timer_resolution_max() {
|
||||
ULONG minimum, maximum, current;
|
||||
if (!NT_SUCCESS(NtQueryTimerResolution(&minimum, &maximum, ¤t)) ||
|
||||
!NT_SUCCESS(NtSetTimerResolution(maximum, TRUE, ¤t))) {
|
||||
@ -81,8 +80,7 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
nt_set_timer_resolution_min() {
|
||||
bool nt_set_timer_resolution_min() {
|
||||
ULONG minimum, maximum, current;
|
||||
if (!NT_SUCCESS(NtQueryTimerResolution(&minimum, &maximum, ¤t)) ||
|
||||
!NT_SUCCESS(NtSetTimerResolution(minimum, TRUE, ¤t))) {
|
||||
@ -96,6 +94,7 @@ namespace {
|
||||
namespace bp = boost::process;
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf {
|
||||
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
|
||||
|
||||
@ -116,30 +115,26 @@ namespace platf {
|
||||
decltype(WlanEnumInterfaces) *fn_WlanEnumInterfaces = nullptr;
|
||||
decltype(WlanSetInterface) *fn_WlanSetInterface = nullptr;
|
||||
|
||||
std::filesystem::path
|
||||
appdata() {
|
||||
std::filesystem::path appdata() {
|
||||
WCHAR sunshine_path[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, sunshine_path, _countof(sunshine_path));
|
||||
return std::filesystem::path { sunshine_path }.remove_filename() / L"config"sv;
|
||||
return std::filesystem::path {sunshine_path}.remove_filename() / L"config"sv;
|
||||
}
|
||||
|
||||
std::string
|
||||
from_sockaddr(const sockaddr *const socket_address) {
|
||||
std::string from_sockaddr(const sockaddr *const socket_address) {
|
||||
char data[INET6_ADDRSTRLEN] = {};
|
||||
|
||||
auto family = socket_address->sa_family;
|
||||
if (family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *) socket_address)->sin6_addr, data, INET6_ADDRSTRLEN);
|
||||
}
|
||||
else if (family == AF_INET) {
|
||||
} else if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *) socket_address)->sin_addr, data, INET_ADDRSTRLEN);
|
||||
}
|
||||
|
||||
return std::string { data };
|
||||
return std::string {data};
|
||||
}
|
||||
|
||||
std::pair<std::uint16_t, std::string>
|
||||
from_sockaddr_ex(const sockaddr *const ip_addr) {
|
||||
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
|
||||
char data[INET6_ADDRSTRLEN] = {};
|
||||
|
||||
auto family = ip_addr->sa_family;
|
||||
@ -147,18 +142,16 @@ namespace platf {
|
||||
if (family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
|
||||
port = ((sockaddr_in6 *) ip_addr)->sin6_port;
|
||||
}
|
||||
else if (family == AF_INET) {
|
||||
} else if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
|
||||
port = ((sockaddr_in *) ip_addr)->sin_port;
|
||||
}
|
||||
|
||||
return { port, std::string { data } };
|
||||
return {port, std::string {data}};
|
||||
}
|
||||
|
||||
adapteraddrs_t
|
||||
get_adapteraddrs() {
|
||||
adapteraddrs_t info { nullptr };
|
||||
adapteraddrs_t get_adapteraddrs() {
|
||||
adapteraddrs_t info {nullptr};
|
||||
ULONG size = 0;
|
||||
|
||||
while (GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, info.get(), &size) == ERROR_BUFFER_OVERFLOW) {
|
||||
@ -168,8 +161,7 @@ namespace platf {
|
||||
return info;
|
||||
}
|
||||
|
||||
std::string
|
||||
get_mac_address(const std::string_view &address) {
|
||||
std::string get_mac_address(const std::string_view &address) {
|
||||
adapteraddrs_t info = get_adapteraddrs();
|
||||
for (auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) {
|
||||
for (auto addr_pos = adapter_pos->FirstUnicastAddress; addr_pos != nullptr; addr_pos = addr_pos->Next) {
|
||||
@ -190,8 +182,7 @@ namespace platf {
|
||||
return "00:00:00:00:00:00"s;
|
||||
}
|
||||
|
||||
HDESK
|
||||
syncThreadDesktop() {
|
||||
HDESK syncThreadDesktop() {
|
||||
auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL);
|
||||
if (!hDesk) {
|
||||
auto err = GetLastError();
|
||||
@ -210,23 +201,15 @@ namespace platf {
|
||||
return hDesk;
|
||||
}
|
||||
|
||||
void
|
||||
print_status(const std::string_view &prefix, HRESULT status) {
|
||||
void print_status(const std::string_view &prefix, HRESULT status) {
|
||||
char err_string[1024];
|
||||
|
||||
DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr,
|
||||
status,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
err_string,
|
||||
sizeof(err_string),
|
||||
nullptr);
|
||||
DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, status, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_string, sizeof(err_string), nullptr);
|
||||
|
||||
BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes };
|
||||
BOOST_LOG(error) << prefix << ": "sv << std::string_view {err_string, bytes};
|
||||
}
|
||||
|
||||
bool
|
||||
IsUserAdmin(HANDLE user_token) {
|
||||
bool IsUserAdmin(HANDLE user_token) {
|
||||
WINBOOL ret;
|
||||
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
||||
PSID AdministratorsGroup;
|
||||
@ -235,16 +218,21 @@ namespace platf {
|
||||
2,
|
||||
SECURITY_BUILTIN_DOMAIN_RID,
|
||||
DOMAIN_ALIAS_RID_ADMINS,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
&AdministratorsGroup);
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
&AdministratorsGroup
|
||||
);
|
||||
if (ret) {
|
||||
if (!CheckTokenMembership(user_token, AdministratorsGroup, &ret)) {
|
||||
ret = false;
|
||||
BOOST_LOG(error) << "Failed to verify token membership for administrative access: " << GetLastError();
|
||||
}
|
||||
FreeSid(AdministratorsGroup);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(error) << "Unable to allocate SID to check administrative access: " << GetLastError();
|
||||
}
|
||||
|
||||
@ -255,8 +243,7 @@ namespace platf {
|
||||
* @brief Obtain the current sessions user's primary token with elevated privileges.
|
||||
* @return The user's token. If user has admin capability it will be elevated, otherwise it will be a limited token. On error, `nullptr`.
|
||||
*/
|
||||
HANDLE
|
||||
retrieve_users_token(bool elevated) {
|
||||
HANDLE retrieve_users_token(bool elevated) {
|
||||
DWORD consoleSessionId;
|
||||
HANDLE userToken;
|
||||
TOKEN_ELEVATION_TYPE elevationType;
|
||||
@ -317,8 +304,7 @@ namespace platf {
|
||||
return userToken;
|
||||
}
|
||||
|
||||
bool
|
||||
merge_user_environment_block(bp::environment &env, HANDLE shell_token) {
|
||||
bool merge_user_environment_block(bp::environment &env, HANDLE shell_token) {
|
||||
// Get the target user's environment block
|
||||
PVOID env_block;
|
||||
if (!CreateEnvironmentBlock(&env_block, shell_token, FALSE)) {
|
||||
@ -328,13 +314,14 @@ namespace platf {
|
||||
// Parse the environment block and populate env
|
||||
for (auto c = (PWCHAR) env_block; *c != UNICODE_NULL; c += wcslen(c) + 1) {
|
||||
// Environment variable entries end with a null-terminator, so std::wstring() will get an entire entry.
|
||||
std::string env_tuple = to_utf8(std::wstring { c });
|
||||
std::string env_tuple = to_utf8(std::wstring {c});
|
||||
std::string env_name = env_tuple.substr(0, env_tuple.find('='));
|
||||
std::string env_val = env_tuple.substr(env_tuple.find('=') + 1);
|
||||
|
||||
// Perform a case-insensitive search to see if this variable name already exists
|
||||
auto itr = std::find_if(env.cbegin(), env.cend(),
|
||||
[&](const auto &e) { return boost::iequals(e.get_name(), env_name); });
|
||||
auto itr = std::find_if(env.cbegin(), env.cend(), [&](const auto &e) {
|
||||
return boost::iequals(e.get_name(), env_name);
|
||||
});
|
||||
if (itr != env.cend()) {
|
||||
// Use this existing name if it is already present to ensure we merge properly
|
||||
env_name = itr->get_name();
|
||||
@ -343,8 +330,7 @@ namespace platf {
|
||||
// For the PATH variable, we will merge the values together
|
||||
if (boost::iequals(env_name, "PATH")) {
|
||||
env[env_name] = env_val + ";" + env[env_name].to_string();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Other variables will be superseded by those in the user's environment block
|
||||
env[env_name] = env_val;
|
||||
}
|
||||
@ -358,8 +344,7 @@ namespace platf {
|
||||
* @brief Check if the current process is running with system-level privileges.
|
||||
* @return `true` if the current process has system-level privileges, `false` otherwise.
|
||||
*/
|
||||
bool
|
||||
is_running_as_system() {
|
||||
bool is_running_as_system() {
|
||||
BOOL ret;
|
||||
PSID SystemSid;
|
||||
DWORD dwSize = SECURITY_MAX_SID_SIZE;
|
||||
@ -379,8 +364,7 @@ namespace platf {
|
||||
BOOST_LOG(error) << "Failed to check token membership: " << GetLastError();
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(error) << "Failed to create a SID for the local system account. This may happen if the system is out of memory or if the SID buffer is too small: " << GetLastError();
|
||||
}
|
||||
|
||||
@ -390,14 +374,12 @@ namespace platf {
|
||||
}
|
||||
|
||||
// Note: This does NOT append a null terminator
|
||||
void
|
||||
append_string_to_environment_block(wchar_t *env_block, int &offset, const std::wstring &wstr) {
|
||||
void append_string_to_environment_block(wchar_t *env_block, int &offset, const std::wstring &wstr) {
|
||||
std::memcpy(&env_block[offset], wstr.data(), wstr.length() * sizeof(wchar_t));
|
||||
offset += wstr.length();
|
||||
}
|
||||
|
||||
std::wstring
|
||||
create_environment_block(bp::environment &env) {
|
||||
std::wstring create_environment_block(bp::environment &env) {
|
||||
int size = 0;
|
||||
for (const auto &entry : env) {
|
||||
auto name = entry.get_name();
|
||||
@ -426,8 +408,7 @@ namespace platf {
|
||||
return std::wstring(env_block, offset);
|
||||
}
|
||||
|
||||
LPPROC_THREAD_ATTRIBUTE_LIST
|
||||
allocate_proc_thread_attr_list(DWORD attribute_count) {
|
||||
LPPROC_THREAD_ATTRIBUTE_LIST allocate_proc_thread_attr_list(DWORD attribute_count) {
|
||||
SIZE_T size;
|
||||
InitializeProcThreadAttributeList(NULL, attribute_count, 0, &size);
|
||||
|
||||
@ -444,8 +425,7 @@ namespace platf {
|
||||
return list;
|
||||
}
|
||||
|
||||
void
|
||||
free_proc_thread_attr_list(LPPROC_THREAD_ATTRIBUTE_LIST list) {
|
||||
void free_proc_thread_attr_list(LPPROC_THREAD_ATTRIBUTE_LIST list) {
|
||||
DeleteProcThreadAttributeList(list);
|
||||
HeapFree(GetProcessHeap(), 0, list);
|
||||
}
|
||||
@ -458,8 +438,7 @@ namespace platf {
|
||||
* @param process_info A reference to a `PROCESS_INFORMATION` structure that contains information about the new process.
|
||||
* @return A `bp::child` object representing the new process, or an empty `bp::child` object if the launch failed.
|
||||
*/
|
||||
bp::child
|
||||
create_boost_child_from_results(bool process_launched, const std::string &cmd, std::error_code &ec, PROCESS_INFORMATION &process_info) {
|
||||
bp::child create_boost_child_from_results(bool process_launched, const std::string &cmd, std::error_code &ec, PROCESS_INFORMATION &process_info) {
|
||||
// Use RAII to ensure the process is closed when we're done with it, even if there was an error.
|
||||
auto close_process_handles = util::fail_guard([process_launched, process_info]() {
|
||||
if (process_launched) {
|
||||
@ -478,8 +457,7 @@ namespace platf {
|
||||
auto child = bp::child((bp::pid_t) process_info.dwProcessId);
|
||||
BOOST_LOG(info) << cmd << " running with PID "sv << child.id();
|
||||
return child;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
auto winerror = GetLastError();
|
||||
BOOST_LOG(error) << "Failed to launch process: "sv << winerror;
|
||||
ec = std::make_error_code(std::errc::invalid_argument);
|
||||
@ -496,8 +474,7 @@ namespace platf {
|
||||
* @param callback A function that will be executed while impersonating the user.
|
||||
* @return Object that will store any error that occurred during the impersonation
|
||||
*/
|
||||
std::error_code
|
||||
impersonate_current_user(HANDLE user_token, std::function<void()> callback) {
|
||||
std::error_code impersonate_current_user(HANDLE user_token, std::function<void()> callback) {
|
||||
std::error_code ec;
|
||||
// Impersonate the user when launching the process. This will ensure that appropriate access
|
||||
// checks are done against the user token, not our SYSTEM token. It will also allow network
|
||||
@ -534,8 +511,7 @@ namespace platf {
|
||||
* @param ec A reference to a `std::error_code` object that will store any error that occurred during the creation of the structure.
|
||||
* @return A structure that contains information about how to launch the new process.
|
||||
*/
|
||||
STARTUPINFOEXW
|
||||
create_startup_info(FILE *file, HANDLE *job, std::error_code &ec) {
|
||||
STARTUPINFOEXW create_startup_info(FILE *file, HANDLE *job, std::error_code &ec) {
|
||||
// Initialize a zeroed-out STARTUPINFOEXW structure and set its size
|
||||
STARTUPINFOEXW startup_info = {};
|
||||
startup_info.StartupInfo.cb = sizeof(startup_info);
|
||||
@ -563,13 +539,7 @@ namespace platf {
|
||||
//
|
||||
// Note: The value we point to here must be valid for the lifetime of the attribute list,
|
||||
// so we need to point into the STARTUPINFO instead of our log_file_variable on the stack.
|
||||
UpdateProcThreadAttribute(startup_info.lpAttributeList,
|
||||
0,
|
||||
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
|
||||
&startup_info.StartupInfo.hStdOutput,
|
||||
sizeof(startup_info.StartupInfo.hStdOutput),
|
||||
NULL,
|
||||
NULL);
|
||||
UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &startup_info.StartupInfo.hStdOutput, sizeof(startup_info.StartupInfo.hStdOutput), NULL, NULL);
|
||||
}
|
||||
|
||||
if (job) {
|
||||
@ -577,13 +547,7 @@ namespace platf {
|
||||
//
|
||||
// Note: The value we point to here must be valid for the lifetime of the attribute list,
|
||||
// so we take a HANDLE* instead of just a HANDLE to use the caller's stack storage.
|
||||
UpdateProcThreadAttribute(startup_info.lpAttributeList,
|
||||
0,
|
||||
PROC_THREAD_ATTRIBUTE_JOB_LIST,
|
||||
job,
|
||||
sizeof(*job),
|
||||
NULL,
|
||||
NULL);
|
||||
UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_JOB_LIST, job, sizeof(*job), NULL, NULL);
|
||||
}
|
||||
|
||||
return startup_info;
|
||||
@ -594,8 +558,7 @@ namespace platf {
|
||||
* @param token The primary token identifying the user to use, or `NULL` to restore original keys.
|
||||
* @return `true` if the override or restore operation was successful.
|
||||
*/
|
||||
bool
|
||||
override_per_user_predefined_keys(HANDLE token) {
|
||||
bool override_per_user_predefined_keys(HANDLE token) {
|
||||
HKEY user_classes_root = NULL;
|
||||
if (token) {
|
||||
auto err = RegOpenUserClassesRoot(token, 0, GENERIC_ALL, &user_classes_root);
|
||||
@ -651,8 +614,7 @@ namespace platf {
|
||||
* @param argument The raw argument to process.
|
||||
* @return An argument string suitable for use by CreateProcess().
|
||||
*/
|
||||
std::wstring
|
||||
escape_argument(const std::wstring &argument) {
|
||||
std::wstring escape_argument(const std::wstring &argument) {
|
||||
// If there are no characters requiring quoting/escaping, we're done
|
||||
if (argument.find_first_of(L" \t\n\v\"") == argument.npos) {
|
||||
return argument;
|
||||
@ -672,11 +634,9 @@ namespace platf {
|
||||
if (it == argument.end()) {
|
||||
escaped_arg.append(backslash_count * 2, L'\\');
|
||||
break;
|
||||
}
|
||||
else if (*it == L'"') {
|
||||
} else if (*it == L'"') {
|
||||
escaped_arg.append(backslash_count * 2 + 1, L'\\');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
escaped_arg.append(backslash_count, L'\\');
|
||||
}
|
||||
|
||||
@ -691,8 +651,7 @@ namespace platf {
|
||||
* @param argument An argument already escaped by `escape_argument()`.
|
||||
* @return An argument string suitable for use by cmd.exe.
|
||||
*/
|
||||
std::wstring
|
||||
escape_argument_for_cmd(const std::wstring &argument) {
|
||||
std::wstring escape_argument_for_cmd(const std::wstring &argument) {
|
||||
// Start with the original string and modify from there
|
||||
std::wstring escaped_arg = argument;
|
||||
|
||||
@ -716,8 +675,7 @@ namespace platf {
|
||||
* @param creation_flags The creation flags for CreateProcess(), which may be modified by this function.
|
||||
* @return A command string suitable for use by CreateProcess().
|
||||
*/
|
||||
std::wstring
|
||||
resolve_command_string(const std::string &raw_cmd, const std::wstring &working_dir, HANDLE token, DWORD &creation_flags) {
|
||||
std::wstring resolve_command_string(const std::string &raw_cmd, const std::wstring &working_dir, HANDLE token, DWORD &creation_flags) {
|
||||
std::wstring raw_cmd_w = from_utf8(raw_cmd);
|
||||
|
||||
// First, convert the given command into parts so we can get the executable/file/URL without parameters
|
||||
@ -744,16 +702,14 @@ namespace platf {
|
||||
|
||||
// If the target is a URL, the class is found using the URL scheme (prior to and not including the ':')
|
||||
lookup_string = scheme.data();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// If the target is not a URL, assume it's a regular file path
|
||||
auto extension = PathFindExtensionW(raw_target.c_str());
|
||||
if (extension == nullptr || *extension == 0) {
|
||||
// If the file has no extension, assume it's a command and allow CreateProcess()
|
||||
// to try to find it via PATH
|
||||
return from_utf8(raw_cmd);
|
||||
}
|
||||
else if (boost::iequals(extension, L".exe")) {
|
||||
} else if (boost::iequals(extension, L".exe")) {
|
||||
// If the file has an .exe extension, we will bypass the resolution here and
|
||||
// directly pass the unmodified command string to CreateProcess(). The argument
|
||||
// escaping rules are subtly different between CreateProcess() and ShellExecute(),
|
||||
@ -814,7 +770,7 @@ namespace platf {
|
||||
// uncommon ones that are unsupported here.
|
||||
//
|
||||
// https://web.archive.org/web/20111002101214/http://msdn.microsoft.com/en-us/library/windows/desktop/cc144101(v=vs.85).aspx
|
||||
std::wstring cmd_string { shell_command_string.data() };
|
||||
std::wstring cmd_string {shell_command_string.data()};
|
||||
size_t match_pos = 0;
|
||||
while ((match_pos = cmd_string.find_first_of(L'%', match_pos)) != std::wstring::npos) {
|
||||
std::wstring match_replacement;
|
||||
@ -843,19 +799,20 @@ namespace platf {
|
||||
case L'6':
|
||||
case L'7':
|
||||
case L'8':
|
||||
case L'9': {
|
||||
// Arguments numbers are 1-based, except for %0 which is equivalent to %1
|
||||
int index = next_char - L'0';
|
||||
if (next_char != L'0') {
|
||||
index--;
|
||||
}
|
||||
case L'9':
|
||||
{
|
||||
// Arguments numbers are 1-based, except for %0 which is equivalent to %1
|
||||
int index = next_char - L'0';
|
||||
if (next_char != L'0') {
|
||||
index--;
|
||||
}
|
||||
|
||||
// Replace with the matching argument, or nothing if the index is invalid
|
||||
if (index < raw_cmd_parts.size()) {
|
||||
match_replacement = raw_cmd_parts.at(index);
|
||||
// Replace with the matching argument, or nothing if the index is invalid
|
||||
if (index < raw_cmd_parts.size()) {
|
||||
match_replacement = raw_cmd_parts.at(index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// All arguments following the target
|
||||
case L'*':
|
||||
@ -878,29 +835,29 @@ namespace platf {
|
||||
// Long file path of target
|
||||
case L'l':
|
||||
case L'd':
|
||||
case L'v': {
|
||||
std::array<WCHAR, MAX_PATH> path;
|
||||
std::array<PCWCHAR, 2> other_dirs { working_dir.c_str(), nullptr };
|
||||
case L'v':
|
||||
{
|
||||
std::array<WCHAR, MAX_PATH> path;
|
||||
std::array<PCWCHAR, 2> other_dirs {working_dir.c_str(), nullptr};
|
||||
|
||||
// PathFindOnPath() is a little gross because it uses the same
|
||||
// buffer for input and output, so we need to copy our input
|
||||
// into the path array.
|
||||
std::wcsncpy(path.data(), raw_target.c_str(), path.size());
|
||||
if (path[path.size() - 1] != 0) {
|
||||
// The path was so long it was truncated by this copy. We'll
|
||||
// assume it was an absolute path (likely) and use it unmodified.
|
||||
match_replacement = raw_target;
|
||||
// PathFindOnPath() is a little gross because it uses the same
|
||||
// buffer for input and output, so we need to copy our input
|
||||
// into the path array.
|
||||
std::wcsncpy(path.data(), raw_target.c_str(), path.size());
|
||||
if (path[path.size() - 1] != 0) {
|
||||
// The path was so long it was truncated by this copy. We'll
|
||||
// assume it was an absolute path (likely) and use it unmodified.
|
||||
match_replacement = raw_target;
|
||||
}
|
||||
// See if we can find the path on our search path or working directory
|
||||
else if (PathFindOnPathW(path.data(), other_dirs.data())) {
|
||||
match_replacement = std::wstring {path.data()};
|
||||
} else {
|
||||
// We couldn't find the target, so we'll just hope for the best
|
||||
match_replacement = raw_target;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// See if we can find the path on our search path or working directory
|
||||
else if (PathFindOnPathW(path.data(), other_dirs.data())) {
|
||||
match_replacement = std::wstring { path.data() };
|
||||
}
|
||||
else {
|
||||
// We couldn't find the target, so we'll just hope for the best
|
||||
match_replacement = raw_target;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Working directory
|
||||
case L'w':
|
||||
@ -938,8 +895,7 @@ namespace platf {
|
||||
* @param group A pointer to a `bp::group` object to which the new process should belong (may be `nullptr`).
|
||||
* @return A `bp::child` object representing the new process, or an empty `bp::child` object if the launch fails.
|
||||
*/
|
||||
bp::child
|
||||
run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
|
||||
bp::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
|
||||
std::wstring start_dir = from_utf8(working_dir.string());
|
||||
HANDLE job = group ? group->native_handle() : nullptr;
|
||||
STARTUPINFOEXW startup_info = create_startup_info(file, job ? &job : nullptr, ec);
|
||||
@ -967,9 +923,11 @@ namespace platf {
|
||||
|
||||
// Find the PATH variable in our environment block using a case-insensitive search
|
||||
auto sunshine_wenv = boost::this_process::wenvironment();
|
||||
std::wstring path_var_name { L"PATH" };
|
||||
std::wstring path_var_name {L"PATH"};
|
||||
std::wstring old_path_val;
|
||||
auto itr = std::find_if(sunshine_wenv.cbegin(), sunshine_wenv.cend(), [&](const auto &e) { return boost::iequals(e.get_name(), path_var_name); });
|
||||
auto itr = std::find_if(sunshine_wenv.cbegin(), sunshine_wenv.cend(), [&](const auto &e) {
|
||||
return boost::iequals(e.get_name(), path_var_name);
|
||||
});
|
||||
if (itr != sunshine_wenv.cend()) {
|
||||
// Use the existing variable if it exists, since Boost treats these as case-sensitive.
|
||||
path_var_name = itr->get_name();
|
||||
@ -984,8 +942,7 @@ namespace platf {
|
||||
auto restore_path = util::fail_guard([&]() {
|
||||
if (old_path_val.empty()) {
|
||||
sunshine_wenv[path_var_name].clear();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
sunshine_wenv[path_var_name].assign(old_path_val);
|
||||
}
|
||||
});
|
||||
@ -1015,17 +972,7 @@ namespace platf {
|
||||
ec = impersonate_current_user(user_token, [&]() {
|
||||
std::wstring env_block = create_environment_block(cloned_env);
|
||||
std::wstring wcmd = resolve_command_string(cmd, start_dir, user_token, creation_flags);
|
||||
ret = CreateProcessAsUserW(user_token,
|
||||
NULL,
|
||||
(LPWSTR) wcmd.c_str(),
|
||||
NULL,
|
||||
NULL,
|
||||
!!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES),
|
||||
creation_flags,
|
||||
env_block.data(),
|
||||
start_dir.empty() ? NULL : start_dir.c_str(),
|
||||
(LPSTARTUPINFOW) &startup_info,
|
||||
&process_info);
|
||||
ret = CreateProcessAsUserW(user_token, NULL, (LPWSTR) wcmd.c_str(), NULL, NULL, !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES), creation_flags, env_block.data(), start_dir.empty() ? NULL : start_dir.c_str(), (LPSTARTUPINFOW) &startup_info, &process_info);
|
||||
});
|
||||
}
|
||||
// Otherwise, launch the process using CreateProcessW()
|
||||
@ -1049,16 +996,7 @@ namespace platf {
|
||||
|
||||
std::wstring env_block = create_environment_block(cloned_env);
|
||||
std::wstring wcmd = resolve_command_string(cmd, start_dir, NULL, creation_flags);
|
||||
ret = CreateProcessW(NULL,
|
||||
(LPWSTR) wcmd.c_str(),
|
||||
NULL,
|
||||
NULL,
|
||||
!!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES),
|
||||
creation_flags,
|
||||
env_block.data(),
|
||||
start_dir.empty() ? NULL : start_dir.c_str(),
|
||||
(LPSTARTUPINFOW) &startup_info,
|
||||
&process_info);
|
||||
ret = CreateProcessW(NULL, (LPWSTR) wcmd.c_str(), NULL, NULL, !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES), creation_flags, env_block.data(), start_dir.empty() ? NULL : start_dir.c_str(), (LPSTARTUPINFOW) &startup_info, &process_info);
|
||||
}
|
||||
|
||||
// Use the results of the launch to create a bp::child object
|
||||
@ -1069,8 +1007,7 @@ namespace platf {
|
||||
* @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) {
|
||||
boost::process::v1::environment _env = boost::this_process::environment();
|
||||
auto working_dir = boost::filesystem::path();
|
||||
std::error_code ec;
|
||||
@ -1078,15 +1015,13 @@ namespace platf {
|
||||
auto child = run_command(false, false, url, working_dir, _env, nullptr, ec, nullptr);
|
||||
if (ec) {
|
||||
BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(info) << "Opened url ["sv << url << "]"sv;
|
||||
child.detach();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
adjust_thread_priority(thread_priority_e priority) {
|
||||
void adjust_thread_priority(thread_priority_e priority) {
|
||||
int win32_priority;
|
||||
|
||||
switch (priority) {
|
||||
@ -1113,8 +1048,7 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
streaming_will_start() {
|
||||
void streaming_will_start() {
|
||||
static std::once_flag load_wlanapi_once_flag;
|
||||
std::call_once(load_wlanapi_once_flag, []() {
|
||||
// wlanapi.dll is not installed by default on Windows Server, so we load it dynamically
|
||||
@ -1150,8 +1084,7 @@ namespace platf {
|
||||
// Reduce timer period to 0.5ms
|
||||
if (nt_set_timer_resolution_max()) {
|
||||
used_nt_set_timer_resolution = true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(error) << "NtSetTimerResolution() failed, falling back to timeBeginPeriod()";
|
||||
timeBeginPeriod(1);
|
||||
used_nt_set_timer_resolution = false;
|
||||
@ -1186,8 +1119,7 @@ namespace platf {
|
||||
// https://docs.microsoft.com/en-us/windows-hardware/drivers/network/oid-wdi-set-connection-quality
|
||||
// https://docs.microsoft.com/en-us/previous-versions/windows/hardware/wireless/native-802-11-media-streaming
|
||||
BOOL value = TRUE;
|
||||
auto error = fn_WlanSetInterface(wlan_handle, &wlan_interface_list->InterfaceInfo[i].InterfaceGuid,
|
||||
wlan_intf_opcode_media_streaming_mode, sizeof(value), &value, nullptr);
|
||||
auto error = fn_WlanSetInterface(wlan_handle, &wlan_interface_list->InterfaceInfo[i].InterfaceGuid, wlan_intf_opcode_media_streaming_mode, sizeof(value), &value, nullptr);
|
||||
if (error == ERROR_SUCCESS) {
|
||||
BOOST_LOG(info) << "WLAN interface "sv << i << " is now in low latency mode"sv;
|
||||
}
|
||||
@ -1195,8 +1127,7 @@ namespace platf {
|
||||
}
|
||||
|
||||
fn_WlanFreeMemory(wlan_interface_list);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
fn_WlanCloseHandle(wlan_handle, nullptr);
|
||||
wlan_handle = NULL;
|
||||
}
|
||||
@ -1220,21 +1151,18 @@ namespace platf {
|
||||
if (SystemParametersInfoW(SPI_SETMOUSEKEYS, 0, &new_mouse_keys_state, 0)) {
|
||||
// Remember to restore the previous settings when we stop streaming
|
||||
enabled_mouse_keys = true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(warning) << "Unable to enable Mouse Keys: "sv << winerr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(warning) << "Unable to get current state of Mouse Keys: "sv << winerr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
streaming_will_stop() {
|
||||
void streaming_will_stop() {
|
||||
// Demote ourselves back to normal priority class
|
||||
SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
|
||||
|
||||
@ -1244,8 +1172,7 @@ namespace platf {
|
||||
if (!nt_set_timer_resolution_min()) {
|
||||
BOOST_LOG(error) << "nt_set_timer_resolution_min() failed even though nt_set_timer_resolution_max() succeeded";
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
timeEndPeriod(1);
|
||||
}
|
||||
|
||||
@ -1268,8 +1195,7 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
restart_on_exit() {
|
||||
void restart_on_exit() {
|
||||
STARTUPINFOEXW startup_info {};
|
||||
startup_info.StartupInfo.cb = sizeof(startup_info);
|
||||
|
||||
@ -1281,16 +1207,7 @@ namespace platf {
|
||||
}
|
||||
|
||||
PROCESS_INFORMATION process_info;
|
||||
if (!CreateProcessW(executable,
|
||||
GetCommandLineW(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
false,
|
||||
CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT,
|
||||
nullptr,
|
||||
nullptr,
|
||||
(LPSTARTUPINFOW) &startup_info,
|
||||
&process_info)) {
|
||||
if (!CreateProcessW(executable, GetCommandLineW(), nullptr, nullptr, false, CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, (LPSTARTUPINFOW) &startup_info, &process_info)) {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(fatal) << "Unable to restart Sunshine: "sv << winerr;
|
||||
return;
|
||||
@ -1300,8 +1217,7 @@ namespace platf {
|
||||
CloseHandle(process_info.hThread);
|
||||
}
|
||||
|
||||
void
|
||||
restart() {
|
||||
void restart() {
|
||||
// If we're running standalone, we have to respawn ourselves via CreateProcess().
|
||||
// If we're running from the service, we should just exit and let it respawn us.
|
||||
if (GetConsoleWindow() != NULL) {
|
||||
@ -1313,13 +1229,11 @@ namespace platf {
|
||||
lifetime::exit_sunshine(0, true);
|
||||
}
|
||||
|
||||
int
|
||||
set_env(const std::string &name, const std::string &value) {
|
||||
int set_env(const std::string &name, const std::string &value) {
|
||||
return _putenv_s(name.c_str(), value.c_str());
|
||||
}
|
||||
|
||||
int
|
||||
unset_env(const std::string &name) {
|
||||
int unset_env(const std::string &name) {
|
||||
return _putenv_s(name.c_str(), "");
|
||||
}
|
||||
|
||||
@ -1328,8 +1242,7 @@ namespace platf {
|
||||
bool requested_exit;
|
||||
};
|
||||
|
||||
static BOOL CALLBACK
|
||||
prgrp_enum_windows(HWND hwnd, LPARAM lParam) {
|
||||
static BOOL CALLBACK prgrp_enum_windows(HWND hwnd, LPARAM lParam) {
|
||||
auto enum_ctx = (enum_wnd_context_t *) lParam;
|
||||
|
||||
// Find the owner PID of this window
|
||||
@ -1345,8 +1258,7 @@ namespace platf {
|
||||
if (SendNotifyMessageW(hwnd, WM_CLOSE, 0, 0)) {
|
||||
BOOST_LOG(debug) << "Sent WM_CLOSE to PID: "sv << wnd_process_id;
|
||||
enum_ctx->requested_exit = true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
auto error = GetLastError();
|
||||
BOOST_LOG(warning) << "Failed to send WM_CLOSE to PID ["sv << wnd_process_id << "]: " << error;
|
||||
}
|
||||
@ -1356,8 +1268,7 @@ namespace platf {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool
|
||||
request_process_group_exit(std::uintptr_t native_handle) {
|
||||
bool request_process_group_exit(std::uintptr_t native_handle) {
|
||||
auto job_handle = (HANDLE) native_handle;
|
||||
|
||||
// Get list of all processes in our job object
|
||||
@ -1367,8 +1278,7 @@ namespace platf {
|
||||
auto fg = util::fail_guard([&process_id_list]() {
|
||||
free(process_id_list);
|
||||
});
|
||||
while (!(success = QueryInformationJobObject(job_handle, JobObjectBasicProcessIdList,
|
||||
process_id_list, required_length, &required_length)) &&
|
||||
while (!(success = QueryInformationJobObject(job_handle, JobObjectBasicProcessIdList, process_id_list, required_length, &required_length)) &&
|
||||
GetLastError() == ERROR_MORE_DATA) {
|
||||
free(process_id_list);
|
||||
process_id_list = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) calloc(1, required_length);
|
||||
@ -1381,8 +1291,7 @@ namespace platf {
|
||||
auto err = GetLastError();
|
||||
BOOST_LOG(warning) << "Failed to enumerate processes in group: "sv << err;
|
||||
return false;
|
||||
}
|
||||
else if (process_id_list->NumberOfProcessIdsInList == 0) {
|
||||
} else if (process_id_list->NumberOfProcessIdsInList == 0) {
|
||||
// If all processes are already dead, treat it as a success
|
||||
return true;
|
||||
}
|
||||
@ -1400,8 +1309,7 @@ namespace platf {
|
||||
return enum_ctx.requested_exit;
|
||||
}
|
||||
|
||||
bool
|
||||
process_group_running(std::uintptr_t native_handle) {
|
||||
bool process_group_running(std::uintptr_t native_handle) {
|
||||
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION accounting_info;
|
||||
|
||||
if (!QueryInformationJobObject((HANDLE) native_handle, JobObjectBasicAccountingInformation, &accounting_info, sizeof(accounting_info), nullptr)) {
|
||||
@ -1413,8 +1321,7 @@ namespace platf {
|
||||
return accounting_info.ActiveProcesses != 0;
|
||||
}
|
||||
|
||||
SOCKADDR_IN
|
||||
to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
|
||||
SOCKADDR_IN to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
|
||||
SOCKADDR_IN saddr_v4 = {};
|
||||
|
||||
saddr_v4.sin_family = AF_INET;
|
||||
@ -1426,8 +1333,7 @@ namespace platf {
|
||||
return saddr_v4;
|
||||
}
|
||||
|
||||
SOCKADDR_IN6
|
||||
to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
|
||||
SOCKADDR_IN6 to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
|
||||
SOCKADDR_IN6 saddr_v6 = {};
|
||||
|
||||
saddr_v6.sin6_family = AF_INET6;
|
||||
@ -1442,8 +1348,7 @@ namespace platf {
|
||||
|
||||
// Use UDP segmentation offload if it is supported by the OS. If the NIC is capable, this will use
|
||||
// hardware acceleration to reduce CPU usage. Support for USO was introduced in Windows 10 20H1.
|
||||
bool
|
||||
send_batch(batched_send_info_t &send_info) {
|
||||
bool send_batch(batched_send_info_t &send_info) {
|
||||
WSAMSG msg;
|
||||
|
||||
// Convert the target address into a SOCKADDR
|
||||
@ -1454,8 +1359,7 @@ namespace platf {
|
||||
|
||||
msg.name = (PSOCKADDR) &taddr_v6;
|
||||
msg.namelen = sizeof(taddr_v6);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
|
||||
|
||||
msg.name = (PSOCKADDR) &taddr_v4;
|
||||
@ -1477,8 +1381,7 @@ namespace platf {
|
||||
bufs[bufcount].len = send_info.payload_size;
|
||||
bufcount++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Translate buffer descriptors into WSABUFs
|
||||
auto payload_offset = send_info.block_offset * send_info.payload_size;
|
||||
auto payload_length = payload_offset + (send_info.block_count * send_info.payload_size);
|
||||
@ -1496,8 +1399,7 @@ namespace platf {
|
||||
msg.dwFlags = 0;
|
||||
|
||||
// At most, one DWORD option and one PKTINFO option
|
||||
char cmbuf[WSA_CMSG_SPACE(sizeof(DWORD)) +
|
||||
std::max(WSA_CMSG_SPACE(sizeof(IN6_PKTINFO)), WSA_CMSG_SPACE(sizeof(IN_PKTINFO)))] = {};
|
||||
char cmbuf[WSA_CMSG_SPACE(sizeof(DWORD)) + std::max(WSA_CMSG_SPACE(sizeof(IN6_PKTINFO)), WSA_CMSG_SPACE(sizeof(IN_PKTINFO)))] = {};
|
||||
ULONG cmbuflen = 0;
|
||||
|
||||
msg.Control.buf = cmbuf;
|
||||
@ -1517,8 +1419,7 @@ namespace platf {
|
||||
cm->cmsg_type = IPV6_PKTINFO;
|
||||
cm->cmsg_len = WSA_CMSG_LEN(sizeof(pktInfo));
|
||||
memcpy(WSA_CMSG_DATA(cm), &pktInfo, sizeof(pktInfo));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
IN_PKTINFO pktInfo;
|
||||
|
||||
SOCKADDR_IN saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0);
|
||||
@ -1550,8 +1451,7 @@ namespace platf {
|
||||
return WSASendMsg((SOCKET) send_info.native_socket, &msg, 0, &bytes_sent, nullptr, nullptr) != SOCKET_ERROR;
|
||||
}
|
||||
|
||||
bool
|
||||
send(send_info_t &send_info) {
|
||||
bool send(send_info_t &send_info) {
|
||||
WSAMSG msg;
|
||||
|
||||
// Convert the target address into a SOCKADDR
|
||||
@ -1562,8 +1462,7 @@ namespace platf {
|
||||
|
||||
msg.name = (PSOCKADDR) &taddr_v6;
|
||||
msg.namelen = sizeof(taddr_v6);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
|
||||
|
||||
msg.name = (PSOCKADDR) &taddr_v4;
|
||||
@ -1605,8 +1504,7 @@ namespace platf {
|
||||
cm->cmsg_type = IPV6_PKTINFO;
|
||||
cm->cmsg_len = WSA_CMSG_LEN(sizeof(pktInfo));
|
||||
memcpy(WSA_CMSG_DATA(cm), &pktInfo, sizeof(pktInfo));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
IN_PKTINFO pktInfo;
|
||||
|
||||
SOCKADDR_IN saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0);
|
||||
@ -1636,7 +1534,8 @@ namespace platf {
|
||||
class qos_t: public deinit_t {
|
||||
public:
|
||||
qos_t(QOS_FLOWID flow_id):
|
||||
flow_id(flow_id) {}
|
||||
flow_id(flow_id) {
|
||||
}
|
||||
|
||||
virtual ~qos_t() {
|
||||
if (!fn_QOSRemoveSocketFromFlow(qos_handle, (SOCKET) NULL, flow_id, 0)) {
|
||||
@ -1657,8 +1556,7 @@ 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) {
|
||||
SOCKADDR_IN saddr_v4;
|
||||
SOCKADDR_IN6 saddr_v6;
|
||||
PSOCKADDR dest_addr;
|
||||
@ -1693,7 +1591,7 @@ namespace platf {
|
||||
return;
|
||||
}
|
||||
|
||||
QOS_VERSION qos_version { 1, 0 };
|
||||
QOS_VERSION qos_version {1, 0};
|
||||
if (!fn_QOSCreateHandle(&qos_version, &qos_handle)) {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(warning) << "QOSCreateHandle() failed: "sv << winerr;
|
||||
@ -1733,15 +1631,13 @@ namespace platf {
|
||||
if (connect((SOCKET) native_socket, (PSOCKADDR) &saddr_v6, sizeof(saddr_v6)) < 0) {
|
||||
auto wsaerr = WSAGetLastError();
|
||||
BOOST_LOG(error) << "qWAVE dual-stack workaround failed: "sv << wsaerr;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
BOOST_LOG(debug) << "Using qWAVE connect() workaround for QoS tagging"sv;
|
||||
using_connect_hack = true;
|
||||
dest_addr = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
saddr_v4 = to_sockaddr(address.to_v4(), port);
|
||||
dest_addr = (PSOCKADDR) &saddr_v4;
|
||||
}
|
||||
@ -1768,15 +1664,16 @@ namespace platf {
|
||||
|
||||
return std::make_unique<qos_t>(flow_id);
|
||||
}
|
||||
int64_t
|
||||
qpc_counter() {
|
||||
|
||||
int64_t qpc_counter() {
|
||||
LARGE_INTEGER performance_counter;
|
||||
if (QueryPerformanceCounter(&performance_counter)) return performance_counter.QuadPart;
|
||||
if (QueryPerformanceCounter(&performance_counter)) {
|
||||
return performance_counter.QuadPart;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds
|
||||
qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2) {
|
||||
std::chrono::nanoseconds qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2) {
|
||||
auto get_frequency = []() {
|
||||
LARGE_INTEGER frequency;
|
||||
frequency.QuadPart = 0;
|
||||
@ -1790,8 +1687,7 @@ namespace platf {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring
|
||||
from_utf8(const std::string &string) {
|
||||
std::wstring from_utf8(const std::string &string) {
|
||||
// No conversion needed if the string is empty
|
||||
if (string.empty()) {
|
||||
return {};
|
||||
@ -1817,16 +1713,14 @@ namespace platf {
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string
|
||||
to_utf8(const std::wstring &string) {
|
||||
std::string to_utf8(const std::wstring &string) {
|
||||
// No conversion needed if the string is empty
|
||||
if (string.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Get the output size required to store the string
|
||||
auto output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(),
|
||||
nullptr, 0, nullptr, nullptr);
|
||||
auto output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(), nullptr, 0, nullptr, nullptr);
|
||||
if (output_size == 0) {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(error) << "Failed to get UTF-8 buffer size: "sv << winerr;
|
||||
@ -1835,8 +1729,7 @@ namespace platf {
|
||||
|
||||
// Perform the conversion
|
||||
std::string output(output_size, '\0');
|
||||
output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(),
|
||||
output.data(), output.size(), nullptr, nullptr);
|
||||
output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(), output.data(), output.size(), nullptr, nullptr);
|
||||
if (output_size == 0) {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(error) << "Failed to convert string to UTF-8: "sv << winerr;
|
||||
@ -1846,8 +1739,7 @@ namespace platf {
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string
|
||||
get_host_name() {
|
||||
std::string get_host_name() {
|
||||
WCHAR hostname[256];
|
||||
if (GetHostNameW(hostname, ARRAYSIZE(hostname)) == SOCKET_ERROR) {
|
||||
BOOST_LOG(error) << "GetHostNameW() failed: "sv << WSAGetLastError();
|
||||
@ -1870,11 +1762,12 @@ namespace platf {
|
||||
}
|
||||
|
||||
~win32_high_precision_timer() {
|
||||
if (timer) CloseHandle(timer);
|
||||
if (timer) {
|
||||
CloseHandle(timer);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sleep_for(const std::chrono::nanoseconds &duration) override {
|
||||
void sleep_for(const std::chrono::nanoseconds &duration) override {
|
||||
if (!timer) {
|
||||
BOOST_LOG(error) << "Attempting high_precision_timer::sleep_for() with uninitialized timer";
|
||||
return;
|
||||
@ -1902,8 +1795,7 @@ namespace platf {
|
||||
HANDLE timer = NULL;
|
||||
};
|
||||
|
||||
std::unique_ptr<high_precision_timer>
|
||||
create_high_precision_timer() {
|
||||
std::unique_ptr<high_precision_timer> create_high_precision_timer() {
|
||||
return std::make_unique<win32_high_precision_timer>();
|
||||
}
|
||||
} // namespace platf
|
||||
|
@ -4,36 +4,33 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <chrono>
|
||||
#include <string_view>
|
||||
|
||||
// platform includes
|
||||
#include <windows.h>
|
||||
#include <winnt.h>
|
||||
|
||||
namespace platf {
|
||||
void
|
||||
print_status(const std::string_view &prefix, HRESULT status);
|
||||
HDESK
|
||||
syncThreadDesktop();
|
||||
void print_status(const std::string_view &prefix, HRESULT status);
|
||||
HDESK syncThreadDesktop();
|
||||
|
||||
int64_t
|
||||
qpc_counter();
|
||||
int64_t qpc_counter();
|
||||
|
||||
std::chrono::nanoseconds
|
||||
qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2);
|
||||
std::chrono::nanoseconds qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2);
|
||||
|
||||
/**
|
||||
* @brief Convert a UTF-8 string into a UTF-16 wide string.
|
||||
* @param string The UTF-8 string.
|
||||
* @return The converted UTF-16 wide string.
|
||||
*/
|
||||
std::wstring
|
||||
from_utf8(const std::string &string);
|
||||
std::wstring from_utf8(const std::string &string);
|
||||
|
||||
/**
|
||||
* @brief Convert a UTF-16 wide string into a UTF-8 string.
|
||||
* @param string The UTF-16 wide string.
|
||||
* @return The converted UTF-8 string.
|
||||
*/
|
||||
std::string
|
||||
to_utf8(const std::wstring &string);
|
||||
std::string to_utf8(const std::wstring &string);
|
||||
} // namespace platf
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user