style: adjust clang-format rules (#2186)

Co-authored-by: Vithorio Polten <reach@vithor.io>
This commit is contained in:
ReenigneArcher 2025-01-19 22:34:47 -05:00 committed by GitHub
commit c2420427b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
158 changed files with 8754 additions and 9994 deletions

View file

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

View file

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

View file

@ -2,16 +2,18 @@
* @file src/audio.cpp * @file src/audio.cpp
* @brief Definitions for audio capture and encoding. * @brief Definitions for audio capture and encoding.
*/ */
// standard includes
#include <thread> #include <thread>
// lib includes
#include <opus/opus_multistream.h> #include <opus/opus_multistream.h>
#include "platform/common.h" // local includes
#include "audio.h" #include "audio.h"
#include "config.h" #include "config.h"
#include "globals.h" #include "globals.h"
#include "logging.h" #include "logging.h"
#include "platform/common.h"
#include "thread_safe.h" #include "thread_safe.h"
#include "utility.h" #include "utility.h"
@ -20,15 +22,11 @@ namespace audio {
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>; using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<float>>>; using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<float>>>;
static int static int start_audio_control(audio_ctx_t &ctx);
start_audio_control(audio_ctx_t &ctx); static void stop_audio_control(audio_ctx_t &);
static void static void apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params);
stop_audio_control(audio_ctx_t &);
static void
apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params);
int int map_stream(int channels, bool quality);
map_stream(int channels, bool quality);
constexpr auto SAMPLE_RATE = 48000; constexpr auto SAMPLE_RATE = 48000;
@ -85,8 +83,7 @@ namespace audio {
}, },
}; };
void void encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
auto packets = mail::man->queue<packet_t>(mail::audio_packets); auto packets = mail::man->queue<packet_t>(mail::audio_packets);
auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])]; auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
if (config.flags[config_t::CUSTOM_SURROUND_PARAMS]) { if (config.flags[config_t::CUSTOM_SURROUND_PARAMS]) {
@ -103,7 +100,8 @@ namespace audio {
stream.coupledStreams, stream.coupledStreams,
stream.mapping, stream.mapping,
OPUS_APPLICATION_RESTRICTED_LOWDELAY, 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_BITRATE(stream.bitrate));
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_VBR(0)); opus_multistream_encoder_ctl(opus.get(), OPUS_SET_VBR(0));
@ -129,8 +127,7 @@ namespace audio {
} }
} }
void void capture(safe::mail_t mail, config_t config, void *channel_data) {
capture(safe::mail_t mail, config_t config, void *channel_data) {
auto shutdown_event = mail->event<bool>(mail::shutdown); auto shutdown_event = mail->event<bool>(mail::shutdown);
auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])]; auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
if (config.flags[config_t::CUSTOM_SURROUND_PARAMS]) { if (config.flags[config_t::CUSTOM_SURROUND_PARAMS]) {
@ -243,14 +240,12 @@ namespace audio {
} }
} }
audio_ctx_ref_t audio_ctx_ref_t get_audio_ctx_ref() {
get_audio_ctx_ref() {
static auto control_shared {safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control)}; static auto control_shared {safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control)};
return control_shared.ref(); return control_shared.ref();
} }
bool bool is_audio_ctx_sink_available(const audio_ctx_t &ctx) {
is_audio_ctx_sink_available(const audio_ctx_t &ctx) {
if (!ctx.control) { if (!ctx.control) {
return false; return false;
} }
@ -263,8 +258,7 @@ namespace audio {
return ctx.control->is_sink_available(sink); return ctx.control->is_sink_available(sink);
} }
int int map_stream(int channels, bool quality) {
map_stream(int channels, bool quality) {
int shift = quality ? 1 : 0; int shift = quality ? 1 : 0;
switch (channels) { switch (channels) {
case 2: case 2:
@ -277,8 +271,7 @@ namespace audio {
return STEREO; return STEREO;
} }
int int start_audio_control(audio_ctx_t &ctx) {
start_audio_control(audio_ctx_t &ctx) {
auto fg = util::fail_guard([]() { auto fg = util::fail_guard([]() {
BOOST_LOG(warning) << "There will be no audio"sv; BOOST_LOG(warning) << "There will be no audio"sv;
}); });
@ -305,8 +298,7 @@ namespace audio {
return 0; return 0;
} }
void void stop_audio_control(audio_ctx_t &ctx) {
stop_audio_control(audio_ctx_t &ctx) {
// restore audio-sink if applicable // restore audio-sink if applicable
if (!ctx.restore_sink) { if (!ctx.restore_sink) {
return; return;
@ -320,8 +312,7 @@ namespace audio {
} }
} }
void void apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params) {
apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params) {
stream.channelCount = params.channelCount; stream.channelCount = params.channelCount;
stream.streams = params.streams; stream.streams = params.streams;
stream.coupledStreams = params.coupledStreams; stream.coupledStreams = params.coupledStreams;

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@
* @file src/config.cpp * @file src/config.cpp
* @brief Definitions for the configuration of Sunshine. * @brief Definitions for the configuration of Sunshine.
*/ */
// standard includes
#include <algorithm> #include <algorithm>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
@ -11,21 +12,22 @@
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
// lib includes
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
// local includes
#include "config.h" #include "config.h"
#include "entry_handler.h" #include "entry_handler.h"
#include "file_handler.h" #include "file_handler.h"
#include "logging.h" #include "logging.h"
#include "nvhttp.h" #include "nvhttp.h"
#include "platform/common.h"
#include "rtsp.h" #include "rtsp.h"
#include "utility.h" #include "utility.h"
#include "platform/common.h"
#ifdef _WIN32 #ifdef _WIN32
#include <shellapi.h> #include <shellapi.h>
#endif #endif
@ -43,15 +45,21 @@ using namespace std::literals;
#define CERTIFICATE_FILE CA_DIR "/cacert.pem" #define CERTIFICATE_FILE CA_DIR "/cacert.pem"
#define APPS_JSON_PATH platf::appdata().string() + "/apps.json" #define APPS_JSON_PATH platf::appdata().string() + "/apps.json"
namespace config { namespace config {
namespace nv { namespace nv {
nvenc::nvenc_two_pass nvenc::nvenc_two_pass twopass_from_view(const std::string_view &preset) {
twopass_from_view(const std::string_view &preset) { if (preset == "disabled") {
if (preset == "disabled") return nvenc::nvenc_two_pass::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; 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; BOOST_LOG(warning) << "config: unknown nvenc_twopass value: " << preset;
return nvenc::nvenc_two_pass::quarter_resolution; return nvenc::nvenc_two_pass::quarter_resolution;
} }
@ -179,10 +187,10 @@ namespace config {
}; };
template<class T> template<class T>
std::optional<int> std::optional<int> quality_from_view(const std::string_view &quality_type, const std::optional<int>(&original)) {
quality_from_view(const std::string_view &quality_type, const std::optional<int>(&original)) {
#define _CONVERT_(x) \ #define _CONVERT_(x) \
if (quality_type == #x##sv) return (int) T::x if (quality_type == #x##sv) \
return (int) T::x
_CONVERT_(balanced); _CONVERT_(balanced);
_CONVERT_(quality); _CONVERT_(quality);
_CONVERT_(speed); _CONVERT_(speed);
@ -191,10 +199,10 @@ namespace config {
} }
template<class T> template<class T>
std::optional<int> std::optional<int> rc_from_view(const std::string_view &rc, const std::optional<int>(&original)) {
rc_from_view(const std::string_view &rc, const std::optional<int>(&original)) {
#define _CONVERT_(x) \ #define _CONVERT_(x) \
if (rc == #x##sv) return (int) T::x if (rc == #x##sv) \
return (int) T::x
_CONVERT_(cbr); _CONVERT_(cbr);
_CONVERT_(cqp); _CONVERT_(cqp);
_CONVERT_(vbr_latency); _CONVERT_(vbr_latency);
@ -204,10 +212,10 @@ namespace config {
} }
template<class T> template<class T>
std::optional<int> std::optional<int> usage_from_view(const std::string_view &usage, const std::optional<int>(&original)) {
usage_from_view(const std::string_view &usage, const std::optional<int>(&original)) {
#define _CONVERT_(x) \ #define _CONVERT_(x) \
if (usage == #x##sv) return (int) T::x if (usage == #x##sv) \
return (int) T::x
_CONVERT_(lowlatency); _CONVERT_(lowlatency);
_CONVERT_(lowlatency_high_quality); _CONVERT_(lowlatency_high_quality);
_CONVERT_(transcoding); _CONVERT_(transcoding);
@ -217,11 +225,16 @@ namespace config {
return original; return original;
} }
int int coder_from_view(const std::string_view &coder) {
coder_from_view(const std::string_view &coder) { if (coder == "auto"sv) {
if (coder == "auto"sv) return _auto; return _auto;
if (coder == "cabac"sv || coder == "ac"sv) return cabac; }
if (coder == "cavlc"sv || coder == "vlc"sv) return cavlc; if (coder == "cabac"sv || coder == "ac"sv) {
return cabac;
}
if (coder == "cavlc"sv || coder == "vlc"sv) {
return cavlc;
}
return _auto; return _auto;
} }
@ -244,10 +257,10 @@ namespace config {
disabled = false ///< Disabled disabled = false ///< Disabled
}; };
std::optional<int> std::optional<int> preset_from_view(const std::string_view &preset) {
preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x) \ #define _CONVERT_(x) \
if (preset == #x##sv) return x if (preset == #x##sv) \
return x
_CONVERT_(veryslow); _CONVERT_(veryslow);
_CONVERT_(slower); _CONVERT_(slower);
_CONVERT_(slow); _CONVERT_(slow);
@ -259,11 +272,16 @@ namespace config {
return std::nullopt; return std::nullopt;
} }
std::optional<int> std::optional<int> coder_from_view(const std::string_view &coder) {
coder_from_view(const std::string_view &coder) { if (coder == "auto"sv) {
if (coder == "auto"sv) return _auto; return _auto;
if (coder == "cabac"sv || coder == "ac"sv) return disabled; }
if (coder == "cavlc"sv || coder == "vlc"sv) return enabled; if (coder == "cabac"sv || coder == "ac"sv) {
return disabled;
}
if (coder == "cavlc"sv || coder == "vlc"sv) {
return enabled;
}
return std::nullopt; return std::nullopt;
} }
@ -277,32 +295,40 @@ namespace config {
cavlc ///< CAVLC cavlc ///< CAVLC
}; };
int int coder_from_view(const std::string_view &coder) {
coder_from_view(const std::string_view &coder) { if (coder == "auto"sv) {
if (coder == "auto"sv) return _auto; return _auto;
if (coder == "cabac"sv || coder == "ac"sv) return cabac; }
if (coder == "cavlc"sv || coder == "vlc"sv) return cavlc; if (coder == "cabac"sv || coder == "ac"sv) {
return cabac;
}
if (coder == "cavlc"sv || coder == "vlc"sv) {
return cavlc;
}
return -1; return -1;
} }
int int allow_software_from_view(const std::string_view &software) {
allow_software_from_view(const std::string_view &software) { if (software == "allowed"sv || software == "forced") {
if (software == "allowed"sv || software == "forced") return 1; return 1;
}
return 0; return 0;
} }
int int force_software_from_view(const std::string_view &software) {
force_software_from_view(const std::string_view &software) { if (software == "forced") {
if (software == "forced") return 1; return 1;
}
return 0; return 0;
} }
int int rt_from_view(const std::string_view &rt) {
rt_from_view(const std::string_view &rt) { if (rt == "disabled" || rt == "off" || rt == "0") {
if (rt == "disabled" || rt == "off" || rt == "0") return 0; return 0;
}
return 1; return 1;
} }
@ -310,10 +336,10 @@ namespace config {
} // namespace vt } // namespace vt
namespace sw { namespace sw {
int int svtav1_preset_from_view(const std::string_view &preset) {
svtav1_preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x, y) \ #define _CONVERT_(x, y) \
if (preset == #x##sv) return y if (preset == #x##sv) \
return y
_CONVERT_(veryslow, 1); _CONVERT_(veryslow, 1);
_CONVERT_(slower, 2); _CONVERT_(slower, 2);
_CONVERT_(slow, 4); _CONVERT_(slow, 4);
@ -329,10 +355,10 @@ namespace config {
} // namespace sw } // namespace sw
namespace dd { namespace dd {
video_t::dd_t::config_option_e video_t::dd_t::config_option_e config_option_from_view(const std::string_view value) {
config_option_from_view(const std::string_view value) {
#define _CONVERT_(x) \ #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_(disabled);
_CONVERT_(verify_only); _CONVERT_(verify_only);
_CONVERT_(ensure_active); _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 return video_t::dd_t::config_option_e::disabled; // Default to this if value is invalid
} }
video_t::dd_t::resolution_option_e video_t::dd_t::resolution_option_e resolution_option_from_view(const std::string_view value) {
resolution_option_from_view(const std::string_view value) {
#define _CONVERT_2_ARG_(str, val) \ #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) #define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
_CONVERT_(disabled); _CONVERT_(disabled);
_CONVERT_2_ARG_(auto, automatic); _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 return video_t::dd_t::resolution_option_e::disabled; // Default to this if value is invalid
} }
video_t::dd_t::refresh_rate_option_e video_t::dd_t::refresh_rate_option_e refresh_rate_option_from_view(const std::string_view value) {
refresh_rate_option_from_view(const std::string_view value) {
#define _CONVERT_2_ARG_(str, val) \ #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) #define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
_CONVERT_(disabled); _CONVERT_(disabled);
_CONVERT_2_ARG_(auto, automatic); _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 return video_t::dd_t::refresh_rate_option_e::disabled; // Default to this if value is invalid
} }
video_t::dd_t::hdr_option_e video_t::dd_t::hdr_option_e hdr_option_from_view(const std::string_view value) {
hdr_option_from_view(const std::string_view value) {
#define _CONVERT_2_ARG_(str, val) \ #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) #define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
_CONVERT_(disabled); _CONVERT_(disabled);
_CONVERT_2_ARG_(auto, automatic); _CONVERT_2_ARG_(auto, automatic);
@ -380,8 +406,7 @@ namespace config {
return video_t::dd_t::hdr_option_e::disabled; // Default to this if value is invalid return video_t::dd_t::hdr_option_e::disabled; // Default to this if value is invalid
} }
video_t::dd_t::mode_remapping_t video_t::dd_t::mode_remapping_t mode_remapping_from_view(const std::string_view value) {
mode_remapping_from_view(const std::string_view value) {
const auto parse_entry_list {[](const auto &entry_list, auto &output_field) { const auto parse_entry_list {[](const auto &entry_list, auto &output_field) {
for (auto &[_, entry] : entry_list) { for (auto &[_, entry] : entry_list) {
auto requested_resolution = entry.template get_optional<std::string>("requested_resolution"s); auto requested_resolution = entry.template get_optional<std::string>("requested_resolution"s);
@ -393,7 +418,8 @@ namespace config {
requested_resolution.value_or(""), requested_resolution.value_or(""),
requested_fps.value_or(""), requested_fps.value_or(""),
final_resolution.value_or(""), final_resolution.value_or(""),
final_refresh_rate.value_or("") }); final_refresh_rate.value_or("")
});
} }
}}; }};
@ -556,23 +582,19 @@ namespace config {
{}, // prep commands {}, // prep commands
}; };
bool bool endline(char ch) {
endline(char ch) {
return ch == '\r' || ch == '\n'; return ch == '\r' || ch == '\n';
} }
bool bool space_tab(char ch) {
space_tab(char ch) {
return ch == ' ' || ch == '\t'; return ch == ' ' || ch == '\t';
} }
bool bool whitespace(char ch) {
whitespace(char ch) {
return space_tab(ch) || endline(ch); return space_tab(ch) || endline(ch);
} }
std::string std::string to_string(const char *begin, const char *end) {
to_string(const char *begin, const char *end) {
std::string result; std::string result;
KITTY_WHILE_LOOP(auto pos = begin, pos != end, { KITTY_WHILE_LOOP(auto pos = begin, pos != end, {
@ -588,8 +610,7 @@ namespace config {
} }
template<class It> template<class It>
It It skip_list(It skipper, It end) {
skip_list(It skipper, It end) {
int stack = 1; int stack = 1;
while (skipper != end && stack) { while (skipper != end && stack) {
if (*skipper == '[') { if (*skipper == '[') {
@ -638,11 +659,11 @@ namespace config {
return std::make_pair( return std::make_pair(
endl, 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> std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content) {
parse_config(const std::string_view &file_content) {
std::unordered_map<std::string, std::string> vars; std::unordered_map<std::string, std::string> vars;
auto pos = std::begin(file_content); auto pos = std::begin(file_content);
@ -667,8 +688,7 @@ namespace config {
return vars; return vars;
} }
void void string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
auto it = vars.find(name); auto it = vars.find(name);
if (it == std::end(vars)) { if (it == std::end(vars)) {
return; return;
@ -680,8 +700,7 @@ namespace config {
} }
template<typename T, typename F> template<typename T, typename F>
void void generic_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, T &input, F &&f) {
generic_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, T &input, F &&f) {
std::string tmp; std::string tmp;
string_f(vars, name, tmp); string_f(vars, name, tmp);
if (!tmp.empty()) { if (!tmp.empty()) {
@ -689,8 +708,7 @@ namespace config {
} }
} }
void 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) {
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; std::string temp;
string_f(vars, name, temp); string_f(vars, name, temp);
@ -702,8 +720,7 @@ namespace config {
} }
} }
void void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
// appdata needs to be retrieved once only // appdata needs to be retrieved once only
static auto appdata = platf::appdata(); static auto appdata = platf::appdata();
@ -727,8 +744,7 @@ namespace config {
} }
} }
void void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
fs::path temp = input; fs::path temp = input;
path_f(vars, name, temp); path_f(vars, name, temp);
@ -736,8 +752,7 @@ namespace config {
input = temp.string(); input = temp.string();
} }
void void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
auto it = vars.find(name); auto it = vars.find(name);
if (it == std::end(vars)) { if (it == std::end(vars)) {
@ -754,16 +769,14 @@ namespace config {
// If that integer is in hexadecimal // If that integer is in hexadecimal
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) { if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
input = util::from_hex<int>(val.substr(2)); input = util::from_hex<int>(val.substr(2));
} } else {
else {
input = util::from_view(val); input = util::from_view(val);
} }
vars.erase(it); vars.erase(it);
} }
void void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
auto it = vars.find(name); auto it = vars.find(name);
if (it == std::end(vars)) { if (it == std::end(vars)) {
@ -780,8 +793,7 @@ namespace config {
// If that integer is in hexadecimal // If that integer is in hexadecimal
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) { if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
input = util::from_hex<int>(val.substr(2)); input = util::from_hex<int>(val.substr(2));
} } else {
else {
input = util::from_view(val); input = util::from_view(val);
} }
@ -789,8 +801,7 @@ namespace config {
} }
template<class F> template<class F>
void void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
std::string tmp; std::string tmp;
string_f(vars, name, tmp); string_f(vars, name, tmp);
if (!tmp.empty()) { if (!tmp.empty()) {
@ -799,8 +810,7 @@ namespace config {
} }
template<class F> template<class F>
void void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
std::string tmp; std::string tmp;
string_f(vars, name, tmp); string_f(vars, name, tmp);
if (!tmp.empty()) { if (!tmp.empty()) {
@ -808,8 +818,7 @@ namespace config {
} }
} }
void 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_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 temp = input;
int_f(vars, name, temp); int_f(vars, name, temp);
@ -820,9 +829,10 @@ namespace config {
} }
} }
bool bool to_bool(std::string &boolean) {
to_bool(std::string &boolean) { std::for_each(std::begin(boolean), std::end(boolean), [](char ch) {
std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char) std::tolower(ch); }); return (char) std::tolower(ch);
});
return boolean == "true"sv || return boolean == "true"sv ||
boolean == "yes"sv || boolean == "yes"sv ||
@ -832,8 +842,7 @@ namespace config {
(std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean)); (std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean));
} }
void void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
std::string tmp; std::string tmp;
string_f(vars, name, tmp); string_f(vars, name, tmp);
@ -844,8 +853,7 @@ namespace config {
input = to_bool(tmp); input = to_bool(tmp);
} }
void void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
std::string tmp; std::string tmp;
string_f(vars, name, tmp); string_f(vars, name, tmp);
@ -863,8 +871,7 @@ namespace config {
input = val; input = val;
} }
void 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_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 temp = input;
double_f(vars, name, temp); double_f(vars, name, temp);
@ -875,8 +882,7 @@ namespace config {
} }
} }
void void list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
std::string string; std::string string;
string_f(vars, name, string); string_f(vars, name, string);
@ -900,15 +906,12 @@ namespace config {
while (pos < std::cend(string)) { while (pos < std::cend(string)) {
if (*pos == '[') { if (*pos == '[') {
pos = skip_list(pos + 1, std::cend(string)) + 1; pos = skip_list(pos + 1, std::cend(string)) + 1;
} } else if (*pos == ']') {
else if (*pos == ']') {
break; break;
} } else if (*pos == ',') {
else if (*pos == ',') {
input.emplace_back(begin, pos); input.emplace_back(begin, pos);
pos = begin = std::find_if_not(pos + 1, std::cend(string), whitespace); pos = begin = std::find_if_not(pos + 1, std::cend(string), whitespace);
} } else {
else {
++pos; ++pos;
} }
} }
@ -918,8 +921,7 @@ namespace config {
} }
} }
void void list_prep_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &input) {
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; std::string string;
string_f(vars, name, string); string_f(vars, name, string);
@ -945,8 +947,7 @@ namespace config {
} }
} }
void void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
std::vector<std::string> list; std::vector<std::string> list;
list_string_f(vars, name, list); list_string_f(vars, name, list);
@ -972,16 +973,14 @@ namespace config {
// If the integer is a hexadecimal // If the integer is a hexadecimal
if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) { if (val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
tmp = util::from_hex<int>(val.substr(2)); tmp = util::from_hex<int>(val.substr(2));
} } else {
else {
tmp = util::from_view(val); tmp = util::from_view(val);
} }
input.emplace_back(tmp); input.emplace_back(tmp);
} }
} }
void void map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
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; std::vector<int> list;
list_int_f(vars, name, list); list_int_f(vars, name, list);
@ -1000,8 +999,7 @@ namespace config {
} }
} }
int int apply_flags(const char *line) {
apply_flags(const char *line) {
int ret = 0; int ret = 0;
while (*line != '\0') { while (*line != '\0') {
switch (*line) { switch (*line) {
@ -1028,8 +1026,7 @@ namespace config {
return ret; return ret;
} }
std::vector<std::string_view> & std::vector<std::string_view> &get_supported_gamepad_options() {
get_supported_gamepad_options() {
const auto options = platf::supported_gamepads(nullptr); const auto options = platf::supported_gamepads(nullptr);
static std::vector<std::string_view> opts {}; static std::vector<std::string_view> opts {};
opts.reserve(options.size()); opts.reserve(options.size());
@ -1039,8 +1036,7 @@ namespace config {
return opts; return opts;
} }
void void apply_config(std::unordered_map<std::string, std::string> &&vars) {
apply_config(std::unordered_map<std::string, std::string> &&vars) {
if (!fs::exists(stream.file_apps.c_str())) { if (!fs::exists(stream.file_apps.c_str())) {
fs::copy_file(SUNSHINE_ASSETS_DIR "/apps.json", stream.file_apps); fs::copy_file(SUNSHINE_ASSETS_DIR "/apps.json", stream.file_apps);
} }
@ -1259,26 +1255,19 @@ namespace config {
if (!log_level_string.empty()) { if (!log_level_string.empty()) {
if (log_level_string == "verbose"sv) { if (log_level_string == "verbose"sv) {
sunshine.min_log_level = 0; sunshine.min_log_level = 0;
} } else if (log_level_string == "debug"sv) {
else if (log_level_string == "debug"sv) {
sunshine.min_log_level = 1; sunshine.min_log_level = 1;
} } else if (log_level_string == "info"sv) {
else if (log_level_string == "info"sv) {
sunshine.min_log_level = 2; sunshine.min_log_level = 2;
} } else if (log_level_string == "warning"sv) {
else if (log_level_string == "warning"sv) {
sunshine.min_log_level = 3; sunshine.min_log_level = 3;
} } else if (log_level_string == "error"sv) {
else if (log_level_string == "error"sv) {
sunshine.min_log_level = 4; sunshine.min_log_level = 4;
} } else if (log_level_string == "fatal"sv) {
else if (log_level_string == "fatal"sv) {
sunshine.min_log_level = 5; sunshine.min_log_level = 5;
} } else if (log_level_string == "none"sv) {
else if (log_level_string == "none"sv) {
sunshine.min_log_level = 6; sunshine.min_log_level = 6;
} } else {
else {
// accept digit directly // accept digit directly
auto val = log_level_string[0]; auto val = log_level_string[0];
if (val >= '0' && val < '7') { if (val >= '0' && val < '7') {
@ -1301,8 +1290,7 @@ namespace config {
} }
} }
int int parse(int argc, char *argv[]) {
parse(int argc, char *argv[]) {
std::unordered_map<std::string, std::string> cmd_vars; std::unordered_map<std::string, std::string> cmd_vars;
#ifdef _WIN32 #ifdef _WIN32
bool shortcut_launch = false; bool shortcut_launch = false;
@ -1319,8 +1307,7 @@ namespace config {
#ifdef _WIN32 #ifdef _WIN32
else if (line == "--shortcut"sv) { else if (line == "--shortcut"sv) {
shortcut_launch = true; shortcut_launch = true;
} } else if (line == "--shortcut-admin"sv) {
else if (line == "--shortcut-admin"sv) {
service_admin_launch = true; service_admin_launch = true;
} }
#endif #endif
@ -1336,15 +1323,13 @@ namespace config {
logging::print_help(*argv); logging::print_help(*argv);
return -1; return -1;
} }
} } else {
else {
auto line_end = line + strlen(line); auto line_end = line + strlen(line);
auto pos = std::find(line, line_end, '='); auto pos = std::find(line, line_end, '=');
if (pos == line_end) { if (pos == line_end) {
sunshine.config_file = line; sunshine.config_file = line;
} } else {
else {
TUPLE_EL(var, 1, parse_option(line, line_end)); TUPLE_EL(var, 1, parse_option(line, line_end));
if (!var) { if (!var) {
logging::print_help(*argv); logging::print_help(*argv);
@ -1385,11 +1370,9 @@ namespace config {
// the path is incorrect or inaccessible. // the path is incorrect or inaccessible.
apply_config(std::move(vars)); apply_config(std::move(vars));
config_loaded = true; 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(); 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(); 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 // Always return 1 to ensure Sunshine doesn't start normally
return 1; return 1;
} }
else if (shortcut_launch) { if (shortcut_launch) {
if (!service_ctrl::is_service_running()) { if (!service_ctrl::is_service_running()) {
// If the service isn't running, relaunch ourselves as admin to start it // If the service isn't running, relaunch ourselves as admin to start it
WCHAR executable[MAX_PATH]; WCHAR executable[MAX_PATH];

View file

@ -4,6 +4,7 @@
*/ */
#pragma once #pragma once
// standard includes
#include <bitset> #include <bitset>
#include <chrono> #include <chrono>
#include <optional> #include <optional>
@ -11,6 +12,7 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
// local includes
#include "nvenc/nvenc_config.h" #include "nvenc/nvenc_config.h"
namespace config { namespace config {
@ -22,6 +24,7 @@ namespace config {
int av1_mode; int av1_mode;
int min_threads; // Minimum number of threads/slices for CPU encoding int min_threads; // Minimum number of threads/slices for CPU encoding
struct { struct {
std::string sw_preset; std::string sw_preset;
std::string sw_tune; std::string sw_tune;
@ -204,17 +207,25 @@ namespace config {
CONST_PIN, ///< Use "universal" pin CONST_PIN, ///< Use "universal" pin
FLAG_SIZE ///< Number of flags FLAG_SIZE ///< Number of flags
}; };
} } // namespace flag
struct prep_cmd_t { struct prep_cmd_t {
prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd, bool &&elevated): prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd, bool &&elevated):
do_cmd(std::move(do_cmd)), undo_cmd(std::move(undo_cmd)), elevated(std::move(elevated)) {} 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): 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 do_cmd;
std::string undo_cmd; std::string undo_cmd;
bool elevated; bool elevated;
}; };
struct sunshine_t { struct sunshine_t {
std::string locale; std::string locale;
int min_log_level; int min_log_level;
@ -248,8 +259,6 @@ namespace config {
extern input_t input; extern input_t input;
extern sunshine_t sunshine; extern sunshine_t sunshine;
int int parse(int argc, char *argv[]);
parse(int argc, char *argv[]); 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);
} // namespace config } // namespace config

View file

@ -6,25 +6,22 @@
*/ */
#define BOOST_BIND_GLOBAL_PLACEHOLDERS #define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include "process.h" // standard includes
#include <filesystem> #include <filesystem>
#include <set> #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/json_parser.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.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/crypto.hpp>
#include <Simple-Web-Server/server_https.hpp> #include <Simple-Web-Server/server_https.hpp>
#include <boost/asio/ssl/context_base.hpp>
// local includes
#include "config.h" #include "config.h"
#include "confighttp.h" #include "confighttp.h"
#include "crypto.h" #include "crypto.h"
@ -36,6 +33,7 @@
#include "network.h" #include "network.h"
#include "nvhttp.h" #include "nvhttp.h"
#include "platform/common.h" #include "platform/common.h"
#include "process.h"
#include "rtsp.h" #include "rtsp.h"
#include "utility.h" #include "utility.h"
#include "uuid.h" #include "uuid.h"
@ -62,8 +60,7 @@ namespace confighttp {
* @brief Log the request details. * @brief Log the request details.
* @param request The HTTP request object. * @param request The HTTP request object.
*/ */
void void print_req(const req_https_t &request) {
print_req(const req_https_t &request) {
BOOST_LOG(debug) << "METHOD :: "sv << request->method; BOOST_LOG(debug) << "METHOD :: "sv << request->method;
BOOST_LOG(debug) << "DESTINATION :: "sv << request->path; BOOST_LOG(debug) << "DESTINATION :: "sv << request->path;
@ -85,8 +82,7 @@ namespace confighttp {
* @param response The HTTP response object. * @param response The HTTP response object.
* @param output_tree The JSON tree to send. * @param output_tree The JSON tree to send.
*/ */
void void send_response(resp_https_t response, const pt::ptree &output_tree) {
send_response(resp_https_t response, const pt::ptree &output_tree) {
std::ostringstream data; std::ostringstream data;
pt::write_json(data, output_tree); pt::write_json(data, output_tree);
response->write(data.str()); response->write(data.str());
@ -97,8 +93,7 @@ namespace confighttp {
* @param response The HTTP response object. * @param response The HTTP response object.
* @param request The HTTP request object. * @param request The HTTP request object.
*/ */
void void send_unauthorized(resp_https_t response, req_https_t request) {
send_unauthorized(resp_https_t response, req_https_t request) {
auto address = net::addr_to_normalized_string(request->remote_endpoint().address()); auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv; BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
const SimpleWeb::CaseInsensitiveMultimap headers { const SimpleWeb::CaseInsensitiveMultimap headers {
@ -113,8 +108,7 @@ namespace confighttp {
* @param request The HTTP request object. * @param request The HTTP request object.
* @param path The path to redirect to. * @param path The path to redirect to.
*/ */
void void send_redirect(resp_https_t response, req_https_t request, const char *path) {
send_redirect(resp_https_t response, req_https_t request, const char *path) {
auto address = net::addr_to_normalized_string(request->remote_endpoint().address()); auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv; BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
const SimpleWeb::CaseInsensitiveMultimap headers { const SimpleWeb::CaseInsensitiveMultimap headers {
@ -129,8 +123,7 @@ namespace confighttp {
* @param request The HTTP request object. * @param request The HTTP request object.
* @return True if the user is authenticated, false otherwise. * @return True if the user is authenticated, false otherwise.
*/ */
bool bool authenticate(resp_https_t response, req_https_t request) {
authenticate(resp_https_t response, req_https_t request) {
auto address = net::addr_to_normalized_string(request->remote_endpoint().address()); auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
auto ip_type = net::from_address(address); auto ip_type = net::from_address(address);
@ -180,8 +173,7 @@ namespace confighttp {
* @param response The HTTP response object. * @param response The HTTP response object.
* @param request The HTTP request object. * @param request The HTTP request object.
*/ */
void void not_found(resp_https_t response, [[maybe_unused]] req_https_t request) {
not_found(resp_https_t response, [[maybe_unused]] req_https_t request) {
constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_not_found; constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_not_found;
pt::ptree tree; pt::ptree tree;
@ -203,8 +195,7 @@ namespace confighttp {
* @param request The HTTP request object. * @param request The HTTP request object.
* @param error_message The error message to include in the response. * @param error_message The error message to include in the response.
*/ */
void void bad_request(resp_https_t response, [[maybe_unused]] req_https_t request, const std::string &error_message = "Bad Request") {
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; constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_bad_request;
pt::ptree tree; pt::ptree tree;
@ -227,9 +218,10 @@ namespace confighttp {
* @param request The HTTP request object. * @param request The HTTP request object.
* @todo combine these functions into a single function that accepts the page, i.e "index", "pin", "apps" * @todo combine these functions into a single function that accepts the page, i.e "index", "pin", "apps"
*/ */
void void getIndexPage(resp_https_t response, req_https_t request) {
getIndexPage(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -244,9 +236,10 @@ namespace confighttp {
* @param response The HTTP response object. * @param response The HTTP response object.
* @param request The HTTP request object. * @param request The HTTP request object.
*/ */
void void getPinPage(resp_https_t response, req_https_t request) {
getPinPage(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -261,9 +254,10 @@ namespace confighttp {
* @param response The HTTP response object. * @param response The HTTP response object.
* @param request The HTTP request object. * @param request The HTTP request object.
*/ */
void void getAppsPage(resp_https_t response, req_https_t request) {
getAppsPage(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -279,9 +273,10 @@ namespace confighttp {
* @param response The HTTP response object. * @param response The HTTP response object.
* @param request The HTTP request object. * @param request The HTTP request object.
*/ */
void void getClientsPage(resp_https_t response, req_https_t request) {
getClientsPage(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -296,9 +291,10 @@ namespace confighttp {
* @param response The HTTP response object. * @param response The HTTP response object.
* @param request The HTTP request object. * @param request The HTTP request object.
*/ */
void void getConfigPage(resp_https_t response, req_https_t request) {
getConfigPage(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -313,9 +309,10 @@ namespace confighttp {
* @param response The HTTP response object. * @param response The HTTP response object.
* @param request The HTTP request object. * @param request The HTTP request object.
*/ */
void void getPasswordPage(resp_https_t response, req_https_t request) {
getPasswordPage(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -330,8 +327,7 @@ namespace confighttp {
* @param response The HTTP response object. * @param response The HTTP response object.
* @param request The HTTP request object. * @param request The HTTP request object.
*/ */
void void getWelcomePage(resp_https_t response, req_https_t request) {
getWelcomePage(resp_https_t response, req_https_t request) {
print_req(request); print_req(request);
if (!config::sunshine.username.empty()) { if (!config::sunshine.username.empty()) {
send_redirect(response, request, "/"); send_redirect(response, request, "/");
@ -348,9 +344,10 @@ namespace confighttp {
* @param response The HTTP response object. * @param response The HTTP response object.
* @param request The HTTP request object. * @param request The HTTP request object.
*/ */
void void getTroubleshootingPage(resp_https_t response, req_https_t request) {
getTroubleshootingPage(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -367,8 +364,7 @@ namespace confighttp {
* @todo combine function with getSunshineLogoImage and possibly getNodeModules * @todo combine function with getSunshineLogoImage and possibly getNodeModules
* @todo use mime_types map * @todo use mime_types map
*/ */
void void getFaviconImage(resp_https_t response, req_https_t request) {
getFaviconImage(resp_https_t response, req_https_t request) {
print_req(request); print_req(request);
std::ifstream in(WEB_DIR "images/sunshine.ico", std::ios::binary); 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 combine function with getFaviconImage and possibly getNodeModules
* @todo use mime_types map * @todo use mime_types map
*/ */
void void getSunshineLogoImage(resp_https_t response, req_https_t request) {
getSunshineLogoImage(resp_https_t response, req_https_t request) {
print_req(request); print_req(request);
std::ifstream in(WEB_DIR "images/logo-sunshine-45.png", std::ios::binary); 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. * @param query The path to check.
* @return True if the path is a child of the base path, false otherwise. * @return True if the path is a child of the base path, false otherwise.
*/ */
bool bool isChildPath(fs::path const &base, fs::path const &query) {
isChildPath(fs::path const &base, fs::path const &query) {
auto relPath = fs::relative(base, query); auto relPath = fs::relative(base, query);
return *(relPath.begin()) != fs::path(".."); return *(relPath.begin()) != fs::path("..");
} }
@ -411,8 +405,7 @@ namespace confighttp {
* @param response The HTTP response object. * @param response The HTTP response object.
* @param request The HTTP request object. * @param request The HTTP request object.
*/ */
void void getNodeModules(resp_https_t response, req_https_t request) {
getNodeModules(resp_https_t response, req_https_t request) {
print_req(request); print_req(request);
fs::path webDirPath(WEB_DIR); fs::path webDirPath(WEB_DIR);
fs::path nodeModulesPath(webDirPath / "assets"); fs::path nodeModulesPath(webDirPath / "assets");
@ -455,9 +448,10 @@ namespace confighttp {
* *
* @api_examples{/api/apps| GET| null} * @api_examples{/api/apps| GET| null}
*/ */
void void getApps(resp_https_t response, req_https_t request) {
getApps(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -474,9 +468,10 @@ namespace confighttp {
* *
* @api_examples{/api/logs| GET| null} * @api_examples{/api/logs| GET| null}
*/ */
void void getLogs(resp_https_t response, req_https_t request) {
getLogs(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -518,9 +513,10 @@ namespace confighttp {
* *
* @api_examples{/api/apps| POST| {"name":"Hello, World!","index":-1}} * @api_examples{/api/apps| POST| {"name":"Hello, World!","index":-1}}
*/ */
void void saveApp(resp_https_t response, req_https_t request) {
saveApp(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -551,16 +547,14 @@ namespace confighttp {
if (index == -1) { if (index == -1) {
apps_node.push_back(std::make_pair("", inputTree)); 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 // Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick
pt::ptree newApps; pt::ptree newApps;
int i = 0; int i = 0;
for (const auto &[k, v] : apps_node) { for (const auto &[k, v] : apps_node) {
if (i == index) { if (i == index) {
newApps.push_back(std::make_pair("", inputTree)); newApps.push_back(std::make_pair("", inputTree));
} } else {
else {
newApps.push_back(std::make_pair("", v)); newApps.push_back(std::make_pair("", v));
} }
i++; i++;
@ -590,8 +584,7 @@ namespace confighttp {
outputTree.put("status", true); outputTree.put("status", true);
send_response(response, outputTree); send_response(response, outputTree);
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(warning) << "SaveApp: "sv << e.what(); BOOST_LOG(warning) << "SaveApp: "sv << e.what();
bad_request(response, request, e.what()); bad_request(response, request, e.what());
} }
@ -604,9 +597,10 @@ namespace confighttp {
* *
* @api_examples{/api/apps/9999| DELETE| null} * @api_examples{/api/apps/9999| DELETE| null}
*/ */
void void deleteApp(resp_https_t response, req_https_t request) {
deleteApp(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -622,8 +616,7 @@ namespace confighttp {
std::string error; std::string error;
if (const int max_index = static_cast<int>(apps_node.size()) - 1; max_index < 0) { if (const int max_index = static_cast<int>(apps_node.size()) - 1; max_index < 0) {
error = "No applications to delete"; error = "No applications to delete";
} } else {
else {
error = "'index' out of range, max index is "s + std::to_string(max_index); error = "'index' out of range, max index is "s + std::to_string(max_index);
} }
bad_request(response, request, error); bad_request(response, request, error);
@ -646,8 +639,7 @@ namespace confighttp {
outputTree.put("status", true); outputTree.put("status", true);
outputTree.put("result", "application "s + std::to_string(index) + " deleted"); outputTree.put("result", "application "s + std::to_string(index) + " deleted");
send_response(response, outputTree); send_response(response, outputTree);
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(warning) << "DeleteApp: "sv << e.what(); BOOST_LOG(warning) << "DeleteApp: "sv << e.what();
bad_request(response, request, 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"}} * @api_examples{/api/covers/upload| POST| {"key":"igdb_1234","url":"https://images.igdb.com/igdb/image/upload/t_cover_big_2x/abc123.png"}}
*/ */
void void uploadCover(resp_https_t response, req_https_t request) {
uploadCover(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
std::stringstream ss; std::stringstream ss;
std::stringstream configStream; std::stringstream configStream;
@ -678,8 +671,7 @@ namespace confighttp {
pt::ptree inputTree; pt::ptree inputTree;
try { try {
pt::read_json(ss, inputTree); pt::read_json(ss, inputTree);
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(warning) << "UploadCover: "sv << e.what(); BOOST_LOG(warning) << "UploadCover: "sv << e.what();
bad_request(response, request, e.what()); bad_request(response, request, e.what());
return; return;
@ -705,8 +697,7 @@ namespace confighttp {
bad_request(response, request, "Failed to download cover"); bad_request(response, request, "Failed to download cover");
return; return;
} }
} } else {
else {
auto data = SimpleWeb::Crypto::Base64::decode(inputTree.get<std::string>("data")); auto data = SimpleWeb::Crypto::Base64::decode(inputTree.get<std::string>("data"));
std::ofstream imgfile(path); std::ofstream imgfile(path);
@ -724,9 +715,10 @@ namespace confighttp {
* *
* @api_examples{/api/config| GET| null} * @api_examples{/api/config| GET| null}
*/ */
void void getConfig(resp_https_t response, req_https_t request) {
getConfig(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -751,8 +743,7 @@ namespace confighttp {
* *
* @api_examples{/api/configLocale| GET| null} * @api_examples{/api/configLocale| GET| null}
*/ */
void void getLocale(resp_https_t response, req_https_t request) {
getLocale(resp_https_t response, req_https_t request) {
// we need to return the locale whether authenticated or not // we need to return the locale whether authenticated or not
print_req(request); print_req(request);
@ -778,9 +769,10 @@ namespace confighttp {
* *
* @api_examples{/api/config| POST| {"key":"value"}} * @api_examples{/api/config| POST| {"key":"value"}}
*/ */
void void saveConfig(resp_https_t response, req_https_t request) {
saveConfig(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -794,15 +786,16 @@ namespace confighttp {
pt::read_json(ss, inputTree); pt::read_json(ss, inputTree);
for (const auto &[k, v] : inputTree) { for (const auto &[k, v] : inputTree) {
std::string value = inputTree.get<std::string>(k); 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; configStream << k << " = " << value << std::endl;
} }
file_handler::write_file(config::sunshine.config_file.c_str(), configStream.str()); file_handler::write_file(config::sunshine.config_file.c_str(), configStream.str());
outputTree.put("status", true); outputTree.put("status", true);
send_response(response, outputTree); send_response(response, outputTree);
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(warning) << "SaveConfig: "sv << e.what(); BOOST_LOG(warning) << "SaveConfig: "sv << e.what();
bad_request(response, request, e.what()); bad_request(response, request, e.what());
} }
@ -815,9 +808,10 @@ namespace confighttp {
* *
* @api_examples{/api/restart| POST| null} * @api_examples{/api/restart| POST| null}
*/ */
void void restart(resp_https_t response, req_https_t request) {
restart(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -832,9 +826,10 @@ namespace confighttp {
* *
* @api_examples{/api/reset-display-device-persistence| POST| null} * @api_examples{/api/reset-display-device-persistence| POST| null}
*/ */
void void resetDisplayDevicePersistence(resp_https_t response, req_https_t request) {
resetDisplayDevicePersistence(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -860,9 +855,10 @@ namespace confighttp {
* *
* @api_examples{/api/password| POST| {"currentUsername":"admin","currentPassword":"admin","newUsername":"admin","newPassword":"admin","confirmNewPassword":"admin"}} * @api_examples{/api/password| POST| {"currentUsername":"admin","currentPassword":"admin","newUsername":"admin","newPassword":"admin","confirmNewPassword":"admin"}}
*/ */
void void savePassword(resp_https_t response, req_https_t request) {
savePassword(resp_https_t response, req_https_t request) { if (!config::sunshine.username.empty() && !authenticate(response, request)) {
if (!config::sunshine.username.empty() && !authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -881,31 +877,29 @@ namespace confighttp {
auto password = inputTree.count("currentPassword") > 0 ? inputTree.get<std::string>("currentPassword") : ""; auto password = inputTree.count("currentPassword") > 0 ? inputTree.get<std::string>("currentPassword") : "";
auto newPassword = inputTree.count("newPassword") > 0 ? inputTree.get<std::string>("newPassword") : ""; auto newPassword = inputTree.count("newPassword") > 0 ? inputTree.get<std::string>("newPassword") : "";
auto confirmPassword = inputTree.count("confirmNewPassword") > 0 ? inputTree.get<std::string>("confirmNewPassword") : ""; 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) { if (newUsername.length() == 0) {
errors.emplace_back("Invalid Username"); errors.emplace_back("Invalid Username");
} } else {
else {
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string(); 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 (config::sunshine.username.empty() || (boost::iequals(username, config::sunshine.username) && hash == config::sunshine.password)) {
if (newPassword.empty() || newPassword != confirmPassword) { if (newPassword.empty() || newPassword != confirmPassword) {
errors.emplace_back("Password Mismatch"); errors.emplace_back("Password Mismatch");
} } else {
else {
http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword); http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword);
http::reload_user_creds(config::sunshine.credentials_file); http::reload_user_creds(config::sunshine.credentials_file);
outputTree.put("status", true); outputTree.put("status", true);
} }
} } else {
else {
errors.emplace_back("Invalid Current Credentials"); errors.emplace_back("Invalid Current Credentials");
} }
} }
if (!errors.empty()) { if (!errors.empty()) {
// join the errors array // join the errors array
std::string error = std::accumulate(errors.begin(), errors.end(), std::string(), std::string error = std::accumulate(errors.begin(), errors.end(), std::string(), [](const std::string &a, const std::string &b) {
[](const std::string &a, const std::string &b) {
return a.empty() ? b : a + ", " + b; return a.empty() ? b : a + ", " + b;
}); });
bad_request(response, request, error); bad_request(response, request, error);
@ -913,8 +907,7 @@ namespace confighttp {
} }
send_response(response, outputTree); send_response(response, outputTree);
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(warning) << "SavePassword: "sv << e.what(); BOOST_LOG(warning) << "SavePassword: "sv << e.what();
bad_request(response, request, e.what()); bad_request(response, request, e.what());
} }
@ -934,9 +927,10 @@ namespace confighttp {
* *
* @api_examples{/api/pin| POST| {"pin":"1234","name":"My PC"}} * @api_examples{/api/pin| POST| {"pin":"1234","name":"My PC"}}
*/ */
void void savePin(resp_https_t response, req_https_t request) {
savePin(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -952,8 +946,7 @@ namespace confighttp {
std::string name = inputTree.get<std::string>("name"); std::string name = inputTree.get<std::string>("name");
outputTree.put("status", nvhttp::pin(pin, name)); outputTree.put("status", nvhttp::pin(pin, name));
send_response(response, outputTree); send_response(response, outputTree);
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(warning) << "SavePin: "sv << e.what(); BOOST_LOG(warning) << "SavePin: "sv << e.what();
bad_request(response, request, e.what()); bad_request(response, request, e.what());
} }
@ -966,9 +959,10 @@ namespace confighttp {
* *
* @api_examples{/api/clients/unpair-all| POST| null} * @api_examples{/api/clients/unpair-all| POST| null}
*/ */
void void unpairAll(resp_https_t response, req_https_t request) {
unpairAll(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -993,9 +987,10 @@ namespace confighttp {
* *
* @api_examples{/api/unpair| POST| {"uuid":"1234"}} * @api_examples{/api/unpair| POST| {"uuid":"1234"}}
*/ */
void void unpair(resp_https_t response, req_https_t request) {
unpair(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -1010,8 +1005,7 @@ namespace confighttp {
std::string uuid = inputTree.get<std::string>("uuid"); std::string uuid = inputTree.get<std::string>("uuid");
outputTree.put("status", nvhttp::unpair_client(uuid)); outputTree.put("status", nvhttp::unpair_client(uuid));
send_response(response, outputTree); send_response(response, outputTree);
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(warning) << "Unpair: "sv << e.what(); BOOST_LOG(warning) << "Unpair: "sv << e.what();
bad_request(response, request, e.what()); bad_request(response, request, e.what());
} }
@ -1024,9 +1018,10 @@ namespace confighttp {
* *
* @api_examples{/api/clients/list| GET| null} * @api_examples{/api/clients/list| GET| null}
*/ */
void void listClients(resp_https_t response, req_https_t request) {
listClients(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -1046,9 +1041,10 @@ namespace confighttp {
* *
* @api_examples{/api/apps/close| POST| null} * @api_examples{/api/apps/close| POST| null}
*/ */
void void closeApp(resp_https_t response, req_https_t request) {
closeApp(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) {
if (!authenticate(response, request)) return; return;
}
print_req(request); print_req(request);
@ -1059,8 +1055,7 @@ namespace confighttp {
send_response(response, outputTree); send_response(response, outputTree);
} }
void void start() {
start() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown); auto shutdown_event = mail::man->event<bool>(mail::shutdown);
auto port_https = net::map_port(PORT_HTTPS); auto port_https = net::map_port(PORT_HTTPS);
@ -1116,8 +1111,7 @@ namespace confighttp {
server->start([](unsigned short port) { server->start([](unsigned short port) {
BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << 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 // It's possible the exception gets thrown after calling server->stop() from a different thread
if (shutdown_event->peek()) { if (shutdown_event->peek()) {
return; return;

View file

@ -4,17 +4,18 @@
*/ */
#pragma once #pragma once
// standard includes
#include <functional> #include <functional>
#include <string> #include <string>
// local includes
#include "thread_safe.h" #include "thread_safe.h"
#define WEB_DIR SUNSHINE_ASSETS_DIR "/web/" #define WEB_DIR SUNSHINE_ASSETS_DIR "/web/"
namespace confighttp { namespace confighttp {
constexpr auto PORT_HTTPS = 1; constexpr auto PORT_HTTPS = 1;
void void start();
start();
} // namespace confighttp } // namespace confighttp
// mime types map // mime types map

View file

@ -2,29 +2,33 @@
* @file src/crypto.cpp * @file src/crypto.cpp
* @brief Definitions for cryptography functions. * @brief Definitions for cryptography functions.
*/ */
#include "crypto.h" // lib includes
#include <openssl/pem.h> #include <openssl/pem.h>
#include <openssl/rsa.h> #include <openssl/rsa.h>
// local includes
#include "crypto.h"
namespace crypto { namespace crypto {
using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>; using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>;
cert_chain_t::cert_chain_t(): cert_chain_t::cert_chain_t():
_certs {}, _cert_ctx { X509_STORE_CTX_new() } {} _certs {},
void _cert_ctx {X509_STORE_CTX_new()} {
cert_chain_t::add(x509_t &&cert) { }
void cert_chain_t::add(x509_t &&cert) {
x509_store_t x509_store {X509_STORE_new()}; x509_store_t x509_store {X509_STORE_new()};
X509_STORE_add_cert(x509_store.get(), cert.get()); X509_STORE_add_cert(x509_store.get(), cert.get());
_certs.emplace_back(std::make_pair(std::move(cert), std::move(x509_store))); _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(); _certs.clear();
} }
static int static int openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
int err_code = X509_STORE_CTX_get_error(ctx); int err_code = X509_STORE_CTX_get_error(ctx);
switch (err_code) { switch (err_code) {
@ -52,8 +56,7 @@ namespace crypto {
* @param cert The certificate to verify. * @param cert The certificate to verify.
* @return nullptr if the certificate is valid, otherwise an error string. * @return nullptr if the certificate is valid, otherwise an error string.
*/ */
const char * const char *cert_chain_t::verify(x509_t::element_type *cert) {
cert_chain_t::verify(x509_t::element_type *cert) {
int err_code = 0; int err_code = 0;
for (auto &[_, x509_store] : _certs) { for (auto &[_, x509_store] : _certs) {
auto fg = util::fail_guard([this]() { auto fg = util::fail_guard([this]() {
@ -86,8 +89,7 @@ namespace crypto {
namespace cipher { namespace cipher {
static int static int init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new()); ctx.reset(EVP_CIPHER_CTX_new());
if (!ctx) { if (!ctx) {
@ -110,8 +112,7 @@ namespace crypto {
return 0; return 0;
} }
static int static int init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new()); ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB // Gen 7 servers use 128-bit AES ECB
@ -131,8 +132,7 @@ namespace crypto {
return 0; return 0;
} }
static int static int init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new()); ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB // Gen 7 servers use 128-bit AES ECB
@ -145,8 +145,7 @@ namespace crypto {
return 0; return 0;
} }
int int gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
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)) { if (!decrypt_ctx && init_decrypt_gcm(decrypt_ctx, &key, iv, padding)) {
return -1; 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 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. * The resulting ciphertext and the GCM tag are written into the tagged_cipher buffer.
*/ */
int int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tag, std::uint8_t *ciphertext, aes_t *iv) {
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)) { if (!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, iv, padding)) {
return -1; return -1;
} }
@ -216,14 +214,12 @@ namespace crypto {
return update_outlen + final_outlen; return update_outlen + final_outlen;
} }
int int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
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 // This overload handles the common case of [GCM tag][cipher text] buffer layout
return encrypt(plaintext, tagged_cipher, tagged_cipher + tag_size, iv); return encrypt(plaintext, tagged_cipher, tagged_cipher + tag_size, iv);
} }
int int ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
auto fg = util::fail_guard([this]() { auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(decrypt_ctx.get()); EVP_CIPHER_CTX_reset(decrypt_ctx.get());
}); });
@ -250,8 +246,7 @@ namespace crypto {
return 0; return 0;
} }
int int ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
auto fg = util::fail_guard([this]() { auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(encrypt_ctx.get()); 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 function handles the creation and initialization of the encryption context, and manages the encryption process.
* The resulting ciphertext is written into the cipher buffer. * The resulting ciphertext is written into the cipher buffer.
*/ */
int int cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
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)) { if (!encrypt_ctx && init_encrypt_cbc(encrypt_ctx, &key, iv, padding)) {
return -1; return -1;
} }
@ -311,18 +305,20 @@ namespace crypto {
} }
ecb_t::ecb_t(const aes_t &key, bool padding): 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): 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): 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 } // namespace cipher
aes_t aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
aes_t key(16); aes_t key(16);
std::string salt_pin; std::string salt_pin;
@ -338,15 +334,13 @@ namespace crypto {
return key; return key;
} }
sha256_t sha256_t hash(const std::string_view &plaintext) {
hash(const std::string_view &plaintext) {
sha256_t hsh; sha256_t hsh;
EVP_Digest(plaintext.data(), plaintext.size(), hsh.data(), nullptr, EVP_sha256(), nullptr); EVP_Digest(plaintext.data(), plaintext.size(), hsh.data(), nullptr, EVP_sha256(), nullptr);
return hsh; return hsh;
} }
x509_t x509_t x509(const std::string_view &x) {
x509(const std::string_view &x) {
bio_t io {BIO_new(BIO_s_mem())}; bio_t io {BIO_new(BIO_s_mem())};
BIO_write(io.get(), x.data(), x.size()); BIO_write(io.get(), x.data(), x.size());
@ -357,8 +351,7 @@ namespace crypto {
return p; return p;
} }
pkey_t pkey_t pkey(const std::string_view &k) {
pkey(const std::string_view &k) {
bio_t io {BIO_new(BIO_s_mem())}; bio_t io {BIO_new(BIO_s_mem())};
BIO_write(io.get(), k.data(), k.size()); BIO_write(io.get(), k.data(), k.size());
@ -369,8 +362,7 @@ namespace crypto {
return p; return p;
} }
std::string std::string pem(x509_t &x509) {
pem(x509_t &x509) {
bio_t bio {BIO_new(BIO_s_mem())}; bio_t bio {BIO_new(BIO_s_mem())};
PEM_write_bio_X509(bio.get(), x509.get()); PEM_write_bio_X509(bio.get(), x509.get());
@ -380,8 +372,7 @@ namespace crypto {
return {mem_ptr->data, mem_ptr->length}; return {mem_ptr->data, mem_ptr->length};
} }
std::string std::string pem(pkey_t &pkey) {
pem(pkey_t &pkey) {
bio_t bio {BIO_new(BIO_s_mem())}; bio_t bio {BIO_new(BIO_s_mem())};
PEM_write_bio_PrivateKey(bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr); PEM_write_bio_PrivateKey(bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
@ -391,8 +382,7 @@ namespace crypto {
return {mem_ptr->data, mem_ptr->length}; return {mem_ptr->data, mem_ptr->length};
} }
std::string_view std::string_view signature(const x509_t &x) {
signature(const x509_t &x) {
// X509_ALGOR *_ = nullptr; // X509_ALGOR *_ = nullptr;
const ASN1_BIT_STRING *asn1 = nullptr; const ASN1_BIT_STRING *asn1 = nullptr;
@ -401,8 +391,7 @@ namespace crypto {
return {(const char *) asn1->data, (std::size_t) asn1->length}; return {(const char *) asn1->data, (std::size_t) asn1->length};
} }
std::string std::string rand(std::size_t bytes) {
rand(std::size_t bytes) {
std::string r; std::string r;
r.resize(bytes); r.resize(bytes);
@ -411,8 +400,7 @@ namespace crypto {
return r; return r;
} }
std::vector<uint8_t> std::vector<uint8_t> sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
md_ctx_t ctx {EVP_MD_CTX_create()}; md_ctx_t ctx {EVP_MD_CTX_create()};
if (EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, (EVP_PKEY *) pkey.get()) != 1) { if (EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, (EVP_PKEY *) pkey.get()) != 1) {
@ -436,8 +424,7 @@ namespace crypto {
return digest; return digest;
} }
creds_t creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
x509_t x509 {X509_new()}; x509_t x509 {X509_new()};
pkey_ctx_t ctx {EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)}; pkey_ctx_t ctx {EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)};
pkey_t pkey; pkey_t pkey;
@ -472,9 +459,7 @@ namespace crypto {
X509_set_pubkey(x509.get(), pkey.get()); X509_set_pubkey(x509.get(), pkey.get());
auto name = X509_get_subject_name(x509.get()); auto name = X509_get_subject_name(x509.get());
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const std::uint8_t *) cn.data(), cn.size(), -1, 0);
(const std::uint8_t *) cn.data(), cn.size(),
-1, 0);
X509_set_issuer_name(x509.get(), name); X509_set_issuer_name(x509.get(), name);
X509_sign(x509.get(), pkey.get(), EVP_sha256()); X509_sign(x509.get(), pkey.get(), EVP_sha256());
@ -482,13 +467,11 @@ namespace crypto {
return {pem(x509), pem(pkey)}; return {pem(x509), pem(pkey)};
} }
std::vector<uint8_t> std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data) {
sign256(const pkey_t &pkey, const std::string_view &data) {
return sign(pkey, data, EVP_sha256()); return sign(pkey, data, EVP_sha256());
} }
bool bool verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
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()); auto pkey = X509_get0_pubkey(x509.get());
md_ctx_t ctx {EVP_MD_CTX_create()}; md_ctx_t ctx {EVP_MD_CTX_create()};
@ -508,18 +491,15 @@ namespace crypto {
return true; return true;
} }
bool bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
return verify(x509, data, signature, EVP_sha256()); return verify(x509, data, signature, EVP_sha256());
} }
void void md_ctx_destroy(EVP_MD_CTX *ctx) {
md_ctx_destroy(EVP_MD_CTX *ctx) {
EVP_MD_CTX_destroy(ctx); EVP_MD_CTX_destroy(ctx);
} }
std::string std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
auto value = rand(bytes); auto value = rand(bytes);
for (std::size_t i = 0; i != value.size(); ++i) { for (std::size_t i = 0; i != value.size(); ++i) {

View file

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

View file

@ -49,8 +49,7 @@ namespace display_device {
*/ */
class sunshine_audio_context_t: public AudioContextInterface { class sunshine_audio_context_t: public AudioContextInterface {
public: public:
[[nodiscard]] bool [[nodiscard]] bool capture() override {
capture() override {
return context_scheduler.execute([](auto &audio_context) { 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. // Explicitly releasing the context first in case it was not release yet so that it can be potentially cleaned up.
audio_context = boost::none; audio_context = boost::none;
@ -61,8 +60,7 @@ namespace display_device {
}); });
} }
[[nodiscard]] bool [[nodiscard]] bool isCaptured() const override {
isCaptured() const override {
return context_scheduler.execute([](const auto &audio_context) { return context_scheduler.execute([](const auto &audio_context) {
if (audio_context) { if (audio_context) {
// In case we still have context we need to check whether it was released or not. // In case we still have context we need to check whether it was released or not.
@ -74,8 +72,7 @@ namespace display_device {
}); });
} }
void void release() override {
release() override {
context_scheduler.schedule([](auto &audio_context, auto &stop_token) { context_scheduler.schedule([](auto &audio_context, auto &stop_token) {
if (audio_context) { if (audio_context) {
audio_context->released = true; audio_context->released = true;
@ -124,8 +121,7 @@ namespace display_device {
* @param value String to be converted * @param value String to be converted
* @return Parsed unsigned integer. * @return Parsed unsigned integer.
*/ */
unsigned int unsigned int stou(const std::string &value) {
stou(const std::string &value) {
unsigned long result {std::stoul(value)}; unsigned long result {std::stoul(value)};
if (result > std::numeric_limits<unsigned int>::max()) { if (result > std::numeric_limits<unsigned int>::max()) {
throw std::out_of_range("stou"); throw std::out_of_range("stou");
@ -151,8 +147,7 @@ namespace display_device {
* } * }
* @examples_end * @examples_end
*/ */
bool bool parse_resolution_string(const std::string &input, std::optional<Resolution> &output) {
parse_resolution_string(const std::string &input, std::optional<Resolution> &output) {
const std::string trimmed_input {boost::algorithm::trim_copy(input)}; const std::string trimmed_input {boost::algorithm::trim_copy(input)};
const std::regex resolution_regex {R"(^(\d+)x(\d+)$)"}; const std::regex resolution_regex {R"(^(\d+)x(\d+)$)"};
@ -163,16 +158,13 @@ namespace display_device {
stou(match[2].str()) stou(match[2].str())
}; };
return true; 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)."; 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" BOOST_LOG(error) << "Failed to parse resolution string " << trimmed_input << ":\n"
<< err.what(); << err.what();
} }
} } else {
else {
if (trimmed_input.empty()) { if (trimmed_input.empty()) {
output = std::nullopt; output = std::nullopt;
return true; return true;
@ -203,9 +195,10 @@ namespace display_device {
* } * }
* @examples_end * @examples_end
*/ */
bool bool parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output, const bool allow_decimal_point = true) {
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) {
static const auto is_zero { [](const auto &character) { return character == '0'; } }; return character == '0';
}};
const std::string trimmed_input {boost::algorithm::trim_copy(input)}; const std::string trimmed_input {boost::algorithm::trim_copy(input)};
const std::regex refresh_rate_regex {allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)"}; const std::regex refresh_rate_regex {allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)"};
@ -237,8 +230,7 @@ namespace display_device {
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}; output = Rational {numerator, denominator};
} } else {
else {
// We do not have a decimal point, just a valid number. // We do not have a decimal point, just a valid number.
// For example: // For example:
// 60: // 60:
@ -247,16 +239,13 @@ namespace display_device {
output = Rational {stou(trimmed_match_1), 1}; output = Rational {stou(trimmed_match_1), 1};
} }
return true; 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)."; 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" BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << ":\n"
<< err.what(); << err.what();
} }
} } else {
else {
if (trimmed_input.empty()) { if (trimmed_input.empty()) {
output = std::nullopt; output = std::nullopt;
return true; return true;
@ -279,8 +268,7 @@ namespace display_device {
* const auto device_prep_option = parse_device_prep_option(video_config); * const auto device_prep_option = parse_device_prep_option(video_config);
* @examples_end * @examples_end
*/ */
std::optional<SingleDisplayConfiguration::DevicePreparation> std::optional<SingleDisplayConfiguration::DevicePreparation> parse_device_prep_option(const config::video_t &video_config) {
parse_device_prep_option(const config::video_t &video_config) {
using enum config::video_t::dd_t::config_option_e; using enum config::video_t::dd_t::config_option_e;
using enum SingleDisplayConfiguration::DevicePreparation; using enum SingleDisplayConfiguration::DevicePreparation;
@ -315,32 +303,30 @@ namespace display_device {
* const bool success = parse_resolution_option(video_config, *launch_session, config); * const bool success = parse_resolution_option(video_config, *launch_session, config);
* @examples_end * @examples_end
*/ */
bool bool parse_resolution_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
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; using resolution_option_e = config::video_t::dd_t::resolution_option_e;
switch (video_config.dd.resolution_option) { switch (video_config.dd.resolution_option) {
case resolution_option_e::automatic: { case resolution_option_e::automatic:
{
if (!session.enable_sops) { 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.)"; 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) {
else if (session.width >= 0 && session.height >= 0) {
config.m_resolution = Resolution { config.m_resolution = Resolution {
static_cast<unsigned int>(session.width), static_cast<unsigned int>(session.width),
static_cast<unsigned int>(session.height) static_cast<unsigned int>(session.height)
}; };
} } else {
else {
BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height; BOOST_LOG(error) << "Resolution provided by client session config is invalid: " << session.width << "x" << session.height;
return false; return false;
} }
break; break;
} }
case resolution_option_e::manual: { case resolution_option_e::manual:
{
if (!session.enable_sops) { 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.)"; 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 {
else {
if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) { if (!parse_resolution_string(video_config.dd.manual_resolution, config.m_resolution)) {
BOOST_LOG(error) << "Failed to parse manual resolution string!"; BOOST_LOG(error) << "Failed to parse manual resolution string!";
return false; return false;
@ -375,22 +361,22 @@ namespace display_device {
* const bool success = parse_refresh_rate_option(video_config, *launch_session, config); * const bool success = parse_refresh_rate_option(video_config, *launch_session, config);
* @examples_end * @examples_end
*/ */
bool bool parse_refresh_rate_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
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; using refresh_rate_option_e = config::video_t::dd_t::refresh_rate_option_e;
switch (video_config.dd.refresh_rate_option) { switch (video_config.dd.refresh_rate_option) {
case refresh_rate_option_e::automatic: { case refresh_rate_option_e::automatic:
{
if (session.fps >= 0) { if (session.fps >= 0) {
config.m_refresh_rate = Rational {static_cast<unsigned int>(session.fps), 1}; config.m_refresh_rate = Rational {static_cast<unsigned int>(session.fps), 1};
} } else {
else {
BOOST_LOG(error) << "FPS value provided by client session config is invalid: " << session.fps; BOOST_LOG(error) << "FPS value provided by client session config is invalid: " << session.fps;
return false; return false;
} }
break; break;
} }
case refresh_rate_option_e::manual: { case refresh_rate_option_e::manual:
{
if (!parse_refresh_rate_string(video_config.dd.manual_refresh_rate, config.m_refresh_rate)) { 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!"; BOOST_LOG(error) << "Failed to parse manual refresh rate string!";
return false; return false;
@ -422,8 +408,7 @@ namespace display_device {
* const auto hdr_option = parse_hdr_option(video_config, *launch_session); * const auto hdr_option = parse_hdr_option(video_config, *launch_session);
* @examples_end * @examples_end
*/ */
std::optional<HdrState> std::optional<HdrState> parse_hdr_option(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
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; using hdr_option_e = config::video_t::dd_t::hdr_option_e;
switch (video_config.dd.hdr_option) { switch (video_config.dd.hdr_option) {
@ -450,8 +435,7 @@ namespace display_device {
* @param video_config User's video related configuration. * @param video_config User's video related configuration.
* @returns Enum value if remapping can be performed, null optional if remapping shall be skipped. * @returns Enum value if remapping can be performed, null optional if remapping shall be skipped.
*/ */
std::optional<remapping_type_e> std::optional<remapping_type_e> determine_remapping_type(const config::video_t &video_config) {
determine_remapping_type(const config::video_t &video_config) {
using dd_t = config::video_t::dd_t; 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_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_refresh_rate {video_config.dd.refresh_rate_option == dd_t::refresh_rate_option_e::automatic};
@ -486,8 +470,7 @@ namespace display_device {
* @param type Remapping type to check. * @param type Remapping type to check.
* @returns True if resolution is to be mapped, false otherwise. * @returns True if resolution is to be mapped, false otherwise.
*/ */
bool bool is_resolution_mapped(const remapping_type_e type) {
is_resolution_mapped(const remapping_type_e type) {
return type == remapping_type_e::resolution_only || type == remapping_type_e::mixed; 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. * @param type Remapping type to check.
* @returns True if FPS is to be mapped, false otherwise. * @returns True if FPS is to be mapped, false otherwise.
*/ */
bool bool is_fps_mapped(const remapping_type_e type) {
is_fps_mapped(const remapping_type_e type) {
return type == remapping_type_e::refresh_rate_only || type == remapping_type_e::mixed; return type == remapping_type_e::refresh_rate_only || type == remapping_type_e::mixed;
} }
@ -507,8 +489,7 @@ namespace display_device {
* @param type Specify which entry fields should be parsed. * @param type Specify which entry fields should be parsed.
* @returns Parsed structure or null optional if a necessary field could not be parsed. * @returns Parsed structure or null optional if a necessary field could not be parsed.
*/ */
std::optional<parsed_remapping_entry_t> 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) {
parse_remapping_entry(const config::video_t::dd_t::mode_remapping_entry_t &entry, const remapping_type_e type) {
parsed_remapping_entry_t result {}; parsed_remapping_entry_t result {};
if (is_resolution_mapped(type) && (!parse_resolution_string(entry.requested_resolution, result.requested_resolution) || if (is_resolution_mapped(type) && (!parse_resolution_string(entry.requested_resolution, result.requested_resolution) ||
@ -539,8 +520,7 @@ namespace display_device {
* const bool success = remap_display_mode_if_needed(video_config, *launch_session, config); * const bool success = remap_display_mode_if_needed(video_config, *launch_session, config);
* @examples_end * @examples_end
*/ */
bool bool remap_display_mode_if_needed(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
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)}; const auto remapping_type {determine_remapping_type(video_config)};
if (!remapping_type) { if (!remapping_type) {
return true; return true;
@ -632,16 +612,18 @@ namespace display_device {
* @param video_config User's video related configuration. * @param video_config User's video related configuration.
* @return An interface or nullptr if the OS does not support the interface. * @return An interface or nullptr if the OS does not support the interface.
*/ */
std::unique_ptr<SettingsManagerInterface> std::unique_ptr<SettingsManagerInterface> make_settings_manager([[maybe_unused]] const std::filesystem::path &persistence_filepath, [[maybe_unused]] const config::video_t &video_config) {
make_settings_manager([[maybe_unused]] const std::filesystem::path &persistence_filepath, [[maybe_unused]] const config::video_t &video_config) {
#ifdef _WIN32 #ifdef _WIN32
return std::make_unique<SettingsManager>( return std::make_unique<SettingsManager>(
std::make_shared<WinDisplayDevice>(std::make_shared<WinApiLayer>()), std::make_shared<WinDisplayDevice>(std::make_shared<WinApiLayer>()),
std::make_shared<sunshine_audio_context_t>(), std::make_shared<sunshine_audio_context_t>(),
std::make_unique<PersistentState>( std::make_unique<PersistentState>(
std::make_shared<FileSettingsPersistence>(persistence_filepath)), std::make_shared<FileSettingsPersistence>(persistence_filepath)
),
WinWorkarounds { 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 #else
return nullptr; return nullptr;
#endif #endif
@ -660,8 +642,7 @@ namespace display_device {
* @brief Reverts the configuration based on the provided option. * @brief Reverts the configuration based on the provided option.
* @note This is function does not lock mutex. * @note This is function does not lock mutex.
*/ */
void void revert_configuration_unlocked(const revert_option_e option) {
revert_configuration_unlocked(const revert_option_e option) {
if (!DD_DATA.sm_instance) { if (!DD_DATA.sm_instance) {
// Platform is not supported, nothing to do. // Platform is not supported, nothing to do.
return; return;
@ -686,9 +667,13 @@ namespace display_device {
std::set<std::string> parsed_devices; std::set<std::string> parsed_devices;
std::transform( std::transform(
std::begin(devices), std::end(devices), std::begin(devices),
std::end(devices),
std::inserter(parsed_devices, std::end(parsed_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; return parsed_devices;
}()}; }()};
@ -702,8 +687,7 @@ namespace display_device {
if (const auto result {settings_iface.revertSettings()}; result == Ok) { if (const auto result {settings_iface.revertSettings()}; result == Ok) {
stop_token.requestStop(); stop_token.requestStop();
return; return;
} } else if (result == ApiTemporarilyUnavailable) {
else if (result == ApiTemporarilyUnavailable) {
// Do nothing and retry next time // Do nothing and retry next time
return; return;
} }
@ -717,8 +701,7 @@ namespace display_device {
} }
} // namespace } // namespace
std::unique_ptr<platf::deinit_t> std::unique_ptr<platf::deinit_t> init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config) {
init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config) {
std::lock_guard lock {DD_DATA.mutex}; std::lock_guard lock {DD_DATA.mutex};
// We can support re-init without any issues, however we should make sure to clean up first! // 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); revert_configuration_unlocked(revert_option_e::try_once);
@ -730,7 +713,9 @@ namespace display_device {
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)); 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" BOOST_LOG(info) << "Currently available display devices:\n"
<< toJson(available_devices); << toJson(available_devices);
@ -749,30 +734,30 @@ namespace display_device {
// we definitely don't want this to happen in destructor. Especially in the // we definitely don't want this to happen in destructor. Especially in the
// deinit_t where the outcome does not really matter. // deinit_t where the outcome does not really matter.
revert_configuration_unlocked(revert_option_e::try_once); revert_configuration_unlocked(revert_option_e::try_once);
} } catch (std::exception &err) {
catch (std::exception &err) {
BOOST_LOG(fatal) << err.what(); BOOST_LOG(fatal) << err.what();
} }
DD_DATA.sm_instance = nullptr; DD_DATA.sm_instance = nullptr;
} }
}; };
return std::make_unique<deinit_t>(); return std::make_unique<deinit_t>();
} }
std::string std::string map_output_name(const std::string &output_name) {
map_output_name(const std::string &output_name) {
std::lock_guard lock {DD_DATA.mutex}; std::lock_guard lock {DD_DATA.mutex};
if (!DD_DATA.sm_instance) { if (!DD_DATA.sm_instance) {
// Fallback to giving back the output name if the platform is not supported. // Fallback to giving back the output name if the platform is not supported.
return output_name; 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 void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
const auto result {parse_configuration(video_config, session)}; const auto result {parse_configuration(video_config, session)};
if (const auto *parsed_config {std::get_if<SingleDisplayConfiguration>(&result)}; parsed_config) { if (const auto *parsed_config {std::get_if<SingleDisplayConfiguration>(&result)}; parsed_config) {
configure_display(*parsed_config); configure_display(*parsed_config);
@ -788,8 +773,7 @@ namespace display_device {
// want to revert active configuration in case we have any // want to revert active configuration in case we have any
} }
void void configure_display(const SingleDisplayConfiguration &config) {
configure_display(const SingleDisplayConfiguration &config) {
std::lock_guard lock {DD_DATA.mutex}; std::lock_guard lock {DD_DATA.mutex};
if (!DD_DATA.sm_instance) { if (!DD_DATA.sm_instance) {
// Platform is not supported, nothing to do. // Platform is not supported, nothing to do.
@ -806,14 +790,12 @@ namespace display_device {
{.m_sleep_durations = {DEFAULT_RETRY_INTERVAL}}); {.m_sleep_durations = {DEFAULT_RETRY_INTERVAL}});
} }
void void revert_configuration() {
revert_configuration() {
std::lock_guard lock {DD_DATA.mutex}; std::lock_guard lock {DD_DATA.mutex};
revert_configuration_unlocked(revert_option_e::try_indefinitely_with_delay); revert_configuration_unlocked(revert_option_e::try_indefinitely_with_delay);
} }
bool bool reset_persistence() {
reset_persistence() {
std::lock_guard lock {DD_DATA.mutex}; std::lock_guard lock {DD_DATA.mutex};
if (!DD_DATA.sm_instance) { if (!DD_DATA.sm_instance) {
// Platform is not supported, assume success. // Platform is not supported, assume success.
@ -828,8 +810,7 @@ namespace display_device {
}); });
} }
std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> 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) {
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)}; const auto device_prep {parse_device_prep_option(video_config)};
if (!device_prep) { if (!device_prep) {
return configuration_disabled_tag_t {}; return configuration_disabled_tag_t {};

View file

@ -4,18 +4,22 @@
*/ */
#pragma once #pragma once
// lib includes // standard includes
#include <display_device/types.h>
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
// lib includes
#include <display_device/types.h>
// forward declarations // forward declarations
namespace platf { namespace platf {
class deinit_t; class deinit_t;
} }
namespace config { namespace config {
struct video_t; struct video_t;
} }
namespace rtsp_stream { namespace rtsp_stream {
struct launch_session_t; struct launch_session_t;
} }
@ -32,8 +36,7 @@ namespace display_device {
* const auto init_guard { init("/my/persitence/file.state", video_config) }; * const auto init_guard { init("/my/persitence/file.state", video_config) };
* @examples_end * @examples_end
*/ */
[[nodiscard]] std::unique_ptr<platf::deinit_t> [[nodiscard]] std::unique_ptr<platf::deinit_t> init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config);
init(const std::filesystem::path &persistence_filepath, const config::video_t &video_config);
/** /**
* @brief Map the output name to a specific display. * @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}") }; * const auto mapped_name_custom { map_output_name("{some-device-id}") };
* @examples_end * @examples_end
*/ */
[[nodiscard]] std::string [[nodiscard]] std::string map_output_name(const std::string &output_name);
map_output_name(const std::string &output_name);
/** /**
* @brief Configure the display device based on the user configuration and the session information. * @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); * configure_display(video_config, *launch_session);
* @examples_end * @examples_end
*/ */
void void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
/** /**
* @brief Configure the display device using the provided configuration. * @brief Configure the display device using the provided configuration.
@ -83,8 +84,7 @@ namespace display_device {
* configure_display(valid_config); * configure_display(valid_config);
* @examples_end * @examples_end
*/ */
void void configure_display(const SingleDisplayConfiguration &config);
configure_display(const SingleDisplayConfiguration &config);
/** /**
* @brief Revert the display configuration and restore the previous state. * @brief Revert the display configuration and restore the previous state.
@ -96,8 +96,7 @@ namespace display_device {
* revert_configuration(); * revert_configuration();
* @examples_end * @examples_end
*/ */
void void revert_configuration();
revert_configuration();
/** /**
* @brief Reset the persistence and currently held initial display state. * @brief Reset the persistence and currently held initial display state.
@ -119,8 +118,7 @@ namespace display_device {
* const auto result = reset_persistence(); * const auto result = reset_persistence();
* @examples_end * @examples_end
*/ */
[[nodiscard]] bool [[nodiscard]] bool reset_persistence();
reset_persistence();
/** /**
* @brief A tag structure indicating that configuration parsing has failed. * @brief A tag structure indicating that configuration parsing has failed.
@ -150,6 +148,5 @@ namespace display_device {
* } * }
* @examples_end * @examples_end
*/ */
[[nodiscard]] std::variant<failed_to_parse_tag_t, configuration_disabled_tag_t, SingleDisplayConfiguration> [[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);
parse_configuration(const config::video_t &video_config, const rtsp_stream::launch_session_t &session);
} // namespace display_device } // namespace display_device

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,7 @@
*/ */
#pragma once #pragma once
// local includes
#include "entry_handler.h" #include "entry_handler.h"
#include "thread_pool.h" #include "thread_pool.h"

View file

@ -4,22 +4,21 @@
*/ */
#define BOOST_BIND_GLOBAL_PLACEHOLDERS #define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include "process.h" // standard includes
#include <filesystem> #include <filesystem>
#include <utility> #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/json_parser.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp> #include <boost/property_tree/xml_parser.hpp>
#include <curl/curl.h>
#include <boost/asio/ssl/context.hpp>
#include <Simple-Web-Server/server_http.hpp> #include <Simple-Web-Server/server_http.hpp>
#include <Simple-Web-Server/server_https.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 "config.h"
#include "crypto.h" #include "crypto.h"
#include "file_handler.h" #include "file_handler.h"
@ -28,6 +27,7 @@
#include "network.h" #include "network.h"
#include "nvhttp.h" #include "nvhttp.h"
#include "platform/common.h" #include "platform/common.h"
#include "process.h"
#include "rtsp.h" #include "rtsp.h"
#include "utility.h" #include "utility.h"
#include "uuid.h" #include "uuid.h"
@ -37,16 +37,13 @@ namespace http {
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace pt = boost::property_tree; namespace pt = boost::property_tree;
int int reload_user_creds(const std::string &file);
reload_user_creds(const std::string &file); bool user_creds_exist(const std::string &file);
bool
user_creds_exist(const std::string &file);
std::string unique_id; std::string unique_id;
net::net_e origin_web_ui_allowed; net::net_e origin_web_ui_allowed;
int int init() {
init() {
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed); 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 (user_creds_exist(config::sunshine.credentials_file)) {
if (reload_user_creds(config::sunshine.credentials_file)) return -1; if (reload_user_creds(config::sunshine.credentials_file)) {
return -1;
} }
else { } else {
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started"; BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
} }
return 0; return 0;
} }
int int save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
pt::ptree outputTree; pt::ptree outputTree;
if (fs::exists(file)) { if (fs::exists(file)) {
try { try {
pt::read_json(file, outputTree); pt::read_json(file, outputTree);
} } catch (std::exception &e) {
catch (std::exception &e) {
BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what(); BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what();
return -1; return -1;
} }
@ -91,8 +87,7 @@ namespace http {
outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string()); outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string());
try { try {
pt::write_json(file, outputTree); 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(); BOOST_LOG(error) << "error writing to the credentials file, perhaps try this again as an administrator? Details: "sv << e.what();
return -1; return -1;
} }
@ -101,8 +96,7 @@ namespace http {
return 0; return 0;
} }
bool bool user_creds_exist(const std::string &file) {
user_creds_exist(const std::string &file) {
if (!fs::exists(file)) { if (!fs::exists(file)) {
return false; return false;
} }
@ -113,32 +107,28 @@ namespace http {
return inputTree.find("username") != inputTree.not_found() && return inputTree.find("username") != inputTree.not_found() &&
inputTree.find("password") != inputTree.not_found() && inputTree.find("password") != inputTree.not_found() &&
inputTree.find("salt") != 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(); BOOST_LOG(error) << "validating user credentials: "sv << e.what();
} }
return false; return false;
} }
int int reload_user_creds(const std::string &file) {
reload_user_creds(const std::string &file) {
pt::ptree inputTree; pt::ptree inputTree;
try { try {
pt::read_json(file, inputTree); pt::read_json(file, inputTree);
config::sunshine.username = inputTree.get<std::string>("username"); config::sunshine.username = inputTree.get<std::string>("username");
config::sunshine.password = inputTree.get<std::string>("password"); config::sunshine.password = inputTree.get<std::string>("password");
config::sunshine.salt = inputTree.get<std::string>("salt"); 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(); BOOST_LOG(error) << "loading user credentials: "sv << e.what();
return -1; return -1;
} }
return 0; return 0;
} }
int int create_creds(const std::string &pkey, const std::string &cert) {
create_creds(const std::string &pkey, const std::string &cert) {
fs::path pkey_path = pkey; fs::path pkey_path = pkey;
fs::path cert_path = cert; fs::path cert_path = cert;
@ -172,18 +162,14 @@ namespace http {
return -1; return -1;
} }
fs::permissions(pkey_path, fs::permissions(pkey_path, fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::replace, err_code);
fs::perms::owner_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
if (err_code) { if (err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message(); BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message();
return -1; return -1;
} }
fs::permissions(cert_path, 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::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
if (err_code) { if (err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message(); BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message();
@ -193,15 +179,13 @@ namespace http {
return 0; return 0;
} }
bool bool download_file(const std::string &url, const std::string &file) {
download_file(const std::string &url, const std::string &file) {
CURL *curl = curl_easy_init(); CURL *curl = curl_easy_init();
if (curl) { if (curl) {
// sonar complains about weak ssl and tls versions // sonar complains about weak ssl and tls versions
// ideally, the setopts should go after the early returns; however sonar cannot detect the fix // 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); curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
} } else {
else {
BOOST_LOG(error) << "Couldn't create CURL instance"; BOOST_LOG(error) << "Couldn't create CURL instance";
return false; return false;
} }
@ -234,8 +218,7 @@ namespace http {
return result == CURLE_OK; return result == CURLE_OK;
} }
std::string std::string url_escape(const std::string &url) {
url_escape(const std::string &url) {
CURL *curl = curl_easy_init(); CURL *curl = curl_easy_init();
char *string = curl_easy_escape(curl, url.c_str(), url.length()); char *string = curl_easy_escape(curl, url.c_str(), url.length());
std::string result(string); std::string result(string);
@ -244,8 +227,7 @@ namespace http {
return result; return result;
} }
std::string std::string url_get_host(const std::string &url) {
url_get_host(const std::string &url) {
CURLU *curlu = curl_url(); CURLU *curlu = curl_url();
curl_url_set(curlu, CURLUPART_URL, url.c_str(), url.length()); curl_url_set(curlu, CURLUPART_URL, url.c_str(), url.length());
char *host; char *host;

View file

@ -4,30 +4,25 @@
*/ */
#pragma once #pragma once
// local includes
#include "network.h" #include "network.h"
#include "thread_safe.h" #include "thread_safe.h"
namespace http { namespace http {
int int init();
init(); int create_creds(const std::string &pkey, const std::string &cert);
int int save_user_creds(
create_creds(const std::string &pkey, const std::string &cert);
int
save_user_creds(
const std::string &file, const std::string &file,
const std::string &username, const std::string &username,
const std::string &password, const std::string &password,
bool run_our_mouth = false); bool run_our_mouth = false
);
int int reload_user_creds(const std::string &file);
reload_user_creds(const std::string &file); bool download_file(const std::string &url, const std::string &file);
bool std::string url_escape(const std::string &url);
download_file(const std::string &url, const std::string &file); std::string url_get_host(const std::string &url);
std::string
url_escape(const std::string &url);
std::string
url_get_host(const std::string &url);
extern std::string unique_id; extern std::string unique_id;
extern net::net_e origin_web_ui_allowed; extern net::net_e origin_web_ui_allowed;

View file

@ -2,13 +2,13 @@
* @file src/input.cpp * @file src/input.cpp
* @brief Definitions for gamepad, keyboard, and mouse input handling. * @brief Definitions for gamepad, keyboard, and mouse input handling.
*/ */
// define uint32_t for <moonlight-common-c/src/Input.h>
#include <cstdint> #include <cstdint>
extern "C" { extern "C" {
#include <moonlight-common-c/src/Input.h> #include <moonlight-common-c/src/Input.h>
#include <moonlight-common-c/src/Limelight.h> #include <moonlight-common-c/src/Limelight.h>
} }
// standard includes
#include <bitset> #include <bitset>
#include <chrono> #include <chrono>
#include <cmath> #include <cmath>
@ -16,6 +16,10 @@ extern "C" {
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
// lib includes
#include <boost/endian/buffers.hpp>
// local includes
#include "config.h" #include "config.h"
#include "globals.h" #include "globals.h"
#include "input.h" #include "input.h"
@ -24,14 +28,13 @@ extern "C" {
#include "thread_pool.h" #include "thread_pool.h"
#include "utility.h" #include "utility.h"
#include <boost/endian/buffers.hpp>
// Win32 WHEEL_DELTA constant // Win32 WHEEL_DELTA constant
#ifndef WHEEL_DELTA #ifndef WHEEL_DELTA
#define WHEEL_DELTA 120 #define WHEEL_DELTA 120
#endif #endif
using namespace std::literals; using namespace std::literals;
namespace input { namespace input {
constexpr auto MAX_GAMEPADS = std::min((std::size_t) platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8); constexpr auto MAX_GAMEPADS = std::min((std::size_t) platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8);
@ -55,8 +58,7 @@ namespace input {
}; };
template<std::size_t N> template<std::size_t N>
int int alloc_id(std::bitset<N> &gamepad_mask) {
alloc_id(std::bitset<N> &gamepad_mask) {
for (int x = 0; x < gamepad_mask.size(); ++x) { for (int x = 0; x < gamepad_mask.size(); ++x) {
if (!gamepad_mask[x]) { if (!gamepad_mask[x]) {
gamepad_mask[x] = true; gamepad_mask[x] = true;
@ -68,22 +70,21 @@ namespace input {
} }
template<std::size_t N> template<std::size_t N>
void void free_id(std::bitset<N> &gamepad_mask, int id) {
free_id(std::bitset<N> &gamepad_mask, int id) {
gamepad_mask[id] = false; gamepad_mask[id] = false;
} }
typedef uint32_t key_press_id_t; 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; 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; 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; return kpid & 0xFF;
} }
@ -92,8 +93,7 @@ namespace input {
* @param f Netfloat value. * @param f Netfloat value.
* @return The native endianness float value. * @return The native endianness float value.
*/ */
float float from_netfloat(netfloat f) {
from_netfloat(netfloat f) {
return boost::endian::endian_load<float, sizeof(float), boost::endian::order::little>(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. * @param max The maximum value for clamping.
* @return Clamped native endianess float value. * @return Clamped native endianess float value.
*/ */
float float from_clamped_netfloat(netfloat f, float min, float max) {
from_clamped_netfloat(netfloat f, float min, float max) {
return std::clamp(from_netfloat(f), min, max); return std::clamp(from_netfloat(f), min, max);
} }
@ -116,16 +115,21 @@ namespace input {
static platf::input_t platf_input; static platf::input_t platf_input;
static std::bitset<platf::MAX_GAMEPADS> gamepadMask {}; static std::bitset<platf::MAX_GAMEPADS> gamepadMask {};
void void free_gamepad(platf::input_t &platf_input, int id) {
free_gamepad(platf::input_t &platf_input, int id) {
platf::gamepad_update(platf_input, id, platf::gamepad_state_t {}); platf::gamepad_update(platf_input, id, platf::gamepad_state_t {});
platf::free_gamepad(platf_input, id); platf::free_gamepad(platf_input, id);
free_id(gamepadMask, id); free_id(gamepadMask, id);
} }
struct gamepad_t { struct gamepad_t {
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() { ~gamepad_t() {
if (id >= 0) { if (id >= 0) {
task_pool.push([id = this->id]() { task_pool.push([id = this->id]() {
@ -158,7 +162,8 @@ namespace input {
input_t( input_t(
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event, 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 {}, shortcutFlags {},
gamepads(MAX_GAMEPADS), gamepads(MAX_GAMEPADS),
client_context {platf::allocate_client_input_context(platf_input)}, client_context {platf::allocate_client_input_context(platf_input)},
@ -167,7 +172,8 @@ namespace input {
mouse_left_button_timeout {}, 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_vscroll_delta {},
accumulated_hscroll_delta {} {} accumulated_hscroll_delta {} {
}
// Keep track of alt+ctrl+shift key combo // Keep track of alt+ctrl+shift key combo
int shortcutFlags; int shortcutFlags;
@ -194,8 +200,7 @@ namespace input {
* @param keyCode The VKEY code * @param keyCode The VKEY code
* @return 0 if no shortcut applied, > 0 if shortcut applied. * @return 0 if no shortcut applied, > 0 if shortcut applied.
*/ */
inline int inline int apply_shortcut(short keyCode) {
apply_shortcut(short keyCode) {
constexpr auto VK_F1 = 0x70; constexpr auto VK_F1 = 0x70;
constexpr auto VK_F13 = 0x7C; constexpr auto VK_F13 = 0x7C;
@ -215,8 +220,7 @@ namespace input {
return 0; return 0;
} }
void void print(PNV_REL_MOUSE_MOVE_PACKET packet) {
print(PNV_REL_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin relative mouse move packet--"sv << std::endl << "--begin relative mouse move packet--"sv << std::endl
<< "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl << "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl
@ -224,8 +228,7 @@ namespace input {
<< "--end relative mouse move packet--"sv; << "--end relative mouse move packet--"sv;
} }
void void print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin absolute mouse move packet--"sv << std::endl << "--begin absolute mouse move packet--"sv << std::endl
<< "x ["sv << util::endian::big(packet->x) << ']' << std::endl << "x ["sv << util::endian::big(packet->x) << ']' << std::endl
@ -235,8 +238,7 @@ namespace input {
<< "--end absolute mouse move packet--"sv; << "--end absolute mouse move packet--"sv;
} }
void void print(PNV_MOUSE_BUTTON_PACKET packet) {
print(PNV_MOUSE_BUTTON_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin mouse button packet--"sv << std::endl << "--begin mouse button packet--"sv << std::endl
<< "action ["sv << util::hex(packet->header.magic).to_string_view() << ']' << 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; << "--end mouse button packet--"sv;
} }
void void print(PNV_SCROLL_PACKET packet) {
print(PNV_SCROLL_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin mouse scroll packet--"sv << std::endl << "--begin mouse scroll packet--"sv << std::endl
<< "scrollAmt1 ["sv << util::endian::big(packet->scrollAmt1) << ']' << std::endl << "scrollAmt1 ["sv << util::endian::big(packet->scrollAmt1) << ']' << std::endl
<< "--end mouse scroll packet--"sv; << "--end mouse scroll packet--"sv;
} }
void void print(PSS_HSCROLL_PACKET packet) {
print(PSS_HSCROLL_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin mouse hscroll packet--"sv << std::endl << "--begin mouse hscroll packet--"sv << std::endl
<< "scrollAmount ["sv << util::endian::big(packet->scrollAmount) << ']' << std::endl << "scrollAmount ["sv << util::endian::big(packet->scrollAmount) << ']' << std::endl
<< "--end mouse hscroll packet--"sv; << "--end mouse hscroll packet--"sv;
} }
void void print(PNV_KEYBOARD_PACKET packet) {
print(PNV_KEYBOARD_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin keyboard packet--"sv << std::endl << "--begin keyboard packet--"sv << std::endl
<< "keyAction ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl << "keyAction ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl
@ -271,8 +270,7 @@ namespace input {
<< "--end keyboard packet--"sv; << "--end keyboard packet--"sv;
} }
void void print(PNV_UNICODE_PACKET packet) {
print(PNV_UNICODE_PACKET packet) {
std::string text(packet->text, util::endian::big(packet->header.size) - sizeof(packet->header.magic)); std::string text(packet->text, util::endian::big(packet->header.size) - sizeof(packet->header.magic));
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin unicode packet--"sv << std::endl << "--begin unicode packet--"sv << std::endl
@ -280,8 +278,7 @@ namespace input {
<< "--end unicode packet--"sv; << "--end unicode packet--"sv;
} }
void void print(PNV_MULTI_CONTROLLER_PACKET packet) {
print(PNV_MULTI_CONTROLLER_PACKET packet) {
// Moonlight spams controller packet even when not necessary // Moonlight spams controller packet even when not necessary
BOOST_LOG(verbose) BOOST_LOG(verbose)
<< "--begin controller packet--"sv << std::endl << "--begin controller packet--"sv << std::endl
@ -301,8 +298,7 @@ namespace input {
* @brief Prints a touch packet. * @brief Prints a touch packet.
* @param packet The touch packet. * @param packet The touch packet.
*/ */
void void print(PSS_TOUCH_PACKET packet) {
print(PSS_TOUCH_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin touch packet--"sv << std::endl << "--begin touch packet--"sv << std::endl
<< "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl << "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl
@ -320,8 +316,7 @@ namespace input {
* @brief Prints a pen packet. * @brief Prints a pen packet.
* @param packet The pen packet. * @param packet The pen packet.
*/ */
void void print(PSS_PEN_PACKET packet) {
print(PSS_PEN_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin pen packet--"sv << std::endl << "--begin pen packet--"sv << std::endl
<< "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << 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. * @brief Prints a controller arrival packet.
* @param packet The controller arrival packet. * @param packet The controller arrival packet.
*/ */
void void print(PSS_CONTROLLER_ARRIVAL_PACKET packet) {
print(PSS_CONTROLLER_ARRIVAL_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin controller arrival packet--"sv << std::endl << "--begin controller arrival packet--"sv << std::endl
<< "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl << "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl
@ -356,8 +350,7 @@ namespace input {
* @brief Prints a controller touch packet. * @brief Prints a controller touch packet.
* @param packet The controller touch packet. * @param packet The controller touch packet.
*/ */
void void print(PSS_CONTROLLER_TOUCH_PACKET packet) {
print(PSS_CONTROLLER_TOUCH_PACKET packet) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "--begin controller touch packet--"sv << std::endl << "--begin controller touch packet--"sv << std::endl
<< "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl << "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl
@ -373,8 +366,7 @@ namespace input {
* @brief Prints a controller motion packet. * @brief Prints a controller motion packet.
* @param packet The controller motion packet. * @param packet The controller motion packet.
*/ */
void void print(PSS_CONTROLLER_MOTION_PACKET packet) {
print(PSS_CONTROLLER_MOTION_PACKET packet) {
BOOST_LOG(verbose) BOOST_LOG(verbose)
<< "--begin controller motion packet--"sv << std::endl << "--begin controller motion packet--"sv << std::endl
<< "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << 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. * @brief Prints a controller battery packet.
* @param packet The controller battery packet. * @param packet The controller battery packet.
*/ */
void void print(PSS_CONTROLLER_BATTERY_PACKET packet) {
print(PSS_CONTROLLER_BATTERY_PACKET packet) {
BOOST_LOG(verbose) BOOST_LOG(verbose)
<< "--begin controller battery packet--"sv << std::endl << "--begin controller battery packet--"sv << std::endl
<< "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl << "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl
@ -399,8 +390,7 @@ namespace input {
<< "--end controller battery packet--"sv; << "--end controller battery packet--"sv;
} }
void void print(void *payload) {
print(void *payload) {
auto header = (PNV_INPUT_HEADER) payload; auto header = (PNV_INPUT_HEADER) payload;
switch (util::endian::little(header->magic)) { switch (util::endian::little(header->magic)) {
@ -451,8 +441,7 @@ namespace input {
} }
} }
void void passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
@ -468,8 +457,7 @@ namespace input {
* @param size The size of the client's surface containing the value. * @param size The size of the client's surface containing the value.
* @return The host-relative coordinate pair if a touchport is available. * @return The host-relative coordinate pair if a touchport is available.
*/ */
std::optional<std::pair<float, float>> 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) {
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_event = input->touch_port_event;
auto &touch_port = input->touch_port; auto &touch_port = input->touch_port;
if (touch_port_event->peek()) { if (touch_port_event->peek()) {
@ -502,8 +490,7 @@ namespace input {
* @param scalar The scalar cartesian coordinate pair. * @param scalar The scalar cartesian coordinate pair.
* @return The scaled radial coordinate. * @return The scaled radial coordinate.
*/ */
float float multiply_polar_by_cartesian_scalar(float r, float angle, const std::pair<float, float> &scalar) {
multiply_polar_by_cartesian_scalar(float r, float angle, const std::pair<float, float> &scalar) {
// Convert polar to cartesian coordinates // Convert polar to cartesian coordinates
float x = r * std::cos(angle); float x = r * std::cos(angle);
float y = r * std::sin(angle); float y = r * std::sin(angle);
@ -516,8 +503,7 @@ namespace input {
return std::sqrt(std::pow(x, 2) + std::pow(y, 2)); return std::sqrt(std::pow(x, 2) + std::pow(y, 2));
} }
std::pair<float, float> std::pair<float, float> scale_client_contact_area(const std::pair<float, float> &val, uint16_t rotation, const std::pair<float, float> &scalar) {
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 // If the rotation is unknown, we'll just scale both axes equally by using
// a 45-degree angle for our scaling calculations // a 45-degree angle for our scaling calculations
float angle = rotation == LI_ROT_UNKNOWN ? (M_PI / 4) : (rotation * (M_PI / 180)); float angle = rotation == LI_ROT_UNKNOWN ? (M_PI / 4) : (rotation * (M_PI / 180));
@ -530,8 +516,7 @@ namespace input {
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 void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
@ -561,15 +546,16 @@ namespace input {
auto &touch_port = input->touch_port; auto &touch_port = input->touch_port;
platf::touch_port_t abs_port { platf::touch_port_t abs_port {
touch_port.offset_x, touch_port.offset_y, touch_port.offset_x,
touch_port.env_width, touch_port.env_height touch_port.offset_y,
touch_port.env_width,
touch_port.env_height
}; };
platf::abs_mouse(platf_input, abs_port, tpcoords->first, tpcoords->second); platf::abs_mouse(platf_input, abs_port, tpcoords->first, tpcoords->second);
} }
void void passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
@ -617,7 +603,8 @@ namespace input {
} }
if ( if (
button == BUTTON_RIGHT && !release && 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, false);
platf::button_mouse(platf_input, BUTTON_RIGHT, true); platf::button_mouse(platf_input, BUTTON_RIGHT, true);
@ -629,8 +616,7 @@ namespace input {
platf::button_mouse(platf_input, button, release); platf::button_mouse(platf_input, button, release);
} }
short short map_keycode(short keycode) {
map_keycode(short keycode) {
auto it = config::input.keybindings.find(keycode); auto it = config::input.keybindings.find(keycode);
if (it != std::end(config::input.keybindings)) { if (it != std::end(config::input.keybindings)) {
return it->second; return it->second;
@ -642,16 +628,14 @@ namespace input {
/** /**
* @brief Update flags for keyboard shortcut combo's * @brief Update flags for keyboard shortcut combo's
*/ */
inline void inline void update_shortcutFlags(int *flags, short keyCode, bool release) {
update_shortcutFlags(int *flags, short keyCode, bool release) {
switch (keyCode) { switch (keyCode) {
case VKEY_SHIFT: case VKEY_SHIFT:
case VKEY_LSHIFT: case VKEY_LSHIFT:
case VKEY_RSHIFT: case VKEY_RSHIFT:
if (release) { if (release) {
*flags &= ~input_t::SHIFT; *flags &= ~input_t::SHIFT;
} } else {
else {
*flags |= input_t::SHIFT; *flags |= input_t::SHIFT;
} }
break; break;
@ -660,8 +644,7 @@ namespace input {
case VKEY_RCONTROL: case VKEY_RCONTROL:
if (release) { if (release) {
*flags &= ~input_t::CTRL; *flags &= ~input_t::CTRL;
} } else {
else {
*flags |= input_t::CTRL; *flags |= input_t::CTRL;
} }
break; break;
@ -670,16 +653,14 @@ namespace input {
case VKEY_RMENU: case VKEY_RMENU:
if (release) { if (release) {
*flags &= ~input_t::ALT; *flags &= ~input_t::ALT;
} } else {
else {
*flags |= input_t::ALT; *flags |= input_t::ALT;
} }
break; break;
} }
} }
bool bool is_modifier(uint16_t keyCode) {
is_modifier(uint16_t keyCode) {
switch (keyCode) { switch (keyCode) {
case VKEY_SHIFT: case VKEY_SHIFT:
case VKEY_LSHIFT: case VKEY_LSHIFT:
@ -696,8 +677,7 @@ namespace input {
} }
} }
void void send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
if (!release) { if (!release) {
// Press any synthetic modifiers required for this key // Press any synthetic modifiers required for this key
if (synthetic_modifiers & MODIFIER_SHIFT) { if (synthetic_modifiers & MODIFIER_SHIFT) {
@ -727,8 +707,7 @@ namespace input {
} }
} }
void void repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
// If key no longer pressed, stop repeating // If key no longer pressed, stop repeating
if (!key_press[make_kpid(key_code, flags)]) { if (!key_press[make_kpid(key_code, flags)]) {
key_press_repeat_id = nullptr; 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; key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags, synthetic_modifiers).task_id;
} }
void void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
if (!config::input.keyboard) { if (!config::input.keyboard) {
return; return;
} }
@ -780,13 +758,11 @@ namespace input {
if (config::input.key_repeat_delay.count() > 0) { 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; 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 // Already released
return; return;
} }
} } else if (!release) {
else if (!release) {
// Already pressed down key // Already pressed down key
return; return;
} }
@ -803,16 +779,14 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The scroll packet. * @param packet The scroll packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PNV_SCROLL_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PNV_SCROLL_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
if (config::input.high_resolution_scrolling) { if (config::input.high_resolution_scrolling) {
platf::scroll(platf_input, util::endian::big(packet->scrollAmt1)); platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
} } else {
else {
input->accumulated_vscroll_delta += util::endian::big(packet->scrollAmt1); input->accumulated_vscroll_delta += util::endian::big(packet->scrollAmt1);
auto full_ticks = input->accumulated_vscroll_delta / WHEEL_DELTA; auto full_ticks = input->accumulated_vscroll_delta / WHEEL_DELTA;
if (full_ticks) { if (full_ticks) {
@ -828,16 +802,14 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The scroll packet. * @param packet The scroll packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_HSCROLL_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_HSCROLL_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
if (config::input.high_resolution_scrolling) { if (config::input.high_resolution_scrolling) {
platf::hscroll(platf_input, util::endian::big(packet->scrollAmount)); platf::hscroll(platf_input, util::endian::big(packet->scrollAmount));
} } else {
else {
input->accumulated_hscroll_delta += util::endian::big(packet->scrollAmount); input->accumulated_hscroll_delta += util::endian::big(packet->scrollAmount);
auto full_ticks = input->accumulated_hscroll_delta / WHEEL_DELTA; auto full_ticks = input->accumulated_hscroll_delta / WHEEL_DELTA;
if (full_ticks) { if (full_ticks) {
@ -848,8 +820,7 @@ namespace input {
} }
} }
void void passthrough(PNV_UNICODE_PACKET packet) {
passthrough(PNV_UNICODE_PACKET packet) {
if (!config::input.keyboard) { if (!config::input.keyboard) {
return; return;
} }
@ -863,8 +834,7 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The controller arrival packet. * @param packet The controller arrival packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_ARRIVAL_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_ARRIVAL_PACKET packet) {
if (!config::input.controller) { if (!config::input.controller) {
return; return;
} }
@ -904,25 +874,23 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The touch packet. * @param packet The touch packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_TOUCH_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_TOUCH_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
// Convert the client normalized coordinates to touchport coordinates // Convert the client normalized coordinates to touchport coordinates
auto coords = client_to_touchport(input, 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});
{ 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) { if (!coords) {
return; return;
} }
auto &touch_port = input->touch_port; auto &touch_port = input->touch_port;
platf::touch_port_t abs_port { platf::touch_port_t abs_port {
touch_port.offset_x, touch_port.offset_y, touch_port.offset_x,
touch_port.env_width, touch_port.env_height touch_port.offset_y,
touch_port.env_width,
touch_port.env_height
}; };
// Renormalize the coordinates // Renormalize the coordinates
@ -940,7 +908,8 @@ namespace input {
{from_clamped_netfloat(packet->contactAreaMajor, 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}, from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f},
rotation, 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 { platf::touch_input_t touch {
packet->eventType, packet->eventType,
@ -961,25 +930,23 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The pen packet. * @param packet The pen packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_PEN_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_PEN_PACKET packet) {
if (!config::input.mouse) { if (!config::input.mouse) {
return; return;
} }
// Convert the client normalized coordinates to touchport coordinates // Convert the client normalized coordinates to touchport coordinates
auto coords = client_to_touchport(input, 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});
{ 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) { if (!coords) {
return; return;
} }
auto &touch_port = input->touch_port; auto &touch_port = input->touch_port;
platf::touch_port_t abs_port { platf::touch_port_t abs_port {
touch_port.offset_x, touch_port.offset_y, touch_port.offset_x,
touch_port.env_width, touch_port.env_height touch_port.offset_y,
touch_port.env_width,
touch_port.env_height
}; };
// Renormalize the coordinates // Renormalize the coordinates
@ -997,7 +964,8 @@ namespace input {
{from_clamped_netfloat(packet->contactAreaMajor, 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}, from_clamped_netfloat(packet->contactAreaMinor, 0.0f, 1.0f) * 65535.f},
rotation, 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 { platf::pen_input_t pen {
packet->eventType, packet->eventType,
@ -1020,8 +988,7 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The controller touch packet. * @param packet The controller touch packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_TOUCH_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_TOUCH_PACKET packet) {
if (!config::input.controller) { if (!config::input.controller) {
return; return;
} }
@ -1054,8 +1021,7 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The controller motion packet. * @param packet The controller motion packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_MOTION_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_MOTION_PACKET packet) {
if (!config::input.controller) { if (!config::input.controller) {
return; return;
} }
@ -1087,8 +1053,7 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param packet The controller battery packet. * @param packet The controller battery packet.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_BATTERY_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PSS_CONTROLLER_BATTERY_PACKET packet) {
if (!config::input.controller) { if (!config::input.controller) {
return; return;
} }
@ -1113,8 +1078,7 @@ namespace input {
platf::gamepad_battery(platf_input, battery); platf::gamepad_battery(platf_input, battery);
} }
void void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
if (!config::input.controller) { if (!config::input.controller) {
return; return;
} }
@ -1141,8 +1105,7 @@ namespace input {
} }
gamepad.id = id; 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. // If this is the final event for a gamepad being removed, free the gamepad and return.
free_gamepad(platf_input, gamepad.id); free_gamepad(platf_input, gamepad.id);
gamepad.id = -1; 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; 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); task_pool.cancel(gamepad.back_timeout_id);
gamepad.back_timeout_id = nullptr; gamepad.back_timeout_id = nullptr;
} }
@ -1243,8 +1205,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PNV_REL_MOUSE_MOVE_PACKET dest, PNV_REL_MOUSE_MOVE_PACKET src) {
batch(PNV_REL_MOUSE_MOVE_PACKET dest, PNV_REL_MOUSE_MOVE_PACKET src) {
short deltaX, deltaY; short deltaX, deltaY;
// Batching is safe as long as the result doesn't overflow a 16-bit integer // 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. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PNV_ABS_MOUSE_MOVE_PACKET dest, PNV_ABS_MOUSE_MOVE_PACKET src) {
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 // Batching must only happen if the reference width and height don't change
if (dest->width != src->width || dest->height != src->height) { if (dest->width != src->width || dest->height != src->height) {
return batch_result_e::terminate_batch; return batch_result_e::terminate_batch;
@ -1285,8 +1245,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PNV_SCROLL_PACKET dest, PNV_SCROLL_PACKET src) {
batch(PNV_SCROLL_PACKET dest, PNV_SCROLL_PACKET src) {
short scrollAmt; short scrollAmt;
// Batching is safe as long as the result doesn't overflow a 16-bit integer // 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. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PSS_HSCROLL_PACKET dest, PSS_HSCROLL_PACKET src) {
batch(PSS_HSCROLL_PACKET dest, PSS_HSCROLL_PACKET src) {
short scrollAmt; short scrollAmt;
// Batching is safe as long as the result doesn't overflow a 16-bit integer // 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. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PNV_MULTI_CONTROLLER_PACKET dest, PNV_MULTI_CONTROLLER_PACKET src) {
batch(PNV_MULTI_CONTROLLER_PACKET dest, PNV_MULTI_CONTROLLER_PACKET src) {
// Do not allow batching if the active controllers change // Do not allow batching if the active controllers change
if (dest->activeGamepadMask != src->activeGamepadMask) { if (dest->activeGamepadMask != src->activeGamepadMask) {
return batch_result_e::terminate_batch; return batch_result_e::terminate_batch;
@ -1355,8 +1312,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PSS_TOUCH_PACKET dest, PSS_TOUCH_PACKET src) {
batch(PSS_TOUCH_PACKET dest, PSS_TOUCH_PACKET src) {
// Only batch hover or move events // Only batch hover or move events
if (dest->eventType != LI_TOUCH_EVENT_MOVE && if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
dest->eventType != LI_TOUCH_EVENT_HOVER) { dest->eventType != LI_TOUCH_EVENT_HOVER) {
@ -1390,8 +1346,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PSS_PEN_PACKET dest, PSS_PEN_PACKET src) {
batch(PSS_PEN_PACKET dest, PSS_PEN_PACKET src) {
// Only batch hover or move events // Only batch hover or move events
if (dest->eventType != LI_TOUCH_EVENT_MOVE && if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
dest->eventType != LI_TOUCH_EVENT_HOVER) { dest->eventType != LI_TOUCH_EVENT_HOVER) {
@ -1424,8 +1379,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PSS_CONTROLLER_TOUCH_PACKET dest, PSS_CONTROLLER_TOUCH_PACKET src) {
batch(PSS_CONTROLLER_TOUCH_PACKET dest, PSS_CONTROLLER_TOUCH_PACKET src) {
// Only batch hover or move events // Only batch hover or move events
if (dest->eventType != LI_TOUCH_EVENT_MOVE && if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
dest->eventType != LI_TOUCH_EVENT_HOVER) { dest->eventType != LI_TOUCH_EVENT_HOVER) {
@ -1465,8 +1419,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PSS_CONTROLLER_MOTION_PACKET dest, PSS_CONTROLLER_MOTION_PACKET src) {
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 // 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. // in case we have more packets for this controller later in the queue.
if (dest->controllerNumber != src->controllerNumber) { if (dest->controllerNumber != src->controllerNumber) {
@ -1489,8 +1442,7 @@ namespace input {
* @param src A later packet to attempt to batch. * @param src A later packet to attempt to batch.
* @return The status of the batching operation. * @return The status of the batching operation.
*/ */
batch_result_e batch_result_e batch(PNV_INPUT_HEADER dest, PNV_INPUT_HEADER src) {
batch(PNV_INPUT_HEADER dest, PNV_INPUT_HEADER src) {
// We can only batch if the packet types are the same // We can only batch if the packet types are the same
if (dest->magic != src->magic) { if (dest->magic != src->magic) {
return batch_result_e::terminate_batch; return batch_result_e::terminate_batch;
@ -1526,8 +1478,7 @@ namespace input {
* @brief Called on a thread pool thread to process an input message. * @brief Called on a thread pool thread to process an input message.
* @param input The input context pointer. * @param input The input context pointer.
*/ */
void void passthrough_next_message(std::shared_ptr<input_t> input) {
passthrough_next_message(std::shared_ptr<input_t> input) {
// 'entry' backs the 'payload' pointer, so they must remain in scope together // 'entry' backs the 'payload' pointer, so they must remain in scope together
std::vector<uint8_t> entry; std::vector<uint8_t> entry;
PNV_INPUT_HEADER payload; PNV_INPUT_HEADER payload;
@ -1558,12 +1509,10 @@ namespace input {
if (batch_result == batch_result_e::terminate_batch) { if (batch_result == batch_result_e::terminate_batch) {
// Stop batching // Stop batching
break; break;
} } else if (batch_result == batch_result_e::batched) {
else if (batch_result == batch_result_e::batched) {
// Erase this entry since it was batched // Erase this entry since it was batched
i = input->input_queue.erase(i); i = input->input_queue.erase(i);
} } else {
else {
// We couldn't batch this entry, but try to batch later entries. // We couldn't batch this entry, but try to batch later entries.
i++; i++;
} }
@ -1627,8 +1576,7 @@ namespace input {
* @param input The input context pointer. * @param input The input context pointer.
* @param input_data The input message. * @param input_data The input message.
*/ */
void void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data) {
passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data) {
{ {
std::lock_guard<std::mutex> lg(input->input_queue_lock); std::lock_guard<std::mutex> lg(input->input_queue_lock);
input->input_queue.push_back(std::move(input_data)); input->input_queue.push_back(std::move(input_data));
@ -1636,8 +1584,7 @@ namespace input {
task_pool.push(passthrough_next_message, input); task_pool.push(passthrough_next_message, input);
} }
void void reset(std::shared_ptr<input_t> &input) {
reset(std::shared_ptr<input_t> &input) {
task_pool.cancel(key_press_repeat_id); task_pool.cancel(key_press_repeat_id);
task_pool.cancel(input->mouse_left_button_timeout); task_pool.cancel(input->mouse_left_button_timeout);
@ -1668,15 +1615,13 @@ namespace input {
} }
}; };
[[nodiscard]] std::unique_ptr<platf::deinit_t> [[nodiscard]] std::unique_ptr<platf::deinit_t> init() {
init() {
platf_input = platf::input(); platf_input = platf::input();
return std::make_unique<deinit_t>(); return std::make_unique<deinit_t>();
} }
bool bool probe_gamepads() {
probe_gamepads() {
auto input = static_cast<platf::input_t *>(platf_input.get()); auto input = static_cast<platf::input_t *>(platf_input.get());
const auto gamepads = platf::supported_gamepads(input); const auto gamepads = platf::supported_gamepads(input);
for (auto &gamepad : gamepads) { for (auto &gamepad : gamepads) {
@ -1687,11 +1632,11 @@ namespace input {
return true; return true;
} }
std::shared_ptr<input_t> std::shared_ptr<input_t> alloc(safe::mail_t mail) {
alloc(safe::mail_t mail) {
auto input = std::make_shared<input_t>( auto input = std::make_shared<input_t>(
mail->event<input::touch_port_t>(mail::touch_port), 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 // Workaround to ensure new frames will be captured when a client connects
task_pool.pushDelayed([]() { task_pool.pushDelayed([]() {

View file

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

View file

@ -47,15 +47,13 @@ namespace logging {
deinit(); deinit();
} }
void void deinit() {
deinit() {
log_flush(); log_flush();
bl::core::get()->remove_sink(sink); bl::core::get()->remove_sink(sink);
sink.reset(); sink.reset();
} }
void void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os) {
formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os) {
constexpr const char *message = "Message"; constexpr const char *message = "Message";
constexpr const char *severity = "Severity"; constexpr const char *severity = "Severity";
@ -90,7 +88,8 @@ namespace logging {
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>( 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 t = std::chrono::system_clock::to_time_t(now);
auto lt = *std::localtime(&t); auto lt = *std::localtime(&t);
@ -99,8 +98,7 @@ namespace logging {
<< log_type << view.attribute_values()[message].extract<std::string>(); << log_type << view.attribute_values()[message].extract<std::string>();
} }
[[nodiscard]] std::unique_ptr<deinit_t> [[nodiscard]] std::unique_ptr<deinit_t> init(int min_log_level, const std::string &log_file) {
init(int min_log_level, const std::string &log_file) {
if (sink) { if (sink) {
// Deinitialize the logging system before reinitializing it. This can probably only ever be hit in tests. // Deinitialize the logging system before reinitializing it. This can probably only ever be hit in tests.
deinit(); deinit();
@ -127,12 +125,10 @@ namespace logging {
return std::make_unique<deinit_t>(); return std::make_unique<deinit_t>();
} }
void void setup_av_logging(int min_log_level) {
setup_av_logging(int min_log_level) {
if (min_log_level >= 1) { if (min_log_level >= 1) {
av_log_set_level(AV_LOG_QUIET); av_log_set_level(AV_LOG_QUIET);
} } else {
else {
av_log_set_level(AV_LOG_DEBUG); av_log_set_level(AV_LOG_DEBUG);
} }
av_log_set_callback([](void *ptr, int level, const char *fmt, va_list vl) { av_log_set_callback([](void *ptr, int level, const char *fmt, va_list vl) {
@ -144,25 +140,20 @@ namespace logging {
// We print AV_LOG_FATAL at the error level. FFmpeg prints things as fatal that // 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. // are expected in some cases, such as lack of codec support or similar things.
BOOST_LOG(error) << buffer; BOOST_LOG(error) << buffer;
} } else if (level <= AV_LOG_WARNING) {
else if (level <= AV_LOG_WARNING) {
BOOST_LOG(warning) << buffer; BOOST_LOG(warning) << buffer;
} } else if (level <= AV_LOG_INFO) {
else if (level <= AV_LOG_INFO) {
BOOST_LOG(info) << buffer; 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 // AV_LOG_VERBOSE is less verbose than AV_LOG_DEBUG
BOOST_LOG(debug) << buffer; BOOST_LOG(debug) << buffer;
} } else {
else {
BOOST_LOG(verbose) << buffer; BOOST_LOG(verbose) << buffer;
} }
}); });
} }
void void setup_libdisplaydevice_logging(int min_log_level) {
setup_libdisplaydevice_logging(int min_log_level) {
constexpr int min_level {static_cast<int>(display_device::Logger::LogLevel::verbose)}; constexpr int min_level {static_cast<int>(display_device::Logger::LogLevel::verbose)};
constexpr int max_level {static_cast<int>(display_device::Logger::LogLevel::fatal)}; 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))}; const auto log_level {static_cast<display_device::Logger::LogLevel>(std::min(std::max(min_level, min_log_level), max_level))};
@ -192,15 +183,13 @@ namespace logging {
}); });
} }
void void log_flush() {
log_flush() {
if (sink) { if (sink) {
sink->flush(); sink->flush();
} }
} }
void void print_help(const char *name) {
print_help(const char *name) {
std::cout std::cout
<< "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl << "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl
<< " Any configurable option can be overwritten with: \"name=value\""sv << std::endl << " Any configurable option can be overwritten with: \"name=value\""sv << std::endl
@ -220,13 +209,11 @@ namespace logging {
<< std::endl; << std::endl;
} }
std::string std::string bracket(const std::string &input) {
bracket(const std::string &input) {
return "["s + input + "]"s; return "["s + input + "]"s;
} }
std::wstring std::wstring bracket(const std::wstring &input) {
bracket(const std::wstring &input) {
return L"["s + input + L"]"s; return L"["s + input + L"]"s;
} }

View file

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

View file

@ -30,31 +30,37 @@ extern "C" {
using namespace std::literals; using namespace std::literals;
std::map<int, std::function<void()>> signal_handlers; std::map<int, std::function<void()>> signal_handlers;
void
on_signal_forwarder(int sig) { void on_signal_forwarder(int sig) {
signal_handlers.at(sig)(); signal_handlers.at(sig)();
} }
template<class FN> template<class FN>
void void on_signal(int sig, FN &&fn) {
on_signal(int sig, FN &&fn) {
signal_handlers.emplace(sig, std::forward<FN>(fn)); signal_handlers.emplace(sig, std::forward<FN>(fn));
std::signal(sig, on_signal_forwarder); std::signal(sig, on_signal_forwarder);
} }
std::map<std::string_view, std::function<int(const char *name, int argc, char **argv)>> cmd_to_func { 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); } }, {"creds"sv, [](const char *name, int argc, char **argv) {
{ "help"sv, [](const char *name, int argc, char **argv) { return args::help(name); } }, return args::creds(name, argc, argv);
{ "version"sv, [](const char *name, int argc, char **argv) { return args::version(); } }, }},
{"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 #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 #endif
}; };
#ifdef _WIN32 #ifdef _WIN32
LRESULT CALLBACK LRESULT CALLBACK SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) { switch (uMsg) {
case WM_CLOSE: case WM_CLOSE:
DestroyWindow(hwnd); DestroyWindow(hwnd);
@ -62,7 +68,8 @@ SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
case WM_DESTROY: case WM_DESTROY:
PostQuitMessage(0); PostQuitMessage(0);
return 0; return 0;
case WM_ENDSESSION: { case WM_ENDSESSION:
{
// Terminate ourselves with a blocking exit call // Terminate ourselves with a blocking exit call
std::cout << "Received WM_ENDSESSION"sv << std::endl; std::cout << "Received WM_ENDSESSION"sv << std::endl;
lifetime::exit_sunshine(0, false); lifetime::exit_sunshine(0, false);
@ -73,8 +80,7 @@ SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
} }
} }
WINAPI BOOL WINAPI BOOL ConsoleCtrlHandler(DWORD type) {
ConsoleCtrlHandler(DWORD type) {
if (type == CTRL_CLOSE_EVENT) { if (type == CTRL_CLOSE_EVENT) {
BOOST_LOG(info) << "Console closed handler called"; BOOST_LOG(info) << "Console closed handler called";
lifetime::exit_sunshine(0, false); lifetime::exit_sunshine(0, false);
@ -83,8 +89,7 @@ ConsoleCtrlHandler(DWORD type) {
} }
#endif #endif
int int main(int argc, char *argv[]) {
main(int argc, char *argv[]) {
lifetime::argv = argv; lifetime::argv = argv;
task_pool_util::TaskPool::task_id_t force_shutdown = nullptr; task_pool_util::TaskPool::task_id_t force_shutdown = nullptr;
@ -188,7 +193,8 @@ main(int argc, char *argv[]) {
nullptr, nullptr,
nullptr, nullptr,
nullptr, nullptr,
nullptr); nullptr
);
session_monitor_hwnd_promise.set_value(wnd); 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) { if (session_monitor_join_thread_future.wait_for(1s) == std::future_status::ready) {
session_monitor_thread.join(); session_monitor_thread.join();
return; return;
} } else {
else {
BOOST_LOG(warning) << "session_monitor_join_thread_future reached timeout"; BOOST_LOG(warning) << "session_monitor_join_thread_future reached timeout";
} }
} } else {
else {
BOOST_LOG(warning) << "session_monitor_hwnd_future reached timeout"; BOOST_LOG(warning) << "session_monitor_hwnd_future reached timeout";
} }

View file

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

View file

@ -4,6 +4,7 @@
*/ */
#pragma once #pragma once
// standard includes
#include <utility> #include <utility>
/** /**
@ -24,7 +25,8 @@ namespace move_by_copy_util {
public: public:
explicit MoveByCopy(move_type &&to_move): explicit MoveByCopy(move_type &&to_move):
_to_move(std::move(to_move)) {} _to_move(std::move(to_move)) {
}
MoveByCopy(MoveByCopy &&other) = default; MoveByCopy(MoveByCopy &&other) = default;
@ -32,11 +34,9 @@ namespace move_by_copy_util {
*this = other; *this = other;
} }
MoveByCopy & MoveByCopy &operator=(MoveByCopy &&other) = default;
operator=(MoveByCopy &&other) = default;
MoveByCopy & MoveByCopy &operator=(const MoveByCopy &other) {
operator=(const MoveByCopy &other) {
this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move); this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move);
return *this; return *this;
@ -48,15 +48,13 @@ namespace move_by_copy_util {
}; };
template<class T> template<class T>
MoveByCopy<T> MoveByCopy<T> cmove(T &movable) {
cmove(T &movable) {
return MoveByCopy<T>(std::move(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 // Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
template<class T> template<class T>
MoveByCopy<T> MoveByCopy<T> const_cmove(const T &movable) {
const_cmove(const T &movable) {
return MoveByCopy<T>(std::move(const_cast<T &>(movable))); return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
} }
} // namespace move_by_copy_util } // namespace move_by_copy_util

View file

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

View file

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

View file

@ -2,8 +2,10 @@
* @file src/nvenc/nvenc_base.cpp * @file src/nvenc/nvenc_base.cpp
* @brief Definitions for abstract platform-agnostic base of standalone NVENC encoder. * @brief Definitions for abstract platform-agnostic base of standalone NVENC encoder.
*/ */
// this include
#include "nvenc_base.h" #include "nvenc_base.h"
// local includes
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/utility.h" #include "src/utility.h"
@ -21,9 +23,10 @@
namespace { namespace {
GUID GUID quality_preset_guid_from_number(unsigned number) {
quality_preset_guid_from_number(unsigned number) { if (number > 7) {
if (number > 7) number = 7; number = 7;
}
switch (number) { switch (number) {
case 1: case 1:
@ -50,13 +53,11 @@ namespace {
} }
}; };
bool bool equal_guids(const GUID &guid1, const GUID &guid2) {
equal_guids(const GUID &guid1, const GUID &guid2) {
return std::memcmp(&guid1, &guid2, sizeof(GUID)) == 0; return std::memcmp(&guid1, &guid2, sizeof(GUID)) == 0;
} }
auto auto quality_preset_string_from_guid(const GUID &guid) {
quality_preset_string_from_guid(const GUID &guid) {
if (equal_guids(guid, NV_ENC_PRESET_P1_GUID)) { if (equal_guids(guid, NV_ENC_PRESET_P1_GUID)) {
return "P1"; return "P1";
} }
@ -93,16 +94,21 @@ namespace nvenc {
// Use destroy_encoder() instead // Use destroy_encoder() instead
} }
bool bool nvenc_base::create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format) {
nvenc_base::create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format) {
// Pick the minimum NvEncode API version required to support the specified codec // Pick the minimum NvEncode API version required to support the specified codec
// to maximize driver compatibility. AV1 was introduced in SDK v12.0. // to maximize driver compatibility. AV1 was introduced in SDK v12.0.
minimum_api_version = (client_config.videoFormat <= 1) ? MAKE_NVENC_VER(11U, 0U) : MAKE_NVENC_VER(12U, 0U); minimum_api_version = (client_config.videoFormat <= 1) ? MAKE_NVENC_VER(11U, 0U) : MAKE_NVENC_VER(12U, 0U);
if (!nvenc && !init_library()) return false; if (!nvenc && !init_library()) {
return false;
}
if (encoder) destroy_encoder(); if (encoder) {
auto fail_guard = util::fail_guard([this] { destroy_encoder(); }); destroy_encoder();
}
auto fail_guard = util::fail_guard([this] {
destroy_encoder();
});
encoder_params.width = client_config.width; encoder_params.width = client_config.width;
encoder_params.height = client_config.height; encoder_params.height = client_config.height;
@ -259,8 +265,7 @@ namespace nvenc {
auto set_ref_frames = [&](uint32_t &ref_frames_option, NV_ENC_NUM_REF_FRAMES &L0_option, uint32_t ref_frames_default) { auto set_ref_frames = [&](uint32_t &ref_frames_option, NV_ENC_NUM_REF_FRAMES &L0_option, uint32_t ref_frames_default) {
if (client_config.numRefFrames > 0) { if (client_config.numRefFrames > 0) {
ref_frames_option = client_config.numRefFrames; ref_frames_option = client_config.numRefFrames;
} } else {
else {
ref_frames_option = ref_frames_default; ref_frames_option = ref_frames_default;
} }
if (ref_frames_option > 0 && !get_encoder_cap(NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES)) { if (ref_frames_option > 0 && !get_encoder_cap(NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES)) {
@ -294,15 +299,15 @@ namespace nvenc {
}; };
switch (client_config.videoFormat) { switch (client_config.videoFormat) {
case 0: { case 0:
{
// H.264 // H.264
enc_config.profileGUID = buffer_is_yuv444() ? NV_ENC_H264_PROFILE_HIGH_444_GUID : NV_ENC_H264_PROFILE_HIGH_GUID; enc_config.profileGUID = buffer_is_yuv444() ? NV_ENC_H264_PROFILE_HIGH_444_GUID : NV_ENC_H264_PROFILE_HIGH_GUID;
auto &format_config = enc_config.encodeCodecConfig.h264Config; auto &format_config = enc_config.encodeCodecConfig.h264Config;
set_h264_hevc_common_format_config(format_config); set_h264_hevc_common_format_config(format_config);
if (config.h264_cavlc || !get_encoder_cap(NV_ENC_CAPS_SUPPORT_CABAC)) { if (config.h264_cavlc || !get_encoder_cap(NV_ENC_CAPS_SUPPORT_CABAC)) {
format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC; format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC;
} } else {
else {
format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CABAC; format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CABAC;
} }
set_ref_frames(format_config.maxNumRefFrames, format_config.numRefL0, 5); set_ref_frames(format_config.maxNumRefFrames, format_config.numRefL0, 5);
@ -311,7 +316,8 @@ namespace nvenc {
break; break;
} }
case 1: { case 1:
{
// HEVC // HEVC
auto &format_config = enc_config.encodeCodecConfig.hevcConfig; auto &format_config = enc_config.encodeCodecConfig.hevcConfig;
set_h264_hevc_common_format_config(format_config); set_h264_hevc_common_format_config(format_config);
@ -328,19 +334,18 @@ namespace nvenc {
format_config.intraRefreshCnt = 299; format_config.intraRefreshCnt = 299;
if (get_encoder_cap(NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH)) { if (get_encoder_cap(NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH)) {
format_config.singleSliceIntraRefresh = 1; format_config.singleSliceIntraRefresh = 1;
} } else {
else {
BOOST_LOG(warning) << "NvEnc: Single Slice Intra Refresh not supported"; BOOST_LOG(warning) << "NvEnc: Single Slice Intra Refresh not supported";
} }
} } else {
else {
BOOST_LOG(error) << "NvEnc: Client asked for intra-refresh but the encoder does not support intra-refresh"; BOOST_LOG(error) << "NvEnc: Client asked for intra-refresh but the encoder does not support intra-refresh";
} }
} }
break; break;
} }
case 2: { case 2:
{
// AV1 // AV1
auto &format_config = enc_config.encodeCodecConfig.av1Config; auto &format_config = enc_config.encodeCodecConfig.av1Config;
format_config.repeatSeqHdr = 1; format_config.repeatSeqHdr = 1;
@ -409,16 +414,36 @@ namespace nvenc {
client_config.videoFormat == 2 ? "AV1 " : client_config.videoFormat == 2 ? "AV1 " :
" "; " ";
std::string extra; std::string extra;
if (init_params.enableEncodeAsync) extra += " async"; if (init_params.enableEncodeAsync) {
if (buffer_is_yuv444()) extra += " yuv444"; extra += " async";
if (buffer_is_10bit()) extra += " 10-bit"; }
if (enc_config.rcParams.multiPass != NV_ENC_MULTI_PASS_DISABLED) extra += " two-pass"; if (buffer_is_yuv444()) {
if (config.vbv_percentage_increase > 0 && get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) extra += " vbv+" + std::to_string(config.vbv_percentage_increase); extra += " yuv444";
if (encoder_params.rfi) extra += " rfi"; }
if (init_params.enableWeightedPrediction) extra += " weighted-prediction"; if (buffer_is_10bit()) {
if (enc_config.rcParams.enableAQ) extra += " spatial-aq"; extra += " 10-bit";
if (enc_config.rcParams.enableMinQP) extra += " qpmin=" + std::to_string(enc_config.rcParams.minQP.qpInterP); }
if (config.insert_filler_data) extra += " filler-data"; if (enc_config.rcParams.multiPass != NV_ENC_MULTI_PASS_DISABLED) {
extra += " two-pass";
}
if (config.vbv_percentage_increase > 0 && get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) {
extra += " vbv+" + std::to_string(config.vbv_percentage_increase);
}
if (encoder_params.rfi) {
extra += " rfi";
}
if (init_params.enableWeightedPrediction) {
extra += " weighted-prediction";
}
if (enc_config.rcParams.enableAQ) {
extra += " spatial-aq";
}
if (enc_config.rcParams.enableMinQP) {
extra += " qpmin=" + std::to_string(enc_config.rcParams.minQP.qpInterP);
}
if (config.insert_filler_data) {
extra += " filler-data";
}
BOOST_LOG(info) << "NvEnc: created encoder " << video_format_string << quality_preset_string_from_guid(init_params.presetGUID) << extra; BOOST_LOG(info) << "NvEnc: created encoder " << video_format_string << quality_preset_string_from_guid(init_params.presetGUID) << extra;
} }
@ -428,8 +453,7 @@ namespace nvenc {
return true; return true;
} }
void void nvenc_base::destroy_encoder() {
nvenc_base::destroy_encoder() {
if (output_bitstream) { if (output_bitstream) {
if (nvenc_failed(nvenc->nvEncDestroyBitstreamBuffer(encoder, output_bitstream))) { if (nvenc_failed(nvenc->nvEncDestroyBitstreamBuffer(encoder, output_bitstream))) {
BOOST_LOG(error) << "NvEnc: NvEncDestroyBitstreamBuffer() failed: " << last_nvenc_error_string; BOOST_LOG(error) << "NvEnc: NvEncDestroyBitstreamBuffer() failed: " << last_nvenc_error_string;
@ -460,8 +484,7 @@ namespace nvenc {
encoder_params = {}; encoder_params = {};
} }
nvenc_encoded_frame nvenc_encoded_frame nvenc_base::encode_frame(uint64_t frame_index, bool force_idr) {
nvenc_base::encode_frame(uint64_t frame_index, bool force_idr) {
if (!encoder) { if (!encoder) {
return {}; return {};
} }
@ -545,9 +568,10 @@ namespace nvenc {
return encoded_frame; return encoded_frame;
} }
bool bool nvenc_base::invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) {
nvenc_base::invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) { if (!encoder || !encoder_params.rfi) {
if (!encoder || !encoder_params.rfi) return false; return false;
}
if (first_frame >= encoder_state.last_rfi_range.first && if (first_frame >= encoder_state.last_rfi_range.first &&
last_frame <= encoder_state.last_rfi_range.second) { last_frame <= encoder_state.last_rfi_range.second) {
@ -582,8 +606,7 @@ namespace nvenc {
return true; return true;
} }
bool bool nvenc_base::nvenc_failed(NVENCSTATUS status) {
nvenc_base::nvenc_failed(NVENCSTATUS status) {
auto status_string = [](NVENCSTATUS status) -> std::string { auto status_string = [](NVENCSTATUS status) -> std::string {
switch (status) { switch (status) {
#define nvenc_status_case(x) \ #define nvenc_status_case(x) \
@ -637,8 +660,7 @@ namespace nvenc {
return false; return false;
} }
uint32_t uint32_t nvenc_base::min_struct_version(uint32_t version, uint32_t v11_struct_version, uint32_t v12_struct_version) {
nvenc_base::min_struct_version(uint32_t version, uint32_t v11_struct_version, uint32_t v12_struct_version) {
assert(minimum_api_version); assert(minimum_api_version);
// Mask off and replace the original NVENCAPI_VERSION // Mask off and replace the original NVENCAPI_VERSION

View file

@ -4,15 +4,16 @@
*/ */
#pragma once #pragma once
// lib includes
#include <ffnvcodec/nvEncodeAPI.h>
// local includes
#include "nvenc_colorspace.h" #include "nvenc_colorspace.h"
#include "nvenc_config.h" #include "nvenc_config.h"
#include "nvenc_encoded_frame.h" #include "nvenc_encoded_frame.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/video.h" #include "src/video.h"
#include <ffnvcodec/nvEncodeAPI.h>
/** /**
* @brief Standalone NVENC encoder * @brief Standalone NVENC encoder
*/ */
@ -31,8 +32,7 @@ namespace nvenc {
virtual ~nvenc_base(); virtual ~nvenc_base();
nvenc_base(const nvenc_base &) = delete; nvenc_base(const nvenc_base &) = delete;
nvenc_base & nvenc_base &operator=(const nvenc_base &) = delete;
operator=(const nvenc_base &) = delete;
/** /**
* @brief Create the encoder. * @brief Create the encoder.
@ -42,15 +42,13 @@ namespace nvenc {
* @param buffer_format Platform-agnostic input surface format. * @param buffer_format Platform-agnostic input surface format.
* @return `true` on success, `false` on error * @return `true` on success, `false` on error
*/ */
bool bool create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format);
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. * @brief Destroy the encoder.
* Derived classes classes call it in the destructor. * Derived classes classes call it in the destructor.
*/ */
void void destroy_encoder();
destroy_encoder();
/** /**
* @brief Encode the next frame using platform-specific input surface. * @brief Encode the next frame using platform-specific input surface.
@ -60,8 +58,7 @@ namespace nvenc {
* @param force_idr Whether to encode frame as forced IDR. * @param force_idr Whether to encode frame as forced IDR.
* @return Encoded frame. * @return Encoded frame.
*/ */
nvenc_encoded_frame nvenc_encoded_frame encode_frame(uint64_t frame_index, bool force_idr);
encode_frame(uint64_t frame_index, bool force_idr);
/** /**
* @brief Perform reference frame invalidation (RFI) procedure. * @brief Perform reference frame invalidation (RFI) procedure.
@ -70,8 +67,7 @@ namespace nvenc {
* @return `true` on success, `false` on error. * @return `true` on success, `false` on error.
* After error next frame must be encoded with `force_idr = true`. * After error next frame must be encoded with `force_idr = true`.
*/ */
bool bool invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
protected: protected:
/** /**
@ -79,8 +75,7 @@ namespace nvenc {
* Called during `create_encoder()` if `nvenc` variable is not initialized. * Called during `create_encoder()` if `nvenc` variable is not initialized.
* @return `true` on success, `false` on error * @return `true` on success, `false` on error
*/ */
virtual bool virtual bool init_library() = 0;
init_library() = 0;
/** /**
* @brief Required. Used for creating outside-facing input surface, * @brief Required. Used for creating outside-facing input surface,
@ -88,16 +83,16 @@ namespace nvenc {
* Called during `create_encoder()`. * Called during `create_encoder()`.
* @return `true` on success, `false` on error * @return `true` on success, `false` on error
*/ */
virtual bool virtual bool create_and_register_input_buffer() = 0;
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()`. * @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. * Typically used for interop copy.
* @return `true` on success, `false` on error * @return `true` on success, `false` on error
*/ */
virtual bool virtual bool synchronize_input_buffer() {
synchronize_input_buffer() { return true; } return true;
}
/** /**
* @brief Optional. Override if you want to create encoder in async mode. * @brief Optional. Override if you want to create encoder in async mode.
@ -105,11 +100,11 @@ namespace nvenc {
* @param timeout_ms Wait timeout in milliseconds * @param timeout_ms Wait timeout in milliseconds
* @return `true` on success, `false` on timeout or error * @return `true` on success, `false` on timeout or error
*/ */
virtual bool virtual bool wait_for_async_event(uint32_t timeout_ms) {
wait_for_async_event(uint32_t timeout_ms) { return false; } return false;
}
bool bool nvenc_failed(NVENCSTATUS status);
nvenc_failed(NVENCSTATUS status);
/** /**
* @brief This function returns the corresponding struct version for the minimum API required by the codec. * @brief This function returns the corresponding struct version for the minimum API required by the codec.
@ -119,8 +114,7 @@ namespace nvenc {
* @param v12_struct_version Optionally specifies the struct version to use with v12 SDK major versions. * @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. * @return A suitable struct version for the active codec.
*/ */
uint32_t uint32_t min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0);
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; const NV_ENC_DEVICE_TYPE device_type;

View file

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

View file

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

View file

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

View file

@ -3,8 +3,10 @@
* @brief Definitions for native Direct3D11 NVENC encoder. * @brief Definitions for native Direct3D11 NVENC encoder.
*/ */
#ifdef _WIN32 #ifdef _WIN32
// this include
#include "nvenc_d3d11_native.h" #include "nvenc_d3d11_native.h"
// local includes
#include "nvenc_utils.h" #include "nvenc_utils.h"
namespace nvenc { namespace nvenc {
@ -16,7 +18,9 @@ namespace nvenc {
} }
nvenc_d3d11_native::~nvenc_d3d11_native() { nvenc_d3d11_native::~nvenc_d3d11_native() {
if (encoder) destroy_encoder(); if (encoder) {
destroy_encoder();
}
} }
ID3D11Texture2D * ID3D11Texture2D *
@ -24,8 +28,7 @@ namespace nvenc {
return d3d_input_texture.GetInterfacePtr(); return d3d_input_texture.GetInterfacePtr();
} }
bool bool nvenc_d3d11_native::create_and_register_input_buffer() {
nvenc_d3d11_native::create_and_register_input_buffer() {
if (encoder_params.buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) { 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"; BOOST_LOG(error) << "NvEnc: 10-bit 4:4:4 encoding is incompatible with D3D11 surface formats, use CUDA interop";
return false; return false;

View file

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

View file

@ -3,8 +3,10 @@
* @brief Definitions for CUDA NVENC encoder with Direct3D11 input surfaces. * @brief Definitions for CUDA NVENC encoder with Direct3D11 input surfaces.
*/ */
#ifdef _WIN32 #ifdef _WIN32
// this include
#include "nvenc_d3d11_on_cuda.h" #include "nvenc_d3d11_on_cuda.h"
// local includes
#include "nvenc_utils.h" #include "nvenc_utils.h"
namespace nvenc { namespace nvenc {
@ -15,7 +17,9 @@ namespace nvenc {
} }
nvenc_d3d11_on_cuda::~nvenc_d3d11_on_cuda() { nvenc_d3d11_on_cuda::~nvenc_d3d11_on_cuda() {
if (encoder) destroy_encoder(); if (encoder) {
destroy_encoder();
}
if (cuda_context) { if (cuda_context) {
{ {
@ -48,14 +52,14 @@ namespace nvenc {
} }
} }
ID3D11Texture2D * ID3D11Texture2D *nvenc_d3d11_on_cuda::get_input_texture() {
nvenc_d3d11_on_cuda::get_input_texture() {
return d3d_input_texture.GetInterfacePtr(); return d3d_input_texture.GetInterfacePtr();
} }
bool bool nvenc_d3d11_on_cuda::init_library() {
nvenc_d3d11_on_cuda::init_library() { if (!nvenc_d3d11::init_library()) {
if (!nvenc_d3d11::init_library()) return false; return false;
}
constexpr auto dll_name = "nvcuda.dll"; constexpr auto dll_name = "nvcuda.dll";
@ -82,8 +86,7 @@ namespace nvenc {
FreeLibrary(cuda_functions.dll); FreeLibrary(cuda_functions.dll);
cuda_functions = {}; cuda_functions = {};
} }
} } else {
else {
BOOST_LOG(debug) << "NvEnc: couldn't load CUDA dynamic library " << dll_name; BOOST_LOG(debug) << "NvEnc: couldn't load CUDA dynamic library " << dll_name;
} }
@ -99,12 +102,10 @@ namespace nvenc {
cuda_succeeded(cuda_functions.cuCtxCreate(&cuda_context, CU_CTX_SCHED_BLOCKING_SYNC, cuda_device)) && cuda_succeeded(cuda_functions.cuCtxCreate(&cuda_context, CU_CTX_SCHED_BLOCKING_SYNC, cuda_device)) &&
cuda_succeeded(cuda_functions.cuCtxPopCurrent(&cuda_context))) { cuda_succeeded(cuda_functions.cuCtxPopCurrent(&cuda_context))) {
device = cuda_context; device = cuda_context;
} } else {
else {
BOOST_LOG(error) << "NvEnc: couldn't create CUDA interop context: error " << last_cuda_error; BOOST_LOG(error) << "NvEnc: couldn't create CUDA interop context: error " << last_cuda_error;
} }
} } else {
else {
BOOST_LOG(error) << "NvEnc: couldn't get DXGI adapter for CUDA interop"; BOOST_LOG(error) << "NvEnc: couldn't get DXGI adapter for CUDA interop";
} }
} }
@ -112,8 +113,7 @@ namespace nvenc {
return device != nullptr; return device != nullptr;
} }
bool bool nvenc_d3d11_on_cuda::create_and_register_input_buffer() {
nvenc_d3d11_on_cuda::create_and_register_input_buffer() {
if (encoder_params.buffer_format != NV_ENC_BUFFER_FORMAT_YUV444_10BIT) { 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"; BOOST_LOG(error) << "NvEnc: CUDA interop is expected to be used only for 10-bit 4:4:4 encoding";
return false; return false;
@ -138,13 +138,16 @@ namespace nvenc {
{ {
auto autopop_context = push_context(); auto autopop_context = push_context();
if (!autopop_context) return false; if (!autopop_context) {
return false;
}
if (!cuda_d3d_input_texture) { if (!cuda_d3d_input_texture) {
if (cuda_failed(cuda_functions.cuGraphicsD3D11RegisterResource( if (cuda_failed(cuda_functions.cuGraphicsD3D11RegisterResource(
&cuda_d3d_input_texture, &cuda_d3d_input_texture,
d3d_input_texture, d3d_input_texture,
CU_GRAPHICS_REGISTER_FLAGS_NONE))) { CU_GRAPHICS_REGISTER_FLAGS_NONE
))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsD3D11RegisterResource() failed: error " << last_cuda_error; BOOST_LOG(error) << "NvEnc: cuGraphicsD3D11RegisterResource() failed: error " << last_cuda_error;
return false; return false;
} }
@ -156,7 +159,9 @@ namespace nvenc {
&cuda_surface_pitch, &cuda_surface_pitch,
// Planar 16-bit YUV // Planar 16-bit YUV
encoder_params.width * 2, encoder_params.width * 2,
encoder_params.height * 3, 16))) { encoder_params.height * 3,
16
))) {
BOOST_LOG(error) << "NvEnc: cuMemAllocPitch() failed: error " << last_cuda_error; BOOST_LOG(error) << "NvEnc: cuMemAllocPitch() failed: error " << last_cuda_error;
return false; return false;
} }
@ -184,10 +189,11 @@ namespace nvenc {
return true; return true;
} }
bool bool nvenc_d3d11_on_cuda::synchronize_input_buffer() {
nvenc_d3d11_on_cuda::synchronize_input_buffer() {
auto autopop_context = push_context(); auto autopop_context = push_context();
if (!autopop_context) return false; if (!autopop_context) {
return false;
}
if (cuda_failed(cuda_functions.cuGraphicsMapResources(1, &cuda_d3d_input_texture, 0))) { if (cuda_failed(cuda_functions.cuGraphicsMapResources(1, &cuda_d3d_input_texture, 0))) {
BOOST_LOG(error) << "NvEnc: cuGraphicsMapResources() failed: error " << last_cuda_error; BOOST_LOG(error) << "NvEnc: cuGraphicsMapResources() failed: error " << last_cuda_error;
@ -230,14 +236,12 @@ namespace nvenc {
return unmap(); return unmap();
} }
bool bool nvenc_d3d11_on_cuda::cuda_succeeded(CUresult result) {
nvenc_d3d11_on_cuda::cuda_succeeded(CUresult result) {
last_cuda_error = result; last_cuda_error = result;
return result == CUDA_SUCCESS; return result == CUDA_SUCCESS;
} }
bool bool nvenc_d3d11_on_cuda::cuda_failed(CUresult result) {
nvenc_d3d11_on_cuda::cuda_failed(CUresult result) {
last_cuda_error = result; last_cuda_error = result;
return result != CUDA_SUCCESS; return result != CUDA_SUCCESS;
} }
@ -251,13 +255,11 @@ namespace nvenc {
} }
} }
nvenc_d3d11_on_cuda::autopop_context nvenc_d3d11_on_cuda::autopop_context nvenc_d3d11_on_cuda::push_context() {
nvenc_d3d11_on_cuda::push_context() {
if (cuda_context && if (cuda_context &&
cuda_succeeded(cuda_functions.cuCtxPushCurrent(cuda_context))) { cuda_succeeded(cuda_functions.cuCtxPushCurrent(cuda_context))) {
return {*this, cuda_context}; return {*this, cuda_context};
} } else {
else {
BOOST_LOG(error) << "NvEnc: cuCtxPushCurrent() failed: error " << last_cuda_error; BOOST_LOG(error) << "NvEnc: cuCtxPushCurrent() failed: error " << last_cuda_error;
return {*this, nullptr}; return {*this, nullptr};
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -7,16 +7,16 @@
// standard includes // standard includes
#include <filesystem> #include <filesystem>
#include <string>
#include <utility> #include <utility>
// lib includes // lib includes
#include <Simple-Web-Server/server_http.hpp>
#include <boost/asio/ssl/context.hpp> #include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/context_base.hpp> #include <boost/asio/ssl/context_base.hpp>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp> #include <boost/property_tree/xml_parser.hpp>
#include <string> #include <Simple-Web-Server/server_http.hpp>
// local includes // local includes
#include "config.h" #include "config.h"
@ -36,6 +36,7 @@
#include "video.h" #include "video.h"
using namespace std::literals; using namespace std::literals;
namespace nvhttp { namespace nvhttp {
namespace fs = std::filesystem; namespace fs = std::filesystem;
@ -61,8 +62,7 @@ namespace nvhttp {
protected: protected:
boost::asio::ssl::context context; boost::asio::ssl::context context;
void void after_bind() override {
after_bind() override {
if (verify) { 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_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) { 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 // This is Server<HTTPS>::accept() with SSL validation support added
void void accept() override {
accept() override {
auto connection = create_connection(*io_service, context); auto connection = create_connection(*io_service, context);
acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const SimpleWeb::error_code &ec) { acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const SimpleWeb::error_code &ec) {
auto lock = connection->handler_runner->continue_lock(); auto lock = connection->handler_runner->continue_lock();
if (!lock) if (!lock) {
return; return;
}
if (ec != SimpleWeb::error::operation_aborted) if (ec != SimpleWeb::error::operation_aborted) {
this->accept(); this->accept();
}
auto session = std::make_shared<Session>(config.max_request_streambuf_size, connection); 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->socket->async_handshake(boost::asio::ssl::stream_base::server, [this, session](const SimpleWeb::error_code &ec) {
session->connection->cancel_timeout(); session->connection->cancel_timeout();
auto lock = session->connection->handler_runner->continue_lock(); auto lock = session->connection->handler_runner->continue_lock();
if (!lock) if (!lock) {
return; return;
}
if (!ec) { if (!ec) {
if (verify && !verify(session->connection->socket->native_handle())) if (verify && !verify(session->connection->socket->native_handle())) {
this->write(session, on_verify_failed); this->write(session, on_verify_failed);
else } else {
this->read(session); this->read(session);
} }
else if (this->on_error) } else if (this->on_error) {
this->on_error(session->request, ec); this->on_error(session->request, ec);
});
} }
else if (this->on_error) });
} else if (this->on_error) {
this->on_error(session->request, ec); this->on_error(session->request, ec);
}
}); });
} }
}; };
@ -148,8 +151,7 @@ namespace nvhttp {
REMOVE ///< Remove certificate REMOVE ///< Remove certificate
}; };
std::string std::string get_arg(const args_t &args, const char *name, const char *default_value = nullptr) {
get_arg(const args_t &args, const char *name, const char *default_value = nullptr) {
auto it = args.find(name); auto it = args.find(name);
if (it == std::end(args)) { if (it == std::end(args)) {
if (default_value != NULL) { if (default_value != NULL) {
@ -161,15 +163,13 @@ namespace nvhttp {
return it->second; return it->second;
} }
void void save_state() {
save_state() {
pt::ptree root; pt::ptree root;
if (fs::exists(config::nvhttp.file_state)) { if (fs::exists(config::nvhttp.file_state)) {
try { try {
pt::read_json(config::nvhttp.file_state, root); 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(); BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
return; return;
} }
@ -193,15 +193,13 @@ namespace nvhttp {
try { try {
pt::write_json(config::nvhttp.file_state, root); 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(); BOOST_LOG(error) << "Couldn't write "sv << config::nvhttp.file_state << ": "sv << e.what();
return; return;
} }
} }
void void load_state() {
load_state() {
if (!fs::exists(config::nvhttp.file_state)) { if (!fs::exists(config::nvhttp.file_state)) {
BOOST_LOG(info) << "File "sv << config::nvhttp.file_state << " doesn't exist"sv; BOOST_LOG(info) << "File "sv << config::nvhttp.file_state << " doesn't exist"sv;
http::unique_id = uuid_util::uuid_t::generate().string(); http::unique_id = uuid_util::uuid_t::generate().string();
@ -211,8 +209,7 @@ namespace nvhttp {
pt::ptree tree; pt::ptree tree;
try { try {
pt::read_json(config::nvhttp.file_state, tree); 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(); BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
return; return;
@ -266,8 +263,7 @@ namespace nvhttp {
client_root = client; client_root = client;
} }
void void add_authorized_client(const std::string &name, std::string &&cert) {
add_authorized_client(const std::string &name, std::string &&cert) {
client_t &client = client_root; client_t &client = client_root;
named_cert_t named_cert; named_cert_t named_cert;
named_cert.name = name; named_cert.name = name;
@ -280,8 +276,7 @@ namespace nvhttp {
} }
} }
std::shared_ptr<rtsp_stream::launch_session_t> std::shared_ptr<rtsp_stream::launch_session_t> make_launch_session(bool host_audio, const args_t &args) {
make_launch_session(bool host_audio, const args_t &args) {
auto launch_session = std::make_shared<rtsp_stream::launch_session_t>(); auto launch_session = std::make_shared<rtsp_stream::launch_session_t>();
launch_session->id = ++session_id_counter; launch_session->id = ++session_id_counter;
@ -295,9 +290,15 @@ namespace nvhttp {
int x = 0; int x = 0;
std::string segment; std::string segment;
while (std::getline(mode, segment, 'x')) { while (std::getline(mode, segment, 'x')) {
if (x == 0) launch_session->width = atoi(segment.c_str()); if (x == 0) {
if (x == 1) launch_session->height = atoi(segment.c_str()); launch_session->width = atoi(segment.c_str());
if (x == 2) launch_session->fps = 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++; x++;
} }
launch_session->unique_id = (get_arg(args, "uniqueid", "unknown")); 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")); auto corever = util::from_view(get_arg(args, "corever", "0"));
if (corever >= 1) { if (corever >= 1) {
launch_session->rtsp_cipher = crypto::cipher::gcm_t { launch_session->rtsp_cipher = crypto::cipher::gcm_t {
launch_session->gcm_key, false launch_session->gcm_key,
false
}; };
launch_session->rtsp_iv_counter = 0; launch_session->rtsp_iv_counter = 0;
} }
@ -331,21 +333,18 @@ namespace nvhttp {
return launch_session; return launch_session;
} }
void void remove_session(const pair_session_t &sess) {
remove_session(const pair_session_t &sess) {
map_id_sess.erase(sess.client.uniqueID); map_id_sess.erase(sess.client.uniqueID);
} }
void void fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
tree.put("root.paired", 0); tree.put("root.paired", 0);
tree.put("root.<xmlattr>.status_code", 400); tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", status_msg); 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 remove_session(sess); // Security measure, delete the session when something went wrong and force a re-pair
} }
void void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
if (sess.last_phase != PAIR_PHASE::NONE) { if (sess.last_phase != PAIR_PHASE::NONE) {
fail_pair(sess, tree, "Out of order call to getservercert"); fail_pair(sess, tree, "Out of order call to getservercert");
return; return;
@ -369,8 +368,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
} }
void void clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
if (sess.last_phase != PAIR_PHASE::GETSERVERCERT) { if (sess.last_phase != PAIR_PHASE::GETSERVERCERT) {
fail_pair(sess, tree, "Out of order call to clientchallenge"); fail_pair(sess, tree, "Out of order call to clientchallenge");
return; return;
@ -413,8 +411,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
} }
void void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
if (sess.last_phase != PAIR_PHASE::CLIENTCHALLENGE) { if (sess.last_phase != PAIR_PHASE::CLIENTCHALLENGE) {
fail_pair(sess, tree, "Out of order call to serverchallengeresp"); fail_pair(sess, tree, "Out of order call to serverchallengeresp");
return; return;
@ -443,8 +440,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
} }
void 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) {
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) { if (sess.last_phase != PAIR_PHASE::SERVERCHALLENGERESP) {
fail_pair(sess, tree, "Out of order call to clientpairingsecret"); fail_pair(sess, tree, "Out of order call to clientpairingsecret");
return; return;
@ -486,8 +482,7 @@ namespace nvhttp {
// The client is now successfully paired and will be authorized to connect // The client is now successfully paired and will be authorized to connect
add_authorized_client(client.name, std::move(client.cert)); add_authorized_client(client.name, std::move(client.cert));
} } else {
else {
tree.put("root.paired", 0); tree.put("root.paired", 0);
} }
@ -509,8 +504,7 @@ namespace nvhttp {
}; };
template<class T> template<class T>
void void print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string; BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string;
BOOST_LOG(debug) << "METHOD :: "sv << request->method; BOOST_LOG(debug) << "METHOD :: "sv << request->method;
@ -530,8 +524,7 @@ namespace nvhttp {
} }
template<class T> template<class T>
void void not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request); print_req<T>(request);
pt::ptree tree; pt::ptree tree;
@ -550,8 +543,7 @@ namespace nvhttp {
} }
template<class T> template<class T>
void 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) {
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); print_req<T>(request);
pt::ptree tree; pt::ptree tree;
@ -593,8 +585,7 @@ namespace nvhttp {
std::getline(std::cin, pin); std::getline(std::cin, pin);
getservercert(ptr->second, tree, pin); getservercert(ptr->second, tree, pin);
} } else {
else {
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 #if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
system_tray::update_tray_require_pin(); system_tray::update_tray_require_pin();
#endif #endif
@ -603,8 +594,7 @@ namespace nvhttp {
fg.disable(); fg.disable();
return; return;
} }
} } else if (it->second == "pairchallenge"sv) {
else if (it->second == "pairchallenge"sv) {
tree.put("root.paired", 1); tree.put("root.paired", 1);
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
return; return;
@ -622,23 +612,19 @@ namespace nvhttp {
if (it = args.find("clientchallenge"); it != std::end(args)) { if (it = args.find("clientchallenge"); it != std::end(args)) {
auto challenge = util::from_hex_vec(it->second, true); auto challenge = util::from_hex_vec(it->second, true);
clientchallenge(sess_it->second, tree, challenge); 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); auto encrypted_response = util::from_hex_vec(it->second, true);
serverchallengeresp(sess_it->second, tree, encrypted_response); 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); auto pairingsecret = util::from_hex_vec(it->second, true);
clientpairingsecret(sess_it->second, add_cert, tree, pairingsecret); clientpairingsecret(sess_it->second, add_cert, tree, pairingsecret);
} } else {
else {
tree.put("root.<xmlattr>.status_code", 404); tree.put("root.<xmlattr>.status_code", 404);
tree.put("root.<xmlattr>.status_message", "Invalid pairing request"); tree.put("root.<xmlattr>.status_message", "Invalid pairing request");
} }
} }
bool bool pin(std::string pin, std::string name) {
pin(std::string pin, std::string name) {
pt::ptree tree; pt::ptree tree;
if (map_id_sess.empty()) { if (map_id_sess.empty()) {
return false; return false;
@ -649,7 +635,9 @@ namespace nvhttp {
tree.put("root.paired", 0); tree.put("root.paired", 0);
tree.put("root.<xmlattr>.status_code", 400); tree.put("root.<xmlattr>.status_code", 400);
tree.put( 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; return false;
} }
@ -672,11 +660,9 @@ namespace nvhttp {
auto &async_response = sess.async_insert_pin.response; auto &async_response = sess.async_insert_pin.response;
if (async_response.has_left() && async_response.left()) { if (async_response.has_left() && async_response.left()) {
async_response.left()->write(data.str()); 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()); async_response.right()->write(data.str());
} } else {
else {
return false; return false;
} }
@ -687,8 +673,7 @@ namespace nvhttp {
} }
template<class T> template<class T>
void void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request); print_req<T>(request);
int pair_status = 0; int pair_status = 0;
@ -719,8 +704,7 @@ namespace nvhttp {
// For HTTP requests, use a placeholder MAC address that Moonlight knows to ignore. // For HTTP requests, use a placeholder MAC address that Moonlight knows to ignore.
if constexpr (std::is_same_v<SunshineHTTPS, T>) { if constexpr (std::is_same_v<SunshineHTTPS, T>) {
tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address()))); 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"); tree.put("root.mac", "00:00:00:00:00:00");
} }
@ -735,8 +719,7 @@ namespace nvhttp {
// support know to ignore this bogus address. // support know to ignore this bogus address.
if (local_endpoint.address().is_v6() && !local_endpoint.address().to_v6().is_v4_mapped()) { if (local_endpoint.address().is_v6() && !local_endpoint.address().to_v6().is_v4_mapped()) {
tree.put("root.LocalIP", "127.0.0.1"); tree.put("root.LocalIP", "127.0.0.1");
} } else {
else {
tree.put("root.LocalIP", net::addr_to_normalized_string(local_endpoint.address())); tree.put("root.LocalIP", net::addr_to_normalized_string(local_endpoint.address()));
} }
@ -782,8 +765,7 @@ namespace nvhttp {
response->close_connection_after_response = true; response->close_connection_after_response = true;
} }
pt::ptree pt::ptree get_all_clients() {
get_all_clients() {
pt::ptree named_cert_nodes; pt::ptree named_cert_nodes;
client_t &client = client_root; client_t &client = client_root;
for (auto &named_cert : client.named_devices) { for (auto &named_cert : client.named_devices) {
@ -796,8 +778,7 @@ namespace nvhttp {
return named_cert_nodes; return named_cert_nodes;
} }
void void applist(resp_https_t response, req_https_t request) {
applist(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request); print_req<SunshineHTTPS>(request);
pt::ptree tree; pt::ptree tree;
@ -825,8 +806,7 @@ namespace nvhttp {
} }
} }
void void launch(bool &host_audio, resp_https_t response, req_https_t request) {
launch(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request); print_req<SunshineHTTPS>(request);
pt::ptree tree; pt::ptree tree;
@ -848,7 +828,8 @@ namespace nvhttp {
args.find("rikey"s) == std::end(args) || args.find("rikey"s) == std::end(args) ||
args.find("rikeyid"s) == std::end(args) || args.find("rikeyid"s) == std::end(args) ||
args.find("localAudioPlayMode"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.resume", 0);
tree.put("root.<xmlattr>.status_code", 400); tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", "Missing a required launch parameter"); 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.<xmlattr>.status_code", 200);
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + 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)));
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); tree.put("root.gamesession", 1);
rtsp_stream::launch_session_raise(launch_session); rtsp_stream::launch_session_raise(launch_session);
@ -926,8 +905,7 @@ namespace nvhttp {
revert_display_configuration = false; revert_display_configuration = false;
} }
void void resume(bool &host_audio, resp_https_t response, req_https_t request) {
resume(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request); print_req<SunshineHTTPS>(request);
pt::ptree tree; pt::ptree tree;
@ -951,7 +929,8 @@ namespace nvhttp {
auto args = request->parse_query_string(); auto args = request->parse_query_string();
if ( if (
args.find("rikey"s) == std::end(args) || 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.resume", 0);
tree.put("root.<xmlattr>.status_code", 400); tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", "Missing a required resume parameter"); tree.put("root.<xmlattr>.status_message", "Missing a required resume parameter");
@ -999,16 +978,13 @@ namespace nvhttp {
} }
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + 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)));
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); tree.put("root.resume", 1);
rtsp_stream::launch_session_raise(launch_session); rtsp_stream::launch_session_raise(launch_session);
} }
void void cancel(resp_https_t response, req_https_t request) {
cancel(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request); print_req<SunshineHTTPS>(request);
pt::ptree tree; pt::ptree tree;
@ -1033,8 +1009,7 @@ namespace nvhttp {
display_device::revert_configuration(); display_device::revert_configuration();
} }
void void appasset(resp_https_t response, req_https_t request) {
appasset(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request); print_req<SunshineHTTPS>(request);
auto args = request->parse_query_string(); auto args = request->parse_query_string();
@ -1047,14 +1022,12 @@ namespace nvhttp {
response->close_connection_after_response = true; response->close_connection_after_response = true;
} }
void void setup(const std::string &pkey, const std::string &cert) {
setup(const std::string &pkey, const std::string &cert) {
conf_intern.pkey = pkey; conf_intern.pkey = pkey;
conf_intern.servercert = cert; conf_intern.servercert = cert;
} }
void void start() {
start() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown); auto shutdown_event = mail::man->event<bool>(mail::shutdown);
auto port_http = net::map_port(PORT_HTTP); auto port_http = net::map_port(PORT_HTTP);
@ -1143,11 +1116,17 @@ namespace nvhttp {
https_server.default_resource["GET"] = not_found<SunshineHTTPS>; https_server.default_resource["GET"] = not_found<SunshineHTTPS>;
https_server.resource["^/serverinfo$"]["GET"] = serverinfo<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["^/applist$"]["GET"] = applist;
https_server.resource["^/appasset$"]["GET"] = appasset; 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["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) {
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) { resume(host_audio, resp, req); }; launch(host_audio, resp, req);
};
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) {
resume(host_audio, resp, req);
};
https_server.resource["^/cancel$"]["GET"] = cancel; https_server.resource["^/cancel$"]["GET"] = cancel;
https_server.config.reuse_address = true; https_server.config.reuse_address = true;
@ -1156,7 +1135,9 @@ namespace nvhttp {
http_server.default_resource["GET"] = not_found<SimpleWeb::HTTP>; http_server.default_resource["GET"] = not_found<SimpleWeb::HTTP>;
http_server.resource["^/serverinfo$"]["GET"] = serverinfo<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.reuse_address = true;
http_server.config.address = net::af_to_any_address_string(address_family); 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) { auto accept_and_run = [&](auto *http_server) {
try { try {
http_server->start(); 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 // It's possible the exception gets thrown after calling http_server->stop() from a different thread
if (shutdown_event->peek()) { if (shutdown_event->peek()) {
return; return;
@ -1190,24 +1170,21 @@ namespace nvhttp {
tcp.join(); tcp.join();
} }
void void erase_all_clients() {
erase_all_clients() {
client_t client; client_t client;
client_root = client; client_root = client;
cert_chain.clear(); cert_chain.clear();
save_state(); save_state();
} }
int int unpair_client(std::string uuid) {
unpair_client(std::string uuid) {
int removed = 0; int removed = 0;
client_t &client = client_root; client_t &client = client_root;
for (auto it = client.named_devices.begin(); it != client.named_devices.end();) { for (auto it = client.named_devices.begin(); it != client.named_devices.end();) {
if ((*it).uuid == uuid) { if ((*it).uuid == uuid) {
it = client.named_devices.erase(it); it = client.named_devices.erase(it);
removed++; removed++;
} } else {
else {
++it; ++it;
} }
} }

View file

@ -9,8 +9,8 @@
#include <string> #include <string>
// lib includes // lib includes
#include <Simple-Web-Server/server_https.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <Simple-Web-Server/server_https.hpp>
// local includes // local includes
#include "crypto.h" #include "crypto.h"
@ -49,21 +49,20 @@ namespace nvhttp {
* nvhttp::start(); * nvhttp::start();
* @examples_end * @examples_end
*/ */
void void start();
start();
/** /**
* @brief Setup the nvhttp server. * @brief Setup the nvhttp server.
* @param pkey * @param pkey
* @param cert * @param cert
*/ */
void void setup(const std::string &pkey, const std::string &cert);
setup(const std::string &pkey, const std::string &cert);
class SunshineHTTPS: public SimpleWeb::HTTPS { class SunshineHTTPS: public SimpleWeb::HTTPS {
public: public:
SunshineHTTPS(boost::asio::io_context &io_context, boost::asio::ssl::context &ctx): SunshineHTTPS(boost::asio::io_context &io_context, boost::asio::ssl::context &ctx):
SimpleWeb::HTTPS(io_context, ctx) {} SimpleWeb::HTTPS(io_context, ctx) {
}
virtual ~SunshineHTTPS() { virtual ~SunshineHTTPS() {
// Gracefully shutdown the TLS connection // Gracefully shutdown the TLS connection
@ -111,8 +110,7 @@ namespace nvhttp {
* @brief removes the temporary pairing session * @brief removes the temporary pairing session
* @param sess * @param sess
*/ */
void void remove_session(const pair_session_t &sess);
remove_session(const pair_session_t &sess);
/** /**
* @brief Pair, phase 1 * @brief Pair, phase 1
@ -124,8 +122,7 @@ namespace nvhttp {
* *
* At this stage we only have to send back our public certificate. * At this stage we only have to send back our public certificate.
*/ */
void void getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin);
getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin);
/** /**
* @brief Pair, phase 2 * @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 * The hash + server_challenge will then be AES encrypted and sent as the `challengeresponse` in the returned XML
*/ */
void void clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge);
clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge);
/** /**
* @brief Pair, phase 3 * @brief Pair, phase 3
@ -149,8 +145,7 @@ namespace nvhttp {
* we have to send back the `pairingsecret`: * we have to send back the `pairingsecret`:
* using our private key we have to sign the certificate_signature + server_secret (generated in phase 2) * using our private key we have to sign the certificate_signature + server_secret (generated in phase 2)
*/ */
void void serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response);
serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response);
/** /**
* @brief Pair, phase 4 (final) * @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 * Then using the client certificate public key we should be able to verify that
* the client secret has been signed by Moonlight * the client secret has been signed by Moonlight
*/ */
void 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);
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. * @brief Compare the user supplied pin to the Moonlight pin.
@ -178,8 +172,7 @@ namespace nvhttp {
* bool pin_status = nvhttp::pin("1234", "laptop"); * bool pin_status = nvhttp::pin("1234", "laptop");
* @examples_end * @examples_end
*/ */
bool bool pin(std::string pin, std::string name);
pin(std::string pin, std::string name);
/** /**
* @brief Remove single client. * @brief Remove single client.
@ -187,8 +180,7 @@ namespace nvhttp {
* nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1"); * nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1");
* @examples_end * @examples_end
*/ */
int int unpair_client(std::string uniqueid);
unpair_client(std::string uniqueid);
/** /**
* @brief Get all paired clients. * @brief Get all paired clients.
@ -197,8 +189,7 @@ namespace nvhttp {
* boost::property_tree::ptree clients = nvhttp::get_all_clients(); * boost::property_tree::ptree clients = nvhttp::get_all_clients();
* @examples_end * @examples_end
*/ */
boost::property_tree::ptree boost::property_tree::ptree get_all_clients();
get_all_clients();
/** /**
* @brief Remove all paired clients. * @brief Remove all paired clients.
@ -206,6 +197,5 @@ namespace nvhttp {
* nvhttp::erase_all_clients(); * nvhttp::erase_all_clients();
* @examples_end * @examples_end
*/ */
void void erase_all_clients();
erase_all_clients();
} // namespace nvhttp } // namespace nvhttp

View file

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

View file

@ -2,20 +2,21 @@
* @file src/platform/linux/audio.cpp * @file src/platform/linux/audio.cpp
* @brief Definitions for audio control on Linux. * @brief Definitions for audio control on Linux.
*/ */
// standard includes
#include <bitset> #include <bitset>
#include <sstream> #include <sstream>
#include <thread> #include <thread>
// lib includes
#include <boost/regex.hpp> #include <boost/regex.hpp>
#include <pulse/error.h> #include <pulse/error.h>
#include <pulse/pulseaudio.h> #include <pulse/pulseaudio.h>
#include <pulse/simple.h> #include <pulse/simple.h>
#include "src/platform/common.h" // local includes
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h"
#include "src/thread_safe.h" #include "src/thread_safe.h"
namespace platf { namespace platf {
@ -32,8 +33,7 @@ namespace platf {
PA_CHANNEL_POSITION_SIDE_RIGHT, PA_CHANNEL_POSITION_SIDE_RIGHT,
}; };
std::string std::string to_string(const char *name, const std::uint8_t *mapping, int channels) {
to_string(const char *name, const std::uint8_t *mapping, int channels) {
std::stringstream ss; std::stringstream ss;
ss << "rate=48000 sink_name="sv << name << " format=float channels="sv << channels << " channel_map="sv; 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 { struct mic_attr_t: public mic_t {
util::safe_ptr<pa_simple, pa_simple_free> mic; util::safe_ptr<pa_simple, pa_simple_free> mic;
capture_e capture_e sample(std::vector<float> &sample_buf) override {
sample(std::vector<float> &sample_buf) override {
auto sample_size = sample_buf.size(); auto sample_size = sample_buf.size();
auto buf = sample_buf.data(); auto buf = sample_buf.data();
@ -69,8 +68,7 @@ namespace platf {
} }
}; };
std::unique_ptr<mic_t> 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) {
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>(); 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};
@ -92,9 +90,8 @@ namespace platf {
int status; int status;
mic->mic.reset( mic->mic.reset(
pa_simple_new(nullptr, "sunshine", 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_stream_direction_t::PA_STREAM_RECORD, source_name.c_str(), );
"sunshine-record", &ss, &pa_map, &pa_attr, &status));
if (!mic->mic) { if (!mic->mic) {
auto err_str = pa_strerror(status); auto err_str = pa_strerror(status);
@ -123,10 +120,10 @@ namespace platf {
using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type; using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type;
template<class T> template<class T>
void void pa_free(T *p) {
pa_free(T *p) {
pa_xfree(p); pa_xfree(p);
} }
using ctx_t = util::safe_ptr<pa_context, pa_context_unref>; using ctx_t = util::safe_ptr<pa_context, pa_context_unref>;
using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>; using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>;
using op_t = util::safe_ptr<pa_operation, pa_operation_unref>; using op_t = util::safe_ptr<pa_operation, pa_operation_unref>;
@ -136,8 +133,7 @@ namespace platf {
using cb_simple_t = std::function<void(ctx_t::pointer, add_const_t<T> i)>; using cb_simple_t = std::function<void(ctx_t::pointer, add_const_t<T> i)>;
template<class T> template<class T>
void void cb(ctx_t::pointer ctx, add_const_t<T> i, void *userdata) {
cb(ctx_t::pointer ctx, add_const_t<T> i, void *userdata) {
auto &f = *(cb_simple_t<T> *) userdata; auto &f = *(cb_simple_t<T> *) userdata;
// Cannot similarly filter on eol here. Unless reported otherwise assume // Cannot similarly filter on eol here. Unless reported otherwise assume
@ -149,8 +145,7 @@ namespace platf {
using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>; using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>;
template<class T> template<class T>
void void cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
auto &f = *(cb_t<T> *) userdata; auto &f = *(cb_t<T> *) userdata;
// For some reason, pulseaudio calls this callback after disconnecting // For some reason, pulseaudio calls this callback after disconnecting
@ -161,22 +156,19 @@ namespace platf {
f(ctx, i, eol); f(ctx, i, eol);
} }
void void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
auto alarm = (safe::alarm_raw_t<int> *) userdata; auto alarm = (safe::alarm_raw_t<int> *) userdata;
alarm->ring(i); alarm->ring(i);
} }
void void ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
auto &f = *(std::function<void(ctx_t::pointer)> *) userdata; auto &f = *(std::function<void(ctx_t::pointer)> *) userdata;
f(ctx); f(ctx);
} }
void void success_cb(ctx_t::pointer ctx, int status, void *userdata) {
success_cb(ctx_t::pointer ctx, int status, void *userdata) {
assert(userdata != nullptr); assert(userdata != nullptr);
auto alarm = (safe::alarm_raw_t<int> *) userdata; 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::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb;
std::thread worker; std::thread worker;
int
init() { int init() {
events = std::make_unique<safe::event_t<ctx_event_e>>(); events = std::make_unique<safe::event_t<ctx_event_e>>();
loop.reset(pa_mainloop_new()); loop.reset(pa_mainloop_new());
ctx.reset(pa_context_new(pa_mainloop_get_api(loop.get()), "sunshine")); ctx.reset(pa_context_new(pa_mainloop_get_api(loop.get()), "sunshine"));
@ -262,8 +254,7 @@ namespace platf {
return 0; return 0;
} }
int int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
auto alarm = safe::make_alarm<int>(); auto alarm = safe::make_alarm<int>();
op_t op { op_t op {
@ -272,15 +263,15 @@ namespace platf {
"module-null-sink", "module-null-sink",
to_string(name, channel_mapping, channels).c_str(), to_string(name, channel_mapping, channels).c_str(),
cb_i, cb_i,
alarm.get()), alarm.get()
),
}; };
alarm->wait(); alarm->wait();
return *alarm->status(); return *alarm->status();
} }
int int unload_null(std::uint32_t i) {
unload_null(std::uint32_t i) {
if (i == PA_INVALID_INDEX) { if (i == PA_INVALID_INDEX) {
return 0; return 0;
} }
@ -301,8 +292,7 @@ namespace platf {
return 0; return 0;
} }
std::optional<sink_t> std::optional<sink_t> sink_info() override {
sink_info() override {
constexpr auto stereo = "sink-sunshine-stereo"; constexpr auto stereo = "sink-sunshine-stereo";
constexpr auto surround51 = "sink-sunshine-surround51"; constexpr auto surround51 = "sink-sunshine-surround51";
constexpr auto surround71 = "sink-sunshine-surround71"; constexpr auto surround71 = "sink-sunshine-surround71";
@ -331,13 +321,11 @@ namespace platf {
index.stereo = sink_info->owner_module; index.stereo = sink_info->owner_module;
++nullcount; ++nullcount;
} } else if (!std::strcmp(sink_info->name, surround51)) {
else if (!std::strcmp(sink_info->name, surround51)) {
index.surround51 = sink_info->owner_module; index.surround51 = sink_info->owner_module;
++nullcount; ++nullcount;
} } else if (!std::strcmp(sink_info->name, surround71)) {
else if (!std::strcmp(sink_info->name, surround71)) {
index.surround71 = sink_info->owner_module; index.surround71 = sink_info->owner_module;
++nullcount; ++nullcount;
@ -365,8 +353,7 @@ namespace platf {
index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo)); index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo));
if (index.stereo == PA_INVALID_INDEX) { if (index.stereo == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for stereo: "sv << pa_strerror(pa_context_errno(ctx.get())); BOOST_LOG(warning) << "Couldn't create virtual sink for stereo: "sv << pa_strerror(pa_context_errno(ctx.get()));
} } else {
else {
++nullcount; ++nullcount;
} }
} }
@ -375,8 +362,7 @@ namespace platf {
index.surround51 = load_null(surround51, speaker::map_surround51, sizeof(speaker::map_surround51)); index.surround51 = load_null(surround51, speaker::map_surround51, sizeof(speaker::map_surround51));
if (index.surround51 == PA_INVALID_INDEX) { 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())); BOOST_LOG(warning) << "Couldn't create virtual sink for surround-51: "sv << pa_strerror(pa_context_errno(ctx.get()));
} } else {
else {
++nullcount; ++nullcount;
} }
} }
@ -385,8 +371,7 @@ namespace platf {
index.surround71 = load_null(surround71, speaker::map_surround71, sizeof(speaker::map_surround71)); index.surround71 = load_null(surround71, speaker::map_surround71, sizeof(speaker::map_surround71));
if (index.surround71 == PA_INVALID_INDEX) { 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())); BOOST_LOG(warning) << "Couldn't create virtual sink for surround-71: "sv << pa_strerror(pa_context_errno(ctx.get()));
} } else {
else {
++nullcount; ++nullcount;
} }
} }
@ -402,8 +387,7 @@ namespace platf {
return std::make_optional(std::move(sink)); return std::make_optional(std::move(sink));
} }
std::string std::string get_default_sink_name() {
get_default_sink_name() {
std::string sink_name; std::string sink_name;
auto alarm = safe::make_alarm<int>(); auto alarm = safe::make_alarm<int>();
@ -425,8 +409,7 @@ namespace platf {
return sink_name; return sink_name;
} }
std::string std::string get_monitor_name(const std::string &sink_name) {
get_monitor_name(const std::string &sink_name) {
std::string monitor_name; std::string monitor_name;
auto alarm = safe::make_alarm<int>(); auto alarm = safe::make_alarm<int>();
@ -457,8 +440,7 @@ namespace platf {
return monitor_name; return monitor_name;
} }
std::unique_ptr<mic_t> std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
// Sink choice priority: // Sink choice priority:
// 1. Config sink // 1. Config sink
// 2. Last sink swapped to (Usually virtual in this case) // 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 // but this happens right after the swap so the default returned by PA was not
// the new one just set! // the new one just set!
auto sink_name = config::audio.sink; auto sink_name = config::audio.sink;
if (sink_name.empty()) sink_name = requested_sink; if (sink_name.empty()) {
if (sink_name.empty()) sink_name = get_default_sink_name(); 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)); return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name));
} }
bool bool is_sink_available(const std::string &sink) override {
is_sink_available(const std::string &sink) override {
BOOST_LOG(warning) << "audio_control_t::is_sink_available() unimplemented: "sv << sink; BOOST_LOG(warning) << "audio_control_t::is_sink_available() unimplemented: "sv << sink;
return true; return true;
} }
int int set_sink(const std::string &sink) override {
set_sink(const std::string &sink) override {
auto alarm = safe::make_alarm<int>(); auto alarm = safe::make_alarm<int>();
BOOST_LOG(info) << "Setting default sink to: ["sv << sink << "]"sv; BOOST_LOG(info) << "Setting default sink to: ["sv << sink << "]"sv;
op_t op { op_t op {
pa_context_set_default_sink( 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) { if (!op) {
@ -525,8 +513,7 @@ namespace platf {
}; };
} // namespace pa } // namespace pa
std::unique_ptr<audio_control_t> std::unique_ptr<audio_control_t> audio_control() {
audio_control() {
auto audio = std::make_unique<pa::server_t>(); auto audio = std::make_unique<pa::server_t>();
if (audio->init()) { if (audio->init()) {

View file

@ -2,13 +2,15 @@
* @file src/platform/linux/cuda.cpp * @file src/platform/linux/cuda.cpp
* @brief Definitions for CUDA encoding. * @brief Definitions for CUDA encoding.
*/ */
// standard includes
#include <bitset> #include <bitset>
#include <fcntl.h> #include <fcntl.h>
#include <filesystem> #include <filesystem>
#include <thread> #include <thread>
#include <NvFBC.h> // lib includes
#include <ffnvcodec/dynlink_loader.h> #include <ffnvcodec/dynlink_loader.h>
#include <NvFBC.h>
extern "C" { extern "C" {
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
@ -16,6 +18,7 @@ extern "C" {
#include <libavutil/imgutils.h> #include <libavutil/imgutils.h>
} }
// local includes
#include "cuda.h" #include "cuda.h"
#include "graphics.h" #include "graphics.h"
#include "src/logging.h" #include "src/logging.h"
@ -27,7 +30,8 @@ extern "C" {
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x) #define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
#define CU_CHECK(x, y) \ #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) \ #define CU_CHECK_IGNORE(x, y) \
check((x), SUNSHINE_STRINGVIEW(y ": ")) check((x), SUNSHINE_STRINGVIEW(y ": "))
@ -35,17 +39,16 @@ extern "C" {
namespace fs = std::filesystem; namespace fs = std::filesystem;
using namespace std::literals; using namespace std::literals;
namespace cuda { namespace cuda {
constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1; constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1;
constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39; constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39;
void void pass_error(const std::string_view &sv, const char *name, const char *description) {
pass_error(const std::string_view &sv, const char *name, const char *description) {
BOOST_LOG(error) << sv << name << ':' << description; BOOST_LOG(error) << sv << name << ':' << description;
} }
void void cff(CudaFunctions *cf) {
cff(CudaFunctions *cf) {
cuda_free_functions(&cf); cuda_free_functions(&cf);
} }
@ -53,8 +56,7 @@ namespace cuda {
static cdf_t cdf; static cdf_t cdf;
inline static int inline static int check(CUresult result, const std::string_view &sv) {
check(CUresult result, const std::string_view &sv) {
if (result != CUDA_SUCCESS) { if (result != CUDA_SUCCESS) {
const char *name; const char *name;
const char *description; const char *description;
@ -69,13 +71,11 @@ namespace cuda {
return 0; return 0;
} }
void void freeStream(CUstream stream) {
freeStream(CUstream stream) {
CU_CHECK_IGNORE(cdf->cuStreamDestroy(stream), "Couldn't destroy cuda stream"); CU_CHECK_IGNORE(cdf->cuStreamDestroy(stream), "Couldn't destroy cuda stream");
} }
void void unregisterResource(CUgraphicsResource resource) {
unregisterResource(CUgraphicsResource resource) {
CU_CHECK_IGNORE(cdf->cuGraphicsUnregisterResource(resource), "Couldn't unregister resource"); CU_CHECK_IGNORE(cdf->cuGraphicsUnregisterResource(resource), "Couldn't unregister resource");
} }
@ -86,8 +86,7 @@ namespace cuda {
tex_t tex; tex_t tex;
}; };
int int init() {
init() {
auto status = cuda_load_functions(&cdf, nullptr); auto status = cuda_load_functions(&cdf, nullptr);
if (status) { if (status) {
BOOST_LOG(error) << "Couldn't load cuda: "sv << 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 { class cuda_t: public platf::avcodec_encode_device_t {
public: public:
int int init(int in_width, int in_height) {
init(int in_width, int in_height) {
if (!cdf) { if (!cdf) {
BOOST_LOG(warning) << "cuda not initialized"sv; BOOST_LOG(warning) << "cuda not initialized"sv;
return -1; return -1;
@ -117,8 +115,7 @@ namespace cuda {
return 0; return 0;
} }
int int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
this->hwframe.reset(frame); this->hwframe.reset(frame);
this->frame = frame; this->frame = frame;
@ -156,8 +153,7 @@ namespace cuda {
return 0; return 0;
} }
void void apply_colorspace() override {
apply_colorspace() override {
sws.apply_colorspace(colorspace); sws.apply_colorspace(colorspace);
auto tex = tex_t::make(height, width * 4); auto tex = tex_t::make(height, width * 4);
@ -185,8 +181,7 @@ namespace cuda {
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 cudaTextureObject_t tex_obj(const tex_t &tex) const {
tex_obj(const tex_t &tex) const {
return linear_interpolation ? tex.texture.linear : tex.texture.point; return linear_interpolation ? tex.texture.linear : tex.texture.point;
} }
@ -203,13 +198,11 @@ namespace cuda {
class cuda_ram_t: public cuda_t { class cuda_ram_t: public cuda_t {
public: public:
int int convert(platf::img_t &img) override {
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()); 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 int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
if (cuda_t::set_frame(frame, hw_frames_ctx)) { if (cuda_t::set_frame(frame, hw_frames_ctx)) {
return -1; return -1;
} }
@ -229,8 +222,7 @@ namespace cuda {
class cuda_vram_t: public cuda_t { class cuda_vram_t: public cuda_t {
public: public:
int int convert(platf::img_t &img) override {
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()); 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. * @param index CUDA device index to open.
* @return File descriptor or -1 on failure. * @return File descriptor or -1 on failure.
*/ */
file_t file_t open_drm_fd_for_cuda_device(int index) {
open_drm_fd_for_cuda_device(int index) {
CUdevice device; CUdevice device;
CU_CHECK(cdf->cuDeviceGet(&device, index), "Couldn't get CUDA device"); CU_CHECK(cdf->cuDeviceGet(&device, index), "Couldn't get CUDA device");
@ -252,8 +243,9 @@ namespace cuda {
BOOST_LOG(debug) << "Found CUDA device with PCI bus ID: "sv << pci_bus_id.data(); BOOST_LOG(debug) << "Found CUDA device with PCI bus ID: "sv << pci_bus_id.data();
// Linux uses lowercase hexadecimal while CUDA uses uppercase // Linux uses lowercase hexadecimal while CUDA uses uppercase
std::transform(pci_bus_id.begin(), pci_bus_id.end(), pci_bus_id.begin(), std::transform(pci_bus_id.begin(), pci_bus_id.end(), pci_bus_id.begin(), [](char c) {
[](char c) { return std::tolower(c); }); return std::tolower(c);
});
// Look for the name of the primary node in sysfs // Look for the name of the primary node in sysfs
try { try {
@ -273,8 +265,7 @@ namespace cuda {
auto device_path = dri_path / file; auto device_path = dri_path / file;
return open(device_path.c_str(), O_RDWR); 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(); 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. * @param offset_y Offset of content in captured frame.
* @return 0 on success or -1 on failure. * @return 0 on success or -1 on failure.
*/ */
int int init(int in_width, int in_height, int offset_x, int offset_y) {
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. // This must be non-zero to tell the video core that it's a hardware encoding device.
data = (void *) 0x1; data = (void *) 0x1;
@ -340,8 +330,7 @@ namespace cuda {
* @param hw_frames_ctx_buf FFmpeg hardware frame context. * @param hw_frames_ctx_buf FFmpeg hardware frame context.
* @return 0 on success or -1 on failure. * @return 0 on success or -1 on failure.
*/ */
int int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
this->hwframe.reset(frame); this->hwframe.reset(frame);
this->frame = frame; this->frame = frame;
@ -377,10 +366,8 @@ namespace cuda {
cuda_ctx->stream = stream.get(); cuda_ctx->stream = stream.get();
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&y_res, nv12->tex[0], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), CU_CHECK(cdf->cuGraphicsGLRegisterImage(&y_res, nv12->tex[0], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), "Couldn't register Y plane texture");
"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(&uv_res, nv12->tex[1], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY),
"Couldn't register UV plane texture");
return 0; return 0;
} }
@ -390,15 +377,13 @@ namespace cuda {
* @param img Captured screen image. * @param img Captured screen image.
* @return 0 on success or -1 on failure. * @return 0 on success or -1 on failure.
*/ */
int int convert(platf::img_t &img) override {
convert(platf::img_t &img) override {
auto &descriptor = (egl::img_descriptor_t &) img; auto &descriptor = (egl::img_descriptor_t &) img;
if (descriptor.sequence == 0) { if (descriptor.sequence == 0) {
// For dummy images, use a blank RGB texture instead of importing a DMA-BUF // For dummy images, use a blank RGB texture instead of importing a DMA-BUF
rgb = egl::create_blank(img); rgb = egl::create_blank(img);
} } else if (descriptor.sequence > sequence) {
else if (descriptor.sequence > sequence) {
sequence = descriptor.sequence; sequence = descriptor.sequence;
rgb = egl::rgb_t {}; rgb = egl::rgb_t {};
@ -445,8 +430,7 @@ namespace cuda {
/** /**
* @brief Configures shader parameters for the specified colorspace. * @brief Configures shader parameters for the specified colorspace.
*/ */
void void apply_colorspace() override {
apply_colorspace() override {
sws.apply_colorspace(colorspace); sws.apply_colorspace(colorspace);
} }
@ -474,8 +458,7 @@ namespace cuda {
int offset_x, offset_y; int offset_x, offset_y;
}; };
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram) {
make_avcodec_encode_device(int width, int height, bool vram) {
if (init()) { if (init()) {
return nullptr; return nullptr;
} }
@ -484,8 +467,7 @@ namespace cuda {
if (vram) { if (vram) {
cuda = std::make_unique<cuda_vram_t>(); cuda = std::make_unique<cuda_vram_t>();
} } else {
else {
cuda = std::make_unique<cuda_ram_t>(); cuda = std::make_unique<cuda_ram_t>();
} }
@ -504,8 +486,7 @@ namespace cuda {
* @param offset_y Offset of content in captured frame. * @param offset_y Offset of content in captured frame.
* @return FFmpeg encoding device context. * @return FFmpeg encoding device context.
*/ */
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y) {
make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y) {
if (init()) { if (init()) {
return nullptr; return nullptr;
} }
@ -523,17 +504,18 @@ namespace cuda {
static PNVFBCCREATEINSTANCE createInstance {}; static PNVFBCCREATEINSTANCE createInstance {};
static NVFBC_API_FUNCTION_LIST func {NVFBC_VERSION}; static NVFBC_API_FUNCTION_LIST func {NVFBC_VERSION};
static constexpr inline NVFBC_BOOL static constexpr inline NVFBC_BOOL nv_bool(bool b) {
nv_bool(bool b) {
return b ? NVFBC_TRUE : NVFBC_FALSE; return b ? NVFBC_TRUE : NVFBC_FALSE;
} }
static void *handle {nullptr}; static void *handle {nullptr};
int
init() { int init() {
static bool funcs_loaded = false; static bool funcs_loaded = false;
if (funcs_loaded) return 0; if (funcs_loaded) {
return 0;
}
if (!handle) { if (!handle) {
handle = dyn::handle({"libnvidia-fbc.so.1", "libnvidia-fbc.so"}); handle = dyn::handle({"libnvidia-fbc.so.1", "libnvidia-fbc.so"});
@ -597,21 +579,21 @@ namespace cuda {
public: public:
handle_t() = default; handle_t() = default;
handle_t(handle_t &&other): 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(); other.handle_flags.reset();
} }
handle_t & handle_t &operator=(handle_t &&other) {
operator=(handle_t &&other) {
std::swap(handle_flags, other.handle_flags); std::swap(handle_flags, other.handle_flags);
std::swap(handle, other.handle); std::swap(handle, other.handle);
return *this; return *this;
} }
static std::optional<handle_t> static std::optional<handle_t> make() {
make() {
NVFBC_CREATE_HANDLE_PARAMS params {NVFBC_CREATE_HANDLE_PARAMS_VER}; NVFBC_CREATE_HANDLE_PARAMS params {NVFBC_CREATE_HANDLE_PARAMS_VER};
// Set privateData to allow NvFBC on consumer NVIDIA GPUs. // Set privateData to allow NvFBC on consumer NVIDIA GPUs.
@ -633,13 +615,11 @@ namespace cuda {
return handle; return handle;
} }
const char * const char *last_error() {
last_error() {
return func.nvFBCGetLastErrorStr(handle); return func.nvFBCGetLastErrorStr(handle);
} }
std::optional<NVFBC_GET_STATUS_PARAMS> std::optional<NVFBC_GET_STATUS_PARAMS> status() {
status() {
NVFBC_GET_STATUS_PARAMS params {NVFBC_GET_STATUS_PARAMS_VER}; NVFBC_GET_STATUS_PARAMS params {NVFBC_GET_STATUS_PARAMS_VER};
auto status = func.nvFBCGetStatus(handle, &params); auto status = func.nvFBCGetStatus(handle, &params);
@ -652,8 +632,7 @@ namespace cuda {
return params; return params;
} }
int int capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
if (func.nvFBCCreateCaptureSession(handle, &capture_params)) { if (func.nvFBCCreateCaptureSession(handle, &capture_params)) {
BOOST_LOG(error) << "Failed to start capture session: "sv << last_error(); BOOST_LOG(error) << "Failed to start capture session: "sv << last_error();
return -1; return -1;
@ -673,8 +652,7 @@ namespace cuda {
return 0; return 0;
} }
int int stop() {
stop() {
if (!handle_flags[SESSION_CAPTURE]) { if (!handle_flags[SESSION_CAPTURE]) {
return 0; return 0;
} }
@ -692,8 +670,7 @@ namespace cuda {
return 0; return 0;
} }
int int reset() {
reset() {
if (!handle_flags[SESSION_HANDLE]) { if (!handle_flags[SESSION_HANDLE]) {
return 0; return 0;
} }
@ -723,8 +700,7 @@ namespace cuda {
class display_t: public platf::display_t { class display_t: public platf::display_t {
public: public:
int int init(const std::string_view &display_name, const ::video::config_t &config) {
init(const std::string_view &display_name, const ::video::config_t &config) {
auto handle = handle_t::make(); auto handle = handle_t::make();
if (!handle) { if (!handle) {
return -1; return -1;
@ -744,12 +720,10 @@ namespace cuda {
if (monitor_nr < 0 || monitor_nr >= status_params->dwOutputNum) { 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; 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; streamedMonitor = monitor_nr;
} }
} } else {
else {
BOOST_LOG(warning) << "XrandR not available, streaming entire virtual desktop"sv; BOOST_LOG(warning) << "XrandR not available, streaming entire virtual desktop"sv;
} }
} }
@ -773,8 +747,7 @@ namespace cuda {
capture_params.eTrackingType = NVFBC_TRACKING_OUTPUT; capture_params.eTrackingType = NVFBC_TRACKING_OUTPUT;
capture_params.dwOutputId = output.dwId; capture_params.dwOutputId = output.dwId;
} } else {
else {
capture_params.eTrackingType = NVFBC_TRACKING_SCREEN; capture_params.eTrackingType = NVFBC_TRACKING_SCREEN;
width = status_params->screenSize.w; width = status_params->screenSize.w;
@ -788,8 +761,7 @@ namespace cuda {
return 0; return 0;
} }
platf::capture_e 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 {
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(); auto next_frame = std::chrono::steady_clock::now();
{ {
@ -849,8 +821,7 @@ namespace cuda {
} }
// Reinitialize the capture session. // Reinitialize the capture session.
platf::capture_e platf::capture_e reinit(bool cursor) {
reinit(bool cursor) {
if (handle.stop()) { if (handle.stop()) {
return platf::capture_e::error; return platf::capture_e::error;
} }
@ -860,8 +831,7 @@ namespace cuda {
capture_params.bPushModel = nv_bool(false); capture_params.bPushModel = nv_bool(false);
capture_params.bWithCursor = nv_bool(true); capture_params.bWithCursor = nv_bool(true);
capture_params.bAllowDirectCapture = nv_bool(false); capture_params.bAllowDirectCapture = nv_bool(false);
} } else {
else {
capture_params.bPushModel = nv_bool(true); capture_params.bPushModel = nv_bool(true);
capture_params.bWithCursor = nv_bool(false); capture_params.bWithCursor = nv_bool(false);
capture_params.bAllowDirectCapture = nv_bool(true); capture_params.bAllowDirectCapture = nv_bool(true);
@ -919,8 +889,7 @@ namespace cuda {
return platf::capture_e::ok; return platf::capture_e::ok;
} }
platf::capture_e 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) {
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) { if (cursor != cursor_visible) {
auto status = reinit(cursor); auto status = reinit(cursor);
if (status != platf::capture_e::ok) { if (status != platf::capture_e::ok) {
@ -960,13 +929,11 @@ namespace cuda {
return platf::capture_e::ok; return platf::capture_e::ok;
} }
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) {
make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) {
return ::cuda::make_avcodec_encode_device(width, height, true); return ::cuda::make_avcodec_encode_device(width, height, true);
} }
std::shared_ptr<platf::img_t> std::shared_ptr<platf::img_t> alloc_img() override {
alloc_img() override {
auto img = std::make_shared<cuda::img_t>(); auto img = std::make_shared<cuda::img_t>();
img->data = nullptr; img->data = nullptr;
@ -985,8 +952,7 @@ namespace cuda {
return img; return img;
}; };
int int dummy_img(platf::img_t *) override {
dummy_img(platf::img_t *) override {
return 0; return 0;
} }
@ -1001,8 +967,7 @@ namespace cuda {
} // namespace cuda } // namespace cuda
namespace platf { namespace platf {
std::shared_ptr<display_t> std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (hwdevice_type != mem_type_e::cuda) { if (hwdevice_type != mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv; BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv;
return nullptr; return nullptr;
@ -1017,8 +982,7 @@ namespace platf {
return display; return display;
} }
std::vector<std::string> std::vector<std::string> nvfbc_display_names() {
nvfbc_display_names() {
if (cuda::init() || cuda::nvfbc::init()) { if (cuda::init() || cuda::nvfbc::init()) {
return {}; return {};
} }

View file

@ -2,14 +2,17 @@
* @file src/platform/linux/cuda.cu * @file src/platform/linux/cuda.cu
* @brief CUDA implementation for Linux. * @brief CUDA implementation for Linux.
*/ */
// #include <algorithm> // standard includes
#include <helper_math.h>
#include <chrono> #include <chrono>
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string_view> #include <string_view>
// platform includes
#include <helper_math.h>
// local includes
#include "cuda.h" #include "cuda.h"
using namespace std::literals; using namespace std::literals;
@ -18,16 +21,20 @@ using namespace std::literals;
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x) #define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
#define CU_CHECK(x, y) \ #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) \ #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) \ #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) \ #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) \ #define CU_CHECK_IGNORE(x, y) \
check((x), SUNSHINE_STRINGVIEW(y ": ")) check((x), SUNSHINE_STRINGVIEW(y ": "))
@ -78,6 +85,7 @@ inline T div_align(T l, T r) {
} }
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);
inline static int check(cudaError_t result, const std::string_view &sv) { inline static int check(cudaError_t result, const std::string_view &sv) {
if (result) { if (result) {
auto name = cudaGetErrorName(result); auto name = cudaGetErrorName(result);
@ -113,8 +121,7 @@ stream_t make_stream(int flags) {
if (!flags) { if (!flags) {
CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream"); CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream");
} } else {
else {
CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags"); CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags");
} }
@ -149,15 +156,24 @@ inline __device__ float calcY(float3 pixel, const cuda_color_t *const color_matr
} }
__global__ void RGBA_to_NV12( __global__ void RGBA_to_NV12(
cudaTextureObject_t srcImage, std::uint8_t *dstY, std::uint8_t *dstUV, cudaTextureObject_t srcImage,
std::uint32_t dstPitchY, std::uint32_t dstPitchUV, std::uint8_t *dstY,
float scale, const viewport_t viewport, const cuda_color_t *const color_matrix) { 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 idX = (threadIdx.x + blockDim.x * blockIdx.x) * 2;
int idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2; int idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2;
if(idX >= viewport.width) return; if (idX >= viewport.width) {
if(idY >= viewport.height) return; return;
}
if (idY >= viewport.height) {
return;
}
float x = idX * scale; float x = idX * scale;
float y = idY * scale; float y = idY * scale;
@ -222,8 +238,14 @@ std::optional<tex_t> tex_t::make(int height, int pitch) {
return tex; return tex;
} }
tex_t::tex_t() : array {}, texture { INVALID_TEXTURE, INVALID_TEXTURE } {} tex_t::tex_t():
tex_t::tex_t(tex_t &&other) : array { other.array }, texture { other.texture } { array {},
texture {INVALID_TEXTURE, INVALID_TEXTURE} {
}
tex_t::tex_t(tex_t &&other):
array {other.array},
texture {other.texture} {
other.array = 0; other.array = 0;
other.texture.point = INVALID_TEXTURE; other.texture.point = INVALID_TEXTURE;
other.texture.linear = INVALID_TEXTURE; other.texture.linear = INVALID_TEXTURE;
@ -256,8 +278,9 @@ tex_t::~tex_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) 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) } { threadsPerBlock {threadsPerBlock},
color_matrix {std::move(color_matrix)} {
// Ensure aspect ratio is maintained // Ensure aspect ratio is maintained
auto scalar = std::fminf(out_width / (float) in_width, out_height / (float) in_height); auto scalar = std::fminf(out_width / (float) in_width, out_height / (float) in_height);
auto out_width_f = in_width * scalar; auto out_width_f = in_width * scalar;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,18 +2,19 @@
* @file src/platform/linux/input/inputtino_gamepad.cpp * @file src/platform/linux/input/inputtino_gamepad.cpp
* @brief Definitions for inputtino gamepad input handling. * @brief Definitions for inputtino gamepad input handling.
*/ */
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/utility.h" #include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
using namespace std::literals; using namespace std::literals;
namespace platf::gamepad { namespace platf::gamepad {
@ -25,8 +26,7 @@ namespace platf::gamepad {
GAMEPAD_STATUS ///< Helper to indicate the number of status GAMEPAD_STATUS ///< Helper to indicate the number of status
}; };
auto auto create_xbox_one() {
create_xbox_one() {
return inputtino::XboxOneJoypad::create({.name = "Sunshine X-Box One (virtual) pad", return inputtino::XboxOneJoypad::create({.name = "Sunshine X-Box One (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147 // https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
.vendor_id = 0x045E, .vendor_id = 0x045E,
@ -34,8 +34,7 @@ namespace platf::gamepad {
.version = 0x0408}); .version = 0x0408});
} }
auto auto create_switch() {
create_switch() {
return inputtino::SwitchJoypad::create({.name = "Sunshine Nintendo (virtual) pad", return inputtino::SwitchJoypad::create({.name = "Sunshine Nintendo (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981 // https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
.vendor_id = 0x057e, .vendor_id = 0x057e,
@ -43,51 +42,38 @@ namespace platf::gamepad {
.version = 0x8111}); .version = 0x8111});
} }
auto auto create_ds5() {
create_ds5() { return inputtino::PS5Joypad::create({.name = "Sunshine DualSense (virtual) pad", .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111});
return inputtino::PS5Joypad::create({ .name = "Sunshine DualSense (virtual) pad",
.vendor_id = 0x054C,
.product_id = 0x0CE6,
.version = 0x8111 });
} }
int int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
ControllerType selectedGamepadType; ControllerType selectedGamepadType;
if (config::input.gamepad == "xone"sv) { if (config::input.gamepad == "xone"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (manual selection)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (manual selection)"sv;
selectedGamepadType = XboxOneWired; 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; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualSense 5 controller (manual selection)"sv;
selectedGamepadType = DualSenseWired; 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; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (manual selection)"sv;
selectedGamepadType = SwitchProWired; 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; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (auto-selected by client-reported type)"sv;
selectedGamepadType = XboxOneWired; 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; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by client-reported type)"sv;
selectedGamepadType = DualSenseWired; 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; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (auto-selected by client-reported type)"sv;
selectedGamepadType = SwitchProWired; 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; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by motion sensor presence)"sv;
selectedGamepadType = DualSenseWired; 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; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by touchpad presence)"sv;
selectedGamepadType = DualSenseWired; selectedGamepadType = DualSenseWired;
} } else {
else {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (default)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (default)"sv;
selectedGamepadType = XboxOneWired; selectedGamepadType = XboxOneWired;
} }
@ -102,8 +88,7 @@ namespace platf::gamepad {
if (metadata.capabilities & LI_CCAP_RGB_LED) { 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; 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))) { 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; BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " is emulating a DualShock 5 controller, but the client gamepad doesn't have motion sensors active"sv;
} }
@ -125,33 +110,34 @@ namespace platf::gamepad {
}; };
switch (selectedGamepadType) { switch (selectedGamepadType) {
case XboxOneWired: { case XboxOneWired:
{
auto xOne = create_xbox_one(); auto xOne = create_xbox_one();
if (xOne) { if (xOne) {
(*xOne).set_on_rumble(on_rumble_fn); (*xOne).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne)); gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne));
raw->gamepads[id.globalIndex] = std::move(gamepad); raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0; return 0;
} } else {
else {
BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage(); BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage();
return -1; return -1;
} }
} }
case SwitchProWired: { case SwitchProWired:
{
auto switchPro = create_switch(); auto switchPro = create_switch();
if (switchPro) { if (switchPro) {
(*switchPro).set_on_rumble(on_rumble_fn); (*switchPro).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro)); gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro));
raw->gamepads[id.globalIndex] = std::move(gamepad); raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0; return 0;
} } else {
else {
BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage(); BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage();
return -1; return -1;
} }
} }
case DualSenseWired: { case DualSenseWired:
{
auto ds5 = create_ds5(); auto ds5 = create_ds5();
if (ds5) { if (ds5) {
(*ds5).set_on_rumble(on_rumble_fn); (*ds5).set_on_rumble(on_rumble_fn);
@ -173,8 +159,7 @@ namespace platf::gamepad {
gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5)); gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5));
raw->gamepads[id.globalIndex] = std::move(gamepad); raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0; return 0;
} } else {
else {
BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage(); BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage();
return -1; return -1;
} }
@ -183,15 +168,13 @@ namespace platf::gamepad {
return -1; return -1;
} }
void void free(input_raw_t *raw, int nr) {
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) // 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]->joypad.reset();
raw->gamepads[nr].reset(); raw->gamepads[nr].reset();
} }
void void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
auto gamepad = raw->gamepads[nr]; auto gamepad = raw->gamepads[nr];
if (!gamepad) { if (!gamepad) {
return; return;
@ -206,8 +189,7 @@ namespace platf::gamepad {
*gamepad->joypad); *gamepad->joypad);
} }
void void touch(input_raw_t *raw, const gamepad_touch_t &touch) {
touch(input_raw_t *raw, const gamepad_touch_t &touch) {
auto gamepad = raw->gamepads[touch.id.globalIndex]; auto gamepad = raw->gamepads[touch.id.globalIndex];
if (!gamepad) { if (!gamepad) {
return; return;
@ -216,15 +198,13 @@ namespace platf::gamepad {
if (std::holds_alternative<inputtino::PS5Joypad>(*gamepad->joypad)) { if (std::holds_alternative<inputtino::PS5Joypad>(*gamepad->joypad)) {
if (touch.pressure > 0.5) { 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); 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); std::get<inputtino::PS5Joypad>(*gamepad->joypad).release_finger(touch.pointerId);
} }
} }
} }
void void motion(input_raw_t *raw, const gamepad_motion_t &motion) {
motion(input_raw_t *raw, const gamepad_motion_t &motion) {
auto gamepad = raw->gamepads[motion.id.globalIndex]; auto gamepad = raw->gamepads[motion.id.globalIndex];
if (!gamepad) { if (!gamepad) {
return; return;
@ -242,8 +222,7 @@ namespace platf::gamepad {
} }
} }
void void battery(input_raw_t *raw, const gamepad_battery_t &battery) {
battery(input_raw_t *raw, const gamepad_battery_t &battery) {
auto gamepad = raw->gamepads[battery.id.globalIndex]; auto gamepad = raw->gamepads[battery.id.globalIndex];
if (!gamepad) { if (!gamepad) {
return; return;
@ -272,8 +251,7 @@ namespace platf::gamepad {
} }
} }
std::vector<supported_gamepad_t> & std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
supported_gamepads(input_t *input) {
if (!input) { if (!input) {
static std::vector gps { static std::vector gps {
supported_gamepad_t {"auto", true, ""}, supported_gamepad_t {"auto", true, ""},

View file

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

View file

@ -2,18 +2,19 @@
* @file src/platform/linux/input/inputtino_keyboard.cpp * @file src/platform/linux/input/inputtino_keyboard.cpp
* @brief Definitions for inputtino keyboard input handling. * @brief Definitions for inputtino keyboard input handling.
*/ */
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_keyboard.h"
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/utility.h" #include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_keyboard.h"
using namespace std::literals; using namespace std::literals;
namespace platf::keyboard { namespace platf::keyboard {
@ -25,8 +26,7 @@ namespace platf::keyboard {
* *
* adapted from: https://stackoverflow.com/a/7639754 * adapted from: https://stackoverflow.com/a/7639754
*/ */
std::string std::string to_hex(const std::basic_string<char32_t> &str) {
to_hex(const std::basic_string<char32_t> &str) {
std::stringstream ss; std::stringstream ss;
ss << std::hex << std::setfill('0'); ss << std::hex << std::setfill('0');
for (const auto &ch : str) { for (const auto &ch : str) {
@ -42,48 +42,123 @@ namespace platf::keyboard {
* A map of linux scan code -> Moonlight keyboard code * A map of linux scan code -> Moonlight keyboard code
*/ */
static const std::map<short, short> key_mappings = { static const std::map<short, short> key_mappings = {
{ KEY_BACKSPACE, 0x08 }, { KEY_TAB, 0x09 }, { KEY_ENTER, 0x0D }, { KEY_LEFTSHIFT, 0x10 }, {KEY_BACKSPACE, 0x08},
{ KEY_LEFTCTRL, 0x11 }, { KEY_CAPSLOCK, 0x14 }, { KEY_ESC, 0x1B }, { KEY_SPACE, 0x20 }, {KEY_TAB, 0x09},
{ KEY_PAGEUP, 0x21 }, { KEY_PAGEDOWN, 0x22 }, { KEY_END, 0x23 }, { KEY_HOME, 0x24 }, {KEY_ENTER, 0x0D},
{ KEY_LEFT, 0x25 }, { KEY_UP, 0x26 }, { KEY_RIGHT, 0x27 }, { KEY_DOWN, 0x28 }, {KEY_LEFTSHIFT, 0x10},
{ KEY_SYSRQ, 0x2C }, { KEY_INSERT, 0x2D }, { KEY_DELETE, 0x2E }, { KEY_0, 0x30 }, {KEY_LEFTCTRL, 0x11},
{ KEY_1, 0x31 }, { KEY_2, 0x32 }, { KEY_3, 0x33 }, { KEY_4, 0x34 }, {KEY_CAPSLOCK, 0x14},
{ KEY_5, 0x35 }, { KEY_6, 0x36 }, { KEY_7, 0x37 }, { KEY_8, 0x38 }, {KEY_ESC, 0x1B},
{ KEY_9, 0x39 }, { KEY_A, 0x41 }, { KEY_B, 0x42 }, { KEY_C, 0x43 }, {KEY_SPACE, 0x20},
{ KEY_D, 0x44 }, { KEY_E, 0x45 }, { KEY_F, 0x46 }, { KEY_G, 0x47 }, {KEY_PAGEUP, 0x21},
{ KEY_H, 0x48 }, { KEY_I, 0x49 }, { KEY_J, 0x4A }, { KEY_K, 0x4B }, {KEY_PAGEDOWN, 0x22},
{ KEY_L, 0x4C }, { KEY_M, 0x4D }, { KEY_N, 0x4E }, { KEY_O, 0x4F }, {KEY_END, 0x23},
{ KEY_P, 0x50 }, { KEY_Q, 0x51 }, { KEY_R, 0x52 }, { KEY_S, 0x53 }, {KEY_HOME, 0x24},
{ KEY_T, 0x54 }, { KEY_U, 0x55 }, { KEY_V, 0x56 }, { KEY_W, 0x57 }, {KEY_LEFT, 0x25},
{ KEY_X, 0x58 }, { KEY_Y, 0x59 }, { KEY_Z, 0x5A }, { KEY_LEFTMETA, 0x5B }, {KEY_UP, 0x26},
{ KEY_RIGHTMETA, 0x5C }, { KEY_KP0, 0x60 }, { KEY_KP1, 0x61 }, { KEY_KP2, 0x62 }, {KEY_RIGHT, 0x27},
{ KEY_KP3, 0x63 }, { KEY_KP4, 0x64 }, { KEY_KP5, 0x65 }, { KEY_KP6, 0x66 }, {KEY_DOWN, 0x28},
{ KEY_KP7, 0x67 }, { KEY_KP8, 0x68 }, { KEY_KP9, 0x69 }, { KEY_KPASTERISK, 0x6A }, {KEY_SYSRQ, 0x2C},
{ KEY_KPPLUS, 0x6B }, { KEY_KPMINUS, 0x6D }, { KEY_KPDOT, 0x6E }, { KEY_KPSLASH, 0x6F }, {KEY_INSERT, 0x2D},
{ KEY_F1, 0x70 }, { KEY_F2, 0x71 }, { KEY_F3, 0x72 }, { KEY_F4, 0x73 }, {KEY_DELETE, 0x2E},
{ KEY_F5, 0x74 }, { KEY_F6, 0x75 }, { KEY_F7, 0x76 }, { KEY_F8, 0x77 }, {KEY_0, 0x30},
{ KEY_F9, 0x78 }, { KEY_F10, 0x79 }, { KEY_F11, 0x7A }, { KEY_F12, 0x7B }, {KEY_1, 0x31},
{ KEY_NUMLOCK, 0x90 }, { KEY_SCROLLLOCK, 0x91 }, { KEY_LEFTSHIFT, 0xA0 }, { KEY_RIGHTSHIFT, 0xA1 }, {KEY_2, 0x32},
{ KEY_LEFTCTRL, 0xA2 }, { KEY_RIGHTCTRL, 0xA3 }, { KEY_LEFTALT, 0xA4 }, { KEY_RIGHTALT, 0xA5 }, {KEY_3, 0x33},
{ KEY_SEMICOLON, 0xBA }, { KEY_EQUAL, 0xBB }, { KEY_COMMA, 0xBC }, { KEY_MINUS, 0xBD }, {KEY_4, 0x34},
{ KEY_DOT, 0xBE }, { KEY_SLASH, 0xBF }, { KEY_GRAVE, 0xC0 }, { KEY_LEFTBRACE, 0xDB }, {KEY_5, 0x35},
{ KEY_BACKSLASH, 0xDC }, { KEY_RIGHTBRACE, 0xDD }, { KEY_APOSTROPHE, 0xDE }, { KEY_102ND, 0xE2 } {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 void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) {
update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) {
if (raw->keyboard) { if (raw->keyboard) {
if (release) { if (release) {
(*raw->keyboard).release(modcode); (*raw->keyboard).release(modcode);
} } else {
else {
(*raw->keyboard).press(modcode); (*raw->keyboard).press(modcode);
} }
} }
} }
void void unicode(input_raw_t *raw, char *utf8, int size) {
unicode(input_raw_t *raw, char *utf8, int size) {
if (raw->keyboard) { if (raw->keyboard) {
/* Reading input text as UTF-8 */ /* Reading input text as UTF-8 */
auto utf8_str = boost::locale::conv::to_utf<wchar_t>(utf8, utf8 + size, "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); auto wincode = key_mappings.find(keycode);
if (keycode == -1 || wincode == key_mappings.end()) { if (keycode == -1 || wincode == key_mappings.end()) {
BOOST_LOG(warning) << "Unicode, unable to find keycode for: "sv << ch; BOOST_LOG(warning) << "Unicode, unable to find keycode for: "sv << ch;
} } else {
else {
(*raw->keyboard).press(wincode->second); (*raw->keyboard).press(wincode->second);
(*raw->keyboard).release(wincode->second); (*raw->keyboard).release(wincode->second);
} }

View file

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

View file

@ -2,38 +2,36 @@
* @file src/platform/linux/input/inputtino_mouse.cpp * @file src/platform/linux/input/inputtino_mouse.cpp
* @brief Definitions for inputtino mouse input handling. * @brief Definitions for inputtino mouse input handling.
*/ */
// lib includes
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <inputtino/input.hpp> #include <inputtino/input.hpp>
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_mouse.h"
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/utility.h" #include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_mouse.h"
using namespace std::literals; using namespace std::literals;
namespace platf::mouse { namespace platf::mouse {
void void move(input_raw_t *raw, int deltaX, int deltaY) {
move(input_raw_t *raw, int deltaX, int deltaY) {
if (raw->mouse) { if (raw->mouse) {
(*raw->mouse).move(deltaX, deltaY); (*raw->mouse).move(deltaX, deltaY);
} }
} }
void void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y) {
move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y) {
if (raw->mouse) { if (raw->mouse) {
(*raw->mouse).move_abs(x, y, touch_port.width, touch_port.height); (*raw->mouse).move_abs(x, y, touch_port.width, touch_port.height);
} }
} }
void void button(input_raw_t *raw, int button, bool release) {
button(input_raw_t *raw, int button, bool release) {
if (raw->mouse) { if (raw->mouse) {
inputtino::Mouse::MOUSE_BUTTON btn_type; inputtino::Mouse::MOUSE_BUTTON btn_type;
switch (button) { switch (button) {
@ -58,29 +56,25 @@ namespace platf::mouse {
} }
if (release) { if (release) {
(*raw->mouse).release(btn_type); (*raw->mouse).release(btn_type);
} } else {
else {
(*raw->mouse).press(btn_type); (*raw->mouse).press(btn_type);
} }
} }
} }
void void scroll(input_raw_t *raw, int high_res_distance) {
scroll(input_raw_t *raw, int high_res_distance) {
if (raw->mouse) { if (raw->mouse) {
(*raw->mouse).vertical_scroll(high_res_distance); (*raw->mouse).vertical_scroll(high_res_distance);
} }
} }
void void hscroll(input_raw_t *raw, int high_res_distance) {
hscroll(input_raw_t *raw, int high_res_distance) {
if (raw->mouse) { if (raw->mouse) {
(*raw->mouse).horizontal_scroll(high_res_distance); (*raw->mouse).horizontal_scroll(high_res_distance);
} }
} }
util::point_t util::point_t get_location(input_raw_t *raw) {
get_location(input_raw_t *raw) {
if (raw->mouse) { if (raw->mouse) {
// TODO: decide what to do after https://github.com/games-on-whales/inputtino/issues/6 is resolved. // 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 x = (*raw->mouse).get_absolute_x();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,16 +12,18 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
// lib includes // platform includes
#include <arpa/inet.h> #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 <dlfcn.h>
#include <fcntl.h>
#include <ifaddrs.h> #include <ifaddrs.h>
#include <netinet/udp.h> #include <netinet/udp.h>
#include <pwd.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> #include <unistd.h>
// local includes // local includes
@ -46,8 +48,7 @@ namespace bp = boost::process;
window_system_e window_system; window_system_e window_system;
namespace dyn { namespace dyn {
void * void *handle(const std::vector<const char *> &libs) {
handle(const std::vector<const char *> &libs) {
void *handle; void *handle;
for (auto lib : libs) { for (auto lib : libs) {
@ -70,8 +71,7 @@ namespace dyn {
return nullptr; return nullptr;
} }
int int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
int err = 0; int err = 0;
for (auto &func : funcs) { for (auto &func : funcs) {
TUPLE_2D_REF(fn, name, func); TUPLE_2D_REF(fn, name, func);
@ -88,11 +88,11 @@ namespace dyn {
return err; return err;
} }
} // namespace dyn } // namespace dyn
namespace platf { namespace platf {
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>; using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
ifaddr_t ifaddr_t get_ifaddrs() {
get_ifaddrs() {
ifaddrs *p {nullptr}; ifaddrs *p {nullptr};
getifaddrs(&p); getifaddrs(&p);
@ -105,8 +105,7 @@ namespace platf {
* @details This is used for the log directory, so it cannot invoke Boost logging! * @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. * @return The path of the appdata directory that should be used.
*/ */
fs::path fs::path appdata() {
appdata() {
static std::once_flag migration_flag; static std::once_flag migration_flag;
static fs::path config_path; static fs::path config_path;
@ -172,8 +171,7 @@ namespace platf {
std::cerr << "Migration failed: " << ec.message() << std::endl; std::cerr << "Migration failed: " << ec.message() << std::endl;
config_path = old_config_path; config_path = old_config_path;
} }
} } else {
else {
// We cannot use Boost logging because it hasn't been initialized yet! // 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 << "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; std::cerr << "It is recommended to remove "sv << old_config_path << std::endl;
@ -185,45 +183,36 @@ namespace platf {
return config_path; return config_path;
} }
std::string std::string from_sockaddr(const sockaddr *const ip_addr) {
from_sockaddr(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN] = {}; char data[INET6_ADDRSTRLEN] = {};
auto family = ip_addr->sa_family; auto family = ip_addr->sa_family;
if (family == AF_INET6) { if (family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
INET6_ADDRSTRLEN); } 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);
} }
return std::string {data}; return std::string {data};
} }
std::pair<std::uint16_t, std::string> std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
from_sockaddr_ex(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN] = {}; char data[INET6_ADDRSTRLEN] = {};
auto family = ip_addr->sa_family; auto family = ip_addr->sa_family;
std::uint16_t port = 0; std::uint16_t port = 0;
if (family == AF_INET6) { if (family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
INET6_ADDRSTRLEN);
port = ((sockaddr_in6 *) ip_addr)->sin6_port; 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);
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
port = ((sockaddr_in *) ip_addr)->sin_port; port = ((sockaddr_in *) ip_addr)->sin_port;
} }
return {port, std::string {data}}; return {port, std::string {data}};
} }
std::string std::string get_mac_address(const std::string_view &address) {
get_mac_address(const std::string_view &address) {
auto ifaddrs = get_ifaddrs(); auto ifaddrs = get_ifaddrs();
for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) { if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) {
@ -240,8 +229,7 @@ namespace platf {
return "00:00:00:00:00:00"s; return "00:00:00:00:00:00"s;
} }
bp::child 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) {
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 // clang-format off
if (!group) { if (!group) {
if (!file) { if (!file) {
@ -266,8 +254,7 @@ namespace platf {
* @brief Open a url in the default web browser. * @brief Open a url in the default web browser.
* @param url The url to open. * @param url The url to open.
*/ */
void void open_url(const std::string &url) {
open_url(const std::string &url) {
// set working dir to user home directory // set working dir to user home directory
auto working_dir = boost::filesystem::path(std::getenv("HOME")); auto working_dir = boost::filesystem::path(std::getenv("HOME"));
std::string cmd = R"(xdg-open ")" + url + R"(")"; 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); auto child = run_command(false, false, cmd, working_dir, _env, nullptr, ec, nullptr);
if (ec) { if (ec) {
BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message(); BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message();
} } else {
else {
BOOST_LOG(info) << "Opened url ["sv << url << "]"sv; BOOST_LOG(info) << "Opened url ["sv << url << "]"sv;
child.detach(); child.detach();
} }
} }
void void adjust_thread_priority(thread_priority_e priority) {
adjust_thread_priority(thread_priority_e priority) {
// Unimplemented // Unimplemented
} }
void void streaming_will_start() {
streaming_will_start() {
// Nothing to do // Nothing to do
} }
void void streaming_will_stop() {
streaming_will_stop() {
// Nothing to do // Nothing to do
} }
void void restart_on_exit() {
restart_on_exit() {
char executable[PATH_MAX]; char executable[PATH_MAX];
ssize_t len = readlink("/proc/self/exe", executable, PATH_MAX - 1); ssize_t len = readlink("/proc/self/exe", executable, PATH_MAX - 1);
if (len == -1) { if (len == -1) {
@ -322,42 +304,35 @@ namespace platf {
} }
} }
void void restart() {
restart() {
// Gracefully clean up and restart ourselves instead of exiting // Gracefully clean up and restart ourselves instead of exiting
atexit(restart_on_exit); atexit(restart_on_exit);
lifetime::exit_sunshine(0, true); lifetime::exit_sunshine(0, true);
} }
int int set_env(const std::string &name, const std::string &value) {
set_env(const std::string &name, const std::string &value) {
return setenv(name.c_str(), value.c_str(), 1); return setenv(name.c_str(), value.c_str(), 1);
} }
int int unset_env(const std::string &name) {
unset_env(const std::string &name) {
return unsetenv(name.c_str()); return unsetenv(name.c_str());
} }
bool bool request_process_group_exit(std::uintptr_t native_handle) {
request_process_group_exit(std::uintptr_t native_handle) {
if (kill(-((pid_t) native_handle), SIGTERM) == 0 || errno == ESRCH) { if (kill(-((pid_t) native_handle), SIGTERM) == 0 || errno == ESRCH) {
BOOST_LOG(debug) << "Successfully sent SIGTERM to process group: "sv << native_handle; BOOST_LOG(debug) << "Successfully sent SIGTERM to process group: "sv << native_handle;
return true; return true;
} } else {
else {
BOOST_LOG(warning) << "Unable to send SIGTERM to process group ["sv << native_handle << "]: "sv << errno; BOOST_LOG(warning) << "Unable to send SIGTERM to process group ["sv << native_handle << "]: "sv << errno;
return false; return false;
} }
} }
bool bool process_group_running(std::uintptr_t native_handle) {
process_group_running(std::uintptr_t native_handle) {
return waitpid(-((pid_t) native_handle), nullptr, WNOHANG) >= 0; return waitpid(-((pid_t) native_handle), nullptr, WNOHANG) >= 0;
} }
struct sockaddr_in struct sockaddr_in to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
struct sockaddr_in saddr_v4 = {}; struct sockaddr_in saddr_v4 = {};
saddr_v4.sin_family = AF_INET; saddr_v4.sin_family = AF_INET;
@ -369,8 +344,7 @@ namespace platf {
return saddr_v4; return saddr_v4;
} }
struct sockaddr_in6 struct sockaddr_in6 to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
struct sockaddr_in6 saddr_v6 = {}; struct sockaddr_in6 saddr_v6 = {};
saddr_v6.sin6_family = AF_INET6; saddr_v6.sin6_family = AF_INET6;
@ -383,8 +357,7 @@ namespace platf {
return saddr_v6; return saddr_v6;
} }
bool bool send_batch(batched_send_info_t &send_info) {
send_batch(batched_send_info_t &send_info) {
auto sockfd = (int) send_info.native_socket; auto sockfd = (int) send_info.native_socket;
struct msghdr msg = {}; struct msghdr msg = {};
@ -396,8 +369,7 @@ namespace platf {
msg.msg_name = (struct sockaddr *) &taddr_v6; msg.msg_name = (struct sockaddr *) &taddr_v6;
msg.msg_namelen = sizeof(taddr_v6); msg.msg_namelen = sizeof(taddr_v6);
} } else {
else {
taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port); taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
msg.msg_name = (struct sockaddr *) &taddr_v4; msg.msg_name = (struct sockaddr *) &taddr_v4;
@ -405,10 +377,10 @@ namespace platf {
} }
union { union {
char buf[CMSG_SPACE(sizeof(uint16_t)) + char buf[CMSG_SPACE(sizeof(uint16_t)) + std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
struct cmsghdr alignment; struct cmsghdr alignment;
} cmbuf = {}; // Must be zeroed for CMSG_NXTHDR() } cmbuf = {}; // Must be zeroed for CMSG_NXTHDR()
socklen_t cmbuflen = 0; socklen_t cmbuflen = 0;
msg.msg_control = cmbuf.buf; msg.msg_control = cmbuf.buf;
@ -430,8 +402,7 @@ namespace platf {
pktinfo_cm->cmsg_type = IPV6_PKTINFO; pktinfo_cm->cmsg_type = IPV6_PKTINFO;
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo)); pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo));
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo)); memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
} } else {
else {
struct in_pktinfo pktInfo; struct in_pktinfo pktInfo;
struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0); 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; iovs[iovlen].iov_len = send_info.payload_size;
iovlen++; iovlen++;
} }
} } else {
else {
// Translate buffer descriptors into iovs // Translate buffer descriptors into iovs
auto payload_offset = (send_info.block_offset + seg_index) * send_info.payload_size; 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); 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_type = UDP_SEGMENT;
cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
*((uint16_t *) CMSG_DATA(cm)) = msg_size; *((uint16_t *) CMSG_DATA(cm)) = msg_size;
} } else {
else {
msg.msg_controllen = cmbuflen; msg.msg_controllen = cmbuflen;
} }
@ -593,8 +562,7 @@ namespace platf {
} }
} }
bool bool send(send_info_t &send_info) {
send(send_info_t &send_info) {
auto sockfd = (int) send_info.native_socket; auto sockfd = (int) send_info.native_socket;
struct msghdr msg = {}; struct msghdr msg = {};
@ -606,8 +574,7 @@ namespace platf {
msg.msg_name = (struct sockaddr *) &taddr_v6; msg.msg_name = (struct sockaddr *) &taddr_v6;
msg.msg_namelen = sizeof(taddr_v6); msg.msg_namelen = sizeof(taddr_v6);
} } else {
else {
taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port); taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
msg.msg_name = (struct sockaddr *) &taddr_v4; 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)))]; char buf[std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
struct cmsghdr alignment; struct cmsghdr alignment;
} cmbuf; } cmbuf;
socklen_t cmbuflen = 0; socklen_t cmbuflen = 0;
msg.msg_control = cmbuf.buf; msg.msg_control = cmbuf.buf;
@ -637,8 +605,7 @@ namespace platf {
pktinfo_cm->cmsg_type = IPV6_PKTINFO; pktinfo_cm->cmsg_type = IPV6_PKTINFO;
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo)); pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo));
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo)); memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
} } else {
else {
struct in_pktinfo pktInfo; struct in_pktinfo pktInfo;
struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0); 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 { class qos_t: public deinit_t {
public: public:
qos_t(int sockfd, std::vector<std::tuple<int, int, int>> options): qos_t(int sockfd, std::vector<std::tuple<int, int, int>> options):
sockfd(sockfd), options(options) { sockfd(sockfd),
options(options) {
qos_ref_count++; qos_ref_count++;
} }
@ -731,8 +699,7 @@ namespace platf {
* @param data_type The type of traffic sent on this socket. * @param data_type The type of traffic sent on this socket.
* @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic.
*/ */
std::unique_ptr<deinit_t> 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) {
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; int sockfd = (int) native_socket;
std::vector<std::tuple<int, int, int>> reset_options; 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()) { if (address.is_v6() && !address.to_v6().is_v4_mapped()) {
level = SOL_IPV6; level = SOL_IPV6;
option = IPV6_TCLASS; option = IPV6_TCLASS;
} } else {
else {
level = SOL_IP; level = SOL_IP;
option = IP_TOS; option = IP_TOS;
} }
@ -773,8 +739,7 @@ namespace platf {
if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) == 0) { if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) == 0) {
// Reset TOS to -1 when QoS is disabled // Reset TOS to -1 when QoS is disabled
reset_options.emplace_back(std::make_tuple(level, option, -1)); reset_options.emplace_back(std::make_tuple(level, option, -1));
} } else {
else {
BOOST_LOG(error) << "Failed to set TOS/TCLASS: "sv << errno; 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) { if (setsockopt(sockfd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)) == 0) {
// Reset SO_PRIORITY to 0 when QoS is disabled // Reset SO_PRIORITY to 0 when QoS is disabled
reset_options.emplace_back(std::make_tuple(SOL_SOCKET, SO_PRIORITY, 0)); reset_options.emplace_back(std::make_tuple(SOL_SOCKET, SO_PRIORITY, 0));
} } else {
else {
BOOST_LOG(error) << "Failed to set SO_PRIORITY: "sv << errno; BOOST_LOG(error) << "Failed to set SO_PRIORITY: "sv << errno;
} }
return std::make_unique<qos_t>(sockfd, reset_options); return std::make_unique<qos_t>(sockfd, reset_options);
} }
std::string std::string get_host_name() {
get_host_name() {
try { try {
return boost::asio::ip::host_name(); 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(); BOOST_LOG(error) << "Failed to get hostname: "sv << err.what();
return "Sunshine"s; return "Sunshine"s;
} }
@ -830,67 +792,62 @@ namespace platf {
static std::bitset<source::MAX_FLAGS> sources; static std::bitset<source::MAX_FLAGS> sources;
#ifdef SUNSHINE_BUILD_CUDA #ifdef SUNSHINE_BUILD_CUDA
std::vector<std::string> std::vector<std::string> nvfbc_display_names();
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::shared_ptr<display_t>
nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
bool bool verify_nvfbc() {
verify_nvfbc() {
return !nvfbc_display_names().empty(); return !nvfbc_display_names().empty();
} }
#endif #endif
#ifdef SUNSHINE_BUILD_WAYLAND #ifdef SUNSHINE_BUILD_WAYLAND
std::vector<std::string> std::vector<std::string> wl_display_names();
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::shared_ptr<display_t>
wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
bool bool verify_wl() {
verify_wl() {
return window_system == window_system_e::WAYLAND && !wl_display_names().empty(); return window_system == window_system_e::WAYLAND && !wl_display_names().empty();
} }
#endif #endif
#ifdef SUNSHINE_BUILD_DRM #ifdef SUNSHINE_BUILD_DRM
std::vector<std::string> std::vector<std::string> kms_display_names(mem_type_e hwdevice_type);
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::shared_ptr<display_t>
kms_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
bool bool verify_kms() {
verify_kms() {
return !kms_display_names(mem_type_e::unknown).empty(); return !kms_display_names(mem_type_e::unknown).empty();
} }
#endif #endif
#ifdef SUNSHINE_BUILD_X11 #ifdef SUNSHINE_BUILD_X11
std::vector<std::string> std::vector<std::string> x11_display_names();
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::shared_ptr<display_t>
x11_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
bool bool verify_x11() {
verify_x11() {
return window_system == window_system_e::X11 && !x11_display_names().empty(); return window_system == window_system_e::X11 && !x11_display_names().empty();
} }
#endif #endif
std::vector<std::string> std::vector<std::string> display_names(mem_type_e hwdevice_type) {
display_names(mem_type_e hwdevice_type) {
#ifdef SUNSHINE_BUILD_CUDA #ifdef SUNSHINE_BUILD_CUDA
// display using NvFBC only supports mem_type_e::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 #endif
#ifdef SUNSHINE_BUILD_WAYLAND #ifdef SUNSHINE_BUILD_WAYLAND
if (sources[source::WAYLAND]) return wl_display_names(); if (sources[source::WAYLAND]) {
return wl_display_names();
}
#endif #endif
#ifdef SUNSHINE_BUILD_DRM #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 #endif
#ifdef SUNSHINE_BUILD_X11 #ifdef SUNSHINE_BUILD_X11
if (sources[source::X11]) return x11_display_names(); if (sources[source::X11]) {
return x11_display_names();
}
#endif #endif
return {}; return {};
} }
@ -899,14 +856,12 @@ namespace platf {
* @brief Returns if GPUs/drivers have changed since the last call to this function. * @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. * @return `true` if a change has occurred or if it is unknown whether a change occurred.
*/ */
bool bool needs_encoder_reenumeration() {
needs_encoder_reenumeration() {
// We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on Linux. // We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on Linux.
return true; return true;
} }
std::shared_ptr<display_t> std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
#ifdef SUNSHINE_BUILD_CUDA #ifdef SUNSHINE_BUILD_CUDA
if (sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) { if (sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) {
BOOST_LOG(info) << "Screencasting with NvFBC"sv; BOOST_LOG(info) << "Screencasting with NvFBC"sv;
@ -935,8 +890,7 @@ namespace platf {
return nullptr; return nullptr;
} }
std::unique_ptr<deinit_t> std::unique_ptr<deinit_t> init() {
init() {
// enable low latency mode for AMD // enable low latency mode for AMD
// https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/30039 // https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/30039
set_env("AMD_DEBUG", "lowlatencyenc"); set_env("AMD_DEBUG", "lowlatencyenc");
@ -1005,8 +959,7 @@ namespace platf {
class linux_high_precision_timer: public high_precision_timer { class linux_high_precision_timer: public high_precision_timer {
public: public:
void void sleep_for(const std::chrono::nanoseconds &duration) override {
sleep_for(const std::chrono::nanoseconds &duration) override {
std::this_thread::sleep_for(duration); std::this_thread::sleep_for(duration);
} }
@ -1015,8 +968,7 @@ namespace platf {
} }
}; };
std::unique_ptr<high_precision_timer> std::unique_ptr<high_precision_timer> create_high_precision_timer() {
create_high_precision_timer() {
return std::make_unique<linux_high_precision_timer>(); return std::make_unique<linux_high_precision_timer>();
} }
} // namespace platf } // namespace platf

View file

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

View file

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

View file

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

View file

@ -4,12 +4,14 @@
*/ */
#pragma once #pragma once
// local includes
#include "misc.h" #include "misc.h"
#include "src/platform/common.h" #include "src/platform/common.h"
namespace egl { namespace egl {
struct surface_descriptor_t; struct surface_descriptor_t;
} }
namespace va { namespace va {
/** /**
* Width --> Width of the image * Width --> Width of the image
@ -18,14 +20,10 @@ namespace va {
* offset_y --> Vertical offset of the image in the texture * offset_y --> Vertical offset of the image in the texture
* file_t card --> The file descriptor of the render device used for encoding * file_t card --> The file descriptor of the render device used for encoding
*/ */
std::unique_ptr<platf::avcodec_encode_device_t> std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram);
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> 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);
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 // Ensure the render device pointed to by fd is capable of encoding h264 with the hevc_mode configured
bool bool validate(int fd);
validate(int fd);
} // namespace va } // namespace va

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@
* @file src/platform/macos/av_video.m * @file src/platform/macos/av_video.m
* @brief Definitions for video capture on macOS. * @brief Definitions for video capture on macOS.
*/ */
// local includes
#import "av_video.h" #import "av_video.h"
@implementation AVVideo @implementation AVVideo
@ -62,8 +63,7 @@
if ([self.session canAddInput:screenInput]) { if ([self.session canAddInput:screenInput]) {
[self.session addInput:screenInput]; [self.session addInput:screenInput];
} } else {
else {
[screenInput release]; [screenInput release];
return nil; return nil;
} }
@ -97,9 +97,7 @@
(NSString *) AVVideoScalingModeKey: AVVideoScalingModeResizeAspect, (NSString *) AVVideoScalingModeKey: AVVideoScalingModeResizeAspect,
}]; }];
dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, DISPATCH_QUEUE_PRIORITY_HIGH);
QOS_CLASS_USER_INITIATED,
DISPATCH_QUEUE_PRIORITY_HIGH);
dispatch_queue_t recordingQueue = dispatch_queue_create("videoCaptureQueue", qos); dispatch_queue_t recordingQueue = dispatch_queue_create("videoCaptureQueue", qos);
[videoOutput setSampleBufferDelegate:self queue:recordingQueue]; [videoOutput setSampleBufferDelegate:self queue:recordingQueue];
@ -107,8 +105,7 @@
if ([self.session canAddOutput:videoOutput]) { if ([self.session canAddOutput:videoOutput]) {
[self.session addOutput:videoOutput]; [self.session addOutput:videoOutput];
} } else {
else {
[videoOutput release]; [videoOutput release];
return nil; return nil;
} }

View file

@ -2,15 +2,15 @@
* @file src/platform/macos/display.mm * @file src/platform/macos/display.mm
* @brief Definitions for display capture on macOS. * @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/common.h"
#include "src/platform/macos/av_img_t.h" #include "src/platform/macos/av_img_t.h"
#include "src/platform/macos/av_video.h" #include "src/platform/macos/av_video.h"
#include "src/platform/macos/misc.h" #include "src/platform/macos/misc.h"
#include "src/platform/macos/nv12_zero_device.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 // Avoid conflict between AVFoundation and libavutil both defining AVMediaType
#define AVMediaType AVMediaType_FFmpeg #define AVMediaType AVMediaType_FFmpeg
#include "src/video.h" #include "src/video.h"
@ -29,8 +29,7 @@ namespace platf {
[av_capture release]; [av_capture release];
} }
capture_e 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(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 signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) {
auto new_sample_buffer = std::make_shared<av_sample_buf_t>(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); 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>( auto old_data_retainer = std::make_shared<temp_retain_av_img_t>(
av_img->sample_buffer, av_img->sample_buffer,
av_img->pixel_buffer, av_img->pixel_buffer,
img_out->data); img_out->data
);
av_img->sample_buffer = new_sample_buffer; av_img->sample_buffer = new_sample_buffer;
av_img->pixel_buffer = new_pixel_buffer; av_img->pixel_buffer = new_pixel_buffer;
@ -74,33 +74,28 @@ namespace platf {
return capture_e::ok; return capture_e::ok;
} }
std::shared_ptr<img_t> std::shared_ptr<img_t> alloc_img() override {
alloc_img() override {
return std::make_shared<av_img_t>(); return std::make_shared<av_img_t>();
} }
std::unique_ptr<avcodec_encode_device_t> std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
if (pix_fmt == pix_fmt_e::yuv420p) { if (pix_fmt == pix_fmt_e::yuv420p) {
av_capture.pixelFormat = kCVPixelFormatType_32BGRA; av_capture.pixelFormat = kCVPixelFormatType_32BGRA;
return std::make_unique<avcodec_encode_device_t>(); 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>(); auto device = std::make_unique<nv12_zero_device>();
device->init(static_cast<void *>(av_capture), pix_fmt, setResolution, setPixelFormat); device->init(static_cast<void *>(av_capture), pix_fmt, setResolution, setPixelFormat);
return device; return device;
} } else {
else {
BOOST_LOG(error) << "Unsupported Pixel Format."sv; BOOST_LOG(error) << "Unsupported Pixel Format."sv;
return nullptr; return nullptr;
} }
} }
int int dummy_img(img_t *img) override {
dummy_img(img_t *img) override {
if (!platf::is_screen_capture_allowed()) { if (!platf::is_screen_capture_allowed()) {
// If we don't have the screen capture permission, this function will hang // If we don't have the screen capture permission, this function will hang
// indefinitely without doing anything useful. Exit instead to avoid this. // 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>( auto old_data_retainer = std::make_shared<temp_retain_av_img_t>(
av_img->sample_buffer, av_img->sample_buffer,
av_img->pixel_buffer, av_img->pixel_buffer,
img->data); img->data
);
av_img->sample_buffer = new_sample_buffer; av_img->sample_buffer = new_sample_buffer;
av_img->pixel_buffer = new_pixel_buffer; av_img->pixel_buffer = new_pixel_buffer;
@ -146,19 +142,16 @@ namespace platf {
* width --> the intended capture width * width --> the intended capture width
* height --> the intended capture height * height --> the intended capture height
*/ */
static void static void setResolution(void *display, int width, int height) {
setResolution(void *display, int width, int height) {
[static_cast<AVVideo *>(display) setFrameWidth:width frameHeight:height]; [static_cast<AVVideo *>(display) setFrameWidth:width frameHeight:height];
} }
static void static void setPixelFormat(void *display, OSType pixelFormat) {
setPixelFormat(void *display, OSType pixelFormat) {
static_cast<AVVideo *>(display).pixelFormat = pixelFormat; static_cast<AVVideo *>(display).pixelFormat = pixelFormat;
} }
}; };
std::shared_ptr<display_t> std::shared_ptr<display_t> display(platf::mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
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) { 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; BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
return nullptr; return nullptr;
@ -200,8 +193,7 @@ namespace platf {
return display; return display;
} }
std::vector<std::string> std::vector<std::string> display_names(mem_type_e hwdevice_type) {
display_names(mem_type_e hwdevice_type) {
__block std::vector<std::string> display_names; __block std::vector<std::string> display_names;
auto display_array = [AVVideo displayNames]; 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. * @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. * @return `true` if a change has occurred or if it is unknown whether a change occurred.
*/ */
bool bool needs_encoder_reenumeration() {
needs_encoder_reenumeration() {
// We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on macOS. // We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on macOS.
return true; return true;
} }

View file

@ -2,22 +2,24 @@
* @file src/platform/macos/input.cpp * @file src/platform/macos/input.cpp
* @brief Definitions for macOS input handling. * @brief Definitions for macOS input handling.
*/ */
#include "src/input.h" // standard includes
#import <Carbon/Carbon.h>
#include <chrono> #include <chrono>
#include <iostream>
#include <thread>
// platform includes
#include <ApplicationServices/ApplicationServices.h>
#import <Carbon/Carbon.h>
#include <CoreFoundation/CoreFoundation.h>
#include <mach/mach.h> #include <mach/mach.h>
// local includes
#include "src/display_device.h" #include "src/display_device.h"
#include "src/input.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/utility.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. * @brief Delay for a double click, in milliseconds.
* @todo Make this configurable. * @todo Make this configurable.
@ -50,8 +52,7 @@ namespace platf {
}; };
// Customized less operator for using std::lower_bound() on a KeyCodeMap array. // Customized less operator for using std::lower_bound() on a KeyCodeMap array.
bool bool operator<(const KeyCodeMap &a, const KeyCodeMap &b) {
operator<(const KeyCodeMap &a, const KeyCodeMap &b) {
return a.win_keycode < b.win_keycode; return a.win_keycode < b.win_keycode;
} }
@ -227,13 +228,15 @@ const KeyCodeMap kKeyCodesMap[] = {
}; };
// clang-format on // clang-format on
int int keysym(int keycode) {
keysym(int keycode) {
KeyCodeMap key_map {}; KeyCodeMap key_map {};
key_map.win_keycode = keycode; key_map.win_keycode = keycode;
const KeyCodeMap *temp_map = std::lower_bound( 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]) || if (temp_map >= kKeyCodesMap + sizeof(kKeyCodesMap) / sizeof(kKeyCodesMap[0]) ||
temp_map->win_keycode != keycode || temp_map->mac_keycode == -1) { temp_map->win_keycode != keycode || temp_map->mac_keycode == -1) {
@ -243,8 +246,7 @@ const KeyCodeMap kKeyCodesMap[] = {
return temp_map->mac_keycode; return temp_map->mac_keycode;
} }
void void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
auto key = keysym(modcode); auto key = keysym(modcode);
BOOST_LOG(debug) << "got keycode: 0x"sv << std::hex << modcode << ", translated to: 0x" << std::hex << key << ", release:" << release; 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; macos_input->kb_flags = release ? macos_input->kb_flags & ~mask : macos_input->kb_flags | mask;
CGEventSetType(event, kCGEventFlagsChanged); CGEventSetType(event, kCGEventFlagsChanged);
CGEventSetFlags(event, macos_input->kb_flags); CGEventSetFlags(event, macos_input->kb_flags);
} } else {
else {
CGEventSetIntegerValueField(event, kCGKeyboardEventKeycode, key); CGEventSetIntegerValueField(event, kCGKeyboardEventKeycode, key);
CGEventSetType(event, release ? kCGEventKeyUp : kCGEventKeyDown); CGEventSetType(event, release ? kCGEventKeyUp : kCGEventKeyDown);
} }
@ -293,30 +294,25 @@ const KeyCodeMap kKeyCodesMap[] = {
CGEventPost(kCGHIDEventTap, event); CGEventPost(kCGHIDEventTap, event);
} }
void void unicode(input_t &input, char *utf8, int size) {
unicode(input_t &input, char *utf8, int size) {
BOOST_LOG(info) << "unicode: Unicode input not yet implemented for MacOS."sv; BOOST_LOG(info) << "unicode: Unicode input not yet implemented for MacOS."sv;
} }
int int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
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; BOOST_LOG(info) << "alloc_gamepad: Gamepad not yet implemented for MacOS."sv;
return -1; return -1;
} }
void void free_gamepad(input_t &input, int nr) {
free_gamepad(input_t &input, int nr) {
BOOST_LOG(info) << "free_gamepad: Gamepad not yet implemented for MacOS."sv; BOOST_LOG(info) << "free_gamepad: Gamepad not yet implemented for MacOS."sv;
} }
void void gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
BOOST_LOG(info) << "gamepad: Gamepad not yet implemented for MacOS."sv; BOOST_LOG(info) << "gamepad: Gamepad not yet implemented for MacOS."sv;
} }
// returns current mouse location: // returns current mouse location:
util::point_t util::point_t get_mouse_loc(input_t &input) {
get_mouse_loc(input_t &input) {
// Creating a new event every time to avoid any reuse risk // Creating a new event every time to avoid any reuse risk
const auto macos_input = static_cast<macos_input_t *>(input.get()); const auto macos_input = static_cast<macos_input_t *>(input.get());
const auto snapshot_event = CGEventCreate(macos_input->source); const auto snapshot_event = CGEventCreate(macos_input->source);
@ -328,14 +324,14 @@ const KeyCodeMap kKeyCodesMap[] = {
}; };
} }
void void post_mouse(
post_mouse(
input_t &input, input_t &input,
const CGMouseButton button, const CGMouseButton button,
const CGEventType type, const CGEventType type,
const util::point_t raw_location, const util::point_t raw_location,
const util::point_t previous_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; 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()); const auto macos_input = static_cast<macos_input_t *>(input.get());
@ -368,8 +364,7 @@ const KeyCodeMap kKeyCodesMap[] = {
CGWarpMouseCursorPosition(location); CGWarpMouseCursorPosition(location);
} }
inline CGEventType inline CGEventType event_type_mouse(input_t &input) {
event_type_mouse(input_t &input) {
const auto macos_input = static_cast<macos_input_t *>(input.get()); const auto macos_input = static_cast<macos_input_t *>(input.get());
if (macos_input->mouse_down[0]) { if (macos_input->mouse_down[0]) {
@ -384,23 +379,23 @@ const KeyCodeMap kKeyCodesMap[] = {
return kCGEventMouseMoved; return kCGEventMouseMoved;
} }
void void move_mouse(
move_mouse(
input_t &input, input_t &input,
const int deltaX, const int deltaX,
const int deltaY) { const int deltaY
) {
const auto current = get_mouse_loc(input); 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); post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, current, 0);
} }
void void abs_mouse(
abs_mouse(
input_t &input, input_t &input,
const touch_port_t &touch_port, const touch_port_t &touch_port,
const float x, const float x,
const float y) { const float y
) {
const auto macos_input = static_cast<macos_input_t *>(input.get()); const auto macos_input = static_cast<macos_input_t *>(input.get());
const auto scaling = macos_input->displayScaling; const auto scaling = macos_input->displayScaling;
const auto display = macos_input->display; const auto display = macos_input->display;
@ -414,8 +409,7 @@ const KeyCodeMap kKeyCodesMap[] = {
post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, get_mouse_loc(input), 0); post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, get_mouse_loc(input), 0);
} }
void void button_mouse(input_t &input, const int button, const bool release) {
button_mouse(input_t &input, const int button, const bool release) {
CGMouseButton mac_button; CGMouseButton mac_button;
CGEventType event; CGEventType event;
@ -447,26 +441,26 @@ const KeyCodeMap kKeyCodesMap[] = {
if (now < macos_input->last_mouse_event[mac_button][release] + MULTICLICK_DELAY_MS) { if (now < macos_input->last_mouse_event[mac_button][release] + MULTICLICK_DELAY_MS) {
post_mouse(input, mac_button, event, mouse_position, mouse_position, 2); post_mouse(input, mac_button, event, mouse_position, mouse_position, 2);
} } else {
else {
post_mouse(input, mac_button, event, mouse_position, mouse_position, 1); post_mouse(input, mac_button, event, mouse_position, mouse_position, 1);
} }
macos_input->last_mouse_event[mac_button][release] = now; macos_input->last_mouse_event[mac_button][release] = now;
} }
void void scroll(input_t &input, const int high_res_distance) {
scroll(input_t &input, const int high_res_distance) {
CGEventRef upEvent = CGEventCreateScrollWheelEvent( CGEventRef upEvent = CGEventCreateScrollWheelEvent(
nullptr, nullptr,
kCGScrollEventUnitLine, kCGScrollEventUnitLine,
2, high_res_distance > 0 ? 1 : -1, high_res_distance); 2,
high_res_distance > 0 ? 1 : -1,
high_res_distance
);
CGEventPost(kCGHIDEventTap, upEvent); CGEventPost(kCGHIDEventTap, upEvent);
CFRelease(upEvent); CFRelease(upEvent);
} }
void void hscroll(input_t &input, int high_res_distance) {
hscroll(input_t &input, int high_res_distance) {
// Unimplemented // Unimplemented
} }
@ -475,8 +469,7 @@ const KeyCodeMap kKeyCodesMap[] = {
* @param input The global input context. * @param input The global input context.
* @return A unique pointer to a per-client input data context. * @return A unique pointer to a per-client input data context.
*/ */
std::unique_ptr<client_input_t> std::unique_ptr<client_input_t> allocate_client_input_context(input_t &input) {
allocate_client_input_context(input_t &input) {
// Unused // Unused
return nullptr; return nullptr;
} }
@ -487,8 +480,7 @@ const KeyCodeMap kKeyCodesMap[] = {
* @param touch_port The current viewport for translating to screen coordinates. * @param touch_port The current viewport for translating to screen coordinates.
* @param touch The touch event. * @param touch The touch event.
*/ */
void void touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
// Unimplemented feature - platform_caps::pen_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 touch_port The current viewport for translating to screen coordinates.
* @param pen The pen event. * @param pen The pen event.
*/ */
void void pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
// Unimplemented feature - platform_caps::pen_touch // Unimplemented feature - platform_caps::pen_touch
} }
@ -508,8 +499,7 @@ const KeyCodeMap kKeyCodesMap[] = {
* @param input The global input context. * @param input The global input context.
* @param touch The touch event. * @param touch The touch event.
*/ */
void void gamepad_touch(input_t &input, const gamepad_touch_t &touch) {
gamepad_touch(input_t &input, const gamepad_touch_t &touch) {
// Unimplemented feature - platform_caps::controller_touch // Unimplemented feature - platform_caps::controller_touch
} }
@ -518,8 +508,7 @@ const KeyCodeMap kKeyCodesMap[] = {
* @param input The global input context. * @param input The global input context.
* @param motion The motion event. * @param motion The motion event.
*/ */
void void gamepad_motion(input_t &input, const gamepad_motion_t &motion) {
gamepad_motion(input_t &input, const gamepad_motion_t &motion) {
// Unimplemented // Unimplemented
} }
@ -528,13 +517,11 @@ const KeyCodeMap kKeyCodesMap[] = {
* @param input The global input context. * @param input The global input context.
* @param battery The battery event. * @param battery The battery event.
*/ */
void void gamepad_battery(input_t &input, const gamepad_battery_t &battery) {
gamepad_battery(input_t &input, const gamepad_battery_t &battery) {
// Unimplemented // Unimplemented
} }
input_t input_t input() {
input() {
input_t result {new macos_input_t()}; input_t result {new macos_input_t()};
const auto macos_input = static_cast<macos_input_t *>(result.get()); const auto macos_input = static_cast<macos_input_t *>(result.get());
@ -550,8 +537,7 @@ const KeyCodeMap kKeyCodesMap[] = {
CGDirectDisplayID displays[max_display]; CGDirectDisplayID displays[max_display];
if (CGGetActiveDisplayList(max_display, displays, &display_count) != kCGErrorSuccess) { if (CGGetActiveDisplayList(max_display, displays, &display_count) != kCGErrorSuccess) {
BOOST_LOG(error) << "Unable to get active display list , error: "sv << std::endl; BOOST_LOG(error) << "Unable to get active display list , error: "sv << std::endl;
} } else {
else {
for (int i = 0; i < display_count; i++) { for (int i = 0; i < display_count; i++) {
CGDirectDisplayID display_id = displays[i]; CGDirectDisplayID display_id = displays[i];
if (display_id == std::atoi(output_name.c_str())) { if (display_id == std::atoi(output_name.c_str())) {
@ -581,8 +567,7 @@ const KeyCodeMap kKeyCodesMap[] = {
return result; return result;
} }
void void freeInput(void *p) {
freeInput(void *p) {
const auto *input = static_cast<macos_input_t *>(p); const auto *input = static_cast<macos_input_t *>(p);
CFRelease(input->source); CFRelease(input->source);
@ -592,8 +577,7 @@ const KeyCodeMap kKeyCodesMap[] = {
delete input; delete input;
} }
std::vector<supported_gamepad_t> & std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
supported_gamepads(input_t *input) {
static std::vector gamepads { static std::vector gamepads {
supported_gamepad_t {"", false, "gamepads.macos_not_implemented"} supported_gamepad_t {"", false, "gamepads.macos_not_implemented"}
}; };
@ -605,8 +589,7 @@ const KeyCodeMap kKeyCodesMap[] = {
* @brief Returns the supported platform capabilities to advertise to the client. * @brief Returns the supported platform capabilities to advertise to the client.
* @return Capability flags. * @return Capability flags.
*/ */
platform_caps::caps_t platform_caps::caps_t get_capabilities() {
get_capabilities() {
return 0; return 0;
} }
} // namespace platf } // namespace platf

View file

@ -2,11 +2,11 @@
* @file src/platform/macos/microphone.mm * @file src/platform/macos/microphone.mm
* @brief Definitions for microphone capture on macOS. * @brief Definitions for microphone capture on macOS.
*/ */
#include "src/platform/common.h" // local includes
#include "src/platform/macos/av_audio.h"
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h"
#include "src/platform/macos/av_audio.h"
namespace platf { namespace platf {
using namespace std::literals; using namespace std::literals;
@ -18,8 +18,7 @@ namespace platf {
[av_audio_capture release]; [av_audio_capture release];
} }
capture_e capture_e sample(std::vector<float> &sample_in) override {
sample(std::vector<float> &sample_in) override {
auto sample_size = sample_in.size(); auto sample_size = sample_in.size();
uint32_t length = 0; uint32_t length = 0;
@ -45,14 +44,12 @@ namespace platf {
AVCaptureDevice *audio_capture_device {}; AVCaptureDevice *audio_capture_device {};
public: public:
int int set_sink(const std::string &sink) override {
set_sink(const std::string &sink) override {
BOOST_LOG(warning) << "audio_control_t::set_sink() unimplemented: "sv << sink; BOOST_LOG(warning) << "audio_control_t::set_sink() unimplemented: "sv << sink;
return 0; return 0;
} }
std::unique_ptr<mic_t> std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
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>(); auto mic = std::make_unique<av_mic_t>();
const char *audio_sink = ""; const char *audio_sink = "";
@ -81,22 +78,19 @@ namespace platf {
return mic; return mic;
} }
bool bool is_sink_available(const std::string &sink) override {
is_sink_available(const std::string &sink) override {
BOOST_LOG(warning) << "audio_control_t::is_sink_available() unimplemented: "sv << sink; BOOST_LOG(warning) << "audio_control_t::is_sink_available() unimplemented: "sv << sink;
return true; return true;
} }
std::optional<sink_t> std::optional<sink_t> sink_info() override {
sink_info() override {
sink_t sink; sink_t sink;
return sink; return sink;
} }
}; };
std::unique_ptr<audio_control_t> std::unique_ptr<audio_control_t> audio_control() {
audio_control() {
return std::make_unique<macos_audio_control_t>(); return std::make_unique<macos_audio_control_t>();
} }
} // namespace platf } // namespace platf

View file

@ -4,21 +4,20 @@
*/ */
#pragma once #pragma once
// standard includes
#include <vector> #include <vector>
// platform includes
#include <CoreGraphics/CoreGraphics.h> #include <CoreGraphics/CoreGraphics.h>
namespace platf { namespace platf {
bool bool is_screen_capture_allowed();
is_screen_capture_allowed();
} }
namespace dyn { namespace dyn {
typedef void (*apiproc)(); typedef void (*apiproc)();
int int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true); void *handle(const std::vector<const char *> &libs);
void *
handle(const std::vector<const char *> &libs);
} // namespace dyn } // namespace dyn

View file

@ -8,24 +8,29 @@
#define __APPLE_USE_RFC_3542 1 #define __APPLE_USE_RFC_3542 1
#endif #endif
#include <Foundation/Foundation.h> // standard includes
#include <arpa/inet.h>
#include <dlfcn.h>
#include <fcntl.h> #include <fcntl.h>
#include <ifaddrs.h> #include <ifaddrs.h>
// platform includes
#include <arpa/inet.h>
#include <dlfcn.h>
#include <Foundation/Foundation.h>
#include <mach-o/dyld.h> #include <mach-o/dyld.h>
#include <net/if_dl.h> #include <net/if_dl.h>
#include <pwd.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 "misc.h"
#include "src/entry_handler.h" #include "src/entry_handler.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.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; using namespace std::literals;
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace bp = boost::process; namespace bp = boost::process;
@ -37,10 +42,8 @@ namespace platf {
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 110000 // __MAC_11_0 #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. // 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 // Need to use weak import so that this will link in macOS 10.14 and earlier
extern "C" bool extern "C" bool CGPreflightScreenCaptureAccess(void) __attribute__((weak_import));
CGPreflightScreenCaptureAccess(void) __attribute__((weak_import)); extern "C" bool CGRequestScreenCaptureAccess(void) __attribute__((weak_import));
extern "C" bool
CGRequestScreenCaptureAccess(void) __attribute__((weak_import));
#endif #endif
namespace { namespace {
@ -48,13 +51,11 @@ namespace platf {
} // namespace } // namespace
// Return whether screen capture is allowed for this process. // Return whether screen capture is allowed for this process.
bool bool is_screen_capture_allowed() {
is_screen_capture_allowed() {
return screen_capture_allowed; return screen_capture_allowed;
} }
std::unique_ptr<deinit_t> std::unique_ptr<deinit_t> init() {
init() {
// This will generate a warning about CGPreflightScreenCaptureAccess and // This will generate a warning about CGPreflightScreenCaptureAccess and
// CGRequestScreenCaptureAccess being unavailable before macOS 10.15, but // CGRequestScreenCaptureAccess being unavailable before macOS 10.15, but
// we have a guard to prevent it from being called on those earlier systems. // we have a guard to prevent it from being called on those earlier systems.
@ -84,8 +85,7 @@ namespace platf {
return std::make_unique<deinit_t>(); return std::make_unique<deinit_t>();
} }
fs::path fs::path appdata() {
appdata() {
const char *homedir; const char *homedir;
if ((homedir = getenv("HOME")) == nullptr) { if ((homedir = getenv("HOME")) == nullptr) {
homedir = getpwuid(geteuid())->pw_dir; homedir = getpwuid(geteuid())->pw_dir;
@ -96,8 +96,7 @@ namespace platf {
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>; using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
ifaddr_t ifaddr_t get_ifaddrs() {
get_ifaddrs() {
ifaddrs *p {nullptr}; ifaddrs *p {nullptr};
getifaddrs(&p); getifaddrs(&p);
@ -105,45 +104,36 @@ namespace platf {
return ifaddr_t {p}; return ifaddr_t {p};
} }
std::string std::string from_sockaddr(const sockaddr *const ip_addr) {
from_sockaddr(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN] = {}; char data[INET6_ADDRSTRLEN] = {};
auto family = ip_addr->sa_family; auto family = ip_addr->sa_family;
if (family == AF_INET6) { if (family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
INET6_ADDRSTRLEN); } 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);
} }
return std::string {data}; return std::string {data};
} }
std::pair<std::uint16_t, std::string> std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
from_sockaddr_ex(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN] = {}; char data[INET6_ADDRSTRLEN] = {};
auto family = ip_addr->sa_family; auto family = ip_addr->sa_family;
std::uint16_t port = 0; std::uint16_t port = 0;
if (family == AF_INET6) { if (family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
INET6_ADDRSTRLEN);
port = ((sockaddr_in6 *) ip_addr)->sin6_port; 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);
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
port = ((sockaddr_in *) ip_addr)->sin_port; port = ((sockaddr_in *) ip_addr)->sin_port;
} }
return {port, std::string {data}}; return {port, std::string {data}};
} }
std::string std::string get_mac_address(const std::string_view &address) {
get_mac_address(const std::string_view &address) {
auto ifaddrs = get_ifaddrs(); auto ifaddrs = get_ifaddrs();
for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { 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); ptr = (unsigned char *) LLADDR((struct sockaddr_dl *) (ifaptr)->ifa_addr);
char buff[100]; char buff[100];
snprintf(buff, sizeof(buff), "%02x:%02x:%02x:%02x:%02x:%02x", snprintf(buff, sizeof(buff), "%02x:%02x:%02x:%02x:%02x:%02x", *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4), *(ptr + 5));
*ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4), *(ptr + 5));
mac_address = buff; mac_address = buff;
break; break;
} }
@ -181,8 +170,7 @@ namespace platf {
return "00:00:00:00:00:00"s; return "00:00:00:00:00:00"s;
} }
bp::child 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) {
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 // clang-format off
if (!group) { if (!group) {
if (!file) { if (!file) {
@ -207,8 +195,7 @@ namespace platf {
* @brief Open a url in the default web browser. * @brief Open a url in the default web browser.
* @param url The url to open. * @param url The url to open.
*/ */
void void open_url(const std::string &url) {
open_url(const std::string &url) {
boost::filesystem::path working_dir; boost::filesystem::path working_dir;
std::string cmd = R"(open ")" + url + R"(")"; 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); auto child = run_command(false, false, cmd, working_dir, _env, nullptr, ec, nullptr);
if (ec) { if (ec) {
BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message(); BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message();
} } else {
else {
BOOST_LOG(info) << "Opened url ["sv << url << "]"sv; BOOST_LOG(info) << "Opened url ["sv << url << "]"sv;
child.detach(); child.detach();
} }
} }
void void adjust_thread_priority(thread_priority_e priority) {
adjust_thread_priority(thread_priority_e priority) {
// Unimplemented // Unimplemented
} }
void void streaming_will_start() {
streaming_will_start() {
// Nothing to do // Nothing to do
} }
void void streaming_will_stop() {
streaming_will_stop() {
// Nothing to do // Nothing to do
} }
void void restart_on_exit() {
restart_on_exit() {
char executable[2048]; char executable[2048];
uint32_t size = sizeof(executable); uint32_t size = sizeof(executable);
if (_NSGetExecutablePath(executable, &size) < 0) { if (_NSGetExecutablePath(executable, &size) < 0) {
@ -261,42 +243,35 @@ namespace platf {
} }
} }
void void restart() {
restart() {
// Gracefully clean up and restart ourselves instead of exiting // Gracefully clean up and restart ourselves instead of exiting
atexit(restart_on_exit); atexit(restart_on_exit);
lifetime::exit_sunshine(0, true); lifetime::exit_sunshine(0, true);
} }
int int set_env(const std::string &name, const std::string &value) {
set_env(const std::string &name, const std::string &value) {
return setenv(name.c_str(), value.c_str(), 1); return setenv(name.c_str(), value.c_str(), 1);
} }
int int unset_env(const std::string &name) {
unset_env(const std::string &name) {
return unsetenv(name.c_str()); return unsetenv(name.c_str());
} }
bool bool request_process_group_exit(std::uintptr_t native_handle) {
request_process_group_exit(std::uintptr_t native_handle) {
if (killpg((pid_t) native_handle, SIGTERM) == 0 || errno == ESRCH) { if (killpg((pid_t) native_handle, SIGTERM) == 0 || errno == ESRCH) {
BOOST_LOG(debug) << "Successfully sent SIGTERM to process group: "sv << native_handle; BOOST_LOG(debug) << "Successfully sent SIGTERM to process group: "sv << native_handle;
return true; return true;
} } else {
else {
BOOST_LOG(warning) << "Unable to send SIGTERM to process group ["sv << native_handle << "]: "sv << errno; BOOST_LOG(warning) << "Unable to send SIGTERM to process group ["sv << native_handle << "]: "sv << errno;
return false; return false;
} }
} }
bool bool process_group_running(std::uintptr_t native_handle) {
process_group_running(std::uintptr_t native_handle) {
return waitpid(-((pid_t) native_handle), nullptr, WNOHANG) >= 0; return waitpid(-((pid_t) native_handle), nullptr, WNOHANG) >= 0;
} }
struct sockaddr_in struct sockaddr_in to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
struct sockaddr_in saddr_v4 = {}; struct sockaddr_in saddr_v4 = {};
saddr_v4.sin_family = AF_INET; saddr_v4.sin_family = AF_INET;
@ -308,8 +283,7 @@ namespace platf {
return saddr_v4; return saddr_v4;
} }
struct sockaddr_in6 struct sockaddr_in6 to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
struct sockaddr_in6 saddr_v6 = {}; struct sockaddr_in6 saddr_v6 = {};
saddr_v6.sin6_family = AF_INET6; saddr_v6.sin6_family = AF_INET6;
@ -322,14 +296,12 @@ namespace platf {
return saddr_v6; return saddr_v6;
} }
bool bool send_batch(batched_send_info_t &send_info) {
send_batch(batched_send_info_t &send_info) {
// Fall back to unbatched send calls // Fall back to unbatched send calls
return false; return false;
} }
bool bool send(send_info_t &send_info) {
send(send_info_t &send_info) {
auto sockfd = (int) send_info.native_socket; auto sockfd = (int) send_info.native_socket;
struct msghdr msg = {}; struct msghdr msg = {};
@ -341,8 +313,7 @@ namespace platf {
msg.msg_name = (struct sockaddr *) &taddr_v6; msg.msg_name = (struct sockaddr *) &taddr_v6;
msg.msg_namelen = sizeof(taddr_v6); msg.msg_namelen = sizeof(taddr_v6);
} } else {
else {
taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port); taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
msg.msg_name = (struct sockaddr *) &taddr_v4; 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)))]; char buf[std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))];
struct cmsghdr alignment; struct cmsghdr alignment;
} cmbuf {}; } cmbuf {};
socklen_t cmbuflen = 0; socklen_t cmbuflen = 0;
msg.msg_control = cmbuf.buf; msg.msg_control = cmbuf.buf;
@ -372,8 +344,7 @@ namespace platf {
pktinfo_cm->cmsg_type = IPV6_PKTINFO; pktinfo_cm->cmsg_type = IPV6_PKTINFO;
pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo)); pktinfo_cm->cmsg_len = CMSG_LEN(sizeof(pktInfo));
memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo)); memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo));
} } else {
else {
struct in_pktinfo pktInfo {}; struct in_pktinfo pktInfo {};
struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0); 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 { class qos_t: public deinit_t {
public: public:
qos_t(int sockfd, std::vector<std::tuple<int, int, int>> options): qos_t(int sockfd, std::vector<std::tuple<int, int, int>> options):
sockfd(sockfd), options(options) { sockfd(sockfd),
options(options) {
qos_ref_count++; qos_ref_count++;
} }
@ -466,8 +438,7 @@ namespace platf {
* @param data_type The type of traffic sent on this socket. * @param data_type The type of traffic sent on this socket.
* @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic.
*/ */
std::unique_ptr<deinit_t> 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) {
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; int sockfd = (int) native_socket;
std::vector<std::tuple<int, int, int>> reset_options; 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) { 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 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)); 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; BOOST_LOG(error) << "Failed to set SO_NET_SERVICE_TYPE: "sv << errno;
} }
} }
@ -501,8 +471,7 @@ namespace platf {
if (address.is_v6()) { if (address.is_v6()) {
level = IPPROTO_IPV6; level = IPPROTO_IPV6;
option = IPV6_TCLASS; option = IPV6_TCLASS;
} } else {
else {
level = IPPROTO_IP; level = IPPROTO_IP;
option = IP_TOS; option = IP_TOS;
} }
@ -529,8 +498,7 @@ namespace platf {
if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) == 0) { if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) == 0) {
// Reset TOS to -1 when QoS is disabled // Reset TOS to -1 when QoS is disabled
reset_options.emplace_back(std::make_tuple(level, option, -1)); reset_options.emplace_back(std::make_tuple(level, option, -1));
} } else {
else {
BOOST_LOG(error) << "Failed to set TOS/TCLASS: "sv << errno; 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); return std::make_unique<qos_t>(sockfd, reset_options);
} }
std::string std::string get_host_name() {
get_host_name() {
try { try {
return boost::asio::ip::host_name(); 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(); BOOST_LOG(error) << "Failed to get hostname: "sv << err.what();
return "Sunshine"s; return "Sunshine"s;
} }
@ -552,8 +518,7 @@ namespace platf {
class macos_high_precision_timer: public high_precision_timer { class macos_high_precision_timer: public high_precision_timer {
public: public:
void void sleep_for(const std::chrono::nanoseconds &duration) override {
sleep_for(const std::chrono::nanoseconds &duration) override {
std::this_thread::sleep_for(duration); std::this_thread::sleep_for(duration);
} }
@ -562,15 +527,13 @@ namespace platf {
} }
}; };
std::unique_ptr<high_precision_timer> std::unique_ptr<high_precision_timer> create_high_precision_timer() {
create_high_precision_timer() {
return std::make_unique<macos_high_precision_timer>(); return std::make_unique<macos_high_precision_timer>();
} }
} // namespace platf } // namespace platf
namespace dyn { namespace dyn {
void * void *handle(const std::vector<const char *> &libs) {
handle(const std::vector<const char *> &libs) {
void *handle; void *handle;
for (auto lib : libs) { for (auto lib : libs) {
@ -593,8 +556,7 @@ namespace dyn {
return nullptr; return nullptr;
} }
int int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
int err = 0; int err = 0;
for (auto &func : funcs) { for (auto &func : funcs) {
TUPLE_2D_REF(fn, name, func); TUPLE_2D_REF(fn, name, func);

View file

@ -2,11 +2,12 @@
* @file src/platform/macos/nv12_zero_device.cpp * @file src/platform/macos/nv12_zero_device.cpp
* @brief Definitions for NV12 zero copy device on macOS. * @brief Definitions for NV12 zero copy device on macOS.
*/ */
// standard includes
#include <utility> #include <utility>
// local includes
#include "src/platform/macos/av_img_t.h" #include "src/platform/macos/av_img_t.h"
#include "src/platform/macos/nv12_zero_device.h" #include "src/platform/macos/nv12_zero_device.h"
#include "src/video.h" #include "src/video.h"
extern "C" { extern "C" {
@ -15,20 +16,17 @@ extern "C" {
namespace platf { namespace platf {
void void free_frame(AVFrame *frame) {
free_frame(AVFrame *frame) {
av_frame_free(&frame); av_frame_free(&frame);
} }
void void free_buffer(void *opaque, uint8_t *data) {
free_buffer(void *opaque, uint8_t *data) {
CVPixelBufferRelease((CVPixelBufferRef) data); CVPixelBufferRelease((CVPixelBufferRef) data);
} }
util::safe_ptr<AVFrame, free_frame> av_frame; util::safe_ptr<AVFrame, free_frame> av_frame;
int int nv12_zero_device::convert(platf::img_t &img) {
nv12_zero_device::convert(platf::img_t &img) {
auto *av_img = (av_img_t *) &img; auto *av_img = (av_img_t *) &img;
// Release any existing CVPixelBuffer previously retained for encoding // Release any existing CVPixelBuffer previously retained for encoding
@ -47,8 +45,7 @@ namespace platf {
return 0; return 0;
} }
int int nv12_zero_device::set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
nv12_zero_device::set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
this->frame = frame; this->frame = frame;
av_frame.reset(frame); av_frame.reset(frame);
@ -58,11 +55,8 @@ namespace platf {
return 0; return 0;
} }
int 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) {
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);
pixel_format_fn(display, pix_fmt == pix_fmt_e::nv12 ?
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange :
kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange);
this->display = display; this->display = display;
this->resolution_fn = std::move(resolution_fn); this->resolution_fn = std::move(resolution_fn);

View file

@ -4,13 +4,13 @@
*/ */
#pragma once #pragma once
// local includes
#include "src/platform/common.h" #include "src/platform/common.h"
struct AVFrame; struct AVFrame;
namespace platf { namespace platf {
void void free_frame(AVFrame *frame);
free_frame(AVFrame *frame);
class nv12_zero_device: public avcodec_encode_device_t { class nv12_zero_device: public avcodec_encode_device_t {
// display holds a pointer to an av_video object. Since the namespaces of AVFoundation // 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; resolution_fn_t resolution_fn;
using pixel_format_fn_t = std::function<void(void *display, int pixelFormat)>; using pixel_format_fn_t = std::function<void(void *display, int pixelFormat)>;
int int init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, const pixel_format_fn_t &pixel_format_fn);
init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, const pixel_format_fn_t &pixel_format_fn);
int int convert(img_t &img) override;
convert(img_t &img) override; int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override;
int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override;
private: private:
util::safe_ptr<AVFrame, free_frame> av_frame; util::safe_ptr<AVFrame, free_frame> av_frame;

View file

@ -2,9 +2,13 @@
* @file src/platform/macos/publish.cpp * @file src/platform/macos/publish.cpp
* @brief Definitions for publishing services on macOS. * @brief Definitions for publishing services on macOS.
*/ */
#include <dns_sd.h> // standard includes
#include <thread> #include <thread>
// platform includes
#include <dns_sd.h>
// local includes
#include "src/logging.h" #include "src/logging.h"
#include "src/network.h" #include "src/network.h"
#include "src/nvhttp.h" #include "src/nvhttp.h"
@ -17,12 +21,13 @@ namespace platf::publish {
/** @brief Custom deleter intended to be used for `std::unique_ptr<DNSServiceRef>`. */ /** @brief Custom deleter intended to be used for `std::unique_ptr<DNSServiceRef>`. */
struct ServiceRefDeleter { struct ServiceRefDeleter {
typedef DNSServiceRef pointer; ///< Type of object to be deleted. typedef DNSServiceRef pointer; ///< Type of object to be deleted.
void
operator()(pointer serviceRef) { void operator()(pointer serviceRef) {
DNSServiceRefDeallocate(serviceRef); DNSServiceRefDeallocate(serviceRef);
BOOST_LOG(info) << "Deregistered DNS service."sv; BOOST_LOG(info) << "Deregistered DNS service."sv;
} }
}; };
/** @brief This class encapsulates the polling and deinitialization of our connection with /** @brief This class encapsulates the polling and deinitialization of our connection with
* the mDNS service. Implements the `::platf::deinit_t` interface. * the mDNS service. Implements the `::platf::deinit_t` interface.
*/ */
@ -48,14 +53,14 @@ namespace platf::publish {
if (ready == -1) { if (ready == -1) {
BOOST_LOG(error) << "Failed to obtain response from DNS service."sv; BOOST_LOG(error) << "Failed to obtain response from DNS service."sv;
break; break;
} } else if (ready != 0) {
else if (ready != 0) {
DNSServiceProcessResult(serviceRef); DNSServiceProcessResult(serviceRef);
break; break;
} }
} }
}}; }};
} }
/** @brief Ensure that we gracefully finish polling the mDNS service before freeing our /** @brief Ensure that we gracefully finish polling the mDNS service before freeing our
* connection to it. * connection to it.
*/ */
@ -63,9 +68,9 @@ namespace platf::publish {
_stopRequested = true; _stopRequested = true;
_thread.join(); _thread.join();
} }
deinit_t(const deinit_t &) = delete; deinit_t(const deinit_t &) = delete;
deinit_t & deinit_t &operator=(const deinit_t &) = delete;
operator=(const deinit_t &) = delete;
private: private:
std::thread _thread; ///< Thread for polling the mDNS service for a response. 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. /** @brief Callback that will be invoked when the mDNS service finishes registering our service.
* @param errorCode Describes whether the registration was successful. * @param errorCode Describes whether the registration was successful.
*/ */
void void registrationCallback(DNSServiceRef /*serviceRef*/, DNSServiceFlags /*flags*/, DNSServiceErrorType errorCode, const char * /*name*/, const char * /*regtype*/, const char * /*domain*/, void * /*context*/) {
registrationCallback(DNSServiceRef /*serviceRef*/, DNSServiceFlags /*flags*/,
DNSServiceErrorType errorCode, const char * /*name*/,
const char * /*regtype*/, const char * /*domain*/, void * /*context*/) {
if (errorCode != kDNSServiceErr_NoError) { if (errorCode != kDNSServiceErr_NoError) {
BOOST_LOG(error) << "Failed to register DNS service: Error "sv << errorCode; BOOST_LOG(error) << "Failed to register DNS service: Error "sv << errorCode;
return; return;
@ -98,8 +100,7 @@ namespace platf::publish {
* which will manage polling for a response from the mDNS service, and then, when * which will manage polling for a response from the mDNS service, and then, when
* deconstructed, will deregister the service. * deconstructed, will deregister the service.
*/ */
[[nodiscard]] std::unique_ptr<::platf::deinit_t> [[nodiscard]] std::unique_ptr<::platf::deinit_t> start() {
start() {
auto serviceRef = DNSServiceRef {}; auto serviceRef = DNSServiceRef {};
const auto status = DNSServiceRegister( const auto status = DNSServiceRegister(
&serviceRef, &serviceRef,

View file

@ -8,6 +8,7 @@
#pragma once #pragma once
// platform includes
#include <mmdeviceapi.h> #include <mmdeviceapi.h>
#ifdef __MINGW32__ #ifdef __MINGW32__
@ -40,64 +41,69 @@ class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
interface IPolicyConfig: public IUnknown { interface IPolicyConfig: public IUnknown {
public: public:
virtual HRESULT virtual HRESULT GetMixFormat(
GetMixFormat(
PCWSTR, PCWSTR,
WAVEFORMATEX **); WAVEFORMATEX **
);
virtual HRESULT STDMETHODCALLTYPE virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
GetDeviceFormat(
PCWSTR, PCWSTR,
INT, INT,
WAVEFORMATEX **); WAVEFORMATEX **
);
virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat( virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
PCWSTR); PCWSTR
);
virtual HRESULT STDMETHODCALLTYPE virtual HRESULT STDMETHODCALLTYPE
SetDeviceFormat( SetDeviceFormat(
PCWSTR, PCWSTR,
WAVEFORMATEX *, WAVEFORMATEX *,
WAVEFORMATEX *); WAVEFORMATEX *
);
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
PCWSTR, PCWSTR,
INT, INT,
PINT64, PINT64,
PINT64); PINT64
);
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
PCWSTR, PCWSTR,
PINT64); PINT64
);
virtual HRESULT STDMETHODCALLTYPE virtual HRESULT STDMETHODCALLTYPE GetShareMode(
GetShareMode(
PCWSTR, PCWSTR,
struct DeviceShareMode *); struct DeviceShareMode *
);
virtual HRESULT STDMETHODCALLTYPE virtual HRESULT STDMETHODCALLTYPE SetShareMode(
SetShareMode(
PCWSTR, PCWSTR,
struct DeviceShareMode *); struct DeviceShareMode *
);
virtual HRESULT STDMETHODCALLTYPE virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
GetPropertyValue(
PCWSTR, PCWSTR,
const PROPERTYKEY &, const PROPERTYKEY &,
PROPVARIANT *); PROPVARIANT *
);
virtual HRESULT STDMETHODCALLTYPE virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
SetPropertyValue(
PCWSTR, PCWSTR,
const PROPERTYKEY &, const PROPERTYKEY &,
PROPVARIANT *); PROPVARIANT *
);
virtual HRESULT STDMETHODCALLTYPE virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
SetDefaultEndpoint(
PCWSTR wszDeviceId, PCWSTR wszDeviceId,
ERole eRole); ERole eRole
);
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
PCWSTR, PCWSTR,
INT); INT
);
}; };

View file

@ -3,22 +3,21 @@
* @brief Definitions for Windows audio capture. * @brief Definitions for Windows audio capture.
*/ */
#define INITGUID #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 <synchapi.h>
#include <newdev.h> // local includes
#include "misc.h"
#include <avrt.h>
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "misc.h"
// Must be the last included file // Must be the last included file
// clang-format off // clang-format off
#include "PolicyConfig.h" #include "PolicyConfig.h"
@ -65,8 +64,7 @@ namespace {
_size, _size,
}; };
constexpr WAVEFORMATEXTENSIBLE constexpr WAVEFORMATEXTENSIBLE create_waveformat(sample_format_e sample_format, WORD channel_count, DWORD channel_mask) {
create_waveformat(sample_format_e sample_format, WORD channel_count, DWORD channel_mask) {
WAVEFORMATEXTENSIBLE waveformat = {}; WAVEFORMATEXTENSIBLE waveformat = {};
switch (sample_format) { switch (sample_format) {
@ -120,8 +118,7 @@ namespace {
using virtual_sink_waveformats_t = std::vector<WAVEFORMATEXTENSIBLE>; using virtual_sink_waveformats_t = std::vector<WAVEFORMATEXTENSIBLE>;
template<WORD channel_count> template<WORD channel_count>
virtual_sink_waveformats_t virtual_sink_waveformats_t create_virtual_sink_waveformats() {
create_virtual_sink_waveformats() {
if constexpr (channel_count == 2) { if constexpr (channel_count == 2) {
auto channel_mask = waveformat_mask_stereo; auto channel_mask = waveformat_mask_stereo;
// only choose 24 or 16-bit formats to avoid clobbering existing Dolby/DTS spatial audio settings // 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::s24, channel_count, channel_mask),
create_waveformat(sample_format_e::s16, 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_mask1 = waveformat_mask_surround51_with_backspeakers;
auto channel_mask2 = waveformat_mask_surround51_with_sidespeakers; auto channel_mask2 = waveformat_mask_surround51_with_sidespeakers;
return { 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_mask1),
create_waveformat(sample_format_e::s16, channel_count, channel_mask2), 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; auto channel_mask = waveformat_mask_surround71;
return { return {
create_waveformat(sample_format_e::f32, channel_count, channel_mask), create_waveformat(sample_format_e::f32, channel_count, channel_mask),
@ -159,8 +154,7 @@ namespace {
} }
} }
std::string std::string waveformat_to_pretty_string(const WAVEFORMATEXTENSIBLE &waveformat) {
waveformat_to_pretty_string(const WAVEFORMATEXTENSIBLE &waveformat) {
std::string result = waveformat.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ? "F" : std::string result = waveformat.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ? "F" :
waveformat.SubFormat == KSDATAFORMAT_SUBTYPE_PCM ? "S" : waveformat.SubFormat == KSDATAFORMAT_SUBTYPE_PCM ? "S" :
"UNKNOWN"; "UNKNOWN";
@ -196,16 +190,15 @@ namespace {
} // namespace } // namespace
using namespace std::literals; using namespace std::literals;
namespace platf::audio { namespace platf::audio {
template<class T> template<class T>
void void Release(T *p) {
Release(T *p) {
p->Release(); p->Release();
} }
template<class T> template<class T>
void void co_task_free(T *p) {
co_task_free(T *p) {
CoTaskMemFree((LPVOID) p); CoTaskMemFree((LPVOID) p);
} }
@ -272,14 +265,14 @@ namespace platf::audio {
}, },
}; };
audio_client_t audio_client_t make_audio_client(device_t &device, const format_t &format) {
make_audio_client(device_t &device, const format_t &format) {
audio_client_t audio_client; audio_client_t audio_client;
auto status = device->Activate( auto status = device->Activate(
IID_IAudioClient, IID_IAudioClient,
CLSCTX_ALL, CLSCTX_ALL,
nullptr, nullptr,
(void **) &audio_client); (void **) &audio_client
);
if (FAILED(status)) { if (FAILED(status)) {
BOOST_LOG(error) << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']'; 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_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, // Enable automatic resampling to 48 KHz AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, // Enable automatic resampling to 48 KHz
0, 0, 0,
0,
(LPWAVEFORMATEX) &capture_waveformat, (LPWAVEFORMATEX) &capture_waveformat,
nullptr); nullptr
);
if (status) { if (status) {
BOOST_LOG(error) << "Couldn't initialize audio client for ["sv << format.name << "]: [0x"sv << util::hex(status).to_string_view() << ']'; 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; return audio_client;
} }
device_t device_t default_device(device_enum_t &device_enum) {
default_device(device_enum_t &device_enum) {
device_t device; device_t device;
HRESULT status; HRESULT status;
status = device_enum->GetDefaultAudioEndpoint( status = device_enum->GetDefaultAudioEndpoint(
eRender, eRender,
eConsole, eConsole,
&device); &device
);
if (FAILED(status)) { if (FAILED(status)) {
BOOST_LOG(error) << "Couldn't get default audio endpoint [0x"sv << util::hex(status).to_string_view() << ']'; 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 { class audio_notification_t: public ::IMMNotificationClient {
public: public:
audio_notification_t() {} audio_notification_t() {
}
// IUnknown implementation (unused by IMMDeviceEnumerator) // IUnknown implementation (unused by IMMDeviceEnumerator)
ULONG STDMETHODCALLTYPE ULONG STDMETHODCALLTYPE AddRef() {
AddRef() {
return 1; return 1;
} }
ULONG STDMETHODCALLTYPE ULONG STDMETHODCALLTYPE Release() {
Release() {
return 1; return 1;
} }
HRESULT STDMETHODCALLTYPE HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) {
QueryInterface(REFIID riid, VOID **ppvInterface) {
if (IID_IUnknown == riid) { if (IID_IUnknown == riid) {
AddRef(); AddRef();
*ppvInterface = (IUnknown *) this; *ppvInterface = (IUnknown *) this;
return S_OK; return S_OK;
} } else if (__uuidof(IMMNotificationClient) == riid) {
else if (__uuidof(IMMNotificationClient) == riid) {
AddRef(); AddRef();
*ppvInterface = (IMMNotificationClient *) this; *ppvInterface = (IMMNotificationClient *) this;
return S_OK; return S_OK;
} } else {
else {
*ppvInterface = NULL; *ppvInterface = NULL;
return E_NOINTERFACE; return E_NOINTERFACE;
} }
} }
// IMMNotificationClient // IMMNotificationClient
HRESULT STDMETHODCALLTYPE HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) {
OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) {
if (flow == eRender) { if (flow == eRender) {
default_render_device_changed_flag.store(true); default_render_device_changed_flag.store(true);
} }
return S_OK; return S_OK;
} }
HRESULT STDMETHODCALLTYPE HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) {
OnDeviceAdded(LPCWSTR pwstrDeviceId) { return S_OK; } return S_OK;
}
HRESULT STDMETHODCALLTYPE HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) {
OnDeviceRemoved(LPCWSTR pwstrDeviceId) { return S_OK; } return S_OK;
}
HRESULT STDMETHODCALLTYPE HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(
OnDeviceStateChanged(
LPCWSTR pwstrDeviceId, LPCWSTR pwstrDeviceId,
DWORD dwNewState) { return S_OK; } DWORD dwNewState
) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(
OnPropertyValueChanged(
LPCWSTR pwstrDeviceId, 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 * @brief Checks if the default rendering device changed and resets the change flag
* @return `true` if the device changed since last call * @return `true` if the device changed since last call
*/ */
bool bool check_default_render_device_changed() {
check_default_render_device_changed() {
return default_render_device_changed_flag.exchange(false); return default_render_device_changed_flag.exchange(false);
} }
@ -416,8 +411,7 @@ namespace platf::audio {
class mic_wasapi_t: public mic_t { class mic_wasapi_t: public mic_t {
public: public:
capture_e capture_e sample(std::vector<float> &sample_out) override {
sample(std::vector<float> &sample_out) override {
auto sample_size = sample_out.size(); auto sample_size = sample_out.size();
// Refill the sample buffer if needed // Refill the sample buffer if needed
@ -438,8 +432,7 @@ namespace platf::audio {
return capture_e::ok; return capture_e::ok;
} }
int int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out) {
init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out) {
audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr)); audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr));
if (!audio_event) { if (!audio_event) {
BOOST_LOG(error) << "Couldn't create Event handle"sv; BOOST_LOG(error) << "Couldn't create Event handle"sv;
@ -454,7 +447,8 @@ namespace platf::audio {
nullptr, nullptr,
CLSCTX_ALL, CLSCTX_ALL,
IID_IMMDeviceEnumerator, IID_IMMDeviceEnumerator,
(void **) &device_enum); (void **) &device_enum
);
if (FAILED(status)) { if (FAILED(status)) {
BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']';
@ -559,8 +553,7 @@ namespace platf::audio {
} }
private: private:
capture_e capture_e _fill_buffer() {
_fill_buffer() {
HRESULT status; HRESULT status;
// Total number of samples // Total number of samples
@ -600,13 +593,16 @@ namespace platf::audio {
for ( for (
status = audio_capture->GetNextPacketSize(&packet_size); status = audio_capture->GetNextPacketSize(&packet_size);
SUCCEEDED(status) && packet_size > 0; SUCCEEDED(status) && packet_size > 0;
status = audio_capture->GetNextPacketSize(&packet_size)) { status = audio_capture->GetNextPacketSize(&packet_size)
) {
DWORD buffer_flags; DWORD buffer_flags;
status = audio_capture->GetBuffer( status = audio_capture->GetBuffer(
(BYTE **) &sample_aligned.samples, (BYTE **) &sample_aligned.samples,
&block_aligned.audio_sample_size, &block_aligned.audio_sample_size,
&buffer_flags, &buffer_flags,
nullptr, nullptr); nullptr,
nullptr
);
switch (status) { switch (status) {
case S_OK: case S_OK:
@ -631,8 +627,7 @@ namespace platf::audio {
if (buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { if (buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) {
std::fill_n(sample_buf_pos, n, 0); std::fill_n(sample_buf_pos, n, 0);
} } else {
else {
std::copy_n(sample_aligned.samples, n, sample_buf_pos); 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 { class audio_control_t: public ::platf::audio_control_t {
public: public:
std::optional<sink_t> std::optional<sink_t> sink_info() override {
sink_info() override {
sink_t sink; sink_t sink;
// Fill host sink name with the device_id of the current default audio device. // 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; match_fields_list_t match_list;
if (config::audio.virtual_sink.empty()) { if (config::audio.virtual_sink.empty()) {
match_list = match_steam_speakers(); match_list = match_steam_speakers();
} } else {
else {
match_list = match_all_fields(from_utf8(config::audio.virtual_sink)); 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[1].name + device_id,
"virtual-"s + formats[2].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; BOOST_LOG(warning) << "Couldn't find the specified virtual audio sink " << config::audio.virtual_sink;
} }
return sink; return sink;
} }
bool bool is_sink_available(const std::string &sink) override {
is_sink_available(const std::string &sink) override {
const auto match_list = match_all_fields(from_utf8(sink)); const auto match_list = match_all_fields(from_utf8(sink));
const auto matched = find_device_id(match_list); const auto matched = find_device_id(match_list);
return static_cast<bool>(matched); 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 * @return A pair of device_id and format reference if the sink name matches
* our naming scheme for virtual audio sinks, `std::nullopt` otherwise. * our naming scheme for virtual audio sinks, `std::nullopt` otherwise.
*/ */
std::optional<std::pair<std::wstring, std::reference_wrapper<const format_t>>> std::optional<std::pair<std::wstring, std::reference_wrapper<const format_t>>> extract_virtual_sink_info(const std::string &sink) {
extract_virtual_sink_info(const std::string &sink) {
// Encoding format: // Encoding format:
// [virtual-(format name)]device_id // [virtual-(format name)]device_id
std::string current = sink; std::string current = sink;
@ -756,8 +746,7 @@ namespace platf::audio {
return std::nullopt; return std::nullopt;
} }
std::unique_ptr<mic_t> std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
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>(); auto mic = std::make_unique<mic_wasapi_t>();
if (mic->init(sample_rate, frame_size, channels)) { if (mic->init(sample_rate, frame_size, channels)) {
@ -784,8 +773,7 @@ namespace platf::audio {
* virtual-(format name) * virtual-(format name)
* If it doesn't contain that prefix, then the format will not be changed * If it doesn't contain that prefix, then the format will not be changed
*/ */
std::optional<std::wstring> std::optional<std::wstring> set_format(const std::string &sink) {
set_format(const std::string &sink) {
if (sink.empty()) { if (sink.empty()) {
return std::nullopt; return std::nullopt;
} }
@ -799,8 +787,7 @@ namespace platf::audio {
auto matched = find_device_id(match_all_fields(from_utf8(sink))); auto matched = find_device_id(match_all_fields(from_utf8(sink)));
if (matched) { if (matched) {
return matched->second; return matched->second;
} } else {
else {
BOOST_LOG(error) << "Couldn't find audio sink " << sink; BOOST_LOG(error) << "Couldn't find audio sink " << sink;
return std::nullopt; return std::nullopt;
} }
@ -826,8 +813,7 @@ namespace platf::audio {
return std::nullopt; return std::nullopt;
} }
int int set_sink(const std::string &sink) override {
set_sink(const std::string &sink) override {
auto device_id = set_format(sink); auto device_id = set_format(sink);
if (!device_id) { if (!device_id) {
return -1; return -1;
@ -840,8 +826,7 @@ namespace platf::audio {
// Depending on the format of the string, we could get either of these errors // 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) { if (status == HRESULT_FROM_WIN32(ERROR_NOT_FOUND) || status == E_INVALIDARG) {
BOOST_LOG(warning) << "Audio sink not found: "sv << sink; 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(); BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << "]: 0x"sv << util::hex(status).to_string_view();
} }
@ -868,15 +853,13 @@ namespace platf::audio {
using match_fields_list_t = std::vector<std::pair<match_field_e, std::wstring>>; 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>; using matched_field_t = std::pair<match_field_e, std::wstring>;
audio_control_t::match_fields_list_t audio_control_t::match_fields_list_t match_steam_speakers() {
match_steam_speakers() {
return { 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 audio_control_t::match_fields_list_t match_all_fields(const std::wstring &name) {
match_all_fields(const std::wstring &name) {
return { return {
{match_field_e::device_id, name}, // {0.0.0.00000000}.{29dd7668-45b2-4846-882d-950f55bf7eb8} {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_friendly_name, name}, // Digital Audio (S/PDIF) (High Definition Audio Device)
@ -890,8 +873,7 @@ namespace platf::audio {
* @param match_list Pairs of match fields and values * @param match_list Pairs of match fields and values
* @return Optional pair of matched field and device_id * @return Optional pair of matched field and device_id
*/ */
std::optional<matched_field_t> std::optional<matched_field_t> find_device_id(const match_fields_list_t &match_list) {
find_device_id(const match_fields_list_t &match_list) {
if (match_list.empty()) { if (match_list.empty()) {
return std::nullopt; return std::nullopt;
} }
@ -965,8 +947,7 @@ namespace platf::audio {
/** /**
* @brief Resets the default audio device from Steam Streaming Speakers. * @brief Resets the default audio device from Steam Streaming Speakers.
*/ */
void void reset_default_device() {
reset_default_device() {
auto matched_steam = find_device_id(match_steam_speakers()); auto matched_steam = find_device_id(match_steam_speakers());
if (!matched_steam) { if (!matched_steam) {
return; return;
@ -1027,8 +1008,7 @@ namespace platf::audio {
* @brief Installs the Steam Streaming Speakers driver, if present. * @brief Installs the Steam Streaming Speakers driver, if present.
* @return `true` if installation was successful. * @return `true` if installation was successful.
*/ */
bool bool install_steam_audio_drivers() {
install_steam_audio_drivers() {
#ifdef STEAM_DRIVER_SUBDIR #ifdef STEAM_DRIVER_SUBDIR
// MinGW's libnewdev.a is missing DiInstallDriverW() even though the headers have it, // 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. // 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; return true;
} } else {
else {
auto err = GetLastError(); auto err = GetLastError();
switch (err) { switch (err) {
case ERROR_ACCESS_DENIED: case ERROR_ACCESS_DENIED:
@ -1096,14 +1075,14 @@ namespace platf::audio {
#endif #endif
} }
int int init() {
init() {
auto status = CoCreateInstance( auto status = CoCreateInstance(
CLSID_CPolicyConfigClient, CLSID_CPolicyConfigClient,
nullptr, nullptr,
CLSCTX_ALL, CLSCTX_ALL,
IID_IPolicyConfig, IID_IPolicyConfig,
(void **) &policy); (void **) &policy
);
if (FAILED(status)) { if (FAILED(status)) {
BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']'; 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, nullptr,
CLSCTX_ALL, CLSCTX_ALL,
IID_IMMDeviceEnumerator, IID_IMMDeviceEnumerator,
(void **) &device_enum); (void **) &device_enum
);
if (FAILED(status)) { if (FAILED(status)) {
BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']'; 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; return 0;
} }
~audio_control_t() override {} ~audio_control_t() override {
}
policy_t policy; policy_t policy;
audio::device_enum_t device_enum; audio::device_enum_t device_enum;
@ -1138,12 +1119,10 @@ namespace platf {
// It's not big enough to justify it's own source file :/ // It's not big enough to justify it's own source file :/
namespace dxgi { namespace dxgi {
int int init();
init();
} }
std::unique_ptr<audio_control_t> std::unique_ptr<audio_control_t> audio_control() {
audio_control() {
auto control = std::make_unique<audio::audio_control_t>(); auto control = std::make_unique<audio::audio_control_t>();
if (control->init()) { if (control->init()) {
@ -1160,8 +1139,7 @@ namespace platf {
return control; return control;
} }
std::unique_ptr<deinit_t> std::unique_ptr<deinit_t> init() {
init() {
if (dxgi::init()) { if (dxgi::init()) {
return nullptr; return nullptr;
} }

View file

@ -4,16 +4,17 @@
*/ */
#pragma once #pragma once
// platform includes
#include <d3d11.h> #include <d3d11.h>
#include <d3d11_4.h> #include <d3d11_4.h>
#include <d3dcommon.h> #include <d3dcommon.h>
#include <dwmapi.h> #include <dwmapi.h>
#include <dxgi.h> #include <dxgi.h>
#include <dxgi1_6.h> #include <dxgi1_6.h>
#include <Unknwn.h> #include <Unknwn.h>
#include <winrt/Windows.Graphics.Capture.h> #include <winrt/Windows.Graphics.Capture.h>
// local includes
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/utility.h" #include "src/utility.h"
#include "src/video.h" #include "src/video.h"
@ -26,8 +27,7 @@ namespace platf::dxgi {
auto constexpr D3D11_CREATE_DEVICE_FLAGS = 0; auto constexpr D3D11_CREATE_DEVICE_FLAGS = 0;
template<class T> template<class T>
void void Release(T *dxgi) {
Release(T *dxgi) {
dxgi->Release(); dxgi->Release();
} }
@ -72,6 +72,7 @@ namespace platf::dxgi {
} // namespace video } // namespace video
class hwdevice_t; class hwdevice_t;
struct cursor_t { struct cursor_t {
std::vector<std::uint8_t> img_data; std::vector<std::uint8_t> img_data;
@ -85,8 +86,7 @@ namespace platf::dxgi {
gpu_cursor_t(): gpu_cursor_t():
cursor_view {0, 0, 0, 0, 0.0f, 1.0f} {}; cursor_view {0, 0, 0, 0, 0.0f, 1.0f} {};
void void set_pos(LONG topleft_x, LONG topleft_y, LONG display_width, LONG display_height, DXGI_MODE_ROTATION display_rotation, bool visible) {
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_x = topleft_x;
this->topleft_y = topleft_y; this->topleft_y = topleft_y;
this->display_width = display_width; this->display_width = display_width;
@ -96,16 +96,14 @@ namespace platf::dxgi {
update_viewport(); update_viewport();
} }
void void set_texture(LONG texture_width, LONG texture_height, texture2d_t &&texture) {
set_texture(LONG texture_width, LONG texture_height, texture2d_t &&texture) {
this->texture = std::move(texture); this->texture = std::move(texture);
this->texture_width = texture_width; this->texture_width = texture_width;
this->texture_height = texture_height; this->texture_height = texture_height;
update_viewport(); update_viewport();
} }
void void update_viewport() {
update_viewport() {
switch (display_rotation) { switch (display_rotation) {
case DXGI_MODE_ROTATION_UNSPECIFIED: case DXGI_MODE_ROTATION_UNSPECIFIED:
case DXGI_MODE_ROTATION_IDENTITY: case DXGI_MODE_ROTATION_IDENTITY:
@ -158,11 +156,9 @@ namespace platf::dxgi {
class display_base_t: public display_t { class display_base_t: public display_t {
public: public:
int int init(const ::video::config_t &config, const std::string &display_name);
init(const ::video::config_t &config, const std::string &display_name);
capture_e 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(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; factory1_t factory;
adapter_t adapter; adapter_t adapter;
@ -209,6 +205,7 @@ namespace platf::dxgi {
UINT IndependentVidPnVSyncControl : 1; UINT IndependentVidPnVSyncControl : 1;
UINT Reserved : 28; UINT Reserved : 28;
}; };
UINT Value; UINT Value;
}; };
} D3DKMT_WDDM_2_7_CAPS; } D3DKMT_WDDM_2_7_CAPS;
@ -231,30 +228,21 @@ namespace platf::dxgi {
typedef NTSTATUS(WINAPI *PD3DKMTQueryAdapterInfo)(D3DKMT_QUERYADAPTERINFO *); typedef NTSTATUS(WINAPI *PD3DKMTQueryAdapterInfo)(D3DKMT_QUERYADAPTERINFO *);
typedef NTSTATUS(WINAPI *PD3DKMTCloseAdapter)(D3DKMT_CLOSEADAPTER *); typedef NTSTATUS(WINAPI *PD3DKMTCloseAdapter)(D3DKMT_CLOSEADAPTER *);
virtual bool virtual bool is_hdr() override;
is_hdr() override; virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) override;
virtual bool
get_hdr_metadata(SS_HDR_METADATA &metadata) override;
const char * const char *dxgi_format_to_string(DXGI_FORMAT format);
dxgi_format_to_string(DXGI_FORMAT format); const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
const char * virtual std::vector<DXGI_FORMAT> get_supported_capture_formats() = 0;
colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
virtual std::vector<DXGI_FORMAT>
get_supported_capture_formats() = 0;
protected: protected:
int int get_pixel_pitch() {
get_pixel_pitch() {
return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4; return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4;
} }
virtual capture_e 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;
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 capture_e virtual int complete_img(img_t *img, bool dummy) = 0;
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 { class display_ram_t: public display_base_t {
public: public:
std::shared_ptr<img_t> std::shared_ptr<img_t> alloc_img() override;
alloc_img() override; int dummy_img(img_t *img) override;
int int complete_img(img_t *img, bool dummy) override;
dummy_img(img_t *img) override; std::vector<DXGI_FORMAT> get_supported_capture_formats() 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> std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
D3D11_MAPPED_SUBRESOURCE img_info; D3D11_MAPPED_SUBRESOURCE img_info;
texture2d_t texture; 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> { class display_vram_t: public display_base_t, public std::enable_shared_from_this<display_vram_t> {
public: public:
std::shared_ptr<img_t> std::shared_ptr<img_t> alloc_img() override;
alloc_img() override; int dummy_img(img_t *img_base) override;
int int complete_img(img_t *img_base, bool dummy) override;
dummy_img(img_t *img_base) override; std::vector<DXGI_FORMAT> get_supported_capture_formats() override;
int
complete_img(img_t *img_base, bool dummy) override;
std::vector<DXGI_FORMAT>
get_supported_capture_formats() override;
bool bool is_codec_supported(std::string_view name, const ::video::config_t &config) override;
is_codec_supported(std::string_view name, const ::video::config_t &config) override;
std::unique_ptr<avcodec_encode_device_t> std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
std::unique_ptr<nvenc_encode_device_t> std::unique_ptr<nvenc_encode_device_t> make_nvenc_encode_device(pix_fmt_e pix_fmt) override;
make_nvenc_encode_device(pix_fmt_e pix_fmt) override;
std::atomic<uint32_t> next_image_id; std::atomic<uint32_t> next_image_id;
}; };
@ -313,14 +289,10 @@ namespace platf::dxgi {
bool has_frame {}; bool has_frame {};
std::chrono::steady_clock::time_point last_protected_content_warning_time {}; std::chrono::steady_clock::time_point last_protected_content_warning_time {};
int int init(display_base_t *display, const ::video::config_t &config);
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 capture_e reset(dup_t::pointer dup_p = dup_t::pointer());
next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); capture_e release_frame();
capture_e
reset(dup_t::pointer dup_p = dup_t::pointer());
capture_e
release_frame();
~duplication_t(); ~duplication_t();
}; };
@ -330,12 +302,9 @@ namespace platf::dxgi {
*/ */
class display_ddup_ram_t: public display_ram_t { class display_ddup_ram_t: public display_ram_t {
public: public:
int int init(const ::video::config_t &config, const std::string &display_name);
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 capture_e release_snapshot() override;
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; duplication_t dup;
cursor_t cursor; cursor_t cursor;
@ -346,12 +315,9 @@ namespace platf::dxgi {
*/ */
class display_ddup_vram_t: public display_vram_t { class display_ddup_vram_t: public display_vram_t {
public: public:
int int init(const ::video::config_t &config, const std::string &display_name);
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 capture_e release_snapshot() override;
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; duplication_t dup;
sampler_state_t sampler_linear; sampler_state_t sampler_linear;
@ -383,21 +349,16 @@ namespace platf::dxgi {
SRWLOCK frame_lock = SRWLOCK_INIT; SRWLOCK frame_lock = SRWLOCK_INIT;
CONDITION_VARIABLE frame_present_cv; CONDITION_VARIABLE frame_present_cv;
void void on_frame_arrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, winrt::Windows::Foundation::IInspectable const &);
on_frame_arrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, winrt::Windows::Foundation::IInspectable const &);
public: public:
wgc_capture_t(); wgc_capture_t();
~wgc_capture_t(); ~wgc_capture_t();
int int init(display_base_t *display, const ::video::config_t &config);
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 capture_e release_frame();
next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time); int set_cursor_visible(bool);
capture_e
release_frame();
int
set_cursor_visible(bool);
}; };
/** /**
@ -407,12 +368,9 @@ namespace platf::dxgi {
wgc_capture_t dup; wgc_capture_t dup;
public: public:
int int init(const ::video::config_t &config, const std::string &display_name);
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 capture_e release_snapshot() override;
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; wgc_capture_t dup;
public: public:
int int init(const ::video::config_t &config, const std::string &display_name);
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 capture_e release_snapshot() override;
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 } // namespace platf::dxgi

View file

@ -2,13 +2,16 @@
* @file src/platform/windows/display_base.cpp * @file src/platform/windows/display_base.cpp
* @brief Definitions for the Windows display base code. * @brief Definitions for the Windows display base code.
*/ */
// standard includes
#include <cmath> #include <cmath>
#include <initguid.h>
#include <thread> #include <thread>
// platform includes
#include <initguid.h>
// lib includes
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
#include <boost/process/v1.hpp> #include <boost/process/v1.hpp>
#include <MinHook.h> #include <MinHook.h>
// We have to include boost/process/v1.hpp before display.h due to WinSock.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 { namespace platf {
using namespace std::literals; using namespace std::literals;
} }
namespace platf::dxgi { namespace platf::dxgi {
namespace bp = boost::process; namespace bp = boost::process;
/** /**
* DDAPI-specific initialization goes here. * DDAPI-specific initialization goes here.
*/ */
int int duplication_t::init(display_base_t *display, const ::video::config_t &config) {
duplication_t::init(display_base_t *display, const ::video::config_t &config) {
HRESULT status; HRESULT status;
// Capture format will be determined from the first call to AcquireNextFrame() // 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() << ']'; BOOST_LOG(warning) << "DuplicateOutput1 Failed [0x"sv << util::hex(status).to_string_view() << ']';
return -1; return -1;
} }
} } else {
else {
BOOST_LOG(warning) << "IDXGIOutput5 is not supported by your OS. Capture performance may be reduced."sv; BOOST_LOG(warning) << "IDXGIOutput5 is not supported by your OS. Capture performance may be reduced."sv;
dxgi::output1_t output1 {}; dxgi::output1_t output1 {};
@ -124,8 +126,7 @@ namespace platf::dxgi {
return 0; return 0;
} }
capture_e capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p) {
duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p) {
auto capture_status = release_frame(); auto capture_status = release_frame();
if (capture_status != capture_e::ok) { if (capture_status != capture_e::ok) {
return capture_status; return capture_status;
@ -157,8 +158,7 @@ namespace platf::dxgi {
} }
} }
capture_e capture_e duplication_t::reset(dup_t::pointer dup_p) {
duplication_t::reset(dup_t::pointer dup_p) {
auto capture_status = release_frame(); auto capture_status = release_frame();
dup.reset(dup_p); dup.reset(dup_p);
@ -166,8 +166,7 @@ namespace platf::dxgi {
return capture_status; return capture_status;
} }
capture_e capture_e duplication_t::release_frame() {
duplication_t::release_frame() {
if (!has_frame) { if (!has_frame) {
return capture_e::ok; return capture_e::ok;
} }
@ -195,16 +194,14 @@ namespace platf::dxgi {
release_frame(); release_frame();
} }
capture_e 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) {
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 { auto adjust_client_frame_rate = [&]() -> DXGI_RATIONAL {
// Adjust capture frame interval when display refresh rate is not integral but very close to requested fps. // Adjust capture frame interval when display refresh rate is not integral but very close to requested fps.
if (display_refresh_rate.Denominator > 1) { if (display_refresh_rate.Denominator > 1) {
DXGI_RATIONAL candidate = display_refresh_rate; DXGI_RATIONAL candidate = display_refresh_rate;
if (client_frame_rate % display_refresh_rate_rounded == 0) { if (client_frame_rate % display_refresh_rate_rounded == 0) {
candidate.Numerator *= client_frame_rate / display_refresh_rate_rounded; 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; candidate.Denominator *= display_refresh_rate_rounded / client_frame_rate;
} }
double candidate_rate = (double) candidate.Numerator / candidate.Denominator; double candidate_rate = (double) candidate.Numerator / candidate.Denominator;
@ -258,8 +255,7 @@ namespace platf::dxgi {
frame_pacing_group_start = std::nullopt; frame_pacing_group_start = std::nullopt;
frame_pacing_group_frames = 0; frame_pacing_group_frames = 0;
status = capture_e::timeout; status = capture_e::timeout;
} } else {
else {
timer->sleep_for(sleep_period); timer->sleep_for(sleep_period);
sleep_overshoot_logger.first_point(sleep_target); sleep_overshoot_logger.first_point(sleep_target);
sleep_overshoot_logger.second_point_now_and_log(); sleep_overshoot_logger.second_point_now_and_log();
@ -268,8 +264,7 @@ namespace platf::dxgi {
if (status == capture_e::ok && img_out) { if (status == capture_e::ok && img_out) {
frame_pacing_group_frames += 1; frame_pacing_group_frames += 1;
} } else {
else {
frame_pacing_group_start = std::nullopt; frame_pacing_group_start = std::nullopt;
frame_pacing_group_frames = 0; frame_pacing_group_frames = 0;
} }
@ -289,8 +284,7 @@ namespace platf::dxgi {
} }
frame_pacing_group_frames = 1; 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 // The D3D11 device is protected by an unfair lock that is held the entire time that
// IDXGIOutputDuplication::AcquireNextFrame() is running. This is normally harmless, // IDXGIOutputDuplication::AcquireNextFrame() is running. This is normally harmless,
// however sometimes the encoding thread needs to interact with our ID3D11Device to // 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 output The DXGI output to capture.
* @param enumeration_only Specifies whether this test is occurring for display enumeration. * @param enumeration_only Specifies whether this test is occurring for display enumeration.
*/ */
bool bool test_dxgi_duplication(adapter_t &adapter, output_t &output, bool enumeration_only) {
test_dxgi_duplication(adapter_t &adapter, output_t &output, bool enumeration_only) {
D3D_FEATURE_LEVEL featureLevels[] { D3D_FEATURE_LEVEL featureLevels[] {
D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_0,
@ -366,11 +359,13 @@ namespace platf::dxgi {
D3D_DRIVER_TYPE_UNKNOWN, D3D_DRIVER_TYPE_UNKNOWN,
nullptr, nullptr,
D3D11_CREATE_DEVICE_FLAGS, D3D11_CREATE_DEVICE_FLAGS,
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL), featureLevels,
sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
D3D11_SDK_VERSION, D3D11_SDK_VERSION,
&device, &device,
nullptr, nullptr,
nullptr); nullptr
);
if (FAILED(status)) { if (FAILED(status)) {
BOOST_LOG(error) << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']';
return false; return false;
@ -403,8 +398,7 @@ namespace platf::dxgi {
// capture the current desktop, just bail immediately. Retrying won't help. // capture the current desktop, just bail immediately. Retrying won't help.
if (enumeration_only && status == E_ACCESSDENIED) { if (enumeration_only && status == E_ACCESSDENIED) {
break; break;
} } else {
else {
std::this_thread::sleep_for(200ms); 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. * @param gpuPreference A pointer to the location where the preference will be written.
* @return Always STATUS_SUCCESS if valid arguments are provided. * @return Always STATUS_SUCCESS if valid arguments are provided.
*/ */
NTSTATUS NTSTATUS __stdcall NtGdiDdDDIGetCachedHybridQueryValueHook(D3DKMT_GPU_PREFERENCE_QUERY_STATE *gpuPreference) {
__stdcall NtGdiDdDDIGetCachedHybridQueryValueHook(D3DKMT_GPU_PREFERENCE_QUERY_STATE *gpuPreference) {
// By faking a cached GPU preference state of D3DKMT_GPU_PREFERENCE_STATE_UNSPECIFIED, this will // 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, // 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 // 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) { if (gpuPreference) {
*gpuPreference = D3DKMT_GPU_PREFERENCE_STATE_UNSPECIFIED; *gpuPreference = D3DKMT_GPU_PREFERENCE_STATE_UNSPECIFIED;
return 0; // STATUS_SUCCESS return 0; // STATUS_SUCCESS
} } else {
else {
return STATUS_INVALID_PARAMETER; return STATUS_INVALID_PARAMETER;
} }
} }
int int display_base_t::init(const ::video::config_t &config, const std::string &display_name) {
display_base_t::init(const ::video::config_t &config, const std::string &display_name) {
std::once_flag windows_cpp_once_flag; std::once_flag windows_cpp_once_flag;
std::call_once(windows_cpp_once_flag, []() { std::call_once(windows_cpp_once_flag, []() {
@ -512,8 +503,7 @@ namespace platf::dxgi {
display_rotation == DXGI_MODE_ROTATION_ROTATE270) { display_rotation == DXGI_MODE_ROTATION_ROTATE270) {
width_before_rotation = height; width_before_rotation = height;
height_before_rotation = width; height_before_rotation = width;
} } else {
else {
width_before_rotation = width; width_before_rotation = width;
height_before_rotation = height; height_before_rotation = height;
} }
@ -570,11 +560,13 @@ namespace platf::dxgi {
D3D_DRIVER_TYPE_UNKNOWN, D3D_DRIVER_TYPE_UNKNOWN,
nullptr, nullptr,
D3D11_CREATE_DEVICE_FLAGS, D3D11_CREATE_DEVICE_FLAGS,
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL), featureLevels,
sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
D3D11_SDK_VERSION, D3D11_SDK_VERSION,
&device, &device,
&feature_level, &feature_level,
&device_ctx); &device_ctx
);
adapter_p->Release(); adapter_p->Release();
@ -649,8 +641,7 @@ namespace platf::dxgi {
if (SUCCEEDED(d3dkmt_query_adapter_info(&d3dkmt_adapter_info))) { if (SUCCEEDED(d3dkmt_query_adapter_info(&d3dkmt_adapter_info))) {
result = d3dkmt_adapter_caps.HwSchEnabled; result = d3dkmt_adapter_caps.HwSchEnabled;
} } else {
else {
BOOST_LOG(warning) << "D3DKMTQueryAdapterInfo() failed while trying to determine GPU HAGS status"; BOOST_LOG(warning) << "D3DKMTQueryAdapterInfo() failed while trying to determine GPU HAGS status";
result = false; result = false;
} }
@ -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 // 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 // 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 // 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) << "Active GPU has HAGS " << (hags_enabled ? "enabled" : "disabled");
BOOST_LOG(info) << "Using " << (priority == D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH ? "high" : "realtime") << " GPU priority"; BOOST_LOG(info) << "Using " << (priority == D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH ? "high" : "realtime") << " GPU priority";
if (FAILED(d3dkmt_set_process_priority(GetCurrentProcess(), 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."; 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"; BOOST_LOG(error) << "Couldn't load D3DKMTSetProcessSchedulingPriorityClass function from gdi32.dll to adjust GPU priority";
} }
} }
@ -740,8 +732,7 @@ namespace platf::dxgi {
return 0; return 0;
} }
bool bool display_base_t::is_hdr() {
display_base_t::is_hdr() {
dxgi::output6_t output6 {}; dxgi::output6_t output6 {};
auto status = output->QueryInterface(IID_IDXGIOutput6, (void **) &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; return desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
} }
bool bool display_base_t::get_hdr_metadata(SS_HDR_METADATA &metadata) {
display_base_t::get_hdr_metadata(SS_HDR_METADATA &metadata) {
dxgi::output6_t output6 {}; dxgi::output6_t output6 {};
std::memset(&metadata, 0, sizeof(metadata)); std::memset(&metadata, 0, sizeof(metadata));
@ -928,20 +918,31 @@ namespace platf::dxgi {
"DXGI_FORMAT_A8P8", "DXGI_FORMAT_A8P8",
"DXGI_FORMAT_B4G4R4A4_UNORM", "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_P208",
"DXGI_FORMAT_V208", "DXGI_FORMAT_V208",
"DXGI_FORMAT_V408" "DXGI_FORMAT_V408"
}; };
const char * const char *display_base_t::dxgi_format_to_string(DXGI_FORMAT format) {
display_base_t::dxgi_format_to_string(DXGI_FORMAT format) {
return format_str[format]; return format_str[format];
} }
const char * const char *display_base_t::colorspace_to_string(DXGI_COLOR_SPACE_TYPE type) {
display_base_t::colorspace_to_string(DXGI_COLOR_SPACE_TYPE type) {
const char *type_str[] = { const char *type_str[] = {
"DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709", "DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709",
"DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709", "DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709",
@ -972,8 +973,7 @@ namespace platf::dxgi {
if (type < ARRAYSIZE(type_str)) { if (type < ARRAYSIZE(type_str)) {
return type_str[type]; return type_str[type];
} } else {
else {
return "UNKNOWN"; return "UNKNOWN";
} }
} }
@ -985,8 +985,7 @@ namespace platf {
* Pick a display adapter and capture method. * Pick a display adapter and capture method.
* @param hwdevice_type enables possible use of hardware encoder * @param hwdevice_type enables possible use of hardware encoder
*/ */
std::shared_ptr<display_t> std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
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 (config::video.capture == "ddx" || config::video.capture.empty()) {
if (hwdevice_type == mem_type_e::dxgi) { if (hwdevice_type == mem_type_e::dxgi) {
auto disp = std::make_shared<dxgi::display_ddup_vram_t>(); auto disp = std::make_shared<dxgi::display_ddup_vram_t>();
@ -994,8 +993,7 @@ namespace platf {
if (!disp->init(config, display_name)) { if (!disp->init(config, display_name)) {
return disp; 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>(); auto disp = std::make_shared<dxgi::display_ddup_ram_t>();
if (!disp->init(config, display_name)) { if (!disp->init(config, display_name)) {
@ -1011,8 +1009,7 @@ namespace platf {
if (!disp->init(config, display_name)) { if (!disp->init(config, display_name)) {
return disp; 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>(); auto disp = std::make_shared<dxgi::display_wgc_ram_t>();
if (!disp->init(config, display_name)) { if (!disp->init(config, display_name)) {
@ -1025,8 +1022,7 @@ namespace platf {
return nullptr; return nullptr;
} }
std::vector<std::string> std::vector<std::string> display_names(mem_type_e) {
display_names(mem_type_e) {
std::vector<std::string> display_names; std::vector<std::string> display_names;
HRESULT status; HRESULT status;
@ -1096,8 +1092,7 @@ namespace platf {
* @brief Returns if GPUs/drivers have changed since the last call to this function. * @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. * @return `true` if a change has occurred or if it is unknown whether a change occurred.
*/ */
bool bool needs_encoder_reenumeration() {
needs_encoder_reenumeration() {
// Serialize access to the static DXGI factory // Serialize access to the static DXGI factory
static std::mutex reenumeration_state_lock; static std::mutex reenumeration_state_lock;
auto lg = std::lock_guard(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. // can deal with any initialization races that may occur when the system is booting.
BOOST_LOG(info) << "Encoder reenumeration is required"sv; BOOST_LOG(info) << "Encoder reenumeration is required"sv;
return true; return true;
} } else {
else {
// The DXGI factory from last time is still current, so no encoder changes have occurred. // The DXGI factory from last time is still current, so no encoder changes have occurred.
return false; return false;
} }

View file

@ -2,8 +2,8 @@
* @file src/platform/windows/display_ram.cpp * @file src/platform/windows/display_ram.cpp
* @brief Definitions for handling ram. * @brief Definitions for handling ram.
*/ */
// local includes
#include "display.h" #include "display.h"
#include "misc.h" #include "misc.h"
#include "src/logging.h" #include "src/logging.h"
@ -19,8 +19,7 @@ namespace platf::dxgi {
} }
}; };
void void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
int height = cursor.shape_info.Height / 2; int height = cursor.shape_info.Height / 2;
int width = cursor.shape_info.Width; int width = cursor.shape_info.Width;
int pitch = cursor.shape_info.Pitch; int pitch = cursor.shape_info.Pitch;
@ -82,8 +81,7 @@ namespace platf::dxgi {
} }
} }
void void apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
auto colors_out = (std::uint8_t *) &cursor_pixel; auto colors_out = (std::uint8_t *) &cursor_pixel;
auto colors_in = (std::uint8_t *) img_pixel_p; auto colors_in = (std::uint8_t *) img_pixel_p;
@ -91,28 +89,24 @@ namespace platf::dxgi {
auto alpha = colors_out[3]; auto alpha = colors_out[3];
if (alpha == 255) { if (alpha == 255) {
*img_pixel_p = cursor_pixel; *img_pixel_p = cursor_pixel;
} } else {
else {
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255; 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[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; colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
} }
} }
void void apply_color_masked(int *img_pixel_p, int cursor_pixel) {
apply_color_masked(int *img_pixel_p, int cursor_pixel) {
// TODO: When use of IDXGIOutput5 is implemented, support different color formats // TODO: When use of IDXGIOutput5 is implemented, support different color formats
auto alpha = ((std::uint8_t *) &cursor_pixel)[3]; auto alpha = ((std::uint8_t *) &cursor_pixel)[3];
if (alpha == 0xFF) { if (alpha == 0xFF) {
*img_pixel_p ^= cursor_pixel; *img_pixel_p ^= cursor_pixel;
} } else {
else {
*img_pixel_p = cursor_pixel; *img_pixel_p = cursor_pixel;
} }
} }
void void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
int height = cursor.shape_info.Height; int height = cursor.shape_info.Height;
int width = cursor.shape_info.Width; int width = cursor.shape_info.Width;
int pitch = cursor.shape_info.Pitch; int pitch = cursor.shape_info.Pitch;
@ -150,8 +144,7 @@ namespace platf::dxgi {
std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) { std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) {
if (masked) { if (masked) {
apply_color_masked(img_pixel_p, cursor_pixel); apply_color_masked(img_pixel_p, cursor_pixel);
} } else {
else {
apply_color_alpha(img_pixel_p, cursor_pixel); apply_color_alpha(img_pixel_p, cursor_pixel);
} }
++img_pixel_p; ++img_pixel_p;
@ -159,8 +152,7 @@ namespace platf::dxgi {
} }
} }
void void blend_cursor(const cursor_t &cursor, img_t &img) {
blend_cursor(const cursor_t &cursor, img_t &img) {
switch (cursor.shape_info.Type) { switch (cursor.shape_info.Type) {
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
blend_cursor_color(cursor, img, false); blend_cursor_color(cursor, img, false);
@ -176,8 +168,7 @@ namespace platf::dxgi {
} }
} }
capture_e 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) {
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; HRESULT status;
DXGI_OUTDUPL_FRAME_INFO frame_info; DXGI_OUTDUPL_FRAME_INFO frame_info;
@ -290,8 +281,7 @@ namespace platf::dxgi {
if (dummy_img(img)) { if (dummy_img(img)) {
return capture_e::error; return capture_e::error;
} }
} } else {
else {
// Map the staging texture for CPU access (making it inaccessible for the GPU) // 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); status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info);
if (FAILED(status)) { if (FAILED(status)) {
@ -325,13 +315,11 @@ namespace platf::dxgi {
return capture_e::ok; return capture_e::ok;
} }
capture_e capture_e display_ddup_ram_t::release_snapshot() {
display_ddup_ram_t::release_snapshot() {
return dup.release_frame(); return dup.release_frame();
} }
std::shared_ptr<platf::img_t> std::shared_ptr<platf::img_t> display_ram_t::alloc_img() {
display_ram_t::alloc_img() {
auto img = std::make_shared<img_t>(); auto img = std::make_shared<img_t>();
// Initialize fields that are format-independent // Initialize fields that are format-independent
@ -341,8 +329,7 @@ namespace platf::dxgi {
return img; return img;
} }
int int display_ram_t::complete_img(platf::img_t *img, bool dummy) {
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 this is not a dummy image, we must know the format by now
if (!dummy && capture_format == DXGI_FORMAT_UNKNOWN) { if (!dummy && capture_format == DXGI_FORMAT_UNKNOWN) {
BOOST_LOG(error) << "display_ram_t::complete_img() called with unknown capture format!"; 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 * @memberof platf::dxgi::display_ram_t
*/ */
int int display_ram_t::dummy_img(platf::img_t *img) {
display_ram_t::dummy_img(platf::img_t *img) {
if (complete_img(img, true)) { if (complete_img(img, true)) {
return -1; return -1;
} }
@ -383,13 +369,11 @@ namespace platf::dxgi {
return 0; return 0;
} }
std::vector<DXGI_FORMAT> std::vector<DXGI_FORMAT> display_ram_t::get_supported_capture_formats() {
display_ram_t::get_supported_capture_formats() {
return {DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8X8_UNORM}; return {DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8X8_UNORM};
} }
int int display_ddup_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
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)) { if (display_base_t::init(config, display_name) || dup.init(this, config)) {
return -1; return -1;
} }
@ -397,8 +381,7 @@ namespace platf::dxgi {
return 0; return 0;
} }
std::unique_ptr<avcodec_encode_device_t> std::unique_ptr<avcodec_encode_device_t> display_ram_t::make_avcodec_encode_device(pix_fmt_e pix_fmt) {
display_ram_t::make_avcodec_encode_device(pix_fmt_e pix_fmt) {
return std::make_unique<avcodec_encode_device_t>(); return std::make_unique<avcodec_encode_device_t>();
} }

View file

@ -2,8 +2,10 @@
* @file src/platform/windows/display_vram.cpp * @file src/platform/windows/display_vram.cpp
* @brief Definitions for handling video ram. * @brief Definitions for handling video ram.
*/ */
// standard includes
#include <cmath> #include <cmath>
// platform includes
#include <d3dcompiler.h> #include <d3dcompiler.h>
#include <directxmath.h> #include <directxmath.h>
@ -12,6 +14,11 @@ extern "C" {
#include <libavutil/hwcontext_d3d11va.h> #include <libavutil/hwcontext_d3d11va.h>
} }
// lib includes
#include <AMF/core/Factory.h>
#include <boost/algorithm/string/predicate.hpp>
// local includes
#include "display.h" #include "display.h"
#include "misc.h" #include "misc.h"
#include "src/config.h" #include "src/config.h"
@ -22,10 +29,6 @@ extern "C" {
#include "src/nvenc/nvenc_utils.h" #include "src/nvenc/nvenc_utils.h"
#include "src/video.h" #include "src/video.h"
#include <AMF/core/Factory.h>
#include <boost/algorithm/string/predicate.hpp>
#if !defined(SUNSHINE_SHADERS_DIR) // for testing this needs to be defined in cmake as we don't do an install #if !defined(SUNSHINE_SHADERS_DIR) // for testing this needs to be defined in cmake as we don't do an install
#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx" #define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx"
#endif #endif
@ -33,8 +36,7 @@ namespace platf {
using namespace std::literals; using namespace std::literals;
} }
static void static void free_frame(AVFrame *frame) {
free_frame(AVFrame *frame) {
av_frame_free(&frame); av_frame_free(&frame);
} }
@ -43,8 +45,7 @@ using frame_t = util::safe_ptr<AVFrame, free_frame>;
namespace platf::dxgi { namespace platf::dxgi {
template<class T> template<class T>
buf_t buf_t make_buffer(device_t::pointer device, const T &t) {
make_buffer(device_t::pointer device, const T &t) {
static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment"); static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment");
D3D11_BUFFER_DESC buffer_desc { D3D11_BUFFER_DESC buffer_desc {
@ -67,8 +68,7 @@ namespace platf::dxgi {
return buf_t {buf_p}; return buf_t {buf_p};
} }
blend_t blend_t make_blend(device_t::pointer device, bool enable, bool invert) {
make_blend(device_t::pointer device, bool enable, bool invert) {
D3D11_BLEND_DESC bdesc {}; D3D11_BLEND_DESC bdesc {};
auto &rt = bdesc.RenderTarget[0]; auto &rt = bdesc.RenderTarget[0];
rt.BlendEnable = enable; rt.BlendEnable = enable;
@ -82,8 +82,7 @@ namespace platf::dxgi {
// Invert colors // Invert colors
rt.SrcBlend = D3D11_BLEND_INV_DEST_COLOR; rt.SrcBlend = D3D11_BLEND_INV_DEST_COLOR;
rt.DestBlend = D3D11_BLEND_INV_SRC_COLOR; rt.DestBlend = D3D11_BLEND_INV_SRC_COLOR;
} } else {
else {
// Regular alpha blending // Regular alpha blending
rt.SrcBlend = D3D11_BLEND_SRC_ALPHA; rt.SrcBlend = D3D11_BLEND_SRC_ALPHA;
rt.DestBlend = D3D11_BLEND_INV_SRC_ALPHA; rt.DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
@ -163,8 +162,7 @@ namespace platf::dxgi {
bool _locked = false; bool _locked = false;
texture_lock_helper(const texture_lock_helper &) = delete; texture_lock_helper(const texture_lock_helper &) = delete;
texture_lock_helper & texture_lock_helper &operator=(const texture_lock_helper &) = delete;
operator=(const texture_lock_helper &) = delete;
texture_lock_helper(texture_lock_helper &&other) { texture_lock_helper(texture_lock_helper &&other) {
_mutex.reset(other._mutex.release()); _mutex.reset(other._mutex.release());
@ -172,9 +170,10 @@ namespace platf::dxgi {
other._locked = false; other._locked = false;
} }
texture_lock_helper & texture_lock_helper &operator=(texture_lock_helper &&other) {
operator=(texture_lock_helper &&other) { if (_locked) {
if (_locked) _mutex->ReleaseSync(0); _mutex->ReleaseSync(0);
}
_mutex.reset(other._mutex.release()); _mutex.reset(other._mutex.release());
_locked = other._locked; _locked = other._locked;
other._locked = false; other._locked = false;
@ -183,29 +182,32 @@ namespace platf::dxgi {
texture_lock_helper(IDXGIKeyedMutex *mutex): texture_lock_helper(IDXGIKeyedMutex *mutex):
_mutex(mutex) { _mutex(mutex) {
if (_mutex) _mutex->AddRef(); if (_mutex) {
_mutex->AddRef();
}
} }
~texture_lock_helper() { ~texture_lock_helper() {
if (_locked) _mutex->ReleaseSync(0); if (_locked) {
_mutex->ReleaseSync(0);
}
} }
bool bool lock() {
lock() { if (_locked) {
if (_locked) return true; return true;
}
HRESULT status = _mutex->AcquireSync(0, INFINITE); HRESULT status = _mutex->AcquireSync(0, INFINITE);
if (status == S_OK) { if (status == S_OK) {
_locked = true; _locked = true;
} } else {
else {
BOOST_LOG(error) << "Failed to acquire texture mutex [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Failed to acquire texture mutex [0x"sv << util::hex(status).to_string_view() << ']';
} }
return _locked; return _locked;
} }
}; };
util::buffer_t<std::uint8_t> util::buffer_t<std::uint8_t> make_cursor_xor_image(const util::buffer_t<std::uint8_t> &img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) {
make_cursor_xor_image(const util::buffer_t<std::uint8_t> &img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) {
constexpr std::uint32_t inverted = 0xFFFFFFFF; constexpr std::uint32_t inverted = 0xFFFFFFFF;
constexpr std::uint32_t transparent = 0; constexpr std::uint32_t transparent = 0;
@ -213,19 +215,18 @@ namespace platf::dxgi {
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
// This type doesn't require any XOR-blending // This type doesn't require any XOR-blending
return {}; return {};
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: { case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
{
util::buffer_t<std::uint8_t> cursor_img = img_data; util::buffer_t<std::uint8_t> cursor_img = img_data;
std::for_each((std::uint32_t *) std::begin(cursor_img), (std::uint32_t *) std::end(cursor_img), [](auto &pixel) { std::for_each((std::uint32_t *) std::begin(cursor_img), (std::uint32_t *) std::end(cursor_img), [](auto &pixel) {
auto alpha = (std::uint8_t)((pixel >> 24) & 0xFF); auto alpha = (std::uint8_t)((pixel >> 24) & 0xFF);
if (alpha == 0xFF) { if (alpha == 0xFF) {
// Pixels with 0xFF alpha will be XOR-blended as is. // Pixels with 0xFF alpha will be XOR-blended as is.
} } else if (alpha == 0x00) {
else if (alpha == 0x00) {
// Pixels with 0x00 alpha will be blended by make_cursor_alpha_image(). // Pixels with 0x00 alpha will be blended by make_cursor_alpha_image().
// We make them transparent for the XOR-blended cursor image. // We make them transparent for the XOR-blended cursor image.
pixel = transparent; pixel = transparent;
} } else {
else {
// Other alpha values are illegal in masked color cursors // Other alpha values are illegal in masked color cursors
BOOST_LOG(warning) << "Illegal alpha value in masked color cursor: " << alpha; BOOST_LOG(warning) << "Illegal alpha value in masked color cursor: " << alpha;
} }
@ -275,14 +276,14 @@ namespace platf::dxgi {
return cursor_img; return cursor_img;
} }
util::buffer_t<std::uint8_t> util::buffer_t<std::uint8_t> make_cursor_alpha_image(const util::buffer_t<std::uint8_t> &img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) {
make_cursor_alpha_image(const util::buffer_t<std::uint8_t> &img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) {
constexpr std::uint32_t black = 0xFF000000; constexpr std::uint32_t black = 0xFF000000;
constexpr std::uint32_t white = 0xFFFFFFFF; constexpr std::uint32_t white = 0xFFFFFFFF;
constexpr std::uint32_t transparent = 0; constexpr std::uint32_t transparent = 0;
switch (shape_info.Type) { switch (shape_info.Type) {
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: { case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
{
util::buffer_t<std::uint8_t> cursor_img = img_data; util::buffer_t<std::uint8_t> cursor_img = img_data;
std::for_each((std::uint32_t *) std::begin(cursor_img), (std::uint32_t *) std::end(cursor_img), [](auto &pixel) { std::for_each((std::uint32_t *) std::begin(cursor_img), (std::uint32_t *) std::end(cursor_img), [](auto &pixel) {
auto alpha = (std::uint8_t)((pixel >> 24) & 0xFF); auto alpha = (std::uint8_t)((pixel >> 24) & 0xFF);
@ -290,12 +291,10 @@ namespace platf::dxgi {
// Pixels with 0xFF alpha will be XOR-blended by make_cursor_xor_image(). // Pixels with 0xFF alpha will be XOR-blended by make_cursor_xor_image().
// We make them transparent for the alpha-blended cursor image. // We make them transparent for the alpha-blended cursor image.
pixel = transparent; pixel = transparent;
} } else if (alpha == 0x00) {
else if (alpha == 0x00) {
// Pixels with 0x00 alpha will be blended as opaque with the alpha-blended image. // Pixels with 0x00 alpha will be blended as opaque with the alpha-blended image.
pixel |= 0xFF000000; pixel |= 0xFF000000;
} } else {
else {
// Other alpha values are illegal in masked color cursors // Other alpha values are illegal in masked color cursors
BOOST_LOG(warning) << "Illegal alpha value in masked color cursor: " << alpha; BOOST_LOG(warning) << "Illegal alpha value in masked color cursor: " << alpha;
} }
@ -350,8 +349,7 @@ namespace platf::dxgi {
return cursor_img; return cursor_img;
} }
blob_t blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) {
compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) {
blob_t::pointer msg_p = nullptr; blob_t::pointer msg_p = nullptr;
blob_t::pointer compiled_p; blob_t::pointer compiled_p;
@ -377,26 +375,22 @@ namespace platf::dxgi {
return blob_t {compiled_p}; return blob_t {compiled_p};
} }
blob_t blob_t compile_pixel_shader(LPCSTR file) {
compile_pixel_shader(LPCSTR file) {
return compile_shader(file, "main_ps", "ps_5_0"); return compile_shader(file, "main_ps", "ps_5_0");
} }
blob_t blob_t compile_vertex_shader(LPCSTR file) {
compile_vertex_shader(LPCSTR file) {
return compile_shader(file, "main_vs", "vs_5_0"); return compile_shader(file, "main_vs", "vs_5_0");
} }
class d3d_base_encode_device final { class d3d_base_encode_device final {
public: public:
int int convert(platf::img_t &img_base) {
convert(platf::img_t &img_base) {
// Garbage collect mapped capture images whose weak references have expired // Garbage collect mapped capture images whose weak references have expired
for (auto it = img_ctx_map.begin(); it != img_ctx_map.end();) { for (auto it = img_ctx_map.begin(); it != img_ctx_map.end();) {
if (it->second.img_weak.expired()) { if (it->second.img_weak.expired()) {
it = img_ctx_map.erase(it); it = img_ctx_map.erase(it);
} } else {
else {
it++; it++;
} }
} }
@ -443,7 +437,9 @@ namespace platf::dxgi {
// Clear render target view(s) once so that the aspect ratio mismatch "bars" appear black // Clear render target view(s) once so that the aspect ratio mismatch "bars" appear black
if (!rtvs_cleared) { if (!rtvs_cleared) {
auto black = create_black_texture_for_rtv_clear(); auto black = create_black_texture_for_rtv_clear();
if (black) draw(black, out_Y_or_YUV_viewports_for_clear, out_UV_viewport_for_clear); if (black) {
draw(black, out_Y_or_YUV_viewports_for_clear, out_UV_viewport_for_clear);
}
rtvs_cleared = true; rtvs_cleared = true;
} }
@ -460,8 +456,7 @@ namespace platf::dxgi {
return 0; return 0;
} }
void void apply_colorspace(const ::video::sunshine_colorspace_t &colorspace) {
apply_colorspace(const ::video::sunshine_colorspace_t &colorspace) {
auto color_vectors = ::video::color_vectors_from_colorspace(colorspace); auto color_vectors = ::video::color_vectors_from_colorspace(colorspace);
if (format == DXGI_FORMAT_AYUV || if (format == DXGI_FORMAT_AYUV ||
@ -486,8 +481,7 @@ namespace platf::dxgi {
this->color_matrix = std::move(color_matrix); this->color_matrix = std::move(color_matrix);
} }
int int init_output(ID3D11Texture2D *frame_texture, int width, int height) {
init_output(ID3D11Texture2D *frame_texture, int width, int height) {
// The underlying frame pool owns the texture, so we must reference it for ourselves // The underlying frame pool owns the texture, so we must reference it for ourselves
frame_texture->AddRef(); frame_texture->AddRef();
output_texture.reset(frame_texture); output_texture.reset(frame_texture);
@ -517,8 +511,7 @@ namespace platf::dxgi {
create_vertex_shader_helper(convert_yuv420_packed_uv_type0s_vs_hlsl, convert_UV_vs); create_vertex_shader_helper(convert_yuv420_packed_uv_type0s_vs_hlsl, convert_UV_vs);
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_hlsl, convert_UV_ps); create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_hlsl, convert_UV_ps);
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_linear_hlsl, convert_UV_fp16_ps); create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_linear_hlsl, convert_UV_fp16_ps);
} } else {
else {
create_vertex_shader_helper(convert_yuv420_packed_uv_type0_vs_hlsl, convert_UV_vs); create_vertex_shader_helper(convert_yuv420_packed_uv_type0_vs_hlsl, convert_UV_vs);
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_ps); create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_ps);
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear_hlsl, convert_UV_fp16_ps); create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear_hlsl, convert_UV_fp16_ps);
@ -531,8 +524,7 @@ namespace platf::dxgi {
create_pixel_shader_helper(convert_yuv420_planar_y_ps_hlsl, convert_Y_or_YUV_ps); create_pixel_shader_helper(convert_yuv420_planar_y_ps_hlsl, convert_Y_or_YUV_ps);
if (display->is_hdr()) { if (display->is_hdr()) {
create_pixel_shader_helper(convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl, convert_Y_or_YUV_fp16_ps); create_pixel_shader_helper(convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl, convert_Y_or_YUV_fp16_ps);
} } else {
else {
create_pixel_shader_helper(convert_yuv420_planar_y_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps); create_pixel_shader_helper(convert_yuv420_planar_y_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
} }
if (downscaling) { if (downscaling) {
@ -540,18 +532,15 @@ namespace platf::dxgi {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_hlsl, convert_UV_ps); create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_hlsl, convert_UV_ps);
if (display->is_hdr()) { if (display->is_hdr()) {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_perceptual_quantizer_hlsl, convert_UV_fp16_ps); create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_perceptual_quantizer_hlsl, convert_UV_fp16_ps);
} } else {
else {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_linear_hlsl, convert_UV_fp16_ps); create_pixel_shader_helper(convert_yuv420_packed_uv_type0s_ps_linear_hlsl, convert_UV_fp16_ps);
} }
} } else {
else {
create_vertex_shader_helper(convert_yuv420_packed_uv_type0_vs_hlsl, convert_UV_vs); create_vertex_shader_helper(convert_yuv420_packed_uv_type0_vs_hlsl, convert_UV_vs);
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_ps); create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_ps);
if (display->is_hdr()) { if (display->is_hdr()) {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl, convert_UV_fp16_ps); create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl, convert_UV_fp16_ps);
} } else {
else {
create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear_hlsl, convert_UV_fp16_ps); create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear_hlsl, convert_UV_fp16_ps);
} }
} }
@ -563,8 +552,7 @@ namespace platf::dxgi {
create_pixel_shader_helper(convert_yuv444_planar_ps_hlsl, convert_Y_or_YUV_ps); create_pixel_shader_helper(convert_yuv444_planar_ps_hlsl, convert_Y_or_YUV_ps);
if (display->is_hdr()) { if (display->is_hdr()) {
create_pixel_shader_helper(convert_yuv444_planar_ps_perceptual_quantizer_hlsl, convert_Y_or_YUV_fp16_ps); create_pixel_shader_helper(convert_yuv444_planar_ps_perceptual_quantizer_hlsl, convert_Y_or_YUV_fp16_ps);
} } else {
else {
create_pixel_shader_helper(convert_yuv444_planar_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps); create_pixel_shader_helper(convert_yuv444_planar_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
} }
break; break;
@ -582,8 +570,7 @@ namespace platf::dxgi {
create_pixel_shader_helper(convert_yuv444_packed_y410_ps_hlsl, convert_Y_or_YUV_ps); create_pixel_shader_helper(convert_yuv444_packed_y410_ps_hlsl, convert_Y_or_YUV_ps);
if (display->is_hdr()) { if (display->is_hdr()) {
create_pixel_shader_helper(convert_yuv444_packed_y410_ps_perceptual_quantizer_hlsl, convert_Y_or_YUV_fp16_ps); create_pixel_shader_helper(convert_yuv444_packed_y410_ps_perceptual_quantizer_hlsl, convert_Y_or_YUV_fp16_ps);
} } else {
else {
create_pixel_shader_helper(convert_yuv444_packed_y410_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps); create_pixel_shader_helper(convert_yuv444_packed_y410_ps_linear_hlsl, convert_Y_or_YUV_fp16_ps);
} }
break; break;
@ -695,10 +682,14 @@ namespace platf::dxgi {
}; };
// Create Y/YUV render target view // Create Y/YUV render target view
if (!create_rtv(out_Y_or_YUV_rtv, rtv_Y_or_YUV_format)) return -1; if (!create_rtv(out_Y_or_YUV_rtv, rtv_Y_or_YUV_format)) {
return -1;
}
// Create UV render target view if needed // Create UV render target view if needed
if (rtv_UV_format != DXGI_FORMAT_UNKNOWN && !create_rtv(out_UV_rtv, rtv_UV_format)) return -1; if (rtv_UV_format != DXGI_FORMAT_UNKNOWN && !create_rtv(out_UV_rtv, rtv_UV_format)) {
return -1;
}
if (rtv_simple_clear) { if (rtv_simple_clear) {
// Clear the RTVs to ensure the aspect ratio padding is black // Clear the RTVs to ensure the aspect ratio padding is black
@ -709,8 +700,7 @@ namespace platf::dxgi {
device_ctx->ClearRenderTargetView(out_UV_rtv.get(), uv_black); device_ctx->ClearRenderTargetView(out_UV_rtv.get(), uv_black);
} }
rtvs_cleared = true; rtvs_cleared = true;
} } else {
else {
// Can't use ClearRenderTargetView(), will clear on first convert() // Can't use ClearRenderTargetView(), will clear on first convert()
rtvs_cleared = false; rtvs_cleared = false;
} }
@ -718,8 +708,7 @@ namespace platf::dxgi {
return 0; return 0;
} }
int int init(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
init(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
switch (pix_fmt) { switch (pix_fmt) {
case pix_fmt_e::nv12: case pix_fmt_e::nv12:
format = DXGI_FORMAT_NV12; format = DXGI_FORMAT_NV12;
@ -761,11 +750,13 @@ namespace platf::dxgi {
D3D_DRIVER_TYPE_UNKNOWN, D3D_DRIVER_TYPE_UNKNOWN,
nullptr, nullptr,
D3D11_CREATE_DEVICE_FLAGS | D3D11_CREATE_DEVICE_VIDEO_SUPPORT, D3D11_CREATE_DEVICE_FLAGS | D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL), featureLevels,
sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
D3D11_SDK_VERSION, D3D11_SDK_VERSION,
&device, &device,
nullptr, nullptr,
&device_ctx); &device_ctx
);
if (FAILED(status)) { if (FAILED(status)) {
BOOST_LOG(error) << "Failed to create encoder D3D11 device [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Failed to create encoder D3D11 device [0x"sv << util::hex(status).to_string_view() << ']';
@ -842,8 +833,7 @@ namespace platf::dxgi {
std::weak_ptr<const platf::img_t> img_weak; std::weak_ptr<const platf::img_t> img_weak;
void void reset() {
reset() {
capture_texture_p = nullptr; capture_texture_p = nullptr;
encoder_texture.reset(); encoder_texture.reset();
encoder_input_res.reset(); encoder_input_res.reset();
@ -852,8 +842,7 @@ namespace platf::dxgi {
} }
}; };
int int initialize_image_context(const img_d3d_t &img, encoder_img_ctx_t &img_ctx) {
initialize_image_context(const img_d3d_t &img, encoder_img_ctx_t &img_ctx) {
// If we've already opened the shared texture, we're done // If we've already opened the shared texture, we're done
if (img_ctx.encoder_texture && img.capture_texture.get() == img_ctx.capture_texture_p) { if (img_ctx.encoder_texture && img.capture_texture.get() == img_ctx.capture_texture_p) {
return 0; return 0;
@ -898,8 +887,7 @@ namespace platf::dxgi {
return 0; return 0;
} }
shader_res_t shader_res_t create_black_texture_for_rtv_clear() {
create_black_texture_for_rtv_clear() {
constexpr auto width = 32; constexpr auto width = 32;
constexpr auto height = 32; constexpr auto height = 32;
@ -974,25 +962,21 @@ namespace platf::dxgi {
class d3d_avcodec_encode_device_t: public avcodec_encode_device_t { class d3d_avcodec_encode_device_t: public avcodec_encode_device_t {
public: public:
int int init(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
init(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
int result = base.init(display, adapter_p, pix_fmt); int result = base.init(display, adapter_p, pix_fmt);
data = base.device.get(); data = base.device.get();
return result; return result;
} }
int int convert(platf::img_t &img_base) override {
convert(platf::img_t &img_base) override {
return base.convert(img_base); return base.convert(img_base);
} }
void void apply_colorspace() override {
apply_colorspace() override {
base.apply_colorspace(colorspace); base.apply_colorspace(colorspace);
} }
void void init_hwframes(AVHWFramesContext *frames) override {
init_hwframes(AVHWFramesContext *frames) override {
// We may be called with a QSV or D3D11VA context // We may be called with a QSV or D3D11VA context
if (frames->device_ctx->type == AV_HWDEVICE_TYPE_D3D11VA) { if (frames->device_ctx->type == AV_HWDEVICE_TYPE_D3D11VA) {
auto d3d11_frames = (AVD3D11VAFramesContext *) frames->hwctx; auto d3d11_frames = (AVD3D11VAFramesContext *) frames->hwctx;
@ -1006,8 +990,7 @@ namespace platf::dxgi {
frames->initial_pool_size = 1; frames->initial_pool_size = 1;
} }
int int prepare_to_derive_context(int hw_device_type) override {
prepare_to_derive_context(int hw_device_type) override {
// QuickSync requires our device to be multithread-protected // QuickSync requires our device to be multithread-protected
if (hw_device_type == AV_HWDEVICE_TYPE_QSV) { if (hw_device_type == AV_HWDEVICE_TYPE_QSV) {
multithread_t mt; multithread_t mt;
@ -1024,8 +1007,7 @@ namespace platf::dxgi {
return 0; return 0;
} }
int int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
this->hwframe.reset(frame); this->hwframe.reset(frame);
this->frame = frame; this->frame = frame;
@ -1055,8 +1037,7 @@ namespace platf::dxgi {
// Get the texture from the mapped frame // Get the texture from the mapped frame
frame_texture = (ID3D11Texture2D *) d3d11_frame->data[0]; frame_texture = (ID3D11Texture2D *) d3d11_frame->data[0];
} } else {
else {
// Otherwise, we can just use the texture inside the original frame // Otherwise, we can just use the texture inside the original frame
frame_texture = (ID3D11Texture2D *) frame->data[0]; frame_texture = (ID3D11Texture2D *) frame->data[0];
} }
@ -1071,20 +1052,20 @@ namespace platf::dxgi {
class d3d_nvenc_encode_device_t: public nvenc_encode_device_t { class d3d_nvenc_encode_device_t: public nvenc_encode_device_t {
public: public:
bool bool init_device(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
init_device(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
buffer_format = nvenc::nvenc_format_from_sunshine_format(pix_fmt); buffer_format = nvenc::nvenc_format_from_sunshine_format(pix_fmt);
if (buffer_format == NV_ENC_BUFFER_FORMAT_UNDEFINED) { if (buffer_format == NV_ENC_BUFFER_FORMAT_UNDEFINED) {
BOOST_LOG(error) << "Unexpected pixel format for NvENC ["sv << from_pix_fmt(pix_fmt) << ']'; BOOST_LOG(error) << "Unexpected pixel format for NvENC ["sv << from_pix_fmt(pix_fmt) << ']';
return false; return false;
} }
if (base.init(display, adapter_p, pix_fmt)) return false; if (base.init(display, adapter_p, pix_fmt)) {
return false;
}
if (pix_fmt == pix_fmt_e::yuv444p16) { if (pix_fmt == pix_fmt_e::yuv444p16) {
nvenc_d3d = std::make_unique<nvenc::nvenc_d3d11_on_cuda>(base.device.get()); nvenc_d3d = std::make_unique<nvenc::nvenc_d3d11_on_cuda>(base.device.get());
} } else {
else {
nvenc_d3d = std::make_unique<nvenc::nvenc_d3d11_native>(base.device.get()); nvenc_d3d = std::make_unique<nvenc::nvenc_d3d11_native>(base.device.get());
} }
nvenc = nvenc_d3d.get(); nvenc = nvenc_d3d.get();
@ -1092,19 +1073,21 @@ namespace platf::dxgi {
return true; return true;
} }
bool bool init_encoder(const ::video::config_t &client_config, const ::video::sunshine_colorspace_t &colorspace) override {
init_encoder(const ::video::config_t &client_config, const ::video::sunshine_colorspace_t &colorspace) override { if (!nvenc_d3d) {
if (!nvenc_d3d) return false; return false;
}
auto nvenc_colorspace = nvenc::nvenc_colorspace_from_sunshine_colorspace(colorspace); auto nvenc_colorspace = nvenc::nvenc_colorspace_from_sunshine_colorspace(colorspace);
if (!nvenc_d3d->create_encoder(config::video.nv, client_config, nvenc_colorspace, buffer_format)) return false; if (!nvenc_d3d->create_encoder(config::video.nv, client_config, nvenc_colorspace, buffer_format)) {
return false;
}
base.apply_colorspace(colorspace); base.apply_colorspace(colorspace);
return base.init_output(nvenc_d3d->get_input_texture(), client_config.width, client_config.height) == 0; return base.init_output(nvenc_d3d->get_input_texture(), client_config.width, client_config.height) == 0;
} }
int int convert(platf::img_t &img_base) override {
convert(platf::img_t &img_base) override {
return base.convert(img_base); return base.convert(img_base);
} }
@ -1114,8 +1097,7 @@ namespace platf::dxgi {
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED; NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED;
}; };
bool bool set_cursor_texture(device_t::pointer device, gpu_cursor_t &cursor, util::buffer_t<std::uint8_t> &&cursor_img, DXGI_OUTDUPL_POINTER_SHAPE_INFO &shape_info) {
set_cursor_texture(device_t::pointer device, gpu_cursor_t &cursor, util::buffer_t<std::uint8_t> &&cursor_img, DXGI_OUTDUPL_POINTER_SHAPE_INFO &shape_info) {
// This cursor image may not be used // This cursor image may not be used
if (cursor_img.size() == 0) { if (cursor_img.size() == 0) {
cursor.input_res.reset(); cursor.input_res.reset();
@ -1159,8 +1141,7 @@ namespace platf::dxgi {
return true; return true;
} }
capture_e capture_e display_ddup_vram_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) {
display_ddup_vram_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; HRESULT status;
DXGI_OUTDUPL_FRAME_INFO frame_info; DXGI_OUTDUPL_FRAME_INFO frame_info;
@ -1209,11 +1190,9 @@ namespace platf::dxgi {
} }
if (frame_info.LastMouseUpdateTime.QuadPart) { if (frame_info.LastMouseUpdateTime.QuadPart) {
cursor_alpha.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, cursor_alpha.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, width, height, display_rotation, frame_info.PointerPosition.Visible);
width, height, display_rotation, frame_info.PointerPosition.Visible);
cursor_xor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, cursor_xor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, width, height, display_rotation, frame_info.PointerPosition.Visible);
width, height, display_rotation, frame_info.PointerPosition.Visible);
} }
const bool blend_mouse_cursor_flag = (cursor_alpha.visible || cursor_xor.visible) && cursor_visible; const bool blend_mouse_cursor_flag = (cursor_alpha.visible || cursor_xor.visible) && cursor_visible;
@ -1272,8 +1251,7 @@ namespace platf::dxgi {
// We don't know the final capture format yet, so we will encode a black dummy image // We don't know the final capture format yet, so we will encode a black dummy image
last_frame_action = lfa::nothing; last_frame_action = lfa::nothing;
out_frame_action = ofa::dummy_fallback; out_frame_action = ofa::dummy_fallback;
} } else {
else {
if (src) { if (src) {
// We got a new frame from DesktopDuplication... // We got a new frame from DesktopDuplication...
if (blend_mouse_cursor_flag) { if (blend_mouse_cursor_flag) {
@ -1285,8 +1263,7 @@ namespace platf::dxgi {
last_frame_action = lfa::copy_src_to_surface; last_frame_action = lfa::copy_src_to_surface;
// Copy the intermediate surface to a new image from pull_free_image_cb and blend the mouse cursor onto it. // Copy the intermediate surface to a new image from pull_free_image_cb and blend the mouse cursor onto it.
out_frame_action = ofa::copy_last_surface_and_blend_cursor; out_frame_action = ofa::copy_last_surface_and_blend_cursor;
} } else {
else {
// ...and we don't need to blend the mouse cursor. // ...and we don't need to blend the mouse cursor.
// Copy the frame to a new image from pull_free_image_cb and save the shared pointer to the image // Copy the frame to a new image from pull_free_image_cb and save the shared pointer to the image
// in case the mouse cursor appears without a new frame from DesktopDuplication. // in case the mouse cursor appears without a new frame from DesktopDuplication.
@ -1294,8 +1271,7 @@ namespace platf::dxgi {
// Use saved last image shared pointer as output image evading copy. // Use saved last image shared pointer as output image evading copy.
out_frame_action = ofa::forward_last_img; out_frame_action = ofa::forward_last_img;
} }
} } else if (!std::holds_alternative<std::monostate>(last_frame_variant)) {
else if (!std::holds_alternative<std::monostate>(last_frame_variant)) {
// We didn't get a new frame from DesktopDuplication... // We didn't get a new frame from DesktopDuplication...
if (blend_mouse_cursor_flag) { if (blend_mouse_cursor_flag) {
// ...but we need to blend the mouse cursor. // ...but we need to blend the mouse cursor.
@ -1307,8 +1283,7 @@ namespace platf::dxgi {
// Copy the intermediate surface which contains last DesktopDuplication frame // Copy the intermediate surface which contains last DesktopDuplication frame
// to a new image from pull_free_image_cb and blend the mouse cursor onto it. // to a new image from pull_free_image_cb and blend the mouse cursor onto it.
out_frame_action = ofa::copy_last_surface_and_blend_cursor; out_frame_action = ofa::copy_last_surface_and_blend_cursor;
} } else {
else {
// ...and we don't need to blend the mouse cursor. // ...and we don't need to blend the mouse cursor.
// This happens when the mouse cursor disappears from screen, // This happens when the mouse cursor disappears from screen,
// or there's mouse cursor on screen, but its drawing is disabled in sunshine. // or there's mouse cursor on screen, but its drawing is disabled in sunshine.
@ -1355,7 +1330,9 @@ namespace platf::dxgi {
// Finish creating the image (if it hasn't happened already), // Finish creating the image (if it hasn't happened already),
// also creates synchronization primitives for shared access from multiple direct3d devices. // also creates synchronization primitives for shared access from multiple direct3d devices.
if (complete_img(d3d_img.get(), dummy)) return { nullptr, nullptr }; if (complete_img(d3d_img.get(), dummy)) {
return {nullptr, nullptr};
}
// This image is shared between capture direct3d device and encoders direct3d devices, // This image is shared between capture direct3d device and encoders direct3d devices,
// we must acquire lock before doing anything to it. // we must acquire lock before doing anything to it.
@ -1372,11 +1349,13 @@ namespace platf::dxgi {
}; };
switch (last_frame_action) { switch (last_frame_action) {
case lfa::nothing: { case lfa::nothing:
{
break; break;
} }
case lfa::replace_surface_with_img: { case lfa::replace_surface_with_img:
{
auto p_surface = std::get_if<texture2d_t>(&last_frame_variant); auto p_surface = std::get_if<texture2d_t>(&last_frame_variant);
if (!p_surface) { if (!p_surface) {
BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__; BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__;
@ -1384,10 +1363,14 @@ namespace platf::dxgi {
} }
std::shared_ptr<platf::img_t> img; std::shared_ptr<platf::img_t> img;
if (!pull_free_image_cb(img)) return capture_e::interrupted; if (!pull_free_image_cb(img)) {
return capture_e::interrupted;
}
auto [d3d_img, lock] = get_locked_d3d_img(img); auto [d3d_img, lock] = get_locked_d3d_img(img);
if (!d3d_img) return capture_e::error; if (!d3d_img) {
return capture_e::error;
}
device_ctx->CopyResource(d3d_img->capture_texture.get(), p_surface->get()); device_ctx->CopyResource(d3d_img->capture_texture.get(), p_surface->get());
@ -1399,44 +1382,57 @@ namespace platf::dxgi {
break; break;
} }
case lfa::replace_img_with_surface: { case lfa::replace_img_with_surface:
{
auto p_img = std::get_if<std::shared_ptr<platf::img_t>>(&last_frame_variant); auto p_img = std::get_if<std::shared_ptr<platf::img_t>>(&last_frame_variant);
if (!p_img) { if (!p_img) {
BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__; BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__;
return capture_e::error; return capture_e::error;
} }
auto [d3d_img, lock] = get_locked_d3d_img(*p_img); auto [d3d_img, lock] = get_locked_d3d_img(*p_img);
if (!d3d_img) return capture_e::error; if (!d3d_img) {
return capture_e::error;
}
p_img = nullptr; p_img = nullptr;
last_frame_variant = texture2d_t {}; last_frame_variant = texture2d_t {};
auto &surface = std::get<texture2d_t>(last_frame_variant); auto &surface = std::get<texture2d_t>(last_frame_variant);
if (!create_surface(surface)) return capture_e::error; if (!create_surface(surface)) {
return capture_e::error;
}
device_ctx->CopyResource(surface.get(), d3d_img->capture_texture.get()); device_ctx->CopyResource(surface.get(), d3d_img->capture_texture.get());
break; break;
} }
case lfa::copy_src_to_img: { case lfa::copy_src_to_img:
{
last_frame_variant = {}; last_frame_variant = {};
std::shared_ptr<platf::img_t> img; std::shared_ptr<platf::img_t> img;
if (!pull_free_image_cb(img)) return capture_e::interrupted; if (!pull_free_image_cb(img)) {
return capture_e::interrupted;
}
auto [d3d_img, lock] = get_locked_d3d_img(img); auto [d3d_img, lock] = get_locked_d3d_img(img);
if (!d3d_img) return capture_e::error; if (!d3d_img) {
return capture_e::error;
}
device_ctx->CopyResource(d3d_img->capture_texture.get(), src.get()); device_ctx->CopyResource(d3d_img->capture_texture.get(), src.get());
last_frame_variant = img; last_frame_variant = img;
break; break;
} }
case lfa::copy_src_to_surface: { case lfa::copy_src_to_surface:
{
auto p_surface = std::get_if<texture2d_t>(&last_frame_variant); auto p_surface = std::get_if<texture2d_t>(&last_frame_variant);
if (!p_surface) { if (!p_surface) {
last_frame_variant = texture2d_t {}; last_frame_variant = texture2d_t {};
p_surface = std::get_if<texture2d_t>(&last_frame_variant); p_surface = std::get_if<texture2d_t>(&last_frame_variant);
if (!create_surface(*p_surface)) return capture_e::error; if (!create_surface(*p_surface)) {
return capture_e::error;
}
} }
device_ctx->CopyResource(p_surface->get(), src.get()); device_ctx->CopyResource(p_surface->get(), src.get());
break; break;
@ -1476,7 +1472,8 @@ namespace platf::dxgi {
}; };
switch (out_frame_action) { switch (out_frame_action) {
case ofa::forward_last_img: { case ofa::forward_last_img:
{
auto p_img = std::get_if<std::shared_ptr<platf::img_t>>(&last_frame_variant); auto p_img = std::get_if<std::shared_ptr<platf::img_t>>(&last_frame_variant);
if (!p_img) { if (!p_img) {
BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__; BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__;
@ -1486,7 +1483,8 @@ namespace platf::dxgi {
break; break;
} }
case ofa::copy_last_surface_and_blend_cursor: { case ofa::copy_last_surface_and_blend_cursor:
{
auto p_surface = std::get_if<texture2d_t>(&last_frame_variant); auto p_surface = std::get_if<texture2d_t>(&last_frame_variant);
if (!p_surface) { if (!p_surface) {
BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__; BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__;
@ -1497,18 +1495,25 @@ namespace platf::dxgi {
return capture_e::error; return capture_e::error;
} }
if (!pull_free_image_cb(img_out)) return capture_e::interrupted; if (!pull_free_image_cb(img_out)) {
return capture_e::interrupted;
}
auto [d3d_img, lock] = get_locked_d3d_img(img_out); auto [d3d_img, lock] = get_locked_d3d_img(img_out);
if (!d3d_img) return capture_e::error; if (!d3d_img) {
return capture_e::error;
}
device_ctx->CopyResource(d3d_img->capture_texture.get(), p_surface->get()); device_ctx->CopyResource(d3d_img->capture_texture.get(), p_surface->get());
blend_cursor(*d3d_img); blend_cursor(*d3d_img);
break; break;
} }
case ofa::dummy_fallback: { case ofa::dummy_fallback:
if (!pull_free_image_cb(img_out)) return capture_e::interrupted; {
if (!pull_free_image_cb(img_out)) {
return capture_e::interrupted;
}
// Clear the image if it has been used as a dummy. // Clear the image if it has been used as a dummy.
// It can have the mouse cursor blended onto it. // It can have the mouse cursor blended onto it.
@ -1516,7 +1521,9 @@ namespace platf::dxgi {
bool reclear_dummy = !old_d3d_img->blank && old_d3d_img->capture_texture; bool reclear_dummy = !old_d3d_img->blank && old_d3d_img->capture_texture;
auto [d3d_img, lock] = get_locked_d3d_img(img_out, true); auto [d3d_img, lock] = get_locked_d3d_img(img_out, true);
if (!d3d_img) return capture_e::error; if (!d3d_img) {
return capture_e::error;
}
if (reclear_dummy) { if (reclear_dummy) {
const float rgb_black[] = {0.0f, 0.0f, 0.0f, 0.0f}; const float rgb_black[] = {0.0f, 0.0f, 0.0f, 0.0f};
@ -1543,13 +1550,11 @@ namespace platf::dxgi {
return capture_e::ok; return capture_e::ok;
} }
capture_e capture_e display_ddup_vram_t::release_snapshot() {
display_ddup_vram_t::release_snapshot() {
return dup.release_frame(); return dup.release_frame();
} }
int int display_ddup_vram_t::init(const ::video::config_t &config, const std::string &display_name) {
display_ddup_vram_t::init(const ::video::config_t &config, const std::string &display_name) {
if (display_base_t::init(config, display_name) || dup.init(this, config)) { if (display_base_t::init(config, display_name) || dup.init(this, config)) {
return -1; return -1;
} }
@ -1605,8 +1610,7 @@ namespace platf::dxgi {
} }
device_ctx->PSSetConstantBuffers(1, 1, &white_multiplier); device_ctx->PSSetConstantBuffers(1, 1, &white_multiplier);
} } else {
else {
status = device->CreatePixelShader(cursor_ps_hlsl->GetBufferPointer(), cursor_ps_hlsl->GetBufferSize(), nullptr, &cursor_ps); status = device->CreatePixelShader(cursor_ps_hlsl->GetBufferPointer(), cursor_ps_hlsl->GetBufferSize(), nullptr, &cursor_ps);
if (status) { if (status) {
BOOST_LOG(error) << "Failed to create cursor blending pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Failed to create cursor blending pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
@ -1636,14 +1640,14 @@ namespace platf::dxgi {
* @param timeout how long to wait for the next frame * @param timeout how long to wait for the next frame
* @param cursor_visible * @param cursor_visible
*/ */
capture_e capture_e display_wgc_vram_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) {
display_wgc_vram_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) {
texture2d_t src; texture2d_t src;
uint64_t frame_qpc; uint64_t frame_qpc;
dup.set_cursor_visible(cursor_visible); dup.set_cursor_visible(cursor_visible);
auto capture_status = dup.next_frame(timeout, &src, frame_qpc); auto capture_status = dup.next_frame(timeout, &src, frame_qpc);
if (capture_status != capture_e::ok) if (capture_status != capture_e::ok) {
return capture_status; return capture_status;
}
auto frame_timestamp = std::chrono::steady_clock::now() - qpc_time_difference(qpc_counter(), frame_qpc); auto frame_timestamp = std::chrono::steady_clock::now() - qpc_time_difference(qpc_counter(), frame_qpc);
D3D11_TEXTURE2D_DESC desc; D3D11_TEXTURE2D_DESC desc;
@ -1664,8 +1668,9 @@ namespace platf::dxgi {
} }
std::shared_ptr<platf::img_t> img; std::shared_ptr<platf::img_t> img;
if (!pull_free_image_cb(img)) if (!pull_free_image_cb(img)) {
return capture_e::interrupted; return capture_e::interrupted;
}
auto d3d_img = std::static_pointer_cast<img_d3d_t>(img); auto d3d_img = std::static_pointer_cast<img_d3d_t>(img);
d3d_img->blank = false; // image is always ready for capture d3d_img->blank = false; // image is always ready for capture
@ -1673,13 +1678,11 @@ namespace platf::dxgi {
texture_lock_helper lock_helper(d3d_img->capture_mutex.get()); texture_lock_helper lock_helper(d3d_img->capture_mutex.get());
if (lock_helper.lock()) { if (lock_helper.lock()) {
device_ctx->CopyResource(d3d_img->capture_texture.get(), src.get()); device_ctx->CopyResource(d3d_img->capture_texture.get(), src.get());
} } else {
else {
BOOST_LOG(error) << "Failed to lock capture texture"; BOOST_LOG(error) << "Failed to lock capture texture";
return capture_e::error; return capture_e::error;
} }
} } else {
else {
return capture_e::error; return capture_e::error;
} }
img_out = img; img_out = img;
@ -1690,21 +1693,19 @@ namespace platf::dxgi {
return capture_e::ok; return capture_e::ok;
} }
capture_e capture_e display_wgc_vram_t::release_snapshot() {
display_wgc_vram_t::release_snapshot() {
return dup.release_frame(); return dup.release_frame();
} }
int int display_wgc_vram_t::init(const ::video::config_t &config, const std::string &display_name) {
display_wgc_vram_t::init(const ::video::config_t &config, const std::string &display_name) { if (display_base_t::init(config, display_name) || dup.init(this, config)) {
if (display_base_t::init(config, display_name) || dup.init(this, config))
return -1; return -1;
}
return 0; return 0;
} }
std::shared_ptr<platf::img_t> std::shared_ptr<platf::img_t> display_vram_t::alloc_img() {
display_vram_t::alloc_img() {
auto img = std::make_shared<img_d3d_t>(); auto img = std::make_shared<img_d3d_t>();
// Initialize format-independent fields // Initialize format-independent fields
@ -1717,8 +1718,7 @@ namespace platf::dxgi {
} }
// This cannot use ID3D11DeviceContext because it can be called concurrently by the encoding thread // This cannot use ID3D11DeviceContext because it can be called concurrently by the encoding thread
int int display_vram_t::complete_img(platf::img_t *img_base, bool dummy) {
display_vram_t::complete_img(platf::img_t *img_base, bool dummy) {
auto img = (img_d3d_t *) img_base; auto img = (img_d3d_t *) img_base;
// If this already has a capture texture and it's not switching dummy state, nothing to do // If this already has a capture texture and it's not switching dummy state, nothing to do
@ -1801,13 +1801,11 @@ namespace platf::dxgi {
/** /**
* @memberof platf::dxgi::display_vram_t * @memberof platf::dxgi::display_vram_t
*/ */
int int display_vram_t::dummy_img(platf::img_t *img_base) {
display_vram_t::dummy_img(platf::img_t *img_base) {
return complete_img(img_base, true); return complete_img(img_base, true);
} }
std::vector<DXGI_FORMAT> std::vector<DXGI_FORMAT> display_vram_t::get_supported_capture_formats() {
display_vram_t::get_supported_capture_formats() {
return { return {
// scRGB FP16 is the ideal format for Wide Color Gamut and Advanced Color // scRGB FP16 is the ideal format for Wide Color Gamut and Advanced Color
// displays (both SDR and HDR). This format uses linear gamma, so we will // displays (both SDR and HDR). This format uses linear gamma, so we will
@ -1837,8 +1835,7 @@ namespace platf::dxgi {
* @param config The codec configuration. * @param config The codec configuration.
* @return `true` if supported, `false` otherwise. * @return `true` if supported, `false` otherwise.
*/ */
bool bool display_vram_t::is_codec_supported(std::string_view name, const ::video::config_t &config) {
display_vram_t::is_codec_supported(std::string_view name, const ::video::config_t &config) {
DXGI_ADAPTER_DESC adapter_desc; DXGI_ADAPTER_DESC adapter_desc;
adapter->GetDesc(&adapter_desc); adapter->GetDesc(&adapter_desc);
@ -1871,8 +1868,7 @@ namespace platf::dxgi {
<< AMF_GET_BUILD_VERSION(version); << AMF_GET_BUILD_VERSION(version);
BOOST_LOG(warning) << "If your AMD GPU supports AV1 encoding, update your graphics drivers!"sv; BOOST_LOG(warning) << "If your AMD GPU supports AV1 encoding, update your graphics drivers!"sv;
return false; return false;
} } else if (config.dynamicRange && version < AMF_MAKE_FULL_VERSION(1, 4, 23, 0)) {
else if (config.dynamicRange && version < AMF_MAKE_FULL_VERSION(1, 4, 23, 0)) {
// Older versions of the AMD AMF runtime can crash when fed P010 surfaces. // Older versions of the AMD AMF runtime can crash when fed P010 surfaces.
// Fail if AMF version is below 1.4.23 where HEVC Main10 encoding was introduced. // Fail if AMF version is below 1.4.23 where HEVC Main10 encoding was introduced.
// AMF 1.4.23 corresponds to driver version 21.12.1 (21.40.11.03) or newer. // AMF 1.4.23 corresponds to driver version 21.12.1 (21.40.11.03) or newer.
@ -1884,20 +1880,16 @@ namespace platf::dxgi {
BOOST_LOG(warning) << "If your AMD GPU supports HEVC Main10 encoding, update your graphics drivers!"sv; BOOST_LOG(warning) << "If your AMD GPU supports HEVC Main10 encoding, update your graphics drivers!"sv;
return false; return false;
} }
} } else {
else {
BOOST_LOG(warning) << "AMFQueryVersion() failed: "sv << result; BOOST_LOG(warning) << "AMFQueryVersion() failed: "sv << result;
} }
} } else {
else {
BOOST_LOG(warning) << "AMF DLL missing export: "sv << AMF_QUERY_VERSION_FUNCTION_NAME; BOOST_LOG(warning) << "AMF DLL missing export: "sv << AMF_QUERY_VERSION_FUNCTION_NAME;
} }
} } else {
else {
BOOST_LOG(warning) << "Detected AMD GPU but AMF failed to load"sv; BOOST_LOG(warning) << "Detected AMD GPU but AMF failed to load"sv;
} }
} } else if (adapter_desc.VendorId == 0x8086) { // Intel
else if (adapter_desc.VendorId == 0x8086) { // Intel
// If it's not a QSV encoder, it's not compatible with an Intel GPU // If it's not a QSV encoder, it's not compatible with an Intel GPU
if (!boost::algorithm::ends_with(name, "_qsv")) { if (!boost::algorithm::ends_with(name, "_qsv")) {
return false; return false;
@ -1909,22 +1901,19 @@ namespace platf::dxgi {
} }
// TODO: Blacklist HEVC 4:4:4 based on adapter model // TODO: Blacklist HEVC 4:4:4 based on adapter model
} }
} } else if (adapter_desc.VendorId == 0x10de) { // Nvidia
else if (adapter_desc.VendorId == 0x10de) { // Nvidia
// If it's not an NVENC encoder, it's not compatible with an Nvidia GPU // If it's not an NVENC encoder, it's not compatible with an Nvidia GPU
if (!boost::algorithm::ends_with(name, "_nvenc")) { if (!boost::algorithm::ends_with(name, "_nvenc")) {
return false; return false;
} }
} } else {
else {
BOOST_LOG(warning) << "Unknown GPU vendor ID: " << util::hex(adapter_desc.VendorId).to_string_view(); BOOST_LOG(warning) << "Unknown GPU vendor ID: " << util::hex(adapter_desc.VendorId).to_string_view();
} }
return true; return true;
} }
std::unique_ptr<avcodec_encode_device_t> std::unique_ptr<avcodec_encode_device_t> display_vram_t::make_avcodec_encode_device(pix_fmt_e pix_fmt) {
display_vram_t::make_avcodec_encode_device(pix_fmt_e pix_fmt) {
auto device = std::make_unique<d3d_avcodec_encode_device_t>(); auto device = std::make_unique<d3d_avcodec_encode_device_t>();
if (device->init(shared_from_this(), adapter.get(), pix_fmt) != 0) { if (device->init(shared_from_this(), adapter.get(), pix_fmt) != 0) {
return nullptr; return nullptr;
@ -1932,8 +1921,7 @@ namespace platf::dxgi {
return device; return device;
} }
std::unique_ptr<nvenc_encode_device_t> std::unique_ptr<nvenc_encode_device_t> display_vram_t::make_nvenc_encode_device(pix_fmt_e pix_fmt) {
display_vram_t::make_nvenc_encode_device(pix_fmt_e pix_fmt) {
auto device = std::make_unique<d3d_nvenc_encode_device_t>(); auto device = std::make_unique<d3d_nvenc_encode_device_t>();
if (!device->init_device(shared_from_this(), adapter.get(), pix_fmt)) { if (!device->init_device(shared_from_this(), adapter.get(), pix_fmt)) {
return nullptr; return nullptr;
@ -1941,14 +1929,15 @@ namespace platf::dxgi {
return device; return device;
} }
int int init() {
init() {
BOOST_LOG(info) << "Compiling shaders..."sv; BOOST_LOG(info) << "Compiling shaders..."sv;
#define compile_vertex_shader_helper(x) \ #define compile_vertex_shader_helper(x) \
if (!(x##_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) return -1; if (!(x##_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \
return -1;
#define compile_pixel_shader_helper(x) \ #define compile_pixel_shader_helper(x) \
if (!(x##_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) return -1; if (!(x##_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \
return -1;
compile_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps); compile_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps);
compile_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear); compile_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear);

View file

@ -2,10 +2,11 @@
* @file src/platform/windows/display_wgc.cpp * @file src/platform/windows/display_wgc.cpp
* @brief Definitions for WinRT Windows.Graphics.Capture API * @brief Definitions for WinRT Windows.Graphics.Capture API
*/ */
// platform includes
#include <dxgi1_2.h> #include <dxgi1_2.h>
// local includes
#include "display.h" #include "display.h"
#include "misc.h" #include "misc.h"
#include "src/logging.h" #include "src/logging.h"
@ -46,12 +47,15 @@ namespace winrt {
} // namespace winrt } // namespace winrt
#if !WINRT_IMPL_HAS_DECLSPEC_UUID #if !WINRT_IMPL_HAS_DECLSPEC_UUID
static constexpr GUID GUID__IDirect3DDxgiInterfaceAccess = { static constexpr GUID GUID__IDirect3DDxgiInterfaceAccess = {
0xA9B3D012, 0x3DF2, 0x4EE3, { 0xB8, 0xD1, 0x86, 0x95, 0xF4, 0x57, 0xD3, 0xC1 } 0xA9B3D012,
0x3DF2,
0x4EE3,
{0xB8, 0xD1, 0x86, 0x95, 0xF4, 0x57, 0xD3, 0xC1}
// compare with __declspec(uuid(...)) for the struct above. // compare with __declspec(uuid(...)) for the struct above.
}; };
template<> template<>
constexpr auto constexpr auto __mingw_uuidof<winrt::IDirect3DDxgiInterfaceAccess>() -> GUID const & {
__mingw_uuidof<winrt::IDirect3DDxgiInterfaceAccess>() -> GUID const & {
return GUID__IDirect3DDxgiInterfaceAccess; return GUID__IDirect3DDxgiInterfaceAccess;
} }
#endif #endif
@ -62,10 +66,12 @@ namespace platf::dxgi {
} }
wgc_capture_t::~wgc_capture_t() { wgc_capture_t::~wgc_capture_t() {
if (capture_session) if (capture_session) {
capture_session.Close(); capture_session.Close();
if (frame_pool) }
if (frame_pool) {
frame_pool.Close(); frame_pool.Close();
}
item = nullptr; item = nullptr;
capture_session = nullptr; capture_session = nullptr;
frame_pool = nullptr; frame_pool = nullptr;
@ -75,8 +81,7 @@ namespace platf::dxgi {
* @brief Initialize the Windows.Graphics.Capture backend. * @brief Initialize the Windows.Graphics.Capture backend.
* @return 0 on success, -1 on failure. * @return 0 on success, -1 on failure.
*/ */
int int wgc_capture_t::init(display_base_t *display, const ::video::config_t &config) {
wgc_capture_t::init(display_base_t *display, const ::video::config_t &config) {
HRESULT status; HRESULT status;
dxgi::dxgi_t dxgi; dxgi::dxgi_t dxgi;
winrt::com_ptr<::IInspectable> d3d_comhandle; winrt::com_ptr<::IInspectable> d3d_comhandle;
@ -93,8 +98,7 @@ namespace platf::dxgi {
BOOST_LOG(error) << "Failed to query WinRT DirectX interface from device [0x"sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(error) << "Failed to query WinRT DirectX interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1; return -1;
} }
} } catch (winrt::hresult_error &e) {
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() << ']'; 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; return -1;
} }
@ -110,35 +114,32 @@ namespace platf::dxgi {
return -1; return -1;
} }
if (config.dynamicRange) if (config.dynamicRange) {
display->capture_format = DXGI_FORMAT_R16G16B16A16_FLOAT; display->capture_format = DXGI_FORMAT_R16G16B16A16_FLOAT;
else } else {
display->capture_format = DXGI_FORMAT_B8G8R8A8_UNORM; display->capture_format = DXGI_FORMAT_B8G8R8A8_UNORM;
}
try { try {
frame_pool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(uwp_device, static_cast<winrt::Windows::Graphics::DirectX::DirectXPixelFormat>(display->capture_format), 2, item.Size()); 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); capture_session = frame_pool.CreateCaptureSession(item);
frame_pool.FrameArrived({this, &wgc_capture_t::on_frame_arrived}); frame_pool.FrameArrived({this, &wgc_capture_t::on_frame_arrived});
} } catch (winrt::hresult_error &e) {
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() << ']'; 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; return -1;
} }
try { try {
if (winrt::ApiInformation::IsPropertyPresent(L"Windows.Graphics.Capture.GraphicsCaptureSession", L"IsBorderRequired")) { if (winrt::ApiInformation::IsPropertyPresent(L"Windows.Graphics.Capture.GraphicsCaptureSession", L"IsBorderRequired")) {
capture_session.IsBorderRequired(false); capture_session.IsBorderRequired(false);
} } else {
else {
BOOST_LOG(warning) << "Can't disable colored border around capture area on this version of Windows"; BOOST_LOG(warning) << "Can't disable colored border around capture area on this version of Windows";
} }
} } catch (winrt::hresult_error &e) {
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() << ']'; 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 { try {
capture_session.StartCapture(); capture_session.StartCapture();
} } catch (winrt::hresult_error &e) {
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() << ']'; 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 -1;
} }
@ -150,20 +151,19 @@ namespace platf::dxgi {
* To maintain parity with the original display interface, this frame will be consumed by the capture thread. * 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. * Acquire a read-write lock, make the produced frame available to the capture thread, then wake the capture thread.
*/ */
void void wgc_capture_t::on_frame_arrived(winrt::Direct3D11CaptureFramePool const &sender, winrt::IInspectable const &) {
wgc_capture_t::on_frame_arrived(winrt::Direct3D11CaptureFramePool const &sender, winrt::IInspectable const &) {
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame frame {nullptr}; winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame frame {nullptr};
try { try {
frame = sender.TryGetNextFrame(); frame = sender.TryGetNextFrame();
} } catch (winrt::hresult_error &e) {
catch (winrt::hresult_error &e) {
BOOST_LOG(warning) << "Failed to capture frame: "sv << e.code(); BOOST_LOG(warning) << "Failed to capture frame: "sv << e.code();
return; return;
} }
if (frame != nullptr) { if (frame != nullptr) {
AcquireSRWLockExclusive(&frame_lock); AcquireSRWLockExclusive(&frame_lock);
if (produced_frame) if (produced_frame) {
produced_frame.Close(); produced_frame.Close();
}
produced_frame = frame; produced_frame = frame;
ReleaseSRWLockExclusive(&frame_lock); ReleaseSRWLockExclusive(&frame_lock);
@ -178,37 +178,38 @@ namespace platf::dxgi {
* @param out a texture containing the frame just captured * @param out a texture containing the frame just captured
* @param out_time the timestamp of the frame just captured * @param out_time the timestamp of the frame just captured
*/ */
capture_e capture_e wgc_capture_t::next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time) {
wgc_capture_t::next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time) {
// this CONSUMER runs in the capture thread // this CONSUMER runs in the capture thread
release_frame(); release_frame();
AcquireSRWLockExclusive(&frame_lock); AcquireSRWLockExclusive(&frame_lock);
if (produced_frame == nullptr && SleepConditionVariableSRW(&frame_present_cv, &frame_lock, timeout.count(), 0) == 0) { if (produced_frame == nullptr && SleepConditionVariableSRW(&frame_present_cv, &frame_lock, timeout.count(), 0) == 0) {
ReleaseSRWLockExclusive(&frame_lock); ReleaseSRWLockExclusive(&frame_lock);
if (GetLastError() == ERROR_TIMEOUT) if (GetLastError() == ERROR_TIMEOUT) {
return capture_e::timeout; return capture_e::timeout;
else } else {
return capture_e::error; return capture_e::error;
} }
}
if (produced_frame) { if (produced_frame) {
consumed_frame = produced_frame; consumed_frame = produced_frame;
produced_frame = nullptr; produced_frame = nullptr;
} }
ReleaseSRWLockExclusive(&frame_lock); ReleaseSRWLockExclusive(&frame_lock);
if (consumed_frame == nullptr) // spurious wakeup if (consumed_frame == nullptr) { // spurious wakeup
return capture_e::timeout; return capture_e::timeout;
}
auto capture_access = consumed_frame.Surface().as<winrt::IDirect3DDxgiInterfaceAccess>(); auto capture_access = consumed_frame.Surface().as<winrt::IDirect3DDxgiInterfaceAccess>();
if (capture_access == nullptr) if (capture_access == nullptr) {
return capture_e::error; return capture_e::error;
}
capture_access->GetInterface(IID_ID3D11Texture2D, (void **) out); capture_access->GetInterface(IID_ID3D11Texture2D, (void **) out);
out_time = consumed_frame.SystemRelativeTime().count(); // raw ticks from query performance counter out_time = consumed_frame.SystemRelativeTime().count(); // raw ticks from query performance counter
return capture_e::ok; return capture_e::ok;
} }
capture_e capture_e wgc_capture_t::release_frame() {
wgc_capture_t::release_frame() {
if (consumed_frame != nullptr) { if (consumed_frame != nullptr) {
consumed_frame.Close(); consumed_frame.Close();
consumed_frame = nullptr; consumed_frame = nullptr;
@ -216,22 +217,21 @@ namespace platf::dxgi {
return capture_e::ok; return capture_e::ok;
} }
int int wgc_capture_t::set_cursor_visible(bool x) {
wgc_capture_t::set_cursor_visible(bool x) {
try { try {
if (capture_session.IsCursorCaptureEnabled() != x) if (capture_session.IsCursorCaptureEnabled() != x) {
capture_session.IsCursorCaptureEnabled(x); capture_session.IsCursorCaptureEnabled(x);
return 0;
} }
catch (winrt::hresult_error &) { return 0;
} catch (winrt::hresult_error &) {
return -1; return -1;
} }
} }
int int display_wgc_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
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)) {
if (display_base_t::init(config, display_name) || dup.init(this, config))
return -1; return -1;
}
texture.reset(); texture.reset();
return 0; return 0;
@ -244,15 +244,15 @@ namespace platf::dxgi {
* @param timeout how long to wait for the next frame * @param timeout how long to wait for the next frame
* @param cursor_visible whether to capture the cursor * @param cursor_visible whether to capture the cursor
*/ */
capture_e 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) {
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; HRESULT status;
texture2d_t src; texture2d_t src;
uint64_t frame_qpc; uint64_t frame_qpc;
dup.set_cursor_visible(cursor_visible); dup.set_cursor_visible(cursor_visible);
auto capture_status = dup.next_frame(timeout, &src, frame_qpc); auto capture_status = dup.next_frame(timeout, &src, frame_qpc);
if (capture_status != capture_e::ok) if (capture_status != capture_e::ok) {
return capture_status; return capture_status;
}
auto frame_timestamp = std::chrono::steady_clock::now() - qpc_time_difference(qpc_counter(), frame_qpc); auto frame_timestamp = std::chrono::steady_clock::now() - qpc_time_difference(qpc_counter(), frame_qpc);
D3D11_TEXTURE2D_DESC desc; D3D11_TEXTURE2D_DESC desc;
@ -329,8 +329,7 @@ namespace platf::dxgi {
return capture_e::ok; return capture_e::ok;
} }
capture_e capture_e display_wgc_ram_t::release_snapshot() {
display_wgc_ram_t::release_snapshot() {
return dup.release_frame(); return dup.release_frame();
} }
} // namespace platf::dxgi } // namespace platf::dxgi

View file

@ -3,13 +3,18 @@
* @brief Definitions for input handling on Windows. * @brief Definitions for input handling on Windows.
*/ */
#define WINVER 0x0A00 #define WINVER 0x0A00
// platform includes
#include <windows.h> #include <windows.h>
// standard includes
#include <cmath> #include <cmath>
#include <thread> #include <thread>
// lib includes
#include <ViGEm/Client.h> #include <ViGEm/Client.h>
// local includes
#include "keylayout.h" #include "keylayout.h"
#include "misc.h" #include "misc.h"
#include "src/config.h" #include "src/config.h"
@ -19,12 +24,9 @@
#ifdef __MINGW32__ #ifdef __MINGW32__
DECLARE_HANDLE(HSYNTHETICPOINTERDEVICE); DECLARE_HANDLE(HSYNTHETICPOINTERDEVICE);
WINUSERAPI HSYNTHETICPOINTERDEVICE WINAPI WINUSERAPI HSYNTHETICPOINTERDEVICE WINAPI CreateSyntheticPointerDevice(POINTER_INPUT_TYPE pointerType, ULONG maxCount, POINTER_FEEDBACK_MODE mode);
CreateSyntheticPointerDevice(POINTER_INPUT_TYPE pointerType, ULONG maxCount, POINTER_FEEDBACK_MODE mode); WINUSERAPI BOOL WINAPI InjectSyntheticPointerInput(HSYNTHETICPOINTERDEVICE device, CONST POINTER_TYPE_INFO *pointerInfo, UINT32 count);
WINUSERAPI BOOL WINAPI WINUSERAPI VOID WINAPI DestroySyntheticPointerDevice(HSYNTHETICPOINTERDEVICE device);
InjectSyntheticPointerInput(HSYNTHETICPOINTERDEVICE device, CONST POINTER_TYPE_INFO *pointerInfo, UINT32 count);
WINUSERAPI VOID WINAPI
DestroySyntheticPointerDevice(HSYNTHETICPOINTERDEVICE device);
#endif #endif
namespace platf { namespace platf {
@ -33,28 +35,32 @@ namespace platf {
thread_local HDESK _lastKnownInputDesktop = nullptr; thread_local HDESK _lastKnownInputDesktop = nullptr;
constexpr touch_port_t target_touch_port { constexpr touch_port_t target_touch_port {
0, 0, 0,
65535, 65535 0,
65535,
65535
}; };
using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>; using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>;
using target_t = util::safe_ptr<_VIGEM_TARGET_T, vigem_target_free>; using target_t = util::safe_ptr<_VIGEM_TARGET_T, vigem_target_free>;
void CALLBACK void CALLBACK x360_notify(
x360_notify(
client_t::pointer client, client_t::pointer client,
target_t::pointer target, target_t::pointer target,
std::uint8_t largeMotor, std::uint8_t smallMotor, std::uint8_t largeMotor,
std::uint8_t smallMotor,
std::uint8_t /* led_number */, std::uint8_t /* led_number */,
void *userdata); void *userdata
);
void CALLBACK void CALLBACK ds4_notify(
ds4_notify(
client_t::pointer client, client_t::pointer client,
target_t::pointer target, target_t::pointer target,
std::uint8_t largeMotor, std::uint8_t smallMotor, std::uint8_t largeMotor,
std::uint8_t smallMotor,
DS4_LIGHTBAR_COLOR /* led_color */, DS4_LIGHTBAR_COLOR /* led_color */,
void *userdata); void *userdata
);
struct gp_touch_context_t { struct gp_touch_context_t {
uint8_t pointerIndex; uint8_t pointerIndex;
@ -134,8 +140,7 @@ namespace platf {
* @param y Y component of motion. * @param y Y component of motion.
* @param z Z component of motion. * @param z Z component of motion.
*/ */
static void static void ds4_update_motion(gamepad_context_t &gamepad, uint8_t motion_type, float x, float y, float z) {
ds4_update_motion(gamepad_context_t &gamepad, uint8_t motion_type, float x, float y, float z) {
auto &report = gamepad.report.ds4.Report; auto &report = gamepad.report.ds4.Report;
// Use int32 to process this data, so we can clamp if needed. // Use int32 to process this data, so we can clamp if needed.
@ -192,8 +197,7 @@ namespace platf {
class vigem_t { class vigem_t {
public: public:
int int init() {
init() {
// Probe ViGEm during startup to see if we can successfully attach gamepads. This will allow us to // Probe ViGEm during startup to see if we can successfully attach gamepads. This will allow us to
// immediately display the error message in the web UI even before the user tries to stream. // immediately display the error message in the web UI even before the user tries to stream.
client_t client {vigem_alloc()}; client_t client {vigem_alloc()};
@ -201,8 +205,7 @@ namespace platf {
if (!VIGEM_SUCCESS(status)) { if (!VIGEM_SUCCESS(status)) {
// Log a special fatal message for this case to show the error in the web UI // Log a special fatal message for this case to show the error in the web UI
BOOST_LOG(fatal) << "ViGEmBus is not installed or running. You must install ViGEmBus for gamepad support!"sv; BOOST_LOG(fatal) << "ViGEmBus is not installed or running. You must install ViGEmBus for gamepad support!"sv;
} } else {
else {
vigem_disconnect(client.get()); vigem_disconnect(client.get());
} }
@ -218,8 +221,7 @@ namespace platf {
* @param gp_type The type of gamepad. * @param gp_type The type of gamepad.
* @return 0 on success. * @return 0 on success.
*/ */
int int alloc_gamepad_internal(const gamepad_id_t &id, feedback_queue_t &feedback_queue, VIGEM_TARGET_TYPE gp_type) {
alloc_gamepad_internal(const gamepad_id_t &id, feedback_queue_t &feedback_queue, VIGEM_TARGET_TYPE gp_type) {
auto &gamepad = gamepads[id.globalIndex]; auto &gamepad = gamepads[id.globalIndex];
assert(!gamepad.gp); assert(!gamepad.gp);
@ -242,8 +244,7 @@ namespace platf {
if (gp_type == Xbox360Wired) { if (gp_type == Xbox360Wired) {
gamepad.gp.reset(vigem_target_x360_alloc()); gamepad.gp.reset(vigem_target_x360_alloc());
XUSB_REPORT_INIT(&gamepad.report.x360); XUSB_REPORT_INIT(&gamepad.report.x360);
} } else {
else {
gamepad.gp.reset(vigem_target_ds4_alloc()); gamepad.gp.reset(vigem_target_ds4_alloc());
// There is no equivalent DS4_REPORT_EX_INIT() // There is no equivalent DS4_REPORT_EX_INIT()
@ -272,8 +273,7 @@ namespace platf {
if (gp_type == Xbox360Wired) { if (gp_type == Xbox360Wired) {
status = vigem_target_x360_register_notification(client.get(), gamepad.gp.get(), x360_notify, this); status = vigem_target_x360_register_notification(client.get(), gamepad.gp.get(), x360_notify, this);
} } else {
else {
status = vigem_target_ds4_register_notification(client.get(), gamepad.gp.get(), ds4_notify, this); status = vigem_target_ds4_register_notification(client.get(), gamepad.gp.get(), ds4_notify, this);
} }
@ -288,8 +288,7 @@ namespace platf {
* @brief Detaches the specified gamepad * @brief Detaches the specified gamepad
* @param nr The gamepad. * @param nr The gamepad.
*/ */
void void free_target(int nr) {
free_target(int nr) {
auto &gamepad = gamepads[nr]; auto &gamepad = gamepads[nr];
if (gamepad.repeat_task) { if (gamepad.repeat_task) {
@ -327,8 +326,7 @@ namespace platf {
* @param largeMotor The large motor. * @param largeMotor The large motor.
* @param smallMotor The small motor. * @param smallMotor The small motor.
*/ */
void void rumble(target_t::pointer target, std::uint8_t largeMotor, std::uint8_t smallMotor) {
rumble(target_t::pointer target, std::uint8_t largeMotor, std::uint8_t smallMotor) {
for (int x = 0; x < gamepads.size(); ++x) { for (int x = 0; x < gamepads.size(); ++x) {
auto &gamepad = gamepads[x]; auto &gamepad = gamepads[x];
@ -342,7 +340,10 @@ namespace platf {
normalizedLargeMotor != gamepad.last_rumble.data.rumble.lowfreq) { normalizedLargeMotor != gamepad.last_rumble.data.rumble.lowfreq) {
// We have to use the client-relative index when communicating back to the client // We have to use the client-relative index when communicating back to the client
gamepad_feedback_msg_t msg = gamepad_feedback_msg_t::make_rumble( gamepad_feedback_msg_t msg = gamepad_feedback_msg_t::make_rumble(
gamepad.client_relative_index, normalizedLargeMotor, normalizedSmallMotor); gamepad.client_relative_index,
normalizedLargeMotor,
normalizedSmallMotor
);
gamepad.feedback_queue->raise(msg); gamepad.feedback_queue->raise(msg);
gamepad.last_rumble = msg; gamepad.last_rumble = msg;
} }
@ -358,8 +359,7 @@ namespace platf {
* @param g The red channel. * @param g The red channel.
* @param b The red channel. * @param b The red channel.
*/ */
void void set_rgb_led(target_t::pointer target, std::uint8_t r, std::uint8_t g, std::uint8_t b) {
set_rgb_led(target_t::pointer target, std::uint8_t r, std::uint8_t g, std::uint8_t b) {
for (int x = 0; x < gamepads.size(); ++x) { for (int x = 0; x < gamepads.size(); ++x) {
auto &gamepad = gamepads[x]; auto &gamepad = gamepads[x];
@ -401,13 +401,14 @@ namespace platf {
client_t client; client_t client;
}; };
void CALLBACK void CALLBACK x360_notify(
x360_notify(
client_t::pointer client, client_t::pointer client,
target_t::pointer target, target_t::pointer target,
std::uint8_t largeMotor, std::uint8_t smallMotor, std::uint8_t largeMotor,
std::uint8_t smallMotor,
std::uint8_t /* led_number */, std::uint8_t /* led_number */,
void *userdata) { void *userdata
) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "largeMotor: "sv << (int) largeMotor << std::endl << "largeMotor: "sv << (int) largeMotor << std::endl
<< "smallMotor: "sv << (int) smallMotor; << "smallMotor: "sv << (int) smallMotor;
@ -415,13 +416,14 @@ namespace platf {
task_pool.push(&vigem_t::rumble, (vigem_t *) userdata, target, largeMotor, smallMotor); task_pool.push(&vigem_t::rumble, (vigem_t *) userdata, target, largeMotor, smallMotor);
} }
void CALLBACK void CALLBACK ds4_notify(
ds4_notify(
client_t::pointer client, client_t::pointer client,
target_t::pointer target, target_t::pointer target,
std::uint8_t largeMotor, std::uint8_t smallMotor, std::uint8_t largeMotor,
std::uint8_t smallMotor,
DS4_LIGHTBAR_COLOR led_color, DS4_LIGHTBAR_COLOR led_color,
void *userdata) { void *userdata
) {
BOOST_LOG(debug) BOOST_LOG(debug)
<< "largeMotor: "sv << (int) largeMotor << std::endl << "largeMotor: "sv << (int) largeMotor << std::endl
<< "smallMotor: "sv << (int) smallMotor << std::endl << "smallMotor: "sv << (int) smallMotor << std::endl
@ -445,8 +447,7 @@ namespace platf {
decltype(DestroySyntheticPointerDevice) *fnDestroySyntheticPointerDevice; decltype(DestroySyntheticPointerDevice) *fnDestroySyntheticPointerDevice;
}; };
input_t input_t input() {
input() {
input_t result {new input_raw_t {}}; input_t result {new input_raw_t {}};
auto &raw = *(input_raw_t *) result.get(); auto &raw = *(input_raw_t *) result.get();
@ -468,8 +469,7 @@ namespace platf {
* @brief Calls SendInput() and switches input desktops if required. * @brief Calls SendInput() and switches input desktops if required.
* @param i The `INPUT` struct to send. * @param i The `INPUT` struct to send.
*/ */
void void send_input(INPUT &i) {
send_input(INPUT &i) {
retry: retry:
auto send = SendInput(1, &i, sizeof(INPUT)); auto send = SendInput(1, &i, sizeof(INPUT));
if (send != 1) { if (send != 1) {
@ -491,8 +491,7 @@ namespace platf {
* @param count The number of elements in `pointerInfo`. * @param count The number of elements in `pointerInfo`.
* @return true if input was successfully injected. * @return true if input was successfully injected.
*/ */
bool bool inject_synthetic_pointer_input(input_raw_t *input, HSYNTHETICPOINTERDEVICE device, const POINTER_TYPE_INFO *pointerInfo, UINT32 count) {
inject_synthetic_pointer_input(input_raw_t *input, HSYNTHETICPOINTERDEVICE device, const POINTER_TYPE_INFO *pointerInfo, UINT32 count) {
retry: retry:
if (!input->fnInjectSyntheticPointerInput(device, pointerInfo, count)) { if (!input->fnInjectSyntheticPointerInput(device, pointerInfo, count)) {
auto hDesk = syncThreadDesktop(); auto hDesk = syncThreadDesktop();
@ -505,8 +504,7 @@ namespace platf {
return true; return true;
} }
void void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
INPUT i {}; INPUT i {};
i.type = INPUT_MOUSE; i.type = INPUT_MOUSE;
@ -528,8 +526,7 @@ namespace platf {
send_input(i); send_input(i);
} }
void void move_mouse(input_t &input, int deltaX, int deltaY) {
move_mouse(input_t &input, int deltaX, int deltaY) {
INPUT i {}; INPUT i {};
i.type = INPUT_MOUSE; i.type = INPUT_MOUSE;
@ -542,8 +539,7 @@ namespace platf {
send_input(i); send_input(i);
} }
util::point_t util::point_t get_mouse_loc(input_t &input) {
get_mouse_loc(input_t &input) {
throw std::runtime_error("not implemented yet, has to pass tests"); throw std::runtime_error("not implemented yet, has to pass tests");
// TODO: Tests are failing, something wrong here? // TODO: Tests are failing, something wrong here?
POINT p; POINT p;
@ -557,8 +553,7 @@ namespace platf {
}; };
} }
void void button_mouse(input_t &input, int button, bool release) {
button_mouse(input_t &input, int button, bool release) {
INPUT i {}; INPUT i {};
i.type = INPUT_MOUSE; i.type = INPUT_MOUSE;
@ -566,18 +561,14 @@ namespace platf {
if (button == 1) { if (button == 1) {
mi.dwFlags = release ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_LEFTDOWN; mi.dwFlags = release ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_LEFTDOWN;
} } else if (button == 2) {
else if (button == 2) {
mi.dwFlags = release ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_MIDDLEDOWN; mi.dwFlags = release ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_MIDDLEDOWN;
} } else if (button == 3) {
else if (button == 3) {
mi.dwFlags = release ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_RIGHTDOWN; mi.dwFlags = release ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_RIGHTDOWN;
} } else if (button == 4) {
else if (button == 4) {
mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN; mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN;
mi.mouseData = XBUTTON1; mi.mouseData = XBUTTON1;
} } else {
else {
mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN; mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN;
mi.mouseData = XBUTTON2; mi.mouseData = XBUTTON2;
} }
@ -585,8 +576,7 @@ namespace platf {
send_input(i); send_input(i);
} }
void void scroll(input_t &input, int distance) {
scroll(input_t &input, int distance) {
INPUT i {}; INPUT i {};
i.type = INPUT_MOUSE; i.type = INPUT_MOUSE;
@ -598,8 +588,7 @@ namespace platf {
send_input(i); send_input(i);
} }
void void hscroll(input_t &input, int distance) {
hscroll(input_t &input, int distance) {
INPUT i {}; INPUT i {};
i.type = INPUT_MOUSE; i.type = INPUT_MOUSE;
@ -611,8 +600,7 @@ namespace platf {
send_input(i); send_input(i);
} }
void void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
INPUT i {}; INPUT i {};
i.type = INPUT_KEYBOARD; i.type = INPUT_KEYBOARD;
auto &ki = i.ki; auto &ki = i.ki;
@ -623,8 +611,7 @@ namespace platf {
if (!(flags & SS_KBE_FLAG_NON_NORMALIZED)) { if (!(flags & SS_KBE_FLAG_NON_NORMALIZED)) {
// Mask off the extended key byte // Mask off the extended key byte
ki.wScan = VK_TO_SCANCODE_MAP[modcode & 0xFF]; ki.wScan = VK_TO_SCANCODE_MAP[modcode & 0xFF];
} } else if (config::input.always_send_scancodes && modcode != VK_LWIN && modcode != VK_RWIN && modcode != VK_PAUSE) {
else if (config::input.always_send_scancodes && modcode != VK_LWIN && modcode != VK_RWIN && modcode != VK_PAUSE) {
// For some reason, MapVirtualKey(VK_LWIN, MAPVK_VK_TO_VSC) doesn't seem to work :/ // For some reason, MapVirtualKey(VK_LWIN, MAPVK_VK_TO_VSC) doesn't seem to work :/
ki.wScan = MapVirtualKey(modcode, MAPVK_VK_TO_VSC); ki.wScan = MapVirtualKey(modcode, MAPVK_VK_TO_VSC);
} }
@ -632,8 +619,7 @@ namespace platf {
// If we can map this to a scancode, send it as a scancode for maximum game compatibility. // If we can map this to a scancode, send it as a scancode for maximum game compatibility.
if (ki.wScan) { if (ki.wScan) {
ki.dwFlags = KEYEVENTF_SCANCODE; ki.dwFlags = KEYEVENTF_SCANCODE;
} } else {
else {
// If there is no scancode mapping or it's non-normalized, send it as a regular VK event. // If there is no scancode mapping or it's non-normalized, send it as a regular VK event.
ki.wVk = modcode; ki.wVk = modcode;
} }
@ -712,8 +698,7 @@ namespace platf {
* @param input The global input context. * @param input The global input context.
* @return A unique pointer to a per-client input data context. * @return A unique pointer to a per-client input data context.
*/ */
std::unique_ptr<client_input_t> std::unique_ptr<client_input_t> allocate_client_input_context(input_t &input) {
allocate_client_input_context(input_t &input) {
return std::make_unique<client_input_raw_t>(input); return std::make_unique<client_input_raw_t>(input);
} }
@ -722,8 +707,7 @@ namespace platf {
* @details Since this swaps entries around, all slot pointers/references are invalid after compaction. * @details Since this swaps entries around, all slot pointers/references are invalid after compaction.
* @param raw The client-specific input context. * @param raw The client-specific input context.
*/ */
void void perform_touch_compaction(client_input_raw_t *raw) {
perform_touch_compaction(client_input_raw_t *raw) {
// Windows requires all active touches be contiguous when fed into InjectSyntheticPointerInput(). // Windows requires all active touches be contiguous when fed into InjectSyntheticPointerInput().
UINT32 i; UINT32 i;
for (i = 0; i < ARRAYSIZE(raw->touchInfo); i++) { for (i = 0; i < ARRAYSIZE(raw->touchInfo); i++) {
@ -754,8 +738,7 @@ namespace platf {
* @param eventType The LI_TOUCH_EVENT value from the client. * @param eventType The LI_TOUCH_EVENT value from the client.
* @return A pointer to the slot entry. * @return A pointer to the slot entry.
*/ */
POINTER_TYPE_INFO * POINTER_TYPE_INFO *pointer_by_id(client_input_raw_t *raw, uint32_t pointerId, uint8_t eventType) {
pointer_by_id(client_input_raw_t *raw, uint32_t pointerId, uint8_t eventType) {
// Compact active touches into a single contiguous block // Compact active touches into a single contiguous block
perform_touch_compaction(raw); perform_touch_compaction(raw);
@ -795,8 +778,7 @@ namespace platf {
* @param x The normalized 0.0-1.0 X coordinate. * @param x The normalized 0.0-1.0 X coordinate.
* @param y The normalized 0.0-1.0 Y coordinate. * @param y The normalized 0.0-1.0 Y coordinate.
*/ */
void void populate_common_pointer_info(POINTER_INFO &pointerInfo, const touch_port_t &touchPort, uint8_t eventType, float x, float y) {
populate_common_pointer_info(POINTER_INFO &pointerInfo, const touch_port_t &touchPort, uint8_t eventType, float x, float y) {
switch (eventType) { switch (eventType) {
case LI_TOUCH_EVENT_HOVER: case LI_TOUCH_EVENT_HOVER:
pointerInfo.pointerFlags &= ~POINTER_FLAG_INCONTACT; pointerInfo.pointerFlags &= ~POINTER_FLAG_INCONTACT;
@ -825,8 +807,7 @@ namespace platf {
// we'll set POINTER_FLAG_UP, otherwise set POINTER_FLAG_UPDATE. // we'll set POINTER_FLAG_UP, otherwise set POINTER_FLAG_UPDATE.
if (pointerInfo.pointerFlags & POINTER_FLAG_INCONTACT) { if (pointerInfo.pointerFlags & POINTER_FLAG_INCONTACT) {
pointerInfo.pointerFlags |= POINTER_FLAG_UP; pointerInfo.pointerFlags |= POINTER_FLAG_UP;
} } else {
else {
pointerInfo.pointerFlags |= POINTER_FLAG_UPDATE; pointerInfo.pointerFlags |= POINTER_FLAG_UPDATE;
} }
pointerInfo.pointerFlags &= ~(POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE); pointerInfo.pointerFlags &= ~(POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE);
@ -857,8 +838,7 @@ namespace platf {
* @brief Repeats the current touch state to avoid the interactions timing out. * @brief Repeats the current touch state to avoid the interactions timing out.
* @param raw The raw client-specific input context. * @param raw The raw client-specific input context.
*/ */
void void repeat_touch(client_input_raw_t *raw) {
repeat_touch(client_input_raw_t *raw) {
if (!inject_synthetic_pointer_input(raw->global, raw->touch, raw->touchInfo, raw->activeTouchSlots)) { if (!inject_synthetic_pointer_input(raw->global, raw->touch, raw->touchInfo, raw->activeTouchSlots)) {
auto err = GetLastError(); auto err = GetLastError();
BOOST_LOG(warning) << "Failed to refresh virtual touch input: "sv << err; BOOST_LOG(warning) << "Failed to refresh virtual touch input: "sv << err;
@ -871,8 +851,7 @@ namespace platf {
* @brief Repeats the current pen state to avoid the interactions timing out. * @brief Repeats the current pen state to avoid the interactions timing out.
* @param raw The raw client-specific input context. * @param raw The raw client-specific input context.
*/ */
void void repeat_pen(client_input_raw_t *raw) {
repeat_pen(client_input_raw_t *raw) {
if (!inject_synthetic_pointer_input(raw->global, raw->pen, &raw->penInfo, 1)) { if (!inject_synthetic_pointer_input(raw->global, raw->pen, &raw->penInfo, 1)) {
auto err = GetLastError(); auto err = GetLastError();
BOOST_LOG(warning) << "Failed to refresh virtual pen input: "sv << err; BOOST_LOG(warning) << "Failed to refresh virtual pen input: "sv << err;
@ -885,8 +864,7 @@ namespace platf {
* @brief Cancels all active touches. * @brief Cancels all active touches.
* @param raw The raw client-specific input context. * @param raw The raw client-specific input context.
*/ */
void void cancel_all_active_touches(client_input_raw_t *raw) {
cancel_all_active_touches(client_input_raw_t *raw) {
// Cancel touch repeat callbacks // Cancel touch repeat callbacks
if (raw->touchRepeatTask) { if (raw->touchRepeatTask) {
task_pool.cancel(raw->touchRepeatTask); task_pool.cancel(raw->touchRepeatTask);
@ -922,8 +900,7 @@ namespace platf {
* @param touch_port The current viewport for translating to screen coordinates. * @param touch_port The current viewport for translating to screen coordinates.
* @param touch The touch event. * @param touch The touch event.
*/ */
void void touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
auto raw = (client_input_raw_t *) input; auto raw = (client_input_raw_t *) input;
// Bail if we're not running on an OS that supports virtual touch input // Bail if we're not running on an OS that supports virtual touch input
@ -944,8 +921,7 @@ namespace platf {
BOOST_LOG(warning) << "Failed to create virtual touch device: "sv << err; BOOST_LOG(warning) << "Failed to create virtual touch device: "sv << err;
return; return;
} }
} } else {
else {
// No need to cancel anything if we had no touch input device // No need to cancel anything if we had no touch input device
return; return;
} }
@ -991,8 +967,7 @@ namespace platf {
// Convert the 0.0f..1.0f float to the 0..1024 range that Windows uses // Convert the 0.0f..1.0f float to the 0..1024 range that Windows uses
touchInfo.pressure = (UINT32) (touch.pressureOrDistance * 1024); touchInfo.pressure = (UINT32) (touch.pressureOrDistance * 1024);
} } else {
else {
// The default touch pressure is 512 // The default touch pressure is 512
touchInfo.pressure = 512; touchInfo.pressure = 512;
} }
@ -1018,8 +993,7 @@ namespace platf {
touchInfo.touchMask |= TOUCH_MASK_CONTACTAREA; touchInfo.touchMask |= TOUCH_MASK_CONTACTAREA;
} }
} } else {
else {
touchInfo.pressure = 0; touchInfo.pressure = 0;
touchInfo.rcContact = {}; touchInfo.rcContact = {};
} }
@ -1027,8 +1001,7 @@ namespace platf {
if (touch.rotation != LI_ROT_UNKNOWN) { if (touch.rotation != LI_ROT_UNKNOWN) {
touchInfo.touchMask |= TOUCH_MASK_ORIENTATION; touchInfo.touchMask |= TOUCH_MASK_ORIENTATION;
touchInfo.orientation = touch.rotation; touchInfo.orientation = touch.rotation;
} } else {
else {
touchInfo.orientation = 0; touchInfo.orientation = 0;
} }
@ -1053,8 +1026,7 @@ namespace platf {
* @param touch_port The current viewport for translating to screen coordinates. * @param touch_port The current viewport for translating to screen coordinates.
* @param pen The pen event. * @param pen The pen event.
*/ */
void void pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
auto raw = (client_input_raw_t *) input; auto raw = (client_input_raw_t *) input;
// Bail if we're not running on an OS that supports virtual pen input // Bail if we're not running on an OS that supports virtual pen input
@ -1075,8 +1047,7 @@ namespace platf {
BOOST_LOG(warning) << "Failed to create virtual pen device: "sv << err; BOOST_LOG(warning) << "Failed to create virtual pen device: "sv << err;
return; return;
} }
} } else {
else {
// No need to cancel anything if we had no pen input device // No need to cancel anything if we had no pen input device
return; return;
} }
@ -1100,8 +1071,7 @@ namespace platf {
// Windows only supports a single pen button, so send all buttons as the barrel button // Windows only supports a single pen button, so send all buttons as the barrel button
if (pen.penButtons) { if (pen.penButtons) {
penInfo.penFlags |= PEN_FLAG_BARREL; penInfo.penFlags |= PEN_FLAG_BARREL;
} } else {
else {
penInfo.penFlags &= ~PEN_FLAG_BARREL; penInfo.penFlags &= ~PEN_FLAG_BARREL;
} }
@ -1126,8 +1096,7 @@ namespace platf {
// Convert the 0.0f..1.0f float to the 0..1024 range that Windows uses // Convert the 0.0f..1.0f float to the 0..1024 range that Windows uses
penInfo.pressure = (UINT32) (pen.pressureOrDistance * 1024); penInfo.pressure = (UINT32) (pen.pressureOrDistance * 1024);
} } else {
else {
// The default pen pressure is 0 // The default pen pressure is 0
penInfo.pressure = 0; penInfo.pressure = 0;
} }
@ -1135,8 +1104,7 @@ namespace platf {
if (pen.rotation != LI_ROT_UNKNOWN) { if (pen.rotation != LI_ROT_UNKNOWN) {
penInfo.penMask |= PEN_MASK_ROTATION; penInfo.penMask |= PEN_MASK_ROTATION;
penInfo.rotation = pen.rotation; penInfo.rotation = pen.rotation;
} } else {
else {
penInfo.rotation = 0; penInfo.rotation = 0;
} }
@ -1151,8 +1119,7 @@ namespace platf {
penInfo.penMask |= PEN_MASK_TILT_X | PEN_MASK_TILT_Y; penInfo.penMask |= PEN_MASK_TILT_X | PEN_MASK_TILT_Y;
penInfo.tiltX = (INT32) (std::atan2(std::sin(-rotationRads) * r, z) * 180.f / M_PI); penInfo.tiltX = (INT32) (std::atan2(std::sin(-rotationRads) * r, z) * 180.f / M_PI);
penInfo.tiltY = (INT32) (std::atan2(std::cos(-rotationRads) * r, z) * 180.f / M_PI); penInfo.tiltY = (INT32) (std::atan2(std::cos(-rotationRads) * r, z) * 180.f / M_PI);
} } else {
else {
penInfo.tiltX = 0; penInfo.tiltX = 0;
penInfo.tiltY = 0; penInfo.tiltY = 0;
} }
@ -1172,8 +1139,7 @@ namespace platf {
} }
} }
void void unicode(input_t &input, char *utf8, int size) {
unicode(input_t &input, char *utf8, int size) {
// We can do no worse than one UTF-16 character per byte of UTF-8 // We can do no worse than one UTF-16 character per byte of UTF-8
WCHAR wide[size]; WCHAR wide[size];
@ -1201,8 +1167,7 @@ namespace platf {
} }
} }
int int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
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(); auto raw = (input_raw_t *) input.get();
if (!raw->vigem) { if (!raw->vigem) {
@ -1214,28 +1179,22 @@ namespace platf {
if (config::input.gamepad == "x360"sv) { if (config::input.gamepad == "x360"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox 360 controller (manual selection)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox 360 controller (manual selection)"sv;
selectedGamepadType = Xbox360Wired; selectedGamepadType = Xbox360Wired;
} } else if (config::input.gamepad == "ds4"sv) {
else if (config::input.gamepad == "ds4"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 4 controller (manual selection)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 4 controller (manual selection)"sv;
selectedGamepadType = DualShock4Wired; selectedGamepadType = DualShock4Wired;
} } else if (metadata.type == LI_CTYPE_PS) {
else if (metadata.type == LI_CTYPE_PS) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 4 controller (auto-selected by client-reported type)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 4 controller (auto-selected by client-reported type)"sv;
selectedGamepadType = DualShock4Wired; selectedGamepadType = DualShock4Wired;
} } else if (metadata.type == LI_CTYPE_XBOX) {
else if (metadata.type == LI_CTYPE_XBOX) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox 360 controller (auto-selected by client-reported type)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox 360 controller (auto-selected by client-reported type)"sv;
selectedGamepadType = Xbox360Wired; selectedGamepadType = Xbox360Wired;
} } 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 4 controller (auto-selected by motion sensor presence)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 4 controller (auto-selected by motion sensor presence)"sv;
selectedGamepadType = DualShock4Wired; selectedGamepadType = DualShock4Wired;
} } 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 4 controller (auto-selected by touchpad presence)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 4 controller (auto-selected by touchpad presence)"sv;
selectedGamepadType = DualShock4Wired; selectedGamepadType = DualShock4Wired;
} } else {
else {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox 360 controller (default)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox 360 controller (default)"sv;
selectedGamepadType = Xbox360Wired; selectedGamepadType = Xbox360Wired;
} }
@ -1250,8 +1209,7 @@ namespace platf {
if (metadata.capabilities & LI_CCAP_RGB_LED) { if (metadata.capabilities & LI_CCAP_RGB_LED) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " has an RGB LED, but it is not usable when emulating an Xbox 360 controller"sv; BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " has an RGB LED, but it is not usable when emulating an Xbox 360 controller"sv;
} }
} } else if (selectedGamepadType == DualShock4Wired) {
else if (selectedGamepadType == DualShock4Wired) {
if (!(metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) { if (!(metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " is emulating a DualShock 4 controller, but the client gamepad doesn't have motion sensors active"sv; BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " is emulating a DualShock 4 controller, but the client gamepad doesn't have motion sensors active"sv;
} }
@ -1263,8 +1221,7 @@ namespace platf {
return raw->vigem->alloc_gamepad_internal(id, feedback_queue, selectedGamepadType); return raw->vigem->alloc_gamepad_internal(id, feedback_queue, selectedGamepadType);
} }
void void free_gamepad(input_t &input, int nr) {
free_gamepad(input_t &input, int nr) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
if (!raw->vigem) { if (!raw->vigem) {
@ -1279,26 +1236,55 @@ namespace platf {
* @param gamepad_state The gamepad button/axis state sent from the client. * @param gamepad_state The gamepad button/axis state sent from the client.
* @return XUSB_BUTTON flags. * @return XUSB_BUTTON flags.
*/ */
static XUSB_BUTTON static XUSB_BUTTON x360_buttons(const gamepad_state_t &gamepad_state) {
x360_buttons(const gamepad_state_t &gamepad_state) {
int buttons {}; int buttons {};
auto flags = gamepad_state.buttonFlags; auto flags = gamepad_state.buttonFlags;
if (flags & DPAD_UP) buttons |= XUSB_GAMEPAD_DPAD_UP; if (flags & DPAD_UP) {
if (flags & DPAD_DOWN) buttons |= XUSB_GAMEPAD_DPAD_DOWN; buttons |= XUSB_GAMEPAD_DPAD_UP;
if (flags & DPAD_LEFT) buttons |= XUSB_GAMEPAD_DPAD_LEFT; }
if (flags & DPAD_RIGHT) buttons |= XUSB_GAMEPAD_DPAD_RIGHT; if (flags & DPAD_DOWN) {
if (flags & START) buttons |= XUSB_GAMEPAD_START; buttons |= XUSB_GAMEPAD_DPAD_DOWN;
if (flags & BACK) buttons |= XUSB_GAMEPAD_BACK; }
if (flags & LEFT_STICK) buttons |= XUSB_GAMEPAD_LEFT_THUMB; if (flags & DPAD_LEFT) {
if (flags & RIGHT_STICK) buttons |= XUSB_GAMEPAD_RIGHT_THUMB; buttons |= XUSB_GAMEPAD_DPAD_LEFT;
if (flags & LEFT_BUTTON) buttons |= XUSB_GAMEPAD_LEFT_SHOULDER; }
if (flags & RIGHT_BUTTON) buttons |= XUSB_GAMEPAD_RIGHT_SHOULDER; if (flags & DPAD_RIGHT) {
if (flags & (HOME | MISC_BUTTON)) buttons |= XUSB_GAMEPAD_GUIDE; buttons |= XUSB_GAMEPAD_DPAD_RIGHT;
if (flags & A) buttons |= XUSB_GAMEPAD_A; }
if (flags & B) buttons |= XUSB_GAMEPAD_B; if (flags & START) {
if (flags & X) buttons |= XUSB_GAMEPAD_X; buttons |= XUSB_GAMEPAD_START;
if (flags & Y) buttons |= XUSB_GAMEPAD_Y; }
if (flags & BACK) {
buttons |= XUSB_GAMEPAD_BACK;
}
if (flags & LEFT_STICK) {
buttons |= XUSB_GAMEPAD_LEFT_THUMB;
}
if (flags & RIGHT_STICK) {
buttons |= XUSB_GAMEPAD_RIGHT_THUMB;
}
if (flags & LEFT_BUTTON) {
buttons |= XUSB_GAMEPAD_LEFT_SHOULDER;
}
if (flags & RIGHT_BUTTON) {
buttons |= XUSB_GAMEPAD_RIGHT_SHOULDER;
}
if (flags & (HOME | MISC_BUTTON)) {
buttons |= XUSB_GAMEPAD_GUIDE;
}
if (flags & A) {
buttons |= XUSB_GAMEPAD_A;
}
if (flags & B) {
buttons |= XUSB_GAMEPAD_B;
}
if (flags & X) {
buttons |= XUSB_GAMEPAD_X;
}
if (flags & Y) {
buttons |= XUSB_GAMEPAD_Y;
}
return (XUSB_BUTTON) buttons; return (XUSB_BUTTON) buttons;
} }
@ -1308,8 +1294,7 @@ namespace platf {
* @param gamepad The gamepad to update. * @param gamepad The gamepad to update.
* @param gamepad_state The gamepad button/axis state sent from the client. * @param gamepad_state The gamepad button/axis state sent from the client.
*/ */
static void static void x360_update_state(gamepad_context_t &gamepad, const gamepad_state_t &gamepad_state) {
x360_update_state(gamepad_context_t &gamepad, const gamepad_state_t &gamepad_state) {
auto &report = gamepad.report.x360; auto &report = gamepad.report.x360;
report.wButtons = x360_buttons(gamepad_state); report.wButtons = x360_buttons(gamepad_state);
@ -1321,17 +1306,14 @@ namespace platf {
report.sThumbRY = gamepad_state.rsY; report.sThumbRY = gamepad_state.rsY;
} }
static DS4_DPAD_DIRECTIONS static DS4_DPAD_DIRECTIONS ds4_dpad(const gamepad_state_t &gamepad_state) {
ds4_dpad(const gamepad_state_t &gamepad_state) {
auto flags = gamepad_state.buttonFlags; auto flags = gamepad_state.buttonFlags;
if (flags & DPAD_UP) { if (flags & DPAD_UP) {
if (flags & DPAD_RIGHT) { if (flags & DPAD_RIGHT) {
return DS4_BUTTON_DPAD_NORTHEAST; return DS4_BUTTON_DPAD_NORTHEAST;
} } else if (flags & DPAD_LEFT) {
else if (flags & DPAD_LEFT) {
return DS4_BUTTON_DPAD_NORTHWEST; return DS4_BUTTON_DPAD_NORTHWEST;
} } else {
else {
return DS4_BUTTON_DPAD_NORTH; return DS4_BUTTON_DPAD_NORTH;
} }
} }
@ -1339,11 +1321,9 @@ namespace platf {
else if (flags & DPAD_DOWN) { else if (flags & DPAD_DOWN) {
if (flags & DPAD_RIGHT) { if (flags & DPAD_RIGHT) {
return DS4_BUTTON_DPAD_SOUTHEAST; return DS4_BUTTON_DPAD_SOUTHEAST;
} } else if (flags & DPAD_LEFT) {
else if (flags & DPAD_LEFT) {
return DS4_BUTTON_DPAD_SOUTHWEST; return DS4_BUTTON_DPAD_SOUTHWEST;
} } else {
else {
return DS4_BUTTON_DPAD_SOUTH; return DS4_BUTTON_DPAD_SOUTH;
} }
} }
@ -1364,50 +1344,76 @@ namespace platf {
* @param gamepad_state The gamepad button/axis state sent from the client. * @param gamepad_state The gamepad button/axis state sent from the client.
* @return DS4_BUTTONS flags. * @return DS4_BUTTONS flags.
*/ */
static DS4_BUTTONS static DS4_BUTTONS ds4_buttons(const gamepad_state_t &gamepad_state) {
ds4_buttons(const gamepad_state_t &gamepad_state) {
int buttons {}; int buttons {};
auto flags = gamepad_state.buttonFlags; auto flags = gamepad_state.buttonFlags;
if (flags & LEFT_STICK) buttons |= DS4_BUTTON_THUMB_LEFT; if (flags & LEFT_STICK) {
if (flags & RIGHT_STICK) buttons |= DS4_BUTTON_THUMB_RIGHT; buttons |= DS4_BUTTON_THUMB_LEFT;
if (flags & LEFT_BUTTON) buttons |= DS4_BUTTON_SHOULDER_LEFT; }
if (flags & RIGHT_BUTTON) buttons |= DS4_BUTTON_SHOULDER_RIGHT; if (flags & RIGHT_STICK) {
if (flags & START) buttons |= DS4_BUTTON_OPTIONS; buttons |= DS4_BUTTON_THUMB_RIGHT;
if (flags & BACK) buttons |= DS4_BUTTON_SHARE; }
if (flags & A) buttons |= DS4_BUTTON_CROSS; if (flags & LEFT_BUTTON) {
if (flags & B) buttons |= DS4_BUTTON_CIRCLE; buttons |= DS4_BUTTON_SHOULDER_LEFT;
if (flags & X) buttons |= DS4_BUTTON_SQUARE; }
if (flags & Y) buttons |= DS4_BUTTON_TRIANGLE; if (flags & RIGHT_BUTTON) {
buttons |= DS4_BUTTON_SHOULDER_RIGHT;
}
if (flags & START) {
buttons |= DS4_BUTTON_OPTIONS;
}
if (flags & BACK) {
buttons |= DS4_BUTTON_SHARE;
}
if (flags & A) {
buttons |= DS4_BUTTON_CROSS;
}
if (flags & B) {
buttons |= DS4_BUTTON_CIRCLE;
}
if (flags & X) {
buttons |= DS4_BUTTON_SQUARE;
}
if (flags & Y) {
buttons |= DS4_BUTTON_TRIANGLE;
}
if (gamepad_state.lt > 0) buttons |= DS4_BUTTON_TRIGGER_LEFT; if (gamepad_state.lt > 0) {
if (gamepad_state.rt > 0) buttons |= DS4_BUTTON_TRIGGER_RIGHT; buttons |= DS4_BUTTON_TRIGGER_LEFT;
}
if (gamepad_state.rt > 0) {
buttons |= DS4_BUTTON_TRIGGER_RIGHT;
}
return (DS4_BUTTONS) buttons; return (DS4_BUTTONS) buttons;
} }
static DS4_SPECIAL_BUTTONS static DS4_SPECIAL_BUTTONS ds4_special_buttons(const gamepad_state_t &gamepad_state) {
ds4_special_buttons(const gamepad_state_t &gamepad_state) {
int buttons {}; int buttons {};
if (gamepad_state.buttonFlags & HOME) buttons |= DS4_SPECIAL_BUTTON_PS; if (gamepad_state.buttonFlags & HOME) {
buttons |= DS4_SPECIAL_BUTTON_PS;
}
// Allow either PS4/PS5 clickpad button or Xbox Series X share button to activate DS4 clickpad // Allow either PS4/PS5 clickpad button or Xbox Series X share button to activate DS4 clickpad
if (gamepad_state.buttonFlags & (TOUCHPAD_BUTTON | MISC_BUTTON)) buttons |= DS4_SPECIAL_BUTTON_TOUCHPAD; if (gamepad_state.buttonFlags & (TOUCHPAD_BUTTON | MISC_BUTTON)) {
buttons |= DS4_SPECIAL_BUTTON_TOUCHPAD;
}
// Manual DS4 emulation: check if BACK button should also trigger DS4 touchpad click // Manual DS4 emulation: check if BACK button should also trigger DS4 touchpad click
if (config::input.gamepad == "ds4"sv && config::input.ds4_back_as_touchpad_click && (gamepad_state.buttonFlags & BACK)) buttons |= DS4_SPECIAL_BUTTON_TOUCHPAD; if (config::input.gamepad == "ds4"sv && config::input.ds4_back_as_touchpad_click && (gamepad_state.buttonFlags & BACK)) {
buttons |= DS4_SPECIAL_BUTTON_TOUCHPAD;
}
return (DS4_SPECIAL_BUTTONS) buttons; return (DS4_SPECIAL_BUTTONS) buttons;
} }
static std::uint8_t static std::uint8_t to_ds4_triggerX(std::int16_t v) {
to_ds4_triggerX(std::int16_t v) {
return (v + std::numeric_limits<std::uint16_t>::max() / 2 + 1) / 257; return (v + std::numeric_limits<std::uint16_t>::max() / 2 + 1) / 257;
} }
static std::uint8_t static std::uint8_t to_ds4_triggerY(std::int16_t v) {
to_ds4_triggerY(std::int16_t v) {
auto new_v = -((std::numeric_limits<std::uint16_t>::max() / 2 + v - 1)) / 257; auto new_v = -((std::numeric_limits<std::uint16_t>::max() / 2 + v - 1)) / 257;
return new_v == 0 ? 0xFF : (std::uint8_t) new_v; return new_v == 0 ? 0xFF : (std::uint8_t) new_v;
@ -1418,8 +1424,7 @@ namespace platf {
* @param gamepad The gamepad to update. * @param gamepad The gamepad to update.
* @param gamepad_state The gamepad button/axis state sent from the client. * @param gamepad_state The gamepad button/axis state sent from the client.
*/ */
static void static void ds4_update_state(gamepad_context_t &gamepad, const gamepad_state_t &gamepad_state) {
ds4_update_state(gamepad_context_t &gamepad, const gamepad_state_t &gamepad_state) {
auto &report = gamepad.report.ds4.Report; auto &report = gamepad.report.ds4.Report;
report.wButtons = static_cast<uint16_t>(ds4_buttons(gamepad_state)) | static_cast<uint16_t>(ds4_dpad(gamepad_state)); report.wButtons = static_cast<uint16_t>(ds4_buttons(gamepad_state)) | static_cast<uint16_t>(ds4_dpad(gamepad_state));
@ -1441,8 +1446,7 @@ namespace platf {
* @param vigem The global ViGEm context object. * @param vigem The global ViGEm context object.
* @param nr The global gamepad index. * @param nr The global gamepad index.
*/ */
void void ds4_update_ts_and_send(vigem_t *vigem, int nr) {
ds4_update_ts_and_send(vigem_t *vigem, int nr) {
auto &gamepad = vigem->gamepads[nr]; auto &gamepad = vigem->gamepads[nr];
// Cancel any pending updates. We will requeue one here when we're finished. // Cancel any pending updates. We will requeue one here when we're finished.
@ -1477,8 +1481,7 @@ namespace platf {
* @param nr The gamepad index to update. * @param nr The gamepad index to update.
* @param gamepad_state The gamepad button/axis state sent from the client. * @param gamepad_state The gamepad button/axis state sent from the client.
*/ */
void void gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
auto vigem = ((input_raw_t *) input.get())->vigem; auto vigem = ((input_raw_t *) input.get())->vigem;
// If there is no gamepad support // If there is no gamepad support
@ -1499,8 +1502,7 @@ namespace platf {
if (!VIGEM_SUCCESS(status)) { if (!VIGEM_SUCCESS(status)) {
BOOST_LOG(warning) << "Couldn't send gamepad input to ViGEm ["sv << util::hex(status).to_string_view() << ']'; BOOST_LOG(warning) << "Couldn't send gamepad input to ViGEm ["sv << util::hex(status).to_string_view() << ']';
} }
} } else {
else {
ds4_update_state(gamepad, gamepad_state); ds4_update_state(gamepad, gamepad_state);
ds4_update_ts_and_send(vigem, nr); ds4_update_ts_and_send(vigem, nr);
} }
@ -1511,8 +1513,7 @@ namespace platf {
* @param input The global input context. * @param input The global input context.
* @param touch The touch event. * @param touch The touch event.
*/ */
void void gamepad_touch(input_t &input, const gamepad_touch_t &touch) {
gamepad_touch(input_t &input, const gamepad_touch_t &touch) {
auto vigem = ((input_raw_t *) input.get())->vigem; auto vigem = ((input_raw_t *) input.get())->vigem;
// If there is no gamepad support // If there is no gamepad support
@ -1542,8 +1543,7 @@ namespace platf {
// Set pointer 0 down // Set pointer 0 down
report.sCurrentTouch.bIsUpTrackingNum1 &= ~0x80; report.sCurrentTouch.bIsUpTrackingNum1 &= ~0x80;
report.sCurrentTouch.bIsUpTrackingNum1++; report.sCurrentTouch.bIsUpTrackingNum1++;
} } else if (gamepad.available_pointers & 0x2) {
else if (gamepad.available_pointers & 0x2) {
// Reserve pointer index 1 for this touch // Reserve pointer index 1 for this touch
gamepad.pointer_id_map[touch.pointerId] = pointerIndex = 1; gamepad.pointer_id_map[touch.pointerId] = pointerIndex = 1;
gamepad.available_pointers &= ~(1 << pointerIndex); gamepad.available_pointers &= ~(1 << pointerIndex);
@ -1551,13 +1551,11 @@ namespace platf {
// Set pointer 1 down // Set pointer 1 down
report.sCurrentTouch.bIsUpTrackingNum2 &= ~0x80; report.sCurrentTouch.bIsUpTrackingNum2 &= ~0x80;
report.sCurrentTouch.bIsUpTrackingNum2++; report.sCurrentTouch.bIsUpTrackingNum2++;
} } else {
else {
BOOST_LOG(warning) << "No more free pointer indices! Did the client miss an touch up event?"sv; BOOST_LOG(warning) << "No more free pointer indices! Did the client miss an touch up event?"sv;
return; return;
} }
} } else if (touch.eventType == LI_TOUCH_EVENT_CANCEL_ALL) {
else if (touch.eventType == LI_TOUCH_EVENT_CANCEL_ALL) {
// Raise both pointers // Raise both pointers
report.sCurrentTouch.bIsUpTrackingNum1 |= 0x80; report.sCurrentTouch.bIsUpTrackingNum1 |= 0x80;
report.sCurrentTouch.bIsUpTrackingNum2 |= 0x80; report.sCurrentTouch.bIsUpTrackingNum2 |= 0x80;
@ -1567,8 +1565,7 @@ namespace platf {
// All pointers are now available // All pointers are now available
gamepad.available_pointers = 0x3; gamepad.available_pointers = 0x3;
} } else {
else {
auto i = gamepad.pointer_id_map.find(touch.pointerId); auto i = gamepad.pointer_id_map.find(touch.pointerId);
if (i == gamepad.pointer_id_map.end()) { if (i == gamepad.pointer_id_map.end()) {
BOOST_LOG(warning) << "Pointer ID not found! Did the client miss a touch down event?"sv; BOOST_LOG(warning) << "Pointer ID not found! Did the client miss a touch down event?"sv;
@ -1584,15 +1581,13 @@ namespace platf {
// Set pointer up // Set pointer up
if (pointerIndex == 0) { if (pointerIndex == 0) {
report.sCurrentTouch.bIsUpTrackingNum1 |= 0x80; report.sCurrentTouch.bIsUpTrackingNum1 |= 0x80;
} } else {
else {
report.sCurrentTouch.bIsUpTrackingNum2 |= 0x80; report.sCurrentTouch.bIsUpTrackingNum2 |= 0x80;
} }
// Free the pointer index // Free the pointer index
gamepad.available_pointers |= (1 << pointerIndex); gamepad.available_pointers |= (1 << pointerIndex);
} } else if (touch.eventType != LI_TOUCH_EVENT_MOVE) {
else if (touch.eventType != LI_TOUCH_EVENT_MOVE) {
BOOST_LOG(warning) << "Unsupported touch event for gamepad: "sv << (uint32_t) touch.eventType; BOOST_LOG(warning) << "Unsupported touch event for gamepad: "sv << (uint32_t) touch.eventType;
return; return;
} }
@ -1611,8 +1606,7 @@ namespace platf {
if (touch.eventType != LI_TOUCH_EVENT_CANCEL_ALL) { if (touch.eventType != LI_TOUCH_EVENT_CANCEL_ALL) {
if (pointerIndex == 0) { if (pointerIndex == 0) {
memcpy(report.sCurrentTouch.bTouchData1, touchData, sizeof(touchData)); memcpy(report.sCurrentTouch.bTouchData1, touchData, sizeof(touchData));
} } else {
else {
memcpy(report.sCurrentTouch.bTouchData2, touchData, sizeof(touchData)); memcpy(report.sCurrentTouch.bTouchData2, touchData, sizeof(touchData));
} }
} }
@ -1625,8 +1619,7 @@ namespace platf {
* @param input The global input context. * @param input The global input context.
* @param motion The motion event. * @param motion The motion event.
*/ */
void void gamepad_motion(input_t &input, const gamepad_motion_t &motion) {
gamepad_motion(input_t &input, const gamepad_motion_t &motion) {
auto vigem = ((input_raw_t *) input.get())->vigem; auto vigem = ((input_raw_t *) input.get())->vigem;
// If there is no gamepad support // If there is no gamepad support
@ -1653,8 +1646,7 @@ namespace platf {
* @param input The global input context. * @param input The global input context.
* @param battery The battery event. * @param battery The battery event.
*/ */
void void gamepad_battery(input_t &input, const gamepad_battery_t &battery) {
gamepad_battery(input_t &input, const gamepad_battery_t &battery) {
auto vigem = ((input_raw_t *) input.get())->vigem; auto vigem = ((input_raw_t *) input.get())->vigem;
// If there is no gamepad support // If there is no gamepad support
@ -1683,8 +1675,7 @@ namespace platf {
case LI_BATTERY_STATE_DISCHARGING: case LI_BATTERY_STATE_DISCHARGING:
if (battery.state == LI_BATTERY_STATE_CHARGING) { if (battery.state == LI_BATTERY_STATE_CHARGING) {
report.bBatteryLvlSpecial |= 0x10; // Connected via USB report.bBatteryLvlSpecial |= 0x10; // Connected via USB
} } else {
else {
report.bBatteryLvlSpecial &= ~0x10; // Not connected via USB report.bBatteryLvlSpecial &= ~0x10; // Not connected via USB
} }
@ -1723,15 +1714,13 @@ namespace platf {
ds4_update_ts_and_send(vigem, battery.id.globalIndex); ds4_update_ts_and_send(vigem, battery.id.globalIndex);
} }
void void freeInput(void *p) {
freeInput(void *p) {
auto input = (input_raw_t *) p; auto input = (input_raw_t *) p;
delete input; delete input;
} }
std::vector<supported_gamepad_t> & std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
supported_gamepads(input_t *input) {
if (!input) { if (!input) {
static std::vector gps { static std::vector gps {
supported_gamepad_t {"auto", true, ""}, supported_gamepad_t {"auto", true, ""},
@ -1766,8 +1755,7 @@ namespace platf {
* @brief Returns the supported platform capabilities to advertise to the client. * @brief Returns the supported platform capabilities to advertise to the client.
* @return Capability flags. * @return Capability flags.
*/ */
platform_caps::caps_t platform_caps::caps_t get_capabilities() {
get_capabilities() {
platform_caps::caps_t caps = 0; platform_caps::caps_t caps = 0;
// We support controller touchpad input as long as we're not emulating X360 // We support controller touchpad input as long as we're not emulating X360
@ -1780,8 +1768,7 @@ namespace platf {
if (config::input.native_pen_touch) { if (config::input.native_pen_touch) {
caps |= platform_caps::pen_touch; caps |= platform_caps::pen_touch;
} }
} } else {
else {
BOOST_LOG(warning) << "Touch input requires Windows 10 1809 or later"sv; BOOST_LOG(warning) << "Touch input requires Windows 10 1809 or later"sv;
} }

View file

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

View file

@ -2,12 +2,15 @@
* @file src/platform/windows/misc.cpp * @file src/platform/windows/misc.cpp
* @brief Miscellaneous definitions for Windows. * @brief Miscellaneous definitions for Windows.
*/ */
// standard includes
#include <csignal> #include <csignal>
#include <filesystem> #include <filesystem>
#include <iomanip> #include <iomanip>
#include <iterator>
#include <set> #include <set>
#include <sstream> #include <sstream>
// lib includes
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/asio/ip/address.hpp> #include <boost/asio/ip/address.hpp>
#include <boost/process/v1.hpp> #include <boost/process/v1.hpp>
@ -34,16 +37,14 @@
#define NTDDI_VERSION NTDDI_WIN10 #define NTDDI_VERSION NTDDI_WIN10
#include <Shlwapi.h> #include <Shlwapi.h>
// local includes
#include "misc.h" #include "misc.h"
#include "nvprefs/nvprefs_interface.h"
#include "src/entry_handler.h" #include "src/entry_handler.h"
#include "src/globals.h" #include "src/globals.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/utility.h" #include "src/utility.h"
#include <iterator>
#include "nvprefs/nvprefs_interface.h"
// UDP_SEND_MSG_SIZE was added in the Windows 10 20H1 SDK // UDP_SEND_MSG_SIZE was added in the Windows 10 20H1 SDK
#ifndef UDP_SEND_MSG_SIZE #ifndef UDP_SEND_MSG_SIZE
@ -63,16 +64,14 @@
#include <winternl.h> #include <winternl.h>
extern "C" { extern "C" {
NTSTATUS NTAPI NTSTATUS NTAPI NtSetTimerResolution(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution);
NtSetTimerResolution(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution);
} }
namespace { namespace {
std::atomic<bool> used_nt_set_timer_resolution = false; std::atomic<bool> used_nt_set_timer_resolution = false;
bool bool nt_set_timer_resolution_max() {
nt_set_timer_resolution_max() {
ULONG minimum, maximum, current; ULONG minimum, maximum, current;
if (!NT_SUCCESS(NtQueryTimerResolution(&minimum, &maximum, &current)) || if (!NT_SUCCESS(NtQueryTimerResolution(&minimum, &maximum, &current)) ||
!NT_SUCCESS(NtSetTimerResolution(maximum, TRUE, &current))) { !NT_SUCCESS(NtSetTimerResolution(maximum, TRUE, &current))) {
@ -81,8 +80,7 @@ namespace {
return true; return true;
} }
bool bool nt_set_timer_resolution_min() {
nt_set_timer_resolution_min() {
ULONG minimum, maximum, current; ULONG minimum, maximum, current;
if (!NT_SUCCESS(NtQueryTimerResolution(&minimum, &maximum, &current)) || if (!NT_SUCCESS(NtQueryTimerResolution(&minimum, &maximum, &current)) ||
!NT_SUCCESS(NtSetTimerResolution(minimum, TRUE, &current))) { !NT_SUCCESS(NtSetTimerResolution(minimum, TRUE, &current))) {
@ -96,6 +94,7 @@ namespace {
namespace bp = boost::process; namespace bp = boost::process;
using namespace std::literals; using namespace std::literals;
namespace platf { namespace platf {
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>; using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
@ -116,30 +115,26 @@ namespace platf {
decltype(WlanEnumInterfaces) *fn_WlanEnumInterfaces = nullptr; decltype(WlanEnumInterfaces) *fn_WlanEnumInterfaces = nullptr;
decltype(WlanSetInterface) *fn_WlanSetInterface = nullptr; decltype(WlanSetInterface) *fn_WlanSetInterface = nullptr;
std::filesystem::path std::filesystem::path appdata() {
appdata() {
WCHAR sunshine_path[MAX_PATH]; WCHAR sunshine_path[MAX_PATH];
GetModuleFileNameW(NULL, sunshine_path, _countof(sunshine_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 std::string from_sockaddr(const sockaddr *const socket_address) {
from_sockaddr(const sockaddr *const socket_address) {
char data[INET6_ADDRSTRLEN] = {}; char data[INET6_ADDRSTRLEN] = {};
auto family = socket_address->sa_family; auto family = socket_address->sa_family;
if (family == AF_INET6) { if (family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *) socket_address)->sin6_addr, data, INET6_ADDRSTRLEN); 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); 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> std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
from_sockaddr_ex(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN] = {}; char data[INET6_ADDRSTRLEN] = {};
auto family = ip_addr->sa_family; auto family = ip_addr->sa_family;
@ -147,8 +142,7 @@ namespace platf {
if (family == AF_INET6) { 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; 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); inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
port = ((sockaddr_in *) ip_addr)->sin_port; port = ((sockaddr_in *) ip_addr)->sin_port;
} }
@ -156,8 +150,7 @@ namespace platf {
return {port, std::string {data}}; return {port, std::string {data}};
} }
adapteraddrs_t adapteraddrs_t get_adapteraddrs() {
get_adapteraddrs() {
adapteraddrs_t info {nullptr}; adapteraddrs_t info {nullptr};
ULONG size = 0; ULONG size = 0;
@ -168,8 +161,7 @@ namespace platf {
return info; return info;
} }
std::string std::string get_mac_address(const std::string_view &address) {
get_mac_address(const std::string_view &address) {
adapteraddrs_t info = get_adapteraddrs(); adapteraddrs_t info = get_adapteraddrs();
for (auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) { 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) { 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; return "00:00:00:00:00:00"s;
} }
HDESK HDESK syncThreadDesktop() {
syncThreadDesktop() {
auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL);
if (!hDesk) { if (!hDesk) {
auto err = GetLastError(); auto err = GetLastError();
@ -210,23 +201,15 @@ namespace platf {
return hDesk; return hDesk;
} }
void void print_status(const std::string_view &prefix, HRESULT status) {
print_status(const std::string_view &prefix, HRESULT status) {
char err_string[1024]; char err_string[1024];
DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, status, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_string, sizeof(err_string), nullptr);
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 bool IsUserAdmin(HANDLE user_token) {
IsUserAdmin(HANDLE user_token) {
WINBOOL ret; WINBOOL ret;
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
PSID AdministratorsGroup; PSID AdministratorsGroup;
@ -235,16 +218,21 @@ namespace platf {
2, 2,
SECURITY_BUILTIN_DOMAIN_RID, SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS, DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0, 0,
&AdministratorsGroup); 0,
0,
0,
0,
0,
&AdministratorsGroup
);
if (ret) { if (ret) {
if (!CheckTokenMembership(user_token, AdministratorsGroup, &ret)) { if (!CheckTokenMembership(user_token, AdministratorsGroup, &ret)) {
ret = false; ret = false;
BOOST_LOG(error) << "Failed to verify token membership for administrative access: " << GetLastError(); BOOST_LOG(error) << "Failed to verify token membership for administrative access: " << GetLastError();
} }
FreeSid(AdministratorsGroup); FreeSid(AdministratorsGroup);
} } else {
else {
BOOST_LOG(error) << "Unable to allocate SID to check administrative access: " << GetLastError(); 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. * @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`. * @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 HANDLE retrieve_users_token(bool elevated) {
retrieve_users_token(bool elevated) {
DWORD consoleSessionId; DWORD consoleSessionId;
HANDLE userToken; HANDLE userToken;
TOKEN_ELEVATION_TYPE elevationType; TOKEN_ELEVATION_TYPE elevationType;
@ -317,8 +304,7 @@ namespace platf {
return userToken; return userToken;
} }
bool bool merge_user_environment_block(bp::environment &env, HANDLE shell_token) {
merge_user_environment_block(bp::environment &env, HANDLE shell_token) {
// Get the target user's environment block // Get the target user's environment block
PVOID env_block; PVOID env_block;
if (!CreateEnvironmentBlock(&env_block, shell_token, FALSE)) { if (!CreateEnvironmentBlock(&env_block, shell_token, FALSE)) {
@ -333,8 +319,9 @@ namespace platf {
std::string env_val = env_tuple.substr(env_tuple.find('=') + 1); std::string env_val = env_tuple.substr(env_tuple.find('=') + 1);
// Perform a case-insensitive search to see if this variable name already exists // Perform a case-insensitive search to see if this variable name already exists
auto itr = std::find_if(env.cbegin(), env.cend(), auto itr = std::find_if(env.cbegin(), env.cend(), [&](const auto &e) {
[&](const auto &e) { return boost::iequals(e.get_name(), env_name); }); return boost::iequals(e.get_name(), env_name);
});
if (itr != env.cend()) { if (itr != env.cend()) {
// Use this existing name if it is already present to ensure we merge properly // Use this existing name if it is already present to ensure we merge properly
env_name = itr->get_name(); env_name = itr->get_name();
@ -343,8 +330,7 @@ namespace platf {
// For the PATH variable, we will merge the values together // For the PATH variable, we will merge the values together
if (boost::iequals(env_name, "PATH")) { if (boost::iequals(env_name, "PATH")) {
env[env_name] = env_val + ";" + env[env_name].to_string(); 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 // Other variables will be superseded by those in the user's environment block
env[env_name] = env_val; env[env_name] = env_val;
} }
@ -358,8 +344,7 @@ namespace platf {
* @brief Check if the current process is running with system-level privileges. * @brief Check if the current process is running with system-level privileges.
* @return `true` if the current process has system-level privileges, `false` otherwise. * @return `true` if the current process has system-level privileges, `false` otherwise.
*/ */
bool bool is_running_as_system() {
is_running_as_system() {
BOOL ret; BOOL ret;
PSID SystemSid; PSID SystemSid;
DWORD dwSize = SECURITY_MAX_SID_SIZE; DWORD dwSize = SECURITY_MAX_SID_SIZE;
@ -379,8 +364,7 @@ namespace platf {
BOOST_LOG(error) << "Failed to check token membership: " << GetLastError(); BOOST_LOG(error) << "Failed to check token membership: " << GetLastError();
ret = false; 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(); 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 // Note: This does NOT append a null terminator
void void append_string_to_environment_block(wchar_t *env_block, int &offset, const std::wstring &wstr) {
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)); std::memcpy(&env_block[offset], wstr.data(), wstr.length() * sizeof(wchar_t));
offset += wstr.length(); offset += wstr.length();
} }
std::wstring std::wstring create_environment_block(bp::environment &env) {
create_environment_block(bp::environment &env) {
int size = 0; int size = 0;
for (const auto &entry : env) { for (const auto &entry : env) {
auto name = entry.get_name(); auto name = entry.get_name();
@ -426,8 +408,7 @@ namespace platf {
return std::wstring(env_block, offset); return std::wstring(env_block, offset);
} }
LPPROC_THREAD_ATTRIBUTE_LIST LPPROC_THREAD_ATTRIBUTE_LIST allocate_proc_thread_attr_list(DWORD attribute_count) {
allocate_proc_thread_attr_list(DWORD attribute_count) {
SIZE_T size; SIZE_T size;
InitializeProcThreadAttributeList(NULL, attribute_count, 0, &size); InitializeProcThreadAttributeList(NULL, attribute_count, 0, &size);
@ -444,8 +425,7 @@ namespace platf {
return list; return list;
} }
void void free_proc_thread_attr_list(LPPROC_THREAD_ATTRIBUTE_LIST list) {
free_proc_thread_attr_list(LPPROC_THREAD_ATTRIBUTE_LIST list) {
DeleteProcThreadAttributeList(list); DeleteProcThreadAttributeList(list);
HeapFree(GetProcessHeap(), 0, 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. * @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. * @return A `bp::child` object representing the new process, or an empty `bp::child` object if the launch failed.
*/ */
bp::child bp::child create_boost_child_from_results(bool process_launched, const std::string &cmd, std::error_code &ec, PROCESS_INFORMATION &process_info) {
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. // 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]() { auto close_process_handles = util::fail_guard([process_launched, process_info]() {
if (process_launched) { if (process_launched) {
@ -478,8 +457,7 @@ namespace platf {
auto child = bp::child((bp::pid_t) process_info.dwProcessId); auto child = bp::child((bp::pid_t) process_info.dwProcessId);
BOOST_LOG(info) << cmd << " running with PID "sv << child.id(); BOOST_LOG(info) << cmd << " running with PID "sv << child.id();
return child; return child;
} } else {
else {
auto winerror = GetLastError(); auto winerror = GetLastError();
BOOST_LOG(error) << "Failed to launch process: "sv << winerror; BOOST_LOG(error) << "Failed to launch process: "sv << winerror;
ec = std::make_error_code(std::errc::invalid_argument); 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. * @param callback A function that will be executed while impersonating the user.
* @return Object that will store any error that occurred during the impersonation * @return Object that will store any error that occurred during the impersonation
*/ */
std::error_code std::error_code impersonate_current_user(HANDLE user_token, std::function<void()> callback) {
impersonate_current_user(HANDLE user_token, std::function<void()> callback) {
std::error_code ec; std::error_code ec;
// Impersonate the user when launching the process. This will ensure that appropriate access // 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 // 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. * @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. * @return A structure that contains information about how to launch the new process.
*/ */
STARTUPINFOEXW STARTUPINFOEXW create_startup_info(FILE *file, HANDLE *job, std::error_code &ec) {
create_startup_info(FILE *file, HANDLE *job, std::error_code &ec) {
// Initialize a zeroed-out STARTUPINFOEXW structure and set its size // Initialize a zeroed-out STARTUPINFOEXW structure and set its size
STARTUPINFOEXW startup_info = {}; STARTUPINFOEXW startup_info = {};
startup_info.StartupInfo.cb = sizeof(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, // 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. // so we need to point into the STARTUPINFO instead of our log_file_variable on the stack.
UpdateProcThreadAttribute(startup_info.lpAttributeList, UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &startup_info.StartupInfo.hStdOutput, sizeof(startup_info.StartupInfo.hStdOutput), NULL, NULL);
0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
&startup_info.StartupInfo.hStdOutput,
sizeof(startup_info.StartupInfo.hStdOutput),
NULL,
NULL);
} }
if (job) { 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, // 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. // so we take a HANDLE* instead of just a HANDLE to use the caller's stack storage.
UpdateProcThreadAttribute(startup_info.lpAttributeList, UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_JOB_LIST, job, sizeof(*job), NULL, NULL);
0,
PROC_THREAD_ATTRIBUTE_JOB_LIST,
job,
sizeof(*job),
NULL,
NULL);
} }
return startup_info; 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. * @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. * @return `true` if the override or restore operation was successful.
*/ */
bool bool override_per_user_predefined_keys(HANDLE token) {
override_per_user_predefined_keys(HANDLE token) {
HKEY user_classes_root = NULL; HKEY user_classes_root = NULL;
if (token) { if (token) {
auto err = RegOpenUserClassesRoot(token, 0, GENERIC_ALL, &user_classes_root); auto err = RegOpenUserClassesRoot(token, 0, GENERIC_ALL, &user_classes_root);
@ -651,8 +614,7 @@ namespace platf {
* @param argument The raw argument to process. * @param argument The raw argument to process.
* @return An argument string suitable for use by CreateProcess(). * @return An argument string suitable for use by CreateProcess().
*/ */
std::wstring std::wstring escape_argument(const std::wstring &argument) {
escape_argument(const std::wstring &argument) {
// If there are no characters requiring quoting/escaping, we're done // If there are no characters requiring quoting/escaping, we're done
if (argument.find_first_of(L" \t\n\v\"") == argument.npos) { if (argument.find_first_of(L" \t\n\v\"") == argument.npos) {
return argument; return argument;
@ -672,11 +634,9 @@ namespace platf {
if (it == argument.end()) { if (it == argument.end()) {
escaped_arg.append(backslash_count * 2, L'\\'); escaped_arg.append(backslash_count * 2, L'\\');
break; break;
} } else if (*it == L'"') {
else if (*it == L'"') {
escaped_arg.append(backslash_count * 2 + 1, L'\\'); escaped_arg.append(backslash_count * 2 + 1, L'\\');
} } else {
else {
escaped_arg.append(backslash_count, L'\\'); escaped_arg.append(backslash_count, L'\\');
} }
@ -691,8 +651,7 @@ namespace platf {
* @param argument An argument already escaped by `escape_argument()`. * @param argument An argument already escaped by `escape_argument()`.
* @return An argument string suitable for use by cmd.exe. * @return An argument string suitable for use by cmd.exe.
*/ */
std::wstring std::wstring escape_argument_for_cmd(const std::wstring &argument) {
escape_argument_for_cmd(const std::wstring &argument) {
// Start with the original string and modify from there // Start with the original string and modify from there
std::wstring escaped_arg = argument; 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. * @param creation_flags The creation flags for CreateProcess(), which may be modified by this function.
* @return A command string suitable for use by CreateProcess(). * @return A command string suitable for use by CreateProcess().
*/ */
std::wstring std::wstring resolve_command_string(const std::string &raw_cmd, const std::wstring &working_dir, HANDLE token, DWORD &creation_flags) {
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); 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 // 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 ':') // If the target is a URL, the class is found using the URL scheme (prior to and not including the ':')
lookup_string = scheme.data(); lookup_string = scheme.data();
} } else {
else {
// If the target is not a URL, assume it's a regular file path // If the target is not a URL, assume it's a regular file path
auto extension = PathFindExtensionW(raw_target.c_str()); auto extension = PathFindExtensionW(raw_target.c_str());
if (extension == nullptr || *extension == 0) { if (extension == nullptr || *extension == 0) {
// If the file has no extension, assume it's a command and allow CreateProcess() // If the file has no extension, assume it's a command and allow CreateProcess()
// to try to find it via PATH // to try to find it via PATH
return from_utf8(raw_cmd); 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 // If the file has an .exe extension, we will bypass the resolution here and
// directly pass the unmodified command string to CreateProcess(). The argument // directly pass the unmodified command string to CreateProcess(). The argument
// escaping rules are subtly different between CreateProcess() and ShellExecute(), // escaping rules are subtly different between CreateProcess() and ShellExecute(),
@ -843,7 +799,8 @@ namespace platf {
case L'6': case L'6':
case L'7': case L'7':
case L'8': case L'8':
case L'9': { case L'9':
{
// Arguments numbers are 1-based, except for %0 which is equivalent to %1 // Arguments numbers are 1-based, except for %0 which is equivalent to %1
int index = next_char - L'0'; int index = next_char - L'0';
if (next_char != L'0') { if (next_char != L'0') {
@ -878,7 +835,8 @@ namespace platf {
// Long file path of target // Long file path of target
case L'l': case L'l':
case L'd': case L'd':
case L'v': { case L'v':
{
std::array<WCHAR, MAX_PATH> path; std::array<WCHAR, MAX_PATH> path;
std::array<PCWCHAR, 2> other_dirs {working_dir.c_str(), nullptr}; std::array<PCWCHAR, 2> other_dirs {working_dir.c_str(), nullptr};
@ -894,8 +852,7 @@ namespace platf {
// See if we can find the path on our search path or working directory // See if we can find the path on our search path or working directory
else if (PathFindOnPathW(path.data(), other_dirs.data())) { else if (PathFindOnPathW(path.data(), other_dirs.data())) {
match_replacement = std::wstring {path.data()}; match_replacement = std::wstring {path.data()};
} } else {
else {
// We couldn't find the target, so we'll just hope for the best // We couldn't find the target, so we'll just hope for the best
match_replacement = raw_target; match_replacement = raw_target;
} }
@ -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`). * @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. * @return A `bp::child` object representing the new process, or an empty `bp::child` object if the launch fails.
*/ */
bp::child 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) {
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()); std::wstring start_dir = from_utf8(working_dir.string());
HANDLE job = group ? group->native_handle() : nullptr; HANDLE job = group ? group->native_handle() : nullptr;
STARTUPINFOEXW startup_info = create_startup_info(file, job ? &job : nullptr, ec); STARTUPINFOEXW startup_info = create_startup_info(file, job ? &job : nullptr, ec);
@ -969,7 +925,9 @@ namespace platf {
auto sunshine_wenv = boost::this_process::wenvironment(); 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; 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()) { if (itr != sunshine_wenv.cend()) {
// Use the existing variable if it exists, since Boost treats these as case-sensitive. // Use the existing variable if it exists, since Boost treats these as case-sensitive.
path_var_name = itr->get_name(); path_var_name = itr->get_name();
@ -984,8 +942,7 @@ namespace platf {
auto restore_path = util::fail_guard([&]() { auto restore_path = util::fail_guard([&]() {
if (old_path_val.empty()) { if (old_path_val.empty()) {
sunshine_wenv[path_var_name].clear(); sunshine_wenv[path_var_name].clear();
} } else {
else {
sunshine_wenv[path_var_name].assign(old_path_val); sunshine_wenv[path_var_name].assign(old_path_val);
} }
}); });
@ -1015,17 +972,7 @@ namespace platf {
ec = impersonate_current_user(user_token, [&]() { ec = impersonate_current_user(user_token, [&]() {
std::wstring env_block = create_environment_block(cloned_env); std::wstring env_block = create_environment_block(cloned_env);
std::wstring wcmd = resolve_command_string(cmd, start_dir, user_token, creation_flags); std::wstring wcmd = resolve_command_string(cmd, start_dir, user_token, creation_flags);
ret = CreateProcessAsUserW(user_token, 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);
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() // Otherwise, launch the process using CreateProcessW()
@ -1049,16 +996,7 @@ namespace platf {
std::wstring env_block = create_environment_block(cloned_env); std::wstring env_block = create_environment_block(cloned_env);
std::wstring wcmd = resolve_command_string(cmd, start_dir, NULL, creation_flags); std::wstring wcmd = resolve_command_string(cmd, start_dir, NULL, creation_flags);
ret = CreateProcessW(NULL, 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);
(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 // 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. * @brief Open a url in the default web browser.
* @param url The url to open. * @param url The url to open.
*/ */
void void open_url(const std::string &url) {
open_url(const std::string &url) {
boost::process::v1::environment _env = boost::this_process::environment(); boost::process::v1::environment _env = boost::this_process::environment();
auto working_dir = boost::filesystem::path(); auto working_dir = boost::filesystem::path();
std::error_code ec; std::error_code ec;
@ -1078,15 +1015,13 @@ namespace platf {
auto child = run_command(false, false, url, working_dir, _env, nullptr, ec, nullptr); auto child = run_command(false, false, url, working_dir, _env, nullptr, ec, nullptr);
if (ec) { if (ec) {
BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message(); BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message();
} } else {
else {
BOOST_LOG(info) << "Opened url ["sv << url << "]"sv; BOOST_LOG(info) << "Opened url ["sv << url << "]"sv;
child.detach(); child.detach();
} }
} }
void void adjust_thread_priority(thread_priority_e priority) {
adjust_thread_priority(thread_priority_e priority) {
int win32_priority; int win32_priority;
switch (priority) { switch (priority) {
@ -1113,8 +1048,7 @@ namespace platf {
} }
} }
void void streaming_will_start() {
streaming_will_start() {
static std::once_flag load_wlanapi_once_flag; static std::once_flag load_wlanapi_once_flag;
std::call_once(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 // 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 // Reduce timer period to 0.5ms
if (nt_set_timer_resolution_max()) { if (nt_set_timer_resolution_max()) {
used_nt_set_timer_resolution = true; used_nt_set_timer_resolution = true;
} } else {
else {
BOOST_LOG(error) << "NtSetTimerResolution() failed, falling back to timeBeginPeriod()"; BOOST_LOG(error) << "NtSetTimerResolution() failed, falling back to timeBeginPeriod()";
timeBeginPeriod(1); timeBeginPeriod(1);
used_nt_set_timer_resolution = false; 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/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 // https://docs.microsoft.com/en-us/previous-versions/windows/hardware/wireless/native-802-11-media-streaming
BOOL value = TRUE; BOOL value = TRUE;
auto error = fn_WlanSetInterface(wlan_handle, &wlan_interface_list->InterfaceInfo[i].InterfaceGuid, auto error = fn_WlanSetInterface(wlan_handle, &wlan_interface_list->InterfaceInfo[i].InterfaceGuid, wlan_intf_opcode_media_streaming_mode, sizeof(value), &value, nullptr);
wlan_intf_opcode_media_streaming_mode, sizeof(value), &value, nullptr);
if (error == ERROR_SUCCESS) { if (error == ERROR_SUCCESS) {
BOOST_LOG(info) << "WLAN interface "sv << i << " is now in low latency mode"sv; 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); fn_WlanFreeMemory(wlan_interface_list);
} } else {
else {
fn_WlanCloseHandle(wlan_handle, nullptr); fn_WlanCloseHandle(wlan_handle, nullptr);
wlan_handle = NULL; wlan_handle = NULL;
} }
@ -1220,21 +1151,18 @@ namespace platf {
if (SystemParametersInfoW(SPI_SETMOUSEKEYS, 0, &new_mouse_keys_state, 0)) { if (SystemParametersInfoW(SPI_SETMOUSEKEYS, 0, &new_mouse_keys_state, 0)) {
// Remember to restore the previous settings when we stop streaming // Remember to restore the previous settings when we stop streaming
enabled_mouse_keys = true; enabled_mouse_keys = true;
} } else {
else {
auto winerr = GetLastError(); auto winerr = GetLastError();
BOOST_LOG(warning) << "Unable to enable Mouse Keys: "sv << winerr; BOOST_LOG(warning) << "Unable to enable Mouse Keys: "sv << winerr;
} }
} } else {
else {
auto winerr = GetLastError(); auto winerr = GetLastError();
BOOST_LOG(warning) << "Unable to get current state of Mouse Keys: "sv << winerr; BOOST_LOG(warning) << "Unable to get current state of Mouse Keys: "sv << winerr;
} }
} }
} }
void void streaming_will_stop() {
streaming_will_stop() {
// Demote ourselves back to normal priority class // Demote ourselves back to normal priority class
SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
@ -1244,8 +1172,7 @@ namespace platf {
if (!nt_set_timer_resolution_min()) { if (!nt_set_timer_resolution_min()) {
BOOST_LOG(error) << "nt_set_timer_resolution_min() failed even though nt_set_timer_resolution_max() succeeded"; BOOST_LOG(error) << "nt_set_timer_resolution_min() failed even though nt_set_timer_resolution_max() succeeded";
} }
} } else {
else {
timeEndPeriod(1); timeEndPeriod(1);
} }
@ -1268,8 +1195,7 @@ namespace platf {
} }
} }
void void restart_on_exit() {
restart_on_exit() {
STARTUPINFOEXW startup_info {}; STARTUPINFOEXW startup_info {};
startup_info.StartupInfo.cb = sizeof(startup_info); startup_info.StartupInfo.cb = sizeof(startup_info);
@ -1281,16 +1207,7 @@ namespace platf {
} }
PROCESS_INFORMATION process_info; PROCESS_INFORMATION process_info;
if (!CreateProcessW(executable, if (!CreateProcessW(executable, GetCommandLineW(), nullptr, nullptr, false, CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, (LPSTARTUPINFOW) &startup_info, &process_info)) {
GetCommandLineW(),
nullptr,
nullptr,
false,
CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT,
nullptr,
nullptr,
(LPSTARTUPINFOW) &startup_info,
&process_info)) {
auto winerr = GetLastError(); auto winerr = GetLastError();
BOOST_LOG(fatal) << "Unable to restart Sunshine: "sv << winerr; BOOST_LOG(fatal) << "Unable to restart Sunshine: "sv << winerr;
return; return;
@ -1300,8 +1217,7 @@ namespace platf {
CloseHandle(process_info.hThread); CloseHandle(process_info.hThread);
} }
void void restart() {
restart() {
// If we're running standalone, we have to respawn ourselves via CreateProcess(). // 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 we're running from the service, we should just exit and let it respawn us.
if (GetConsoleWindow() != NULL) { if (GetConsoleWindow() != NULL) {
@ -1313,13 +1229,11 @@ namespace platf {
lifetime::exit_sunshine(0, true); lifetime::exit_sunshine(0, true);
} }
int int set_env(const std::string &name, const std::string &value) {
set_env(const std::string &name, const std::string &value) {
return _putenv_s(name.c_str(), value.c_str()); return _putenv_s(name.c_str(), value.c_str());
} }
int int unset_env(const std::string &name) {
unset_env(const std::string &name) {
return _putenv_s(name.c_str(), ""); return _putenv_s(name.c_str(), "");
} }
@ -1328,8 +1242,7 @@ namespace platf {
bool requested_exit; bool requested_exit;
}; };
static BOOL CALLBACK static BOOL CALLBACK prgrp_enum_windows(HWND hwnd, LPARAM lParam) {
prgrp_enum_windows(HWND hwnd, LPARAM lParam) {
auto enum_ctx = (enum_wnd_context_t *) lParam; auto enum_ctx = (enum_wnd_context_t *) lParam;
// Find the owner PID of this window // Find the owner PID of this window
@ -1345,8 +1258,7 @@ namespace platf {
if (SendNotifyMessageW(hwnd, WM_CLOSE, 0, 0)) { if (SendNotifyMessageW(hwnd, WM_CLOSE, 0, 0)) {
BOOST_LOG(debug) << "Sent WM_CLOSE to PID: "sv << wnd_process_id; BOOST_LOG(debug) << "Sent WM_CLOSE to PID: "sv << wnd_process_id;
enum_ctx->requested_exit = true; enum_ctx->requested_exit = true;
} } else {
else {
auto error = GetLastError(); auto error = GetLastError();
BOOST_LOG(warning) << "Failed to send WM_CLOSE to PID ["sv << wnd_process_id << "]: " << error; BOOST_LOG(warning) << "Failed to send WM_CLOSE to PID ["sv << wnd_process_id << "]: " << error;
} }
@ -1356,8 +1268,7 @@ namespace platf {
return TRUE; return TRUE;
} }
bool bool request_process_group_exit(std::uintptr_t native_handle) {
request_process_group_exit(std::uintptr_t native_handle) {
auto job_handle = (HANDLE) native_handle; auto job_handle = (HANDLE) native_handle;
// Get list of all processes in our job object // Get list of all processes in our job object
@ -1367,8 +1278,7 @@ namespace platf {
auto fg = util::fail_guard([&process_id_list]() { auto fg = util::fail_guard([&process_id_list]() {
free(process_id_list); free(process_id_list);
}); });
while (!(success = QueryInformationJobObject(job_handle, JobObjectBasicProcessIdList, while (!(success = QueryInformationJobObject(job_handle, JobObjectBasicProcessIdList, process_id_list, required_length, &required_length)) &&
process_id_list, required_length, &required_length)) &&
GetLastError() == ERROR_MORE_DATA) { GetLastError() == ERROR_MORE_DATA) {
free(process_id_list); free(process_id_list);
process_id_list = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) calloc(1, required_length); process_id_list = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) calloc(1, required_length);
@ -1381,8 +1291,7 @@ namespace platf {
auto err = GetLastError(); auto err = GetLastError();
BOOST_LOG(warning) << "Failed to enumerate processes in group: "sv << err; BOOST_LOG(warning) << "Failed to enumerate processes in group: "sv << err;
return false; 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 // If all processes are already dead, treat it as a success
return true; return true;
} }
@ -1400,8 +1309,7 @@ namespace platf {
return enum_ctx.requested_exit; return enum_ctx.requested_exit;
} }
bool bool process_group_running(std::uintptr_t native_handle) {
process_group_running(std::uintptr_t native_handle) {
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION accounting_info; JOBOBJECT_BASIC_ACCOUNTING_INFORMATION accounting_info;
if (!QueryInformationJobObject((HANDLE) native_handle, JobObjectBasicAccountingInformation, &accounting_info, sizeof(accounting_info), nullptr)) { if (!QueryInformationJobObject((HANDLE) native_handle, JobObjectBasicAccountingInformation, &accounting_info, sizeof(accounting_info), nullptr)) {
@ -1413,8 +1321,7 @@ namespace platf {
return accounting_info.ActiveProcesses != 0; return accounting_info.ActiveProcesses != 0;
} }
SOCKADDR_IN SOCKADDR_IN to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
SOCKADDR_IN saddr_v4 = {}; SOCKADDR_IN saddr_v4 = {};
saddr_v4.sin_family = AF_INET; saddr_v4.sin_family = AF_INET;
@ -1426,8 +1333,7 @@ namespace platf {
return saddr_v4; return saddr_v4;
} }
SOCKADDR_IN6 SOCKADDR_IN6 to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
SOCKADDR_IN6 saddr_v6 = {}; SOCKADDR_IN6 saddr_v6 = {};
saddr_v6.sin6_family = AF_INET6; 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 // 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. // hardware acceleration to reduce CPU usage. Support for USO was introduced in Windows 10 20H1.
bool bool send_batch(batched_send_info_t &send_info) {
send_batch(batched_send_info_t &send_info) {
WSAMSG msg; WSAMSG msg;
// Convert the target address into a SOCKADDR // Convert the target address into a SOCKADDR
@ -1454,8 +1359,7 @@ namespace platf {
msg.name = (PSOCKADDR) &taddr_v6; msg.name = (PSOCKADDR) &taddr_v6;
msg.namelen = sizeof(taddr_v6); msg.namelen = sizeof(taddr_v6);
} } else {
else {
taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port); taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
msg.name = (PSOCKADDR) &taddr_v4; msg.name = (PSOCKADDR) &taddr_v4;
@ -1477,8 +1381,7 @@ namespace platf {
bufs[bufcount].len = send_info.payload_size; bufs[bufcount].len = send_info.payload_size;
bufcount++; bufcount++;
} }
} } else {
else {
// Translate buffer descriptors into WSABUFs // Translate buffer descriptors into WSABUFs
auto payload_offset = send_info.block_offset * send_info.payload_size; auto payload_offset = send_info.block_offset * send_info.payload_size;
auto payload_length = payload_offset + (send_info.block_count * 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; msg.dwFlags = 0;
// At most, one DWORD option and one PKTINFO option // At most, one DWORD option and one PKTINFO option
char cmbuf[WSA_CMSG_SPACE(sizeof(DWORD)) + char cmbuf[WSA_CMSG_SPACE(sizeof(DWORD)) + std::max(WSA_CMSG_SPACE(sizeof(IN6_PKTINFO)), WSA_CMSG_SPACE(sizeof(IN_PKTINFO)))] = {};
std::max(WSA_CMSG_SPACE(sizeof(IN6_PKTINFO)), WSA_CMSG_SPACE(sizeof(IN_PKTINFO)))] = {};
ULONG cmbuflen = 0; ULONG cmbuflen = 0;
msg.Control.buf = cmbuf; msg.Control.buf = cmbuf;
@ -1517,8 +1419,7 @@ namespace platf {
cm->cmsg_type = IPV6_PKTINFO; cm->cmsg_type = IPV6_PKTINFO;
cm->cmsg_len = WSA_CMSG_LEN(sizeof(pktInfo)); cm->cmsg_len = WSA_CMSG_LEN(sizeof(pktInfo));
memcpy(WSA_CMSG_DATA(cm), &pktInfo, sizeof(pktInfo)); memcpy(WSA_CMSG_DATA(cm), &pktInfo, sizeof(pktInfo));
} } else {
else {
IN_PKTINFO pktInfo; IN_PKTINFO pktInfo;
SOCKADDR_IN saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0); 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; return WSASendMsg((SOCKET) send_info.native_socket, &msg, 0, &bytes_sent, nullptr, nullptr) != SOCKET_ERROR;
} }
bool bool send(send_info_t &send_info) {
send(send_info_t &send_info) {
WSAMSG msg; WSAMSG msg;
// Convert the target address into a SOCKADDR // Convert the target address into a SOCKADDR
@ -1562,8 +1462,7 @@ namespace platf {
msg.name = (PSOCKADDR) &taddr_v6; msg.name = (PSOCKADDR) &taddr_v6;
msg.namelen = sizeof(taddr_v6); msg.namelen = sizeof(taddr_v6);
} } else {
else {
taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port); taddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
msg.name = (PSOCKADDR) &taddr_v4; msg.name = (PSOCKADDR) &taddr_v4;
@ -1605,8 +1504,7 @@ namespace platf {
cm->cmsg_type = IPV6_PKTINFO; cm->cmsg_type = IPV6_PKTINFO;
cm->cmsg_len = WSA_CMSG_LEN(sizeof(pktInfo)); cm->cmsg_len = WSA_CMSG_LEN(sizeof(pktInfo));
memcpy(WSA_CMSG_DATA(cm), &pktInfo, sizeof(pktInfo)); memcpy(WSA_CMSG_DATA(cm), &pktInfo, sizeof(pktInfo));
} } else {
else {
IN_PKTINFO pktInfo; IN_PKTINFO pktInfo;
SOCKADDR_IN saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0); 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 { class qos_t: public deinit_t {
public: public:
qos_t(QOS_FLOWID flow_id): qos_t(QOS_FLOWID flow_id):
flow_id(flow_id) {} flow_id(flow_id) {
}
virtual ~qos_t() { virtual ~qos_t() {
if (!fn_QOSRemoveSocketFromFlow(qos_handle, (SOCKET) NULL, flow_id, 0)) { 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 data_type The type of traffic sent on this socket.
* @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic.
*/ */
std::unique_ptr<deinit_t> 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) {
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_IN saddr_v4;
SOCKADDR_IN6 saddr_v6; SOCKADDR_IN6 saddr_v6;
PSOCKADDR dest_addr; PSOCKADDR dest_addr;
@ -1733,15 +1631,13 @@ namespace platf {
if (connect((SOCKET) native_socket, (PSOCKADDR) &saddr_v6, sizeof(saddr_v6)) < 0) { if (connect((SOCKET) native_socket, (PSOCKADDR) &saddr_v6, sizeof(saddr_v6)) < 0) {
auto wsaerr = WSAGetLastError(); auto wsaerr = WSAGetLastError();
BOOST_LOG(error) << "qWAVE dual-stack workaround failed: "sv << wsaerr; BOOST_LOG(error) << "qWAVE dual-stack workaround failed: "sv << wsaerr;
} } else {
else {
BOOST_LOG(debug) << "Using qWAVE connect() workaround for QoS tagging"sv; BOOST_LOG(debug) << "Using qWAVE connect() workaround for QoS tagging"sv;
using_connect_hack = true; using_connect_hack = true;
dest_addr = nullptr; dest_addr = nullptr;
} }
} }
} } else {
else {
saddr_v4 = to_sockaddr(address.to_v4(), port); saddr_v4 = to_sockaddr(address.to_v4(), port);
dest_addr = (PSOCKADDR) &saddr_v4; dest_addr = (PSOCKADDR) &saddr_v4;
} }
@ -1768,15 +1664,16 @@ namespace platf {
return std::make_unique<qos_t>(flow_id); return std::make_unique<qos_t>(flow_id);
} }
int64_t
qpc_counter() { int64_t qpc_counter() {
LARGE_INTEGER performance_counter; LARGE_INTEGER performance_counter;
if (QueryPerformanceCounter(&performance_counter)) return performance_counter.QuadPart; if (QueryPerformanceCounter(&performance_counter)) {
return performance_counter.QuadPart;
}
return 0; return 0;
} }
std::chrono::nanoseconds std::chrono::nanoseconds qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2) {
qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2) {
auto get_frequency = []() { auto get_frequency = []() {
LARGE_INTEGER frequency; LARGE_INTEGER frequency;
frequency.QuadPart = 0; frequency.QuadPart = 0;
@ -1790,8 +1687,7 @@ namespace platf {
return {}; return {};
} }
std::wstring std::wstring from_utf8(const std::string &string) {
from_utf8(const std::string &string) {
// No conversion needed if the string is empty // No conversion needed if the string is empty
if (string.empty()) { if (string.empty()) {
return {}; return {};
@ -1817,16 +1713,14 @@ namespace platf {
return output; return output;
} }
std::string std::string to_utf8(const std::wstring &string) {
to_utf8(const std::wstring &string) {
// No conversion needed if the string is empty // No conversion needed if the string is empty
if (string.empty()) { if (string.empty()) {
return {}; return {};
} }
// Get the output size required to store the string // Get the output size required to store the string
auto output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(), auto output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(), nullptr, 0, nullptr, nullptr);
nullptr, 0, nullptr, nullptr);
if (output_size == 0) { if (output_size == 0) {
auto winerr = GetLastError(); auto winerr = GetLastError();
BOOST_LOG(error) << "Failed to get UTF-8 buffer size: "sv << winerr; BOOST_LOG(error) << "Failed to get UTF-8 buffer size: "sv << winerr;
@ -1835,8 +1729,7 @@ namespace platf {
// Perform the conversion // Perform the conversion
std::string output(output_size, '\0'); std::string output(output_size, '\0');
output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(), output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(), output.data(), output.size(), nullptr, nullptr);
output.data(), output.size(), nullptr, nullptr);
if (output_size == 0) { if (output_size == 0) {
auto winerr = GetLastError(); auto winerr = GetLastError();
BOOST_LOG(error) << "Failed to convert string to UTF-8: "sv << winerr; BOOST_LOG(error) << "Failed to convert string to UTF-8: "sv << winerr;
@ -1846,8 +1739,7 @@ namespace platf {
return output; return output;
} }
std::string std::string get_host_name() {
get_host_name() {
WCHAR hostname[256]; WCHAR hostname[256];
if (GetHostNameW(hostname, ARRAYSIZE(hostname)) == SOCKET_ERROR) { if (GetHostNameW(hostname, ARRAYSIZE(hostname)) == SOCKET_ERROR) {
BOOST_LOG(error) << "GetHostNameW() failed: "sv << WSAGetLastError(); BOOST_LOG(error) << "GetHostNameW() failed: "sv << WSAGetLastError();
@ -1870,11 +1762,12 @@ namespace platf {
} }
~win32_high_precision_timer() { ~win32_high_precision_timer() {
if (timer) CloseHandle(timer); if (timer) {
CloseHandle(timer);
}
} }
void void sleep_for(const std::chrono::nanoseconds &duration) override {
sleep_for(const std::chrono::nanoseconds &duration) override {
if (!timer) { if (!timer) {
BOOST_LOG(error) << "Attempting high_precision_timer::sleep_for() with uninitialized timer"; BOOST_LOG(error) << "Attempting high_precision_timer::sleep_for() with uninitialized timer";
return; return;
@ -1902,8 +1795,7 @@ namespace platf {
HANDLE timer = NULL; HANDLE timer = NULL;
}; };
std::unique_ptr<high_precision_timer> std::unique_ptr<high_precision_timer> create_high_precision_timer() {
create_high_precision_timer() {
return std::make_unique<win32_high_precision_timer>(); return std::make_unique<win32_high_precision_timer>();
} }
} // namespace platf } // namespace platf

View file

@ -4,36 +4,33 @@
*/ */
#pragma once #pragma once
// standard includes
#include <chrono> #include <chrono>
#include <string_view> #include <string_view>
// platform includes
#include <windows.h> #include <windows.h>
#include <winnt.h> #include <winnt.h>
namespace platf { namespace platf {
void void print_status(const std::string_view &prefix, HRESULT status);
print_status(const std::string_view &prefix, HRESULT status); HDESK syncThreadDesktop();
HDESK
syncThreadDesktop();
int64_t int64_t qpc_counter();
qpc_counter();
std::chrono::nanoseconds std::chrono::nanoseconds qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2);
qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2);
/** /**
* @brief Convert a UTF-8 string into a UTF-16 wide string. * @brief Convert a UTF-8 string into a UTF-16 wide string.
* @param string The UTF-8 string. * @param string The UTF-8 string.
* @return The converted UTF-16 wide string. * @return The converted UTF-16 wide string.
*/ */
std::wstring std::wstring from_utf8(const std::string &string);
from_utf8(const std::string &string);
/** /**
* @brief Convert a UTF-16 wide string into a UTF-8 string. * @brief Convert a UTF-16 wide string into a UTF-8 string.
* @param string The UTF-16 wide string. * @param string The UTF-16 wide string.
* @return The converted UTF-8 string. * @return The converted UTF-8 string.
*/ */
std::string std::string to_utf8(const std::wstring &string);
to_utf8(const std::wstring &string);
} // namespace platf } // namespace platf

View file

@ -2,8 +2,10 @@
* @file src/platform/windows/nvprefs/driver_settings.cpp * @file src/platform/windows/nvprefs/driver_settings.cpp
* @brief Definitions for nvidia driver settings. * @brief Definitions for nvidia driver settings.
*/ */
// local includes // this include
#include "driver_settings.h" #include "driver_settings.h"
// local includes
#include "nvprefs_common.h" #include "nvprefs_common.h"
namespace { namespace {
@ -11,15 +13,13 @@ namespace {
const auto sunshine_application_profile_name = L"SunshineStream"; const auto sunshine_application_profile_name = L"SunshineStream";
const auto sunshine_application_path = L"sunshine.exe"; const auto sunshine_application_path = L"sunshine.exe";
void void nvapi_error_message(NvAPI_Status status) {
nvapi_error_message(NvAPI_Status status) {
NvAPI_ShortString message = {}; NvAPI_ShortString message = {};
NvAPI_GetErrorMessage(status, message); NvAPI_GetErrorMessage(status, message);
nvprefs::error_message(std::string("NvAPI error: ") + message); nvprefs::error_message(std::string("NvAPI error: ") + message);
} }
void void fill_nvapi_string(NvAPI_UnicodeString &dest, const wchar_t *src) {
fill_nvapi_string(NvAPI_UnicodeString &dest, const wchar_t *src) {
static_assert(sizeof(NvU16) == sizeof(wchar_t)); static_assert(sizeof(NvU16) == sizeof(wchar_t));
memcpy_s(dest, NVAPI_UNICODE_STRING_MAX * sizeof(NvU16), src, (wcslen(src) + 1) * sizeof(wchar_t)); memcpy_s(dest, NVAPI_UNICODE_STRING_MAX * sizeof(NvU16), src, (wcslen(src) + 1) * sizeof(wchar_t));
} }
@ -34,9 +34,10 @@ namespace nvprefs {
} }
} }
bool bool driver_settings_t::init() {
driver_settings_t::init() { if (session_handle) {
if (session_handle) return true; return true;
}
NvAPI_Status status; NvAPI_Status status;
@ -56,8 +57,7 @@ namespace nvprefs {
return load_settings(); return load_settings();
} }
void void driver_settings_t::destroy() {
driver_settings_t::destroy() {
if (session_handle) { if (session_handle) {
NvAPI_DRS_DestroySession(session_handle); NvAPI_DRS_DestroySession(session_handle);
session_handle = 0; session_handle = 0;
@ -65,9 +65,10 @@ namespace nvprefs {
NvAPI_Unload(); NvAPI_Unload();
} }
bool bool driver_settings_t::load_settings() {
driver_settings_t::load_settings() { if (!session_handle) {
if (!session_handle) return false; return false;
}
NvAPI_Status status = NvAPI_DRS_LoadSettings(session_handle); NvAPI_Status status = NvAPI_DRS_LoadSettings(session_handle);
if (status != NVAPI_OK) { if (status != NVAPI_OK) {
@ -80,9 +81,10 @@ namespace nvprefs {
return true; return true;
} }
bool bool driver_settings_t::save_settings() {
driver_settings_t::save_settings() { if (!session_handle) {
if (!session_handle) return false; return false;
}
NvAPI_Status status = NvAPI_DRS_SaveSettings(session_handle); NvAPI_Status status = NvAPI_DRS_SaveSettings(session_handle);
if (status != NVAPI_OK) { if (status != NVAPI_OK) {
@ -94,9 +96,10 @@ namespace nvprefs {
return true; return true;
} }
bool bool driver_settings_t::restore_global_profile_to_undo(const undo_data_t &undo_data) {
driver_settings_t::restore_global_profile_to_undo(const undo_data_t &undo_data) { if (!session_handle) {
if (!session_handle) return false; return false;
}
const auto &swapchain_data = undo_data.get_opengl_swapchain(); const auto &swapchain_data = undo_data.get_opengl_swapchain();
if (swapchain_data) { if (swapchain_data) {
@ -130,8 +133,7 @@ namespace nvprefs {
error_message("NvAPI_DRS_SetSetting() OGL_CPL_PREFER_DXPRESENT failed"); error_message("NvAPI_DRS_SetSetting() OGL_CPL_PREFER_DXPRESENT failed");
return false; return false;
} }
} } else {
else {
status = NvAPI_DRS_DeleteProfileSetting(session_handle, profile_handle, OGL_CPL_PREFER_DXPRESENT_ID); status = NvAPI_DRS_DeleteProfileSetting(session_handle, profile_handle, OGL_CPL_PREFER_DXPRESENT_ID);
if (status != NVAPI_OK && status != NVAPI_SETTING_NOT_FOUND) { if (status != NVAPI_OK && status != NVAPI_SETTING_NOT_FOUND) {
@ -142,11 +144,9 @@ namespace nvprefs {
} }
info_message("Restored OGL_CPL_PREFER_DXPRESENT for base profile"); info_message("Restored OGL_CPL_PREFER_DXPRESENT for base profile");
} } else if (status == NVAPI_OK || status == NVAPI_SETTING_NOT_FOUND) {
else if (status == NVAPI_OK || status == NVAPI_SETTING_NOT_FOUND) {
info_message("OGL_CPL_PREFER_DXPRESENT has been changed from our value in base profile, not restoring"); info_message("OGL_CPL_PREFER_DXPRESENT has been changed from our value in base profile, not restoring");
} } else {
else {
error_message("NvAPI_DRS_GetSetting() OGL_CPL_PREFER_DXPRESENT failed"); error_message("NvAPI_DRS_GetSetting() OGL_CPL_PREFER_DXPRESENT failed");
return false; return false;
} }
@ -155,9 +155,10 @@ namespace nvprefs {
return true; return true;
} }
bool bool driver_settings_t::check_and_modify_global_profile(std::optional<undo_data_t> &undo_data) {
driver_settings_t::check_and_modify_global_profile(std::optional<undo_data_t> &undo_data) { if (!session_handle) {
if (!session_handle) return false; return false;
}
undo_data.reset(); undo_data.reset();
NvAPI_Status status; NvAPI_Status status;
@ -184,8 +185,7 @@ namespace nvprefs {
undo_data = undo_data_t(); undo_data = undo_data_t();
if (status == NVAPI_OK) { if (status == NVAPI_OK) {
undo_data->set_opengl_swapchain(OGL_CPL_PREFER_DXPRESENT_PREFER_ENABLED, setting.u32CurrentValue); undo_data->set_opengl_swapchain(OGL_CPL_PREFER_DXPRESENT_PREFER_ENABLED, setting.u32CurrentValue);
} } else {
else {
undo_data->set_opengl_swapchain(OGL_CPL_PREFER_DXPRESENT_PREFER_ENABLED, std::nullopt); undo_data->set_opengl_swapchain(OGL_CPL_PREFER_DXPRESENT_PREFER_ENABLED, std::nullopt);
} }
@ -204,8 +204,7 @@ namespace nvprefs {
} }
info_message("Changed OGL_CPL_PREFER_DXPRESENT to OGL_CPL_PREFER_DXPRESENT_PREFER_ENABLED for base profile"); info_message("Changed OGL_CPL_PREFER_DXPRESENT to OGL_CPL_PREFER_DXPRESENT_PREFER_ENABLED for base profile");
} } else if (status != NVAPI_OK) {
else if (status != NVAPI_OK) {
nvapi_error_message(status); nvapi_error_message(status);
error_message("NvAPI_DRS_GetSetting() OGL_CPL_PREFER_DXPRESENT failed"); error_message("NvAPI_DRS_GetSetting() OGL_CPL_PREFER_DXPRESENT failed");
return false; return false;
@ -214,9 +213,10 @@ namespace nvprefs {
return true; return true;
} }
bool bool driver_settings_t::check_and_modify_application_profile(bool &modified) {
driver_settings_t::check_and_modify_application_profile(bool &modified) { if (!session_handle) {
if (!session_handle) return false; return false;
}
modified = false; modified = false;
NvAPI_Status status; NvAPI_Status status;
@ -285,8 +285,7 @@ namespace nvprefs {
info_message(std::wstring(L"Removed PREFERRED_PSTATE for ") + sunshine_application_path); info_message(std::wstring(L"Removed PREFERRED_PSTATE for ") + sunshine_application_path);
} }
} } else if (status != NVAPI_OK ||
else if (status != NVAPI_OK ||
setting.settingLocation != NVDRS_CURRENT_PROFILE_LOCATION || setting.settingLocation != NVDRS_CURRENT_PROFILE_LOCATION ||
setting.u32CurrentValue != PREFERRED_PSTATE_PREFER_MAX) { setting.u32CurrentValue != PREFERRED_PSTATE_PREFER_MAX) {
// Set power setting if needed // Set power setting if needed

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