clang: adjust formatting rules (#1015)

This commit is contained in:
ReenigneArcher 2023-03-27 21:45:29 -04:00 committed by GitHub
commit 21eb4eb6dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
103 changed files with 26883 additions and 25173 deletions

View file

@ -7,7 +7,7 @@
BasedOnStyle: LLVM BasedOnStyle: LLVM
AccessModifierOffset: -2 AccessModifierOffset: -2
AlignAfterOpenBracket: DontAlign AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: Consecutive AlignConsecutiveAssignments: false
AlignOperands: Align AlignOperands: Align
AllowAllArgumentsOnNextLine: false AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false AllowAllConstructorInitializersOnNextLine: false
@ -18,8 +18,9 @@ AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLambdasOnASingleLine: All AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None AlignTrailingComments: false
AlwaysBreakTemplateDeclarations: Yes AlwaysBreakAfterReturnType: All
AlwaysBreakTemplateDeclarations: MultiLine
BreakBeforeBraces: Custom BreakBeforeBraces: Custom
BraceWrapping: BraceWrapping:
AfterCaseLabel: false AfterCaseLabel: false
@ -37,32 +38,32 @@ BraceWrapping:
SplitEmptyRecord: true SplitEmptyRecord: true
BreakBeforeBinaryOperators: None BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: false BreakBeforeTernaryOperators: false
BreakConstructorInitializers: BeforeColon BreakConstructorInitializers: AfterColon
BreakInheritanceList: BeforeColon BreakInheritanceList: AfterColon
ColumnLimit: 0 ColumnLimit: 0
CompactNamespaces: false CompactNamespaces: false
ContinuationIndentWidth: 2 ContinuationIndentWidth: 2
IndentCaseLabels: false IndentCaseLabels: true
IndentPPDirectives: None IndentPPDirectives: BeforeHash
IndentWidth: 2 IndentWidth: 2
KeepEmptyLinesAtTheStartOfBlocks: true KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 2 MaxEmptyLinesToKeep: 1
NamespaceIndentation: None NamespaceIndentation: All
ObjCSpaceAfterProperty: false ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right PointerAlignment: Right
ReflowComments: false ReflowComments: false
SpaceAfterCStyleCast: false SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true SpaceBeforeCtorInitializerColon: false
SpaceBeforeInheritanceColon: true SpaceBeforeInheritanceColon: false
SpaceBeforeParens: Never SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1 SpacesBeforeTrailingComments: 2
SpacesInAngles: Never SpacesInAngles: Never
SpacesInCStyleCastParentheses: false SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false SpacesInContainerLiterals: false

View file

@ -0,0 +1,40 @@
# standard imports
import os
import subprocess
# variables
directories = [
'src',
'tools',
os.path.join('third-party', 'glad'),
os.path.join('third-party', 'nvfbc'),
os.path.join('third-party', 'wayland-protocols')
]
file_types = [
'cpp',
'h',
'm',
'mm'
]
def clang_format(file: str):
print(f'Formatting {file} ...')
subprocess.run(['clang-format', '-i', file])
def main():
"""
Main entry point.
"""
# walk the directories
for directory in directories:
for root, dirs, files in os.walk(directory):
for file in files:
file_path = os.path.join(root, file)
if os.path.isfile(file_path) and file.rsplit('.')[-1] in file_types:
clang_format(file=file_path)
if __name__ == '__main__':
main()

View file

@ -11,271 +11,279 @@
#include "utility.h" #include "utility.h"
namespace audio { namespace audio {
using namespace std::literals; using namespace std::literals;
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<std::int16_t>>>; using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<std::int16_t>>>;
struct audio_ctx_t { struct audio_ctx_t {
// We want to change the sink for the first stream only // We want to change the sink for the first stream only
std::unique_ptr<std::atomic_bool> sink_flag; std::unique_ptr<std::atomic_bool> sink_flag;
std::unique_ptr<platf::audio_control_t> control; std::unique_ptr<platf::audio_control_t> control;
bool restore_sink; bool restore_sink;
platf::sink_t sink; platf::sink_t sink;
}; };
static int start_audio_control(audio_ctx_t &ctx); static int
static void stop_audio_control(audio_ctx_t &); start_audio_control(audio_ctx_t &ctx);
static void
stop_audio_control(audio_ctx_t &);
int map_stream(int channels, bool quality); int
map_stream(int channels, bool quality);
constexpr auto SAMPLE_RATE = 48000; constexpr auto SAMPLE_RATE = 48000;
opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] { opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] {
{ {
SAMPLE_RATE, SAMPLE_RATE,
2, 2,
1, 1,
1, 1,
platf::speaker::map_stereo, platf::speaker::map_stereo,
96000, 96000,
}, },
{ {
SAMPLE_RATE, SAMPLE_RATE,
2, 2,
1, 1,
1, 1,
platf::speaker::map_stereo, platf::speaker::map_stereo,
512000, 512000,
}, },
{ {
SAMPLE_RATE, SAMPLE_RATE,
6, 6,
4, 4,
2, 2,
platf::speaker::map_surround51, platf::speaker::map_surround51,
256000, 256000,
}, },
{ {
SAMPLE_RATE, SAMPLE_RATE,
6, 6,
6, 6,
0, 0,
platf::speaker::map_surround51, platf::speaker::map_surround51,
1536000, 1536000,
}, },
{ {
SAMPLE_RATE, SAMPLE_RATE,
8, 8,
5, 5,
3, 3,
platf::speaker::map_surround71, platf::speaker::map_surround71,
450000, 450000,
}, },
{ {
SAMPLE_RATE, SAMPLE_RATE,
8, 8,
8, 8,
0, 0,
platf::speaker::map_surround71, platf::speaker::map_surround71,
2048000, 2048000,
}, },
}; };
auto control_shared = safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control); auto control_shared = safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control);
void encodeThread(sample_queue_t samples, config_t config, void *channel_data) { void
auto packets = mail::man->queue<packet_t>(mail::audio_packets); encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])]; auto packets = mail::man->queue<packet_t>(mail::audio_packets);
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
// Encoding takes place on this thread // Encoding takes place on this thread
platf::adjust_thread_priority(platf::thread_priority_e::high); platf::adjust_thread_priority(platf::thread_priority_e::high);
opus_t opus { opus_multistream_encoder_create( opus_t opus { opus_multistream_encoder_create(
stream->sampleRate, stream->sampleRate,
stream->channelCount, stream->channelCount,
stream->streams, stream->streams,
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));
auto frame_size = config.packetDuration * stream->sampleRate / 1000; auto frame_size = config.packetDuration * stream->sampleRate / 1000;
while(auto sample = samples->pop()) { while (auto sample = samples->pop()) {
buffer_t packet { 1400 }; buffer_t packet { 1400 };
int bytes = opus_multistream_encode(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size()); int bytes = opus_multistream_encode(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
if(bytes < 0) { if (bytes < 0) {
BOOST_LOG(error) << "Couldn't encode audio: "sv << opus_strerror(bytes); BOOST_LOG(error) << "Couldn't encode audio: "sv << opus_strerror(bytes);
packets->stop(); packets->stop();
return;
}
packet.fake_resize(bytes);
packets->raise(channel_data, std::move(packet));
}
}
void capture(safe::mail_t mail, config_t config, void *channel_data) {
auto shutdown_event = mail->event<bool>(mail::shutdown);
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
auto ref = control_shared.ref();
if(!ref) {
return;
}
auto &control = ref->control;
if(!control) {
shutdown_event->view();
return;
}
// Order of priority:
// 1. Config
// 2. Virtual if available
// 3. Host
std::string *sink = &ref->sink.host;
if(!config::audio.sink.empty()) {
sink = &config::audio.sink;
}
else if(ref->sink.null) {
auto &null = *ref->sink.null;
switch(stream->channelCount) {
case 2:
sink = &null.stereo;
break;
case 6:
sink = &null.surround51;
break;
case 8:
sink = &null.surround71;
break;
}
}
// Only the first to start a session may change the default sink
if(!ref->sink_flag->exchange(true, std::memory_order_acquire)) {
ref->restore_sink = !config.flags[config_t::HOST_AUDIO];
// If the sink is empty (Host has no sink!), definately switch to the virtual.
if(ref->sink.host.empty()) {
if(control->set_sink(*sink)) {
return;
}
}
// If the client requests audio on the host, don't change the default sink
else if(!config.flags[config_t::HOST_AUDIO] && control->set_sink(*sink)) {
return;
}
}
// Capture takes place on this thread
platf::adjust_thread_priority(platf::thread_priority_e::critical);
auto samples = std::make_shared<sample_queue_t::element_type>(30);
std::thread thread { encodeThread, samples, config, channel_data };
auto fg = util::fail_guard([&]() {
samples->stop();
thread.join();
shutdown_event->view();
});
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
int samples_per_frame = frame_size * stream->channelCount;
auto mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if(!mic) {
BOOST_LOG(error) << "Couldn't create audio input"sv;
return;
}
while(!shutdown_event->peek()) {
std::vector<std::int16_t> sample_buffer;
sample_buffer.resize(samples_per_frame);
auto status = mic->sample(sample_buffer);
switch(status) {
case platf::capture_e::ok:
break;
case platf::capture_e::timeout:
continue;
case platf::capture_e::reinit:
mic.reset();
mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if(!mic) {
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv;
return; return;
} }
return;
default: packet.fake_resize(bytes);
packets->raise(channel_data, std::move(packet));
}
}
void
capture(safe::mail_t mail, config_t config, void *channel_data) {
auto shutdown_event = mail->event<bool>(mail::shutdown);
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
auto ref = control_shared.ref();
if (!ref) {
return; return;
} }
samples->raise(std::move(sample_buffer)); auto &control = ref->control;
if (!control) {
shutdown_event->view();
return;
}
// Order of priority:
// 1. Config
// 2. Virtual if available
// 3. Host
std::string *sink = &ref->sink.host;
if (!config::audio.sink.empty()) {
sink = &config::audio.sink;
}
else if (ref->sink.null) {
auto &null = *ref->sink.null;
switch (stream->channelCount) {
case 2:
sink = &null.stereo;
break;
case 6:
sink = &null.surround51;
break;
case 8:
sink = &null.surround71;
break;
}
}
// Only the first to start a session may change the default sink
if (!ref->sink_flag->exchange(true, std::memory_order_acquire)) {
ref->restore_sink = !config.flags[config_t::HOST_AUDIO];
// If the sink is empty (Host has no sink!), definately switch to the virtual.
if (ref->sink.host.empty()) {
if (control->set_sink(*sink)) {
return;
}
}
// If the client requests audio on the host, don't change the default sink
else if (!config.flags[config_t::HOST_AUDIO] && control->set_sink(*sink)) {
return;
}
}
// Capture takes place on this thread
platf::adjust_thread_priority(platf::thread_priority_e::critical);
auto samples = std::make_shared<sample_queue_t::element_type>(30);
std::thread thread { encodeThread, samples, config, channel_data };
auto fg = util::fail_guard([&]() {
samples->stop();
thread.join();
shutdown_event->view();
});
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
int samples_per_frame = frame_size * stream->channelCount;
auto mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if (!mic) {
BOOST_LOG(error) << "Couldn't create audio input"sv;
return;
}
while (!shutdown_event->peek()) {
std::vector<std::int16_t> sample_buffer;
sample_buffer.resize(samples_per_frame);
auto status = mic->sample(sample_buffer);
switch (status) {
case platf::capture_e::ok:
break;
case platf::capture_e::timeout:
continue;
case platf::capture_e::reinit:
mic.reset();
mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if (!mic) {
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv;
return;
}
return;
default:
return;
}
samples->raise(std::move(sample_buffer));
}
} }
}
int map_stream(int channels, bool quality) { int
int shift = quality ? 1 : 0; map_stream(int channels, bool quality) {
switch(channels) { int shift = quality ? 1 : 0;
case 2: switch (channels) {
return STEREO + shift; case 2:
case 6: return STEREO + shift;
return SURROUND51 + shift; case 6:
case 8: return SURROUND51 + shift;
return SURROUND71 + shift; case 8:
return SURROUND71 + shift;
}
return STEREO;
} }
return STEREO;
}
int start_audio_control(audio_ctx_t &ctx) { int
auto fg = util::fail_guard([]() { start_audio_control(audio_ctx_t &ctx) {
BOOST_LOG(warning) << "There will be no audio"sv; auto fg = util::fail_guard([]() {
}); BOOST_LOG(warning) << "There will be no audio"sv;
});
ctx.sink_flag = std::make_unique<std::atomic_bool>(false); ctx.sink_flag = std::make_unique<std::atomic_bool>(false);
// The default sink has not been replaced yet. // The default sink has not been replaced yet.
ctx.restore_sink = false; ctx.restore_sink = false;
if(!(ctx.control = platf::audio_control())) { if (!(ctx.control = platf::audio_control())) {
return 0;
}
auto sink = ctx.control->sink_info();
if (!sink) {
// Let the calling code know it failed
ctx.control.reset();
return 0;
}
ctx.sink = std::move(*sink);
fg.disable();
return 0; return 0;
} }
auto sink = ctx.control->sink_info(); void
if(!sink) { stop_audio_control(audio_ctx_t &ctx) {
// Let the calling code know it failed // restore audio-sink if applicable
ctx.control.reset(); if (!ctx.restore_sink) {
return 0; return;
}
const std::string &sink = config::audio.sink.empty() ? ctx.sink.host : config::audio.sink;
if (!sink.empty()) {
// Best effort, it's allowed to fail
ctx.control->set_sink(sink);
}
} }
} // namespace audio
ctx.sink = std::move(*sink);
fg.disable();
return 0;
}
void stop_audio_control(audio_ctx_t &ctx) {
// restore audio-sink if applicable
if(!ctx.restore_sink) {
return;
}
const std::string &sink = config::audio.sink.empty() ? ctx.sink.host : config::audio.sink;
if(!sink.empty()) {
// Best effort, it's allowed to fail
ctx.control->set_sink(sink);
}
}
} // namespace audio

View file

@ -4,44 +4,45 @@
#include "thread_safe.h" #include "thread_safe.h"
#include "utility.h" #include "utility.h"
namespace audio { namespace audio {
enum stream_config_e : int { enum stream_config_e : int {
STEREO, STEREO,
HIGH_STEREO, HIGH_STEREO,
SURROUND51, SURROUND51,
HIGH_SURROUND51, HIGH_SURROUND51,
SURROUND71, SURROUND71,
HIGH_SURROUND71, HIGH_SURROUND71,
MAX_STREAM_CONFIG MAX_STREAM_CONFIG
};
struct opus_stream_config_t {
std::int32_t sampleRate;
int channelCount;
int streams;
int coupledStreams;
const std::uint8_t *mapping;
int bitrate;
};
extern opus_stream_config_t stream_configs[MAX_STREAM_CONFIG];
struct config_t {
enum flags_e : int {
HIGH_QUALITY,
HOST_AUDIO,
MAX_FLAGS
}; };
int packetDuration; struct opus_stream_config_t {
int channels; std::int32_t sampleRate;
int mask; int channelCount;
int streams;
int coupledStreams;
const std::uint8_t *mapping;
int bitrate;
};
std::bitset<MAX_FLAGS> flags; extern opus_stream_config_t stream_configs[MAX_STREAM_CONFIG];
};
using buffer_t = util::buffer_t<std::uint8_t>; struct config_t {
using packet_t = std::pair<void *, buffer_t>; enum flags_e : int {
void capture(safe::mail_t mail, config_t config, void *channel_data); HIGH_QUALITY,
} // namespace audio HOST_AUDIO,
MAX_FLAGS
};
int packetDuration;
int channels;
int mask;
std::bitset<MAX_FLAGS> flags;
};
using buffer_t = util::buffer_t<std::uint8_t>;
using packet_t = std::pair<void *, buffer_t>;
void
capture(safe::mail_t mail, config_t config, void *channel_data);
} // namespace audio
#endif #endif

View file

@ -12,289 +12,293 @@ extern "C" {
using namespace std::literals; using namespace std::literals;
namespace cbs { namespace cbs {
void close(CodedBitstreamContext *c) { void
ff_cbs_close(&c); close(CodedBitstreamContext *c) {
} ff_cbs_close(&c);
using ctx_t = util::safe_ptr<CodedBitstreamContext, close>;
class frag_t : public CodedBitstreamFragment {
public:
frag_t(frag_t &&o) {
std::copy((std::uint8_t *)&o, (std::uint8_t *)(&o + 1), (std::uint8_t *)this);
o.data = nullptr;
o.units = nullptr;
};
frag_t() {
std::fill_n((std::uint8_t *)this, sizeof(*this), 0);
} }
frag_t &operator=(frag_t &&o) { using ctx_t = util::safe_ptr<CodedBitstreamContext, close>;
std::copy((std::uint8_t *)&o, (std::uint8_t *)(&o + 1), (std::uint8_t *)this);
o.data = nullptr; class frag_t: public CodedBitstreamFragment {
o.units = nullptr; public:
frag_t(frag_t &&o) {
std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this);
return *this; o.data = nullptr;
}; o.units = nullptr;
};
frag_t() {
~frag_t() { std::fill_n((std::uint8_t *) this, sizeof(*this), 0);
if(data || units) {
ff_cbs_fragment_free(this);
} }
}
};
util::buffer_t<std::uint8_t> write(const cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) { frag_t &
cbs::frag_t frag; operator=(frag_t &&o) {
auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr); std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this);
if(err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {}; o.data = nullptr;
} o.units = nullptr;
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag); return *this;
if(err < 0) { };
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Could not write fragment data: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {}; ~frag_t() {
} if (data || units) {
ff_cbs_fragment_free(this);
}
}
};
// frag.data_size * 8 - frag.data_bit_padding == bits in fragment util::buffer_t<std::uint8_t>
util::buffer_t<std::uint8_t> data { frag.data_size }; write(const cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
std::copy_n(frag.data, frag.data_size, std::begin(data)); cbs::frag_t frag;
auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr);
return data; if (err < 0) {
} char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::ctx_t cbs_ctx;
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
return write(cbs_ctx, nal, uh, codec_id);
}
util::buffer_t<std::uint8_t> make_sps_h264(const AVCodecContext *ctx) {
H264RawSPS sps {};
/* b_per_p == ctx->max_b_frames for h264 */
/* desired_b_depth == avoption("b_depth") == 1 */
/* max_b_depth == std::min(av_log2(ctx->b_per_p) + 1, desired_b_depth) ==> 1 */
auto max_b_depth = 1;
auto dpb_frame = ctx->gop_size == 1 ? 0 : 1 + max_b_depth;
auto mb_width = (FFALIGN(ctx->width, 16) / 16) * 16;
auto mb_height = (FFALIGN(ctx->height, 16) / 16) * 16;
sps.nal_unit_header.nal_ref_idc = 3;
sps.nal_unit_header.nal_unit_type = H264_NAL_SPS;
sps.profile_idc = FF_PROFILE_H264_HIGH & 0xFF;
sps.constraint_set1_flag = 1;
if(ctx->level != FF_LEVEL_UNKNOWN) {
sps.level_idc = ctx->level;
}
else {
auto framerate = ctx->framerate;
auto level = ff_h264_guess_level(
sps.profile_idc,
ctx->bit_rate,
framerate.num / framerate.den,
mb_width,
mb_height,
dpb_frame);
if(!level) {
BOOST_LOG(error) << "Could not guess h264 level"sv;
return {}; return {};
} }
sps.level_idc = level->level_idc;
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag);
if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Could not write fragment data: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
}
// frag.data_size * 8 - frag.data_bit_padding == bits in fragment
util::buffer_t<std::uint8_t> data { frag.data_size };
std::copy_n(frag.data, frag.data_size, std::begin(data));
return data;
} }
sps.seq_parameter_set_id = 0; util::buffer_t<std::uint8_t>
sps.chroma_format_idc = 1; write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::ctx_t cbs_ctx;
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
sps.log2_max_frame_num_minus4 = 3; // 4; return write(cbs_ctx, nal, uh, codec_id);
sps.pic_order_cnt_type = 0;
sps.log2_max_pic_order_cnt_lsb_minus4 = 0; // 4;
sps.max_num_ref_frames = dpb_frame;
sps.pic_width_in_mbs_minus1 = mb_width / 16 - 1;
sps.pic_height_in_map_units_minus1 = mb_height / 16 - 1;
sps.frame_mbs_only_flag = 1;
sps.direct_8x8_inference_flag = 1;
if(ctx->width != mb_width || ctx->height != mb_height) {
sps.frame_cropping_flag = 1;
sps.frame_crop_left_offset = 0;
sps.frame_crop_top_offset = 0;
sps.frame_crop_right_offset = (mb_width - ctx->width) / 2;
sps.frame_crop_bottom_offset = (mb_height - ctx->height) / 2;
} }
sps.vui_parameters_present_flag = 1; util::buffer_t<std::uint8_t>
make_sps_h264(const AVCodecContext *ctx) {
H264RawSPS sps {};
auto &vui = sps.vui; /* b_per_p == ctx->max_b_frames for h264 */
/* desired_b_depth == avoption("b_depth") == 1 */
/* max_b_depth == std::min(av_log2(ctx->b_per_p) + 1, desired_b_depth) ==> 1 */
auto max_b_depth = 1;
auto dpb_frame = ctx->gop_size == 1 ? 0 : 1 + max_b_depth;
auto mb_width = (FFALIGN(ctx->width, 16) / 16) * 16;
auto mb_height = (FFALIGN(ctx->height, 16) / 16) * 16;
vui.video_format = 5; sps.nal_unit_header.nal_ref_idc = 3;
vui.colour_description_present_flag = 1; sps.nal_unit_header.nal_unit_type = H264_NAL_SPS;
vui.video_signal_type_present_flag = 1;
vui.video_full_range_flag = ctx->color_range == AVCOL_RANGE_JPEG;
vui.colour_primaries = ctx->color_primaries;
vui.transfer_characteristics = ctx->color_trc;
vui.matrix_coefficients = ctx->colorspace;
vui.low_delay_hrd_flag = 1 - vui.fixed_frame_rate_flag; sps.profile_idc = FF_PROFILE_H264_HIGH & 0xFF;
vui.bitstream_restriction_flag = 1; sps.constraint_set1_flag = 1;
vui.motion_vectors_over_pic_boundaries_flag = 1;
vui.log2_max_mv_length_horizontal = 15;
vui.log2_max_mv_length_vertical = 15;
vui.max_num_reorder_frames = max_b_depth;
vui.max_dec_frame_buffering = max_b_depth + 1;
return write(sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H264); if (ctx->level != FF_LEVEL_UNKNOWN) {
} sps.level_idc = ctx->level;
}
else {
auto framerate = ctx->framerate;
hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) { auto level = ff_h264_guess_level(
cbs::ctx_t ctx; sps.profile_idc,
if(ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) { ctx->bit_rate,
return {}; framerate.num / framerate.den,
mb_width,
mb_height,
dpb_frame);
if (!level) {
BOOST_LOG(error) << "Could not guess h264 level"sv;
return {};
}
sps.level_idc = level->level_idc;
}
sps.seq_parameter_set_id = 0;
sps.chroma_format_idc = 1;
sps.log2_max_frame_num_minus4 = 3; // 4;
sps.pic_order_cnt_type = 0;
sps.log2_max_pic_order_cnt_lsb_minus4 = 0; // 4;
sps.max_num_ref_frames = dpb_frame;
sps.pic_width_in_mbs_minus1 = mb_width / 16 - 1;
sps.pic_height_in_map_units_minus1 = mb_height / 16 - 1;
sps.frame_mbs_only_flag = 1;
sps.direct_8x8_inference_flag = 1;
if (ctx->width != mb_width || ctx->height != mb_height) {
sps.frame_cropping_flag = 1;
sps.frame_crop_left_offset = 0;
sps.frame_crop_top_offset = 0;
sps.frame_crop_right_offset = (mb_width - ctx->width) / 2;
sps.frame_crop_bottom_offset = (mb_height - ctx->height) / 2;
}
sps.vui_parameters_present_flag = 1;
auto &vui = sps.vui;
vui.video_format = 5;
vui.colour_description_present_flag = 1;
vui.video_signal_type_present_flag = 1;
vui.video_full_range_flag = ctx->color_range == AVCOL_RANGE_JPEG;
vui.colour_primaries = ctx->color_primaries;
vui.transfer_characteristics = ctx->color_trc;
vui.matrix_coefficients = ctx->colorspace;
vui.low_delay_hrd_flag = 1 - vui.fixed_frame_rate_flag;
vui.bitstream_restriction_flag = 1;
vui.motion_vectors_over_pic_boundaries_flag = 1;
vui.log2_max_mv_length_horizontal = 15;
vui.log2_max_mv_length_vertical = 15;
vui.max_num_reorder_frames = max_b_depth;
vui.max_dec_frame_buffering = max_b_depth + 1;
return write(sps.nal_unit_header.nal_unit_type, (void *) &sps.nal_unit_header, AV_CODEC_ID_H264);
} }
cbs::frag_t frag; hevc_t
make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) {
return {};
}
int err = ff_cbs_read_packet(ctx.get(), &frag, packet); cbs::frag_t frag;
if(err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {}; int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
}
auto vps_p = ((CodedBitstreamH265Context *) ctx->priv_data)->active_vps;
auto sps_p = ((CodedBitstreamH265Context *) ctx->priv_data)->active_sps;
H265RawSPS sps { *sps_p };
H265RawVPS vps { *vps_p };
vps.profile_tier_level.general_profile_compatibility_flag[4] = 1;
sps.profile_tier_level.general_profile_compatibility_flag[4] = 1;
auto &vui = sps.vui;
std::memset(&vui, 0, sizeof(vui));
sps.vui_parameters_present_flag = 1;
// skip sample aspect ratio
vui.video_format = 5;
vui.colour_description_present_flag = 1;
vui.video_signal_type_present_flag = 1;
vui.video_full_range_flag = avctx->color_range == AVCOL_RANGE_JPEG;
vui.colour_primaries = avctx->color_primaries;
vui.transfer_characteristics = avctx->color_trc;
vui.matrix_coefficients = avctx->colorspace;
vui.vui_timing_info_present_flag = vps.vps_timing_info_present_flag;
vui.vui_num_units_in_tick = vps.vps_num_units_in_tick;
vui.vui_time_scale = vps.vps_time_scale;
vui.vui_poc_proportional_to_timing_flag = vps.vps_poc_proportional_to_timing_flag;
vui.vui_num_ticks_poc_diff_one_minus1 = vps.vps_num_ticks_poc_diff_one_minus1;
vui.vui_hrd_parameters_present_flag = 0;
vui.bitstream_restriction_flag = 1;
vui.motion_vectors_over_pic_boundaries_flag = 1;
vui.restricted_ref_pic_lists_flag = 1;
vui.max_bytes_per_pic_denom = 0;
vui.max_bits_per_min_cu_denom = 0;
vui.log2_max_mv_length_horizontal = 15;
vui.log2_max_mv_length_vertical = 15;
cbs::ctx_t write_ctx;
ff_cbs_init(&write_ctx, AV_CODEC_ID_H265, nullptr);
return hevc_t {
nal_t {
write(write_ctx, vps.nal_unit_header.nal_unit_type, (void *) &vps.nal_unit_header, AV_CODEC_ID_H265),
write(ctx, vps_p->nal_unit_header.nal_unit_type, (void *) &vps_p->nal_unit_header, AV_CODEC_ID_H265),
},
nal_t {
write(write_ctx, sps.nal_unit_header.nal_unit_type, (void *) &sps.nal_unit_header, AV_CODEC_ID_H265),
write(ctx, sps_p->nal_unit_header.nal_unit_type, (void *) &sps_p->nal_unit_header, AV_CODEC_ID_H265),
},
};
} }
util::buffer_t<std::uint8_t>
read_sps_h264(const AVPacket *packet) {
cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
return {};
}
auto vps_p = ((CodedBitstreamH265Context *)ctx->priv_data)->active_vps; cbs::frag_t frag;
auto sps_p = ((CodedBitstreamH265Context *)ctx->priv_data)->active_sps;
H265RawSPS sps { *sps_p }; int err = ff_cbs_read_packet(ctx.get(), &frag, &*packet);
H265RawVPS vps { *vps_p }; if (err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
vps.profile_tier_level.general_profile_compatibility_flag[4] = 1; return {};
sps.profile_tier_level.general_profile_compatibility_flag[4] = 1; }
auto &vui = sps.vui; auto h264 = (H264RawNALUnitHeader *) ((CodedBitstreamH264Context *) ctx->priv_data)->active_sps;
std::memset(&vui, 0, sizeof(vui)); return write(h264->nal_unit_type, (void *) h264, AV_CODEC_ID_H264);
sps.vui_parameters_present_flag = 1;
// skip sample aspect ratio
vui.video_format = 5;
vui.colour_description_present_flag = 1;
vui.video_signal_type_present_flag = 1;
vui.video_full_range_flag = avctx->color_range == AVCOL_RANGE_JPEG;
vui.colour_primaries = avctx->color_primaries;
vui.transfer_characteristics = avctx->color_trc;
vui.matrix_coefficients = avctx->colorspace;
vui.vui_timing_info_present_flag = vps.vps_timing_info_present_flag;
vui.vui_num_units_in_tick = vps.vps_num_units_in_tick;
vui.vui_time_scale = vps.vps_time_scale;
vui.vui_poc_proportional_to_timing_flag = vps.vps_poc_proportional_to_timing_flag;
vui.vui_num_ticks_poc_diff_one_minus1 = vps.vps_num_ticks_poc_diff_one_minus1;
vui.vui_hrd_parameters_present_flag = 0;
vui.bitstream_restriction_flag = 1;
vui.motion_vectors_over_pic_boundaries_flag = 1;
vui.restricted_ref_pic_lists_flag = 1;
vui.max_bytes_per_pic_denom = 0;
vui.max_bits_per_min_cu_denom = 0;
vui.log2_max_mv_length_horizontal = 15;
vui.log2_max_mv_length_vertical = 15;
cbs::ctx_t write_ctx;
ff_cbs_init(&write_ctx, AV_CODEC_ID_H265, nullptr);
return hevc_t {
nal_t {
write(write_ctx, vps.nal_unit_header.nal_unit_type, (void *)&vps.nal_unit_header, AV_CODEC_ID_H265),
write(ctx, vps_p->nal_unit_header.nal_unit_type, (void *)&vps_p->nal_unit_header, AV_CODEC_ID_H265),
},
nal_t {
write(write_ctx, sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H265),
write(ctx, sps_p->nal_unit_header.nal_unit_type, (void *)&sps_p->nal_unit_header, AV_CODEC_ID_H265),
},
};
}
util::buffer_t<std::uint8_t> read_sps_h264(const AVPacket *packet) {
cbs::ctx_t ctx;
if(ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
return {};
} }
cbs::frag_t frag; h264_t
make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet) {
int err = ff_cbs_read_packet(ctx.get(), &frag, &*packet); return h264_t {
if(err < 0) { make_sps_h264(ctx),
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; read_sps_h264(packet),
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); };
return {};
} }
auto h264 = (H264RawNALUnitHeader *)((CodedBitstreamH264Context *)ctx->priv_data)->active_sps; bool
return write(h264->nal_unit_type, (void *)h264, AV_CODEC_ID_H264); validate_sps(const AVPacket *packet, int codec_id) {
} cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, (AVCodecID) codec_id, nullptr)) {
h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet) {
return h264_t {
make_sps_h264(ctx),
read_sps_h264(packet),
};
}
bool validate_sps(const AVPacket *packet, int codec_id) {
cbs::ctx_t ctx;
if(ff_cbs_init(&ctx, (AVCodecID)codec_id, nullptr)) {
return false;
}
cbs::frag_t frag;
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if(err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return false;
}
if(codec_id == AV_CODEC_ID_H264) {
auto h264 = (CodedBitstreamH264Context *)ctx->priv_data;
if(!h264->active_sps->vui_parameters_present_flag) {
return false; return false;
} }
return true; cbs::frag_t frag;
}
return ((CodedBitstreamH265Context *)ctx->priv_data)->active_sps->vui_parameters_present_flag; int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
} if (err < 0) {
} // namespace cbs char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return false;
}
if (codec_id == AV_CODEC_ID_H264) {
auto h264 = (CodedBitstreamH264Context *) ctx->priv_data;
if (!h264->active_sps->vui_parameters_present_flag) {
return false;
}
return true;
}
return ((CodedBitstreamH265Context *) ctx->priv_data)->active_sps->vui_parameters_present_flag;
}
} // namespace cbs

View file

@ -8,27 +8,30 @@ struct AVCodecContext;
namespace cbs { namespace cbs {
struct nal_t { struct nal_t {
util::buffer_t<std::uint8_t> _new; util::buffer_t<std::uint8_t> _new;
util::buffer_t<std::uint8_t> old; util::buffer_t<std::uint8_t> old;
}; };
struct hevc_t { struct hevc_t {
nal_t vps; nal_t vps;
nal_t sps; nal_t sps;
}; };
struct h264_t { struct h264_t {
nal_t sps; nal_t sps;
}; };
hevc_t make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet); hevc_t
h264_t make_sps_h264(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);
/** /**
* Check if SPS->VUI is present * Check if SPS->VUI is present
*/ */
bool validate_sps(const AVPacket *packet, int codec_id); bool
} // namespace cbs validate_sps(const AVPacket *packet, int codec_id);
} // namespace cbs
#endif #endif

File diff suppressed because it is too large Load diff

View file

@ -9,153 +9,157 @@
#include <vector> #include <vector>
namespace config { namespace config {
struct video_t { struct video_t {
// ffmpeg params // ffmpeg params
int qp; // higher == more compression and less quality int qp; // higher == more compression and less quality
int hevc_mode; int hevc_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;
} sw; } sw;
struct { struct {
std::optional<int> nv_preset; std::optional<int> nv_preset;
std::optional<int> nv_tune; std::optional<int> nv_tune;
std::optional<int> nv_rc; std::optional<int> nv_rc;
int nv_coder; int nv_coder;
} nv; } nv;
struct { struct {
std::optional<int> qsv_preset; std::optional<int> qsv_preset;
std::optional<int> qsv_cavlc; std::optional<int> qsv_cavlc;
} qsv; } qsv;
struct { struct {
std::optional<int> amd_quality_h264; std::optional<int> amd_quality_h264;
std::optional<int> amd_quality_hevc; std::optional<int> amd_quality_hevc;
std::optional<int> amd_rc_h264; std::optional<int> amd_rc_h264;
std::optional<int> amd_rc_hevc; std::optional<int> amd_rc_hevc;
std::optional<int> amd_usage_h264; std::optional<int> amd_usage_h264;
std::optional<int> amd_usage_hevc; std::optional<int> amd_usage_hevc;
std::optional<int> amd_preanalysis; std::optional<int> amd_preanalysis;
std::optional<int> amd_vbaq; std::optional<int> amd_vbaq;
int amd_coder; int amd_coder;
} amd; } amd;
struct { struct {
int vt_allow_sw; int vt_allow_sw;
int vt_require_sw; int vt_require_sw;
int vt_realtime; int vt_realtime;
int vt_coder; int vt_coder;
} vt; } vt;
std::string capture; std::string capture;
std::string encoder; std::string encoder;
std::string adapter_name; std::string adapter_name;
std::string output_name; std::string output_name;
bool dwmflush; bool dwmflush;
}; };
struct audio_t { struct audio_t {
std::string sink; std::string sink;
std::string virtual_sink; std::string virtual_sink;
}; };
struct stream_t { struct stream_t {
std::chrono::milliseconds ping_timeout; std::chrono::milliseconds ping_timeout;
std::string file_apps; std::string file_apps;
int fec_percentage; int fec_percentage;
// max unique instances of video and audio streams // max unique instances of video and audio streams
int channels; int channels;
}; };
struct nvhttp_t { struct nvhttp_t {
// Could be any of the following values: // Could be any of the following values:
// pc|lan|wan // pc|lan|wan
std::string origin_pin_allowed; std::string origin_pin_allowed;
std::string origin_web_ui_allowed; std::string origin_web_ui_allowed;
std::string pkey; // must be 2048 bits std::string pkey; // must be 2048 bits
std::string cert; // must be signed with a key of 2048 bits std::string cert; // must be signed with a key of 2048 bits
std::string sunshine_name; std::string sunshine_name;
std::string file_state; std::string file_state;
std::string external_ip; std::string external_ip;
std::vector<std::string> resolutions; std::vector<std::string> resolutions;
std::vector<int> fps; std::vector<int> fps;
}; };
struct input_t { struct input_t {
std::unordered_map<int, int> keybindings; std::unordered_map<int, int> keybindings;
std::chrono::milliseconds back_button_timeout; std::chrono::milliseconds back_button_timeout;
std::chrono::milliseconds key_repeat_delay; std::chrono::milliseconds key_repeat_delay;
std::chrono::duration<double> key_repeat_period; std::chrono::duration<double> key_repeat_period;
std::string gamepad; std::string gamepad;
bool keyboard; bool keyboard;
bool mouse; bool mouse;
bool controller; bool controller;
}; };
namespace flag { namespace flag {
enum flag_e : std::size_t { enum flag_e : std::size_t {
PIN_STDIN = 0, // Read PIN from stdin instead of http PIN_STDIN = 0, // Read PIN from stdin instead of http
FRESH_STATE, // Do not load or save state FRESH_STATE, // Do not load or save state
FORCE_VIDEO_HEADER_REPLACE, // force replacing headers inside video data FORCE_VIDEO_HEADER_REPLACE, // force replacing headers inside video data
UPNP, // Try Universal Plug 'n Play UPNP, // Try Universal Plug 'n Play
CONST_PIN, // Use "universal" pin CONST_PIN, // Use "universal" pin
FLAG_SIZE FLAG_SIZE
}; };
} }
struct prep_cmd_t { struct prep_cmd_t {
prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd) : do_cmd(std::move(do_cmd)), undo_cmd(std::move(undo_cmd)) {} prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd):
explicit prep_cmd_t(std::string &&do_cmd) : do_cmd(std::move(do_cmd)) {} do_cmd(std::move(do_cmd)), undo_cmd(std::move(undo_cmd)) {}
std::string do_cmd; explicit prep_cmd_t(std::string &&do_cmd):
std::string undo_cmd; do_cmd(std::move(do_cmd)) {}
}; std::string do_cmd;
std::string undo_cmd;
};
struct sunshine_t { struct sunshine_t {
int min_log_level; int min_log_level;
std::bitset<flag::FLAG_SIZE> flags; std::bitset<flag::FLAG_SIZE> flags;
std::string credentials_file; std::string credentials_file;
std::string username; std::string username;
std::string password; std::string password;
std::string salt; std::string salt;
std::string config_file; std::string config_file;
struct cmd_t { struct cmd_t {
std::string name; std::string name;
int argc; int argc;
char **argv; char **argv;
} cmd; } cmd;
std::uint16_t port; std::uint16_t port;
std::string log_file; std::string log_file;
std::vector<prep_cmd_t> prep_cmds; std::vector<prep_cmd_t> prep_cmds;
}; };
extern video_t video; extern video_t video;
extern audio_t audio; extern audio_t audio;
extern stream_t stream; extern stream_t stream;
extern nvhttp_t nvhttp; extern nvhttp_t nvhttp;
extern input_t input; extern input_t input;
extern sunshine_t sunshine; extern sunshine_t sunshine;
int parse(int argc, char *argv[]); int
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content); parse(int argc, char *argv[]);
} // namespace config std::unordered_map<std::string, std::string>
parse_config(const std::string_view &file_content);
} // namespace config
#endif #endif

File diff suppressed because it is too large Load diff

View file

@ -10,11 +10,11 @@
#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 start(); void
} // namespace confighttp start();
} // namespace confighttp
// mime types map // mime types map
const std::map<std::string, std::string> mime_types = { const std::map<std::string, std::string> mime_types = {
@ -35,4 +35,4 @@ const std::map<std::string, std::string> mime_types = {
{ "xml", "text/xml" }, { "xml", "text/xml" },
}; };
#endif // SUNSHINE_CONFIGHTTP_H #endif // SUNSHINE_CONFIGHTTP_H

View file

@ -4,484 +4,511 @@
#include <openssl/pem.h> #include <openssl/pem.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() : _certs {}, _cert_ctx { X509_STORE_CTX_new() } {} cert_chain_t::cert_chain_t():
void cert_chain_t::add(x509_t &&cert) { _certs {}, _cert_ctx { X509_STORE_CTX_new() } {}
x509_store_t x509_store { X509_STORE_new() }; void
cert_chain_t::add(x509_t &&cert) {
x509_store_t x509_store { X509_STORE_new() };
X509_STORE_add_cert(x509_store.get(), cert.get()); 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)));
}
static int openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
int err_code = X509_STORE_CTX_get_error(ctx);
switch(err_code) {
// Expired or not-yet-valid certificates are fine. Sometimes Moonlight is running on embedded devices
// that don't have accurate clocks (or haven't yet synchronized by the time Moonlight first runs).
// This behavior also matches what GeForce Experience does.
// FIXME: Checking for X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY is a temporary workaround to get moonlight-embedded to work on the raspberry pi
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_CERT_HAS_EXPIRED:
return 1;
default:
return ok;
} }
}
/* static int
openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
int err_code = X509_STORE_CTX_get_error(ctx);
switch (err_code) {
// Expired or not-yet-valid certificates are fine. Sometimes Moonlight is running on embedded devices
// that don't have accurate clocks (or haven't yet synchronized by the time Moonlight first runs).
// This behavior also matches what GeForce Experience does.
// FIXME: Checking for X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY is a temporary workaround to get moonlight-embedded to work on the raspberry pi
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_CERT_HAS_EXPIRED:
return 1;
default:
return ok;
}
}
/*
* When certificates from two or more instances of Moonlight have been added to x509_store_t, * When certificates from two or more instances of Moonlight have been added to x509_store_t,
* only one of them will be verified by X509_verify_cert, resulting in only a single instance of * only one of them will be verified by X509_verify_cert, resulting in only a single instance of
* Moonlight to be able to use Sunshine * Moonlight to be able to use Sunshine
* *
* To circumvent this, x509_store_t instance will be created for each instance of the certificates. * To circumvent this, x509_store_t instance will be created for each instance of the certificates.
*/ */
const char *cert_chain_t::verify(x509_t::element_type *cert) { const char *
int err_code = 0; cert_chain_t::verify(x509_t::element_type *cert) {
for(auto &[_, x509_store] : _certs) { int err_code = 0;
auto fg = util::fail_guard([this]() { for (auto &[_, x509_store] : _certs) {
X509_STORE_CTX_cleanup(_cert_ctx.get()); auto fg = util::fail_guard([this]() {
}); X509_STORE_CTX_cleanup(_cert_ctx.get());
});
X509_STORE_CTX_init(_cert_ctx.get(), x509_store.get(), cert, nullptr); X509_STORE_CTX_init(_cert_ctx.get(), x509_store.get(), cert, nullptr);
X509_STORE_CTX_set_verify_cb(_cert_ctx.get(), openssl_verify_cb); X509_STORE_CTX_set_verify_cb(_cert_ctx.get(), openssl_verify_cb);
// We don't care to validate the entire chain for the purposes of client auth. // We don't care to validate the entire chain for the purposes of client auth.
// Some versions of clients forked from Moonlight Embedded produce client certs // Some versions of clients forked from Moonlight Embedded produce client certs
// that OpenSSL doesn't detect as self-signed due to some X509v3 extensions. // that OpenSSL doesn't detect as self-signed due to some X509v3 extensions.
X509_STORE_CTX_set_flags(_cert_ctx.get(), X509_V_FLAG_PARTIAL_CHAIN); X509_STORE_CTX_set_flags(_cert_ctx.get(), X509_V_FLAG_PARTIAL_CHAIN);
auto err = X509_verify_cert(_cert_ctx.get()); auto err = X509_verify_cert(_cert_ctx.get());
if(err == 1) { if (err == 1) {
return nullptr; return nullptr;
}
err_code = X509_STORE_CTX_get_error(_cert_ctx.get());
if (err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) {
return X509_verify_cert_error_string(err_code);
}
} }
err_code = X509_STORE_CTX_get_error(_cert_ctx.get()); return X509_verify_cert_error_string(err_code);
}
if(err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) { namespace cipher {
return X509_verify_cert_error_string(err_code);
static int
init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new());
if (!ctx) {
return -1;
}
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
return -1;
}
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) {
return -1;
}
if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
return 0;
} }
static int
init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
return -1;
}
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) {
return -1;
}
if (EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
return 0;
}
static int
init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_cbc(), nullptr, key->data(), iv->data()) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
return 0;
}
int
gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
if (!decrypt_ctx && init_decrypt_gcm(decrypt_ctx, &key, iv, padding)) {
return -1;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if (EVP_DecryptInit_ex(decrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return false;
}
auto cipher = tagged_cipher.substr(tag_size);
auto tag = tagged_cipher.substr(0, tag_size);
plaintext.resize((cipher.size() + 15) / 16 * 16);
int size;
if (EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *) cipher.data(), cipher.size()) != 1) {
return -1;
}
if (EVP_CIPHER_CTX_ctrl(decrypt_ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.data())) != 1) {
return -1;
}
int len = size;
if (EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data() + size, &len) != 1) {
return -1;
}
plaintext.resize(size + len);
return 0;
}
int
gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
if (!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, iv, padding)) {
return -1;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if (EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return -1;
}
auto tag = tagged_cipher;
auto cipher = tag + tag_size;
int len;
int size = round_to_pkcs7_padded(plaintext.size());
// Encrypt into the caller's buffer
if (EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *) plaintext.data(), plaintext.size()) != 1) {
return -1;
}
// GCM encryption won't ever fill ciphertext here but we have to call it anyway
if (EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
return -1;
}
if (EVP_CIPHER_CTX_ctrl(encrypt_ctx.get(), EVP_CTRL_GCM_GET_TAG, tag_size, tag) != 1) {
return -1;
}
return len + size;
}
int
ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
int len;
auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(decrypt_ctx.get());
});
// Gen 7 servers use 128-bit AES ECB
if (EVP_DecryptInit_ex(decrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(decrypt_ctx.get(), padding);
plaintext.resize((cipher.size() + 15) / 16 * 16);
auto size = (int) plaintext.size();
// Decrypt into the caller's buffer, leaving room for the auth tag to be prepended
if (EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *) cipher.data(), cipher.size()) != 1) {
return -1;
}
if (EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data(), &len) != 1) {
return -1;
}
plaintext.resize(len + size);
return 0;
}
int
ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(encrypt_ctx.get());
});
// Gen 7 servers use 128-bit AES ECB
if (EVP_EncryptInit_ex(encrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(encrypt_ctx.get(), padding);
int len;
cipher.resize((plaintext.size() + 15) / 16 * 16);
auto size = (int) cipher.size();
// Encrypt into the caller's buffer
if (EVP_EncryptUpdate(encrypt_ctx.get(), cipher.data(), &size, (const std::uint8_t *) plaintext.data(), plaintext.size()) != 1) {
return -1;
}
if (EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher.data() + size, &len) != 1) {
return -1;
}
cipher.resize(len + size);
return 0;
}
int
cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
if (!encrypt_ctx && init_encrypt_cbc(encrypt_ctx, &key, iv, padding)) {
return -1;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if (EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return false;
}
int len;
int size = plaintext.size(); // round_to_pkcs7_padded(plaintext.size());
// Encrypt into the caller's buffer
if (EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *) plaintext.data(), plaintext.size()) != 1) {
return -1;
}
if (EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
return -1;
}
return size + len;
}
ecb_t::ecb_t(const aes_t &key, bool padding):
cipher_t { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding } {}
cbc_t::cbc_t(const aes_t &key, bool padding):
cipher_t { nullptr, nullptr, key, padding } {}
gcm_t::gcm_t(const crypto::aes_t &key, bool padding):
cipher_t { nullptr, nullptr, key, padding } {}
} // namespace cipher
aes_t
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
aes_t key;
std::string salt_pin;
salt_pin.reserve(salt.size() + pin.size());
salt_pin.insert(std::end(salt_pin), std::begin(salt), std::end(salt));
salt_pin.insert(std::end(salt_pin), std::begin(pin), std::end(pin));
auto hsh = hash(salt_pin);
std::copy(std::begin(hsh), std::begin(hsh) + key.size(), std::begin(key));
return key;
} }
return X509_verify_cert_error_string(err_code); sha256_t
} hash(const std::string_view &plaintext) {
sha256_t hsh;
namespace cipher { EVP_Digest(plaintext.data(), plaintext.size(), hsh.data(), nullptr, EVP_sha256(), nullptr);
return hsh;
static int init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
ctx.reset(EVP_CIPHER_CTX_new());
if(!ctx) {
return -1;
} }
if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) { x509_t
return -1; x509(const std::string_view &x) {
bio_t io { BIO_new(BIO_s_mem()) };
BIO_write(io.get(), x.data(), x.size());
x509_t p;
PEM_read_bio_X509(io.get(), &p, nullptr, nullptr);
return p;
} }
if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) { pkey_t
return -1; pkey(const std::string_view &k) {
bio_t io { BIO_new(BIO_s_mem()) };
BIO_write(io.get(), k.data(), k.size());
pkey_t p = nullptr;
PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr);
return p;
} }
if(EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) { std::string
return -1; pem(x509_t &x509) {
} bio_t bio { BIO_new(BIO_s_mem()) };
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
return 0; PEM_write_bio_X509(bio.get(), x509.get());
} BUF_MEM *mem_ptr;
BIO_get_mem_ptr(bio.get(), &mem_ptr);
static int init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) { return { mem_ptr->data, mem_ptr->length };
ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB
if(EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
return -1;
} }
if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) { std::string
return -1; pem(pkey_t &pkey) {
bio_t bio { BIO_new(BIO_s_mem()) };
PEM_write_bio_PrivateKey(bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
BUF_MEM *mem_ptr;
BIO_get_mem_ptr(bio.get(), &mem_ptr);
return { mem_ptr->data, mem_ptr->length };
} }
if(EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) { std::string_view
return -1; signature(const x509_t &x) {
} // X509_ALGOR *_ = nullptr;
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
return 0; const ASN1_BIT_STRING *asn1 = nullptr;
} X509_get0_signature(&asn1, nullptr, x.get());
static int init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) { return { (const char *) asn1->data, (std::size_t) asn1->length };
ctx.reset(EVP_CIPHER_CTX_new());
// Gen 7 servers use 128-bit AES ECB
if(EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_cbc(), nullptr, key->data(), iv->data()) != 1) {
return -1;
} }
EVP_CIPHER_CTX_set_padding(ctx.get(), padding); std::string
rand(std::size_t bytes) {
std::string r;
r.resize(bytes);
return 0; RAND_bytes((uint8_t *) r.data(), r.size());
}
int gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) { return r;
if(!decrypt_ctx && init_decrypt_gcm(decrypt_ctx, &key, iv, padding)) {
return -1;
} }
// Calling with cipher == nullptr results in a parameter change std::vector<uint8_t>
// without requiring a reallocation of the internal cipher ctx. sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
if(EVP_DecryptInit_ex(decrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) { md_ctx_t ctx { EVP_MD_CTX_create() };
return false;
if (EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, pkey.get()) != 1) {
return {};
}
if (EVP_DigestSignUpdate(ctx.get(), data.data(), data.size()) != 1) {
return {};
}
std::size_t slen = digest_size;
std::vector<uint8_t> digest;
digest.resize(slen);
if (EVP_DigestSignFinal(ctx.get(), digest.data(), &slen) != 1) {
return {};
}
return digest;
} }
auto cipher = tagged_cipher.substr(tag_size); creds_t
auto tag = tagged_cipher.substr(0, tag_size); gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
x509_t x509 { X509_new() };
pkey_ctx_t ctx { EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr) };
pkey_t pkey;
plaintext.resize((cipher.size() + 15) / 16 * 16); EVP_PKEY_keygen_init(ctx.get());
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), key_bits);
EVP_PKEY_keygen(ctx.get(), &pkey);
int size; X509_set_version(x509.get(), 2);
if(EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
return -1;
}
if(EVP_CIPHER_CTX_ctrl(decrypt_ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.data())) != 1) { // Generate a real serial number to avoid SEC_ERROR_REUSED_ISSUER_AND_SERIAL with Firefox
return -1; bignum_t serial { BN_new() };
} BN_rand(serial.get(), 159, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); // 159 bits to fit in 20 bytes in DER format
BN_set_negative(serial.get(), 0); // Serial numbers must be positive
BN_to_ASN1_INTEGER(serial.get(), X509_get_serialNumber(x509.get()));
int len = size; constexpr auto year = 60 * 60 * 24 * 365;
if(EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data() + size, &len) != 1) {
return -1;
}
plaintext.resize(size + len);
return 0;
}
int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
if(!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, iv, padding)) {
return -1;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if(EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return -1;
}
auto tag = tagged_cipher;
auto cipher = tag + tag_size;
int len;
int size = round_to_pkcs7_padded(plaintext.size());
// Encrypt into the caller's buffer
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
return -1;
}
// GCM encryption won't ever fill ciphertext here but we have to call it anyway
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
return -1;
}
if(EVP_CIPHER_CTX_ctrl(encrypt_ctx.get(), EVP_CTRL_GCM_GET_TAG, tag_size, tag) != 1) {
return -1;
}
return len + size;
}
int ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
int len;
auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(decrypt_ctx.get());
});
// Gen 7 servers use 128-bit AES ECB
if(EVP_DecryptInit_ex(decrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(decrypt_ctx.get(), padding);
plaintext.resize((cipher.size() + 15) / 16 * 16);
auto size = (int)plaintext.size();
// Decrypt into the caller's buffer, leaving room for the auth tag to be prepended
if(EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
return -1;
}
if(EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data(), &len) != 1) {
return -1;
}
plaintext.resize(len + size);
return 0;
}
int ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
auto fg = util::fail_guard([this]() {
EVP_CIPHER_CTX_reset(encrypt_ctx.get());
});
// Gen 7 servers use 128-bit AES ECB
if(EVP_EncryptInit_ex(encrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1;
}
EVP_CIPHER_CTX_set_padding(encrypt_ctx.get(), padding);
int len;
cipher.resize((plaintext.size() + 15) / 16 * 16);
auto size = (int)cipher.size();
// Encrypt into the caller's buffer
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher.data(), &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
return -1;
}
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher.data() + size, &len) != 1) {
return -1;
}
cipher.resize(len + size);
return 0;
}
int cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
if(!encrypt_ctx && init_encrypt_cbc(encrypt_ctx, &key, iv, padding)) {
return -1;
}
// Calling with cipher == nullptr results in a parameter change
// without requiring a reallocation of the internal cipher ctx.
if(EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
return false;
}
int len;
int size = plaintext.size(); // round_to_pkcs7_padded(plaintext.size());
// Encrypt into the caller's buffer
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
return -1;
}
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
return -1;
}
return size + len;
}
ecb_t::ecb_t(const aes_t &key, bool padding)
: cipher_t { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding } {}
cbc_t::cbc_t(const aes_t &key, bool padding)
: cipher_t { nullptr, nullptr, key, padding } {}
gcm_t::gcm_t(const crypto::aes_t &key, bool padding)
: cipher_t { nullptr, nullptr, key, padding } {}
} // namespace cipher
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
aes_t key;
std::string salt_pin;
salt_pin.reserve(salt.size() + pin.size());
salt_pin.insert(std::end(salt_pin), std::begin(salt), std::end(salt));
salt_pin.insert(std::end(salt_pin), std::begin(pin), std::end(pin));
auto hsh = hash(salt_pin);
std::copy(std::begin(hsh), std::begin(hsh) + key.size(), std::begin(key));
return key;
}
sha256_t hash(const std::string_view &plaintext) {
sha256_t hsh;
EVP_Digest(plaintext.data(), plaintext.size(), hsh.data(), nullptr, EVP_sha256(), nullptr);
return hsh;
}
x509_t x509(const std::string_view &x) {
bio_t io { BIO_new(BIO_s_mem()) };
BIO_write(io.get(), x.data(), x.size());
x509_t p;
PEM_read_bio_X509(io.get(), &p, nullptr, nullptr);
return p;
}
pkey_t pkey(const std::string_view &k) {
bio_t io { BIO_new(BIO_s_mem()) };
BIO_write(io.get(), k.data(), k.size());
pkey_t p = nullptr;
PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr);
return p;
}
std::string pem(x509_t &x509) {
bio_t bio { BIO_new(BIO_s_mem()) };
PEM_write_bio_X509(bio.get(), x509.get());
BUF_MEM *mem_ptr;
BIO_get_mem_ptr(bio.get(), &mem_ptr);
return { mem_ptr->data, mem_ptr->length };
}
std::string pem(pkey_t &pkey) {
bio_t bio { BIO_new(BIO_s_mem()) };
PEM_write_bio_PrivateKey(bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
BUF_MEM *mem_ptr;
BIO_get_mem_ptr(bio.get(), &mem_ptr);
return { mem_ptr->data, mem_ptr->length };
}
std::string_view signature(const x509_t &x) {
// X509_ALGOR *_ = nullptr;
const ASN1_BIT_STRING *asn1 = nullptr;
X509_get0_signature(&asn1, nullptr, x.get());
return { (const char *)asn1->data, (std::size_t)asn1->length };
}
std::string rand(std::size_t bytes) {
std::string r;
r.resize(bytes);
RAND_bytes((uint8_t *)r.data(), r.size());
return r;
}
std::vector<uint8_t> sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
md_ctx_t ctx { EVP_MD_CTX_create() };
if(EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, pkey.get()) != 1) {
return {};
}
if(EVP_DigestSignUpdate(ctx.get(), data.data(), data.size()) != 1) {
return {};
}
std::size_t slen = digest_size;
std::vector<uint8_t> digest;
digest.resize(slen);
if(EVP_DigestSignFinal(ctx.get(), digest.data(), &slen) != 1) {
return {};
}
return digest;
}
creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
x509_t x509 { X509_new() };
pkey_ctx_t ctx { EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr) };
pkey_t pkey;
EVP_PKEY_keygen_init(ctx.get());
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), key_bits);
EVP_PKEY_keygen(ctx.get(), &pkey);
X509_set_version(x509.get(), 2);
// Generate a real serial number to avoid SEC_ERROR_REUSED_ISSUER_AND_SERIAL with Firefox
bignum_t serial { BN_new() };
BN_rand(serial.get(), 159, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); // 159 bits to fit in 20 bytes in DER format
BN_set_negative(serial.get(), 0); // Serial numbers must be positive
BN_to_ASN1_INTEGER(serial.get(), X509_get_serialNumber(x509.get()));
constexpr auto year = 60 * 60 * 24 * 365;
#if OPENSSL_VERSION_NUMBER < 0x10100000L #if OPENSSL_VERSION_NUMBER < 0x10100000L
X509_gmtime_adj(X509_get_notBefore(x509.get()), 0); X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
X509_gmtime_adj(X509_get_notAfter(x509.get()), 20 * year); X509_gmtime_adj(X509_get_notAfter(x509.get()), 20 * year);
#else #else
asn1_string_t not_before { ASN1_STRING_dup(X509_get0_notBefore(x509.get())) }; asn1_string_t not_before { ASN1_STRING_dup(X509_get0_notBefore(x509.get())) };
asn1_string_t not_after { ASN1_STRING_dup(X509_get0_notAfter(x509.get())) }; asn1_string_t not_after { ASN1_STRING_dup(X509_get0_notAfter(x509.get())) };
X509_gmtime_adj(not_before.get(), 0); X509_gmtime_adj(not_before.get(), 0);
X509_gmtime_adj(not_after.get(), 20 * year); X509_gmtime_adj(not_after.get(), 20 * year);
X509_set1_notBefore(x509.get(), not_before.get()); X509_set1_notBefore(x509.get(), not_before.get());
X509_set1_notAfter(x509.get(), not_after.get()); X509_set1_notAfter(x509.get(), not_after.get());
#endif #endif
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(), (const std::uint8_t *) cn.data(), cn.size(),
-1, 0); -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());
return { pem(x509), pem(pkey) }; return { pem(x509), pem(pkey) };
}
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data) {
return sign(pkey, data, EVP_sha256());
}
bool verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
auto pkey = X509_get_pubkey(x509.get());
md_ctx_t ctx { EVP_MD_CTX_create() };
if(EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
return false;
} }
if(EVP_DigestVerifyUpdate(ctx.get(), data.data(), data.size()) != 1) { std::vector<uint8_t>
return false; sign256(const pkey_t &pkey, const std::string_view &data) {
return sign(pkey, data, EVP_sha256());
} }
if(EVP_DigestVerifyFinal(ctx.get(), (const uint8_t *)signature.data(), signature.size()) != 1) { bool
return false; verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
auto pkey = X509_get_pubkey(x509.get());
md_ctx_t ctx { EVP_MD_CTX_create() };
if (EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
return false;
}
if (EVP_DigestVerifyUpdate(ctx.get(), data.data(), data.size()) != 1) {
return false;
}
if (EVP_DigestVerifyFinal(ctx.get(), (const uint8_t *) signature.data(), signature.size()) != 1) {
return false;
}
return true;
} }
return true; bool
} verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
return verify(x509, data, signature, EVP_sha256());
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
return verify(x509, data, signature, EVP_sha256());
}
void md_ctx_destroy(EVP_MD_CTX *ctx) {
EVP_MD_CTX_destroy(ctx);
}
std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
auto value = rand(bytes);
for(std::size_t i = 0; i != value.size(); ++i) {
value[i] = alphabet[value[i] % alphabet.length()];
} }
return value;
}
} // namespace crypto void
md_ctx_destroy(EVP_MD_CTX *ctx) {
EVP_MD_CTX_destroy(ctx);
}
std::string
rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
auto value = rand(bytes);
for (std::size_t i = 0; i != value.size(); ++i) {
value[i] = alphabet[value[i] % alphabet.length()];
}
return value;
}
} // namespace crypto

View file

@ -12,124 +12,148 @@
#include "utility.h" #include "utility.h"
namespace crypto { namespace crypto {
struct creds_t { struct creds_t {
std::string x509; std::string x509;
std::string pkey; std::string pkey;
}; };
constexpr std::size_t digest_size = 256; constexpr std::size_t digest_size = 256;
void md_ctx_destroy(EVP_MD_CTX *); void
md_ctx_destroy(EVP_MD_CTX *);
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>; using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
using aes_t = std::array<std::uint8_t, 16>; using aes_t = std::array<std::uint8_t, 16>;
using x509_t = util::safe_ptr<X509, X509_free>; using x509_t = util::safe_ptr<X509, X509_free>;
using x509_store_t = util::safe_ptr<X509_STORE, X509_STORE_free>; using x509_store_t = util::safe_ptr<X509_STORE, X509_STORE_free>;
using x509_store_ctx_t = util::safe_ptr<X509_STORE_CTX, X509_STORE_CTX_free>; using x509_store_ctx_t = util::safe_ptr<X509_STORE_CTX, X509_STORE_CTX_free>;
using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>; using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>; using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>;
using bio_t = util::safe_ptr<BIO, BIO_free_all>; using bio_t = util::safe_ptr<BIO, BIO_free_all>;
using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>; using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>;
using pkey_ctx_t = util::safe_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>; using pkey_ctx_t = util::safe_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
using bignum_t = util::safe_ptr<BIGNUM, BN_free>; using bignum_t = util::safe_ptr<BIGNUM, BN_free>;
sha256_t hash(const std::string_view &plaintext); sha256_t
hash(const std::string_view &plaintext);
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin); aes_t
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
x509_t x509(const std::string_view &x); x509_t
pkey_t pkey(const std::string_view &k); x509(const std::string_view &x);
std::string pem(x509_t &x509); pkey_t
std::string pem(pkey_t &pkey); pkey(const std::string_view &k);
std::string
pem(x509_t &x509);
std::string
pem(pkey_t &pkey);
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data); std::vector<uint8_t>
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature); sign256(const pkey_t &pkey, const std::string_view &data);
bool
verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits); creds_t
gen_creds(const std::string_view &cn, std::uint32_t key_bits);
std::string_view signature(const x509_t &x); std::string_view
signature(const x509_t &x);
std::string rand(std::size_t bytes); std::string
std::string rand_alphabet(std::size_t bytes, rand(std::size_t bytes);
const std::string_view &alphabet = std::string_view { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-" }); 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 add(x509_t &&cert); void
add(x509_t &&cert);
const char *verify(x509_t::element_type *cert); const char *
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;
x509_store_ctx_t _cert_ctx; x509_store_ctx_t _cert_ctx;
}; };
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
return ((size + 15) / 16) * 16; round_to_pkcs7_padded(std::size_t size) {
} return ((size + 15) / 16) * 16;
}
class cipher_t { class cipher_t {
public: public:
cipher_ctx_t decrypt_ctx; cipher_ctx_t decrypt_ctx;
cipher_ctx_t encrypt_ctx; cipher_ctx_t encrypt_ctx;
aes_t key; aes_t key;
bool padding; bool padding;
}; };
class ecb_t : public cipher_t { class ecb_t: public cipher_t {
public: public:
ecb_t() = default; ecb_t() = default;
ecb_t(ecb_t &&) noexcept = default; ecb_t(ecb_t &&) noexcept = default;
ecb_t &operator=(ecb_t &&) noexcept = default; ecb_t &
operator=(ecb_t &&) noexcept = default;
ecb_t(const aes_t &key, bool padding = true); ecb_t(const aes_t &key, bool padding = true);
int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher); int
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext); encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
}; int
decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
};
class gcm_t : public cipher_t { 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 &operator=(gcm_t &&) noexcept = default; gcm_t &
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);
/** /**
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size()) + crypto::cipher::tag_size * length of cipher must be at least: round_to_pkcs7_padded(plaintext.size()) + crypto::cipher::tag_size
* *
* return -1 on error * return -1 on error
* return bytes written on success * return bytes written on success
*/ */
int encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv); int
encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv); int
}; decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
};
class cbc_t : public cipher_t { 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 &operator=(cbc_t &&) noexcept = default; cbc_t &
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);
/** /**
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size()) * length of cipher must be at least: round_to_pkcs7_padded(plaintext.size())
* *
* return -1 on error * return -1 on error
* return bytes written on success * return bytes written on success
*/ */
int encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv); int
}; encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
} // namespace cipher };
} // namespace crypto } // namespace cipher
} // namespace crypto
#endif //SUNSHINE_CRYPTO_H #endif //SUNSHINE_CRYPTO_H

View file

@ -27,209 +27,219 @@
#include "uuid.h" #include "uuid.h"
namespace http { namespace http {
using namespace std::literals; using namespace std::literals;
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace pt = boost::property_tree; namespace pt = boost::property_tree;
int reload_user_creds(const std::string &file); int
bool user_creds_exist(const std::string &file); reload_user_creds(const std::string &file);
bool
user_creds_exist(const std::string &file);
std::string unique_id; std::string unique_id;
net::net_e origin_pin_allowed; net::net_e origin_pin_allowed;
net::net_e origin_web_ui_allowed; net::net_e origin_web_ui_allowed;
int init() { int
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; init() {
origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); 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_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed);
origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed);
if(clean_slate) { if (clean_slate) {
unique_id = uuid_util::uuid_t::generate().string(); unique_id = uuid_util::uuid_t::generate().string();
auto dir = std::filesystem::temp_directory_path() / "Sunshine"sv; auto dir = std::filesystem::temp_directory_path() / "Sunshine"sv;
config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string(); config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string();
config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string(); config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string();
}
if(!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) {
if(create_creds(config::nvhttp.pkey, config::nvhttp.cert)) {
return -1;
} }
}
if(user_creds_exist(config::sunshine.credentials_file)) {
if(reload_user_creds(config::sunshine.credentials_file)) return -1;
}
else {
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
}
return 0;
}
int save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) { if (!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) {
pt::ptree outputTree; if (create_creds(config::nvhttp.pkey, config::nvhttp.cert)) {
return -1;
}
}
if (user_creds_exist(config::sunshine.credentials_file)) {
if (reload_user_creds(config::sunshine.credentials_file)) return -1;
}
else {
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
}
return 0;
}
if(fs::exists(file)) { int
save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
pt::ptree outputTree;
if (fs::exists(file)) {
try {
pt::read_json(file, outputTree);
}
catch (std::exception &e) {
BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what();
return -1;
}
}
auto salt = crypto::rand_alphabet(16);
outputTree.put("username", username);
outputTree.put("salt", salt);
outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string());
try { try {
pt::read_json(file, outputTree); pt::write_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) << "generating user credentials: "sv << e.what();
return -1; return -1;
} }
BOOST_LOG(info) << "New credentials have been created"sv;
return 0;
} }
auto salt = crypto::rand_alphabet(16); bool
outputTree.put("username", username); user_creds_exist(const std::string &file) {
outputTree.put("salt", salt); if (!fs::exists(file)) {
outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string()); return false;
try { }
pt::write_json(file, outputTree);
}
catch(std::exception &e) {
BOOST_LOG(error) << "generating user credentials: "sv << e.what();
return -1;
}
BOOST_LOG(info) << "New credentials have been created"sv; pt::ptree inputTree;
return 0; try {
} pt::read_json(file, inputTree);
return inputTree.find("username") != inputTree.not_found() &&
inputTree.find("password") != inputTree.not_found() &&
inputTree.find("salt") != inputTree.not_found();
}
catch (std::exception &e) {
BOOST_LOG(error) << "validating user credentials: "sv << e.what();
}
bool user_creds_exist(const std::string &file) {
if(!fs::exists(file)) {
return false; return false;
} }
pt::ptree inputTree; int
try { reload_user_creds(const std::string &file) {
pt::read_json(file, inputTree); pt::ptree inputTree;
return inputTree.find("username") != inputTree.not_found() && try {
inputTree.find("password") != inputTree.not_found() && pt::read_json(file, inputTree);
inputTree.find("salt") != inputTree.not_found(); config::sunshine.username = inputTree.get<std::string>("username");
} config::sunshine.password = inputTree.get<std::string>("password");
catch(std::exception &e) { config::sunshine.salt = inputTree.get<std::string>("salt");
BOOST_LOG(error) << "validating user credentials: "sv << e.what(); }
catch (std::exception &e) {
BOOST_LOG(error) << "loading user credentials: "sv << e.what();
return -1;
}
return 0;
} }
return false; int
} create_creds(const std::string &pkey, const std::string &cert) {
fs::path pkey_path = pkey;
fs::path cert_path = cert;
int reload_user_creds(const std::string &file) { auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048);
pt::ptree inputTree;
try {
pt::read_json(file, inputTree);
config::sunshine.username = inputTree.get<std::string>("username");
config::sunshine.password = inputTree.get<std::string>("password");
config::sunshine.salt = inputTree.get<std::string>("salt");
}
catch(std::exception &e) {
BOOST_LOG(error) << "loading user credentials: "sv << e.what();
return -1;
}
return 0;
}
int create_creds(const std::string &pkey, const std::string &cert) { auto pkey_dir = pkey_path;
fs::path pkey_path = pkey; auto cert_dir = cert_path;
fs::path cert_path = cert; pkey_dir.remove_filename();
cert_dir.remove_filename();
auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048); std::error_code err_code {};
fs::create_directories(pkey_dir, err_code);
if (err_code) {
BOOST_LOG(error) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message();
return -1;
}
auto pkey_dir = pkey_path; fs::create_directories(cert_dir, err_code);
auto cert_dir = cert_path; if (err_code) {
pkey_dir.remove_filename(); BOOST_LOG(error) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message();
cert_dir.remove_filename(); return -1;
}
std::error_code err_code {}; if (write_file(pkey.c_str(), creds.pkey)) {
fs::create_directories(pkey_dir, err_code); BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.pkey << ']';
if(err_code) { return -1;
BOOST_LOG(error) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message(); }
return -1;
if (write_file(cert.c_str(), creds.x509)) {
BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.cert << ']';
return -1;
}
fs::permissions(pkey_path,
fs::perms::owner_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
if (err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message();
return -1;
}
fs::permissions(cert_path,
fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
if (err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message();
return -1;
}
return 0;
} }
fs::create_directories(cert_dir, err_code); bool
if(err_code) { download_file(const std::string &url, const std::string &file) {
BOOST_LOG(error) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message(); CURL *curl = curl_easy_init();
return -1; if (!curl) {
} BOOST_LOG(error) << "Couldn't create CURL instance";
return false;
if(write_file(pkey.c_str(), creds.pkey)) { }
BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.pkey << ']'; FILE *fp = fopen(file.c_str(), "wb");
return -1; if (!fp) {
} BOOST_LOG(error) << "Couldn't open ["sv << file << ']';
curl_easy_cleanup(curl);
if(write_file(cert.c_str(), creds.x509)) { return false;
BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.cert << ']'; }
return -1; curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
} curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
fs::permissions(pkey_path,
fs::perms::owner_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
if(err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message();
return -1;
}
fs::permissions(cert_path,
fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write,
fs::perm_options::replace, err_code);
if(err_code) {
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message();
return -1;
}
return 0;
}
bool download_file(const std::string &url, const std::string &file) {
CURL *curl = curl_easy_init();
if(!curl) {
BOOST_LOG(error) << "Couldn't create CURL instance";
return false;
}
FILE *fp = fopen(file.c_str(), "wb");
if(!fp) {
BOOST_LOG(error) << "Couldn't open ["sv << file << ']';
curl_easy_cleanup(curl);
return false;
}
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
#ifdef _WIN32 #ifdef _WIN32
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
#endif #endif
CURLcode result = curl_easy_perform(curl); CURLcode result = curl_easy_perform(curl);
if(result != CURLE_OK) { if (result != CURLE_OK) {
BOOST_LOG(error) << "Couldn't download ["sv << url << ", code:" << result << ']'; BOOST_LOG(error) << "Couldn't download ["sv << url << ", code:" << result << ']';
}
curl_easy_cleanup(curl);
fclose(fp);
return result == CURLE_OK;
} }
curl_easy_cleanup(curl);
fclose(fp);
return result == CURLE_OK;
}
std::string url_escape(const std::string &url) { std::string
CURL *curl = curl_easy_init(); url_escape(const std::string &url) {
char *string = curl_easy_escape(curl, url.c_str(), url.length()); CURL *curl = curl_easy_init();
std::string result(string); char *string = curl_easy_escape(curl, url.c_str(), url.length());
curl_free(string); std::string result(string);
curl_easy_cleanup(curl); curl_free(string);
return result; curl_easy_cleanup(curl);
} return result;
}
std::string url_get_host(const std::string &url) { std::string
CURLU *curlu = curl_url(); url_get_host(const std::string &url) {
curl_url_set(curlu, CURLUPART_URL, url.c_str(), url.length()); CURLU *curlu = curl_url();
char *host; curl_url_set(curlu, CURLUPART_URL, url.c_str(), url.length());
if(curl_url_get(curlu, CURLUPART_HOST, &host, 0) != CURLUE_OK) { char *host;
if (curl_url_get(curlu, CURLUPART_HOST, &host, 0) != CURLUE_OK) {
curl_url_cleanup(curlu);
return "";
}
std::string result(host);
curl_free(host);
curl_url_cleanup(curlu); curl_url_cleanup(curlu);
return ""; return result;
} }
std::string result(host);
curl_free(host);
curl_url_cleanup(curlu);
return result;
}
} // namespace http } // namespace http

View file

@ -3,21 +3,28 @@
namespace http { namespace http {
int init(); int
int create_creds(const std::string &pkey, const std::string &cert); init();
int save_user_creds( int
const std::string &file, create_creds(const std::string &pkey, const std::string &cert);
const std::string &username, int
const std::string &password, save_user_creds(
bool run_our_mouth = false); const std::string &file,
const std::string &username,
const std::string &password,
bool run_our_mouth = false);
int reload_user_creds(const std::string &file); int
bool download_file(const std::string &url, const std::string &file); reload_user_creds(const std::string &file);
std::string url_escape(const std::string &url); bool
std::string url_get_host(const std::string &url); download_file(const std::string &url, const std::string &file);
std::string
url_escape(const std::string &url);
std::string
url_get_host(const std::string &url);
extern std::string unique_id; extern std::string unique_id;
extern net::net_e origin_pin_allowed; extern net::net_e origin_pin_allowed;
extern net::net_e origin_web_ui_allowed; extern net::net_e origin_web_ui_allowed;
} // namespace http } // namespace http

File diff suppressed because it is too large Load diff

View file

@ -9,25 +9,29 @@
#include "thread_safe.h" #include "thread_safe.h"
namespace input { namespace input {
struct input_t; struct input_t;
void print(void *input); void
void reset(std::shared_ptr<input_t> &input); print(void *input);
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data); void
reset(std::shared_ptr<input_t> &input);
void
passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
[[nodiscard]] std::unique_ptr<platf::deinit_t>
init();
[[nodiscard]] std::unique_ptr<platf::deinit_t> init(); std::shared_ptr<input_t>
alloc(safe::mail_t mail);
std::shared_ptr<input_t> alloc(safe::mail_t mail); struct touch_port_t: public platf::touch_port_t {
int env_width, env_height;
struct touch_port_t : public platf::touch_port_t { // Offset x and y coordinates of the client
int env_width, env_height; float client_offsetX, client_offsetY;
// Offset x and y coordinates of the client float scalar_inv;
float client_offsetX, client_offsetY; };
} // namespace input
float scalar_inv; #endif // SUNSHINE_INPUT_H
};
} // namespace input
#endif // SUNSHINE_INPUT_H

View file

@ -42,12 +42,12 @@ using namespace std::literals;
namespace bl = boost::log; namespace bl = boost::log;
thread_pool_util::ThreadPool task_pool; thread_pool_util::ThreadPool task_pool;
bl::sources::severity_logger<int> verbose(0); // Dominating output bl::sources::severity_logger<int> verbose(0); // Dominating output
bl::sources::severity_logger<int> debug(1); // Follow what is happening bl::sources::severity_logger<int> debug(1); // Follow what is happening
bl::sources::severity_logger<int> info(2); // Should be informed about bl::sources::severity_logger<int> info(2); // Should be informed about
bl::sources::severity_logger<int> warning(3); // Strange events bl::sources::severity_logger<int> warning(3); // Strange events
bl::sources::severity_logger<int> error(4); // Recoverable errors bl::sources::severity_logger<int> error(4); // Recoverable errors
bl::sources::severity_logger<int> fatal(5); // Unrecoverable errors bl::sources::severity_logger<int> fatal(5); // Unrecoverable errors
bool display_cursor = true; bool display_cursor = true;
@ -55,7 +55,8 @@ using text_sink = bl::sinks::asynchronous_sink<bl::sinks::text_ostream_backend>;
boost::shared_ptr<text_sink> sink; boost::shared_ptr<text_sink> sink;
struct NoDelete { struct NoDelete {
void operator()(void *) {} void
operator()(void *) {}
}; };
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int) BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int)
@ -69,7 +70,8 @@ BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int)
* print_help("sunshine"); * print_help("sunshine");
* ``` * ```
*/ */
void print_help(const char *name) { void
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
@ -90,19 +92,20 @@ void print_help(const char *name) {
} }
namespace help { namespace help {
int entry(const char *name, int argc, char *argv[]) { int
print_help(name); entry(const char *name, int argc, char *argv[]) {
return 0; print_help(name);
} return 0;
} // namespace help }
} // namespace help
namespace version { namespace version {
int entry(const char *name, int argc, char *argv[]) { int
std::cout << PROJECT_NAME << " version: v" << PROJECT_VER << std::endl; entry(const char *name, int argc, char *argv[]) {
return 0; std::cout << PROJECT_NAME << " version: v" << PROJECT_VER << std::endl;
} return 0;
} // namespace version }
} // namespace version
/** /**
* @brief Flush the log. * @brief Flush the log.
@ -112,34 +115,38 @@ int entry(const char *name, int argc, char *argv[]) {
* log_flush(); * log_flush();
* ``` * ```
*/ */
void log_flush() { void
log_flush() {
sink->flush(); sink->flush();
} }
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 on_signal(int sig, FN &&fn) { void
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);
} }
namespace gen_creds { namespace gen_creds {
int entry(const char *name, int argc, char *argv[]) { int
if(argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) { entry(const char *name, int argc, char *argv[]) {
print_help(name); if (argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) {
print_help(name);
return 0;
}
http::save_user_creds(config::sunshine.credentials_file, argv[0], argv[1]);
return 0; return 0;
} }
} // namespace gen_creds
http::save_user_creds(config::sunshine.credentials_file, argv[0], argv[1]);
return 0;
}
} // namespace gen_creds
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, gen_creds::entry }, { "creds"sv, gen_creds::entry },
@ -148,20 +155,21 @@ std::map<std::string_view, std::function<int(const char *name, int argc, char **
}; };
#ifdef _WIN32 #ifdef _WIN32
LRESULT CALLBACK SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT CALLBACK
switch(uMsg) { SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
case WM_ENDSESSION: { switch (uMsg) {
// Raise a SIGINT to trigger our cleanup logic and terminate ourselves case WM_ENDSESSION: {
std::cout << "Received WM_ENDSESSION"sv << std::endl; // Raise a SIGINT to trigger our cleanup logic and terminate ourselves
std::raise(SIGINT); std::cout << "Received WM_ENDSESSION"sv << std::endl;
std::raise(SIGINT);
// The signal handling is asynchronous, so we will wait here to be terminated. // The signal handling is asynchronous, so we will wait here to be terminated.
// If for some reason we don't terminate in a few seconds, Windows will kill us. // If for some reason we don't terminate in a few seconds, Windows will kill us.
SuspendThread(GetCurrentThread()); SuspendThread(GetCurrentThread());
return 0; return 0;
} }
default: default:
return DefWindowProc(hwnd, uMsg, wParam, lParam); return DefWindowProc(hwnd, uMsg, wParam, lParam);
} }
} }
#endif #endif
@ -176,7 +184,8 @@ LRESULT CALLBACK SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, L
* main(1, const char* args[] = {"sunshine", nullptr}); * main(1, const char* args[] = {"sunshine", nullptr});
* ``` * ```
*/ */
int main(int argc, char *argv[]) { int
main(int argc, char *argv[]) {
task_pool_util::TaskPool::task_id_t force_shutdown = nullptr; task_pool_util::TaskPool::task_id_t force_shutdown = nullptr;
#ifdef _WIN32 #ifdef _WIN32
@ -187,8 +196,8 @@ int main(int argc, char *argv[]) {
std::thread window_thread([]() { std::thread window_thread([]() {
WNDCLASSA wnd_class {}; WNDCLASSA wnd_class {};
wnd_class.lpszClassName = "SunshineSessionMonitorClass"; wnd_class.lpszClassName = "SunshineSessionMonitorClass";
wnd_class.lpfnWndProc = SessionMonitorWindowProc; wnd_class.lpfnWndProc = SessionMonitorWindowProc;
if(!RegisterClassA(&wnd_class)) { if (!RegisterClassA(&wnd_class)) {
std::cout << "Failed to register session monitor window class"sv << std::endl; std::cout << "Failed to register session monitor window class"sv << std::endl;
return; return;
} }
@ -206,7 +215,7 @@ int main(int argc, char *argv[]) {
nullptr, nullptr,
nullptr, nullptr,
nullptr); nullptr);
if(!wnd) { if (!wnd) {
std::cout << "Failed to create session monitor window"sv << std::endl; std::cout << "Failed to create session monitor window"sv << std::endl;
return; return;
} }
@ -215,7 +224,7 @@ int main(int argc, char *argv[]) {
// Run the message loop for our window // Run the message loop for our window
MSG msg {}; MSG msg {};
while(GetMessage(&msg, nullptr, 0, 0) > 0) { while (GetMessage(&msg, nullptr, 0, 0) > 0) {
TranslateMessage(&msg); TranslateMessage(&msg);
DispatchMessage(&msg); DispatchMessage(&msg);
} }
@ -225,11 +234,11 @@ int main(int argc, char *argv[]) {
mail::man = std::make_shared<safe::mail_raw_t>(); mail::man = std::make_shared<safe::mail_raw_t>();
if(config::parse(argc, argv)) { if (config::parse(argc, argv)) {
return 0; return 0;
} }
if(config::sunshine.min_log_level >= 1) { if (config::sunshine.min_log_level >= 1) {
av_log_set_level(AV_LOG_QUIET); av_log_set_level(AV_LOG_QUIET);
} }
else { else {
@ -244,30 +253,30 @@ int main(int argc, char *argv[]) {
sink->set_filter(severity >= config::sunshine.min_log_level); sink->set_filter(severity >= config::sunshine.min_log_level);
sink->set_formatter([message = "Message"s, severity = "Severity"s](const bl::record_view &view, bl::formatting_ostream &os) { sink->set_formatter([message = "Message"s, severity = "Severity"s](const bl::record_view &view, bl::formatting_ostream &os) {
constexpr int DATE_BUFFER_SIZE = 21 + 2 + 1; // Full string plus ": \0" constexpr int DATE_BUFFER_SIZE = 21 + 2 + 1; // Full string plus ": \0"
auto log_level = view.attribute_values()[severity].extract<int>().get(); auto log_level = view.attribute_values()[severity].extract<int>().get();
std::string_view log_type; std::string_view log_type;
switch(log_level) { switch (log_level) {
case 0: case 0:
log_type = "Verbose: "sv; log_type = "Verbose: "sv;
break; break;
case 1: case 1:
log_type = "Debug: "sv; log_type = "Debug: "sv;
break; break;
case 2: case 2:
log_type = "Info: "sv; log_type = "Info: "sv;
break; break;
case 3: case 3:
log_type = "Warning: "sv; log_type = "Warning: "sv;
break; break;
case 4: case 4:
log_type = "Error: "sv; log_type = "Error: "sv;
break; break;
case 5: case 5:
log_type = "Fatal: "sv; log_type = "Fatal: "sv;
break; break;
}; };
char _date[DATE_BUFFER_SIZE]; char _date[DATE_BUFFER_SIZE];
@ -284,13 +293,13 @@ int main(int argc, char *argv[]) {
bl::core::get()->add_sink(sink); bl::core::get()->add_sink(sink);
auto fg = util::fail_guard(log_flush); auto fg = util::fail_guard(log_flush);
if(!config::sunshine.cmd.name.empty()) { if (!config::sunshine.cmd.name.empty()) {
auto fn = cmd_to_func.find(config::sunshine.cmd.name); auto fn = cmd_to_func.find(config::sunshine.cmd.name);
if(fn == std::end(cmd_to_func)) { if (fn == std::end(cmd_to_func)) {
BOOST_LOG(fatal) << "Unknown command: "sv << config::sunshine.cmd.name; BOOST_LOG(fatal) << "Unknown command: "sv << config::sunshine.cmd.name;
BOOST_LOG(info) << "Possible commands:"sv; BOOST_LOG(info) << "Possible commands:"sv;
for(auto &[key, _] : cmd_to_func) { for (auto &[key, _] : cmd_to_func) {
BOOST_LOG(info) << '\t' << key; BOOST_LOG(info) << '\t' << key;
} }
@ -338,16 +347,16 @@ int main(int argc, char *argv[]) {
proc::refresh(config::stream.file_apps); proc::refresh(config::stream.file_apps);
auto deinit_guard = platf::init(); auto deinit_guard = platf::init();
if(!deinit_guard) { if (!deinit_guard) {
return 4; return 4;
} }
reed_solomon_init(); reed_solomon_init();
auto input_deinit_guard = input::init(); auto input_deinit_guard = input::init();
if(video::init()) { if (video::init()) {
return 2; return 2;
} }
if(http::init()) { if (http::init()) {
return 3; return 3;
} }
@ -362,7 +371,7 @@ int main(int argc, char *argv[]) {
}); });
// FIXME: Temporary workaround: Simple-Web_server needs to be updated or replaced // FIXME: Temporary workaround: Simple-Web_server needs to be updated or replaced
if(shutdown_event->peek()) { if (shutdown_event->peek()) {
return 0; return 0;
} }
@ -395,8 +404,9 @@ int main(int argc, char *argv[]) {
* std::string contents = read_file("path/to/file"); * std::string contents = read_file("path/to/file");
* ``` * ```
*/ */
std::string read_file(const char *path) { std::string
if(!std::filesystem::exists(path)) { read_file(const char *path) {
if (!std::filesystem::exists(path)) {
BOOST_LOG(debug) << "Missing file: " << path; BOOST_LOG(debug) << "Missing file: " << path;
return {}; return {};
} }
@ -406,7 +416,7 @@ std::string read_file(const char *path) {
std::string input; std::string input;
std::string base64_cert; std::string base64_cert;
while(!in.eof()) { while (!in.eof()) {
std::getline(in, input); std::getline(in, input);
base64_cert += input + '\n'; base64_cert += input + '\n';
} }
@ -425,10 +435,11 @@ std::string read_file(const char *path) {
* int write_status = write_file("path/to/file", "file contents"); * int write_status = write_file("path/to/file", "file contents");
* ``` * ```
*/ */
int write_file(const char *path, const std::string_view &contents) { int
write_file(const char *path, const std::string_view &contents) {
std::ofstream out(path); std::ofstream out(path);
if(!out.is_open()) { if (!out.is_open()) {
return -1; return -1;
} }
@ -447,8 +458,9 @@ int write_file(const char *path, const std::string_view &contents) {
* std::uint16_t mapped_port = map_port(1); * std::uint16_t mapped_port = map_port(1);
* ``` * ```
*/ */
std::uint16_t map_port(int port) { std::uint16_t
map_port(int port) {
// TODO: Ensure port is in the range of 21-65535 // TODO: Ensure port is in the range of 21-65535
// TODO: Ensure port is not already in use by another application // TODO: Ensure port is not already in use by another application
return (std::uint16_t)((int)config::sunshine.port + port); return (std::uint16_t)((int) config::sunshine.port + port);
} }

View file

@ -28,42 +28,55 @@ extern boost::log::sources::severity_logger<int> error;
extern boost::log::sources::severity_logger<int> fatal; extern boost::log::sources::severity_logger<int> fatal;
// functions // functions
int main(int argc, char *argv[]); int
void log_flush(); main(int argc, char *argv[]);
void open_url(const std::string &url); void
void tray_open_ui_cb(struct tray_menu *item); log_flush();
void tray_donate_github_cb(struct tray_menu *item); void
void tray_donate_mee6_cb(struct tray_menu *item); open_url(const std::string &url);
void tray_donate_patreon_cb(struct tray_menu *item); void
void tray_donate_paypal_cb(struct tray_menu *item); tray_open_ui_cb(struct tray_menu *item);
void tray_quit_cb(struct tray_menu *item); void
void print_help(const char *name); tray_donate_github_cb(struct tray_menu *item);
std::string read_file(const char *path); void
int write_file(const char *path, const std::string_view &contents); tray_donate_mee6_cb(struct tray_menu *item);
std::uint16_t map_port(int port); void
tray_donate_patreon_cb(struct tray_menu *item);
void
tray_donate_paypal_cb(struct tray_menu *item);
void
tray_quit_cb(struct tray_menu *item);
void
print_help(const char *name);
std::string
read_file(const char *path);
int
write_file(const char *path, const std::string_view &contents);
std::uint16_t
map_port(int port);
// namespaces // namespaces
namespace mail { namespace mail {
#define MAIL(x) \ #define MAIL(x) \
constexpr auto x = std::string_view { \ constexpr auto x = std::string_view { \
#x \ #x \
} }
extern safe::mail_t man; extern safe::mail_t man;
// Global mail // Global mail
MAIL(shutdown); MAIL(shutdown);
MAIL(broadcast_shutdown); MAIL(broadcast_shutdown);
MAIL(video_packets); MAIL(video_packets);
MAIL(audio_packets); MAIL(audio_packets);
MAIL(switch_display); MAIL(switch_display);
// Local mail // Local mail
MAIL(touch_port); MAIL(touch_port);
MAIL(idr); MAIL(idr);
MAIL(rumble); MAIL(rumble);
MAIL(hdr); MAIL(hdr);
#undef MAIL #undef MAIL
} // namespace mail } // namespace mail
#endif // SUNSHINE_MAIN_H #endif // SUNSHINE_MAIN_H

View file

@ -3,49 +3,54 @@
#include <utility> #include <utility>
namespace move_by_copy_util { namespace move_by_copy_util {
/* /*
* When a copy is made, it moves the object * When a copy is made, it moves the object
* This allows you to move an object when a move can't be done. * This allows you to move an object when a move can't be done.
*/ */
template<class T> template <class T>
class MoveByCopy { class MoveByCopy {
public: public:
typedef T move_type; typedef T move_type;
private: private:
move_type _to_move; move_type _to_move;
public: public:
explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) {} explicit MoveByCopy(move_type &&to_move):
_to_move(std::move(to_move)) {}
MoveByCopy(MoveByCopy &&other) = default; MoveByCopy(MoveByCopy &&other) = default;
MoveByCopy(const MoveByCopy &other) { MoveByCopy(const MoveByCopy &other) {
*this = other; *this = other;
}
MoveByCopy &
operator=(MoveByCopy &&other) = default;
MoveByCopy &
operator=(const MoveByCopy &other) {
this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move);
return *this;
}
operator move_type() {
return std::move(_to_move);
}
};
template <class T>
MoveByCopy<T>
cmove(T &movable) {
return MoveByCopy<T>(std::move(movable));
} }
MoveByCopy &operator=(MoveByCopy &&other) = default; // Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
template <class T>
MoveByCopy &operator=(const MoveByCopy &other) { MoveByCopy<T>
this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move); const_cmove(const T &movable) {
return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
return *this;
} }
} // namespace move_by_copy_util
operator move_type() {
return std::move(_to_move);
}
};
template<class T>
MoveByCopy<T> cmove(T &movable) {
return MoveByCopy<T>(std::move(movable));
}
// Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
template<class T>
MoveByCopy<T> const_cmove(const T &movable) {
return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
}
} // namespace move_by_copy_util
#endif #endif

View file

@ -6,108 +6,116 @@
using namespace std::literals; using namespace std::literals;
namespace net { namespace net {
// In the format "xxx.xxx.xxx.xxx/x" // In the format "xxx.xxx.xxx.xxx/x"
std::pair<std::uint32_t, std::uint32_t> ip_block(const std::string_view &ip); std::pair<std::uint32_t, std::uint32_t>
ip_block(const std::string_view &ip);
std::vector<std::pair<std::uint32_t, std::uint32_t>> pc_ips { std::vector<std::pair<std::uint32_t, std::uint32_t>> pc_ips {
ip_block("127.0.0.1/32"sv) ip_block("127.0.0.1/32"sv)
}; };
std::vector<std::tuple<std::uint32_t, std::uint32_t>> lan_ips { std::vector<std::tuple<std::uint32_t, std::uint32_t>> lan_ips {
ip_block("192.168.0.0/16"sv), ip_block("192.168.0.0/16"sv),
ip_block("172.16.0.0/12"sv), ip_block("172.16.0.0/12"sv),
ip_block("10.0.0.0/8"sv) ip_block("10.0.0.0/8"sv)
}; };
std::uint32_t ip(const std::string_view &ip_str) { std::uint32_t
auto begin = std::begin(ip_str); ip(const std::string_view &ip_str) {
auto end = std::end(ip_str); auto begin = std::begin(ip_str);
auto temp_end = std::find(begin, end, '.'); auto end = std::end(ip_str);
auto temp_end = std::find(begin, end, '.');
std::uint32_t ip = 0; std::uint32_t ip = 0;
auto shift = 24; auto shift = 24;
while(temp_end != end) { while (temp_end != end) {
ip += (util::from_chars(begin, temp_end) << shift); ip += (util::from_chars(begin, temp_end) << shift);
shift -= 8; shift -= 8;
begin = temp_end + 1; begin = temp_end + 1;
temp_end = std::find(begin, end, '.'); temp_end = std::find(begin, end, '.');
}
ip += util::from_chars(begin, end);
return ip;
}
// In the format "xxx.xxx.xxx.xxx/x"
std::pair<std::uint32_t, std::uint32_t> ip_block(const std::string_view &ip_str) {
auto begin = std::begin(ip_str);
auto end = std::find(begin, std::end(ip_str), '/');
auto addr = ip({ begin, (std::size_t)(end - begin) });
auto bits = 32 - util::from_chars(end + 1, std::end(ip_str));
return { addr, addr + ((1 << bits) - 1) };
}
net_e from_enum_string(const std::string_view &view) {
if(view == "wan") {
return WAN;
}
if(view == "lan") {
return LAN;
}
return PC;
}
net_e from_address(const std::string_view &view) {
auto addr = ip(view);
for(auto [ip_low, ip_high] : pc_ips) {
if(addr >= ip_low && addr <= ip_high) {
return PC;
} }
ip += util::from_chars(begin, end);
return ip;
} }
for(auto [ip_low, ip_high] : lan_ips) { // In the format "xxx.xxx.xxx.xxx/x"
if(addr >= ip_low && addr <= ip_high) { std::pair<std::uint32_t, std::uint32_t>
ip_block(const std::string_view &ip_str) {
auto begin = std::begin(ip_str);
auto end = std::find(begin, std::end(ip_str), '/');
auto addr = ip({ begin, (std::size_t)(end - begin) });
auto bits = 32 - util::from_chars(end + 1, std::end(ip_str));
return { addr, addr + ((1 << bits) - 1) };
}
net_e
from_enum_string(const std::string_view &view) {
if (view == "wan") {
return WAN;
}
if (view == "lan") {
return LAN; return LAN;
} }
return PC;
}
net_e
from_address(const std::string_view &view) {
auto addr = ip(view);
for (auto [ip_low, ip_high] : pc_ips) {
if (addr >= ip_low && addr <= ip_high) {
return PC;
}
}
for (auto [ip_low, ip_high] : lan_ips) {
if (addr >= ip_low && addr <= ip_high) {
return LAN;
}
}
return WAN;
} }
return WAN; std::string_view
} to_enum_string(net_e net) {
switch (net) {
case PC:
return "pc"sv;
case LAN:
return "lan"sv;
case WAN:
return "wan"sv;
}
std::string_view to_enum_string(net_e net) { // avoid warning
switch(net) {
case PC:
return "pc"sv;
case LAN:
return "lan"sv;
case WAN:
return "wan"sv; return "wan"sv;
} }
// avoid warning host_t
return "wan"sv; host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port) {
} enet_address_set_host(&addr, "0.0.0.0");
enet_address_set_port(&addr, port);
host_t host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port) { return host_t { enet_host_create(AF_INET, &addr, peers, 1, 0, 0) };
enet_address_set_host(&addr, "0.0.0.0"); }
enet_address_set_port(&addr, port);
return host_t { enet_host_create(AF_INET, &addr, peers, 1, 0, 0) }; void
} free_host(ENetHost *host) {
std::for_each(host->peers, host->peers + host->peerCount, [](ENetPeer &peer_ref) {
ENetPeer *peer = &peer_ref;
void free_host(ENetHost *host) { if (peer) {
std::for_each(host->peers, host->peers + host->peerCount, [](ENetPeer &peer_ref) { enet_peer_disconnect_now(peer, 0);
ENetPeer *peer = &peer_ref; }
});
if(peer) { enet_host_destroy(host);
enet_peer_disconnect_now(peer, 0); }
} } // namespace net
});
enet_host_destroy(host);
}
} // namespace net

View file

@ -10,24 +10,29 @@
#include "utility.h" #include "utility.h"
namespace net { namespace net {
void free_host(ENetHost *host); void
free_host(ENetHost *host);
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 *;
using packet_t = util::safe_ptr<ENetPacket, enet_packet_destroy>; using packet_t = util::safe_ptr<ENetPacket, enet_packet_destroy>;
enum net_e : int { enum net_e : int {
PC, PC,
LAN, LAN,
WAN WAN
}; };
net_e from_enum_string(const std::string_view &view); net_e
std::string_view to_enum_string(net_e net); from_enum_string(const std::string_view &view);
std::string_view
to_enum_string(net_e net);
net_e from_address(const std::string_view &view); net_e
from_address(const std::string_view &view);
host_t host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port); host_t
} // namespace net host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port);
} // namespace net
#endif // SUNSHINE_NETWORK_H #endif // SUNSHINE_NETWORK_H

File diff suppressed because it is too large Load diff

View file

@ -17,31 +17,34 @@
*/ */
namespace nvhttp { namespace nvhttp {
/** /**
* @brief The protocol version. * @brief The protocol version.
*/ */
constexpr auto VERSION = "7.1.431.-1"; constexpr auto VERSION = "7.1.431.-1";
// The negative 4th version number tells Moonlight that this is Sunshine // The negative 4th version number tells Moonlight that this is Sunshine
/** /**
* @brief The GFE version we are replicating. * @brief The GFE version we are replicating.
*/ */
constexpr auto GFE_VERSION = "3.23.0.74"; constexpr auto GFE_VERSION = "3.23.0.74";
/** /**
* @brief The HTTP port, as a difference from the config port. * @brief The HTTP port, as a difference from the config port.
*/ */
constexpr auto PORT_HTTP = 0; constexpr auto PORT_HTTP = 0;
/** /**
* @brief The HTTPS port, as a difference from the config port. * @brief The HTTPS port, as a difference from the config port.
*/ */
constexpr auto PORT_HTTPS = -5; constexpr auto PORT_HTTPS = -5;
// functions // functions
void start(); void
bool pin(std::string pin); start();
void erase_all_clients(); bool
} // namespace nvhttp pin(std::string pin);
void
erase_all_clients();
} // namespace nvhttp
#endif // SUNSHINE_NVHTTP_H #endif // SUNSHINE_NVHTTP_H

View file

@ -27,222 +27,230 @@ struct AVHWFramesContext;
// Forward declarations of boost classes to avoid having to include boost headers // Forward declarations of boost classes to avoid having to include boost headers
// here, which results in issues with Windows.h and WinSock2.h include order. // here, which results in issues with Windows.h and WinSock2.h include order.
namespace boost { namespace boost {
namespace asio { namespace asio {
namespace ip { namespace ip {
class address; class address;
} // namespace ip } // namespace ip
} // namespace asio } // namespace asio
namespace filesystem { namespace filesystem {
class path; class path;
} }
namespace process { namespace process {
class child; class child;
class group; class group;
template<typename Char> template <typename Char>
class basic_environment; class basic_environment;
typedef basic_environment<char> environment; typedef basic_environment<char> environment;
} // namespace process } // namespace process
} // namespace boost } // namespace boost
namespace video { namespace video {
struct config_t; struct config_t;
} // namespace video } // namespace video
namespace platf { namespace platf {
constexpr auto MAX_GAMEPADS = 32; constexpr auto MAX_GAMEPADS = 32;
constexpr std::uint16_t DPAD_UP = 0x0001; constexpr std::uint16_t DPAD_UP = 0x0001;
constexpr std::uint16_t DPAD_DOWN = 0x0002; constexpr std::uint16_t DPAD_DOWN = 0x0002;
constexpr std::uint16_t DPAD_LEFT = 0x0004; constexpr std::uint16_t DPAD_LEFT = 0x0004;
constexpr std::uint16_t DPAD_RIGHT = 0x0008; constexpr std::uint16_t DPAD_RIGHT = 0x0008;
constexpr std::uint16_t START = 0x0010; constexpr std::uint16_t START = 0x0010;
constexpr std::uint16_t BACK = 0x0020; constexpr std::uint16_t BACK = 0x0020;
constexpr std::uint16_t LEFT_STICK = 0x0040; constexpr std::uint16_t LEFT_STICK = 0x0040;
constexpr std::uint16_t RIGHT_STICK = 0x0080; constexpr std::uint16_t RIGHT_STICK = 0x0080;
constexpr std::uint16_t LEFT_BUTTON = 0x0100; constexpr std::uint16_t LEFT_BUTTON = 0x0100;
constexpr std::uint16_t RIGHT_BUTTON = 0x0200; constexpr std::uint16_t RIGHT_BUTTON = 0x0200;
constexpr std::uint16_t HOME = 0x0400; constexpr std::uint16_t HOME = 0x0400;
constexpr std::uint16_t A = 0x1000; constexpr std::uint16_t A = 0x1000;
constexpr std::uint16_t B = 0x2000; constexpr std::uint16_t B = 0x2000;
constexpr std::uint16_t X = 0x4000; constexpr std::uint16_t X = 0x4000;
constexpr std::uint16_t Y = 0x8000; constexpr std::uint16_t Y = 0x8000;
struct rumble_t { struct rumble_t {
KITTY_DEFAULT_CONSTR(rumble_t) KITTY_DEFAULT_CONSTR(rumble_t)
rumble_t(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) rumble_t(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq):
: id { id }, lowfreq { lowfreq }, highfreq { highfreq } {} id { id }, lowfreq { lowfreq }, highfreq { highfreq } {}
std::uint16_t id; std::uint16_t id;
std::uint16_t lowfreq; std::uint16_t lowfreq;
std::uint16_t highfreq; std::uint16_t highfreq;
}; };
using rumble_queue_t = safe::mail_raw_t::queue_t<rumble_t>; using rumble_queue_t = safe::mail_raw_t::queue_t<rumble_t>;
namespace speaker { namespace speaker {
enum speaker_e { enum speaker_e {
FRONT_LEFT, FRONT_LEFT,
FRONT_RIGHT, FRONT_RIGHT,
FRONT_CENTER, FRONT_CENTER,
LOW_FREQUENCY, LOW_FREQUENCY,
BACK_LEFT, BACK_LEFT,
BACK_RIGHT, BACK_RIGHT,
SIDE_LEFT, SIDE_LEFT,
SIDE_RIGHT, SIDE_RIGHT,
MAX_SPEAKERS, MAX_SPEAKERS,
}; };
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,
FRONT_RIGHT, FRONT_RIGHT,
FRONT_CENTER, FRONT_CENTER,
LOW_FREQUENCY, LOW_FREQUENCY,
BACK_LEFT, BACK_LEFT,
BACK_RIGHT, BACK_RIGHT,
}; };
constexpr std::uint8_t map_surround71[] { constexpr std::uint8_t map_surround71[] {
FRONT_LEFT, FRONT_LEFT,
FRONT_RIGHT, FRONT_RIGHT,
FRONT_CENTER, FRONT_CENTER,
LOW_FREQUENCY, LOW_FREQUENCY,
BACK_LEFT, BACK_LEFT,
BACK_RIGHT, BACK_RIGHT,
SIDE_LEFT, SIDE_LEFT,
SIDE_RIGHT, SIDE_RIGHT,
}; };
} // namespace speaker } // namespace speaker
enum class mem_type_e { enum class mem_type_e {
system, system,
vaapi, vaapi,
dxgi, dxgi,
cuda, cuda,
unknown unknown
}; };
enum class pix_fmt_e { enum class pix_fmt_e {
yuv420p, yuv420p,
yuv420p10, yuv420p10,
nv12, nv12,
p010, p010,
unknown unknown
}; };
inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) { inline std::string_view
using namespace std::literals; from_pix_fmt(pix_fmt_e pix_fmt) {
using namespace std::literals;
#define _CONVERT(x) \ #define _CONVERT(x) \
case pix_fmt_e::x: \ case pix_fmt_e::x: \
return #x##sv return #x##sv
switch(pix_fmt) { switch (pix_fmt) {
_CONVERT(yuv420p); _CONVERT(yuv420p);
_CONVERT(yuv420p10); _CONVERT(yuv420p10);
_CONVERT(nv12); _CONVERT(nv12);
_CONVERT(p010); _CONVERT(p010);
_CONVERT(unknown); _CONVERT(unknown);
} }
#undef _CONVERT #undef _CONVERT
return "unknown"sv; return "unknown"sv;
}
// Dimensions for touchscreen input
struct touch_port_t {
int offset_x, offset_y;
int width, height;
};
struct gamepad_state_t {
std::uint16_t buttonFlags;
std::uint8_t lt;
std::uint8_t rt;
std::int16_t lsX;
std::int16_t lsY;
std::int16_t rsX;
std::int16_t rsY;
};
class deinit_t {
public:
virtual ~deinit_t() = default;
};
struct img_t {
public:
img_t() = default;
img_t(img_t &&) = delete;
img_t(const img_t &) = delete;
img_t &operator=(img_t &&) = delete;
img_t &operator=(const img_t &) = delete;
std::uint8_t *data {};
std::int32_t width {};
std::int32_t height {};
std::int32_t pixel_pitch {};
std::int32_t row_pitch {};
virtual ~img_t() = default;
};
struct sink_t {
// Play on host PC
std::string host;
// On macOS and Windows, it is not possible to create a virtual sink
// Therefore, it is optional
struct null_t {
std::string stereo;
std::string surround51;
std::string surround71;
};
std::optional<null_t> null;
};
struct hwdevice_t {
void *data {};
AVFrame *frame {};
virtual int convert(platf::img_t &img) {
return -1;
} }
/** // Dimensions for touchscreen input
struct touch_port_t {
int offset_x, offset_y;
int width, height;
};
struct gamepad_state_t {
std::uint16_t buttonFlags;
std::uint8_t lt;
std::uint8_t rt;
std::int16_t lsX;
std::int16_t lsY;
std::int16_t rsX;
std::int16_t rsY;
};
class deinit_t {
public:
virtual ~deinit_t() = default;
};
struct img_t {
public:
img_t() = default;
img_t(img_t &&) = delete;
img_t(const img_t &) = delete;
img_t &
operator=(img_t &&) = delete;
img_t &
operator=(const img_t &) = delete;
std::uint8_t *data {};
std::int32_t width {};
std::int32_t height {};
std::int32_t pixel_pitch {};
std::int32_t row_pitch {};
virtual ~img_t() = default;
};
struct sink_t {
// Play on host PC
std::string host;
// On macOS and Windows, it is not possible to create a virtual sink
// Therefore, it is optional
struct null_t {
std::string stereo;
std::string surround51;
std::string surround71;
};
std::optional<null_t> null;
};
struct hwdevice_t {
void *data {};
AVFrame *frame {};
virtual int
convert(platf::img_t &img) {
return -1;
}
/**
* implementations must take ownership of 'frame' * implementations must take ownership of 'frame'
*/ */
virtual int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) { virtual int
BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?"; set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
return -1; BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?";
}; return -1;
};
virtual void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) {}; virtual void
set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) {};
/** /**
* Implementations may set parameters during initialization of the hwframes context * Implementations may set parameters during initialization of the hwframes context
*/ */
virtual void init_hwframes(AVHWFramesContext *frames) {}; virtual void
init_hwframes(AVHWFramesContext *frames) {};
/** /**
* Implementations may make modifications required before context derivation * Implementations may make modifications required before context derivation
*/ */
virtual int prepare_to_derive_context(int hw_device_type) { virtual int
return 0; prepare_to_derive_context(int hw_device_type) {
return 0;
};
virtual ~hwdevice_t() = default;
}; };
virtual ~hwdevice_t() = default; enum class capture_e : int {
}; ok,
reinit,
timeout,
error
};
enum class capture_e : int { class display_t {
ok, public:
reinit, /**
timeout,
error
};
class display_t {
public:
/**
* When display has a new image ready or a timeout occurs, this callback will be called with the image. * When display has a new image ready or a timeout occurs, this callback will be called with the image.
* If a frame was captured, frame_captured will be true. If a timeout occurred, it will be false. * If a frame was captured, frame_captured will be true. If a timeout occurred, it will be false.
* *
@ -253,11 +261,12 @@ public:
* Returns the image object that should be filled next. * Returns the image object that should be filled next.
* This may or may not be the image send with the callback * This may or may not be the image send with the callback
*/ */
using snapshot_cb_t = std::function<std::shared_ptr<img_t>(std::shared_ptr<img_t> &img, bool frame_captured)>; using snapshot_cb_t = std::function<std::shared_ptr<img_t>(std::shared_ptr<img_t> &img, bool frame_captured)>;
display_t() noexcept : offset_x { 0 }, offset_y { 0 } {} display_t() noexcept:
offset_x { 0 }, offset_y { 0 } {}
/** /**
* snapshot_cb --> the callback * snapshot_cb --> the callback
* std::shared_ptr<img_t> img --> The first image to use * std::shared_ptr<img_t> img --> The first image to use
* bool *cursor --> A pointer to the flag that indicates wether the cursor should be captured as well * bool *cursor --> A pointer to the flag that indicates wether the cursor should be captured as well
@ -267,66 +276,82 @@ public:
* capture_e::error on error * capture_e::error on error
* capture_e::reinit when need of reinitialization * capture_e::reinit when need of reinitialization
*/ */
virtual capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) = 0; virtual capture_e
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) = 0;
virtual std::shared_ptr<img_t> alloc_img() = 0; virtual std::shared_ptr<img_t>
alloc_img() = 0;
virtual int dummy_img(img_t *img) = 0; virtual int
dummy_img(img_t *img) = 0;
virtual std::shared_ptr<hwdevice_t> make_hwdevice(pix_fmt_e pix_fmt) { virtual std::shared_ptr<hwdevice_t>
return std::make_shared<hwdevice_t>(); make_hwdevice(pix_fmt_e pix_fmt) {
} return std::make_shared<hwdevice_t>();
}
virtual bool is_hdr() { virtual bool
return false; is_hdr() {
} return false;
}
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) { virtual bool
std::memset(&metadata, 0, sizeof(metadata)); get_hdr_metadata(SS_HDR_METADATA &metadata) {
return false; std::memset(&metadata, 0, sizeof(metadata));
} return false;
}
virtual ~display_t() = default; virtual ~display_t() = default;
// Offsets for when streaming a specific monitor. By default, they are 0. // Offsets for when streaming a specific monitor. By default, they are 0.
int offset_x, offset_y; int offset_x, offset_y;
int env_width, env_height; int env_width, env_height;
int width, height; int width, height;
}; };
class mic_t { class mic_t {
public: public:
virtual capture_e sample(std::vector<std::int16_t> &frame_buffer) = 0; virtual capture_e
sample(std::vector<std::int16_t> &frame_buffer) = 0;
virtual ~mic_t() = default; virtual ~mic_t() = default;
}; };
class audio_control_t { class audio_control_t {
public: public:
virtual int set_sink(const std::string &sink) = 0; virtual int
set_sink(const std::string &sink) = 0;
virtual std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0; virtual std::unique_ptr<mic_t>
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0;
virtual std::optional<sink_t> sink_info() = 0; virtual std::optional<sink_t>
sink_info() = 0;
virtual ~audio_control_t() = default; virtual ~audio_control_t() = default;
}; };
void freeInput(void *); void
freeInput(void *);
using input_t = util::safe_ptr<void, freeInput>; using input_t = util::safe_ptr<void, freeInput>;
std::filesystem::path appdata(); std::filesystem::path
appdata();
std::string get_mac_address(const std::string_view &address); std::string
get_mac_address(const std::string_view &address);
std::string from_sockaddr(const sockaddr *const); std::string
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const); from_sockaddr(const sockaddr *const);
std::pair<std::uint16_t, std::string>
from_sockaddr_ex(const sockaddr *const);
std::unique_ptr<audio_control_t> audio_control(); std::unique_ptr<audio_control_t>
audio_control();
/** /**
* display_name --> The name of the monitor that SHOULD be displayed * display_name --> The name of the monitor that SHOULD be displayed
* If display_name is empty --> Use the first monitor that's compatible you can find * If display_name is empty --> Use the first monitor that's compatible you can find
* If you require to use this parameter in a seperate thread --> make a copy of it. * If you require to use this parameter in a seperate thread --> make a copy of it.
@ -335,68 +360,92 @@ std::unique_ptr<audio_control_t> audio_control();
* *
* Returns display_t based on hwdevice_type * Returns display_t based on hwdevice_type
*/ */
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); std::shared_ptr<display_t>
display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
// A list of names of displays accepted as display_name with the mem_type_e // A list of names of displays accepted as display_name with the mem_type_e
std::vector<std::string> display_names(mem_type_e hwdevice_type); std::vector<std::string>
display_names(mem_type_e hwdevice_type);
boost::process::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, boost::process::environment &env, FILE *file, std::error_code &ec, boost::process::group *group); boost::process::child
run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, boost::process::environment &env, FILE *file, std::error_code &ec, boost::process::group *group);
enum class thread_priority_e : int { enum class thread_priority_e : int {
low, low,
normal, normal,
high, high,
critical critical
}; };
void adjust_thread_priority(thread_priority_e priority); void
adjust_thread_priority(thread_priority_e priority);
// Allow OS-specific actions to be taken to prepare for streaming // Allow OS-specific actions to be taken to prepare for streaming
void streaming_will_start(); void
void streaming_will_stop(); streaming_will_start();
void
streaming_will_stop();
bool restart_supported(); bool
bool restart(); restart_supported();
bool
restart();
struct batched_send_info_t { struct batched_send_info_t {
const char *buffer; const char *buffer;
size_t block_size; size_t block_size;
size_t block_count; size_t block_count;
std::uintptr_t native_socket; std::uintptr_t native_socket;
boost::asio::ip::address &target_address; boost::asio::ip::address &target_address;
uint16_t target_port; uint16_t target_port;
}; };
bool send_batch(batched_send_info_t &send_info); bool
send_batch(batched_send_info_t &send_info);
enum class qos_data_type_e : int { enum class qos_data_type_e : int {
audio, audio,
video video
}; };
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); 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);
input_t input(); input_t
void move_mouse(input_t &input, int deltaX, int deltaY); input();
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y); void
void button_mouse(input_t &input, int button, bool release); move_mouse(input_t &input, int deltaX, int deltaY);
void scroll(input_t &input, int distance); void
void hscroll(input_t &input, int distance); abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
void keyboard(input_t &input, uint16_t modcode, bool release); void
void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state); button_mouse(input_t &input, int button, bool release);
void unicode(input_t &input, char *utf8, int size); void
scroll(input_t &input, int distance);
void
hscroll(input_t &input, int distance);
void
keyboard(input_t &input, uint16_t modcode, bool release);
void
gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state);
void
unicode(input_t &input, char *utf8, int size);
int alloc_gamepad(input_t &input, int nr, rumble_queue_t rumble_queue); int
void free_gamepad(input_t &input, int nr); alloc_gamepad(input_t &input, int nr, rumble_queue_t rumble_queue);
void
free_gamepad(input_t &input, int nr);
#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> start(); [[nodiscard]] std::unique_ptr<deinit_t>
} start();
}
[[nodiscard]] std::unique_ptr<deinit_t> init(); [[nodiscard]] std::unique_ptr<deinit_t>
init();
std::vector<std::string_view> &supported_gamepads(); std::vector<std::string_view> &
} // namespace platf supported_gamepads();
} // namespace platf
#endif //SUNSHINE_COMMON_H #endif //SUNSHINE_COMMON_H

View file

@ -17,491 +17,509 @@
#include "src/thread_safe.h" #include "src/thread_safe.h"
namespace platf { namespace platf {
using namespace std::literals; using namespace std::literals;
constexpr pa_channel_position_t position_mapping[] { constexpr pa_channel_position_t position_mapping[] {
PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_LEFT,
PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_FRONT_RIGHT,
PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_FRONT_CENTER,
PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_LFE,
PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_LEFT,
PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_REAR_RIGHT,
PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_LEFT,
PA_CHANNEL_POSITION_SIDE_RIGHT, PA_CHANNEL_POSITION_SIDE_RIGHT,
};
std::string to_string(const char *name, const std::uint8_t *mapping, int channels) {
std::stringstream ss;
ss << "rate=48000 sink_name="sv << name << " format=s16le channels="sv << channels << " channel_map="sv;
std::for_each_n(mapping, channels - 1, [&ss](std::uint8_t pos) {
ss << pa_channel_position_to_string(position_mapping[pos]) << ',';
});
ss << pa_channel_position_to_string(position_mapping[mapping[channels - 1]]);
ss << " sink_properties=device.description="sv << name;
auto result = ss.str();
BOOST_LOG(debug) << "null-sink args: "sv << result;
return result;
}
struct mic_attr_t : public mic_t {
util::safe_ptr<pa_simple, pa_simple_free> mic;
capture_e sample(std::vector<std::int16_t> &sample_buf) override {
auto sample_size = sample_buf.size();
auto buf = sample_buf.data();
int status;
if(pa_simple_read(mic.get(), buf, sample_size * 2, &status)) {
BOOST_LOG(error) << "pa_simple_read() failed: "sv << pa_strerror(status);
return capture_e::error;
}
return capture_e::ok;
}
};
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
auto mic = std::make_unique<mic_attr_t>();
pa_sample_spec ss { PA_SAMPLE_S16LE, sample_rate, (std::uint8_t)channels };
pa_channel_map pa_map;
pa_map.channels = channels;
std::for_each_n(pa_map.map, pa_map.channels, [mapping](auto &channel) mutable {
channel = position_mapping[*mapping++];
});
pa_buffer_attr pa_attr = {};
pa_attr.maxlength = frame_size * 8;
int status;
mic->mic.reset(
pa_simple_new(nullptr, "sunshine",
pa_stream_direction_t::PA_STREAM_RECORD, source_name.c_str(),
"sunshine-record", &ss, &pa_map, &pa_attr, &status));
if(!mic->mic) {
auto err_str = pa_strerror(status);
BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str;
return nullptr;
}
return mic;
}
namespace pa {
template<bool B, class T>
struct add_const_helper;
template<class T>
struct add_const_helper<true, T> {
using type = const std::remove_pointer_t<T> *;
};
template<class T>
struct add_const_helper<false, T> {
using type = const T *;
};
template<class T>
using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type;
template<class T>
void pa_free(T *p) {
pa_xfree(p);
}
using ctx_t = util::safe_ptr<pa_context, pa_context_unref>;
using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>;
using op_t = util::safe_ptr<pa_operation, pa_operation_unref>;
using string_t = util::safe_ptr<char, pa_free<char>>;
template<class T>
using cb_simple_t = std::function<void(ctx_t::pointer, add_const_t<T> i)>;
template<class T>
void cb(ctx_t::pointer ctx, add_const_t<T> i, void *userdata) {
auto &f = *(cb_simple_t<T> *)userdata;
// Cannot similarly filter on eol here. Unless reported otherwise assume
// we have no need for special filtering like cb?
f(ctx, i);
}
template<class T>
using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>;
template<class T>
void cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
auto &f = *(cb_t<T> *)userdata;
// For some reason, pulseaudio calls this callback after disconnecting
if(i && eol) {
return;
}
f(ctx, i, eol);
}
void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
auto alarm = (safe::alarm_raw_t<int> *)userdata;
alarm->ring(i);
}
void ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
auto &f = *(std::function<void(ctx_t::pointer)> *)userdata;
f(ctx);
}
void success_cb(ctx_t::pointer ctx, int status, void *userdata) {
assert(userdata != nullptr);
auto alarm = (safe::alarm_raw_t<int> *)userdata;
alarm->ring(status ? 0 : 1);
}
class server_t : public audio_control_t {
enum ctx_event_e : int {
ready,
terminated,
failed
}; };
public: std::string
loop_t loop; to_string(const char *name, const std::uint8_t *mapping, int channels) {
ctx_t ctx; std::stringstream ss;
std::string requested_sink;
struct { ss << "rate=48000 sink_name="sv << name << " format=s16le channels="sv << channels << " channel_map="sv;
std::uint32_t stereo = PA_INVALID_INDEX; std::for_each_n(mapping, channels - 1, [&ss](std::uint8_t pos) {
std::uint32_t surround51 = PA_INVALID_INDEX; ss << pa_channel_position_to_string(position_mapping[pos]) << ',';
std::uint32_t surround71 = PA_INVALID_INDEX;
} index;
std::unique_ptr<safe::event_t<ctx_event_e>> events;
std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb;
std::thread worker;
int init() {
events = std::make_unique<safe::event_t<ctx_event_e>>();
loop.reset(pa_mainloop_new());
ctx.reset(pa_context_new(pa_mainloop_get_api(loop.get()), "sunshine"));
events_cb = std::make_unique<std::function<void(ctx_t::pointer)>>([this](ctx_t::pointer ctx) {
switch(pa_context_get_state(ctx)) {
case PA_CONTEXT_READY:
events->raise(ready);
break;
case PA_CONTEXT_TERMINATED:
BOOST_LOG(debug) << "Pulseadio context terminated"sv;
events->raise(terminated);
break;
case PA_CONTEXT_FAILED:
BOOST_LOG(debug) << "Pulseadio context failed"sv;
events->raise(failed);
break;
case PA_CONTEXT_CONNECTING:
BOOST_LOG(debug) << "Connecting to pulseaudio"sv;
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
break;
}
}); });
pa_context_set_state_callback(ctx.get(), ctx_state_cb, events_cb.get()); ss << pa_channel_position_to_string(position_mapping[mapping[channels - 1]]);
auto status = pa_context_connect(ctx.get(), nullptr, PA_CONTEXT_NOFLAGS, nullptr); ss << " sink_properties=device.description="sv << name;
if(status) { auto result = ss.str();
BOOST_LOG(error) << "Couldn't connect to pulseaudio: "sv << pa_strerror(status);
return -1;
}
worker = std::thread { BOOST_LOG(debug) << "null-sink args: "sv << result;
[](loop_t::pointer loop) { return result;
int retval;
auto status = pa_mainloop_run(loop, &retval);
if(status < 0) {
BOOST_LOG(error) << "Couldn't run pulseaudio main loop"sv;
return;
}
},
loop.get()
};
auto event = events->pop();
if(event == failed) {
return -1;
}
return 0;
} }
int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) { struct mic_attr_t: public mic_t {
auto alarm = safe::make_alarm<int>(); util::safe_ptr<pa_simple, pa_simple_free> mic;
op_t op { capture_e
pa_context_load_module( sample(std::vector<std::int16_t> &sample_buf) override {
ctx.get(), auto sample_size = sample_buf.size();
"module-null-sink",
to_string(name, channel_mapping, channels).c_str(),
cb_i,
alarm.get()),
};
alarm->wait(); auto buf = sample_buf.data();
return *alarm->status(); int status;
} if (pa_simple_read(mic.get(), buf, sample_size * 2, &status)) {
BOOST_LOG(error) << "pa_simple_read() failed: "sv << pa_strerror(status);
int unload_null(std::uint32_t i) { return capture_e::error;
if(i == PA_INVALID_INDEX) { }
return 0;
return capture_e::ok;
}
};
std::unique_ptr<mic_t>
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
auto mic = std::make_unique<mic_attr_t>();
pa_sample_spec ss { PA_SAMPLE_S16LE, sample_rate, (std::uint8_t) channels };
pa_channel_map pa_map;
pa_map.channels = channels;
std::for_each_n(pa_map.map, pa_map.channels, [mapping](auto &channel) mutable {
channel = position_mapping[*mapping++];
});
pa_buffer_attr pa_attr = {};
pa_attr.maxlength = frame_size * 8;
int status;
mic->mic.reset(
pa_simple_new(nullptr, "sunshine",
pa_stream_direction_t::PA_STREAM_RECORD, source_name.c_str(),
"sunshine-record", &ss, &pa_map, &pa_attr, &status));
if (!mic->mic) {
auto err_str = pa_strerror(status);
BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str;
return nullptr;
} }
auto alarm = safe::make_alarm<int>(); return mic;
op_t op {
pa_context_unload_module(ctx.get(), i, success_cb, alarm.get())
};
alarm->wait();
if(*alarm->status()) {
BOOST_LOG(error) << "Couldn't unload null-sink with index ["sv << i << "]: "sv << pa_strerror(pa_context_errno(ctx.get()));
return -1;
}
return 0;
} }
std::optional<sink_t> sink_info() override { namespace pa {
constexpr auto stereo = "sink-sunshine-stereo"; template <bool B, class T>
constexpr auto surround51 = "sink-sunshine-surround51"; struct add_const_helper;
constexpr auto surround71 = "sink-sunshine-surround71";
auto alarm = safe::make_alarm<int>(); template <class T>
struct add_const_helper<true, T> {
using type = const std::remove_pointer_t<T> *;
};
sink_t sink; template <class T>
struct add_const_helper<false, T> {
using type = const T *;
};
// Count of all virtual sinks that are created by us template <class T>
int nullcount = 0; using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type;
cb_t<pa_sink_info *> f = [&](ctx_t::pointer ctx, const pa_sink_info *sink_info, int eol) { template <class T>
if(!sink_info) { void
if(!eol) { pa_free(T *p) {
BOOST_LOG(error) << "Couldn't get pulseaudio sink info: "sv << pa_strerror(pa_context_errno(ctx)); pa_xfree(p);
}
using ctx_t = util::safe_ptr<pa_context, pa_context_unref>;
using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>;
using op_t = util::safe_ptr<pa_operation, pa_operation_unref>;
using string_t = util::safe_ptr<char, pa_free<char>>;
alarm->ring(-1); template <class T>
} using cb_simple_t = std::function<void(ctx_t::pointer, add_const_t<T> i)>;
alarm->ring(0); template <class T>
void
cb(ctx_t::pointer ctx, add_const_t<T> i, void *userdata) {
auto &f = *(cb_simple_t<T> *) userdata;
// Cannot similarly filter on eol here. Unless reported otherwise assume
// we have no need for special filtering like cb?
f(ctx, i);
}
template <class T>
using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>;
template <class T>
void
cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
auto &f = *(cb_t<T> *) userdata;
// For some reason, pulseaudio calls this callback after disconnecting
if (i && eol) {
return; return;
} }
// Ensure Sunshine won't create a sink that already exists. f(ctx, i, eol);
if(!std::strcmp(sink_info->name, stereo)) {
index.stereo = sink_info->owner_module;
++nullcount;
}
else if(!std::strcmp(sink_info->name, surround51)) {
index.surround51 = sink_info->owner_module;
++nullcount;
}
else if(!std::strcmp(sink_info->name, surround71)) {
index.surround71 = sink_info->owner_module;
++nullcount;
}
};
op_t op { pa_context_get_sink_info_list(ctx.get(), cb<pa_sink_info *>, &f) };
if(!op) {
BOOST_LOG(error) << "Couldn't create card info operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
return std::nullopt;
} }
alarm->wait(); void
cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
auto alarm = (safe::alarm_raw_t<int> *) userdata;
if(*alarm->status()) { alarm->ring(i);
return std::nullopt;
} }
auto sink_name = get_default_sink_name(); void
sink.host = sink_name; ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
auto &f = *(std::function<void(ctx_t::pointer)> *) userdata;
if(index.stereo == PA_INVALID_INDEX) { f(ctx);
index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo));
if(index.stereo == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for stereo: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
++nullcount;
}
} }
if(index.surround51 == PA_INVALID_INDEX) { void
index.surround51 = load_null(surround51, speaker::map_surround51, sizeof(speaker::map_surround51)); success_cb(ctx_t::pointer ctx, int status, void *userdata) {
if(index.surround51 == PA_INVALID_INDEX) { assert(userdata != nullptr);
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-51: "sv << pa_strerror(pa_context_errno(ctx.get()));
} auto alarm = (safe::alarm_raw_t<int> *) userdata;
else { alarm->ring(status ? 0 : 1);
++nullcount;
}
} }
if(index.surround71 == PA_INVALID_INDEX) { class server_t: public audio_control_t {
index.surround71 = load_null(surround71, speaker::map_surround71, sizeof(speaker::map_surround71)); enum ctx_event_e : int {
if(index.surround71 == PA_INVALID_INDEX) { ready,
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-71: "sv << pa_strerror(pa_context_errno(ctx.get())); terminated,
} failed
else { };
++nullcount;
}
}
if(sink_name.empty()) { public:
BOOST_LOG(warning) << "Couldn't find an active default sink. Continuing with virtual audio only."sv; loop_t loop;
} ctx_t ctx;
std::string requested_sink;
if(nullcount == 3) { struct {
sink.null = std::make_optional(sink_t::null_t { stereo, surround51, surround71 }); std::uint32_t stereo = PA_INVALID_INDEX;
} std::uint32_t surround51 = PA_INVALID_INDEX;
std::uint32_t surround71 = PA_INVALID_INDEX;
} index;
return std::make_optional(std::move(sink)); std::unique_ptr<safe::event_t<ctx_event_e>> events;
} std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb;
std::string get_default_sink_name() { std::thread worker;
std::string sink_name; int
auto alarm = safe::make_alarm<int>(); init() {
events = std::make_unique<safe::event_t<ctx_event_e>>();
loop.reset(pa_mainloop_new());
ctx.reset(pa_context_new(pa_mainloop_get_api(loop.get()), "sunshine"));
cb_simple_t<pa_server_info *> server_f = [&](ctx_t::pointer ctx, const pa_server_info *server_info) { events_cb = std::make_unique<std::function<void(ctx_t::pointer)>>([this](ctx_t::pointer ctx) {
if(!server_info) { switch (pa_context_get_state(ctx)) {
BOOST_LOG(error) << "Couldn't get pulseaudio server info: "sv << pa_strerror(pa_context_errno(ctx)); case PA_CONTEXT_READY:
alarm->ring(-1); events->raise(ready);
} break;
case PA_CONTEXT_TERMINATED:
BOOST_LOG(debug) << "Pulseadio context terminated"sv;
events->raise(terminated);
break;
case PA_CONTEXT_FAILED:
BOOST_LOG(debug) << "Pulseadio context failed"sv;
events->raise(failed);
break;
case PA_CONTEXT_CONNECTING:
BOOST_LOG(debug) << "Connecting to pulseaudio"sv;
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
break;
}
});
if(server_info->default_sink_name) { pa_context_set_state_callback(ctx.get(), ctx_state_cb, events_cb.get());
sink_name = server_info->default_sink_name;
}
alarm->ring(0);
};
op_t server_op { pa_context_get_server_info(ctx.get(), cb<pa_server_info *>, &server_f) }; auto status = pa_context_connect(ctx.get(), nullptr, PA_CONTEXT_NOFLAGS, nullptr);
alarm->wait(); if (status) {
// No need to check status. If it failed just return default name. BOOST_LOG(error) << "Couldn't connect to pulseaudio: "sv << pa_strerror(status);
return sink_name; return -1;
}
std::string get_monitor_name(const std::string &sink_name) {
std::string monitor_name;
auto alarm = safe::make_alarm<int>();
if(sink_name.empty()) {
return monitor_name;
}
cb_t<pa_sink_info *> sink_f = [&](ctx_t::pointer ctx, const pa_sink_info *sink_info, int eol) {
if(!sink_info) {
if(!eol) {
BOOST_LOG(error) << "Couldn't get pulseaudio sink info for ["sv << sink_name
<< "]: "sv << pa_strerror(pa_context_errno(ctx));
alarm->ring(-1);
} }
alarm->ring(0); worker = std::thread {
return; [](loop_t::pointer loop) {
int retval;
auto status = pa_mainloop_run(loop, &retval);
if (status < 0) {
BOOST_LOG(error) << "Couldn't run pulseaudio main loop"sv;
return;
}
},
loop.get()
};
auto event = events->pop();
if (event == failed) {
return -1;
}
return 0;
} }
monitor_name = sink_info->monitor_source_name; int
load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
auto alarm = safe::make_alarm<int>();
op_t op {
pa_context_load_module(
ctx.get(),
"module-null-sink",
to_string(name, channel_mapping, channels).c_str(),
cb_i,
alarm.get()),
};
alarm->wait();
return *alarm->status();
}
int
unload_null(std::uint32_t i) {
if (i == PA_INVALID_INDEX) {
return 0;
}
auto alarm = safe::make_alarm<int>();
op_t op {
pa_context_unload_module(ctx.get(), i, success_cb, alarm.get())
};
alarm->wait();
if (*alarm->status()) {
BOOST_LOG(error) << "Couldn't unload null-sink with index ["sv << i << "]: "sv << pa_strerror(pa_context_errno(ctx.get()));
return -1;
}
return 0;
}
std::optional<sink_t>
sink_info() override {
constexpr auto stereo = "sink-sunshine-stereo";
constexpr auto surround51 = "sink-sunshine-surround51";
constexpr auto surround71 = "sink-sunshine-surround71";
auto alarm = safe::make_alarm<int>();
sink_t sink;
// Count of all virtual sinks that are created by us
int nullcount = 0;
cb_t<pa_sink_info *> f = [&](ctx_t::pointer ctx, const pa_sink_info *sink_info, int eol) {
if (!sink_info) {
if (!eol) {
BOOST_LOG(error) << "Couldn't get pulseaudio sink info: "sv << pa_strerror(pa_context_errno(ctx));
alarm->ring(-1);
}
alarm->ring(0);
return;
}
// Ensure Sunshine won't create a sink that already exists.
if (!std::strcmp(sink_info->name, stereo)) {
index.stereo = sink_info->owner_module;
++nullcount;
}
else if (!std::strcmp(sink_info->name, surround51)) {
index.surround51 = sink_info->owner_module;
++nullcount;
}
else if (!std::strcmp(sink_info->name, surround71)) {
index.surround71 = sink_info->owner_module;
++nullcount;
}
};
op_t op { pa_context_get_sink_info_list(ctx.get(), cb<pa_sink_info *>, &f) };
if (!op) {
BOOST_LOG(error) << "Couldn't create card info operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
return std::nullopt;
}
alarm->wait();
if (*alarm->status()) {
return std::nullopt;
}
auto sink_name = get_default_sink_name();
sink.host = sink_name;
if (index.stereo == PA_INVALID_INDEX) {
index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo));
if (index.stereo == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for stereo: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
++nullcount;
}
}
if (index.surround51 == PA_INVALID_INDEX) {
index.surround51 = load_null(surround51, speaker::map_surround51, sizeof(speaker::map_surround51));
if (index.surround51 == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-51: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
++nullcount;
}
}
if (index.surround71 == PA_INVALID_INDEX) {
index.surround71 = load_null(surround71, speaker::map_surround71, sizeof(speaker::map_surround71));
if (index.surround71 == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-71: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
++nullcount;
}
}
if (sink_name.empty()) {
BOOST_LOG(warning) << "Couldn't find an active default sink. Continuing with virtual audio only."sv;
}
if (nullcount == 3) {
sink.null = std::make_optional(sink_t::null_t { stereo, surround51, surround71 });
}
return std::make_optional(std::move(sink));
}
std::string
get_default_sink_name() {
std::string sink_name;
auto alarm = safe::make_alarm<int>();
cb_simple_t<pa_server_info *> server_f = [&](ctx_t::pointer ctx, const pa_server_info *server_info) {
if (!server_info) {
BOOST_LOG(error) << "Couldn't get pulseaudio server info: "sv << pa_strerror(pa_context_errno(ctx));
alarm->ring(-1);
}
if (server_info->default_sink_name) {
sink_name = server_info->default_sink_name;
}
alarm->ring(0);
};
op_t server_op { pa_context_get_server_info(ctx.get(), cb<pa_server_info *>, &server_f) };
alarm->wait();
// No need to check status. If it failed just return default name.
return sink_name;
}
std::string
get_monitor_name(const std::string &sink_name) {
std::string monitor_name;
auto alarm = safe::make_alarm<int>();
if (sink_name.empty()) {
return monitor_name;
}
cb_t<pa_sink_info *> sink_f = [&](ctx_t::pointer ctx, const pa_sink_info *sink_info, int eol) {
if (!sink_info) {
if (!eol) {
BOOST_LOG(error) << "Couldn't get pulseaudio sink info for ["sv << sink_name
<< "]: "sv << pa_strerror(pa_context_errno(ctx));
alarm->ring(-1);
}
alarm->ring(0);
return;
}
monitor_name = sink_info->monitor_source_name;
};
op_t sink_op { pa_context_get_sink_info_by_name(ctx.get(), sink_name.c_str(), cb<pa_sink_info *>, &sink_f) };
alarm->wait();
// No need to check status. If it failed just return default name.
BOOST_LOG(info) << "Found default monitor by name: "sv << monitor_name;
return monitor_name;
}
std::unique_ptr<mic_t>
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
// Sink choice priority:
// 1. Config sink
// 2. Last sink swapped to (Usually virtual in this case)
// 3. Default Sink
// An attempt was made to always use default to match the switching mechanic,
// but this happens right after the swap so the default returned by PA was not
// the new one just set!
auto sink_name = config::audio.sink;
if (sink_name.empty()) sink_name = requested_sink;
if (sink_name.empty()) sink_name = get_default_sink_name();
return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name));
}
int
set_sink(const std::string &sink) override {
auto alarm = safe::make_alarm<int>();
BOOST_LOG(info) << "Setting default sink to: ["sv << sink << "]"sv;
op_t op {
pa_context_set_default_sink(
ctx.get(), sink.c_str(), success_cb, alarm.get()),
};
if (!op) {
BOOST_LOG(error) << "Couldn't create set default-sink operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
return -1;
}
alarm->wait();
if (*alarm->status()) {
BOOST_LOG(error) << "Couldn't set default-sink ["sv << sink << "]: "sv << pa_strerror(pa_context_errno(ctx.get()));
return -1;
}
requested_sink = sink;
return 0;
}
~server_t() override {
unload_null(index.stereo);
unload_null(index.surround51);
unload_null(index.surround71);
if (worker.joinable()) {
pa_context_disconnect(ctx.get());
KITTY_WHILE_LOOP(auto event = events->pop(), event != terminated && event != failed, {
event = events->pop();
})
pa_mainloop_quit(loop.get(), 0);
worker.join();
}
}
}; };
} // namespace pa
op_t sink_op { pa_context_get_sink_info_by_name(ctx.get(), sink_name.c_str(), cb<pa_sink_info *>, &sink_f) }; std::unique_ptr<audio_control_t>
audio_control() {
auto audio = std::make_unique<pa::server_t>();
alarm->wait(); if (audio->init()) {
// No need to check status. If it failed just return default name. return nullptr;
BOOST_LOG(info) << "Found default monitor by name: "sv << monitor_name;
return monitor_name;
}
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
// Sink choice priority:
// 1. Config sink
// 2. Last sink swapped to (Usually virtual in this case)
// 3. Default Sink
// An attempt was made to always use default to match the switching mechanic,
// but this happens right after the swap so the default returned by PA was not
// the new one just set!
auto sink_name = config::audio.sink;
if(sink_name.empty()) sink_name = requested_sink;
if(sink_name.empty()) sink_name = get_default_sink_name();
return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name));
}
int set_sink(const std::string &sink) override {
auto alarm = safe::make_alarm<int>();
BOOST_LOG(info) << "Setting default sink to: ["sv << sink << "]"sv;
op_t op {
pa_context_set_default_sink(
ctx.get(), sink.c_str(), success_cb, alarm.get()),
};
if(!op) {
BOOST_LOG(error) << "Couldn't create set default-sink operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
return -1;
} }
alarm->wait(); return audio;
if(*alarm->status()) {
BOOST_LOG(error) << "Couldn't set default-sink ["sv << sink << "]: "sv << pa_strerror(pa_context_errno(ctx.get()));
return -1;
}
requested_sink = sink;
return 0;
} }
} // namespace platf
~server_t() override {
unload_null(index.stereo);
unload_null(index.surround51);
unload_null(index.surround71);
if(worker.joinable()) {
pa_context_disconnect(ctx.get());
KITTY_WHILE_LOOP(auto event = events->pop(), event != terminated && event != failed, {
event = events->pop();
})
pa_mainloop_quit(loop.get(), 0);
worker.join();
}
}
};
} // namespace pa
std::unique_ptr<audio_control_t> audio_control() {
auto audio = std::make_unique<pa::server_t>();
if(audio->init()) {
return nullptr;
}
return audio;
}
} // namespace platf

File diff suppressed because it is too large Load diff

View file

@ -1,107 +1,121 @@
#if !defined(SUNSHINE_PLATFORM_CUDA_H) && defined(SUNSHINE_BUILD_CUDA) #if !defined(SUNSHINE_PLATFORM_CUDA_H) && defined(SUNSHINE_BUILD_CUDA)
#define SUNSHINE_PLATFORM_CUDA_H #define SUNSHINE_PLATFORM_CUDA_H
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
namespace platf { namespace platf {
class hwdevice_t; class hwdevice_t;
class img_t; class img_t;
} // namespace platf } // namespace platf
namespace cuda { namespace cuda {
namespace nvfbc { namespace nvfbc {
std::vector<std::string> display_names(); std::vector<std::string>
} display_names();
std::shared_ptr<platf::hwdevice_t> make_hwdevice(int width, int height, bool vram); }
int init(); std::shared_ptr<platf::hwdevice_t>
} // namespace cuda make_hwdevice(int width, int height, bool vram);
int
init();
} // namespace cuda
typedef struct cudaArray *cudaArray_t; typedef struct cudaArray *cudaArray_t;
#if !defined(__CUDACC__) #if !defined(__CUDACC__)
typedef struct CUstream_st *cudaStream_t; typedef struct CUstream_st *cudaStream_t;
typedef unsigned long long cudaTextureObject_t; typedef unsigned long long cudaTextureObject_t;
#else /* defined(__CUDACC__) */ #else /* defined(__CUDACC__) */
typedef __location__(device_builtin) struct CUstream_st *cudaStream_t; typedef __location__(device_builtin) struct CUstream_st *cudaStream_t;
typedef __location__(device_builtin) unsigned long long cudaTextureObject_t; typedef __location__(device_builtin) unsigned long long cudaTextureObject_t;
#endif /* !defined(__CUDACC__) */ #endif /* !defined(__CUDACC__) */
namespace cuda { namespace cuda {
class freeCudaPtr_t { class freeCudaPtr_t {
public: public:
void operator()(void *ptr); void
}; operator()(void *ptr);
};
class freeCudaStream_t { class freeCudaStream_t {
public: public:
void operator()(cudaStream_t ptr); void
}; 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 make_stream(int flags = 0); stream_t
make_stream(int flags = 0);
struct viewport_t { struct viewport_t {
int width, height; int width, height;
int offsetX, offsetY; int offsetX, offsetY;
}; };
class tex_t { class tex_t {
public: public:
static std::optional<tex_t> make(int height, int pitch); static std::optional<tex_t>
make(int height, int pitch);
tex_t(); tex_t();
tex_t(tex_t &&); tex_t(tex_t &&);
tex_t &operator=(tex_t &&other); tex_t &
operator=(tex_t &&other);
~tex_t(); ~tex_t();
int copy(std::uint8_t *src, int height, int pitch); int
copy(std::uint8_t *src, int height, int pitch);
cudaArray_t array; cudaArray_t array;
struct texture { struct texture {
cudaTextureObject_t point; cudaTextureObject_t point;
cudaTextureObject_t linear; cudaTextureObject_t linear;
} texture; } texture;
}; };
class sws_t { class sws_t {
public: public:
sws_t() = default; sws_t() = default;
sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix); sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix);
/** /**
* in_width, in_height -- The width and height of the captured image in pixels * in_width, in_height -- The width and height of the captured image in pixels
* out_width, out_height -- the width and height of the NV12 image in pixels * out_width, out_height -- the width and height of the NV12 image in pixels
* *
* 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> make(int in_width, int in_height, int out_width, int out_height, int pitch); static std::optional<sws_t>
make(int in_width, int in_height, int out_width, int out_height, int pitch);
// Converts loaded image into a CUDevicePtr // Converts loaded image into a CUDevicePtr
int convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream); int
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); convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream);
int
convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport);
void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range); void
set_colorspace(std::uint32_t colorspace, std::uint32_t color_range);
int load_ram(platf::img_t &img, cudaArray_t array); int
load_ram(platf::img_t &img, cudaArray_t array);
ptr_t color_matrix; ptr_t color_matrix;
int threadsPerBlock; int threadsPerBlock;
viewport_t viewport; viewport_t viewport;
float scale; float scale;
}; };
} // namespace cuda } // namespace cuda
#endif #endif

File diff suppressed because it is too large Load diff

View file

@ -17,303 +17,340 @@
#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 close(int __fd); extern "C" int
close(int __fd);
// X11 Display // X11 Display
extern "C" struct _XDisplay; extern "C" struct _XDisplay;
struct AVFrame; struct AVFrame;
void free_frame(AVFrame *frame); void
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 drain_errors(const std::string_view &prefix); void
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 &operator=(tex_t &&) = default; tex_t &
operator=(tex_t &&) = default;
~tex_t(); ~tex_t();
static tex_t make(std::size_t count); static tex_t
}; make(std::size_t count);
};
class frame_buf_t : public util::buffer_t<GLuint> { class frame_buf_t: public util::buffer_t<GLuint> {
using util::buffer_t<GLuint>::buffer_t; using util::buffer_t<GLuint>::buffer_t;
public: public:
frame_buf_t(frame_buf_t &&) = default; frame_buf_t(frame_buf_t &&) = default;
frame_buf_t &operator=(frame_buf_t &&) = default; frame_buf_t &
operator=(frame_buf_t &&) = default;
~frame_buf_t(); ~frame_buf_t();
static frame_buf_t make(std::size_t count); static frame_buf_t
make(std::size_t count);
inline void bind(std::nullptr_t, std::nullptr_t) { inline void
int x = 0; bind(std::nullptr_t, std::nullptr_t) {
for(auto fb : (*this)) { int x = 0;
ctx.BindFramebuffer(GL_FRAMEBUFFER, fb); for (auto fb : (*this)) {
ctx.FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, 0, 0); ctx.BindFramebuffer(GL_FRAMEBUFFER, fb);
ctx.FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, 0, 0);
++x; ++x;
} }
return;
}
template<class It>
void bind(It it_begin, It it_end) {
using namespace std::literals;
if(std::distance(it_begin, it_end) > size()) {
BOOST_LOG(warning) << "To many elements to bind"sv;
return; return;
} }
int x = 0; template <class It>
std::for_each(it_begin, it_end, [&](auto tex) { void
ctx.BindFramebuffer(GL_FRAMEBUFFER, (*this)[x]); bind(It it_begin, It it_end) {
ctx.BindTexture(GL_TEXTURE_2D, tex); using namespace std::literals;
if (std::distance(it_begin, it_end) > size()) {
BOOST_LOG(warning) << "To many elements to bind"sv;
return;
}
ctx.FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, tex, 0); int x = 0;
std::for_each(it_begin, it_end, [&](auto tex) {
ctx.BindFramebuffer(GL_FRAMEBUFFER, (*this)[x]);
ctx.BindTexture(GL_TEXTURE_2D, tex);
++x; ctx.FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, tex, 0);
});
}
/** ++x;
});
}
/**
* Copies a part of the framebuffer to texture * Copies a part of the framebuffer to texture
*/ */
void copy(int id, int texture, int offset_x, int offset_y, int width, int height); void
}; copy(int id, int texture, int offset_x, int offset_y, int width, int height);
};
class shader_t { class shader_t {
KITTY_USING_MOVE_T(shader_internal_t, GLuint, std::numeric_limits<GLuint>::max(), { KITTY_USING_MOVE_T(shader_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
if(el != std::numeric_limits<GLuint>::max()) { if (el != std::numeric_limits<GLuint>::max()) {
ctx.DeleteShader(el); ctx.DeleteShader(el);
} }
}); });
public: public:
std::string err_str(); std::string
err_str();
static util::Either<shader_t, std::string> compile(const std::string_view &source, GLenum type); static util::Either<shader_t, std::string>
compile(const std::string_view &source, GLenum type);
GLuint handle() const; GLuint
handle() const;
private: private:
shader_internal_t _shader; shader_internal_t _shader;
}; };
class buffer_t { class buffer_t {
KITTY_USING_MOVE_T(buffer_internal_t, GLuint, std::numeric_limits<GLuint>::max(), { KITTY_USING_MOVE_T(buffer_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
if(el != std::numeric_limits<GLuint>::max()) { if (el != std::numeric_limits<GLuint>::max()) {
ctx.DeleteBuffers(1, &el); ctx.DeleteBuffers(1, &el);
} }
}); });
public: public:
static buffer_t make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data); static buffer_t
make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data);
GLuint handle() const; GLuint
handle() const;
const char *block() const; const char *
block() const;
void update(const std::string_view &view, std::size_t offset = 0); void
void update(std::string_view *members, std::size_t count, 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);
private: private:
const char *_block; const char *_block;
std::size_t _size; std::size_t _size;
util::buffer_t<GLint> _offsets; util::buffer_t<GLint> _offsets;
buffer_internal_t _buffer; buffer_internal_t _buffer;
}; };
class program_t { class program_t {
KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits<GLuint>::max(), { KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
if(el != std::numeric_limits<GLuint>::max()) { if (el != std::numeric_limits<GLuint>::max()) {
ctx.DeleteProgram(el); ctx.DeleteProgram(el);
} }
}); });
public: public:
std::string err_str(); std::string
err_str();
static util::Either<program_t, std::string> link(const shader_t &vert, const shader_t &frag); static util::Either<program_t, std::string>
link(const shader_t &vert, const shader_t &frag);
void bind(const buffer_t &buffer); void
bind(const buffer_t &buffer);
std::optional<buffer_t> uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count); std::optional<buffer_t>
uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count);
GLuint handle() const; GLuint
handle() const;
private: private:
program_internal_t _program; program_internal_t _program;
}; };
} // namespace gl } // namespace gl
namespace gbm { namespace gbm {
struct device; struct device;
typedef void (*device_destroy_fn)(device *gbm); typedef void (*device_destroy_fn)(device *gbm);
typedef device *(*create_device_fn)(int fd); typedef device *(*create_device_fn)(int fd);
extern device_destroy_fn device_destroy; extern device_destroy_fn device_destroy;
extern create_device_fn create_device; extern create_device_fn create_device;
using gbm_t = util::dyn_safe_ptr<device, &device_destroy>; using gbm_t = util::dyn_safe_ptr<device, &device_destroy>;
int init(); int
init();
} // namespace gbm } // namespace gbm
namespace egl { namespace egl {
using display_t = util::dyn_safe_ptr_v2<void, EGLBoolean, &eglTerminate>; using display_t = util::dyn_safe_ptr_v2<void, EGLBoolean, &eglTerminate>;
struct rgb_img_t { struct rgb_img_t {
display_t::pointer display; display_t::pointer display;
EGLImage xrgb8; EGLImage xrgb8;
gl::tex_t tex; gl::tex_t tex;
}; };
struct nv12_img_t { struct nv12_img_t {
display_t::pointer display; display_t::pointer display;
EGLImage r8; EGLImage r8;
EGLImage bg88; EGLImage bg88;
gl::tex_t tex; gl::tex_t tex;
gl::frame_buf_t buf; gl::frame_buf_t buf;
// sizeof(va::DRMPRIMESurfaceDescriptor::objects) / sizeof(va::DRMPRIMESurfaceDescriptor::objects[0]); // sizeof(va::DRMPRIMESurfaceDescriptor::objects) / sizeof(va::DRMPRIMESurfaceDescriptor::objects[0]);
static constexpr std::size_t num_fds = 4; static constexpr std::size_t num_fds = 4;
std::array<file_t, num_fds> fds; std::array<file_t, num_fds> fds;
}; };
KITTY_USING_MOVE_T(rgb_t, rgb_img_t, , { KITTY_USING_MOVE_T(rgb_t, rgb_img_t, , {
if(el.xrgb8) { if (el.xrgb8) {
eglDestroyImage(el.display, el.xrgb8); eglDestroyImage(el.display, el.xrgb8);
} }
}); });
KITTY_USING_MOVE_T(nv12_t, nv12_img_t, , { KITTY_USING_MOVE_T(nv12_t, nv12_img_t, , {
if(el.r8) { if (el.r8) {
eglDestroyImage(el.display, el.r8); eglDestroyImage(el.display, el.r8);
} }
if(el.bg88) { if (el.bg88) {
eglDestroyImage(el.display, el.bg88); eglDestroyImage(el.display, el.bg88);
} }
}); });
KITTY_USING_MOVE_T(ctx_t, (std::tuple<display_t::pointer, EGLContext>), , { KITTY_USING_MOVE_T(ctx_t, (std::tuple<display_t::pointer, EGLContext>), , {
TUPLE_2D_REF(disp, ctx, el); TUPLE_2D_REF(disp, ctx, el);
if(ctx) { if (ctx) {
eglMakeCurrent(disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglMakeCurrent(disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(disp, ctx); eglDestroyContext(disp, ctx);
} }
}); });
struct surface_descriptor_t { struct surface_descriptor_t {
int width; int width;
int height; int height;
int fds[4]; int fds[4];
std::uint32_t fourcc; std::uint32_t fourcc;
std::uint64_t modifier; std::uint64_t modifier;
std::uint32_t pitches[4]; std::uint32_t pitches[4];
std::uint32_t offsets[4]; std::uint32_t offsets[4];
}; };
display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display); display_t
std::optional<ctx_t> make_ctx(display_t::pointer 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<rgb_t> import_source( std::optional<rgb_t>
display_t::pointer egl_display, import_source(
const surface_descriptor_t &xrgb); display_t::pointer egl_display,
const surface_descriptor_t &xrgb);
std::optional<nv12_t> import_target( std::optional<nv12_t>
display_t::pointer egl_display, import_target(
std::array<file_t, nv12_img_t::num_fds> &&fds, display_t::pointer egl_display,
const surface_descriptor_t &r8, const surface_descriptor_t &gr88); std::array<file_t, nv12_img_t::num_fds> &&fds,
const surface_descriptor_t &r8, const surface_descriptor_t &gr88);
class cursor_t : public platf::img_t { class cursor_t: public platf::img_t {
public: public:
int x, y; int x, y;
unsigned long serial; unsigned long serial;
std::vector<std::uint8_t> buffer; std::vector<std::uint8_t> buffer;
}; };
// Allow cursor and the underlying image to be kept together // Allow cursor and the underlying image to be kept together
class img_descriptor_t : public cursor_t { class img_descriptor_t: public cursor_t {
public: public:
~img_descriptor_t() { ~img_descriptor_t() {
reset(); reset();
} }
void reset() { void
for(auto x = 0; x < 4; ++x) { reset() {
if(sd.fds[x] >= 0) { for (auto x = 0; x < 4; ++x) {
close(sd.fds[x]); if (sd.fds[x] >= 0) {
close(sd.fds[x]);
sd.fds[x] = -1; sd.fds[x] = -1;
}
} }
} }
}
surface_descriptor_t sd; surface_descriptor_t sd;
// Increment sequence when new rgb_t needs to be created // Increment sequence when new rgb_t needs to be created
std::uint64_t sequence; std::uint64_t sequence;
}; };
class sws_t { class sws_t {
public: public:
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_heigth, gl::tex_t &&tex); static std::optional<sws_t>
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_heigth); make(int in_width, int in_height, int out_width, int out_heigth, gl::tex_t &&tex);
static std::optional<sws_t>
make(int in_width, int in_height, int out_width, int out_heigth);
// Convert the loaded image into the first two framebuffers // Convert the loaded image into the first two framebuffers
int convert(gl::frame_buf_t &fb); int
convert(gl::frame_buf_t &fb);
// Make an area of the image black // Make an area of the image black
int blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height); int
blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height);
void load_ram(platf::img_t &img); void
void load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture); load_ram(platf::img_t &img);
void
load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture);
void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range); void
set_colorspace(std::uint32_t colorspace, std::uint32_t color_range);
// 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
gl::tex_t tex; gl::tex_t tex;
// The cursor image will be blended into this framebuffer // The cursor image will be blended into this framebuffer
gl::frame_buf_t cursor_framebuffer; gl::frame_buf_t cursor_framebuffer;
gl::frame_buf_t copy_framebuffer; gl::frame_buf_t copy_framebuffer;
// Y - shader, UV - shader, Cursor - shader // Y - shader, UV - shader, Cursor - shader
gl::program_t program[3]; gl::program_t program[3];
gl::buffer_t color_matrix; gl::buffer_t color_matrix;
int out_width, out_height; int out_width, out_height;
int in_width, in_height; int in_width, in_height;
int offsetX, offsetY; int offsetX, offsetY;
// Pointer to the texture to be converted to nv12 // Pointer to the texture to be converted to nv12
int loaded_texture; int loaded_texture;
// Store latest cursor for load_vram // Store latest cursor for load_vram
std::uint64_t serial; std::uint64_t serial;
}; };
bool fail(); bool
} // namespace egl fail();
} // namespace egl
#endif #endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@
#include "src/utility.h" #include "src/utility.h"
KITTY_USING_MOVE_T(file_t, int, -1, { KITTY_USING_MOVE_T(file_t, int, -1, {
if(el >= 0) { if (el >= 0) {
close(el); close(el);
} }
}); });
@ -21,11 +21,13 @@ enum class window_system_e {
extern window_system_e window_system; extern window_system_e window_system;
namespace dyn { namespace dyn {
typedef void (*apiproc)(void); typedef void (*apiproc)(void);
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true); int
void *handle(const std::vector<const char *> &libs); load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
void *
handle(const std::vector<const char *> &libs);
} // namespace dyn } // namespace dyn
#endif #endif

View file

@ -12,418 +12,426 @@ using namespace std::literals;
namespace avahi { namespace avahi {
/** Error codes used by avahi */ /** Error codes used by avahi */
enum err_e { enum err_e {
OK = 0, /**< OK */ OK = 0, /**< OK */
ERR_FAILURE = -1, /**< Generic error code */ ERR_FAILURE = -1, /**< Generic error code */
ERR_BAD_STATE = -2, /**< Object was in a bad state */ ERR_BAD_STATE = -2, /**< Object was in a bad state */
ERR_INVALID_HOST_NAME = -3, /**< Invalid host name */ ERR_INVALID_HOST_NAME = -3, /**< Invalid host name */
ERR_INVALID_DOMAIN_NAME = -4, /**< Invalid domain name */ ERR_INVALID_DOMAIN_NAME = -4, /**< Invalid domain name */
ERR_NO_NETWORK = -5, /**< No suitable network protocol available */ ERR_NO_NETWORK = -5, /**< No suitable network protocol available */
ERR_INVALID_TTL = -6, /**< Invalid DNS TTL */ ERR_INVALID_TTL = -6, /**< Invalid DNS TTL */
ERR_IS_PATTERN = -7, /**< RR key is pattern */ ERR_IS_PATTERN = -7, /**< RR key is pattern */
ERR_COLLISION = -8, /**< Name collision */ ERR_COLLISION = -8, /**< Name collision */
ERR_INVALID_RECORD = -9, /**< Invalid RR */ ERR_INVALID_RECORD = -9, /**< Invalid RR */
ERR_INVALID_SERVICE_NAME = -10, /**< Invalid service name */ ERR_INVALID_SERVICE_NAME = -10, /**< Invalid service name */
ERR_INVALID_SERVICE_TYPE = -11, /**< Invalid service type */ ERR_INVALID_SERVICE_TYPE = -11, /**< Invalid service type */
ERR_INVALID_PORT = -12, /**< Invalid port number */ ERR_INVALID_PORT = -12, /**< Invalid port number */
ERR_INVALID_KEY = -13, /**< Invalid key */ ERR_INVALID_KEY = -13, /**< Invalid key */
ERR_INVALID_ADDRESS = -14, /**< Invalid address */ ERR_INVALID_ADDRESS = -14, /**< Invalid address */
ERR_TIMEOUT = -15, /**< Timeout reached */ ERR_TIMEOUT = -15, /**< Timeout reached */
ERR_TOO_MANY_CLIENTS = -16, /**< Too many clients */ ERR_TOO_MANY_CLIENTS = -16, /**< Too many clients */
ERR_TOO_MANY_OBJECTS = -17, /**< Too many objects */ ERR_TOO_MANY_OBJECTS = -17, /**< Too many objects */
ERR_TOO_MANY_ENTRIES = -18, /**< Too many entries */ ERR_TOO_MANY_ENTRIES = -18, /**< Too many entries */
ERR_OS = -19, /**< OS error */ ERR_OS = -19, /**< OS error */
ERR_ACCESS_DENIED = -20, /**< Access denied */ ERR_ACCESS_DENIED = -20, /**< Access denied */
ERR_INVALID_OPERATION = -21, /**< Invalid operation */ ERR_INVALID_OPERATION = -21, /**< Invalid operation */
ERR_DBUS_ERROR = -22, /**< An unexpected D-Bus error occurred */ ERR_DBUS_ERROR = -22, /**< An unexpected D-Bus error occurred */
ERR_DISCONNECTED = -23, /**< Daemon connection failed */ ERR_DISCONNECTED = -23, /**< Daemon connection failed */
ERR_NO_MEMORY = -24, /**< Memory exhausted */ ERR_NO_MEMORY = -24, /**< Memory exhausted */
ERR_INVALID_OBJECT = -25, /**< The object passed to this function was invalid */ ERR_INVALID_OBJECT = -25, /**< The object passed to this function was invalid */
ERR_NO_DAEMON = -26, /**< Daemon not running */ ERR_NO_DAEMON = -26, /**< Daemon not running */
ERR_INVALID_INTERFACE = -27, /**< Invalid interface */ ERR_INVALID_INTERFACE = -27, /**< Invalid interface */
ERR_INVALID_PROTOCOL = -28, /**< Invalid protocol */ ERR_INVALID_PROTOCOL = -28, /**< Invalid protocol */
ERR_INVALID_FLAGS = -29, /**< Invalid flags */ ERR_INVALID_FLAGS = -29, /**< Invalid flags */
ERR_NOT_FOUND = -30, /**< Not found */ ERR_NOT_FOUND = -30, /**< Not found */
ERR_INVALID_CONFIG = -31, /**< Configuration error */ ERR_INVALID_CONFIG = -31, /**< Configuration error */
ERR_VERSION_MISMATCH = -32, /**< Verson mismatch */ ERR_VERSION_MISMATCH = -32, /**< Verson mismatch */
ERR_INVALID_SERVICE_SUBTYPE = -33, /**< Invalid service subtype */ ERR_INVALID_SERVICE_SUBTYPE = -33, /**< Invalid service subtype */
ERR_INVALID_PACKET = -34, /**< Invalid packet */ ERR_INVALID_PACKET = -34, /**< Invalid packet */
ERR_INVALID_DNS_ERROR = -35, /**< Invlaid DNS return code */ ERR_INVALID_DNS_ERROR = -35, /**< Invlaid DNS return code */
ERR_DNS_FORMERR = -36, /**< DNS Error: Form error */ ERR_DNS_FORMERR = -36, /**< DNS Error: Form error */
ERR_DNS_SERVFAIL = -37, /**< DNS Error: Server Failure */ ERR_DNS_SERVFAIL = -37, /**< DNS Error: Server Failure */
ERR_DNS_NXDOMAIN = -38, /**< DNS Error: No such domain */ ERR_DNS_NXDOMAIN = -38, /**< DNS Error: No such domain */
ERR_DNS_NOTIMP = -39, /**< DNS Error: Not implemented */ ERR_DNS_NOTIMP = -39, /**< DNS Error: Not implemented */
ERR_DNS_REFUSED = -40, /**< DNS Error: Operation refused */ ERR_DNS_REFUSED = -40, /**< DNS Error: Operation refused */
ERR_DNS_YXDOMAIN = -41, ERR_DNS_YXDOMAIN = -41,
ERR_DNS_YXRRSET = -42, ERR_DNS_YXRRSET = -42,
ERR_DNS_NXRRSET = -43, ERR_DNS_NXRRSET = -43,
ERR_DNS_NOTAUTH = -44, /**< DNS Error: Not authorized */ ERR_DNS_NOTAUTH = -44, /**< DNS Error: Not authorized */
ERR_DNS_NOTZONE = -45, ERR_DNS_NOTZONE = -45,
ERR_INVALID_RDATA = -46, /**< Invalid RDATA */ ERR_INVALID_RDATA = -46, /**< Invalid RDATA */
ERR_INVALID_DNS_CLASS = -47, /**< Invalid DNS class */ ERR_INVALID_DNS_CLASS = -47, /**< Invalid DNS class */
ERR_INVALID_DNS_TYPE = -48, /**< Invalid DNS type */ ERR_INVALID_DNS_TYPE = -48, /**< Invalid DNS type */
ERR_NOT_SUPPORTED = -49, /**< Not supported */ ERR_NOT_SUPPORTED = -49, /**< Not supported */
ERR_NOT_PERMITTED = -50, /**< Operation not permitted */ ERR_NOT_PERMITTED = -50, /**< Operation not permitted */
ERR_INVALID_ARGUMENT = -51, /**< Invalid argument */ ERR_INVALID_ARGUMENT = -51, /**< Invalid argument */
ERR_IS_EMPTY = -52, /**< Is empty */ ERR_IS_EMPTY = -52, /**< Is empty */
ERR_NO_CHANGE = -53, /**< The requested operation is invalid because it is redundant */ ERR_NO_CHANGE = -53, /**< The requested operation is invalid because it is redundant */
ERR_MAX = -54 ERR_MAX = -54
};
constexpr auto IF_UNSPEC = -1;
enum proto {
PROTO_INET = 0, /**< IPv4 */
PROTO_INET6 = 1, /**< IPv6 */
PROTO_UNSPEC = -1 /**< Unspecified/all protocol(s) */
};
enum ServerState {
SERVER_INVALID, /**< Invalid state (initial) */
SERVER_REGISTERING, /**< Host RRs are being registered */
SERVER_RUNNING, /**< All host RRs have been established */
SERVER_COLLISION, /**< There is a collision with a host RR. All host RRs have been withdrawn, the user should set a new host name via avahi_server_set_host_name() */
SERVER_FAILURE /**< Some fatal failure happened, the server is unable to proceed */
};
enum ClientState {
CLIENT_S_REGISTERING = SERVER_REGISTERING, /**< Server state: REGISTERING */
CLIENT_S_RUNNING = SERVER_RUNNING, /**< Server state: RUNNING */
CLIENT_S_COLLISION = SERVER_COLLISION, /**< Server state: COLLISION */
CLIENT_FAILURE = 100, /**< Some kind of error happened on the client side */
CLIENT_CONNECTING = 101 /**< We're still connecting. This state is only entered when AVAHI_CLIENT_NO_FAIL has been passed to avahi_client_new() and the daemon is not yet available. */
};
enum EntryGroupState {
ENTRY_GROUP_UNCOMMITED, /**< The group has not yet been commited, the user must still call avahi_entry_group_commit() */
ENTRY_GROUP_REGISTERING, /**< The entries of the group are currently being registered */
ENTRY_GROUP_ESTABLISHED, /**< The entries have successfully been established */
ENTRY_GROUP_COLLISION, /**< A name collision for one of the entries in the group has been detected, the entries have been withdrawn */
ENTRY_GROUP_FAILURE /**< Some kind of failure happened, the entries have been withdrawn */
};
enum ClientFlags {
CLIENT_IGNORE_USER_CONFIG = 1, /**< Don't read user configuration */
CLIENT_NO_FAIL = 2 /**< Don't fail if the daemon is not available when avahi_client_new() is called, instead enter CLIENT_CONNECTING state and wait for the daemon to appear */
};
/** Some flags for publishing functions */
enum PublishFlags {
PUBLISH_UNIQUE = 1, /**< For raw records: The RRset is intended to be unique */
PUBLISH_NO_PROBE = 2, /**< For raw records: Though the RRset is intended to be unique no probes shall be sent */
PUBLISH_NO_ANNOUNCE = 4, /**< For raw records: Do not announce this RR to other hosts */
PUBLISH_ALLOW_MULTIPLE = 8, /**< For raw records: Allow multiple local records of this type, even if they are intended to be unique */
/** \cond fulldocs */
PUBLISH_NO_REVERSE = 16, /**< For address records: don't create a reverse (PTR) entry */
PUBLISH_NO_COOKIE = 32, /**< For service records: do not implicitly add the local service cookie to TXT data */
/** \endcond */
PUBLISH_UPDATE = 64, /**< Update existing records instead of adding new ones */
/** \cond fulldocs */
PUBLISH_USE_WIDE_AREA = 128, /**< Register the record using wide area DNS (i.e. unicast DNS update) */
PUBLISH_USE_MULTICAST = 256 /**< Register the record using multicast DNS */
/** \endcond */
};
using IfIndex = int;
using Protocol = int;
struct EntryGroup;
struct Poll;
struct SimplePoll;
struct Client;
typedef void (*ClientCallback)(Client *, ClientState, void *userdata);
typedef void (*EntryGroupCallback)(EntryGroup *g, EntryGroupState state, void *userdata);
typedef void (*free_fn)(void *);
typedef Client *(*client_new_fn)(const Poll *poll_api, ClientFlags flags, ClientCallback callback, void *userdata, int *error);
typedef void (*client_free_fn)(Client *);
typedef char *(*alternative_service_name_fn)(char *);
typedef Client *(*entry_group_get_client_fn)(EntryGroup *);
typedef EntryGroup *(*entry_group_new_fn)(Client *, EntryGroupCallback, void *userdata);
typedef int (*entry_group_add_service_fn)(
EntryGroup *group,
IfIndex interface,
Protocol protocol,
PublishFlags flags,
const char *name,
const char *type,
const char *domain,
const char *host,
uint16_t port,
...);
typedef int (*entry_group_is_empty_fn)(EntryGroup *);
typedef int (*entry_group_reset_fn)(EntryGroup *);
typedef int (*entry_group_commit_fn)(EntryGroup *);
typedef char *(*strdup_fn)(const char *);
typedef char *(*strerror_fn)(int);
typedef int (*client_errno_fn)(Client *);
typedef Poll *(*simple_poll_get_fn)(SimplePoll *);
typedef int (*simple_poll_loop_fn)(SimplePoll *);
typedef void (*simple_poll_quit_fn)(SimplePoll *);
typedef SimplePoll *(*simple_poll_new_fn)();
typedef void (*simple_poll_free_fn)(SimplePoll *);
free_fn free;
client_new_fn client_new;
client_free_fn client_free;
alternative_service_name_fn alternative_service_name;
entry_group_get_client_fn entry_group_get_client;
entry_group_new_fn entry_group_new;
entry_group_add_service_fn entry_group_add_service;
entry_group_is_empty_fn entry_group_is_empty;
entry_group_reset_fn entry_group_reset;
entry_group_commit_fn entry_group_commit;
strdup_fn strdup;
strerror_fn strerror;
client_errno_fn client_errno;
simple_poll_get_fn simple_poll_get;
simple_poll_loop_fn simple_poll_loop;
simple_poll_quit_fn simple_poll_quit;
simple_poll_new_fn simple_poll_new;
simple_poll_free_fn simple_poll_free;
int init_common() {
static void *handle { nullptr };
static bool funcs_loaded = false;
if(funcs_loaded) return 0;
if(!handle) {
handle = dyn::handle({ "libavahi-common.so.3", "libavahi-common.so" });
if(!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *)&alternative_service_name, "avahi_alternative_service_name" },
{ (dyn::apiproc *)&free, "avahi_free" },
{ (dyn::apiproc *)&strdup, "avahi_strdup" },
{ (dyn::apiproc *)&strerror, "avahi_strerror" },
{ (dyn::apiproc *)&simple_poll_get, "avahi_simple_poll_get" },
{ (dyn::apiproc *)&simple_poll_loop, "avahi_simple_poll_loop" },
{ (dyn::apiproc *)&simple_poll_quit, "avahi_simple_poll_quit" },
{ (dyn::apiproc *)&simple_poll_new, "avahi_simple_poll_new" },
{ (dyn::apiproc *)&simple_poll_free, "avahi_simple_poll_free" },
}; };
if(dyn::load(handle, funcs)) { constexpr auto IF_UNSPEC = -1;
return -1; enum proto {
} PROTO_INET = 0, /**< IPv4 */
PROTO_INET6 = 1, /**< IPv6 */
funcs_loaded = true; PROTO_UNSPEC = -1 /**< Unspecified/all protocol(s) */
return 0;
}
int init_client() {
if(init_common()) {
return -1;
}
static void *handle { nullptr };
static bool funcs_loaded = false;
if(funcs_loaded) return 0;
if(!handle) {
handle = dyn::handle({ "libavahi-client.so.3", "libavahi-client.so" });
if(!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *)&client_new, "avahi_client_new" },
{ (dyn::apiproc *)&client_free, "avahi_client_free" },
{ (dyn::apiproc *)&entry_group_get_client, "avahi_entry_group_get_client" },
{ (dyn::apiproc *)&entry_group_new, "avahi_entry_group_new" },
{ (dyn::apiproc *)&entry_group_add_service, "avahi_entry_group_add_service" },
{ (dyn::apiproc *)&entry_group_is_empty, "avahi_entry_group_is_empty" },
{ (dyn::apiproc *)&entry_group_reset, "avahi_entry_group_reset" },
{ (dyn::apiproc *)&entry_group_commit, "avahi_entry_group_commit" },
{ (dyn::apiproc *)&client_errno, "avahi_client_errno" },
}; };
if(dyn::load(handle, funcs)) { enum ServerState {
return -1; SERVER_INVALID, /**< Invalid state (initial) */
SERVER_REGISTERING, /**< Host RRs are being registered */
SERVER_RUNNING, /**< All host RRs have been established */
SERVER_COLLISION, /**< There is a collision with a host RR. All host RRs have been withdrawn, the user should set a new host name via avahi_server_set_host_name() */
SERVER_FAILURE /**< Some fatal failure happened, the server is unable to proceed */
};
enum ClientState {
CLIENT_S_REGISTERING = SERVER_REGISTERING, /**< Server state: REGISTERING */
CLIENT_S_RUNNING = SERVER_RUNNING, /**< Server state: RUNNING */
CLIENT_S_COLLISION = SERVER_COLLISION, /**< Server state: COLLISION */
CLIENT_FAILURE = 100, /**< Some kind of error happened on the client side */
CLIENT_CONNECTING = 101 /**< We're still connecting. This state is only entered when AVAHI_CLIENT_NO_FAIL has been passed to avahi_client_new() and the daemon is not yet available. */
};
enum EntryGroupState {
ENTRY_GROUP_UNCOMMITED, /**< The group has not yet been commited, the user must still call avahi_entry_group_commit() */
ENTRY_GROUP_REGISTERING, /**< The entries of the group are currently being registered */
ENTRY_GROUP_ESTABLISHED, /**< The entries have successfully been established */
ENTRY_GROUP_COLLISION, /**< A name collision for one of the entries in the group has been detected, the entries have been withdrawn */
ENTRY_GROUP_FAILURE /**< Some kind of failure happened, the entries have been withdrawn */
};
enum ClientFlags {
CLIENT_IGNORE_USER_CONFIG = 1, /**< Don't read user configuration */
CLIENT_NO_FAIL = 2 /**< Don't fail if the daemon is not available when avahi_client_new() is called, instead enter CLIENT_CONNECTING state and wait for the daemon to appear */
};
/** Some flags for publishing functions */
enum PublishFlags {
PUBLISH_UNIQUE = 1, /**< For raw records: The RRset is intended to be unique */
PUBLISH_NO_PROBE = 2, /**< For raw records: Though the RRset is intended to be unique no probes shall be sent */
PUBLISH_NO_ANNOUNCE = 4, /**< For raw records: Do not announce this RR to other hosts */
PUBLISH_ALLOW_MULTIPLE = 8, /**< For raw records: Allow multiple local records of this type, even if they are intended to be unique */
/** \cond fulldocs */
PUBLISH_NO_REVERSE = 16, /**< For address records: don't create a reverse (PTR) entry */
PUBLISH_NO_COOKIE = 32, /**< For service records: do not implicitly add the local service cookie to TXT data */
/** \endcond */
PUBLISH_UPDATE = 64, /**< Update existing records instead of adding new ones */
/** \cond fulldocs */
PUBLISH_USE_WIDE_AREA = 128, /**< Register the record using wide area DNS (i.e. unicast DNS update) */
PUBLISH_USE_MULTICAST = 256 /**< Register the record using multicast DNS */
/** \endcond */
};
using IfIndex = int;
using Protocol = int;
struct EntryGroup;
struct Poll;
struct SimplePoll;
struct Client;
typedef void (*ClientCallback)(Client *, ClientState, void *userdata);
typedef void (*EntryGroupCallback)(EntryGroup *g, EntryGroupState state, void *userdata);
typedef void (*free_fn)(void *);
typedef Client *(*client_new_fn)(const Poll *poll_api, ClientFlags flags, ClientCallback callback, void *userdata, int *error);
typedef void (*client_free_fn)(Client *);
typedef char *(*alternative_service_name_fn)(char *);
typedef Client *(*entry_group_get_client_fn)(EntryGroup *);
typedef EntryGroup *(*entry_group_new_fn)(Client *, EntryGroupCallback, void *userdata);
typedef int (*entry_group_add_service_fn)(
EntryGroup *group,
IfIndex interface,
Protocol protocol,
PublishFlags flags,
const char *name,
const char *type,
const char *domain,
const char *host,
uint16_t port,
...);
typedef int (*entry_group_is_empty_fn)(EntryGroup *);
typedef int (*entry_group_reset_fn)(EntryGroup *);
typedef int (*entry_group_commit_fn)(EntryGroup *);
typedef char *(*strdup_fn)(const char *);
typedef char *(*strerror_fn)(int);
typedef int (*client_errno_fn)(Client *);
typedef Poll *(*simple_poll_get_fn)(SimplePoll *);
typedef int (*simple_poll_loop_fn)(SimplePoll *);
typedef void (*simple_poll_quit_fn)(SimplePoll *);
typedef SimplePoll *(*simple_poll_new_fn)();
typedef void (*simple_poll_free_fn)(SimplePoll *);
free_fn free;
client_new_fn client_new;
client_free_fn client_free;
alternative_service_name_fn alternative_service_name;
entry_group_get_client_fn entry_group_get_client;
entry_group_new_fn entry_group_new;
entry_group_add_service_fn entry_group_add_service;
entry_group_is_empty_fn entry_group_is_empty;
entry_group_reset_fn entry_group_reset;
entry_group_commit_fn entry_group_commit;
strdup_fn strdup;
strerror_fn strerror;
client_errno_fn client_errno;
simple_poll_get_fn simple_poll_get;
simple_poll_loop_fn simple_poll_loop;
simple_poll_quit_fn simple_poll_quit;
simple_poll_new_fn simple_poll_new;
simple_poll_free_fn simple_poll_free;
int
init_common() {
static void *handle { nullptr };
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (!handle) {
handle = dyn::handle({ "libavahi-common.so.3", "libavahi-common.so" });
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &alternative_service_name, "avahi_alternative_service_name" },
{ (dyn::apiproc *) &free, "avahi_free" },
{ (dyn::apiproc *) &strdup, "avahi_strdup" },
{ (dyn::apiproc *) &strerror, "avahi_strerror" },
{ (dyn::apiproc *) &simple_poll_get, "avahi_simple_poll_get" },
{ (dyn::apiproc *) &simple_poll_loop, "avahi_simple_poll_loop" },
{ (dyn::apiproc *) &simple_poll_quit, "avahi_simple_poll_quit" },
{ (dyn::apiproc *) &simple_poll_new, "avahi_simple_poll_new" },
{ (dyn::apiproc *) &simple_poll_free, "avahi_simple_poll_free" },
};
if (dyn::load(handle, funcs)) {
return -1;
}
funcs_loaded = true;
return 0;
} }
funcs_loaded = true; int
return 0; init_client() {
} if (init_common()) {
} // namespace avahi return -1;
}
static void *handle { nullptr };
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (!handle) {
handle = dyn::handle({ "libavahi-client.so.3", "libavahi-client.so" });
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &client_new, "avahi_client_new" },
{ (dyn::apiproc *) &client_free, "avahi_client_free" },
{ (dyn::apiproc *) &entry_group_get_client, "avahi_entry_group_get_client" },
{ (dyn::apiproc *) &entry_group_new, "avahi_entry_group_new" },
{ (dyn::apiproc *) &entry_group_add_service, "avahi_entry_group_add_service" },
{ (dyn::apiproc *) &entry_group_is_empty, "avahi_entry_group_is_empty" },
{ (dyn::apiproc *) &entry_group_reset, "avahi_entry_group_reset" },
{ (dyn::apiproc *) &entry_group_commit, "avahi_entry_group_commit" },
{ (dyn::apiproc *) &client_errno, "avahi_client_errno" },
};
if (dyn::load(handle, funcs)) {
return -1;
}
funcs_loaded = true;
return 0;
}
} // namespace avahi
namespace platf::publish { namespace platf::publish {
template<class T> template <class T>
void free(T *p) { void
avahi::free(p); free(T *p) {
} avahi::free(p);
template<class T>
using ptr_t = util::safe_ptr<T, free<T>>;
using client_t = util::dyn_safe_ptr<avahi::Client, &avahi::client_free>;
using poll_t = util::dyn_safe_ptr<avahi::SimplePoll, &avahi::simple_poll_free>;
avahi::EntryGroup *group = nullptr;
poll_t poll;
client_t client;
ptr_t<char> name;
void create_services(avahi::Client *c);
void entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
group = g;
switch(state) {
case avahi::ENTRY_GROUP_ESTABLISHED:
BOOST_LOG(info) << "Avahi service " << name.get() << " successfully established.";
break;
case avahi::ENTRY_GROUP_COLLISION:
name.reset(avahi::alternative_service_name(name.get()));
BOOST_LOG(info) << "Avahi service name collision, renaming service to " << name.get();
create_services(avahi::entry_group_get_client(g));
break;
case avahi::ENTRY_GROUP_FAILURE:
BOOST_LOG(error) << "Avahi entry group failure: " << avahi::strerror(avahi::client_errno(avahi::entry_group_get_client(g)));
avahi::simple_poll_quit(poll.get());
break;
case avahi::ENTRY_GROUP_UNCOMMITED:
case avahi::ENTRY_GROUP_REGISTERING:;
} }
}
void create_services(avahi::Client *c) { template <class T>
int ret; using ptr_t = util::safe_ptr<T, free<T>>;
using client_t = util::dyn_safe_ptr<avahi::Client, &avahi::client_free>;
using poll_t = util::dyn_safe_ptr<avahi::SimplePoll, &avahi::simple_poll_free>;
auto fg = util::fail_guard([]() { avahi::EntryGroup *group = nullptr;
avahi::simple_poll_quit(poll.get());
});
if(!group) { poll_t poll;
if(!(group = avahi::entry_group_new(c, entry_group_callback, nullptr))) { client_t client;
BOOST_LOG(error) << "avahi::entry_group_new() failed: "sv << avahi::strerror(avahi::client_errno(c));
return; ptr_t<char> name;
void
create_services(avahi::Client *c);
void
entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
group = g;
switch (state) {
case avahi::ENTRY_GROUP_ESTABLISHED:
BOOST_LOG(info) << "Avahi service " << name.get() << " successfully established.";
break;
case avahi::ENTRY_GROUP_COLLISION:
name.reset(avahi::alternative_service_name(name.get()));
BOOST_LOG(info) << "Avahi service name collision, renaming service to " << name.get();
create_services(avahi::entry_group_get_client(g));
break;
case avahi::ENTRY_GROUP_FAILURE:
BOOST_LOG(error) << "Avahi entry group failure: " << avahi::strerror(avahi::client_errno(avahi::entry_group_get_client(g)));
avahi::simple_poll_quit(poll.get());
break;
case avahi::ENTRY_GROUP_UNCOMMITED:
case avahi::ENTRY_GROUP_REGISTERING:;
} }
} }
if(avahi::entry_group_is_empty(group)) { void
BOOST_LOG(info) << "Adding avahi service "sv << name.get(); create_services(avahi::Client *c) {
int ret;
ret = avahi::entry_group_add_service( auto fg = util::fail_guard([]() {
group, avahi::simple_poll_quit(poll.get());
avahi::IF_UNSPEC, avahi::PROTO_UNSPEC, });
avahi::PublishFlags(0),
name.get(),
SERVICE_TYPE,
nullptr, nullptr,
map_port(nvhttp::PORT_HTTP),
nullptr);
if(ret < 0) { if (!group) {
if(ret == avahi::ERR_COLLISION) { if (!(group = avahi::entry_group_new(c, entry_group_callback, nullptr))) {
// A service name collision with a local service happened. Let's pick a new name BOOST_LOG(error) << "avahi::entry_group_new() failed: "sv << avahi::strerror(avahi::client_errno(c));
name.reset(avahi::alternative_service_name(name.get())); return;
BOOST_LOG(info) << "Service name collision, renaming service to "sv << name.get(); }
}
avahi::entry_group_reset(group); if (avahi::entry_group_is_empty(group)) {
BOOST_LOG(info) << "Adding avahi service "sv << name.get();
create_services(c); ret = avahi::entry_group_add_service(
group,
avahi::IF_UNSPEC, avahi::PROTO_UNSPEC,
avahi::PublishFlags(0),
name.get(),
SERVICE_TYPE,
nullptr, nullptr,
map_port(nvhttp::PORT_HTTP),
nullptr);
fg.disable(); if (ret < 0) {
if (ret == avahi::ERR_COLLISION) {
// A service name collision with a local service happened. Let's pick a new name
name.reset(avahi::alternative_service_name(name.get()));
BOOST_LOG(info) << "Service name collision, renaming service to "sv << name.get();
avahi::entry_group_reset(group);
create_services(c);
fg.disable();
return;
}
BOOST_LOG(error) << "Failed to add "sv << SERVICE_TYPE << " service: "sv << avahi::strerror(ret);
return; return;
} }
BOOST_LOG(error) << "Failed to add "sv << SERVICE_TYPE << " service: "sv << avahi::strerror(ret); ret = avahi::entry_group_commit(group);
return; if (ret < 0) {
BOOST_LOG(error) << "Failed to commit entry group: "sv << avahi::strerror(ret);
return;
}
} }
ret = avahi::entry_group_commit(group); fg.disable();
if(ret < 0) { }
BOOST_LOG(error) << "Failed to commit entry group: "sv << avahi::strerror(ret);
return; void
client_callback(avahi::Client *c, avahi::ClientState state, void *) {
switch (state) {
case avahi::CLIENT_S_RUNNING:
create_services(c);
break;
case avahi::CLIENT_FAILURE:
BOOST_LOG(error) << "Client failure: "sv << avahi::strerror(avahi::client_errno(c));
avahi::simple_poll_quit(poll.get());
break;
case avahi::CLIENT_S_COLLISION:
case avahi::CLIENT_S_REGISTERING:
if (group)
avahi::entry_group_reset(group);
break;
case avahi::CLIENT_CONNECTING:;
} }
} }
fg.disable(); class deinit_t: public ::platf::deinit_t {
} public:
std::thread poll_thread;
void client_callback(avahi::Client *c, avahi::ClientState state, void *) { deinit_t(std::thread poll_thread):
switch(state) { poll_thread { std::move(poll_thread) } {}
case avahi::CLIENT_S_RUNNING:
create_services(c);
break;
case avahi::CLIENT_FAILURE:
BOOST_LOG(error) << "Client failure: "sv << avahi::strerror(avahi::client_errno(c));
avahi::simple_poll_quit(poll.get());
break;
case avahi::CLIENT_S_COLLISION:
case avahi::CLIENT_S_REGISTERING:
if(group)
avahi::entry_group_reset(group);
break;
case avahi::CLIENT_CONNECTING:;
}
}
class deinit_t : public ::platf::deinit_t { ~deinit_t() override {
public: if (avahi::simple_poll_quit && poll) {
std::thread poll_thread; avahi::simple_poll_quit(poll.get());
}
deinit_t(std::thread poll_thread) : poll_thread { std::move(poll_thread) } {} if (poll_thread.joinable()) {
poll_thread.join();
}
}
};
~deinit_t() override { [[nodiscard]] std::unique_ptr<::platf::deinit_t>
if(avahi::simple_poll_quit && poll) { start() {
avahi::simple_poll_quit(poll.get()); if (avahi::init_client()) {
return nullptr;
} }
if(poll_thread.joinable()) { int avhi_error;
poll_thread.join();
poll.reset(avahi::simple_poll_new());
if (!poll) {
BOOST_LOG(error) << "Failed to create simple poll object."sv;
return nullptr;
} }
name.reset(avahi::strdup(SERVICE_NAME));
client.reset(
avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error));
if (!client) {
BOOST_LOG(error) << "Failed to create client: "sv << avahi::strerror(avhi_error);
return nullptr;
}
return std::make_unique<deinit_t>(std::thread { avahi::simple_poll_loop, poll.get() });
} }
}; } // namespace platf::publish
[[nodiscard]] std::unique_ptr<::platf::deinit_t> start() {
if(avahi::init_client()) {
return nullptr;
}
int avhi_error;
poll.reset(avahi::simple_poll_new());
if(!poll) {
BOOST_LOG(error) << "Failed to create simple poll object."sv;
return nullptr;
}
name.reset(avahi::strdup(SERVICE_NAME));
client.reset(
avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error));
if(!client) {
BOOST_LOG(error) << "Failed to create client: "sv << avahi::strerror(avhi_error);
return nullptr;
}
return std::make_unique<deinit_t>(std::thread { avahi::simple_poll_loop, poll.get() });
}
} // namespace platf::publish

File diff suppressed because it is too large Load diff

View file

@ -5,23 +5,28 @@
#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
* Height --> Height of the image * Height --> Height of the image
* offset_x --> Horizontal offset of the image in the texture * offset_x --> Horizontal offset of the image in the texture
* 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::shared_ptr<platf::hwdevice_t> make_hwdevice(int width, int height, bool vram); std::shared_ptr<platf::hwdevice_t>
std::shared_ptr<platf::hwdevice_t> make_hwdevice(int width, int height, int offset_x, int offset_y, bool vram); make_hwdevice(int width, int height, bool vram);
std::shared_ptr<platf::hwdevice_t> make_hwdevice(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram); std::shared_ptr<platf::hwdevice_t>
make_hwdevice(int width, int height, int offset_x, int offset_y, bool vram);
std::shared_ptr<platf::hwdevice_t>
make_hwdevice(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 validate(int fd); bool
validate(int fd);
int init(); int
} // namespace va init();
} // namespace va
#endif #endif

View file

@ -21,45 +21,49 @@ using namespace std::literals;
namespace wl { 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 classCall(void *data, Params... params) -> decltype(((*reinterpret_cast<T *>(data)).*m)(params...)) { static auto
return ((*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...);
}
#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 display_t::init(const char *display_name) { int
if(!display_name) { display_t::init(const char *display_name) {
display_name = std::getenv("WAYLAND_DISPLAY"); if (!display_name) {
display_name = std::getenv("WAYLAND_DISPLAY");
}
if (!display_name) {
BOOST_LOG(error) << "Environment variable WAYLAND_DISPLAY has not been defined"sv;
return -1;
}
display_internal.reset(wl_display_connect(display_name));
if (!display_internal) {
BOOST_LOG(error) << "Couldn't connect to Wayland display: "sv << display_name;
return -1;
}
BOOST_LOG(info) << "Found display ["sv << display_name << ']';
return 0;
} }
if(!display_name) { void
BOOST_LOG(error) << "Environment variable WAYLAND_DISPLAY has not been defined"sv; display_t::roundtrip() {
return -1; wl_display_roundtrip(display_internal.get());
} }
display_internal.reset(wl_display_connect(display_name)); wl_registry *
if(!display_internal) { display_t::registry() {
BOOST_LOG(error) << "Couldn't connect to Wayland display: "sv << display_name; return wl_display_get_registry(display_internal.get());
return -1;
} }
BOOST_LOG(info) << "Found display ["sv << display_name << ']'; inline monitor_t::monitor_t(wl_output *output):
output { output }, listener {
return 0;
}
void display_t::roundtrip() {
wl_display_roundtrip(display_internal.get());
}
wl_registry *display_t::registry() {
return wl_display_get_registry(display_internal.get());
}
inline monitor_t::monitor_t(wl_output *output)
: output { output }, listener {
&CLASS_CALL(monitor_t, xdg_position), &CLASS_CALL(monitor_t, xdg_position),
&CLASS_CALL(monitor_t, xdg_size), &CLASS_CALL(monitor_t, xdg_size),
&CLASS_CALL(monitor_t, xdg_done), &CLASS_CALL(monitor_t, xdg_done),
@ -67,208 +71,226 @@ inline monitor_t::monitor_t(wl_output *output)
&CLASS_CALL(monitor_t, xdg_description) &CLASS_CALL(monitor_t, xdg_description)
} {} } {}
inline void monitor_t::xdg_name(zxdg_output_v1 *, const char *name) { inline void
this->name = name; monitor_t::xdg_name(zxdg_output_v1 *, const char *name) {
this->name = name;
BOOST_LOG(info) << "Name: "sv << this->name; BOOST_LOG(info) << "Name: "sv << this->name;
} }
void monitor_t::xdg_description(zxdg_output_v1 *, const char *description) { void
this->description = description; monitor_t::xdg_description(zxdg_output_v1 *, const char *description) {
this->description = description;
BOOST_LOG(info) << "Found monitor: "sv << this->description; BOOST_LOG(info) << "Found monitor: "sv << this->description;
} }
void monitor_t::xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y) { void
viewport.offset_x = x; monitor_t::xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y) {
viewport.offset_y = y; viewport.offset_x = x;
viewport.offset_y = y;
BOOST_LOG(info) << "Offset: "sv << x << 'x' << y; BOOST_LOG(info) << "Offset: "sv << x << 'x' << y;
} }
void monitor_t::xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height) { void
viewport.width = width; monitor_t::xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height) {
viewport.height = height; viewport.width = width;
viewport.height = height;
BOOST_LOG(info) << "Resolution: "sv << width << 'x' << height; BOOST_LOG(info) << "Resolution: "sv << width << 'x' << height;
} }
void monitor_t::xdg_done(zxdg_output_v1 *) { void
BOOST_LOG(info) << "All info about monitor ["sv << name << "] has been send"sv; monitor_t::xdg_done(zxdg_output_v1 *) {
} BOOST_LOG(info) << "All info about monitor ["sv << name << "] has been send"sv;
}
void monitor_t::listen(zxdg_output_manager_v1 *output_manager) { void
auto xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, output); monitor_t::listen(zxdg_output_manager_v1 *output_manager) {
zxdg_output_v1_add_listener(xdg_output, &listener, this); auto xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, output);
} zxdg_output_v1_add_listener(xdg_output, &listener, this);
}
interface_t::interface_t() noexcept interface_t::interface_t() noexcept
: output_manager { nullptr }, listener { :
output_manager { nullptr },
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 interface_t::listen(wl_registry *registry) { void
wl_registry_add_listener(registry, &listener, this); interface_t::listen(wl_registry *registry) {
} wl_registry_add_listener(registry, &listener, this);
void interface_t::add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version) {
BOOST_LOG(debug) << "Available interface: "sv << interface << '(' << id << ") version "sv << version;
if(!std::strcmp(interface, wl_output_interface.name)) {
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
monitors.emplace_back(
std::make_unique<monitor_t>(
(wl_output *)wl_registry_bind(registry, id, &wl_output_interface, version)));
} }
else if(!std::strcmp(interface, zxdg_output_manager_v1_interface.name)) {
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
output_manager = (zxdg_output_manager_v1 *)wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, version);
this->interface[XDG_OUTPUT] = true; void
interface_t::add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version) {
BOOST_LOG(debug) << "Available interface: "sv << interface << '(' << id << ") version "sv << version;
if (!std::strcmp(interface, wl_output_interface.name)) {
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
monitors.emplace_back(
std::make_unique<monitor_t>(
(wl_output *) wl_registry_bind(registry, id, &wl_output_interface, version)));
}
else if (!std::strcmp(interface, zxdg_output_manager_v1_interface.name)) {
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
output_manager = (zxdg_output_manager_v1 *) wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, version);
this->interface[XDG_OUTPUT] = true;
}
else if (!std::strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) {
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);
this->interface[WLR_EXPORT_DMABUF] = true;
}
} }
else if(!std::strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) {
BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version;
dmabuf_manager = (zwlr_export_dmabuf_manager_v1 *)wl_registry_bind(registry, id, &zwlr_export_dmabuf_manager_v1_interface, version);
this->interface[WLR_EXPORT_DMABUF] = true; void
interface_t::del_interface(wl_registry *registry, uint32_t id) {
BOOST_LOG(info) << "Delete: "sv << id;
} }
}
void interface_t::del_interface(wl_registry *registry, uint32_t id) { dmabuf_t::dmabuf_t():
BOOST_LOG(info) << "Delete: "sv << id; status { READY }, frames {}, current_frame { &frames[0] }, listener {
}
dmabuf_t::dmabuf_t()
: 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),
&CLASS_CALL(dmabuf_t, cancel) &CLASS_CALL(dmabuf_t, cancel)
} { } {
}
void dmabuf_t::listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor) {
auto frame = zwlr_export_dmabuf_manager_v1_capture_output(dmabuf_manager, blend_cursor, output);
zwlr_export_dmabuf_frame_v1_add_listener(frame, &listener, this);
status = WAITING;
}
dmabuf_t::~dmabuf_t() {
for(auto &frame : frames) {
frame.destroy();
} }
}
void dmabuf_t::frame( void
zwlr_export_dmabuf_frame_v1 *frame, dmabuf_t::listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor) {
std::uint32_t width, std::uint32_t height, auto frame = zwlr_export_dmabuf_manager_v1_capture_output(dmabuf_manager, blend_cursor, output);
std::uint32_t x, std::uint32_t y, zwlr_export_dmabuf_frame_v1_add_listener(frame, &listener, this);
std::uint32_t buffer_flags, std::uint32_t flags,
std::uint32_t format,
std::uint32_t high, std::uint32_t low,
std::uint32_t obj_count) {
auto next_frame = get_next_frame();
next_frame->sd.fourcc = format; status = WAITING;
next_frame->sd.width = width; }
next_frame->sd.height = height;
next_frame->sd.modifier = (((std::uint64_t)high) << 32) | low;
}
void dmabuf_t::object( dmabuf_t::~dmabuf_t() {
zwlr_export_dmabuf_frame_v1 *frame, for (auto &frame : frames) {
std::uint32_t index, frame.destroy();
std::int32_t fd,
std::uint32_t size,
std::uint32_t offset,
std::uint32_t stride,
std::uint32_t plane_index) {
auto next_frame = get_next_frame();
next_frame->sd.fds[plane_index] = fd;
next_frame->sd.pitches[plane_index] = stride;
next_frame->sd.offsets[plane_index] = offset;
}
void dmabuf_t::ready(
zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec) {
zwlr_export_dmabuf_frame_v1_destroy(frame);
current_frame->destroy();
current_frame = get_next_frame();
status = READY;
}
void dmabuf_t::cancel(
zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t reason) {
zwlr_export_dmabuf_frame_v1_destroy(frame);
auto next_frame = get_next_frame();
next_frame->destroy();
status = REINIT;
}
void frame_t::destroy() {
for(auto x = 0; x < 4; ++x) {
if(sd.fds[x] >= 0) {
close(sd.fds[x]);
sd.fds[x] = -1;
} }
} }
}
frame_t::frame_t() { void
// File descriptors aren't open dmabuf_t::frame(
std::fill_n(sd.fds, 4, -1); zwlr_export_dmabuf_frame_v1 *frame,
}; std::uint32_t width, std::uint32_t height,
std::uint32_t x, std::uint32_t y,
std::uint32_t buffer_flags, std::uint32_t flags,
std::uint32_t format,
std::uint32_t high, std::uint32_t low,
std::uint32_t obj_count) {
auto next_frame = get_next_frame();
std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name) { next_frame->sd.fourcc = format;
display_t display; next_frame->sd.width = width;
next_frame->sd.height = height;
if(display.init(display_name)) { next_frame->sd.modifier = (((std::uint64_t) high) << 32) | low;
return {};
} }
interface_t interface; void
interface.listen(display.registry()); dmabuf_t::object(
zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t index,
std::int32_t fd,
std::uint32_t size,
std::uint32_t offset,
std::uint32_t stride,
std::uint32_t plane_index) {
auto next_frame = get_next_frame();
display.roundtrip(); next_frame->sd.fds[plane_index] = fd;
next_frame->sd.pitches[plane_index] = stride;
if(!interface[interface_t::XDG_OUTPUT]) { next_frame->sd.offsets[plane_index] = offset;
BOOST_LOG(error) << "Missing Wayland wire XDG_OUTPUT"sv;
return {};
} }
for(auto &monitor : interface.monitors) { void
monitor->listen(interface.output_manager); dmabuf_t::ready(
zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec) {
zwlr_export_dmabuf_frame_v1_destroy(frame);
current_frame->destroy();
current_frame = get_next_frame();
status = READY;
} }
display.roundtrip(); void
dmabuf_t::cancel(
zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t reason) {
zwlr_export_dmabuf_frame_v1_destroy(frame);
return std::move(interface.monitors); auto next_frame = get_next_frame();
} next_frame->destroy();
static bool validate() { status = REINIT;
display_t display; }
return display.init() == 0; void
} frame_t::destroy() {
for (auto x = 0; x < 4; ++x) {
if (sd.fds[x] >= 0) {
close(sd.fds[x]);
int init() { sd.fds[x] = -1;
static bool validated = validate(); }
}
}
return !validated; frame_t::frame_t() {
} // File descriptors aren't open
std::fill_n(sd.fds, 4, -1);
};
} // namespace wl std::vector<std::unique_ptr<monitor_t>>
monitors(const char *display_name) {
display_t display;
if (display.init(display_name)) {
return {};
}
interface_t interface;
interface.listen(display.registry());
display.roundtrip();
if (!interface[interface_t::XDG_OUTPUT]) {
BOOST_LOG(error) << "Missing Wayland wire XDG_OUTPUT"sv;
return {};
}
for (auto &monitor : interface.monitors) {
monitor->listen(interface.output_manager);
}
display.roundtrip();
return std::move(interface.monitors);
}
static bool
validate() {
display_t display;
return display.init() == 0;
}
int
init() {
static bool validated = validate();
return !validated;
}
} // namespace wl
#pragma GCC diagnostic pop #pragma GCC diagnostic pop

View file

@ -4,8 +4,8 @@
#include <bitset> #include <bitset>
#ifdef SUNSHINE_BUILD_WAYLAND #ifdef SUNSHINE_BUILD_WAYLAND
#include <wlr-export-dmabuf-unstable-v1.h> #include <wlr-export-dmabuf-unstable-v1.h>
#include <xdg-output-unstable-v1.h> #include <xdg-output-unstable-v1.h>
#endif #endif
#include "graphics.h" #include "graphics.h"
@ -17,200 +17,234 @@
#ifdef SUNSHINE_BUILD_WAYLAND #ifdef SUNSHINE_BUILD_WAYLAND
namespace wl { namespace wl {
using display_internal_t = util::safe_ptr<wl_display, wl_display_disconnect>; using display_internal_t = util::safe_ptr<wl_display, wl_display_disconnect>;
class frame_t { class frame_t {
public: public:
frame_t(); frame_t();
egl::surface_descriptor_t sd; egl::surface_descriptor_t sd;
void destroy(); void
}; destroy();
class dmabuf_t {
public:
enum status_e {
WAITING,
READY,
REINIT,
}; };
dmabuf_t(dmabuf_t &&) = delete; class dmabuf_t {
dmabuf_t(const dmabuf_t &) = delete; public:
enum status_e {
WAITING,
READY,
REINIT,
};
dmabuf_t &operator=(const dmabuf_t &) = delete; dmabuf_t(dmabuf_t &&) = delete;
dmabuf_t &operator=(dmabuf_t &&) = delete; dmabuf_t(const dmabuf_t &) = delete;
dmabuf_t(); dmabuf_t &
operator=(const dmabuf_t &) = delete;
dmabuf_t &
operator=(dmabuf_t &&) = delete;
void listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor = false); dmabuf_t();
~dmabuf_t(); void
listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor = false);
void frame( ~dmabuf_t();
zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t width, std::uint32_t height,
std::uint32_t x, std::uint32_t y,
std::uint32_t buffer_flags, std::uint32_t flags,
std::uint32_t format,
std::uint32_t high, std::uint32_t low,
std::uint32_t obj_count);
void object( void
zwlr_export_dmabuf_frame_v1 *frame, frame(
std::uint32_t index, zwlr_export_dmabuf_frame_v1 *frame,
std::int32_t fd, std::uint32_t width, std::uint32_t height,
std::uint32_t size, std::uint32_t x, std::uint32_t y,
std::uint32_t offset, std::uint32_t buffer_flags, std::uint32_t flags,
std::uint32_t stride, std::uint32_t format,
std::uint32_t plane_index); std::uint32_t high, std::uint32_t low,
std::uint32_t obj_count);
void ready( void
zwlr_export_dmabuf_frame_v1 *frame, object(
std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec); zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t index,
std::int32_t fd,
std::uint32_t size,
std::uint32_t offset,
std::uint32_t stride,
std::uint32_t plane_index);
void cancel( void
zwlr_export_dmabuf_frame_v1 *frame, ready(
std::uint32_t reason); zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec);
inline frame_t *get_next_frame() { void
return current_frame == &frames[0] ? &frames[1] : &frames[0]; cancel(
} zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t reason);
status_e status; inline frame_t *
get_next_frame() {
return current_frame == &frames[0] ? &frames[1] : &frames[0];
}
std::array<frame_t, 2> frames; status_e status;
frame_t *current_frame;
zwlr_export_dmabuf_frame_v1_listener listener; std::array<frame_t, 2> frames;
}; frame_t *current_frame;
class monitor_t { zwlr_export_dmabuf_frame_v1_listener listener;
public:
monitor_t(monitor_t &&) = delete;
monitor_t(const monitor_t &) = delete;
monitor_t &operator=(const monitor_t &) = delete;
monitor_t &operator=(monitor_t &&) = delete;
monitor_t(wl_output *output);
void xdg_name(zxdg_output_v1 *, const char *name);
void xdg_description(zxdg_output_v1 *, const char *description);
void xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y);
void xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height);
void xdg_done(zxdg_output_v1 *);
void listen(zxdg_output_manager_v1 *output_manager);
wl_output *output;
std::string name;
std::string description;
platf::touch_port_t viewport;
zxdg_output_v1_listener listener;
};
class interface_t {
struct bind_t {
std::uint32_t id;
std::uint32_t version;
}; };
public: class monitor_t {
enum interface_e { public:
XDG_OUTPUT, monitor_t(monitor_t &&) = delete;
WLR_EXPORT_DMABUF, monitor_t(const monitor_t &) = delete;
MAX_INTERFACES,
monitor_t &
operator=(const monitor_t &) = delete;
monitor_t &
operator=(monitor_t &&) = delete;
monitor_t(wl_output *output);
void
xdg_name(zxdg_output_v1 *, const char *name);
void
xdg_description(zxdg_output_v1 *, const char *description);
void
xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y);
void
xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height);
void
xdg_done(zxdg_output_v1 *);
void
listen(zxdg_output_manager_v1 *output_manager);
wl_output *output;
std::string name;
std::string description;
platf::touch_port_t viewport;
zxdg_output_v1_listener listener;
}; };
interface_t(interface_t &&) = delete; class interface_t {
interface_t(const interface_t &) = delete; struct bind_t {
std::uint32_t id;
std::uint32_t version;
};
interface_t &operator=(const interface_t &) = delete; public:
interface_t &operator=(interface_t &&) = delete; enum interface_e {
XDG_OUTPUT,
WLR_EXPORT_DMABUF,
MAX_INTERFACES,
};
interface_t() noexcept; interface_t(interface_t &&) = delete;
interface_t(const interface_t &) = delete;
void listen(wl_registry *registry); interface_t &
operator=(const interface_t &) = delete;
interface_t &
operator=(interface_t &&) = delete;
std::vector<std::unique_ptr<monitor_t>> monitors; interface_t() noexcept;
zwlr_export_dmabuf_manager_v1 *dmabuf_manager; void
zxdg_output_manager_v1 *output_manager; listen(wl_registry *registry);
bool operator[](interface_e bit) const { std::vector<std::unique_ptr<monitor_t>> monitors;
return interface[bit];
}
private: zwlr_export_dmabuf_manager_v1 *dmabuf_manager;
void add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version); zxdg_output_manager_v1 *output_manager;
void del_interface(wl_registry *registry, uint32_t id);
std::bitset<MAX_INTERFACES> interface; bool
operator[](interface_e bit) const {
return interface[bit];
}
wl_registry_listener listener; private:
}; void
add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version);
void
del_interface(wl_registry *registry, uint32_t id);
class display_t { std::bitset<MAX_INTERFACES> interface;
public:
/** wl_registry_listener listener;
};
class display_t {
public:
/**
* Initialize display with display_name * Initialize display with display_name
* If display_name == nullptr -> display_name = std::getenv("WAYLAND_DISPLAY") * If display_name == nullptr -> display_name = std::getenv("WAYLAND_DISPLAY")
*/ */
int init(const char *display_name = nullptr); int
init(const char *display_name = nullptr);
// Roundtrip with Wayland connection // Roundtrip with Wayland connection
void roundtrip(); void
roundtrip();
// 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 *registry(); wl_registry *
registry();
inline display_internal_t::pointer get() { inline display_internal_t::pointer
return display_internal.get(); get() {
} return display_internal.get();
}
private: private:
display_internal_t display_internal; display_internal_t display_internal;
}; };
std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name = nullptr); std::vector<std::unique_ptr<monitor_t>>
monitors(const char *display_name = nullptr);
int init(); int
} // namespace wl init();
} // namespace wl
#else #else
struct wl_output; struct wl_output;
struct zxdg_output_manager_v1; struct zxdg_output_manager_v1;
namespace wl { namespace wl {
class monitor_t { class monitor_t {
public: public:
monitor_t(monitor_t &&) = delete; monitor_t(monitor_t &&) = delete;
monitor_t(const monitor_t &) = delete; monitor_t(const monitor_t &) = delete;
monitor_t &operator=(const monitor_t &) = delete; monitor_t &
monitor_t &operator=(monitor_t &&) = delete; operator=(const monitor_t &) = delete;
monitor_t &
operator=(monitor_t &&) = delete;
monitor_t(wl_output *output); monitor_t(wl_output *output);
void listen(zxdg_output_manager_v1 *output_manager); void
listen(zxdg_output_manager_v1 *output_manager);
wl_output *output; wl_output *output;
std::string name; std::string name;
std::string description; std::string description;
platf::touch_port_t viewport; platf::touch_port_t viewport;
}; };
inline std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name = nullptr) { return {}; } inline std::vector<std::unique_ptr<monitor_t>>
monitors(const char *display_name = nullptr) { return {}; }
inline int init() { return -1; } inline int
} // namespace wl init() { return -1; }
} // namespace wl
#endif #endif
#endif #endif

View file

@ -7,372 +7,386 @@
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;
struct img_t : public platf::img_t { struct img_t: public platf::img_t {
~img_t() override { ~img_t() override {
delete[] data; delete[] data;
data = nullptr; data = nullptr;
}
};
class wlr_t : public platf::display_t {
public:
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
delay = std::chrono::nanoseconds { 1s } / config.framerate;
mem_type = hwdevice_type;
if(display.init()) {
return -1;
} }
};
interface.listen(display.registry()); class wlr_t: public platf::display_t {
public:
int
init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
delay = std::chrono::nanoseconds { 1s } / config.framerate;
mem_type = hwdevice_type;
display.roundtrip(); if (display.init()) {
return -1;
if(!interface[wl::interface_t::XDG_OUTPUT]) {
BOOST_LOG(error) << "Missing Wayland wire for xdg_output"sv;
return -1;
}
if(!interface[wl::interface_t::WLR_EXPORT_DMABUF]) {
BOOST_LOG(error) << "Missing Wayland wire for wlr-export-dmabuf"sv;
return -1;
}
auto monitor = interface.monitors[0].get();
if(!display_name.empty()) {
auto streamedMonitor = util::from_view(display_name);
if(streamedMonitor >= 0 && streamedMonitor < interface.monitors.size()) {
monitor = interface.monitors[streamedMonitor].get();
} }
}
monitor->listen(interface.output_manager); interface.listen(display.registry());
display.roundtrip();
output = monitor->output;
offset_x = monitor->viewport.offset_x;
offset_y = monitor->viewport.offset_y;
width = monitor->viewport.width;
height = monitor->viewport.height;
this->env_width = ::wl::env_width;
this->env_height = ::wl::env_height;
BOOST_LOG(info) << "Selected monitor ["sv << monitor->description << "] for streaming"sv;
BOOST_LOG(debug) << "Offset: "sv << offset_x << 'x' << offset_y;
BOOST_LOG(debug) << "Resolution: "sv << width << 'x' << height;
BOOST_LOG(debug) << "Desktop Resolution: "sv << env_width << 'x' << env_height;
return 0;
}
int dummy_img(platf::img_t *img) override {
return 0;
}
inline platf::capture_e snapshot(platf::img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
auto to = std::chrono::steady_clock::now() + timeout;
dmabuf.listen(interface.dmabuf_manager, output, cursor);
do {
display.roundtrip(); display.roundtrip();
if(to < std::chrono::steady_clock::now()) { if (!interface[wl::interface_t::XDG_OUTPUT]) {
return platf::capture_e::timeout; BOOST_LOG(error) << "Missing Wayland wire for xdg_output"sv;
return -1;
} }
} while(dmabuf.status == dmabuf_t::WAITING);
auto current_frame = dmabuf.current_frame; if (!interface[wl::interface_t::WLR_EXPORT_DMABUF]) {
BOOST_LOG(error) << "Missing Wayland wire for wlr-export-dmabuf"sv;
return -1;
}
if( auto monitor = interface.monitors[0].get();
dmabuf.status == dmabuf_t::REINIT ||
current_frame->sd.width != width ||
current_frame->sd.height != height) {
return platf::capture_e::reinit; if (!display_name.empty()) {
auto streamedMonitor = util::from_view(display_name);
if (streamedMonitor >= 0 && streamedMonitor < interface.monitors.size()) {
monitor = interface.monitors[streamedMonitor].get();
}
}
monitor->listen(interface.output_manager);
display.roundtrip();
output = monitor->output;
offset_x = monitor->viewport.offset_x;
offset_y = monitor->viewport.offset_y;
width = monitor->viewport.width;
height = monitor->viewport.height;
this->env_width = ::wl::env_width;
this->env_height = ::wl::env_height;
BOOST_LOG(info) << "Selected monitor ["sv << monitor->description << "] for streaming"sv;
BOOST_LOG(debug) << "Offset: "sv << offset_x << 'x' << offset_y;
BOOST_LOG(debug) << "Resolution: "sv << width << 'x' << height;
BOOST_LOG(debug) << "Desktop Resolution: "sv << env_width << 'x' << env_height;
return 0;
} }
return platf::capture_e::ok; int
} dummy_img(platf::img_t *img) override {
return 0;
}
platf::mem_type_e mem_type; inline platf::capture_e
snapshot(platf::img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
auto to = std::chrono::steady_clock::now() + timeout;
std::chrono::nanoseconds delay; dmabuf.listen(interface.dmabuf_manager, output, cursor);
do {
display.roundtrip();
wl::display_t display; if (to < std::chrono::steady_clock::now()) {
interface_t interface; return platf::capture_e::timeout;
dmabuf_t dmabuf; }
} while (dmabuf.status == dmabuf_t::WAITING);
wl_output *output; auto current_frame = dmabuf.current_frame;
};
class wlr_ram_t : public wlr_t { if (
public: dmabuf.status == dmabuf_t::REINIT ||
platf::capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<platf::img_t> img, bool *cursor) override { current_frame->sd.width != width ||
auto next_frame = std::chrono::steady_clock::now(); current_frame->sd.height != height) {
return platf::capture_e::reinit;
while(img) {
auto now = std::chrono::steady_clock::now();
if(next_frame > now) {
std::this_thread::sleep_for((next_frame - now) / 3 * 2);
} }
while(next_frame > now) {
now = std::chrono::steady_clock::now();
}
next_frame = now + delay;
auto status = snapshot(img.get(), 1000ms, *cursor); return platf::capture_e::ok;
switch(status) { }
case platf::capture_e::reinit:
case platf::capture_e::error: platf::mem_type_e mem_type;
return status;
case platf::capture_e::timeout: std::chrono::nanoseconds delay;
img = snapshot_cb(img, false);
break; wl::display_t display;
case platf::capture_e::ok: interface_t interface;
img = snapshot_cb(img, true); dmabuf_t dmabuf;
break;
default: wl_output *output;
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; };
class wlr_ram_t: public wlr_t {
public:
platf::capture_e
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<platf::img_t> img, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
while (img) {
auto now = std::chrono::steady_clock::now();
if (next_frame > now) {
std::this_thread::sleep_for((next_frame - now) / 3 * 2);
}
while (next_frame > now) {
now = std::chrono::steady_clock::now();
}
next_frame = now + delay;
auto status = snapshot(img.get(), 1000ms, *cursor);
switch (status) {
case platf::capture_e::reinit:
case platf::capture_e::error:
return status;
case platf::capture_e::timeout:
img = snapshot_cb(img, false);
break;
case platf::capture_e::ok:
img = snapshot_cb(img, true);
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
return status;
}
}
return platf::capture_e::ok;
}
platf::capture_e
snapshot(platf::img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
auto status = wlr_t::snapshot(img_out_base, timeout, cursor);
if (status != platf::capture_e::ok) {
return status; return status;
} }
}
return platf::capture_e::ok; auto current_frame = dmabuf.current_frame;
}
platf::capture_e snapshot(platf::img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) { auto rgb_opt = egl::import_source(egl_display.get(), current_frame->sd);
auto status = wlr_t::snapshot(img_out_base, timeout, cursor);
if(status != platf::capture_e::ok) {
return status;
}
auto current_frame = dmabuf.current_frame; if (!rgb_opt) {
return platf::capture_e::reinit;
auto rgb_opt = egl::import_source(egl_display.get(), current_frame->sd);
if(!rgb_opt) {
return platf::capture_e::reinit;
}
gl::ctx.BindTexture(GL_TEXTURE_2D, (*rgb_opt)->tex[0]);
int w, h;
gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
BOOST_LOG(debug) << "width and height: w "sv << w << " h "sv << h;
gl::ctx.GetTextureSubImage((*rgb_opt)->tex[0], 0, 0, 0, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->height * img_out_base->row_pitch, img_out_base->data);
gl::ctx.BindTexture(GL_TEXTURE_2D, 0);
return platf::capture_e::ok;
}
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
if(wlr_t::init(hwdevice_type, display_name, config)) {
return -1;
}
egl_display = egl::make_display(display.get());
if(!egl_display) {
return -1;
}
auto ctx_opt = egl::make_ctx(egl_display.get());
if(!ctx_opt) {
return -1;
}
ctx = std::move(*ctx_opt);
return 0;
}
std::shared_ptr<platf::hwdevice_t> make_hwdevice(platf::pix_fmt_e pix_fmt) override {
if(mem_type == platf::mem_type_e::vaapi) {
return va::make_hwdevice(width, height, false);
}
return std::make_shared<platf::hwdevice_t>();
}
std::shared_ptr<platf::img_t> alloc_img() override {
auto img = std::make_shared<img_t>();
img->width = width;
img->height = height;
img->pixel_pitch = 4;
img->row_pitch = img->pixel_pitch * width;
img->data = new std::uint8_t[height * img->row_pitch];
return img;
}
egl::display_t egl_display;
egl::ctx_t ctx;
};
class wlr_vram_t : public wlr_t {
public:
platf::capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<platf::img_t> img, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
while(img) {
auto now = std::chrono::steady_clock::now();
if(next_frame > now) {
std::this_thread::sleep_for((next_frame - now) / 3 * 2);
} }
while(next_frame > now) {
now = std::chrono::steady_clock::now();
}
next_frame = now + delay;
auto status = snapshot(img.get(), 1000ms, *cursor); gl::ctx.BindTexture(GL_TEXTURE_2D, (*rgb_opt)->tex[0]);
switch(status) {
case platf::capture_e::reinit: int w, h;
case platf::capture_e::error: gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
return status; gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
case platf::capture_e::timeout: BOOST_LOG(debug) << "width and height: w "sv << w << " h "sv << h;
img = snapshot_cb(img, false);
break; gl::ctx.GetTextureSubImage((*rgb_opt)->tex[0], 0, 0, 0, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->height * img_out_base->row_pitch, img_out_base->data);
case platf::capture_e::ok: gl::ctx.BindTexture(GL_TEXTURE_2D, 0);
img = snapshot_cb(img, true);
break; return platf::capture_e::ok;
default: }
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']';
int
init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
if (wlr_t::init(hwdevice_type, display_name, config)) {
return -1;
}
egl_display = egl::make_display(display.get());
if (!egl_display) {
return -1;
}
auto ctx_opt = egl::make_ctx(egl_display.get());
if (!ctx_opt) {
return -1;
}
ctx = std::move(*ctx_opt);
return 0;
}
std::shared_ptr<platf::hwdevice_t>
make_hwdevice(platf::pix_fmt_e pix_fmt) override {
if (mem_type == platf::mem_type_e::vaapi) {
return va::make_hwdevice(width, height, false);
}
return std::make_shared<platf::hwdevice_t>();
}
std::shared_ptr<platf::img_t>
alloc_img() override {
auto img = std::make_shared<img_t>();
img->width = width;
img->height = height;
img->pixel_pitch = 4;
img->row_pitch = img->pixel_pitch * width;
img->data = new std::uint8_t[height * img->row_pitch];
return img;
}
egl::display_t egl_display;
egl::ctx_t ctx;
};
class wlr_vram_t: public wlr_t {
public:
platf::capture_e
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<platf::img_t> img, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
while (img) {
auto now = std::chrono::steady_clock::now();
if (next_frame > now) {
std::this_thread::sleep_for((next_frame - now) / 3 * 2);
}
while (next_frame > now) {
now = std::chrono::steady_clock::now();
}
next_frame = now + delay;
auto status = snapshot(img.get(), 1000ms, *cursor);
switch (status) {
case platf::capture_e::reinit:
case platf::capture_e::error:
return status;
case platf::capture_e::timeout:
img = snapshot_cb(img, false);
break;
case platf::capture_e::ok:
img = snapshot_cb(img, true);
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
return status;
}
}
return platf::capture_e::ok;
}
platf::capture_e
snapshot(platf::img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
auto status = wlr_t::snapshot(img_out_base, timeout, cursor);
if (status != platf::capture_e::ok) {
return status; return status;
} }
auto img = (egl::img_descriptor_t *) img_out_base;
img->reset();
auto current_frame = dmabuf.current_frame;
++sequence;
img->sequence = sequence;
img->sd = current_frame->sd;
// Prevent dmabuf from closing the file descriptors.
std::fill_n(current_frame->sd.fds, 4, -1);
return platf::capture_e::ok;
} }
return platf::capture_e::ok; std::shared_ptr<platf::img_t>
} alloc_img() override {
auto img = std::make_shared<egl::img_descriptor_t>();
platf::capture_e snapshot(platf::img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) { img->sequence = 0;
auto status = wlr_t::snapshot(img_out_base, timeout, cursor); img->serial = std::numeric_limits<decltype(img->serial)>::max();
if(status != platf::capture_e::ok) { img->data = nullptr;
return status;
// File descriptors aren't open
std::fill_n(img->sd.fds, 4, -1);
return img;
} }
auto img = (egl::img_descriptor_t *)img_out_base; std::shared_ptr<platf::hwdevice_t>
img->reset(); make_hwdevice(platf::pix_fmt_e pix_fmt) override {
if (mem_type == platf::mem_type_e::vaapi) {
return va::make_hwdevice(width, height, 0, 0, true);
}
auto current_frame = dmabuf.current_frame; return std::make_shared<platf::hwdevice_t>();
++sequence;
img->sequence = sequence;
img->sd = current_frame->sd;
// Prevent dmabuf from closing the file descriptors.
std::fill_n(current_frame->sd.fds, 4, -1);
return platf::capture_e::ok;
}
std::shared_ptr<platf::img_t> alloc_img() override {
auto img = std::make_shared<egl::img_descriptor_t>();
img->sequence = 0;
img->serial = std::numeric_limits<decltype(img->serial)>::max();
img->data = nullptr;
// File descriptors aren't open
std::fill_n(img->sd.fds, 4, -1);
return img;
}
std::shared_ptr<platf::hwdevice_t> make_hwdevice(platf::pix_fmt_e pix_fmt) override {
if(mem_type == platf::mem_type_e::vaapi) {
return va::make_hwdevice(width, height, 0, 0, true);
} }
return std::make_shared<platf::hwdevice_t>(); int
} dummy_img(platf::img_t *img) override {
return snapshot(img, 1000ms, false) != platf::capture_e::ok;
}
int dummy_img(platf::img_t *img) override { std::uint64_t sequence {};
return snapshot(img, 1000ms, false) != platf::capture_e::ok; };
}
std::uint64_t sequence {}; } // namespace wl
};
} // namespace wl
namespace platf { namespace platf {
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { std::shared_ptr<display_t>
if(hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) { wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv; if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
return nullptr; BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
} return nullptr;
}
if(hwdevice_type == platf::mem_type_e::vaapi) { if (hwdevice_type == platf::mem_type_e::vaapi) {
auto wlr = std::make_shared<wl::wlr_vram_t>(); auto wlr = std::make_shared<wl::wlr_vram_t>();
if(wlr->init(hwdevice_type, display_name, config)) { if (wlr->init(hwdevice_type, display_name, config)) {
return nullptr;
}
return wlr;
}
auto wlr = std::make_shared<wl::wlr_ram_t>();
if (wlr->init(hwdevice_type, display_name, config)) {
return nullptr; return nullptr;
} }
return wlr; return wlr;
} }
auto wlr = std::make_shared<wl::wlr_ram_t>(); std::vector<std::string>
if(wlr->init(hwdevice_type, display_name, config)) { wl_display_names() {
return nullptr; std::vector<std::string> display_names;
wl::display_t display;
if (display.init()) {
return {};
}
wl::interface_t interface;
interface.listen(display.registry());
display.roundtrip();
if (!interface[wl::interface_t::XDG_OUTPUT]) {
BOOST_LOG(warning) << "Missing Wayland wire for xdg_output"sv;
return {};
}
if (!interface[wl::interface_t::WLR_EXPORT_DMABUF]) {
BOOST_LOG(warning) << "Missing Wayland wire for wlr-export-dmabuf"sv;
return {};
}
wl::env_width = 0;
wl::env_height = 0;
for (auto &monitor : interface.monitors) {
monitor->listen(interface.output_manager);
}
display.roundtrip();
for (int x = 0; x < interface.monitors.size(); ++x) {
auto monitor = interface.monitors[x].get();
wl::env_width = std::max(wl::env_width, (int) (monitor->viewport.offset_x + monitor->viewport.width));
wl::env_height = std::max(wl::env_height, (int) (monitor->viewport.offset_y + monitor->viewport.height));
display_names.emplace_back(std::to_string(x));
}
return display_names;
} }
return wlr; } // namespace platf
}
std::vector<std::string> wl_display_names() {
std::vector<std::string> display_names;
wl::display_t display;
if(display.init()) {
return {};
}
wl::interface_t interface;
interface.listen(display.registry());
display.roundtrip();
if(!interface[wl::interface_t::XDG_OUTPUT]) {
BOOST_LOG(warning) << "Missing Wayland wire for xdg_output"sv;
return {};
}
if(!interface[wl::interface_t::WLR_EXPORT_DMABUF]) {
BOOST_LOG(warning) << "Missing Wayland wire for wlr-export-dmabuf"sv;
return {};
}
wl::env_width = 0;
wl::env_height = 0;
for(auto &monitor : interface.monitors) {
monitor->listen(interface.output_manager);
}
display.roundtrip();
for(int x = 0; x < interface.monitors.size(); ++x) {
auto monitor = interface.monitors[x].get();
wl::env_width = std::max(wl::env_width, (int)(monitor->viewport.offset_x + monitor->viewport.width));
wl::env_height = std::max(wl::env_height, (int)(monitor->viewport.offset_y + monitor->viewport.height));
display_names.emplace_back(std::to_string(x));
}
return display_names;
}
} // namespace platf

File diff suppressed because it is too large Load diff

View file

@ -10,51 +10,61 @@
extern "C" struct _XDisplay; extern "C" struct _XDisplay;
namespace egl { namespace egl {
class cursor_t; class cursor_t;
} }
namespace platf::x11 { namespace platf::x11 {
#ifdef SUNSHINE_BUILD_X11 #ifdef SUNSHINE_BUILD_X11
struct cursor_ctx_raw_t; struct cursor_ctx_raw_t;
void freeCursorCtx(cursor_ctx_raw_t *ctx); void
void freeDisplay(_XDisplay *xdisplay); freeCursorCtx(cursor_ctx_raw_t *ctx);
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> make(); static std::optional<cursor_t>
make();
void capture(egl::cursor_t &img); void
capture(egl::cursor_t &img);
/** /**
* Capture and blend the cursor into the image * Capture and blend the cursor into the image
* *
* 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 blend(img_t &img, int offsetX, int offsetY); void
blend(img_t &img, int offsetX, int offsetY);
cursor_ctx_t ctx; cursor_ctx_t ctx;
}; };
xdisplay_t make_display(); xdisplay_t
make_display();
#else #else
// It's never something different from nullptr // It's never something different from nullptr
util::safe_ptr<_XDisplay, std::default_delete<_XDisplay>>; util::safe_ptr<_XDisplay, std::default_delete<_XDisplay>>;
class cursor_t { class cursor_t {
public: public:
static std::optional<cursor_t> make() { return std::nullopt; } static std::optional<cursor_t>
make() { return std::nullopt; }
void capture(egl::cursor_t &) {} void
void blend(img_t &, int, int) {} capture(egl::cursor_t &) {}
}; void
blend(img_t &, int, int) {}
};
xdisplay_t make_display() { return nullptr; } xdisplay_t
make_display() { return nullptr; }
#endif #endif
} // namespace platf::x11 } // namespace platf::x11
#endif #endif

View file

@ -7,14 +7,14 @@
#define kBufferLength 2048 #define kBufferLength 2048
@interface AVAudio : NSObject <AVCaptureAudioDataOutputSampleBufferDelegate> { @interface AVAudio: NSObject <AVCaptureAudioDataOutputSampleBufferDelegate> {
@public @public
TPCircularBuffer audioSampleBuffer; TPCircularBuffer audioSampleBuffer;
} }
@property(nonatomic, assign) AVCaptureSession *audioCaptureSession; @property (nonatomic, assign) AVCaptureSession *audioCaptureSession;
@property(nonatomic, assign) AVCaptureConnection *audioConnection; @property (nonatomic, assign) AVCaptureConnection *audioConnection;
@property(nonatomic, assign) NSCondition *samplesArrivedSignal; @property (nonatomic, assign) NSCondition *samplesArrivedSignal;
+ (NSArray *)microphoneNames; + (NSArray *)microphoneNames;
+ (AVCaptureDevice *)findMicrophone:(NSString *)name; + (AVCaptureDevice *)findMicrophone:(NSString *)name;
@ -23,4 +23,4 @@
@end @end
#endif //SUNSHINE_PLATFORM_AV_AUDIO_H #endif //SUNSHINE_PLATFORM_AV_AUDIO_H

View file

@ -3,8 +3,7 @@
@implementation AVAudio @implementation AVAudio
+ (NSArray<AVCaptureDevice *> *)microphones { + (NSArray<AVCaptureDevice *> *)microphones {
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) { 10, 15, 0 })]) {
if([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) { 10, 15, 0 })]) {
// This will generate a warning about AVCaptureDeviceDiscoverySession being // This will generate a warning about AVCaptureDeviceDiscoverySession being
// unavailable before macOS 10.15, but we have a guard to prevent it from // unavailable before macOS 10.15, but we have a guard to prevent it from
// being called on those earlier systems. // being called on those earlier systems.
@ -34,7 +33,7 @@
+ (NSArray<NSString *> *)microphoneNames { + (NSArray<NSString *> *)microphoneNames {
NSMutableArray *result = [[NSMutableArray alloc] init]; NSMutableArray *result = [[NSMutableArray alloc] init];
for(AVCaptureDevice *device in [AVAudio microphones]) { for (AVCaptureDevice *device in [AVAudio microphones]) {
[result addObject:[device localizedName]]; [result addObject:[device localizedName]];
} }
@ -42,8 +41,8 @@
} }
+ (AVCaptureDevice *)findMicrophone:(NSString *)name { + (AVCaptureDevice *)findMicrophone:(NSString *)name {
for(AVCaptureDevice *device in [AVAudio microphones]) { for (AVCaptureDevice *device in [AVAudio microphones]) {
if([[device localizedName] isEqualToString:name]) { if ([[device localizedName] isEqualToString:name]) {
return device; return device;
} }
} }
@ -66,11 +65,11 @@
NSError *error; NSError *error;
AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if(audioInput == nil) { if (audioInput == nil) {
return -1; return -1;
} }
if([self.audioCaptureSession canAddInput:audioInput]) { if ([self.audioCaptureSession canAddInput:audioInput]) {
[self.audioCaptureSession addInput:audioInput]; [self.audioCaptureSession addInput:audioInput];
} }
else { else {
@ -81,22 +80,22 @@
AVCaptureAudioDataOutput *audioOutput = [[AVCaptureAudioDataOutput alloc] init]; AVCaptureAudioDataOutput *audioOutput = [[AVCaptureAudioDataOutput alloc] init];
[audioOutput setAudioSettings:@{ [audioOutput setAudioSettings:@{
(NSString *)AVFormatIDKey: [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM], (NSString *) AVFormatIDKey: [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM],
(NSString *)AVSampleRateKey: [NSNumber numberWithUnsignedInt:sampleRate], (NSString *) AVSampleRateKey: [NSNumber numberWithUnsignedInt:sampleRate],
(NSString *)AVNumberOfChannelsKey: [NSNumber numberWithUnsignedInt:channels], (NSString *) AVNumberOfChannelsKey: [NSNumber numberWithUnsignedInt:channels],
(NSString *)AVLinearPCMBitDepthKey: [NSNumber numberWithUnsignedInt:16], (NSString *) AVLinearPCMBitDepthKey: [NSNumber numberWithUnsignedInt:16],
(NSString *)AVLinearPCMIsFloatKey: @NO, (NSString *) AVLinearPCMIsFloatKey: @NO,
(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, QOS_CLASS_USER_INITIATED,
DISPATCH_QUEUE_PRIORITY_HIGH); 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 {
@ -121,7 +120,7 @@
- (void)captureOutput:(AVCaptureOutput *)output - (void)captureOutput:(AVCaptureOutput *)output
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection { fromConnection:(AVCaptureConnection *)connection {
if(connection == self.audioConnection) { if (connection == self.audioConnection) {
AudioBufferList audioBufferList; AudioBufferList audioBufferList;
CMBlockBufferRef blockBuffer; CMBlockBufferRef blockBuffer;

View file

@ -7,12 +7,12 @@
#include <CoreVideo/CoreVideo.h> #include <CoreVideo/CoreVideo.h>
namespace platf { namespace platf {
struct av_img_t : public img_t { struct av_img_t: public img_t {
CVPixelBufferRef pixel_buffer = nullptr; CVPixelBufferRef pixel_buffer = nullptr;
CMSampleBufferRef sample_buffer = nullptr; CMSampleBufferRef sample_buffer = nullptr;
~av_img_t(); ~av_img_t();
}; };
} // namespace platf } // namespace platf
#endif /* av_img_t_h */ #endif /* av_img_t_h */

View file

@ -3,33 +3,32 @@
#import <AVFoundation/AVFoundation.h> #import <AVFoundation/AVFoundation.h>
struct CaptureSession { struct CaptureSession {
AVCaptureVideoDataOutput *output; AVCaptureVideoDataOutput *output;
NSCondition *captureStopped; NSCondition *captureStopped;
}; };
@interface AVVideo : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate> @interface AVVideo: NSObject <AVCaptureVideoDataOutputSampleBufferDelegate>
#define kMaxDisplays 32 #define kMaxDisplays 32
@property(nonatomic, assign) CGDirectDisplayID displayID; @property (nonatomic, assign) CGDirectDisplayID displayID;
@property(nonatomic, assign) CMTime minFrameDuration; @property (nonatomic, assign) CMTime minFrameDuration;
@property(nonatomic, assign) OSType pixelFormat; @property (nonatomic, assign) OSType pixelFormat;
@property(nonatomic, assign) int frameWidth; @property (nonatomic, assign) int frameWidth;
@property(nonatomic, assign) int frameHeight; @property (nonatomic, assign) int frameHeight;
@property(nonatomic, assign) float scaling; @property (nonatomic, assign) float scaling;
@property(nonatomic, assign) int paddingLeft; @property (nonatomic, assign) int paddingLeft;
@property(nonatomic, assign) int paddingRight; @property (nonatomic, assign) int paddingRight;
@property(nonatomic, assign) int paddingTop; @property (nonatomic, assign) int paddingTop;
@property(nonatomic, assign) int paddingBottom; @property (nonatomic, assign) int paddingBottom;
typedef bool (^FrameCallbackBlock)(CMSampleBufferRef); typedef bool (^FrameCallbackBlock)(CMSampleBufferRef);
@property(nonatomic, assign) AVCaptureSession *session; @property (nonatomic, assign) AVCaptureSession *session;
@property(nonatomic, assign) NSMapTable<AVCaptureConnection *, AVCaptureVideoDataOutput *> *videoOutputs; @property (nonatomic, assign) NSMapTable<AVCaptureConnection *, AVCaptureVideoDataOutput *> *videoOutputs;
@property(nonatomic, assign) NSMapTable<AVCaptureConnection *, FrameCallbackBlock> *captureCallbacks; @property (nonatomic, assign) NSMapTable<AVCaptureConnection *, FrameCallbackBlock> *captureCallbacks;
@property(nonatomic, assign) NSMapTable<AVCaptureConnection *, dispatch_semaphore_t> *captureSignals; @property (nonatomic, assign) NSMapTable<AVCaptureConnection *, dispatch_semaphore_t> *captureSignals;
+ (NSArray<NSDictionary *> *)displayNames; + (NSArray<NSDictionary *> *)displayNames;
@ -40,4 +39,4 @@ typedef bool (^FrameCallbackBlock)(CMSampleBufferRef);
@end @end
#endif //SUNSHINE_PLATFORM_AV_VIDEO_H #endif //SUNSHINE_PLATFORM_AV_VIDEO_H

View file

@ -10,13 +10,13 @@
+ (NSArray<NSDictionary *> *)displayNames { + (NSArray<NSDictionary *> *)displayNames {
CGDirectDisplayID displays[kMaxDisplays]; CGDirectDisplayID displays[kMaxDisplays];
uint32_t count; uint32_t count;
if(CGGetActiveDisplayList(kMaxDisplays, displays, &count) != kCGErrorSuccess) { if (CGGetActiveDisplayList(kMaxDisplays, displays, &count) != kCGErrorSuccess) {
return [NSArray array]; return [NSArray array];
} }
NSMutableArray *result = [NSMutableArray array]; NSMutableArray *result = [NSMutableArray array];
for(uint32_t i = 0; i < count; i++) { for (uint32_t i = 0; i < count; i++) {
[result addObject:@{ [result addObject:@{
@"id": [NSNumber numberWithUnsignedInt:displays[i]], @"id": [NSNumber numberWithUnsignedInt:displays[i]],
@"name": [NSString stringWithFormat:@"%d", displays[i]] @"name": [NSString stringWithFormat:@"%d", displays[i]]
@ -31,27 +31,27 @@
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayID); CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayID);
self.displayID = displayID; self.displayID = displayID;
self.pixelFormat = kCVPixelFormatType_32BGRA; self.pixelFormat = kCVPixelFormatType_32BGRA;
self.frameWidth = CGDisplayModeGetPixelWidth(mode); self.frameWidth = CGDisplayModeGetPixelWidth(mode);
self.frameHeight = CGDisplayModeGetPixelHeight(mode); self.frameHeight = CGDisplayModeGetPixelHeight(mode);
self.scaling = CGDisplayPixelsWide(displayID) / CGDisplayModeGetPixelWidth(mode); self.scaling = CGDisplayPixelsWide(displayID) / CGDisplayModeGetPixelWidth(mode);
self.paddingLeft = 0; self.paddingLeft = 0;
self.paddingRight = 0; self.paddingRight = 0;
self.paddingTop = 0; self.paddingTop = 0;
self.paddingBottom = 0; self.paddingBottom = 0;
self.minFrameDuration = CMTimeMake(1, frameRate); self.minFrameDuration = CMTimeMake(1, frameRate);
self.session = [[AVCaptureSession alloc] init]; self.session = [[AVCaptureSession alloc] init];
self.videoOutputs = [[NSMapTable alloc] init]; self.videoOutputs = [[NSMapTable alloc] init];
self.captureCallbacks = [[NSMapTable alloc] init]; self.captureCallbacks = [[NSMapTable alloc] init];
self.captureSignals = [[NSMapTable alloc] init]; self.captureSignals = [[NSMapTable alloc] init];
CFRelease(mode); CFRelease(mode);
AVCaptureScreenInput *screenInput = [[AVCaptureScreenInput alloc] initWithDisplayID:self.displayID]; AVCaptureScreenInput *screenInput = [[AVCaptureScreenInput alloc] initWithDisplayID:self.displayID];
[screenInput setMinFrameDuration:self.minFrameDuration]; [screenInput setMinFrameDuration:self.minFrameDuration];
if([self.session canAddInput:screenInput]) { if ([self.session canAddInput:screenInput]) {
[self.session addInput:screenInput]; [self.session addInput:screenInput];
} }
else { else {
@ -75,40 +75,40 @@
- (void)setFrameWidth:(int)frameWidth frameHeight:(int)frameHeight { - (void)setFrameWidth:(int)frameWidth frameHeight:(int)frameHeight {
CGImageRef screenshot = CGDisplayCreateImage(self.displayID); CGImageRef screenshot = CGDisplayCreateImage(self.displayID);
self.frameWidth = frameWidth; self.frameWidth = frameWidth;
self.frameHeight = frameHeight; self.frameHeight = frameHeight;
double screenRatio = (double)CGImageGetWidth(screenshot) / (double)CGImageGetHeight(screenshot); double screenRatio = (double) CGImageGetWidth(screenshot) / (double) CGImageGetHeight(screenshot);
double streamRatio = (double)frameWidth / (double)frameHeight; double streamRatio = (double) frameWidth / (double) frameHeight;
if(screenRatio < streamRatio) { if (screenRatio < streamRatio) {
int padding = frameWidth - (frameHeight * screenRatio); int padding = frameWidth - (frameHeight * screenRatio);
self.paddingLeft = padding / 2; self.paddingLeft = padding / 2;
self.paddingRight = padding - self.paddingLeft; self.paddingRight = padding - self.paddingLeft;
self.paddingTop = 0; self.paddingTop = 0;
self.paddingBottom = 0; self.paddingBottom = 0;
} }
else { else {
int padding = frameHeight - (frameWidth / screenRatio); int padding = frameHeight - (frameWidth / screenRatio);
self.paddingLeft = 0; self.paddingLeft = 0;
self.paddingRight = 0; self.paddingRight = 0;
self.paddingTop = padding / 2; self.paddingTop = padding / 2;
self.paddingBottom = padding - self.paddingTop; self.paddingBottom = padding - self.paddingTop;
} }
// XXX: if the streamed image is larger than the native resolution, we add a black box around // XXX: if the streamed image is larger than the native resolution, we add a black box around
// the frame. Instead the frame should be resized entirely. // the frame. Instead the frame should be resized entirely.
int delta_width = frameWidth - (CGImageGetWidth(screenshot) + self.paddingLeft + self.paddingRight); int delta_width = frameWidth - (CGImageGetWidth(screenshot) + self.paddingLeft + self.paddingRight);
if(delta_width > 0) { if (delta_width > 0) {
int adjust_left = delta_width / 2; int adjust_left = delta_width / 2;
int adjust_right = delta_width - adjust_left; int adjust_right = delta_width - adjust_left;
self.paddingLeft += adjust_left; self.paddingLeft += adjust_left;
self.paddingRight += adjust_right; self.paddingRight += adjust_right;
} }
int delta_height = frameHeight - (CGImageGetHeight(screenshot) + self.paddingTop + self.paddingBottom); int delta_height = frameHeight - (CGImageGetHeight(screenshot) + self.paddingTop + self.paddingBottom);
if(delta_height > 0) { if (delta_height > 0) {
int adjust_top = delta_height / 2; int adjust_top = delta_height / 2;
int adjust_bottom = delta_height - adjust_top; int adjust_bottom = delta_height - adjust_top;
self.paddingTop += adjust_top; self.paddingTop += adjust_top;
self.paddingBottom += adjust_bottom; self.paddingBottom += adjust_bottom;
@ -122,24 +122,24 @@
AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init]; AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init];
[videoOutput setVideoSettings:@{ [videoOutput setVideoSettings:@{
(NSString *)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithUnsignedInt:self.pixelFormat], (NSString *) kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithUnsignedInt:self.pixelFormat],
(NSString *)kCVPixelBufferWidthKey: [NSNumber numberWithInt:self.frameWidth], (NSString *) kCVPixelBufferWidthKey: [NSNumber numberWithInt:self.frameWidth],
(NSString *)kCVPixelBufferExtendedPixelsRightKey: [NSNumber numberWithInt:self.paddingRight], (NSString *) kCVPixelBufferExtendedPixelsRightKey: [NSNumber numberWithInt:self.paddingRight],
(NSString *)kCVPixelBufferExtendedPixelsLeftKey: [NSNumber numberWithInt:self.paddingLeft], (NSString *) kCVPixelBufferExtendedPixelsLeftKey: [NSNumber numberWithInt:self.paddingLeft],
(NSString *)kCVPixelBufferExtendedPixelsTopKey: [NSNumber numberWithInt:self.paddingTop], (NSString *) kCVPixelBufferExtendedPixelsTopKey: [NSNumber numberWithInt:self.paddingTop],
(NSString *)kCVPixelBufferExtendedPixelsBottomKey: [NSNumber numberWithInt:self.paddingBottom], (NSString *) kCVPixelBufferExtendedPixelsBottomKey: [NSNumber numberWithInt:self.paddingBottom],
(NSString *)kCVPixelBufferHeightKey: [NSNumber numberWithInt:self.frameHeight] (NSString *) kCVPixelBufferHeightKey: [NSNumber numberWithInt:self.frameHeight]
}]; }];
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, QOS_CLASS_USER_INITIATED,
DISPATCH_QUEUE_PRIORITY_HIGH); 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];
[self.session stopRunning]; [self.session stopRunning];
if([self.session canAddOutput:videoOutput]) { if ([self.session canAddOutput:videoOutput]) {
[self.session addOutput:videoOutput]; [self.session addOutput:videoOutput];
} }
else { else {
@ -148,7 +148,7 @@
} }
AVCaptureConnection *videoConnection = [videoOutput connectionWithMediaType:AVMediaTypeVideo]; AVCaptureConnection *videoConnection = [videoOutput connectionWithMediaType:AVMediaTypeVideo];
dispatch_semaphore_t signal = dispatch_semaphore_create(0); dispatch_semaphore_t signal = dispatch_semaphore_create(0);
[self.videoOutputs setObject:videoOutput forKey:videoConnection]; [self.videoOutputs setObject:videoOutput forKey:videoConnection];
[self.captureCallbacks setObject:frameCallback forKey:videoConnection]; [self.captureCallbacks setObject:frameCallback forKey:videoConnection];
@ -163,11 +163,10 @@
- (void)captureOutput:(AVCaptureOutput *)captureOutput - (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection { fromConnection:(AVCaptureConnection *)connection {
FrameCallbackBlock callback = [self.captureCallbacks objectForKey:connection]; FrameCallbackBlock callback = [self.captureCallbacks objectForKey:connection];
if(callback != nil) { if (callback != nil) {
if(!callback(sampleBuffer)) { if (!callback(sampleBuffer)) {
@synchronized(self) { @synchronized(self) {
[self.session stopRunning]; [self.session stopRunning];
[self.captureCallbacks removeObjectForKey:connection]; [self.captureCallbacks removeObjectForKey:connection];

View file

@ -14,189 +14,197 @@
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace platf { namespace platf {
using namespace std::literals; using namespace std::literals;
av_img_t::~av_img_t() { av_img_t::~av_img_t() {
if(pixel_buffer != NULL) { if (pixel_buffer != NULL) {
CVPixelBufferUnlockBaseAddress(pixel_buffer, 0); CVPixelBufferUnlockBaseAddress(pixel_buffer, 0);
}
if(sample_buffer != nullptr) {
CFRelease(sample_buffer);
}
data = nullptr;
}
struct av_display_t : public display_t {
AVVideo *av_capture;
CGDirectDisplayID display_id;
~av_display_t() {
[av_capture release];
}
capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override {
__block auto img_next = std::move(img);
auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) {
auto av_img_next = std::static_pointer_cast<av_img_t>(img_next);
CFRetain(sampleBuffer);
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
if(av_img_next->pixel_buffer != nullptr)
CVPixelBufferUnlockBaseAddress(av_img_next->pixel_buffer, 0);
if(av_img_next->sample_buffer != nullptr)
CFRelease(av_img_next->sample_buffer);
av_img_next->sample_buffer = sampleBuffer;
av_img_next->pixel_buffer = pixelBuffer;
img_next->data = (uint8_t *)CVPixelBufferGetBaseAddress(pixelBuffer);
size_t extraPixels[4];
CVPixelBufferGetExtendedPixels(pixelBuffer, &extraPixels[0], &extraPixels[1], &extraPixels[2], &extraPixels[3]);
img_next->width = CVPixelBufferGetWidth(pixelBuffer) + extraPixels[0] + extraPixels[1];
img_next->height = CVPixelBufferGetHeight(pixelBuffer) + extraPixels[2] + extraPixels[3];
img_next->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer);
img_next->pixel_pitch = img_next->row_pitch / img_next->width;
img_next = snapshot_cb(img_next, true);
return img_next != nullptr;
}];
// FIXME: We should time out if an image isn't returned for a while
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
return capture_e::ok;
}
std::shared_ptr<img_t> alloc_img() override {
return std::make_shared<av_img_t>();
}
std::shared_ptr<hwdevice_t> make_hwdevice(pix_fmt_e pix_fmt) override {
if(pix_fmt == pix_fmt_e::yuv420p) {
av_capture.pixelFormat = kCVPixelFormatType_32BGRA;
return std::make_shared<hwdevice_t>();
} }
else if(pix_fmt == pix_fmt_e::nv12) {
auto device = std::make_shared<nv12_zero_device>();
device->init(static_cast<void *>(av_capture), setResolution, setPixelFormat); if (sample_buffer != nullptr) {
CFRelease(sample_buffer);
}
return device; data = nullptr;
}
else {
BOOST_LOG(error) << "Unsupported Pixel Format."sv;
return nullptr;
}
} }
int dummy_img(img_t *img) override { struct av_display_t: public display_t {
auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) { AVVideo *av_capture;
auto av_img = (av_img_t *)img; CGDirectDisplayID display_id;
CFRetain(sampleBuffer); ~av_display_t() {
[av_capture release];
}
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); capture_e
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override {
__block auto img_next = std::move(img);
// XXX: next_img->img should be moved to a smart pointer with auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) {
// the CFRelease as custom deallocator auto av_img_next = std::static_pointer_cast<av_img_t>(img_next);
if(av_img->pixel_buffer != nullptr)
CVPixelBufferUnlockBaseAddress(((av_img_t *)img)->pixel_buffer, 0);
if(av_img->sample_buffer != nullptr) CFRetain(sampleBuffer);
CFRelease(av_img->sample_buffer);
av_img->sample_buffer = sampleBuffer; CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
av_img->pixel_buffer = pixelBuffer; CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
img->data = (uint8_t *)CVPixelBufferGetBaseAddress(pixelBuffer);
size_t extraPixels[4]; if (av_img_next->pixel_buffer != nullptr)
CVPixelBufferGetExtendedPixels(pixelBuffer, &extraPixels[0], &extraPixels[1], &extraPixels[2], &extraPixels[3]); CVPixelBufferUnlockBaseAddress(av_img_next->pixel_buffer, 0);
img->width = CVPixelBufferGetWidth(pixelBuffer) + extraPixels[0] + extraPixels[1]; if (av_img_next->sample_buffer != nullptr)
img->height = CVPixelBufferGetHeight(pixelBuffer) + extraPixels[2] + extraPixels[3]; CFRelease(av_img_next->sample_buffer);
img->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer);
img->pixel_pitch = img->row_pitch / img->width;
return false; av_img_next->sample_buffer = sampleBuffer;
}]; av_img_next->pixel_buffer = pixelBuffer;
img_next->data = (uint8_t *) CVPixelBufferGetBaseAddress(pixelBuffer);
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER); size_t extraPixels[4];
CVPixelBufferGetExtendedPixels(pixelBuffer, &extraPixels[0], &extraPixels[1], &extraPixels[2], &extraPixels[3]);
return 0; img_next->width = CVPixelBufferGetWidth(pixelBuffer) + extraPixels[0] + extraPixels[1];
} img_next->height = CVPixelBufferGetHeight(pixelBuffer) + extraPixels[2] + extraPixels[3];
img_next->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer);
img_next->pixel_pitch = img_next->row_pitch / img_next->width;
/** img_next = snapshot_cb(img_next, true);
return img_next != nullptr;
}];
// FIXME: We should time out if an image isn't returned for a while
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
return capture_e::ok;
}
std::shared_ptr<img_t>
alloc_img() override {
return std::make_shared<av_img_t>();
}
std::shared_ptr<hwdevice_t>
make_hwdevice(pix_fmt_e pix_fmt) override {
if (pix_fmt == pix_fmt_e::yuv420p) {
av_capture.pixelFormat = kCVPixelFormatType_32BGRA;
return std::make_shared<hwdevice_t>();
}
else if (pix_fmt == pix_fmt_e::nv12) {
auto device = std::make_shared<nv12_zero_device>();
device->init(static_cast<void *>(av_capture), setResolution, setPixelFormat);
return device;
}
else {
BOOST_LOG(error) << "Unsupported Pixel Format."sv;
return nullptr;
}
}
int
dummy_img(img_t *img) override {
auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) {
auto av_img = (av_img_t *) img;
CFRetain(sampleBuffer);
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
// XXX: next_img->img should be moved to a smart pointer with
// the CFRelease as custom deallocator
if (av_img->pixel_buffer != nullptr)
CVPixelBufferUnlockBaseAddress(((av_img_t *) img)->pixel_buffer, 0);
if (av_img->sample_buffer != nullptr)
CFRelease(av_img->sample_buffer);
av_img->sample_buffer = sampleBuffer;
av_img->pixel_buffer = pixelBuffer;
img->data = (uint8_t *) CVPixelBufferGetBaseAddress(pixelBuffer);
size_t extraPixels[4];
CVPixelBufferGetExtendedPixels(pixelBuffer, &extraPixels[0], &extraPixels[1], &extraPixels[2], &extraPixels[3]);
img->width = CVPixelBufferGetWidth(pixelBuffer) + extraPixels[0] + extraPixels[1];
img->height = CVPixelBufferGetHeight(pixelBuffer) + extraPixels[2] + extraPixels[3];
img->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer);
img->pixel_pitch = img->row_pitch / img->width;
return false;
}];
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
return 0;
}
/**
* A bridge from the pure C++ code of the hwdevice_t class to the pure Objective C code. * A bridge from the pure C++ code of the hwdevice_t class to the pure Objective C code.
* *
* display --> an opaque pointer to an object of this class * display --> an opaque pointer to an object of this class
* width --> the intended capture width * width --> the intended capture width
* height --> the intended capture height * height --> the intended capture height
*/ */
static void setResolution(void *display, int width, int height) { static void
[static_cast<AVVideo *>(display) setFrameWidth:width frameHeight:height]; setResolution(void *display, int width, int height) {
} [static_cast<AVVideo *>(display) setFrameWidth:width frameHeight:height];
}
static void setPixelFormat(void *display, OSType pixelFormat) { static void
static_cast<AVVideo *>(display).pixelFormat = pixelFormat; setPixelFormat(void *display, OSType pixelFormat) {
} static_cast<AVVideo *>(display).pixelFormat = pixelFormat;
}; }
};
std::shared_ptr<display_t> display(platf::mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { std::shared_ptr<display_t>
if(hwdevice_type != platf::mem_type_e::system) { display(platf::mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv; if (hwdevice_type != platf::mem_type_e::system) {
return nullptr; BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
} return nullptr;
}
auto display = std::make_shared<av_display_t>(); auto display = std::make_shared<av_display_t>();
display->display_id = CGMainDisplayID(); display->display_id = CGMainDisplayID();
if(!display_name.empty()) { if (!display_name.empty()) {
auto display_array = [AVVideo displayNames]; auto display_array = [AVVideo displayNames];
for(NSDictionary *item in display_array) { for (NSDictionary *item in display_array) {
NSString *name = item[@"name"]; NSString *name = item[@"name"];
if(name.UTF8String == display_name) { if (name.UTF8String == display_name) {
NSNumber *display_id = item[@"id"]; NSNumber *display_id = item[@"id"];
display->display_id = [display_id unsignedIntValue]; display->display_id = [display_id unsignedIntValue];
}
} }
} }
display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:config.framerate];
if (!display->av_capture) {
BOOST_LOG(error) << "Video setup failed."sv;
return nullptr;
}
display->width = display->av_capture.frameWidth;
display->height = display->av_capture.frameHeight;
return display;
} }
display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:config.framerate]; std::vector<std::string>
display_names(mem_type_e hwdevice_type) {
__block std::vector<std::string> display_names;
if(!display->av_capture) { auto display_array = [AVVideo displayNames];
BOOST_LOG(error) << "Video setup failed."sv;
return nullptr; display_names.reserve([display_array count]);
[display_array enumerateObjectsUsingBlock:^(NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
NSString *name = obj[@"name"];
display_names.push_back(name.UTF8String);
}];
return display_names;
} }
} // namespace platf
display->width = display->av_capture.frameWidth;
display->height = display->av_capture.frameHeight;
return display;
}
std::vector<std::string> display_names(mem_type_e hwdevice_type) {
__block std::vector<std::string> display_names;
auto display_array = [AVVideo displayNames];
display_names.reserve([display_array count]);
[display_array enumerateObjectsUsingBlock:^(NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
NSString *name = obj[@"name"];
display_names.push_back(name.UTF8String);
}];
return display_names;
}
} // namespace platf

View file

@ -11,36 +11,37 @@
#define MULTICLICK_DELAY_NS 500000000 #define MULTICLICK_DELAY_NS 500000000
namespace platf { namespace platf {
using namespace std::literals; using namespace std::literals;
struct macos_input_t { struct macos_input_t {
public: public:
CGDirectDisplayID display; CGDirectDisplayID display;
CGFloat displayScaling; CGFloat displayScaling;
CGEventSourceRef source; CGEventSourceRef source;
// keyboard related stuff // keyboard related stuff
CGEventRef kb_event; CGEventRef kb_event;
CGEventFlags kb_flags; CGEventFlags kb_flags;
// mouse related stuff // mouse related stuff
CGEventRef mouse_event; // mouse event source CGEventRef mouse_event; // mouse event source
bool mouse_down[3]; // mouse button status bool mouse_down[3]; // mouse button status
uint64_t last_mouse_event[3][2]; // timestamp of last mouse events uint64_t last_mouse_event[3][2]; // timestamp of last mouse events
}; };
// A struct to hold a Windows keycode to Mac virtual keycode mapping. // A struct to hold a Windows keycode to Mac virtual keycode mapping.
struct KeyCodeMap { struct KeyCodeMap {
int win_keycode; int win_keycode;
int mac_keycode; int mac_keycode;
}; };
// 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 operator<(const KeyCodeMap &a, const KeyCodeMap &b) { bool
return a.win_keycode < b.win_keycode; operator<(const KeyCodeMap &a, const KeyCodeMap &b) {
} return a.win_keycode < b.win_keycode;
}
// clang-format off // clang-format off
const KeyCodeMap kKeyCodesMap[] = { const KeyCodeMap kKeyCodesMap[] = {
{ 0x08 /* VKEY_BACK */, kVK_Delete }, { 0x08 /* VKEY_BACK */, kVK_Delete },
{ 0x09 /* VKEY_TAB */, kVK_Tab }, { 0x09 /* VKEY_TAB */, kVK_Tab },
@ -210,264 +211,281 @@ const KeyCodeMap kKeyCodesMap[] = {
{ 0xFD /* VKEY_PA1 */, -1 }, { 0xFD /* VKEY_PA1 */, -1 },
{ 0xFE /* VKEY_OEM_CLEAR */, kVK_ANSI_KeypadClear } { 0xFE /* VKEY_OEM_CLEAR */, kVK_ANSI_KeypadClear }
}; };
// clang-format on // clang-format on
int keysym(int keycode) { int
KeyCodeMap key_map; keysym(int keycode) {
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) {
return -1;
}
return temp_map->mac_keycode;
}
void
keyboard(input_t &input, uint16_t modcode, bool release) {
auto key = keysym(modcode);
BOOST_LOG(debug) << "got keycode: 0x"sv << std::hex << modcode << ", translated to: 0x" << std::hex << key << ", release:" << release;
if (key < 0) {
return;
}
auto macos_input = ((macos_input_t *) input.get());
auto event = macos_input->kb_event;
if (key == kVK_Shift || key == kVK_RightShift ||
key == kVK_Command || key == kVK_RightCommand ||
key == kVK_Option || key == kVK_RightOption ||
key == kVK_Control || key == kVK_RightControl) {
CGEventFlags mask;
switch (key) {
case kVK_Shift:
case kVK_RightShift:
mask = kCGEventFlagMaskShift;
break;
case kVK_Command:
case kVK_RightCommand:
mask = kCGEventFlagMaskCommand;
break;
case kVK_Option:
case kVK_RightOption:
mask = kCGEventFlagMaskAlternate;
break;
case kVK_Control:
case kVK_RightControl:
mask = kCGEventFlagMaskControl;
break;
}
macos_input->kb_flags = release ? macos_input->kb_flags & ~mask : macos_input->kb_flags | mask;
CGEventSetType(event, kCGEventFlagsChanged);
CGEventSetFlags(event, macos_input->kb_flags);
}
else {
CGEventSetIntegerValueField(event, kCGKeyboardEventKeycode, key);
CGEventSetType(event, release ? kCGEventKeyUp : kCGEventKeyDown);
}
CGEventPost(kCGHIDEventTap, event);
}
void
unicode(input_t &input, char *utf8, int size) {
BOOST_LOG(info) << "unicode: Unicode input not yet implemented for MacOS."sv;
}
int
alloc_gamepad(input_t &input, int nr, rumble_queue_t rumble_queue) {
BOOST_LOG(info) << "alloc_gamepad: Gamepad not yet implemented for MacOS."sv;
return -1; return -1;
} }
return temp_map->mac_keycode; void
} free_gamepad(input_t &input, int nr) {
BOOST_LOG(info) << "free_gamepad: Gamepad not yet implemented for MacOS."sv;
void keyboard(input_t &input, uint16_t modcode, bool release) {
auto key = keysym(modcode);
BOOST_LOG(debug) << "got keycode: 0x"sv << std::hex << modcode << ", translated to: 0x" << std::hex << key << ", release:" << release;
if(key < 0) {
return;
} }
auto macos_input = ((macos_input_t *)input.get()); void
auto event = macos_input->kb_event; gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
BOOST_LOG(info) << "gamepad: Gamepad not yet implemented for MacOS."sv;
}
if(key == kVK_Shift || key == kVK_RightShift || // returns current mouse location:
key == kVK_Command || key == kVK_RightCommand || inline CGPoint
key == kVK_Option || key == kVK_RightOption || get_mouse_loc(input_t &input) {
key == kVK_Control || key == kVK_RightControl) { return CGEventGetLocation(((macos_input_t *) input.get())->mouse_event);
}
CGEventFlags mask; void
post_mouse(input_t &input, CGMouseButton button, CGEventType type, CGPoint location, int click_count) {
BOOST_LOG(debug) << "mouse_event: "sv << button << ", type: "sv << type << ", location:"sv << location.x << ":"sv << location.y << " click_count: "sv << click_count;
switch(key) { auto macos_input = (macos_input_t *) input.get();
case kVK_Shift: auto display = macos_input->display;
case kVK_RightShift: auto event = macos_input->mouse_event;
mask = kCGEventFlagMaskShift;
break; if (location.x < 0)
case kVK_Command: location.x = 0;
case kVK_RightCommand: if (location.x >= CGDisplayPixelsWide(display))
mask = kCGEventFlagMaskCommand; location.x = CGDisplayPixelsWide(display) - 1;
break;
case kVK_Option: if (location.y < 0)
case kVK_RightOption: location.y = 0;
mask = kCGEventFlagMaskAlternate; if (location.y >= CGDisplayPixelsHigh(display))
break; location.y = CGDisplayPixelsHigh(display) - 1;
case kVK_Control:
case kVK_RightControl: CGEventSetType(event, type);
mask = kCGEventFlagMaskControl; CGEventSetLocation(event, location);
break; CGEventSetIntegerValueField(event, kCGMouseEventButtonNumber, button);
CGEventSetIntegerValueField(event, kCGMouseEventClickState, click_count);
CGEventPost(kCGHIDEventTap, event);
// For why this is here, see:
// https://stackoverflow.com/questions/15194409/simulated-mouseevent-not-working-properly-osx
CGWarpMouseCursorPosition(location);
}
inline CGEventType
event_type_mouse(input_t &input) {
auto macos_input = ((macos_input_t *) input.get());
if (macos_input->mouse_down[0]) {
return kCGEventLeftMouseDragged;
}
else if (macos_input->mouse_down[1]) {
return kCGEventOtherMouseDragged;
}
else if (macos_input->mouse_down[2]) {
return kCGEventRightMouseDragged;
}
else {
return kCGEventMouseMoved;
}
}
void
move_mouse(input_t &input, int deltaX, int deltaY) {
auto current = get_mouse_loc(input);
CGPoint location = CGPointMake(current.x + deltaX, current.y + deltaY);
post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, 0);
}
void
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
auto scaling = ((macos_input_t *) input.get())->displayScaling;
CGPoint location = CGPointMake(x * scaling, y * scaling);
post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, 0);
}
uint64_t
time_diff(uint64_t start) {
uint64_t elapsed;
Nanoseconds elapsedNano;
elapsed = mach_absolute_time() - start;
elapsedNano = AbsoluteToNanoseconds(*(AbsoluteTime *) &elapsed);
return *(uint64_t *) &elapsedNano;
}
void
button_mouse(input_t &input, int button, bool release) {
CGMouseButton mac_button;
CGEventType event;
auto mouse = ((macos_input_t *) input.get());
switch (button) {
case 1:
mac_button = kCGMouseButtonLeft;
event = release ? kCGEventLeftMouseUp : kCGEventLeftMouseDown;
break;
case 2:
mac_button = kCGMouseButtonCenter;
event = release ? kCGEventOtherMouseUp : kCGEventOtherMouseDown;
break;
case 3:
mac_button = kCGMouseButtonRight;
event = release ? kCGEventRightMouseUp : kCGEventRightMouseDown;
break;
default:
BOOST_LOG(warning) << "Unsupported mouse button for MacOS: "sv << button;
return;
} }
macos_input->kb_flags = release ? macos_input->kb_flags & ~mask : macos_input->kb_flags | mask; mouse->mouse_down[mac_button] = !release;
CGEventSetType(event, kCGEventFlagsChanged);
CGEventSetFlags(event, macos_input->kb_flags); // if the last mouse down was less than MULTICLICK_DELAY_NS, we send a double click event
} if (time_diff(mouse->last_mouse_event[mac_button][release]) < MULTICLICK_DELAY_NS) {
else { post_mouse(input, mac_button, event, get_mouse_loc(input), 2);
CGEventSetIntegerValueField(event, kCGKeyboardEventKeycode, key); }
CGEventSetType(event, release ? kCGEventKeyUp : kCGEventKeyDown); else {
post_mouse(input, mac_button, event, get_mouse_loc(input), 1);
}
mouse->last_mouse_event[mac_button][release] = mach_absolute_time();
} }
CGEventPost(kCGHIDEventTap, event); void
} scroll(input_t &input, int high_res_distance) {
CGEventRef upEvent = CGEventCreateScrollWheelEvent(
void unicode(input_t &input, char *utf8, int size) { NULL,
BOOST_LOG(info) << "unicode: Unicode input not yet implemented for MacOS."sv; kCGScrollEventUnitLine,
} 2, high_res_distance > 0 ? 1 : -1, high_res_distance);
CGEventPost(kCGHIDEventTap, upEvent);
int alloc_gamepad(input_t &input, int nr, rumble_queue_t rumble_queue) { CFRelease(upEvent);
BOOST_LOG(info) << "alloc_gamepad: Gamepad not yet implemented for MacOS."sv;
return -1;
}
void free_gamepad(input_t &input, int nr) {
BOOST_LOG(info) << "free_gamepad: Gamepad not yet implemented for MacOS."sv;
}
void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
BOOST_LOG(info) << "gamepad: Gamepad not yet implemented for MacOS."sv;
}
// returns current mouse location:
inline CGPoint get_mouse_loc(input_t &input) {
return CGEventGetLocation(((macos_input_t *)input.get())->mouse_event);
}
void post_mouse(input_t &input, CGMouseButton button, CGEventType type, CGPoint location, int click_count) {
BOOST_LOG(debug) << "mouse_event: "sv << button << ", type: "sv << type << ", location:"sv << location.x << ":"sv << location.y << " click_count: "sv << click_count;
auto macos_input = (macos_input_t *)input.get();
auto display = macos_input->display;
auto event = macos_input->mouse_event;
if(location.x < 0)
location.x = 0;
if(location.x >= CGDisplayPixelsWide(display))
location.x = CGDisplayPixelsWide(display) - 1;
if(location.y < 0)
location.y = 0;
if(location.y >= CGDisplayPixelsHigh(display))
location.y = CGDisplayPixelsHigh(display) - 1;
CGEventSetType(event, type);
CGEventSetLocation(event, location);
CGEventSetIntegerValueField(event, kCGMouseEventButtonNumber, button);
CGEventSetIntegerValueField(event, kCGMouseEventClickState, click_count);
CGEventPost(kCGHIDEventTap, event);
// For why this is here, see:
// https://stackoverflow.com/questions/15194409/simulated-mouseevent-not-working-properly-osx
CGWarpMouseCursorPosition(location);
}
inline CGEventType event_type_mouse(input_t &input) {
auto macos_input = ((macos_input_t *)input.get());
if(macos_input->mouse_down[0]) {
return kCGEventLeftMouseDragged;
}
else if(macos_input->mouse_down[1]) {
return kCGEventOtherMouseDragged;
}
else if(macos_input->mouse_down[2]) {
return kCGEventRightMouseDragged;
}
else {
return kCGEventMouseMoved;
}
}
void move_mouse(input_t &input, int deltaX, int deltaY) {
auto current = get_mouse_loc(input);
CGPoint location = CGPointMake(current.x + deltaX, current.y + deltaY);
post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, 0);
}
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
auto scaling = ((macos_input_t *)input.get())->displayScaling;
CGPoint location = CGPointMake(x * scaling, y * scaling);
post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, 0);
}
uint64_t time_diff(uint64_t start) {
uint64_t elapsed;
Nanoseconds elapsedNano;
elapsed = mach_absolute_time() - start;
elapsedNano = AbsoluteToNanoseconds(*(AbsoluteTime *)&elapsed);
return *(uint64_t *)&elapsedNano;
}
void button_mouse(input_t &input, int button, bool release) {
CGMouseButton mac_button;
CGEventType event;
auto mouse = ((macos_input_t *)input.get());
switch(button) {
case 1:
mac_button = kCGMouseButtonLeft;
event = release ? kCGEventLeftMouseUp : kCGEventLeftMouseDown;
break;
case 2:
mac_button = kCGMouseButtonCenter;
event = release ? kCGEventOtherMouseUp : kCGEventOtherMouseDown;
break;
case 3:
mac_button = kCGMouseButtonRight;
event = release ? kCGEventRightMouseUp : kCGEventRightMouseDown;
break;
default:
BOOST_LOG(warning) << "Unsupported mouse button for MacOS: "sv << button;
return;
} }
mouse->mouse_down[mac_button] = !release; void
hscroll(input_t &input, int high_res_distance) {
// if the last mouse down was less than MULTICLICK_DELAY_NS, we send a double click event // Unimplemented
if(time_diff(mouse->last_mouse_event[mac_button][release]) < MULTICLICK_DELAY_NS) {
post_mouse(input, mac_button, event, get_mouse_loc(input), 2);
}
else {
post_mouse(input, mac_button, event, get_mouse_loc(input), 1);
} }
mouse->last_mouse_event[mac_button][release] = mach_absolute_time(); input_t
} input() {
input_t result { new macos_input_t() };
void scroll(input_t &input, int high_res_distance) { auto macos_input = (macos_input_t *) result.get();
CGEventRef upEvent = CGEventCreateScrollWheelEvent(
NULL,
kCGScrollEventUnitLine,
2, high_res_distance > 0 ? 1 : -1, high_res_distance);
CGEventPost(kCGHIDEventTap, upEvent);
CFRelease(upEvent);
}
void hscroll(input_t &input, int high_res_distance) { // If we don't use the main display in the future, this has to be adapted
// Unimplemented macos_input->display = CGMainDisplayID();
}
input_t input() { // Input coordinates are based on the virtual resolution not the physical, so we need the scaling factor
input_t result { new macos_input_t() }; CGDisplayModeRef mode = CGDisplayCopyDisplayMode(macos_input->display);
macos_input->displayScaling = ((CGFloat) CGDisplayPixelsWide(macos_input->display)) / ((CGFloat) CGDisplayModeGetPixelWidth(mode));
CFRelease(mode);
auto macos_input = (macos_input_t *)result.get(); macos_input->source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
// If we don't use the main display in the future, this has to be adapted macos_input->kb_event = CGEventCreate(macos_input->source);
macos_input->display = CGMainDisplayID(); macos_input->kb_flags = 0;
// Input coordinates are based on the virtual resolution not the physical, so we need the scaling factor macos_input->mouse_event = CGEventCreate(macos_input->source);
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(macos_input->display); macos_input->mouse_down[0] = false;
macos_input->displayScaling = ((CGFloat)CGDisplayPixelsWide(macos_input->display)) / ((CGFloat)CGDisplayModeGetPixelWidth(mode)); macos_input->mouse_down[1] = false;
CFRelease(mode); macos_input->mouse_down[2] = false;
macos_input->last_mouse_event[0][0] = 0;
macos_input->last_mouse_event[0][1] = 0;
macos_input->last_mouse_event[1][0] = 0;
macos_input->last_mouse_event[1][1] = 0;
macos_input->last_mouse_event[2][0] = 0;
macos_input->last_mouse_event[2][1] = 0;
macos_input->source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); BOOST_LOG(debug) << "Display "sv << macos_input->display << ", pixel dimention: " << CGDisplayPixelsWide(macos_input->display) << "x"sv << CGDisplayPixelsHigh(macos_input->display);
macos_input->kb_event = CGEventCreate(macos_input->source); return result;
macos_input->kb_flags = 0; }
macos_input->mouse_event = CGEventCreate(macos_input->source); void
macos_input->mouse_down[0] = false; freeInput(void *p) {
macos_input->mouse_down[1] = false; auto *input = (macos_input_t *) p;
macos_input->mouse_down[2] = false;
macos_input->last_mouse_event[0][0] = 0;
macos_input->last_mouse_event[0][1] = 0;
macos_input->last_mouse_event[1][0] = 0;
macos_input->last_mouse_event[1][1] = 0;
macos_input->last_mouse_event[2][0] = 0;
macos_input->last_mouse_event[2][1] = 0;
BOOST_LOG(debug) << "Display "sv << macos_input->display << ", pixel dimention: " << CGDisplayPixelsWide(macos_input->display) << "x"sv << CGDisplayPixelsHigh(macos_input->display); CFRelease(input->source);
CFRelease(input->kb_event);
CFRelease(input->mouse_event);
return result; delete input;
} }
void freeInput(void *p) { std::vector<std::string_view> &
auto *input = (macos_input_t *)p; supported_gamepads() {
static std::vector<std::string_view> gamepads { ""sv };
CFRelease(input->source); return gamepads;
CFRelease(input->kb_event); }
CFRelease(input->mouse_event); } // namespace platf
delete input;
}
std::vector<std::string_view> &supported_gamepads() {
static std::vector<std::string_view> gamepads { ""sv };
return gamepads;
}
} // namespace platf

View file

@ -5,83 +5,88 @@
#include "src/main.h" #include "src/main.h"
namespace platf { namespace platf {
using namespace std::literals; using namespace std::literals;
struct av_mic_t : public mic_t { struct av_mic_t: public mic_t {
AVAudio *av_audio_capture; AVAudio *av_audio_capture;
~av_mic_t() { ~av_mic_t() {
[av_audio_capture release]; [av_audio_capture release];
}
capture_e sample(std::vector<std::int16_t> &sample_in) override {
auto sample_size = sample_in.size();
uint32_t length = 0;
void *byteSampleBuffer = TPCircularBufferTail(&av_audio_capture->audioSampleBuffer, &length);
while(length < sample_size * sizeof(std::int16_t)) {
[av_audio_capture.samplesArrivedSignal wait];
byteSampleBuffer = TPCircularBufferTail(&av_audio_capture->audioSampleBuffer, &length);
} }
const int16_t *sampleBuffer = (int16_t *)byteSampleBuffer; capture_e
std::vector<int16_t> vectorBuffer(sampleBuffer, sampleBuffer + sample_size); sample(std::vector<std::int16_t> &sample_in) override {
auto sample_size = sample_in.size();
std::copy_n(std::begin(vectorBuffer), sample_size, std::begin(sample_in)); uint32_t length = 0;
void *byteSampleBuffer = TPCircularBufferTail(&av_audio_capture->audioSampleBuffer, &length);
TPCircularBufferConsume(&av_audio_capture->audioSampleBuffer, sample_size * sizeof(std::int16_t)); while (length < sample_size * sizeof(std::int16_t)) {
[av_audio_capture.samplesArrivedSignal wait];
return capture_e::ok; byteSampleBuffer = TPCircularBufferTail(&av_audio_capture->audioSampleBuffer, &length);
}
};
struct macos_audio_control_t : public audio_control_t {
AVCaptureDevice *audio_capture_device;
public:
int set_sink(const std::string &sink) override {
BOOST_LOG(warning) << "audio_control_t::set_sink() unimplemented: "sv << sink;
return 0;
}
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
auto mic = std::make_unique<av_mic_t>();
const char *audio_sink = "";
if(!config::audio.sink.empty()) {
audio_sink = config::audio.sink.c_str();
}
if((audio_capture_device = [AVAudio findMicrophone:[NSString stringWithUTF8String:audio_sink]]) == nullptr) {
BOOST_LOG(error) << "opening microphone '"sv << audio_sink << "' failed. Please set a valid input source in the Sunshine config."sv;
BOOST_LOG(error) << "Available inputs:"sv;
for(NSString *name in [AVAudio microphoneNames]) {
BOOST_LOG(error) << "\t"sv << [name UTF8String];
} }
return nullptr; const int16_t *sampleBuffer = (int16_t *) byteSampleBuffer;
std::vector<int16_t> vectorBuffer(sampleBuffer, sampleBuffer + sample_size);
std::copy_n(std::begin(vectorBuffer), sample_size, std::begin(sample_in));
TPCircularBufferConsume(&av_audio_capture->audioSampleBuffer, sample_size * sizeof(std::int16_t));
return capture_e::ok;
}
};
struct macos_audio_control_t: public audio_control_t {
AVCaptureDevice *audio_capture_device;
public:
int
set_sink(const std::string &sink) override {
BOOST_LOG(warning) << "audio_control_t::set_sink() unimplemented: "sv << sink;
return 0;
} }
mic->av_audio_capture = [[AVAudio alloc] init]; std::unique_ptr<mic_t>
microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
auto mic = std::make_unique<av_mic_t>();
const char *audio_sink = "";
if([mic->av_audio_capture setupMicrophone:audio_capture_device sampleRate:sample_rate frameSize:frame_size channels:channels]) { if (!config::audio.sink.empty()) {
BOOST_LOG(error) << "Failed to setup microphone."sv; audio_sink = config::audio.sink.c_str();
return nullptr; }
if ((audio_capture_device = [AVAudio findMicrophone:[NSString stringWithUTF8String:audio_sink]]) == nullptr) {
BOOST_LOG(error) << "opening microphone '"sv << audio_sink << "' failed. Please set a valid input source in the Sunshine config."sv;
BOOST_LOG(error) << "Available inputs:"sv;
for (NSString *name in [AVAudio microphoneNames]) {
BOOST_LOG(error) << "\t"sv << [name UTF8String];
}
return nullptr;
}
mic->av_audio_capture = [[AVAudio alloc] init];
if ([mic->av_audio_capture setupMicrophone:audio_capture_device sampleRate:sample_rate frameSize:frame_size channels:channels]) {
BOOST_LOG(error) << "Failed to setup microphone."sv;
return nullptr;
}
return mic;
} }
return mic; std::optional<sink_t>
sink_info() override {
sink_t sink;
return sink;
}
};
std::unique_ptr<audio_control_t>
audio_control() {
return std::make_unique<macos_audio_control_t>();
} }
} // namespace platf
std::optional<sink_t> sink_info() override {
sink_t sink;
return sink;
}
};
std::unique_ptr<audio_control_t> audio_control() {
return std::make_unique<macos_audio_control_t>();
}
} // namespace platf

View file

@ -6,11 +6,13 @@
#include <CoreGraphics/CoreGraphics.h> #include <CoreGraphics/CoreGraphics.h>
namespace dyn { namespace dyn {
typedef void (*apiproc)(void); typedef void (*apiproc)(void);
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true); int
void *handle(const std::vector<const char *> &libs); load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
void *
handle(const std::vector<const char *> &libs);
} // namespace dyn } // namespace dyn
#endif #endif

View file

@ -20,229 +20,247 @@ namespace platf {
// Even though the following two functions are available starting in macOS 10.15, they weren't // Even though the following two functions are available starting in macOS 10.15, they weren't
// actually in the Mac SDK until Xcode 12.2, the first to include the SDK for macOS 11 // actually in the Mac SDK until Xcode 12.2, the first to include the SDK for macOS 11
#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 CGPreflightScreenCaptureAccess(void) __attribute__((weak_import)); extern "C" bool
extern "C" bool CGRequestScreenCaptureAccess(void) __attribute__((weak_import)); CGPreflightScreenCaptureAccess(void) __attribute__((weak_import));
extern "C" bool
CGRequestScreenCaptureAccess(void) __attribute__((weak_import));
#endif #endif
std::unique_ptr<deinit_t> init() { std::unique_ptr<deinit_t>
// This will generate a warning about CGPreflightScreenCaptureAccess and init() {
// CGRequestScreenCaptureAccess being unavailable before macOS 10.15, but // This will generate a warning about CGPreflightScreenCaptureAccess and
// we have a guard to prevent it from being called on those earlier systems. // CGRequestScreenCaptureAccess being unavailable before macOS 10.15, but
// Unfortunately the supported way to silence this warning, using @available, // we have a guard to prevent it from being called on those earlier systems.
// produces linker errors for __isPlatformVersionAtLeast, so we have to use // Unfortunately the supported way to silence this warning, using @available,
// a different method. // produces linker errors for __isPlatformVersionAtLeast, so we have to use
// We also ignore "tautological-pointer-compare" because when compiling with // a different method.
// Xcode 12.2 and later, these functions are not weakly linked and will never // We also ignore "tautological-pointer-compare" because when compiling with
// be null, and therefore generate this warning. Since we are weakly linking // Xcode 12.2 and later, these functions are not weakly linked and will never
// when compiling with earlier Xcode versions, the check for null is // be null, and therefore generate this warning. Since we are weakly linking
// necessary and so we ignore the warning. // when compiling with earlier Xcode versions, the check for null is
// necessary and so we ignore the warning.
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new" #pragma clang diagnostic ignored "-Wunguarded-availability-new"
#pragma clang diagnostic ignored "-Wtautological-pointer-compare" #pragma clang diagnostic ignored "-Wtautological-pointer-compare"
if([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) { 10, 15, 0 })] && if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) { 10, 15, 0 })] &&
// Double check that these weakly-linked symbols have been loaded: // Double check that these weakly-linked symbols have been loaded:
CGPreflightScreenCaptureAccess != nullptr && CGRequestScreenCaptureAccess != nullptr && CGPreflightScreenCaptureAccess != nullptr && CGRequestScreenCaptureAccess != nullptr &&
!CGPreflightScreenCaptureAccess()) { !CGPreflightScreenCaptureAccess()) {
BOOST_LOG(error) << "No screen capture permission!"sv; BOOST_LOG(error) << "No screen capture permission!"sv;
BOOST_LOG(error) << "Please activate it in 'System Preferences' -> 'Privacy' -> 'Screen Recording'"sv; BOOST_LOG(error) << "Please activate it in 'System Preferences' -> 'Privacy' -> 'Screen Recording'"sv;
CGRequestScreenCaptureAccess(); CGRequestScreenCaptureAccess();
return nullptr; return nullptr;
} }
#pragma clang diagnostic pop #pragma clang diagnostic pop
return std::make_unique<deinit_t>(); return std::make_unique<deinit_t>();
}
fs::path appdata() {
const char *homedir;
if((homedir = getenv("HOME")) == nullptr) {
homedir = getpwuid(geteuid())->pw_dir;
} }
return fs::path { homedir } / ".config/sunshine"sv; fs::path
} appdata() {
const char *homedir;
if ((homedir = getenv("HOME")) == nullptr) {
homedir = getpwuid(geteuid())->pw_dir;
}
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>; return fs::path { homedir } / ".config/sunshine"sv;
ifaddr_t get_ifaddrs() {
ifaddrs *p { nullptr };
getifaddrs(&p);
return ifaddr_t { p };
}
std::string from_sockaddr(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data,
INET6_ADDRSTRLEN);
} }
if(family == AF_INET) { using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data,
INET_ADDRSTRLEN); ifaddr_t
get_ifaddrs() {
ifaddrs *p { nullptr };
getifaddrs(&p);
return ifaddr_t { p };
} }
return std::string { data }; std::string
} from_sockaddr(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN];
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) { auto family = ip_addr->sa_family;
char data[INET6_ADDRSTRLEN]; if (family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data,
INET6_ADDRSTRLEN);
}
auto family = ip_addr->sa_family; if (family == AF_INET) {
std::uint16_t port; inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data,
if(family == AF_INET6) { INET_ADDRSTRLEN);
inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, }
INET6_ADDRSTRLEN);
port = ((sockaddr_in6 *)ip_addr)->sin6_port; return std::string { data };
} }
if(family == AF_INET) { std::pair<std::uint16_t, std::string>
inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, from_sockaddr_ex(const sockaddr *const ip_addr) {
INET_ADDRSTRLEN); char data[INET6_ADDRSTRLEN];
port = ((sockaddr_in *)ip_addr)->sin_port;
auto family = ip_addr->sa_family;
std::uint16_t port;
if (family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *) ip_addr)->sin6_addr, data,
INET6_ADDRSTRLEN);
port = ((sockaddr_in6 *) ip_addr)->sin6_port;
}
if (family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in *) ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
port = ((sockaddr_in *) ip_addr)->sin_port;
}
return { port, std::string { data } };
} }
return { port, std::string { data } }; std::string
} get_mac_address(const std::string_view &address) {
auto ifaddrs = get_ifaddrs();
std::string get_mac_address(const std::string_view &address) { for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
auto ifaddrs = get_ifaddrs(); if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) {
BOOST_LOG(verbose) << "Looking for MAC of "sv << pos->ifa_name;
for(auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { struct ifaddrs *ifap, *ifaptr;
if(pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) { unsigned char *ptr;
BOOST_LOG(verbose) << "Looking for MAC of "sv << pos->ifa_name; std::string mac_address;
struct ifaddrs *ifap, *ifaptr; if (getifaddrs(&ifap) == 0) {
unsigned char *ptr; for (ifaptr = ifap; ifaptr != NULL; ifaptr = (ifaptr)->ifa_next) {
std::string mac_address; if (!strcmp((ifaptr)->ifa_name, pos->ifa_name) && (((ifaptr)->ifa_addr)->sa_family == AF_LINK)) {
ptr = (unsigned char *) LLADDR((struct sockaddr_dl *) (ifaptr)->ifa_addr);
char buff[100];
if(getifaddrs(&ifap) == 0) { snprintf(buff, sizeof(buff), "%02x:%02x:%02x:%02x:%02x:%02x",
for(ifaptr = ifap; ifaptr != NULL; ifaptr = (ifaptr)->ifa_next) { *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4), *(ptr + 5));
if(!strcmp((ifaptr)->ifa_name, pos->ifa_name) && (((ifaptr)->ifa_addr)->sa_family == AF_LINK)) { mac_address = buff;
ptr = (unsigned char *)LLADDR((struct sockaddr_dl *)(ifaptr)->ifa_addr); break;
char buff[100]; }
}
snprintf(buff, sizeof(buff), "%02x:%02x:%02x:%02x:%02x:%02x", freeifaddrs(ifap);
*ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4), *(ptr + 5));
mac_address = buff; if (ifaptr != NULL) {
break; BOOST_LOG(verbose) << "Found MAC of "sv << pos->ifa_name << ": "sv << mac_address;
return mac_address;
} }
} }
}
}
freeifaddrs(ifap); BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
return "00:00:00:00:00:00"s;
}
if(ifaptr != NULL) { bp::child
BOOST_LOG(verbose) << "Found MAC of "sv << pos->ifa_name << ": "sv << mac_address; run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
return mac_address; BOOST_LOG(warning) << "run_unprivileged() is not yet implemented for this platform. The new process will run with Sunshine's permissions."sv;
} if (!group) {
if (!file) {
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
}
else {
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec);
}
}
else {
if (!file) {
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec, *group);
}
else {
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec, *group);
} }
} }
} }
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; void
return "00:00:00:00:00:00"s; adjust_thread_priority(thread_priority_e priority) {
} // Unimplemented
bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
BOOST_LOG(warning) << "run_unprivileged() is not yet implemented for this platform. The new process will run with Sunshine's permissions."sv;
if(!group) {
if(!file) {
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
}
else {
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec);
}
} }
else {
if(!file) { void
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec, *group); streaming_will_start() {
} // Nothing to do
else {
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec, *group);
}
} }
}
void adjust_thread_priority(thread_priority_e priority) { void
// Unimplemented streaming_will_stop() {
} // Nothing to do
}
void streaming_will_start() { bool
// Nothing to do restart_supported() {
} // Restart not supported yet
return false;
}
void streaming_will_stop() { bool
// Nothing to do restart() {
} // Restart not supported yet
return false;
}
bool restart_supported() { bool
// Restart not supported yet send_batch(batched_send_info_t &send_info) {
return false; // Fall back to unbatched send calls
} return false;
}
bool restart() { std::unique_ptr<deinit_t>
// Restart not supported yet enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) {
return false; // Unimplemented
} //
// NB: When implementing, remember to consider that some routes can drop DSCP-tagged packets completely!
return nullptr;
}
bool send_batch(batched_send_info_t &send_info) { } // namespace platf
// Fall back to unbatched send calls
return false;
}
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) {
// Unimplemented
//
// NB: When implementing, remember to consider that some routes can drop DSCP-tagged packets completely!
return nullptr;
}
} // namespace platf
namespace dyn { namespace dyn {
void *handle(const std::vector<const char *> &libs) { void *
void *handle; handle(const std::vector<const char *> &libs) {
void *handle;
for(auto lib : libs) { for (auto lib : libs) {
handle = dlopen(lib, RTLD_LAZY | RTLD_LOCAL); handle = dlopen(lib, RTLD_LAZY | RTLD_LOCAL);
if(handle) { if (handle) {
return handle; return handle;
}
} }
std::stringstream ss;
ss << "Couldn't find any of the following libraries: ["sv << libs.front();
std::for_each(std::begin(libs) + 1, std::end(libs), [&](auto lib) {
ss << ", "sv << lib;
});
ss << ']';
BOOST_LOG(error) << ss.str();
return nullptr;
} }
std::stringstream ss; int
ss << "Couldn't find any of the following libraries: ["sv << libs.front(); load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
std::for_each(std::begin(libs) + 1, std::end(libs), [&](auto lib) { int err = 0;
ss << ", "sv << lib; for (auto &func : funcs) {
}); TUPLE_2D_REF(fn, name, func);
ss << ']'; *fn = (void (*)()) dlsym(handle, name);
BOOST_LOG(error) << ss.str(); if (!*fn && strict) {
BOOST_LOG(error) << "Couldn't find function: "sv << name;
return nullptr; err = -1;
} }
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
int err = 0;
for(auto &func : funcs) {
TUPLE_2D_REF(fn, name, func);
*fn = (void (*)())dlsym(handle, name);
if(!*fn && strict) {
BOOST_LOG(error) << "Couldn't find function: "sv << name;
err = -1;
} }
}
return err; return err;
} }
} // namespace dyn } // namespace dyn

View file

@ -9,74 +9,79 @@ extern "C" {
namespace platf { namespace platf {
void free_frame(AVFrame *frame) { void
av_frame_free(&frame); free_frame(AVFrame *frame) {
} av_frame_free(&frame);
}
util::safe_ptr<AVFrame, free_frame> av_frame; util::safe_ptr<AVFrame, free_frame> av_frame;
int nv12_zero_device::convert(platf::img_t &img) { int
av_frame_make_writable(av_frame.get()); nv12_zero_device::convert(platf::img_t &img) {
av_frame_make_writable(av_frame.get());
av_img_t *av_img = (av_img_t *)&img; av_img_t *av_img = (av_img_t *) &img;
size_t left_pad, right_pad, top_pad, bottom_pad; size_t left_pad, right_pad, top_pad, bottom_pad;
CVPixelBufferGetExtendedPixels(av_img->pixel_buffer, &left_pad, &right_pad, &top_pad, &bottom_pad); CVPixelBufferGetExtendedPixels(av_img->pixel_buffer, &left_pad, &right_pad, &top_pad, &bottom_pad);
const uint8_t *data = (const uint8_t *)CVPixelBufferGetBaseAddressOfPlane(av_img->pixel_buffer, 0) - left_pad - (top_pad * img.width); const uint8_t *data = (const uint8_t *) CVPixelBufferGetBaseAddressOfPlane(av_img->pixel_buffer, 0) - left_pad - (top_pad * img.width);
int result = av_image_fill_arrays(av_frame->data, av_frame->linesize, data, (AVPixelFormat)av_frame->format, img.width, img.height, 32); int result = av_image_fill_arrays(av_frame->data, av_frame->linesize, data, (AVPixelFormat) av_frame->format, img.width, img.height, 32);
// We will create the black bars for the padding top/bottom or left/right here in very cheap way. // We will create the black bars for the padding top/bottom or left/right here in very cheap way.
// The luminance is 0, therefore, we simply need to set the chroma values to 128 for each pixel // The luminance is 0, therefore, we simply need to set the chroma values to 128 for each pixel
// for black bars (instead of green with chroma 0). However, this only works 100% correct, when // for black bars (instead of green with chroma 0). However, this only works 100% correct, when
// the resolution is devisable by 32. This could be improved by calculating the chroma values for // the resolution is devisable by 32. This could be improved by calculating the chroma values for
// the outer content pixels, which should introduce only a minor performance hit. // the outer content pixels, which should introduce only a minor performance hit.
// //
// XXX: Improve the algorithm to take into account the outer pixels // XXX: Improve the algorithm to take into account the outer pixels
size_t uv_plane_height = CVPixelBufferGetHeightOfPlane(av_img->pixel_buffer, 1); size_t uv_plane_height = CVPixelBufferGetHeightOfPlane(av_img->pixel_buffer, 1);
if(left_pad || right_pad) { if (left_pad || right_pad) {
for(int l = 0; l < uv_plane_height + (top_pad / 2); l++) { for (int l = 0; l < uv_plane_height + (top_pad / 2); l++) {
int line = l * av_frame->linesize[1]; int line = l * av_frame->linesize[1];
memset((void *)&av_frame->data[1][line], 128, (size_t)left_pad); memset((void *) &av_frame->data[1][line], 128, (size_t) left_pad);
memset((void *)&av_frame->data[1][line + img.width - right_pad], 128, right_pad); memset((void *) &av_frame->data[1][line + img.width - right_pad], 128, right_pad);
}
} }
if (top_pad || bottom_pad) {
memset((void *) &av_frame->data[1][0], 128, (top_pad / 2) * av_frame->linesize[1]);
memset((void *) &av_frame->data[1][((top_pad / 2) + uv_plane_height) * av_frame->linesize[1]], 128, bottom_pad / 2 * av_frame->linesize[1]);
}
return result > 0 ? 0 : -1;
} }
if(top_pad || bottom_pad) { int
memset((void *)&av_frame->data[1][0], 128, (top_pad / 2) * av_frame->linesize[1]); nv12_zero_device::set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
memset((void *)&av_frame->data[1][((top_pad / 2) + uv_plane_height) * av_frame->linesize[1]], 128, bottom_pad / 2 * av_frame->linesize[1]); this->frame = frame;
av_frame.reset(frame);
resolution_fn(this->display, frame->width, frame->height);
return 0;
} }
return result > 0 ? 0 : -1; void
} nv12_zero_device::set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) {
}
int nv12_zero_device::set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) { int
this->frame = frame; nv12_zero_device::init(void *display, resolution_fn_t resolution_fn, pixel_format_fn_t pixel_format_fn) {
pixel_format_fn(display, '420v');
av_frame.reset(frame); this->display = display;
this->resolution_fn = resolution_fn;
resolution_fn(this->display, frame->width, frame->height); // we never use this pointer but it's existence is checked/used
// by the platform independed code
data = this;
return 0; return 0;
} }
void nv12_zero_device::set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) { } // namespace platf
}
int nv12_zero_device::init(void *display, resolution_fn_t resolution_fn, pixel_format_fn_t pixel_format_fn) {
pixel_format_fn(display, '420v');
this->display = display;
this->resolution_fn = resolution_fn;
// we never use this pointer but it's existence is checked/used
// by the platform independed code
data = this;
return 0;
}
} // namespace platf

View file

@ -5,25 +5,29 @@
namespace platf { namespace platf {
class nv12_zero_device : public hwdevice_t { class nv12_zero_device: public hwdevice_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
// and FFMPEG collide, we need this opaque pointer and cannot use the definition // and FFMPEG collide, we need this opaque pointer and cannot use the definition
void *display; void *display;
public: public:
// this function is used to set the resolution on an av_video object that we cannot // this function is used to set the resolution on an av_video object that we cannot
// call directly because of namespace collisions between AVFoundation and FFMPEG // call directly because of namespace collisions between AVFoundation and FFMPEG
using resolution_fn_t = std::function<void(void *display, int width, int height)>; using resolution_fn_t = std::function<void(void *display, int width, int height)>;
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 init(void *display, resolution_fn_t resolution_fn, pixel_format_fn_t pixel_format_fn); int
init(void *display, resolution_fn_t resolution_fn, pixel_format_fn_t pixel_format_fn);
int convert(img_t &img); int
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx); convert(img_t &img);
void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range); int
}; set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx);
void
set_colorspace(std::uint32_t colorspace, std::uint32_t color_range);
};
} // namespace platf } // namespace platf
#endif /* vtdevice_h */ #endif /* vtdevice_h */

View file

@ -12,418 +12,426 @@ using namespace std::literals;
namespace avahi { namespace avahi {
/** Error codes used by avahi */ /** Error codes used by avahi */
enum err_e { enum err_e {
OK = 0, /**< OK */ OK = 0, /**< OK */
ERR_FAILURE = -1, /**< Generic error code */ ERR_FAILURE = -1, /**< Generic error code */
ERR_BAD_STATE = -2, /**< Object was in a bad state */ ERR_BAD_STATE = -2, /**< Object was in a bad state */
ERR_INVALID_HOST_NAME = -3, /**< Invalid host name */ ERR_INVALID_HOST_NAME = -3, /**< Invalid host name */
ERR_INVALID_DOMAIN_NAME = -4, /**< Invalid domain name */ ERR_INVALID_DOMAIN_NAME = -4, /**< Invalid domain name */
ERR_NO_NETWORK = -5, /**< No suitable network protocol available */ ERR_NO_NETWORK = -5, /**< No suitable network protocol available */
ERR_INVALID_TTL = -6, /**< Invalid DNS TTL */ ERR_INVALID_TTL = -6, /**< Invalid DNS TTL */
ERR_IS_PATTERN = -7, /**< RR key is pattern */ ERR_IS_PATTERN = -7, /**< RR key is pattern */
ERR_COLLISION = -8, /**< Name collision */ ERR_COLLISION = -8, /**< Name collision */
ERR_INVALID_RECORD = -9, /**< Invalid RR */ ERR_INVALID_RECORD = -9, /**< Invalid RR */
ERR_INVALID_SERVICE_NAME = -10, /**< Invalid service name */ ERR_INVALID_SERVICE_NAME = -10, /**< Invalid service name */
ERR_INVALID_SERVICE_TYPE = -11, /**< Invalid service type */ ERR_INVALID_SERVICE_TYPE = -11, /**< Invalid service type */
ERR_INVALID_PORT = -12, /**< Invalid port number */ ERR_INVALID_PORT = -12, /**< Invalid port number */
ERR_INVALID_KEY = -13, /**< Invalid key */ ERR_INVALID_KEY = -13, /**< Invalid key */
ERR_INVALID_ADDRESS = -14, /**< Invalid address */ ERR_INVALID_ADDRESS = -14, /**< Invalid address */
ERR_TIMEOUT = -15, /**< Timeout reached */ ERR_TIMEOUT = -15, /**< Timeout reached */
ERR_TOO_MANY_CLIENTS = -16, /**< Too many clients */ ERR_TOO_MANY_CLIENTS = -16, /**< Too many clients */
ERR_TOO_MANY_OBJECTS = -17, /**< Too many objects */ ERR_TOO_MANY_OBJECTS = -17, /**< Too many objects */
ERR_TOO_MANY_ENTRIES = -18, /**< Too many entries */ ERR_TOO_MANY_ENTRIES = -18, /**< Too many entries */
ERR_OS = -19, /**< OS error */ ERR_OS = -19, /**< OS error */
ERR_ACCESS_DENIED = -20, /**< Access denied */ ERR_ACCESS_DENIED = -20, /**< Access denied */
ERR_INVALID_OPERATION = -21, /**< Invalid operation */ ERR_INVALID_OPERATION = -21, /**< Invalid operation */
ERR_DBUS_ERROR = -22, /**< An unexpected D-Bus error occurred */ ERR_DBUS_ERROR = -22, /**< An unexpected D-Bus error occurred */
ERR_DISCONNECTED = -23, /**< Daemon connection failed */ ERR_DISCONNECTED = -23, /**< Daemon connection failed */
ERR_NO_MEMORY = -24, /**< Memory exhausted */ ERR_NO_MEMORY = -24, /**< Memory exhausted */
ERR_INVALID_OBJECT = -25, /**< The object passed to this function was invalid */ ERR_INVALID_OBJECT = -25, /**< The object passed to this function was invalid */
ERR_NO_DAEMON = -26, /**< Daemon not running */ ERR_NO_DAEMON = -26, /**< Daemon not running */
ERR_INVALID_INTERFACE = -27, /**< Invalid interface */ ERR_INVALID_INTERFACE = -27, /**< Invalid interface */
ERR_INVALID_PROTOCOL = -28, /**< Invalid protocol */ ERR_INVALID_PROTOCOL = -28, /**< Invalid protocol */
ERR_INVALID_FLAGS = -29, /**< Invalid flags */ ERR_INVALID_FLAGS = -29, /**< Invalid flags */
ERR_NOT_FOUND = -30, /**< Not found */ ERR_NOT_FOUND = -30, /**< Not found */
ERR_INVALID_CONFIG = -31, /**< Configuration error */ ERR_INVALID_CONFIG = -31, /**< Configuration error */
ERR_VERSION_MISMATCH = -32, /**< Verson mismatch */ ERR_VERSION_MISMATCH = -32, /**< Verson mismatch */
ERR_INVALID_SERVICE_SUBTYPE = -33, /**< Invalid service subtype */ ERR_INVALID_SERVICE_SUBTYPE = -33, /**< Invalid service subtype */
ERR_INVALID_PACKET = -34, /**< Invalid packet */ ERR_INVALID_PACKET = -34, /**< Invalid packet */
ERR_INVALID_DNS_ERROR = -35, /**< Invlaid DNS return code */ ERR_INVALID_DNS_ERROR = -35, /**< Invlaid DNS return code */
ERR_DNS_FORMERR = -36, /**< DNS Error: Form error */ ERR_DNS_FORMERR = -36, /**< DNS Error: Form error */
ERR_DNS_SERVFAIL = -37, /**< DNS Error: Server Failure */ ERR_DNS_SERVFAIL = -37, /**< DNS Error: Server Failure */
ERR_DNS_NXDOMAIN = -38, /**< DNS Error: No such domain */ ERR_DNS_NXDOMAIN = -38, /**< DNS Error: No such domain */
ERR_DNS_NOTIMP = -39, /**< DNS Error: Not implemented */ ERR_DNS_NOTIMP = -39, /**< DNS Error: Not implemented */
ERR_DNS_REFUSED = -40, /**< DNS Error: Operation refused */ ERR_DNS_REFUSED = -40, /**< DNS Error: Operation refused */
ERR_DNS_YXDOMAIN = -41, ERR_DNS_YXDOMAIN = -41,
ERR_DNS_YXRRSET = -42, ERR_DNS_YXRRSET = -42,
ERR_DNS_NXRRSET = -43, ERR_DNS_NXRRSET = -43,
ERR_DNS_NOTAUTH = -44, /**< DNS Error: Not authorized */ ERR_DNS_NOTAUTH = -44, /**< DNS Error: Not authorized */
ERR_DNS_NOTZONE = -45, ERR_DNS_NOTZONE = -45,
ERR_INVALID_RDATA = -46, /**< Invalid RDATA */ ERR_INVALID_RDATA = -46, /**< Invalid RDATA */
ERR_INVALID_DNS_CLASS = -47, /**< Invalid DNS class */ ERR_INVALID_DNS_CLASS = -47, /**< Invalid DNS class */
ERR_INVALID_DNS_TYPE = -48, /**< Invalid DNS type */ ERR_INVALID_DNS_TYPE = -48, /**< Invalid DNS type */
ERR_NOT_SUPPORTED = -49, /**< Not supported */ ERR_NOT_SUPPORTED = -49, /**< Not supported */
ERR_NOT_PERMITTED = -50, /**< Operation not permitted */ ERR_NOT_PERMITTED = -50, /**< Operation not permitted */
ERR_INVALID_ARGUMENT = -51, /**< Invalid argument */ ERR_INVALID_ARGUMENT = -51, /**< Invalid argument */
ERR_IS_EMPTY = -52, /**< Is empty */ ERR_IS_EMPTY = -52, /**< Is empty */
ERR_NO_CHANGE = -53, /**< The requested operation is invalid because it is redundant */ ERR_NO_CHANGE = -53, /**< The requested operation is invalid because it is redundant */
ERR_MAX = -54 ERR_MAX = -54
};
constexpr auto IF_UNSPEC = -1;
enum proto {
PROTO_INET = 0, /**< IPv4 */
PROTO_INET6 = 1, /**< IPv6 */
PROTO_UNSPEC = -1 /**< Unspecified/all protocol(s) */
};
enum ServerState {
SERVER_INVALID, /**< Invalid state (initial) */
SERVER_REGISTERING, /**< Host RRs are being registered */
SERVER_RUNNING, /**< All host RRs have been established */
SERVER_COLLISION, /**< There is a collision with a host RR. All host RRs have been withdrawn, the user should set a new host name via avahi_server_set_host_name() */
SERVER_FAILURE /**< Some fatal failure happened, the server is unable to proceed */
};
enum ClientState {
CLIENT_S_REGISTERING = SERVER_REGISTERING, /**< Server state: REGISTERING */
CLIENT_S_RUNNING = SERVER_RUNNING, /**< Server state: RUNNING */
CLIENT_S_COLLISION = SERVER_COLLISION, /**< Server state: COLLISION */
CLIENT_FAILURE = 100, /**< Some kind of error happened on the client side */
CLIENT_CONNECTING = 101 /**< We're still connecting. This state is only entered when AVAHI_CLIENT_NO_FAIL has been passed to avahi_client_new() and the daemon is not yet available. */
};
enum EntryGroupState {
ENTRY_GROUP_UNCOMMITED, /**< The group has not yet been commited, the user must still call avahi_entry_group_commit() */
ENTRY_GROUP_REGISTERING, /**< The entries of the group are currently being registered */
ENTRY_GROUP_ESTABLISHED, /**< The entries have successfully been established */
ENTRY_GROUP_COLLISION, /**< A name collision for one of the entries in the group has been detected, the entries have been withdrawn */
ENTRY_GROUP_FAILURE /**< Some kind of failure happened, the entries have been withdrawn */
};
enum ClientFlags {
CLIENT_IGNORE_USER_CONFIG = 1, /**< Don't read user configuration */
CLIENT_NO_FAIL = 2 /**< Don't fail if the daemon is not available when avahi_client_new() is called, instead enter CLIENT_CONNECTING state and wait for the daemon to appear */
};
/** Some flags for publishing functions */
enum PublishFlags {
PUBLISH_UNIQUE = 1, /**< For raw records: The RRset is intended to be unique */
PUBLISH_NO_PROBE = 2, /**< For raw records: Though the RRset is intended to be unique no probes shall be sent */
PUBLISH_NO_ANNOUNCE = 4, /**< For raw records: Do not announce this RR to other hosts */
PUBLISH_ALLOW_MULTIPLE = 8, /**< For raw records: Allow multiple local records of this type, even if they are intended to be unique */
/** \cond fulldocs */
PUBLISH_NO_REVERSE = 16, /**< For address records: don't create a reverse (PTR) entry */
PUBLISH_NO_COOKIE = 32, /**< For service records: do not implicitly add the local service cookie to TXT data */
/** \endcond */
PUBLISH_UPDATE = 64, /**< Update existing records instead of adding new ones */
/** \cond fulldocs */
PUBLISH_USE_WIDE_AREA = 128, /**< Register the record using wide area DNS (i.e. unicast DNS update) */
PUBLISH_USE_MULTICAST = 256 /**< Register the record using multicast DNS */
/** \endcond */
};
using IfIndex = int;
using Protocol = int;
struct EntryGroup;
struct Poll;
struct SimplePoll;
struct Client;
typedef void (*ClientCallback)(Client *, ClientState, void *userdata);
typedef void (*EntryGroupCallback)(EntryGroup *g, EntryGroupState state, void *userdata);
typedef void (*free_fn)(void *);
typedef Client *(*client_new_fn)(const Poll *poll_api, ClientFlags flags, ClientCallback callback, void *userdata, int *error);
typedef void (*client_free_fn)(Client *);
typedef char *(*alternative_service_name_fn)(char *);
typedef Client *(*entry_group_get_client_fn)(EntryGroup *);
typedef EntryGroup *(*entry_group_new_fn)(Client *, EntryGroupCallback, void *userdata);
typedef int (*entry_group_add_service_fn)(
EntryGroup *group,
IfIndex interface,
Protocol protocol,
PublishFlags flags,
const char *name,
const char *type,
const char *domain,
const char *host,
uint16_t port,
...);
typedef int (*entry_group_is_empty_fn)(EntryGroup *);
typedef int (*entry_group_reset_fn)(EntryGroup *);
typedef int (*entry_group_commit_fn)(EntryGroup *);
typedef char *(*strdup_fn)(const char *);
typedef char *(*strerror_fn)(int);
typedef int (*client_errno_fn)(Client *);
typedef Poll *(*simple_poll_get_fn)(SimplePoll *);
typedef int (*simple_poll_loop_fn)(SimplePoll *);
typedef void (*simple_poll_quit_fn)(SimplePoll *);
typedef SimplePoll *(*simple_poll_new_fn)();
typedef void (*simple_poll_free_fn)(SimplePoll *);
free_fn free;
client_new_fn client_new;
client_free_fn client_free;
alternative_service_name_fn alternative_service_name;
entry_group_get_client_fn entry_group_get_client;
entry_group_new_fn entry_group_new;
entry_group_add_service_fn entry_group_add_service;
entry_group_is_empty_fn entry_group_is_empty;
entry_group_reset_fn entry_group_reset;
entry_group_commit_fn entry_group_commit;
strdup_fn strdup;
strerror_fn strerror;
client_errno_fn client_errno;
simple_poll_get_fn simple_poll_get;
simple_poll_loop_fn simple_poll_loop;
simple_poll_quit_fn simple_poll_quit;
simple_poll_new_fn simple_poll_new;
simple_poll_free_fn simple_poll_free;
int init_common() {
static void *handle { nullptr };
static bool funcs_loaded = false;
if(funcs_loaded) return 0;
if(!handle) {
handle = dyn::handle({ "libavahi-common.3.dylib", "libavahi-common.dylib" });
if(!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *)&alternative_service_name, "avahi_alternative_service_name" },
{ (dyn::apiproc *)&free, "avahi_free" },
{ (dyn::apiproc *)&strdup, "avahi_strdup" },
{ (dyn::apiproc *)&strerror, "avahi_strerror" },
{ (dyn::apiproc *)&simple_poll_get, "avahi_simple_poll_get" },
{ (dyn::apiproc *)&simple_poll_loop, "avahi_simple_poll_loop" },
{ (dyn::apiproc *)&simple_poll_quit, "avahi_simple_poll_quit" },
{ (dyn::apiproc *)&simple_poll_new, "avahi_simple_poll_new" },
{ (dyn::apiproc *)&simple_poll_free, "avahi_simple_poll_free" },
}; };
if(dyn::load(handle, funcs)) { constexpr auto IF_UNSPEC = -1;
return -1; enum proto {
} PROTO_INET = 0, /**< IPv4 */
PROTO_INET6 = 1, /**< IPv6 */
funcs_loaded = true; PROTO_UNSPEC = -1 /**< Unspecified/all protocol(s) */
return 0;
}
int init_client() {
if(init_common()) {
return -1;
}
static void *handle { nullptr };
static bool funcs_loaded = false;
if(funcs_loaded) return 0;
if(!handle) {
handle = dyn::handle({ "libavahi-client.3.dylib", "libavahi-client.dylib" });
if(!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *)&client_new, "avahi_client_new" },
{ (dyn::apiproc *)&client_free, "avahi_client_free" },
{ (dyn::apiproc *)&entry_group_get_client, "avahi_entry_group_get_client" },
{ (dyn::apiproc *)&entry_group_new, "avahi_entry_group_new" },
{ (dyn::apiproc *)&entry_group_add_service, "avahi_entry_group_add_service" },
{ (dyn::apiproc *)&entry_group_is_empty, "avahi_entry_group_is_empty" },
{ (dyn::apiproc *)&entry_group_reset, "avahi_entry_group_reset" },
{ (dyn::apiproc *)&entry_group_commit, "avahi_entry_group_commit" },
{ (dyn::apiproc *)&client_errno, "avahi_client_errno" },
}; };
if(dyn::load(handle, funcs)) { enum ServerState {
return -1; SERVER_INVALID, /**< Invalid state (initial) */
SERVER_REGISTERING, /**< Host RRs are being registered */
SERVER_RUNNING, /**< All host RRs have been established */
SERVER_COLLISION, /**< There is a collision with a host RR. All host RRs have been withdrawn, the user should set a new host name via avahi_server_set_host_name() */
SERVER_FAILURE /**< Some fatal failure happened, the server is unable to proceed */
};
enum ClientState {
CLIENT_S_REGISTERING = SERVER_REGISTERING, /**< Server state: REGISTERING */
CLIENT_S_RUNNING = SERVER_RUNNING, /**< Server state: RUNNING */
CLIENT_S_COLLISION = SERVER_COLLISION, /**< Server state: COLLISION */
CLIENT_FAILURE = 100, /**< Some kind of error happened on the client side */
CLIENT_CONNECTING = 101 /**< We're still connecting. This state is only entered when AVAHI_CLIENT_NO_FAIL has been passed to avahi_client_new() and the daemon is not yet available. */
};
enum EntryGroupState {
ENTRY_GROUP_UNCOMMITED, /**< The group has not yet been commited, the user must still call avahi_entry_group_commit() */
ENTRY_GROUP_REGISTERING, /**< The entries of the group are currently being registered */
ENTRY_GROUP_ESTABLISHED, /**< The entries have successfully been established */
ENTRY_GROUP_COLLISION, /**< A name collision for one of the entries in the group has been detected, the entries have been withdrawn */
ENTRY_GROUP_FAILURE /**< Some kind of failure happened, the entries have been withdrawn */
};
enum ClientFlags {
CLIENT_IGNORE_USER_CONFIG = 1, /**< Don't read user configuration */
CLIENT_NO_FAIL = 2 /**< Don't fail if the daemon is not available when avahi_client_new() is called, instead enter CLIENT_CONNECTING state and wait for the daemon to appear */
};
/** Some flags for publishing functions */
enum PublishFlags {
PUBLISH_UNIQUE = 1, /**< For raw records: The RRset is intended to be unique */
PUBLISH_NO_PROBE = 2, /**< For raw records: Though the RRset is intended to be unique no probes shall be sent */
PUBLISH_NO_ANNOUNCE = 4, /**< For raw records: Do not announce this RR to other hosts */
PUBLISH_ALLOW_MULTIPLE = 8, /**< For raw records: Allow multiple local records of this type, even if they are intended to be unique */
/** \cond fulldocs */
PUBLISH_NO_REVERSE = 16, /**< For address records: don't create a reverse (PTR) entry */
PUBLISH_NO_COOKIE = 32, /**< For service records: do not implicitly add the local service cookie to TXT data */
/** \endcond */
PUBLISH_UPDATE = 64, /**< Update existing records instead of adding new ones */
/** \cond fulldocs */
PUBLISH_USE_WIDE_AREA = 128, /**< Register the record using wide area DNS (i.e. unicast DNS update) */
PUBLISH_USE_MULTICAST = 256 /**< Register the record using multicast DNS */
/** \endcond */
};
using IfIndex = int;
using Protocol = int;
struct EntryGroup;
struct Poll;
struct SimplePoll;
struct Client;
typedef void (*ClientCallback)(Client *, ClientState, void *userdata);
typedef void (*EntryGroupCallback)(EntryGroup *g, EntryGroupState state, void *userdata);
typedef void (*free_fn)(void *);
typedef Client *(*client_new_fn)(const Poll *poll_api, ClientFlags flags, ClientCallback callback, void *userdata, int *error);
typedef void (*client_free_fn)(Client *);
typedef char *(*alternative_service_name_fn)(char *);
typedef Client *(*entry_group_get_client_fn)(EntryGroup *);
typedef EntryGroup *(*entry_group_new_fn)(Client *, EntryGroupCallback, void *userdata);
typedef int (*entry_group_add_service_fn)(
EntryGroup *group,
IfIndex interface,
Protocol protocol,
PublishFlags flags,
const char *name,
const char *type,
const char *domain,
const char *host,
uint16_t port,
...);
typedef int (*entry_group_is_empty_fn)(EntryGroup *);
typedef int (*entry_group_reset_fn)(EntryGroup *);
typedef int (*entry_group_commit_fn)(EntryGroup *);
typedef char *(*strdup_fn)(const char *);
typedef char *(*strerror_fn)(int);
typedef int (*client_errno_fn)(Client *);
typedef Poll *(*simple_poll_get_fn)(SimplePoll *);
typedef int (*simple_poll_loop_fn)(SimplePoll *);
typedef void (*simple_poll_quit_fn)(SimplePoll *);
typedef SimplePoll *(*simple_poll_new_fn)();
typedef void (*simple_poll_free_fn)(SimplePoll *);
free_fn free;
client_new_fn client_new;
client_free_fn client_free;
alternative_service_name_fn alternative_service_name;
entry_group_get_client_fn entry_group_get_client;
entry_group_new_fn entry_group_new;
entry_group_add_service_fn entry_group_add_service;
entry_group_is_empty_fn entry_group_is_empty;
entry_group_reset_fn entry_group_reset;
entry_group_commit_fn entry_group_commit;
strdup_fn strdup;
strerror_fn strerror;
client_errno_fn client_errno;
simple_poll_get_fn simple_poll_get;
simple_poll_loop_fn simple_poll_loop;
simple_poll_quit_fn simple_poll_quit;
simple_poll_new_fn simple_poll_new;
simple_poll_free_fn simple_poll_free;
int
init_common() {
static void *handle { nullptr };
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (!handle) {
handle = dyn::handle({ "libavahi-common.3.dylib", "libavahi-common.dylib" });
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &alternative_service_name, "avahi_alternative_service_name" },
{ (dyn::apiproc *) &free, "avahi_free" },
{ (dyn::apiproc *) &strdup, "avahi_strdup" },
{ (dyn::apiproc *) &strerror, "avahi_strerror" },
{ (dyn::apiproc *) &simple_poll_get, "avahi_simple_poll_get" },
{ (dyn::apiproc *) &simple_poll_loop, "avahi_simple_poll_loop" },
{ (dyn::apiproc *) &simple_poll_quit, "avahi_simple_poll_quit" },
{ (dyn::apiproc *) &simple_poll_new, "avahi_simple_poll_new" },
{ (dyn::apiproc *) &simple_poll_free, "avahi_simple_poll_free" },
};
if (dyn::load(handle, funcs)) {
return -1;
}
funcs_loaded = true;
return 0;
} }
funcs_loaded = true; int
return 0; init_client() {
} if (init_common()) {
} // namespace avahi return -1;
}
static void *handle { nullptr };
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (!handle) {
handle = dyn::handle({ "libavahi-client.3.dylib", "libavahi-client.dylib" });
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &client_new, "avahi_client_new" },
{ (dyn::apiproc *) &client_free, "avahi_client_free" },
{ (dyn::apiproc *) &entry_group_get_client, "avahi_entry_group_get_client" },
{ (dyn::apiproc *) &entry_group_new, "avahi_entry_group_new" },
{ (dyn::apiproc *) &entry_group_add_service, "avahi_entry_group_add_service" },
{ (dyn::apiproc *) &entry_group_is_empty, "avahi_entry_group_is_empty" },
{ (dyn::apiproc *) &entry_group_reset, "avahi_entry_group_reset" },
{ (dyn::apiproc *) &entry_group_commit, "avahi_entry_group_commit" },
{ (dyn::apiproc *) &client_errno, "avahi_client_errno" },
};
if (dyn::load(handle, funcs)) {
return -1;
}
funcs_loaded = true;
return 0;
}
} // namespace avahi
namespace platf::publish { namespace platf::publish {
template<class T> template <class T>
void free(T *p) { void
avahi::free(p); free(T *p) {
} avahi::free(p);
template<class T>
using ptr_t = util::safe_ptr<T, free<T>>;
using client_t = util::dyn_safe_ptr<avahi::Client, &avahi::client_free>;
using poll_t = util::dyn_safe_ptr<avahi::SimplePoll, &avahi::simple_poll_free>;
avahi::EntryGroup *group = nullptr;
poll_t poll;
client_t client;
ptr_t<char> name;
void create_services(avahi::Client *c);
void entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
group = g;
switch(state) {
case avahi::ENTRY_GROUP_ESTABLISHED:
BOOST_LOG(info) << "Avahi service " << name.get() << " successfully established.";
break;
case avahi::ENTRY_GROUP_COLLISION:
name.reset(avahi::alternative_service_name(name.get()));
BOOST_LOG(info) << "Avahi service name collision, renaming service to " << name.get();
create_services(avahi::entry_group_get_client(g));
break;
case avahi::ENTRY_GROUP_FAILURE:
BOOST_LOG(error) << "Avahi entry group failure: " << avahi::strerror(avahi::client_errno(avahi::entry_group_get_client(g)));
avahi::simple_poll_quit(poll.get());
break;
case avahi::ENTRY_GROUP_UNCOMMITED:
case avahi::ENTRY_GROUP_REGISTERING:;
} }
}
void create_services(avahi::Client *c) { template <class T>
int ret; using ptr_t = util::safe_ptr<T, free<T>>;
using client_t = util::dyn_safe_ptr<avahi::Client, &avahi::client_free>;
using poll_t = util::dyn_safe_ptr<avahi::SimplePoll, &avahi::simple_poll_free>;
auto fg = util::fail_guard([]() { avahi::EntryGroup *group = nullptr;
avahi::simple_poll_quit(poll.get());
});
if(!group) { poll_t poll;
if(!(group = avahi::entry_group_new(c, entry_group_callback, nullptr))) { client_t client;
BOOST_LOG(error) << "avahi::entry_group_new() failed: "sv << avahi::strerror(avahi::client_errno(c));
return; ptr_t<char> name;
void
create_services(avahi::Client *c);
void
entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
group = g;
switch (state) {
case avahi::ENTRY_GROUP_ESTABLISHED:
BOOST_LOG(info) << "Avahi service " << name.get() << " successfully established.";
break;
case avahi::ENTRY_GROUP_COLLISION:
name.reset(avahi::alternative_service_name(name.get()));
BOOST_LOG(info) << "Avahi service name collision, renaming service to " << name.get();
create_services(avahi::entry_group_get_client(g));
break;
case avahi::ENTRY_GROUP_FAILURE:
BOOST_LOG(error) << "Avahi entry group failure: " << avahi::strerror(avahi::client_errno(avahi::entry_group_get_client(g)));
avahi::simple_poll_quit(poll.get());
break;
case avahi::ENTRY_GROUP_UNCOMMITED:
case avahi::ENTRY_GROUP_REGISTERING:;
} }
} }
if(avahi::entry_group_is_empty(group)) { void
BOOST_LOG(info) << "Adding avahi service "sv << name.get(); create_services(avahi::Client *c) {
int ret;
ret = avahi::entry_group_add_service( auto fg = util::fail_guard([]() {
group, avahi::simple_poll_quit(poll.get());
avahi::IF_UNSPEC, avahi::PROTO_UNSPEC, });
avahi::PublishFlags(0),
name.get(),
SERVICE_TYPE,
nullptr, nullptr,
map_port(nvhttp::PORT_HTTP),
nullptr);
if(ret < 0) { if (!group) {
if(ret == avahi::ERR_COLLISION) { if (!(group = avahi::entry_group_new(c, entry_group_callback, nullptr))) {
// A service name collision with a local service happened. Let's pick a new name BOOST_LOG(error) << "avahi::entry_group_new() failed: "sv << avahi::strerror(avahi::client_errno(c));
name.reset(avahi::alternative_service_name(name.get())); return;
BOOST_LOG(info) << "Service name collision, renaming service to "sv << name.get(); }
}
avahi::entry_group_reset(group); if (avahi::entry_group_is_empty(group)) {
BOOST_LOG(info) << "Adding avahi service "sv << name.get();
create_services(c); ret = avahi::entry_group_add_service(
group,
avahi::IF_UNSPEC, avahi::PROTO_UNSPEC,
avahi::PublishFlags(0),
name.get(),
SERVICE_TYPE,
nullptr, nullptr,
map_port(nvhttp::PORT_HTTP),
nullptr);
fg.disable(); if (ret < 0) {
if (ret == avahi::ERR_COLLISION) {
// A service name collision with a local service happened. Let's pick a new name
name.reset(avahi::alternative_service_name(name.get()));
BOOST_LOG(info) << "Service name collision, renaming service to "sv << name.get();
avahi::entry_group_reset(group);
create_services(c);
fg.disable();
return;
}
BOOST_LOG(error) << "Failed to add "sv << SERVICE_TYPE << " service: "sv << avahi::strerror(ret);
return; return;
} }
BOOST_LOG(error) << "Failed to add "sv << SERVICE_TYPE << " service: "sv << avahi::strerror(ret); ret = avahi::entry_group_commit(group);
return; if (ret < 0) {
BOOST_LOG(error) << "Failed to commit entry group: "sv << avahi::strerror(ret);
return;
}
} }
ret = avahi::entry_group_commit(group); fg.disable();
if(ret < 0) { }
BOOST_LOG(error) << "Failed to commit entry group: "sv << avahi::strerror(ret);
return; void
client_callback(avahi::Client *c, avahi::ClientState state, void *) {
switch (state) {
case avahi::CLIENT_S_RUNNING:
create_services(c);
break;
case avahi::CLIENT_FAILURE:
BOOST_LOG(error) << "Client failure: "sv << avahi::strerror(avahi::client_errno(c));
avahi::simple_poll_quit(poll.get());
break;
case avahi::CLIENT_S_COLLISION:
case avahi::CLIENT_S_REGISTERING:
if (group)
avahi::entry_group_reset(group);
break;
case avahi::CLIENT_CONNECTING:;
} }
} }
fg.disable(); class deinit_t: public ::platf::deinit_t {
} public:
std::thread poll_thread;
void client_callback(avahi::Client *c, avahi::ClientState state, void *) { deinit_t(std::thread poll_thread):
switch(state) { poll_thread { std::move(poll_thread) } {}
case avahi::CLIENT_S_RUNNING:
create_services(c);
break;
case avahi::CLIENT_FAILURE:
BOOST_LOG(error) << "Client failure: "sv << avahi::strerror(avahi::client_errno(c));
avahi::simple_poll_quit(poll.get());
break;
case avahi::CLIENT_S_COLLISION:
case avahi::CLIENT_S_REGISTERING:
if(group)
avahi::entry_group_reset(group);
break;
case avahi::CLIENT_CONNECTING:;
}
}
class deinit_t : public ::platf::deinit_t { ~deinit_t() override {
public: if (avahi::simple_poll_quit && poll) {
std::thread poll_thread; avahi::simple_poll_quit(poll.get());
}
deinit_t(std::thread poll_thread) : poll_thread { std::move(poll_thread) } {} if (poll_thread.joinable()) {
poll_thread.join();
}
}
};
~deinit_t() override { [[nodiscard]] std::unique_ptr<::platf::deinit_t>
if(avahi::simple_poll_quit && poll) { start() {
avahi::simple_poll_quit(poll.get()); if (avahi::init_client()) {
return nullptr;
} }
if(poll_thread.joinable()) { int avhi_error;
poll_thread.join();
poll.reset(avahi::simple_poll_new());
if (!poll) {
BOOST_LOG(error) << "Failed to create simple poll object."sv;
return nullptr;
} }
name.reset(avahi::strdup(SERVICE_NAME));
client.reset(
avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error));
if (!client) {
BOOST_LOG(error) << "Failed to create client: "sv << avahi::strerror(avhi_error);
return nullptr;
}
return std::make_unique<deinit_t>(std::thread { avahi::simple_poll_loop, poll.get() });
} }
}; }; // namespace platf::publish
[[nodiscard]] std::unique_ptr<::platf::deinit_t> start() {
if(avahi::init_client()) {
return nullptr;
}
int avhi_error;
poll.reset(avahi::simple_poll_new());
if(!poll) {
BOOST_LOG(error) << "Failed to create simple poll object."sv;
return nullptr;
}
name.reset(avahi::strdup(SERVICE_NAME));
client.reset(
avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error));
if(!client) {
BOOST_LOG(error) << "Failed to create client: "sv << avahi::strerror(avhi_error);
return nullptr;
}
return std::make_unique<deinit_t>(std::thread { avahi::simple_poll_loop, poll.get() });
}
}; // namespace platf::publish

View file

@ -6,16 +6,15 @@
// http://eretik.omegahg.com/ // http://eretik.omegahg.com/
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#pragma once #pragma once
#ifdef __MINGW32__ #ifdef __MINGW32__
#undef DEFINE_GUID #undef DEFINE_GUID
#ifdef __cplusplus #ifdef __cplusplus
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#else #else
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#endif #endif
DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8); DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8);
DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9); DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9);
@ -37,13 +36,15 @@ class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
// //
// @compatible: Windows 7 and Later // @compatible: Windows 7 and Later
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
interface IPolicyConfig : public IUnknown { interface IPolicyConfig: public IUnknown {
public: public:
virtual HRESULT GetMixFormat( virtual HRESULT
GetMixFormat(
PCWSTR, PCWSTR,
WAVEFORMATEX **); WAVEFORMATEX **);
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( virtual HRESULT STDMETHODCALLTYPE
GetDeviceFormat(
PCWSTR, PCWSTR,
INT, INT,
WAVEFORMATEX **); WAVEFORMATEX **);
@ -51,7 +52,8 @@ public:
virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat( virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
PCWSTR); PCWSTR);
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( virtual HRESULT STDMETHODCALLTYPE
SetDeviceFormat(
PCWSTR, PCWSTR,
WAVEFORMATEX *, WAVEFORMATEX *,
WAVEFORMATEX *); WAVEFORMATEX *);
@ -66,25 +68,30 @@ public:
PCWSTR, PCWSTR,
PINT64); PINT64);
virtual HRESULT STDMETHODCALLTYPE GetShareMode( virtual HRESULT STDMETHODCALLTYPE
GetShareMode(
PCWSTR, PCWSTR,
struct DeviceShareMode *); struct DeviceShareMode *);
virtual HRESULT STDMETHODCALLTYPE SetShareMode( virtual HRESULT STDMETHODCALLTYPE
SetShareMode(
PCWSTR, PCWSTR,
struct DeviceShareMode *); struct DeviceShareMode *);
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( virtual HRESULT STDMETHODCALLTYPE
GetPropertyValue(
PCWSTR, PCWSTR,
const PROPERTYKEY &, const PROPERTYKEY &,
PROPVARIANT *); PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( virtual HRESULT STDMETHODCALLTYPE
SetPropertyValue(
PCWSTR, PCWSTR,
const PROPERTYKEY &, const PROPERTYKEY &,
PROPVARIANT *); PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( virtual HRESULT STDMETHODCALLTYPE
SetDefaultEndpoint(
PCWSTR wszDeviceId, PCWSTR wszDeviceId,
ERole eRole); ERole eRole);
@ -108,18 +115,21 @@ class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaCl
// //
// @compatible: Windows Vista and Later // @compatible: Windows Vista and Later
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
interface IPolicyConfigVista : public IUnknown { interface IPolicyConfigVista: public IUnknown {
public: public:
virtual HRESULT GetMixFormat( virtual HRESULT
GetMixFormat(
PCWSTR, PCWSTR,
WAVEFORMATEX **); // not available on Windows 7, use method from IPolicyConfig WAVEFORMATEX **); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( virtual HRESULT STDMETHODCALLTYPE
GetDeviceFormat(
PCWSTR, PCWSTR,
INT, INT,
WAVEFORMATEX **); WAVEFORMATEX **);
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( virtual HRESULT STDMETHODCALLTYPE
SetDeviceFormat(
PCWSTR, PCWSTR,
WAVEFORMATEX *, WAVEFORMATEX *,
WAVEFORMATEX *); WAVEFORMATEX *);
@ -128,37 +138,42 @@ public:
PCWSTR, PCWSTR,
INT, INT,
PINT64, PINT64,
PINT64); // not available on Windows 7, use method from IPolicyConfig PINT64); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
PCWSTR, PCWSTR,
PINT64); // not available on Windows 7, use method from IPolicyConfig PINT64); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE GetShareMode( virtual HRESULT STDMETHODCALLTYPE
GetShareMode(
PCWSTR, PCWSTR,
struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE SetShareMode( virtual HRESULT STDMETHODCALLTYPE
SetShareMode(
PCWSTR, PCWSTR,
struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( virtual HRESULT STDMETHODCALLTYPE
GetPropertyValue(
PCWSTR, PCWSTR,
const PROPERTYKEY &, const PROPERTYKEY &,
PROPVARIANT *); PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( virtual HRESULT STDMETHODCALLTYPE
SetPropertyValue(
PCWSTR, PCWSTR,
const PROPERTYKEY &, const PROPERTYKEY &,
PROPVARIANT *); PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( virtual HRESULT STDMETHODCALLTYPE
SetDefaultEndpoint(
PCWSTR wszDeviceId, PCWSTR wszDeviceId,
ERole eRole); ERole eRole);
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
PCWSTR, PCWSTR,
INT); // not available on Windows 7, use method from IPolicyConfig INT); // not available on Windows 7, use method from IPolicyConfig
}; };
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

File diff suppressed because it is too large Load diff

View file

@ -16,196 +16,229 @@
#include "src/utility.h" #include "src/utility.h"
namespace platf::dxgi { namespace platf::dxgi {
extern const char *format_str[]; extern const char *format_str[];
// Add D3D11_CREATE_DEVICE_DEBUG here to enable the D3D11 debug runtime. // Add D3D11_CREATE_DEVICE_DEBUG here to enable the D3D11 debug runtime.
// You should have a debugger like WinDbg attached to receive debug messages. // You should have a debugger like WinDbg attached to receive debug messages.
auto constexpr D3D11_CREATE_DEVICE_FLAGS = D3D11_CREATE_DEVICE_VIDEO_SUPPORT; auto constexpr D3D11_CREATE_DEVICE_FLAGS = D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
template<class T> template <class T>
void Release(T *dxgi) { void
dxgi->Release(); Release(T *dxgi) {
} dxgi->Release();
using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
using dxgi_t = util::safe_ptr<IDXGIDevice, Release<IDXGIDevice>>;
using dxgi1_t = util::safe_ptr<IDXGIDevice1, Release<IDXGIDevice1>>;
using device_t = util::safe_ptr<ID3D11Device, Release<ID3D11Device>>;
using device1_t = util::safe_ptr<ID3D11Device1, Release<ID3D11Device1>>;
using device_ctx_t = util::safe_ptr<ID3D11DeviceContext, Release<ID3D11DeviceContext>>;
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>;
using output5_t = util::safe_ptr<IDXGIOutput5, Release<IDXGIOutput5>>;
using output6_t = util::safe_ptr<IDXGIOutput6, Release<IDXGIOutput6>>;
using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>;
using texture2d_t = util::safe_ptr<ID3D11Texture2D, Release<ID3D11Texture2D>>;
using texture1d_t = util::safe_ptr<ID3D11Texture1D, Release<ID3D11Texture1D>>;
using resource_t = util::safe_ptr<IDXGIResource, Release<IDXGIResource>>;
using resource1_t = util::safe_ptr<IDXGIResource1, Release<IDXGIResource1>>;
using multithread_t = util::safe_ptr<ID3D11Multithread, Release<ID3D11Multithread>>;
using vs_t = util::safe_ptr<ID3D11VertexShader, Release<ID3D11VertexShader>>;
using ps_t = util::safe_ptr<ID3D11PixelShader, Release<ID3D11PixelShader>>;
using blend_t = util::safe_ptr<ID3D11BlendState, Release<ID3D11BlendState>>;
using input_layout_t = util::safe_ptr<ID3D11InputLayout, Release<ID3D11InputLayout>>;
using render_target_t = util::safe_ptr<ID3D11RenderTargetView, Release<ID3D11RenderTargetView>>;
using shader_res_t = util::safe_ptr<ID3D11ShaderResourceView, Release<ID3D11ShaderResourceView>>;
using buf_t = util::safe_ptr<ID3D11Buffer, Release<ID3D11Buffer>>;
using raster_state_t = util::safe_ptr<ID3D11RasterizerState, Release<ID3D11RasterizerState>>;
using sampler_state_t = util::safe_ptr<ID3D11SamplerState, Release<ID3D11SamplerState>>;
using blob_t = util::safe_ptr<ID3DBlob, Release<ID3DBlob>>;
using depth_stencil_state_t = util::safe_ptr<ID3D11DepthStencilState, Release<ID3D11DepthStencilState>>;
using depth_stencil_view_t = util::safe_ptr<ID3D11DepthStencilView, Release<ID3D11DepthStencilView>>;
using keyed_mutex_t = util::safe_ptr<IDXGIKeyedMutex, Release<IDXGIKeyedMutex>>;
namespace video {
using device_t = util::safe_ptr<ID3D11VideoDevice, Release<ID3D11VideoDevice>>;
using ctx_t = util::safe_ptr<ID3D11VideoContext, Release<ID3D11VideoContext>>;
using processor_t = util::safe_ptr<ID3D11VideoProcessor, Release<ID3D11VideoProcessor>>;
using processor_out_t = util::safe_ptr<ID3D11VideoProcessorOutputView, Release<ID3D11VideoProcessorOutputView>>;
using processor_in_t = util::safe_ptr<ID3D11VideoProcessorInputView, Release<ID3D11VideoProcessorInputView>>;
using processor_enum_t = util::safe_ptr<ID3D11VideoProcessorEnumerator, Release<ID3D11VideoProcessorEnumerator>>;
} // namespace video
class hwdevice_t;
struct cursor_t {
std::vector<std::uint8_t> img_data;
DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info;
int x, y;
bool visible;
};
class gpu_cursor_t {
public:
gpu_cursor_t() : cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {};
void set_pos(LONG rel_x, LONG rel_y, bool visible) {
cursor_view.TopLeftX = rel_x;
cursor_view.TopLeftY = rel_y;
this->visible = visible;
} }
void set_texture(LONG width, LONG height, texture2d_t &&texture) { using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
cursor_view.Width = width; using dxgi_t = util::safe_ptr<IDXGIDevice, Release<IDXGIDevice>>;
cursor_view.Height = height; using dxgi1_t = util::safe_ptr<IDXGIDevice1, Release<IDXGIDevice1>>;
using device_t = util::safe_ptr<ID3D11Device, Release<ID3D11Device>>;
using device1_t = util::safe_ptr<ID3D11Device1, Release<ID3D11Device1>>;
using device_ctx_t = util::safe_ptr<ID3D11DeviceContext, Release<ID3D11DeviceContext>>;
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>;
using output5_t = util::safe_ptr<IDXGIOutput5, Release<IDXGIOutput5>>;
using output6_t = util::safe_ptr<IDXGIOutput6, Release<IDXGIOutput6>>;
using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>;
using texture2d_t = util::safe_ptr<ID3D11Texture2D, Release<ID3D11Texture2D>>;
using texture1d_t = util::safe_ptr<ID3D11Texture1D, Release<ID3D11Texture1D>>;
using resource_t = util::safe_ptr<IDXGIResource, Release<IDXGIResource>>;
using resource1_t = util::safe_ptr<IDXGIResource1, Release<IDXGIResource1>>;
using multithread_t = util::safe_ptr<ID3D11Multithread, Release<ID3D11Multithread>>;
using vs_t = util::safe_ptr<ID3D11VertexShader, Release<ID3D11VertexShader>>;
using ps_t = util::safe_ptr<ID3D11PixelShader, Release<ID3D11PixelShader>>;
using blend_t = util::safe_ptr<ID3D11BlendState, Release<ID3D11BlendState>>;
using input_layout_t = util::safe_ptr<ID3D11InputLayout, Release<ID3D11InputLayout>>;
using render_target_t = util::safe_ptr<ID3D11RenderTargetView, Release<ID3D11RenderTargetView>>;
using shader_res_t = util::safe_ptr<ID3D11ShaderResourceView, Release<ID3D11ShaderResourceView>>;
using buf_t = util::safe_ptr<ID3D11Buffer, Release<ID3D11Buffer>>;
using raster_state_t = util::safe_ptr<ID3D11RasterizerState, Release<ID3D11RasterizerState>>;
using sampler_state_t = util::safe_ptr<ID3D11SamplerState, Release<ID3D11SamplerState>>;
using blob_t = util::safe_ptr<ID3DBlob, Release<ID3DBlob>>;
using depth_stencil_state_t = util::safe_ptr<ID3D11DepthStencilState, Release<ID3D11DepthStencilState>>;
using depth_stencil_view_t = util::safe_ptr<ID3D11DepthStencilView, Release<ID3D11DepthStencilView>>;
using keyed_mutex_t = util::safe_ptr<IDXGIKeyedMutex, Release<IDXGIKeyedMutex>>;
this->texture = std::move(texture); namespace video {
} using device_t = util::safe_ptr<ID3D11VideoDevice, Release<ID3D11VideoDevice>>;
using ctx_t = util::safe_ptr<ID3D11VideoContext, Release<ID3D11VideoContext>>;
using processor_t = util::safe_ptr<ID3D11VideoProcessor, Release<ID3D11VideoProcessor>>;
using processor_out_t = util::safe_ptr<ID3D11VideoProcessorOutputView, Release<ID3D11VideoProcessorOutputView>>;
using processor_in_t = util::safe_ptr<ID3D11VideoProcessorInputView, Release<ID3D11VideoProcessorInputView>>;
using processor_enum_t = util::safe_ptr<ID3D11VideoProcessorEnumerator, Release<ID3D11VideoProcessorEnumerator>>;
} // namespace video
texture2d_t texture; class hwdevice_t;
shader_res_t input_res; struct cursor_t {
std::vector<std::uint8_t> img_data;
D3D11_VIEWPORT cursor_view; DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info;
int x, y;
bool visible;
};
bool visible; class gpu_cursor_t {
}; public:
gpu_cursor_t():
cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {};
void
set_pos(LONG rel_x, LONG rel_y, bool visible) {
cursor_view.TopLeftX = rel_x;
cursor_view.TopLeftY = rel_y;
class duplication_t { this->visible = visible;
public: }
dup_t dup;
bool has_frame {};
bool use_dwmflush {};
capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); void
capture_e reset(dup_t::pointer dup_p = dup_t::pointer()); set_texture(LONG width, LONG height, texture2d_t &&texture) {
capture_e release_frame(); cursor_view.Width = width;
cursor_view.Height = height;
~duplication_t(); this->texture = std::move(texture);
}; }
class display_base_t : public display_t { texture2d_t texture;
public: shader_res_t input_res;
int init(const ::video::config_t &config, const std::string &display_name);
capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override;
std::chrono::nanoseconds delay; D3D11_VIEWPORT cursor_view;
factory1_t factory; bool visible;
adapter_t adapter; };
output_t output;
device_t device;
device_ctx_t device_ctx;
duplication_t dup;
DXGI_FORMAT capture_format; class duplication_t {
D3D_FEATURE_LEVEL feature_level; public:
dup_t dup;
bool has_frame {};
bool use_dwmflush {};
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS { capture_e
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p);
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, capture_e
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL, reset(dup_t::pointer dup_p = dup_t::pointer());
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, capture_e
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, release_frame();
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
} D3DKMT_SCHEDULINGPRIORITYCLASS;
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); ~duplication_t();
};
virtual bool is_hdr() override; class display_base_t: public display_t {
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) override; public:
int
init(const ::video::config_t &config, const std::string &display_name);
capture_e
capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override;
protected: std::chrono::nanoseconds delay;
int get_pixel_pitch() {
return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4;
}
const char *dxgi_format_to_string(DXGI_FORMAT format); factory1_t factory;
const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type); adapter_t adapter;
output_t output;
device_t device;
device_ctx_t device_ctx;
duplication_t dup;
virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) = 0; DXGI_FORMAT capture_format;
virtual int complete_img(img_t *img, bool dummy) = 0; D3D_FEATURE_LEVEL feature_level;
virtual std::vector<DXGI_FORMAT> get_supported_sdr_capture_formats() = 0;
virtual std::vector<DXGI_FORMAT> get_supported_hdr_capture_formats() = 0;
};
class display_ram_t : public display_base_t { typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS {
public: D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override; D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH,
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
} D3DKMT_SCHEDULINGPRIORITYCLASS;
std::shared_ptr<img_t> alloc_img() override; typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
int dummy_img(img_t *img) override;
int complete_img(img_t *img, bool dummy) override;
std::vector<DXGI_FORMAT> get_supported_sdr_capture_formats() override;
std::vector<DXGI_FORMAT> get_supported_hdr_capture_formats() override;
int init(const ::video::config_t &config, const std::string &display_name); virtual bool
is_hdr() override;
virtual bool
get_hdr_metadata(SS_HDR_METADATA &metadata) override;
cursor_t cursor; protected:
D3D11_MAPPED_SUBRESOURCE img_info; int
texture2d_t texture; get_pixel_pitch() {
}; return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4;
}
class display_vram_t : public display_base_t, public std::enable_shared_from_this<display_vram_t> { const char *
public: dxgi_format_to_string(DXGI_FORMAT format);
virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override; const char *
colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
std::shared_ptr<img_t> alloc_img() override; virtual capture_e
int dummy_img(img_t *img_base) override; snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) = 0;
int complete_img(img_t *img_base, bool dummy) override; virtual int
std::vector<DXGI_FORMAT> get_supported_sdr_capture_formats() override; complete_img(img_t *img, bool dummy) = 0;
std::vector<DXGI_FORMAT> get_supported_hdr_capture_formats() override; virtual std::vector<DXGI_FORMAT>
get_supported_sdr_capture_formats() = 0;
virtual std::vector<DXGI_FORMAT>
get_supported_hdr_capture_formats() = 0;
};
int init(const ::video::config_t &config, const std::string &display_name); class display_ram_t: public display_base_t {
public:
virtual capture_e
snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override;
std::shared_ptr<platf::hwdevice_t> make_hwdevice(pix_fmt_e pix_fmt) override; std::shared_ptr<img_t>
alloc_img() override;
int
dummy_img(img_t *img) override;
int
complete_img(img_t *img, bool dummy) override;
std::vector<DXGI_FORMAT>
get_supported_sdr_capture_formats() override;
std::vector<DXGI_FORMAT>
get_supported_hdr_capture_formats() override;
sampler_state_t sampler_linear; int
init(const ::video::config_t &config, const std::string &display_name);
blend_t blend_alpha; cursor_t cursor;
blend_t blend_invert; D3D11_MAPPED_SUBRESOURCE img_info;
blend_t blend_disable; texture2d_t texture;
};
ps_t scene_ps; class display_vram_t: public display_base_t, public std::enable_shared_from_this<display_vram_t> {
vs_t scene_vs; public:
virtual capture_e
snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override;
gpu_cursor_t cursor_alpha; std::shared_ptr<img_t>
gpu_cursor_t cursor_xor; alloc_img() override;
int
dummy_img(img_t *img_base) override;
int
complete_img(img_t *img_base, bool dummy) override;
std::vector<DXGI_FORMAT>
get_supported_sdr_capture_formats() override;
std::vector<DXGI_FORMAT>
get_supported_hdr_capture_formats() override;
texture2d_t last_frame_copy; int
init(const ::video::config_t &config, const std::string &display_name);
std::atomic<uint32_t> next_image_id; std::shared_ptr<platf::hwdevice_t>
}; make_hwdevice(pix_fmt_e pix_fmt) override;
} // namespace platf::dxgi
sampler_state_t sampler_linear;
blend_t blend_alpha;
blend_t blend_invert;
blend_t blend_disable;
ps_t scene_ps;
vs_t scene_vs;
gpu_cursor_t cursor_alpha;
gpu_cursor_t cursor_xor;
texture2d_t last_frame_copy;
std::atomic<uint32_t> next_image_id;
};
} // namespace platf::dxgi
#endif #endif

File diff suppressed because it is too large Load diff

View file

@ -2,366 +2,378 @@
#include "src/main.h" #include "src/main.h"
namespace platf { namespace platf {
using namespace std::literals; using namespace std::literals;
} }
namespace platf::dxgi { namespace platf::dxgi {
struct img_t : public ::platf::img_t { struct img_t: public ::platf::img_t {
~img_t() override { ~img_t() override {
delete[] data; delete[] data;
data = nullptr; data = nullptr;
} }
}; };
void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { void
int height = cursor.shape_info.Height / 2; blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
int width = cursor.shape_info.Width; int height = cursor.shape_info.Height / 2;
int pitch = cursor.shape_info.Pitch; int width = cursor.shape_info.Width;
int pitch = cursor.shape_info.Pitch;
// img cursor.{x,y} < 0, skip parts of the cursor.img_data // img cursor.{x,y} < 0, skip parts of the cursor.img_data
auto cursor_skip_y = -std::min(0, cursor.y); auto cursor_skip_y = -std::min(0, cursor.y);
auto cursor_skip_x = -std::min(0, cursor.x); auto cursor_skip_x = -std::min(0, cursor.x);
// img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data
auto cursor_truncate_y = std::max(0, cursor.y - img.height); auto cursor_truncate_y = std::max(0, cursor.y - img.height);
auto cursor_truncate_x = std::max(0, cursor.x - img.width); auto cursor_truncate_x = std::max(0, cursor.x - img.width);
auto cursor_width = width - cursor_skip_x - cursor_truncate_x; auto cursor_width = width - cursor_skip_x - cursor_truncate_x;
auto cursor_height = height - cursor_skip_y - cursor_truncate_y; auto cursor_height = height - cursor_skip_y - cursor_truncate_y;
if(cursor_height > height || cursor_width > width) { if (cursor_height > height || cursor_width > width) {
return; return;
} }
auto img_skip_y = std::max(0, cursor.y); auto img_skip_y = std::max(0, cursor.y);
auto img_skip_x = std::max(0, cursor.x); auto img_skip_x = std::max(0, cursor.x);
auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch; auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch;
int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y));
int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x));
auto pixels_per_byte = width / pitch; auto pixels_per_byte = width / pitch;
auto bytes_per_row = delta_width / pixels_per_byte; auto bytes_per_row = delta_width / pixels_per_byte;
auto img_data = (int *)img.data; auto img_data = (int *) img.data;
for(int i = 0; i < delta_height; ++i) { for (int i = 0; i < delta_height; ++i) {
auto and_mask = &cursor_img_data[i * pitch]; auto and_mask = &cursor_img_data[i * pitch];
auto xor_mask = &cursor_img_data[(i + height) * pitch]; auto xor_mask = &cursor_img_data[(i + height) * pitch];
auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x];
auto skip_x = cursor_skip_x; auto skip_x = cursor_skip_x;
for(int x = 0; x < bytes_per_row; ++x) { for (int x = 0; x < bytes_per_row; ++x) {
for(auto bit = 0u; bit < 8; ++bit) { for (auto bit = 0u; bit < 8; ++bit) {
if(skip_x > 0) { if (skip_x > 0) {
--skip_x; --skip_x;
continue; continue;
}
int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0;
int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0;
*img_pixel_p &= and_;
*img_pixel_p ^= xor_;
++img_pixel_p;
} }
int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0; ++and_mask;
int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0; ++xor_mask;
}
}
}
*img_pixel_p &= and_; void
*img_pixel_p ^= xor_; apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
auto colors_out = (std::uint8_t *) &cursor_pixel;
auto colors_in = (std::uint8_t *) img_pixel_p;
//TODO: When use of IDXGIOutput5 is implemented, support different color formats
auto alpha = colors_out[3];
if (alpha == 255) {
*img_pixel_p = cursor_pixel;
}
else {
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
}
}
void
apply_color_masked(int *img_pixel_p, int cursor_pixel) {
//TODO: When use of IDXGIOutput5 is implemented, support different color formats
auto alpha = ((std::uint8_t *) &cursor_pixel)[3];
if (alpha == 0xFF) {
*img_pixel_p ^= cursor_pixel;
}
else {
*img_pixel_p = cursor_pixel;
}
}
void
blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
int height = cursor.shape_info.Height;
int width = cursor.shape_info.Width;
int pitch = cursor.shape_info.Pitch;
// img cursor.y < 0, skip parts of the cursor.img_data
auto cursor_skip_y = -std::min(0, cursor.y);
auto cursor_skip_x = -std::min(0, cursor.x);
// img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data
auto cursor_truncate_y = std::max(0, cursor.y - img.height);
auto cursor_truncate_x = std::max(0, cursor.x - img.width);
auto img_skip_y = std::max(0, cursor.y);
auto img_skip_x = std::max(0, cursor.x);
auto cursor_width = width - cursor_skip_x - cursor_truncate_x;
auto cursor_height = height - cursor_skip_y - cursor_truncate_y;
if (cursor_height > height || cursor_width > width) {
return;
}
auto cursor_img_data = (int *) &cursor.img_data[cursor_skip_y * pitch];
int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y));
int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x));
auto img_data = (int *) img.data;
for (int i = 0; i < delta_height; ++i) {
auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x];
auto cursor_end = &cursor_begin[delta_width];
auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x];
std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) {
if (masked) {
apply_color_masked(img_pixel_p, cursor_pixel);
}
else {
apply_color_alpha(img_pixel_p, cursor_pixel);
}
++img_pixel_p; ++img_pixel_p;
} });
++and_mask;
++xor_mask;
}
}
}
void apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
auto colors_out = (std::uint8_t *)&cursor_pixel;
auto colors_in = (std::uint8_t *)img_pixel_p;
//TODO: When use of IDXGIOutput5 is implemented, support different color formats
auto alpha = colors_out[3];
if(alpha == 255) {
*img_pixel_p = cursor_pixel;
}
else {
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
}
}
void apply_color_masked(int *img_pixel_p, int cursor_pixel) {
//TODO: When use of IDXGIOutput5 is implemented, support different color formats
auto alpha = ((std::uint8_t *)&cursor_pixel)[3];
if(alpha == 0xFF) {
*img_pixel_p ^= cursor_pixel;
}
else {
*img_pixel_p = cursor_pixel;
}
}
void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
int height = cursor.shape_info.Height;
int width = cursor.shape_info.Width;
int pitch = cursor.shape_info.Pitch;
// img cursor.y < 0, skip parts of the cursor.img_data
auto cursor_skip_y = -std::min(0, cursor.y);
auto cursor_skip_x = -std::min(0, cursor.x);
// img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data
auto cursor_truncate_y = std::max(0, cursor.y - img.height);
auto cursor_truncate_x = std::max(0, cursor.x - img.width);
auto img_skip_y = std::max(0, cursor.y);
auto img_skip_x = std::max(0, cursor.x);
auto cursor_width = width - cursor_skip_x - cursor_truncate_x;
auto cursor_height = height - cursor_skip_y - cursor_truncate_y;
if(cursor_height > height || cursor_width > width) {
return;
}
auto cursor_img_data = (int *)&cursor.img_data[cursor_skip_y * pitch];
int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y));
int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x));
auto img_data = (int *)img.data;
for(int i = 0; i < delta_height; ++i) {
auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x];
auto cursor_end = &cursor_begin[delta_width];
auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x];
std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) {
if(masked) {
apply_color_masked(img_pixel_p, cursor_pixel);
}
else {
apply_color_alpha(img_pixel_p, cursor_pixel);
}
++img_pixel_p;
});
}
}
void blend_cursor(const cursor_t &cursor, img_t &img) {
switch(cursor.shape_info.Type) {
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
blend_cursor_color(cursor, img, false);
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:
blend_cursor_monochrome(cursor, img);
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
blend_cursor_color(cursor, img, true);
break;
default:
BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']';
}
}
capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) {
auto img = (img_t *)img_base;
HRESULT status;
DXGI_OUTDUPL_FRAME_INFO frame_info;
resource_t::pointer res_p {};
auto capture_status = dup.next_frame(frame_info, timeout, &res_p);
resource_t res { res_p };
if(capture_status != capture_e::ok) {
return capture_status;
}
const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0;
const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0;
const bool update_flag = mouse_update_flag || frame_update_flag;
if(!update_flag) {
return capture_e::timeout;
}
if(frame_info.PointerShapeBufferSize > 0) {
auto &img_data = cursor.img_data;
img_data.resize(frame_info.PointerShapeBufferSize);
UINT dummy;
status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
} }
} }
if(frame_info.LastMouseUpdateTime.QuadPart) { void
cursor.x = frame_info.PointerPosition.Position.x; blend_cursor(const cursor_t &cursor, img_t &img) {
cursor.y = frame_info.PointerPosition.Position.y; switch (cursor.shape_info.Type) {
cursor.visible = frame_info.PointerPosition.Visible; case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
blend_cursor_color(cursor, img, false);
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:
blend_cursor_monochrome(cursor, img);
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
blend_cursor_color(cursor, img, true);
break;
default:
BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']';
}
} }
if(frame_update_flag) { capture_e
{ display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) {
texture2d_t src {}; auto img = (img_t *) img_base;
status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src);
HRESULT status;
DXGI_OUTDUPL_FRAME_INFO frame_info;
resource_t::pointer res_p {};
auto capture_status = dup.next_frame(frame_info, timeout, &res_p);
resource_t res { res_p };
if (capture_status != capture_e::ok) {
return capture_status;
}
const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0;
const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0;
const bool update_flag = mouse_update_flag || frame_update_flag;
if (!update_flag) {
return capture_e::timeout;
}
if (frame_info.PointerShapeBufferSize > 0) {
auto &img_data = cursor.img_data;
img_data.resize(frame_info.PointerShapeBufferSize);
UINT dummy;
status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info);
if (FAILED(status)) {
BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
}
if (frame_info.LastMouseUpdateTime.QuadPart) {
cursor.x = frame_info.PointerPosition.Position.x;
cursor.y = frame_info.PointerPosition.Position.y;
cursor.visible = frame_info.PointerPosition.Visible;
}
if (frame_update_flag) {
{
texture2d_t src {};
status = res->QueryInterface(IID_ID3D11Texture2D, (void **) &src);
if (FAILED(status)) {
BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
D3D11_TEXTURE2D_DESC desc;
src->GetDesc(&desc);
// If we don't know the capture format yet, grab it from this texture and create the staging texture
if (capture_format == DXGI_FORMAT_UNKNOWN) {
capture_format = desc.Format;
BOOST_LOG(info) << "Capture format ["sv << dxgi_format_to_string(capture_format) << ']';
D3D11_TEXTURE2D_DESC t {};
t.Width = width;
t.Height = height;
t.MipLevels = 1;
t.ArraySize = 1;
t.SampleDesc.Count = 1;
t.Usage = D3D11_USAGE_STAGING;
t.Format = capture_format;
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
auto status = device->CreateTexture2D(&t, nullptr, &texture);
if (FAILED(status)) {
BOOST_LOG(error) << "Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
}
// It's possible for our display enumeration to race with mode changes and result in
// mismatched image pool and desktop texture sizes. If this happens, just reinit again.
if (desc.Width != width || desc.Height != height) {
BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']';
return capture_e::reinit;
}
// It's also possible for the capture format to change on the fly. If that happens,
// reinitialize capture to try format detection again and create new images.
if (capture_format != desc.Format) {
BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']';
return capture_e::reinit;
}
//Copy from GPU to CPU
device_ctx->CopyResource(texture.get(), src.get());
}
}
// If we don't know the final capture format yet, encode a dummy image
if (capture_format == DXGI_FORMAT_UNKNOWN) {
BOOST_LOG(debug) << "Capture format is still unknown. Encoding a blank image"sv;
if (dummy_img(img)) {
return capture_e::error;
}
}
else {
// Map the staging texture for CPU access (making it inaccessible for the GPU)
status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info);
if (FAILED(status)) {
BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']';
if(FAILED(status)) {
BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error; return capture_e::error;
} }
D3D11_TEXTURE2D_DESC desc; // Now that we know the capture format, we can finish creating the image
src->GetDesc(&desc); if (complete_img(img, false)) {
device_ctx->Unmap(texture.get(), 0);
// If we don't know the capture format yet, grab it from this texture and create the staging texture img_info.pData = nullptr;
if(capture_format == DXGI_FORMAT_UNKNOWN) { return capture_e::error;
capture_format = desc.Format;
BOOST_LOG(info) << "Capture format ["sv << dxgi_format_to_string(capture_format) << ']';
D3D11_TEXTURE2D_DESC t {};
t.Width = width;
t.Height = height;
t.MipLevels = 1;
t.ArraySize = 1;
t.SampleDesc.Count = 1;
t.Usage = D3D11_USAGE_STAGING;
t.Format = capture_format;
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
auto status = device->CreateTexture2D(&t, nullptr, &texture);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
} }
// It's possible for our display enumeration to race with mode changes and result in std::copy_n((std::uint8_t *) img_info.pData, height * img_info.RowPitch, (std::uint8_t *) img->data);
// mismatched image pool and desktop texture sizes. If this happens, just reinit again.
if(desc.Width != width || desc.Height != height) {
BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']';
return capture_e::reinit;
}
// It's also possible for the capture format to change on the fly. If that happens, // Unmap the staging texture to allow GPU access again
// reinitialize capture to try format detection again and create new images.
if(capture_format != desc.Format) {
BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']';
return capture_e::reinit;
}
//Copy from GPU to CPU
device_ctx->CopyResource(texture.get(), src.get());
}
}
// If we don't know the final capture format yet, encode a dummy image
if(capture_format == DXGI_FORMAT_UNKNOWN) {
BOOST_LOG(debug) << "Capture format is still unknown. Encoding a blank image"sv;
if(dummy_img(img)) {
return capture_e::error;
}
}
else {
// Map the staging texture for CPU access (making it inaccessible for the GPU)
status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
// Now that we know the capture format, we can finish creating the image
if(complete_img(img, false)) {
device_ctx->Unmap(texture.get(), 0); device_ctx->Unmap(texture.get(), 0);
img_info.pData = nullptr; img_info.pData = nullptr;
return capture_e::error;
} }
std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data); if (cursor_visible && cursor.visible) {
blend_cursor(cursor, *img);
}
// Unmap the staging texture to allow GPU access again return capture_e::ok;
device_ctx->Unmap(texture.get(), 0);
img_info.pData = nullptr;
} }
if(cursor_visible && cursor.visible) { std::shared_ptr<platf::img_t>
blend_cursor(cursor, *img); display_ram_t::alloc_img() {
auto img = std::make_shared<img_t>();
// Initialize fields that are format-independent
img->width = width;
img->height = height;
return img;
} }
return capture_e::ok; int
} display_ram_t::complete_img(platf::img_t *img, bool dummy) {
// If this is not a dummy image, we must know the format by now
if (!dummy && capture_format == DXGI_FORMAT_UNKNOWN) {
BOOST_LOG(error) << "display_ram_t::complete_img() called with unknown capture format!";
return -1;
}
std::shared_ptr<platf::img_t> display_ram_t::alloc_img() { img->pixel_pitch = get_pixel_pitch();
auto img = std::make_shared<img_t>();
// Initialize fields that are format-independent if (dummy && !img->row_pitch) {
img->width = width; // Assume our dummy image will have no padding
img->height = height; img->row_pitch = img->pixel_pitch * img->width;
}
return img; // Reallocate the image buffer if the pitch changes
} if (!dummy && img->row_pitch != img_info.RowPitch) {
img->row_pitch = img_info.RowPitch;
delete img->data;
img->data = nullptr;
}
int display_ram_t::complete_img(platf::img_t *img, bool dummy) { if (!img->data) {
// If this is not a dummy image, we must know the format by now img->data = new std::uint8_t[img->row_pitch * height];
if(!dummy && capture_format == DXGI_FORMAT_UNKNOWN) { }
BOOST_LOG(error) << "display_ram_t::complete_img() called with unknown capture format!";
return -1; return 0;
} }
img->pixel_pitch = get_pixel_pitch(); int
display_ram_t::dummy_img(platf::img_t *img) {
if (complete_img(img, true)) {
return -1;
}
if(dummy && !img->row_pitch) { std::fill_n((std::uint8_t *) img->data, height * img->row_pitch, 0);
// Assume our dummy image will have no padding return 0;
img->row_pitch = img->pixel_pitch * img->width;
} }
// Reallocate the image buffer if the pitch changes std::vector<DXGI_FORMAT>
if(!dummy && img->row_pitch != img_info.RowPitch) { display_ram_t::get_supported_sdr_capture_formats() {
img->row_pitch = img_info.RowPitch; return { DXGI_FORMAT_B8G8R8A8_UNORM };
delete img->data;
img->data = nullptr;
} }
if(!img->data) { std::vector<DXGI_FORMAT>
img->data = new std::uint8_t[img->row_pitch * height]; display_ram_t::get_supported_hdr_capture_formats() {
// HDR is unsupported
return {};
} }
return 0; int
} display_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
if (display_base_t::init(config, display_name)) {
return -1;
}
int display_ram_t::dummy_img(platf::img_t *img) { return 0;
if(complete_img(img, true)) {
return -1;
} }
} // namespace platf::dxgi
std::fill_n((std::uint8_t *)img->data, height * img->row_pitch, 0);
return 0;
}
std::vector<DXGI_FORMAT> display_ram_t::get_supported_sdr_capture_formats() {
return { DXGI_FORMAT_B8G8R8A8_UNORM };
}
std::vector<DXGI_FORMAT> display_ram_t::get_supported_hdr_capture_formats() {
// HDR is unsupported
return {};
}
int display_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
if(display_base_t::init(config, display_name)) {
return -1;
}
return 0;
}
} // namespace platf::dxgi

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,8 +6,10 @@
#include <winnt.h> #include <winnt.h>
namespace platf { namespace platf {
void print_status(const std::string_view &prefix, HRESULT status); void
HDESK syncThreadDesktop(); print_status(const std::string_view &prefix, HRESULT status);
} // namespace platf HDESK
syncThreadDesktop();
} // namespace platf
#endif #endif

View file

@ -35,7 +35,7 @@ constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1;
#define SERVICE_DOMAIN "local" #define SERVICE_DOMAIN "local"
constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN); constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN);
constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN); constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN);
#ifndef __MINGW32__ #ifndef __MINGW32__
typedef struct _DNS_SERVICE_INSTANCE { typedef struct _DNS_SERVICE_INSTANCE {
@ -59,7 +59,8 @@ typedef struct _DNS_SERVICE_INSTANCE {
} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE; } DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE;
#endif #endif
typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE( typedef VOID WINAPI
DNS_SERVICE_REGISTER_COMPLETE(
_In_ DWORD Status, _In_ DWORD Status,
_In_ PVOID pQueryContext, _In_ PVOID pQueryContext,
_In_ PDNS_SERVICE_INSTANCE pInstance); _In_ PDNS_SERVICE_INSTANCE pInstance);
@ -88,122 +89,127 @@ _FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _I
} /* extern "C" */ } /* extern "C" */
namespace platf::publish { namespace platf::publish {
VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) { VOID WINAPI
auto alarm = (safe::alarm_t<PDNS_SERVICE_INSTANCE>::element_type *)pQueryContext; register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) {
auto alarm = (safe::alarm_t<PDNS_SERVICE_INSTANCE>::element_type *) pQueryContext;
if(status) { if (status) {
print_status("register_cb()"sv, status); print_status("register_cb()"sv, status);
}
alarm->ring(pInstance);
}
static int service(bool enable, PDNS_SERVICE_INSTANCE &existing_instance) {
auto alarm = safe::make_alarm<PDNS_SERVICE_INSTANCE>();
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() };
std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() };
auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local");
DNS_SERVICE_INSTANCE instance {};
instance.pszInstanceName = name.data();
instance.wPort = map_port(nvhttp::PORT_HTTP);
instance.pszHostName = host.data();
DNS_SERVICE_REGISTER_REQUEST req {};
req.Version = DNS_QUERY_REQUEST_VERSION1;
req.pQueryContext = alarm.get();
req.pServiceInstance = enable ? &instance : existing_instance;
req.pRegisterCompletionCallback = register_cb;
DNS_STATUS status {};
if(enable) {
status = _DnsServiceRegister(&req, nullptr);
if(status != DNS_REQUEST_PENDING) {
print_status("DnsServiceRegister()"sv, status);
return -1;
}
}
else {
status = _DnsServiceDeRegister(&req, nullptr);
if(status != DNS_REQUEST_PENDING) {
print_status("DnsServiceDeRegister()"sv, status);
return -1;
}
}
alarm->wait();
auto registered_instance = alarm->status();
if(enable) {
// Store this instance for later deregistration
existing_instance = registered_instance;
}
else if(registered_instance) {
// Deregistration was successful
_DnsServiceFreeInstance(registered_instance);
existing_instance = nullptr;
}
return registered_instance ? 0 : -1;
}
class mdns_registration_t : public ::platf::deinit_t {
public:
mdns_registration_t() : existing_instance(nullptr) {
if(service(true, existing_instance)) {
BOOST_LOG(error) << "Unable to register Sunshine mDNS service"sv;
return;
} }
BOOST_LOG(info) << "Registered Sunshine mDNS service"sv; alarm->ring(pInstance);
} }
~mdns_registration_t() override { static int
if(existing_instance) { service(bool enable, PDNS_SERVICE_INSTANCE &existing_instance) {
if(service(false, existing_instance)) { auto alarm = safe::make_alarm<PDNS_SERVICE_INSTANCE>();
BOOST_LOG(error) << "Unable to unregister Sunshine mDNS service"sv;
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() };
std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() };
auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local");
DNS_SERVICE_INSTANCE instance {};
instance.pszInstanceName = name.data();
instance.wPort = map_port(nvhttp::PORT_HTTP);
instance.pszHostName = host.data();
DNS_SERVICE_REGISTER_REQUEST req {};
req.Version = DNS_QUERY_REQUEST_VERSION1;
req.pQueryContext = alarm.get();
req.pServiceInstance = enable ? &instance : existing_instance;
req.pRegisterCompletionCallback = register_cb;
DNS_STATUS status {};
if (enable) {
status = _DnsServiceRegister(&req, nullptr);
if (status != DNS_REQUEST_PENDING) {
print_status("DnsServiceRegister()"sv, status);
return -1;
}
}
else {
status = _DnsServiceDeRegister(&req, nullptr);
if (status != DNS_REQUEST_PENDING) {
print_status("DnsServiceDeRegister()"sv, status);
return -1;
}
}
alarm->wait();
auto registered_instance = alarm->status();
if (enable) {
// Store this instance for later deregistration
existing_instance = registered_instance;
}
else if (registered_instance) {
// Deregistration was successful
_DnsServiceFreeInstance(registered_instance);
existing_instance = nullptr;
}
return registered_instance ? 0 : -1;
}
class mdns_registration_t: public ::platf::deinit_t {
public:
mdns_registration_t():
existing_instance(nullptr) {
if (service(true, existing_instance)) {
BOOST_LOG(error) << "Unable to register Sunshine mDNS service"sv;
return; return;
} }
BOOST_LOG(info) << "Unregistered Sunshine mDNS service"sv; BOOST_LOG(info) << "Registered Sunshine mDNS service"sv;
} }
~mdns_registration_t() override {
if (existing_instance) {
if (service(false, existing_instance)) {
BOOST_LOG(error) << "Unable to unregister Sunshine mDNS service"sv;
return;
}
BOOST_LOG(info) << "Unregistered Sunshine mDNS service"sv;
}
}
private:
PDNS_SERVICE_INSTANCE existing_instance;
};
int
load_funcs(HMODULE handle) {
auto fg = util::fail_guard([handle]() {
FreeLibrary(handle);
});
_DnsServiceFreeInstance = (_DnsServiceFreeInstance_fn) GetProcAddress(handle, "DnsServiceFreeInstance");
_DnsServiceDeRegister = (_DnsServiceDeRegister_fn) GetProcAddress(handle, "DnsServiceDeRegister");
_DnsServiceRegister = (_DnsServiceRegister_fn) GetProcAddress(handle, "DnsServiceRegister");
if (!(_DnsServiceFreeInstance && _DnsServiceDeRegister && _DnsServiceRegister)) {
BOOST_LOG(error) << "mDNS service not available in dnsapi.dll"sv;
return -1;
}
fg.disable();
return 0;
} }
private: std::unique_ptr<::platf::deinit_t>
PDNS_SERVICE_INSTANCE existing_instance; start() {
}; HMODULE handle = LoadLibrary("dnsapi.dll");
int load_funcs(HMODULE handle) { if (!handle || load_funcs(handle)) {
auto fg = util::fail_guard([handle]() { BOOST_LOG(error) << "Couldn't load dnsapi.dll, You'll need to add PC manually from Moonlight"sv;
FreeLibrary(handle); return nullptr;
}); }
_DnsServiceFreeInstance = (_DnsServiceFreeInstance_fn)GetProcAddress(handle, "DnsServiceFreeInstance"); return std::make_unique<mdns_registration_t>();
_DnsServiceDeRegister = (_DnsServiceDeRegister_fn)GetProcAddress(handle, "DnsServiceDeRegister");
_DnsServiceRegister = (_DnsServiceRegister_fn)GetProcAddress(handle, "DnsServiceRegister");
if(!(_DnsServiceFreeInstance && _DnsServiceDeRegister && _DnsServiceRegister)) {
BOOST_LOG(error) << "mDNS service not available in dnsapi.dll"sv;
return -1;
} }
} // namespace platf::publish
fg.disable();
return 0;
}
std::unique_ptr<::platf::deinit_t> start() {
HMODULE handle = LoadLibrary("dnsapi.dll");
if(!handle || load_funcs(handle)) {
BOOST_LOG(error) << "Couldn't load dnsapi.dll, You'll need to add PC manually from Moonlight"sv;
return nullptr;
}
return std::make_unique<mdns_registration_t>();
}
} // namespace platf::publish

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@
#define SUNSHINE_PROCESS_H #define SUNSHINE_PROCESS_H
#ifndef __kernel_entry #ifndef __kernel_entry
#define __kernel_entry #define __kernel_entry
#endif #endif
#include <optional> #include <optional>
@ -16,10 +16,10 @@
#include "utility.h" #include "utility.h"
namespace proc { namespace proc {
using file_t = util::safe_ptr_v2<FILE, int, fclose>; using file_t = util::safe_ptr_v2<FILE, int, fclose>;
typedef config::prep_cmd_t cmd_t; typedef config::prep_cmd_t cmd_t;
/* /*
* pre_cmds -- guaranteed to be executed unless any of the commands fail. * pre_cmds -- guaranteed to be executed unless any of the commands fail.
* detached -- commands detached from Sunshine * detached -- commands detached from Sunshine
* cmd -- Runs indefinitely until: * cmd -- Runs indefinitely until:
@ -31,78 +31,89 @@ typedef config::prep_cmd_t cmd_t;
* "null" -- The output of the commands are discarded * "null" -- The output of the commands are discarded
* filename -- The output of the commands are appended to filename * filename -- The output of the commands are appended to filename
*/ */
struct ctx_t { struct ctx_t {
std::vector<cmd_t> prep_cmds; std::vector<cmd_t> prep_cmds;
/** /**
* Some applications, such as Steam, * Some applications, such as Steam,
* either exit quickly, or keep running indefinitely. * either exit quickly, or keep running indefinitely.
* Steam.exe is one such application. * Steam.exe is one such application.
* That is why some applications need be run and forgotten about * That is why some applications need be run and forgotten about
*/ */
std::vector<std::string> detached; std::vector<std::string> detached;
std::string name; std::string name;
std::string cmd; std::string cmd;
std::string working_dir; std::string working_dir;
std::string output; std::string output;
std::string image_path; std::string image_path;
std::string id; std::string id;
}; };
class proc_t { class proc_t {
public: public:
KITTY_DEFAULT_CONSTR_MOVE_THROW(proc_t) KITTY_DEFAULT_CONSTR_MOVE_THROW(proc_t)
proc_t( proc_t(
boost::process::environment &&env, boost::process::environment &&env,
std::vector<ctx_t> &&apps) : _app_id(0), std::vector<ctx_t> &&apps):
_env(std::move(env)), _app_id(0),
_apps(std::move(apps)) {} _env(std::move(env)),
_apps(std::move(apps)) {}
int execute(int app_id); int
execute(int app_id);
/** /**
* @return _app_id if a process is running, otherwise returns 0 * @return _app_id if a process is running, otherwise returns 0
*/ */
int running(); int
running();
~proc_t(); ~proc_t();
const std::vector<ctx_t> &get_apps() const; const std::vector<ctx_t> &
std::vector<ctx_t> &get_apps(); get_apps() const;
std::string get_app_image(int app_id); std::vector<ctx_t> &
get_apps();
std::string
get_app_image(int app_id);
void terminate(); void
terminate();
private: private:
int _app_id; int _app_id;
boost::process::environment _env; boost::process::environment _env;
std::vector<ctx_t> _apps; std::vector<ctx_t> _apps;
ctx_t _app; ctx_t _app;
// If no command associated with _app_id, yet it's still running // If no command associated with _app_id, yet it's still running
bool placebo {}; bool placebo {};
boost::process::child _process; boost::process::child _process;
boost::process::group _process_handle; boost::process::group _process_handle;
file_t _pipe; file_t _pipe;
std::vector<cmd_t>::const_iterator _app_prep_it; std::vector<cmd_t>::const_iterator _app_prep_it;
std::vector<cmd_t>::const_iterator _app_prep_begin; std::vector<cmd_t>::const_iterator _app_prep_begin;
}; };
/** /**
* Calculate a stable id based on name and image data * Calculate a stable id based on name and image data
* @return tuple of id calculated without index (for use if no collision) and one with * @return tuple of id calculated without index (for use if no collision) and one with
*/ */
std::tuple<std::string, std::string> calculate_app_id(const std::string &app_name, std::string app_image_path, int index); std::tuple<std::string, std::string>
calculate_app_id(const std::string &app_name, std::string app_image_path, int index);
std::string validate_app_image_path(std::string app_image_path); std::string
void refresh(const std::string &file_name); validate_app_image_path(std::string app_image_path);
std::optional<proc::proc_t> parse(const std::string &file_name); void
refresh(const std::string &file_name);
std::optional<proc::proc_t>
parse(const std::string &file_name);
extern proc_t proc; extern proc_t proc;
} // namespace proc } // namespace proc
#endif // SUNSHINE_PROCESS_H #endif // SUNSHINE_PROCESS_H

View file

@ -4,154 +4,181 @@
#include <iterator> #include <iterator>
namespace round_robin_util { namespace round_robin_util {
template<class V, class T> template <class V, class T>
class it_wrap_t { class it_wrap_t {
public: public:
using iterator_category = std::random_access_iterator_tag; using iterator_category = std::random_access_iterator_tag;
using value_type = V; using value_type = V;
using difference_type = V; using difference_type = V;
using pointer = V *; using pointer = V *;
using reference = V &; using reference = V &;
typedef T iterator; typedef T iterator;
typedef std::ptrdiff_t diff_t; typedef std::ptrdiff_t diff_t;
iterator
operator+=(diff_t step) {
while (step-- > 0) {
++_this();
}
return _this();
}
iterator
operator-=(diff_t step) {
while (step-- > 0) {
--_this();
}
return _this();
}
iterator
operator+(diff_t step) {
iterator new_ = _this();
return new_ += step;
}
iterator
operator-(diff_t step) {
iterator new_ = _this();
return new_ -= step;
}
diff_t
operator-(iterator first) {
diff_t step = 0;
while (first != _this()) {
++step;
++first;
}
return step;
}
iterator
operator++() {
_this().inc();
return _this();
}
iterator
operator--() {
_this().dec();
return _this();
}
iterator
operator++(int) {
iterator new_ = _this();
iterator operator+=(diff_t step) {
while(step-- > 0) {
++_this(); ++_this();
return new_;
} }
return _this(); iterator
} operator--(int) {
iterator new_ = _this();
iterator operator-=(diff_t step) {
while(step-- > 0) {
--_this(); --_this();
return new_;
} }
return _this(); reference
} operator*() { return *_this().get(); }
const reference
operator*() const { return *_this().get(); }
iterator operator+(diff_t step) { pointer
iterator new_ = _this(); operator->() { return &*_this(); }
const pointer
operator->() const { return &*_this(); }
return new_ += step; bool
} operator!=(const iterator &other) const {
return !(_this() == other);
iterator operator-(diff_t step) {
iterator new_ = _this();
return new_ -= step;
}
diff_t operator-(iterator first) {
diff_t step = 0;
while(first != _this()) {
++step;
++first;
} }
return step; bool
} operator<(const iterator &other) const {
return !(_this() >= other);
iterator operator++() {
_this().inc();
return _this();
}
iterator operator--() {
_this().dec();
return _this();
}
iterator operator++(int) {
iterator new_ = _this();
++_this();
return new_;
}
iterator operator--(int) {
iterator new_ = _this();
--_this();
return new_;
}
reference operator*() { return *_this().get(); }
const reference operator*() const { return *_this().get(); }
pointer operator->() { return &*_this(); }
const pointer operator->() const { return &*_this(); }
bool operator!=(const iterator &other) const {
return !(_this() == other);
}
bool operator<(const iterator &other) const {
return !(_this() >= other);
}
bool operator>=(const iterator &other) const {
return _this() == other || _this() > other;
}
bool operator<=(const iterator &other) const {
return _this() == other || _this() < other;
}
bool operator==(const iterator &other) const { return _this().eq(other); };
bool operator>(const iterator &other) const { return _this().gt(other); }
private:
iterator &_this() { return *static_cast<iterator *>(this); }
const iterator &_this() const { return *static_cast<const iterator *>(this); }
};
template<class V, class It>
class round_robin_t : public it_wrap_t<V, round_robin_t<V, It>> {
public:
using iterator = It;
using pointer = V *;
round_robin_t(iterator begin, iterator end) : _begin(begin), _end(end), _pos(begin) {}
void inc() {
++_pos;
if(_pos == _end) {
_pos = _begin;
}
}
void dec() {
if(_pos == _begin) {
_pos = _end;
} }
--_pos; bool
operator>=(const iterator &other) const {
return _this() == other || _this() > other;
}
bool
operator<=(const iterator &other) const {
return _this() == other || _this() < other;
}
bool
operator==(const iterator &other) const { return _this().eq(other); };
bool
operator>(const iterator &other) const { return _this().gt(other); }
private:
iterator &
_this() { return *static_cast<iterator *>(this); }
const iterator &
_this() const { return *static_cast<const iterator *>(this); }
};
template <class V, class It>
class round_robin_t: public it_wrap_t<V, round_robin_t<V, It>> {
public:
using iterator = It;
using pointer = V *;
round_robin_t(iterator begin, iterator end):
_begin(begin), _end(end), _pos(begin) {}
void
inc() {
++_pos;
if (_pos == _end) {
_pos = _begin;
}
}
void
dec() {
if (_pos == _begin) {
_pos = _end;
}
--_pos;
}
bool
eq(const round_robin_t &other) const {
return *_pos == *other._pos;
}
pointer
get() const {
return &*_pos;
}
private:
It _begin;
It _end;
It _pos;
};
template <class V, class It>
round_robin_t<V, It>
make_round_robin(It begin, It end) {
return round_robin_t<V, It>(begin, end);
} }
} // namespace round_robin_util
bool eq(const round_robin_t &other) const {
return *_pos == *other._pos;
}
pointer get() const {
return &*_pos;
}
private:
It _begin;
It _end;
It _pos;
};
template<class V, class It>
round_robin_t<V, It> make_round_robin(It begin, It end) {
return round_robin_t<V, It>(begin, end);
}
} // namespace round_robin_util
#endif #endif

File diff suppressed because it is too large Load diff

View file

@ -9,20 +9,23 @@
#include "thread_safe.h" #include "thread_safe.h"
namespace rtsp_stream { namespace rtsp_stream {
constexpr auto RTSP_SETUP_PORT = 21; constexpr auto RTSP_SETUP_PORT = 21;
struct launch_session_t { struct launch_session_t {
crypto::aes_t gcm_key; crypto::aes_t gcm_key;
crypto::aes_t iv; crypto::aes_t iv;
bool host_audio; bool host_audio;
}; };
void launch_session_raise(launch_session_t launch_session); void
int session_count(); launch_session_raise(launch_session_t launch_session);
int
session_count();
void rtpThread(); void
rtpThread();
} // namespace rtsp_stream } // namespace rtsp_stream
#endif // SUNSHINE_RTSP_H #endif // SUNSHINE_RTSP_H

File diff suppressed because it is too large Load diff

View file

@ -10,39 +10,44 @@
#include "video.h" #include "video.h"
namespace stream { namespace stream {
constexpr auto VIDEO_STREAM_PORT = 9; constexpr auto VIDEO_STREAM_PORT = 9;
constexpr auto CONTROL_PORT = 10; constexpr auto CONTROL_PORT = 10;
constexpr auto AUDIO_STREAM_PORT = 11; constexpr auto AUDIO_STREAM_PORT = 11;
struct session_t; struct session_t;
struct config_t { struct config_t {
audio::config_t audio; audio::config_t audio;
video::config_t monitor; video::config_t monitor;
int packetsize; int packetsize;
int minRequiredFecPackets; int minRequiredFecPackets;
int featureFlags; int featureFlags;
int controlProtocolType; int controlProtocolType;
int audioQosType; int audioQosType;
int videoQosType; int videoQosType;
std::optional<int> gcmap; std::optional<int> gcmap;
}; };
namespace session { namespace session {
enum class state_e : int { enum class state_e : int {
STOPPED, STOPPED,
STOPPING, STOPPING,
STARTING, STARTING,
RUNNING, RUNNING,
}; };
std::shared_ptr<session_t> alloc(config_t &config, crypto::aes_t &gcm_key, crypto::aes_t &iv); std::shared_ptr<session_t>
int start(session_t &session, const std::string &addr_string); alloc(config_t &config, crypto::aes_t &gcm_key, crypto::aes_t &iv);
void stop(session_t &session); int
void join(session_t &session); start(session_t &session, const std::string &addr_string);
state_e state(session_t &session); void
} // namespace session stop(session_t &session);
} // namespace stream void
join(session_t &session);
state_e
state(session_t &session);
} // namespace session
} // namespace stream
#endif // SUNSHINE_STREAM_H #endif // SUNSHINE_STREAM_H

View file

@ -9,85 +9,94 @@
namespace sync_util { namespace sync_util {
template<class T, class M = std::mutex> template <class T, class M = std::mutex>
class sync_t { class sync_t {
public: public:
using value_t = T; using value_t = T;
using mutex_t = M; using mutex_t = M;
std::lock_guard<mutex_t> lock() { std::lock_guard<mutex_t>
return std::lock_guard { _lock }; lock() {
} return std::lock_guard { _lock };
}
template<class... Args> template <class... Args>
sync_t(Args &&...args) : raw { std::forward<Args>(args)... } {} sync_t(Args &&...args):
raw { std::forward<Args>(args)... } {}
sync_t &operator=(sync_t &&other) noexcept { sync_t &
std::lock(_lock, other._lock); operator=(sync_t &&other) noexcept {
std::lock(_lock, other._lock);
raw = std::move(other.raw); raw = std::move(other.raw);
_lock.unlock(); _lock.unlock();
other._lock.unlock(); other._lock.unlock();
return *this; return *this;
} }
sync_t &operator=(sync_t &other) noexcept { sync_t &
std::lock(_lock, other._lock); operator=(sync_t &other) noexcept {
std::lock(_lock, other._lock);
raw = other.raw; raw = other.raw;
_lock.unlock(); _lock.unlock();
other._lock.unlock(); other._lock.unlock();
return *this; return *this;
} }
template<class V> template <class V>
sync_t &operator=(V &&val) { sync_t &
auto lg = lock(); operator=(V &&val) {
auto lg = lock();
raw = val; raw = val;
return *this; return *this;
} }
sync_t &operator=(const value_t &val) noexcept { sync_t &
auto lg = lock(); operator=(const value_t &val) noexcept {
auto lg = lock();
raw = val; raw = val;
return *this; return *this;
} }
sync_t &operator=(value_t &&val) noexcept { sync_t &
auto lg = lock(); operator=(value_t &&val) noexcept {
auto lg = lock();
raw = std::move(val); raw = std::move(val);
return *this; return *this;
} }
value_t *operator->() { value_t *
return &raw; operator->() {
} return &raw;
}
value_t &operator*() { value_t &
return raw; operator*() {
} return raw;
}
const value_t &operator*() const { const value_t &
return raw; operator*() const {
} return raw;
}
value_t raw; value_t raw;
private: private:
mutex_t _lock; mutex_t _lock;
}; };
} // namespace sync_util } // namespace sync_util
#endif // SUNSHINE_SYNC_H
#endif // SUNSHINE_SYNC_H

View file

@ -4,200 +4,209 @@
// macros // macros
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 #if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
#if defined(_WIN32) || defined(_WIN64) #if defined(_WIN32) || defined(_WIN64)
#define TRAY_ICON WEB_DIR "images/favicon.ico" #define TRAY_ICON WEB_DIR "images/favicon.ico"
#elif defined(__linux__) || defined(linux) || defined(__linux) #elif defined(__linux__) || defined(linux) || defined(__linux)
#define TRAY_ICON "sunshine" #define TRAY_ICON "sunshine"
#elif defined(__APPLE__) || defined(__MACH__) #elif defined(__APPLE__) || defined(__MACH__)
#define TRAY_ICON WEB_DIR "images/logo-sunshine-16.png" #define TRAY_ICON WEB_DIR "images/logo-sunshine-16.png"
#include <dispatch/dispatch.h> #include <dispatch/dispatch.h>
#endif #endif
// standard includes // standard includes
#include <csignal> #include <csignal>
#include <string> #include <string>
// lib includes // lib includes
#include "tray/tray.h" #include "tray/tray.h"
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/process/environment.hpp> #include <boost/process/environment.hpp>
// local includes // local includes
#include "confighttp.h" #include "confighttp.h"
#include "main.h" #include "main.h"
#include "platform/common.h" #include "platform/common.h"
#include "process.h" #include "process.h"
using namespace std::literals; using namespace std::literals;
// system_tray namespace // system_tray namespace
namespace system_tray { namespace system_tray {
/** /**
* @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 open_url(const std::string &url) { void
boost::filesystem::path working_dir; open_url(const std::string &url) {
boost::filesystem::path working_dir;
// if windows // if windows
#if defined(_WIN32) || defined(_WIN64) #if defined(_WIN32) || defined(_WIN64)
// set working dir to Windows system directory // set working dir to Windows system directory
working_dir = boost::filesystem::path(std::getenv("SystemRoot")); working_dir = boost::filesystem::path(std::getenv("SystemRoot"));
// this isn't ideal as it briefly shows a command window // this isn't ideal as it briefly shows a command window
// but start a command built into cmd, not an executable // but start a command built into cmd, not an executable
std::string cmd = R"(cmd /C "start )" + url + R"(")"; std::string cmd = R"(cmd /C "start )" + url + R"(")";
#else // if unix #else // if unix
// set working dir to user home directory // set working dir to user home directory
working_dir = boost::filesystem::path(std::getenv("HOME")); working_dir = boost::filesystem::path(std::getenv("HOME"));
std::string cmd = R"(open ")" + url + R"(")"; std::string cmd = R"(open ")" + url + R"(")";
#endif #endif
boost::process::environment _env = boost::this_process::environment(); boost::process::environment _env = boost::this_process::environment();
std::error_code ec; std::error_code ec;
auto child = platf::run_unprivileged(cmd, working_dir, _env, nullptr, ec, nullptr); auto child = platf::run_unprivileged(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 {
BOOST_LOG(info) << "Opened url ["sv << url << "]"sv;
child.detach();
}
} }
else {
BOOST_LOG(info) << "Opened url ["sv << url << "]"sv;
child.detach();
}
}
/** /**
* @brief Callback for opening the UI from the system tray. * @brief Callback for opening the UI from the system tray.
* @param item The tray menu item. * @param item The tray menu item.
*/ */
void tray_open_ui_cb(struct tray_menu *item) { void
BOOST_LOG(info) << "Opening UI from system tray"sv; tray_open_ui_cb(struct tray_menu *item) {
BOOST_LOG(info) << "Opening UI from system tray"sv;
// create the url with the port // create the url with the port
std::string url = "https://localhost:" + std::to_string(map_port(confighttp::PORT_HTTPS)); std::string url = "https://localhost:" + std::to_string(map_port(confighttp::PORT_HTTPS));
// open the url in the default web browser // open the url in the default web browser
open_url(url); open_url(url);
} }
/** /**
* @brief Callback for opening GitHub Sponsors from the system tray. * @brief Callback for opening GitHub Sponsors from the system tray.
* @param item The tray menu item. * @param item The tray menu item.
*/ */
void tray_donate_github_cb(struct tray_menu *item) { void
open_url("https://github.com/sponsors/LizardByte"); tray_donate_github_cb(struct tray_menu *item) {
} open_url("https://github.com/sponsors/LizardByte");
}
/** /**
* @brief Callback for opening MEE6 donation from the system tray. * @brief Callback for opening MEE6 donation from the system tray.
* @param item The tray menu item. * @param item The tray menu item.
*/ */
void tray_donate_mee6_cb(struct tray_menu *item) { void
open_url("https://mee6.xyz/m/804382334370578482"); tray_donate_mee6_cb(struct tray_menu *item) {
} open_url("https://mee6.xyz/m/804382334370578482");
}
/** /**
* @brief Callback for opening Patreon from the system tray. * @brief Callback for opening Patreon from the system tray.
* @param item The tray menu item. * @param item The tray menu item.
*/ */
void tray_donate_patreon_cb(struct tray_menu *item) { void
open_url("https://www.patreon.com/LizardByte"); tray_donate_patreon_cb(struct tray_menu *item) {
} open_url("https://www.patreon.com/LizardByte");
}
/** /**
* @brief Callback for opening PayPal donation from the system tray. * @brief Callback for opening PayPal donation from the system tray.
* @param item The tray menu item. * @param item The tray menu item.
*/ */
void tray_donate_paypal_cb(struct tray_menu *item) { void
open_url("https://www.paypal.com/paypalme/ReenigneArcher"); tray_donate_paypal_cb(struct tray_menu *item) {
} open_url("https://www.paypal.com/paypalme/ReenigneArcher");
}
/** /**
* @brief Callback for exiting Sunshine from the system tray. * @brief Callback for exiting Sunshine from the system tray.
* @param item The tray menu item. * @param item The tray menu item.
*/ */
void tray_quit_cb(struct tray_menu *item) { void
BOOST_LOG(info) << "Quiting from system tray"sv; tray_quit_cb(struct tray_menu *item) {
BOOST_LOG(info) << "Quiting from system tray"sv;
std::raise(SIGINT); std::raise(SIGINT);
} }
// Tray menu // Tray menu
static struct tray tray = { static struct tray tray = {
.icon = TRAY_ICON, .icon = TRAY_ICON,
#if defined(_WIN32) || defined(_WIN64) #if defined(_WIN32) || defined(_WIN64)
.tooltip = const_cast<char *>("Sunshine"), // cast the string literal to a non-const char* pointer .tooltip = const_cast<char *>("Sunshine"), // cast the string literal to a non-const char* pointer
#endif #endif
.menu = .menu =
(struct tray_menu[]) { (struct tray_menu[]) {
// todo - use boost/locale to translate menu strings // todo - use boost/locale to translate menu strings
{ .text = "Open Sunshine", .cb = tray_open_ui_cb }, { .text = "Open Sunshine", .cb = tray_open_ui_cb },
{ .text = "-" }, { .text = "-" },
{ .text = "Donate", { .text = "Donate",
.submenu = .submenu =
(struct tray_menu[]) { (struct tray_menu[]) {
{ .text = "GitHub Sponsors", .cb = tray_donate_github_cb }, { .text = "GitHub Sponsors", .cb = tray_donate_github_cb },
{ .text = "MEE6", .cb = tray_donate_mee6_cb }, { .text = "MEE6", .cb = tray_donate_mee6_cb },
{ .text = "Patreon", .cb = tray_donate_patreon_cb }, { .text = "Patreon", .cb = tray_donate_patreon_cb },
{ .text = "PayPal", .cb = tray_donate_paypal_cb }, { .text = "PayPal", .cb = tray_donate_paypal_cb },
{ .text = nullptr } } }, { .text = nullptr } } },
{ .text = "-" }, { .text = "-" },
{ .text = "Quit", .cb = tray_quit_cb }, { .text = "Quit", .cb = tray_quit_cb },
{ .text = nullptr } }, { .text = nullptr } },
}; };
/**
/**
* @brief Create the system tray. * @brief Create the system tray.
* @details This function has an endless loop, so it should be run in a separate thread. * @details This function has an endless loop, so it should be run in a separate thread.
* @return 1 if the system tray failed to create, otherwise 0 once the tray has been terminated. * @return 1 if the system tray failed to create, otherwise 0 once the tray has been terminated.
*/ */
int system_tray() { int
if(tray_init(&tray) < 0) { system_tray() {
BOOST_LOG(warning) << "Failed to create system tray"sv; if (tray_init(&tray) < 0) {
return 1; BOOST_LOG(warning) << "Failed to create system tray"sv;
} return 1;
else { }
BOOST_LOG(info) << "System tray created"sv; else {
BOOST_LOG(info) << "System tray created"sv;
}
while (tray_loop(1) == 0) {
BOOST_LOG(debug) << "System tray loop"sv;
}
return 0;
} }
while(tray_loop(1) == 0) { /**
BOOST_LOG(debug) << "System tray loop"sv;
}
return 0;
}
/**
* @brief Run the system tray with platform specific options. * @brief Run the system tray with platform specific options.
* @note macOS requires that UI elements be created on the main thread, so the system tray is not implemented for macOS. * @note macOS requires that UI elements be created on the main thread, so the system tray is not implemented for macOS.
*/ */
void run_tray() { void
// create the system tray run_tray() {
#if defined(__APPLE__) || defined(__MACH__) // create the system tray
// macOS requires that UI elements be created on the main thread #if defined(__APPLE__) || defined(__MACH__)
// creating tray using dispatch queue does not work, although the code doesn't actually throw any (visible) errors // macOS requires that UI elements be created on the main thread
// creating tray using dispatch queue does not work, although the code doesn't actually throw any (visible) errors
// dispatch_async(dispatch_get_main_queue(), ^{ // dispatch_async(dispatch_get_main_queue(), ^{
// system_tray(); // system_tray();
// }); // });
BOOST_LOG(info) << "system_tray() is not yet implemented for this platform."sv; BOOST_LOG(info) << "system_tray() is not yet implemented for this platform."sv;
#else // Windows, Linux #else // Windows, Linux
// create tray in separate thread // create tray in separate thread
std::thread tray_thread(system_tray); std::thread tray_thread(system_tray);
tray_thread.detach(); tray_thread.detach();
#endif #endif
} }
/** /**
* @brief Exit the system tray. * @brief Exit the system tray.
* @return 0 after exiting the system tray. * @return 0 after exiting the system tray.
*/ */
int end_tray() { int
tray_exit(); end_tray() {
return 0; tray_exit();
} return 0;
}
} // namespace system_tray } // namespace system_tray
#endif #endif

View file

@ -7,17 +7,27 @@
// system_tray namespace // system_tray namespace
namespace system_tray { namespace system_tray {
void open_url(const std::string &url); void
void tray_open_ui_cb(struct tray_menu *item); open_url(const std::string &url);
void tray_donate_github_cb(struct tray_menu *item); void
void tray_donate_mee6_cb(struct tray_menu *item); tray_open_ui_cb(struct tray_menu *item);
void tray_donate_patreon_cb(struct tray_menu *item); void
void tray_donate_paypal_cb(struct tray_menu *item); tray_donate_github_cb(struct tray_menu *item);
void tray_quit_cb(struct tray_menu *item); void
tray_donate_mee6_cb(struct tray_menu *item);
void
tray_donate_patreon_cb(struct tray_menu *item);
void
tray_donate_paypal_cb(struct tray_menu *item);
void
tray_quit_cb(struct tray_menu *item);
int system_tray(); int
int run_tray(); system_tray();
int end_tray(); int
run_tray();
int
end_tray();
} // namespace system_tray } // namespace system_tray
#endif #endif

View file

@ -15,231 +15,246 @@
#include "utility.h" #include "utility.h"
namespace task_pool_util { namespace task_pool_util {
class _ImplBase { class _ImplBase {
public:
// _unique_base_type _this_ptr;
inline virtual ~_ImplBase() = default;
virtual void run() = 0;
};
template<class Function>
class _Impl : public _ImplBase {
Function _func;
public:
_Impl(Function &&f) : _func(std::forward<Function>(f)) {}
void run() override {
_func();
}
};
class TaskPool {
public:
typedef std::unique_ptr<_ImplBase> __task;
typedef _ImplBase *task_id_t;
typedef std::chrono::steady_clock::time_point __time_point;
template<class R>
class timer_task_t {
public: public:
task_id_t task_id; // _unique_base_type _this_ptr;
std::future<R> future;
timer_task_t(task_id_t task_id, std::future<R> &future) : task_id { task_id }, future { std::move(future) } {} inline virtual ~_ImplBase() = default;
virtual void
run() = 0;
}; };
protected: template <class Function>
std::deque<__task> _tasks; class _Impl: public _ImplBase {
std::vector<std::pair<__time_point, __task>> _timer_tasks; Function _func;
std::mutex _task_mutex;
public: public:
TaskPool() = default; _Impl(Function &&f):
TaskPool(TaskPool &&other) noexcept : _tasks { std::move(other._tasks) }, _timer_tasks { std::move(other._timer_tasks) } {} _func(std::forward<Function>(f)) {}
TaskPool &operator=(TaskPool &&other) noexcept { void
std::swap(_tasks, other._tasks); run() override {
std::swap(_timer_tasks, other._timer_tasks); _func();
}
};
return *this; class TaskPool {
} public:
typedef std::unique_ptr<_ImplBase> __task;
typedef _ImplBase *task_id_t;
template<class Function, class... Args> typedef std::chrono::steady_clock::time_point __time_point;
auto push(Function &&newTask, Args &&...args) {
static_assert(std::is_invocable_v<Function, Args &&...>, "arguments don't match the function");
using __return = std::invoke_result_t<Function, Args &&...>; template <class R>
using task_t = std::packaged_task<__return()>; class timer_task_t {
public:
task_id_t task_id;
std::future<R> future;
auto bind = [task = std::forward<Function>(newTask), tuple_args = std::make_tuple(std::forward<Args>(args)...)]() mutable { timer_task_t(task_id_t task_id, std::future<R> &future):
return std::apply(task, std::move(tuple_args)); task_id { task_id }, future { std::move(future) } {}
}; };
task_t task(std::move(bind)); protected:
std::deque<__task> _tasks;
std::vector<std::pair<__time_point, __task>> _timer_tasks;
std::mutex _task_mutex;
auto future = task.get_future(); public:
TaskPool() = default;
TaskPool(TaskPool &&other) noexcept:
_tasks { std::move(other._tasks) }, _timer_tasks { std::move(other._timer_tasks) } {}
std::lock_guard<std::mutex> lg(_task_mutex); TaskPool &
_tasks.emplace_back(toRunnable(std::move(task))); operator=(TaskPool &&other) noexcept {
std::swap(_tasks, other._tasks);
std::swap(_timer_tasks, other._timer_tasks);
return future; return *this;
}
void pushDelayed(std::pair<__time_point, __task> &&task) {
std::lock_guard lg(_task_mutex);
auto it = _timer_tasks.cbegin();
for(; it < _timer_tasks.cend(); ++it) {
if(std::get<0>(*it) < task.first) {
break;
}
} }
_timer_tasks.emplace(it, task.first, std::move(task.second)); template <class Function, class... Args>
} auto
push(Function &&newTask, Args &&...args) {
static_assert(std::is_invocable_v<Function, Args &&...>, "arguments don't match the function");
/** using __return = std::invoke_result_t<Function, Args &&...>;
using task_t = std::packaged_task<__return()>;
auto bind = [task = std::forward<Function>(newTask), tuple_args = std::make_tuple(std::forward<Args>(args)...)]() mutable {
return std::apply(task, std::move(tuple_args));
};
task_t task(std::move(bind));
auto future = task.get_future();
std::lock_guard<std::mutex> lg(_task_mutex);
_tasks.emplace_back(toRunnable(std::move(task)));
return future;
}
void
pushDelayed(std::pair<__time_point, __task> &&task) {
std::lock_guard lg(_task_mutex);
auto it = _timer_tasks.cbegin();
for (; it < _timer_tasks.cend(); ++it) {
if (std::get<0>(*it) < task.first) {
break;
}
}
_timer_tasks.emplace(it, task.first, std::move(task.second));
}
/**
* @return an id to potentially delay the task * @return an id to potentially delay the task
*/ */
template<class Function, class X, class Y, class... Args> template <class Function, class X, class Y, class... Args>
auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&...args) { auto
static_assert(std::is_invocable_v<Function, Args &&...>, "arguments don't match the function"); pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&...args) {
static_assert(std::is_invocable_v<Function, Args &&...>, "arguments don't match the function");
using __return = std::invoke_result_t<Function, Args &&...>; using __return = std::invoke_result_t<Function, Args &&...>;
using task_t = std::packaged_task<__return()>; using task_t = std::packaged_task<__return()>;
__time_point time_point; __time_point time_point;
if constexpr(std::is_floating_point_v<X>) { if constexpr (std::is_floating_point_v<X>) {
time_point = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::nanoseconds>(duration); time_point = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
} }
else { else {
time_point = std::chrono::steady_clock::now() + duration; time_point = std::chrono::steady_clock::now() + duration;
}
auto bind = [task = std::forward<Function>(newTask), tuple_args = std::make_tuple(std::forward<Args>(args)...)]() mutable {
return std::apply(task, std::move(tuple_args));
};
task_t task(std::move(bind));
auto future = task.get_future();
auto runnable = toRunnable(std::move(task));
task_id_t task_id = &*runnable;
pushDelayed(std::pair { time_point, std::move(runnable) });
return timer_task_t<__return> { task_id, future };
} }
auto bind = [task = std::forward<Function>(newTask), tuple_args = std::make_tuple(std::forward<Args>(args)...)]() mutable { /**
return std::apply(task, std::move(tuple_args));
};
task_t task(std::move(bind));
auto future = task.get_future();
auto runnable = toRunnable(std::move(task));
task_id_t task_id = &*runnable;
pushDelayed(std::pair { time_point, std::move(runnable) });
return timer_task_t<__return> { task_id, future };
}
/**
* @param duration The delay before executing the task * @param duration The delay before executing the task
*/ */
template<class X, class Y> template <class X, class Y>
void delay(task_id_t task_id, std::chrono::duration<X, Y> duration) { void
std::lock_guard<std::mutex> lg(_task_mutex); delay(task_id_t task_id, std::chrono::duration<X, Y> duration) {
std::lock_guard<std::mutex> lg(_task_mutex);
auto it = _timer_tasks.begin(); auto it = _timer_tasks.begin();
for(; it < _timer_tasks.cend(); ++it) { for (; it < _timer_tasks.cend(); ++it) {
const __task &task = std::get<1>(*it); const __task &task = std::get<1>(*it);
if(&*task == task_id) { if (&*task == task_id) {
std::get<0>(*it) = std::chrono::steady_clock::now() + duration; std::get<0>(*it) = std::chrono::steady_clock::now() + duration;
break; break;
}
}
if (it == _timer_tasks.cend()) {
return;
}
// smaller time goes to the back
auto prev = it - 1;
while (it > _timer_tasks.cbegin()) {
if (std::get<0>(*it) > std::get<0>(*prev)) {
std::swap(*it, *prev);
}
--prev;
--it;
} }
} }
if(it == _timer_tasks.cend()) { bool
return; cancel(task_id_t task_id) {
} std::lock_guard lg(_task_mutex);
// smaller time goes to the back auto it = _timer_tasks.begin();
auto prev = it - 1; for (; it < _timer_tasks.cend(); ++it) {
while(it > _timer_tasks.cbegin()) { const __task &task = std::get<1>(*it);
if(std::get<0>(*it) > std::get<0>(*prev)) {
std::swap(*it, *prev); if (&*task == task_id) {
_timer_tasks.erase(it);
return true;
}
} }
--prev; return false;
--it;
} }
}
bool cancel(task_id_t task_id) { std::optional<std::pair<__time_point, __task>>
std::lock_guard lg(_task_mutex); pop(task_id_t task_id) {
std::lock_guard lg(_task_mutex);
auto it = _timer_tasks.begin(); auto pos = std::find_if(std::begin(_timer_tasks), std::end(_timer_tasks), [&task_id](const auto &t) { return t.second.get() == task_id; });
for(; it < _timer_tasks.cend(); ++it) {
const __task &task = std::get<1>(*it);
if(&*task == task_id) { if (pos == std::end(_timer_tasks)) {
_timer_tasks.erase(it); return std::nullopt;
return true;
} }
return std::move(*pos);
} }
return false; std::optional<__task>
} pop() {
std::lock_guard lg(_task_mutex);
std::optional<std::pair<__time_point, __task>> pop(task_id_t task_id) { if (!_tasks.empty()) {
std::lock_guard lg(_task_mutex); __task task = std::move(_tasks.front());
_tasks.pop_front();
return std::move(task);
}
auto pos = std::find_if(std::begin(_timer_tasks), std::end(_timer_tasks), [&task_id](const auto &t) { return t.second.get() == task_id; }); if (!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now()) {
__task task = std::move(std::get<1>(_timer_tasks.back()));
_timer_tasks.pop_back();
return std::move(task);
}
if(pos == std::end(_timer_tasks)) {
return std::nullopt; return std::nullopt;
} }
return std::move(*pos); bool
} ready() {
std::lock_guard<std::mutex> lg(_task_mutex);
std::optional<__task> pop() { return !_tasks.empty() || (!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now());
std::lock_guard lg(_task_mutex);
if(!_tasks.empty()) {
__task task = std::move(_tasks.front());
_tasks.pop_front();
return std::move(task);
} }
if(!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now()) { std::optional<__time_point>
__task task = std::move(std::get<1>(_timer_tasks.back())); next() {
_timer_tasks.pop_back(); std::lock_guard<std::mutex> lg(_task_mutex);
return std::move(task); if (_timer_tasks.empty()) {
return std::nullopt;
}
return std::get<0>(_timer_tasks.back());
} }
return std::nullopt; private:
} template <class Function>
std::unique_ptr<_ImplBase>
bool ready() { toRunnable(Function &&f) {
std::lock_guard<std::mutex> lg(_task_mutex); return std::make_unique<_Impl<Function>>(std::forward<Function &&>(f));
return !_tasks.empty() || (!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now());
}
std::optional<__time_point> next() {
std::lock_guard<std::mutex> lg(_task_mutex);
if(_timer_tasks.empty()) {
return std::nullopt;
} }
};
return std::get<0>(_timer_tasks.back()); } // namespace task_pool_util
}
private:
template<class Function>
std::unique_ptr<_ImplBase> toRunnable(Function &&f) {
return std::make_unique<_Impl<Function>>(std::forward<Function &&>(f));
}
};
} // namespace task_pool_util
#endif #endif

View file

@ -5,117 +5,126 @@
#include <thread> #include <thread>
namespace thread_pool_util { namespace thread_pool_util {
/* /*
* Allow threads to execute unhindered * Allow threads to execute unhindered
* while keeping full control over the threads. * while keeping full control over the threads.
*/ */
class ThreadPool : public task_pool_util::TaskPool { class ThreadPool: public task_pool_util::TaskPool {
public: public:
typedef TaskPool::__task __task; typedef TaskPool::__task __task;
private: private:
std::vector<std::thread> _thread; std::vector<std::thread> _thread;
std::condition_variable _cv; std::condition_variable _cv;
std::mutex _lock; std::mutex _lock;
bool _continue; bool _continue;
public: public:
ThreadPool() : _continue { false } {} ThreadPool():
_continue { false } {}
explicit ThreadPool(int threads) : _thread(threads), _continue { true } { explicit ThreadPool(int threads):
for(auto &t : _thread) { _thread(threads), _continue { true } {
t = std::thread(&ThreadPool::_main, this); for (auto &t : _thread) {
} t = std::thread(&ThreadPool::_main, this);
}
~ThreadPool() noexcept {
if(!_continue) return;
stop();
join();
}
template<class Function, class... Args>
auto push(Function &&newTask, Args &&...args) {
std::lock_guard lg(_lock);
auto future = TaskPool::push(std::forward<Function>(newTask), std::forward<Args>(args)...);
_cv.notify_one();
return future;
}
void pushDelayed(std::pair<__time_point, __task> &&task) {
std::lock_guard lg(_lock);
TaskPool::pushDelayed(std::move(task));
}
template<class Function, class X, class Y, class... Args>
auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&...args) {
std::lock_guard lg(_lock);
auto future = TaskPool::pushDelayed(std::forward<Function>(newTask), duration, std::forward<Args>(args)...);
// Update all timers for wait_until
_cv.notify_all();
return future;
}
void start(int threads) {
_continue = true;
_thread.resize(threads);
for(auto &t : _thread) {
t = std::thread(&ThreadPool::_main, this);
}
}
void stop() {
std::lock_guard lg(_lock);
_continue = false;
_cv.notify_all();
}
void join() {
for(auto &t : _thread) {
t.join();
}
}
public:
void _main() {
while(_continue) {
if(auto task = this->pop()) {
(*task)->run();
} }
else { }
std::unique_lock uniq_lock(_lock);
if(ready()) { ~ThreadPool() noexcept {
continue; if (!_continue) return;
}
if(!_continue) { stop();
break; join();
} }
if(auto tp = next()) { template <class Function, class... Args>
_cv.wait_until(uniq_lock, *tp); auto
push(Function &&newTask, Args &&...args) {
std::lock_guard lg(_lock);
auto future = TaskPool::push(std::forward<Function>(newTask), std::forward<Args>(args)...);
_cv.notify_one();
return future;
}
void
pushDelayed(std::pair<__time_point, __task> &&task) {
std::lock_guard lg(_lock);
TaskPool::pushDelayed(std::move(task));
}
template <class Function, class X, class Y, class... Args>
auto
pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&...args) {
std::lock_guard lg(_lock);
auto future = TaskPool::pushDelayed(std::forward<Function>(newTask), duration, std::forward<Args>(args)...);
// Update all timers for wait_until
_cv.notify_all();
return future;
}
void
start(int threads) {
_continue = true;
_thread.resize(threads);
for (auto &t : _thread) {
t = std::thread(&ThreadPool::_main, this);
}
}
void
stop() {
std::lock_guard lg(_lock);
_continue = false;
_cv.notify_all();
}
void
join() {
for (auto &t : _thread) {
t.join();
}
}
public:
void
_main() {
while (_continue) {
if (auto task = this->pop()) {
(*task)->run();
} }
else { else {
_cv.wait(uniq_lock); std::unique_lock uniq_lock(_lock);
if (ready()) {
continue;
}
if (!_continue) {
break;
}
if (auto tp = next()) {
_cv.wait_until(uniq_lock, *tp);
}
else {
_cv.wait(uniq_lock);
}
} }
} }
}
// Execute remaining tasks // Execute remaining tasks
while(auto task = this->pop()) { while (auto task = this->pop()) {
(*task)->run(); (*task)->run();
}
} }
} };
}; } // namespace thread_pool_util
} // namespace thread_pool_util
#endif #endif

File diff suppressed because it is too large Load diff

View file

@ -14,173 +14,175 @@
using namespace std::literals; using namespace std::literals;
namespace upnp { namespace upnp {
constexpr auto INET6_ADDRESS_STRLEN = 46; constexpr auto INET6_ADDRESS_STRLEN = 46;
constexpr auto IPv4 = 0; constexpr auto IPv4 = 0;
constexpr auto IPv6 = 1; constexpr auto IPv6 = 1;
using device_t = util::safe_ptr<UPNPDev, freeUPNPDevlist>; using device_t = util::safe_ptr<UPNPDev, freeUPNPDevlist>;
KITTY_USING_MOVE_T(urls_t, UPNPUrls, , { KITTY_USING_MOVE_T(urls_t, UPNPUrls, , {
FreeUPNPUrls(&el); FreeUPNPUrls(&el);
}); });
struct mapping_t { struct mapping_t {
struct { struct {
std::string wan; std::string wan;
std::string lan; std::string lan;
} port; } port;
std::string description; std::string description;
bool tcp; bool tcp;
};
void unmap(
const urls_t &urls,
const IGDdatas &data,
std::vector<mapping_t>::const_reverse_iterator begin,
std::vector<mapping_t>::const_reverse_iterator end) {
BOOST_LOG(debug) << "Unmapping UPNP ports"sv;
for(auto it = begin; it != end; ++it) {
auto status = UPNP_DeletePortMapping(
urls->controlURL,
data.first.servicetype,
it->port.wan.c_str(),
it->tcp ? "TCP" : "UDP",
nullptr);
if(status) {
BOOST_LOG(warning) << "Failed to unmap port ["sv << it->port.wan << "] to port ["sv << it->port.lan << "]: error code ["sv << status << ']';
break;
}
}
}
class deinit_t : public platf::deinit_t {
public:
using iter_t = std::vector<mapping_t>::const_reverse_iterator;
deinit_t(urls_t &&urls, IGDdatas data, std::vector<mapping_t> &&mapping)
: urls { std::move(urls) }, data { data }, mapping { std::move(mapping) } {}
~deinit_t() {
BOOST_LOG(info) << "Unmapping UPNP ports..."sv;
unmap(urls, data, std::rbegin(mapping), std::rend(mapping));
}
urls_t urls;
IGDdatas data;
std::vector<mapping_t> mapping;
};
static std::string_view status_string(int status) {
switch(status) {
case 0:
return "No IGD device found"sv;
case 1:
return "Valid IGD device found"sv;
case 2:
return "Valid IGD device found, but it isn't connected"sv;
case 3:
return "A UPnP device has been found, but it wasn't recognized as an IGD"sv;
}
return "Unknown status"sv;
}
std::unique_ptr<platf::deinit_t> start() {
if(!config::sunshine.flags[config::flag::UPNP]) {
return nullptr;
}
int err {};
device_t device { upnpDiscover(2000, nullptr, nullptr, 0, IPv4, 2, &err) };
if(!device || err) {
BOOST_LOG(error) << "Couldn't discover any UPNP devices"sv;
return nullptr;
}
for(auto dev = device.get(); dev != nullptr; dev = dev->pNext) {
BOOST_LOG(debug) << "Found device: "sv << dev->descURL;
}
std::array<char, INET6_ADDRESS_STRLEN> lan_addr;
std::array<char, INET6_ADDRESS_STRLEN> wan_addr;
urls_t urls;
IGDdatas data;
auto status = UPNP_GetValidIGD(device.get(), &urls.el, &data, lan_addr.data(), lan_addr.size());
if(status != 1 && status != 2) {
BOOST_LOG(error) << status_string(status);
return nullptr;
}
BOOST_LOG(debug) << "Found valid IGD device: "sv << urls->rootdescURL;
if(UPNP_GetExternalIPAddress(urls->controlURL, data.first.servicetype, wan_addr.data())) {
BOOST_LOG(warning) << "Could not get external ip"sv;
}
else {
BOOST_LOG(debug) << "Found external ip: "sv << wan_addr.data();
if(config::nvhttp.external_ip.empty()) {
config::nvhttp.external_ip = wan_addr.data();
}
}
auto rtsp = std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT));
auto video = std::to_string(map_port(stream::VIDEO_STREAM_PORT));
auto audio = std::to_string(map_port(stream::AUDIO_STREAM_PORT));
auto control = std::to_string(map_port(stream::CONTROL_PORT));
auto gs_http = std::to_string(map_port(nvhttp::PORT_HTTP));
auto gs_https = std::to_string(map_port(nvhttp::PORT_HTTPS));
auto wm_http = std::to_string(map_port(confighttp::PORT_HTTPS));
std::vector<mapping_t> mappings {
{ rtsp, rtsp, "RTSP setup port"s, true },
{ video, video, "Video stream port"s, false },
{ audio, audio, "Control stream port"s, false },
{ control, control, "Audio stream port"s, false },
{ gs_http, gs_http, "Gamestream http port"s, true },
{ gs_https, gs_https, "Gamestream https port"s, true },
}; };
// Only map port for the Web Manager if it is configured to accept connection from WAN void
if(net::from_enum_string(config::nvhttp.origin_web_ui_allowed) > net::LAN) { unmap(
mappings.emplace_back(mapping_t { wm_http, wm_http, "Sunshine Web UI port"s, true }); const urls_t &urls,
} const IGDdatas &data,
std::vector<mapping_t>::const_reverse_iterator begin,
std::vector<mapping_t>::const_reverse_iterator end) {
BOOST_LOG(debug) << "Unmapping UPNP ports"sv;
auto it = std::begin(mappings); for (auto it = begin; it != end; ++it) {
auto status = UPNP_DeletePortMapping(
urls->controlURL,
data.first.servicetype,
it->port.wan.c_str(),
it->tcp ? "TCP" : "UDP",
nullptr);
status = 0; if (status) {
for(; it != std::end(mappings); ++it) { BOOST_LOG(warning) << "Failed to unmap port ["sv << it->port.wan << "] to port ["sv << it->port.lan << "]: error code ["sv << status << ']';
status = UPNP_AddPortMapping( break;
urls->controlURL, }
data.first.servicetype,
it->port.wan.c_str(),
it->port.lan.c_str(),
lan_addr.data(),
it->description.c_str(),
it->tcp ? "TCP" : "UDP",
nullptr,
"86400");
if(status) {
BOOST_LOG(error) << "Failed to map port ["sv << it->port.wan << "] to port ["sv << it->port.lan << "]: error code ["sv << status << ']';
break;
} }
} }
if(status) { class deinit_t: public platf::deinit_t {
unmap(urls, data, std::make_reverse_iterator(it), std::rend(mappings)); public:
using iter_t = std::vector<mapping_t>::const_reverse_iterator;
deinit_t(urls_t &&urls, IGDdatas data, std::vector<mapping_t> &&mapping):
urls { std::move(urls) }, data { data }, mapping { std::move(mapping) } {}
return nullptr; ~deinit_t() {
BOOST_LOG(info) << "Unmapping UPNP ports..."sv;
unmap(urls, data, std::rbegin(mapping), std::rend(mapping));
}
urls_t urls;
IGDdatas data;
std::vector<mapping_t> mapping;
};
static std::string_view
status_string(int status) {
switch (status) {
case 0:
return "No IGD device found"sv;
case 1:
return "Valid IGD device found"sv;
case 2:
return "Valid IGD device found, but it isn't connected"sv;
case 3:
return "A UPnP device has been found, but it wasn't recognized as an IGD"sv;
}
return "Unknown status"sv;
} }
return std::make_unique<deinit_t>(std::move(urls), data, std::move(mappings)); std::unique_ptr<platf::deinit_t>
} start() {
} // namespace upnp if (!config::sunshine.flags[config::flag::UPNP]) {
return nullptr;
}
int err {};
device_t device { upnpDiscover(2000, nullptr, nullptr, 0, IPv4, 2, &err) };
if (!device || err) {
BOOST_LOG(error) << "Couldn't discover any UPNP devices"sv;
return nullptr;
}
for (auto dev = device.get(); dev != nullptr; dev = dev->pNext) {
BOOST_LOG(debug) << "Found device: "sv << dev->descURL;
}
std::array<char, INET6_ADDRESS_STRLEN> lan_addr;
std::array<char, INET6_ADDRESS_STRLEN> wan_addr;
urls_t urls;
IGDdatas data;
auto status = UPNP_GetValidIGD(device.get(), &urls.el, &data, lan_addr.data(), lan_addr.size());
if (status != 1 && status != 2) {
BOOST_LOG(error) << status_string(status);
return nullptr;
}
BOOST_LOG(debug) << "Found valid IGD device: "sv << urls->rootdescURL;
if (UPNP_GetExternalIPAddress(urls->controlURL, data.first.servicetype, wan_addr.data())) {
BOOST_LOG(warning) << "Could not get external ip"sv;
}
else {
BOOST_LOG(debug) << "Found external ip: "sv << wan_addr.data();
if (config::nvhttp.external_ip.empty()) {
config::nvhttp.external_ip = wan_addr.data();
}
}
auto rtsp = std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT));
auto video = std::to_string(map_port(stream::VIDEO_STREAM_PORT));
auto audio = std::to_string(map_port(stream::AUDIO_STREAM_PORT));
auto control = std::to_string(map_port(stream::CONTROL_PORT));
auto gs_http = std::to_string(map_port(nvhttp::PORT_HTTP));
auto gs_https = std::to_string(map_port(nvhttp::PORT_HTTPS));
auto wm_http = std::to_string(map_port(confighttp::PORT_HTTPS));
std::vector<mapping_t> mappings {
{ rtsp, rtsp, "RTSP setup port"s, true },
{ video, video, "Video stream port"s, false },
{ audio, audio, "Control stream port"s, false },
{ control, control, "Audio stream port"s, false },
{ gs_http, gs_http, "Gamestream http port"s, true },
{ gs_https, gs_https, "Gamestream https port"s, true },
};
// Only map port for the Web Manager if it is configured to accept connection from WAN
if (net::from_enum_string(config::nvhttp.origin_web_ui_allowed) > net::LAN) {
mappings.emplace_back(mapping_t { wm_http, wm_http, "Sunshine Web UI port"s, true });
}
auto it = std::begin(mappings);
status = 0;
for (; it != std::end(mappings); ++it) {
status = UPNP_AddPortMapping(
urls->controlURL,
data.first.servicetype,
it->port.wan.c_str(),
it->port.lan.c_str(),
lan_addr.data(),
it->description.c_str(),
it->tcp ? "TCP" : "UDP",
nullptr,
"86400");
if (status) {
BOOST_LOG(error) << "Failed to map port ["sv << it->port.wan << "] to port ["sv << it->port.lan << "]: error code ["sv << status << ']';
break;
}
}
if (status) {
unmap(urls, data, std::make_reverse_iterator(it), std::rend(mappings));
return nullptr;
}
return std::make_unique<deinit_t>(std::move(urls), data, std::move(mappings));
}
} // namespace upnp

View file

@ -4,7 +4,8 @@
#include "platform/common.h" #include "platform/common.h"
namespace upnp { namespace upnp {
[[nodiscard]] std::unique_ptr<platf::deinit_t> start(); [[nodiscard]] std::unique_ptr<platf::deinit_t>
start();
} }
#endif #endif

File diff suppressed because it is too large Load diff

View file

@ -6,72 +6,78 @@
#include <random> #include <random>
namespace uuid_util { namespace uuid_util {
union uuid_t { union uuid_t {
std::uint8_t b8[16]; std::uint8_t b8[16];
std::uint16_t b16[8]; std::uint16_t b16[8];
std::uint32_t b32[4]; std::uint32_t b32[4];
std::uint64_t b64[2]; std::uint64_t b64[2];
static uuid_t generate(std::default_random_engine &engine) { static uuid_t
std::uniform_int_distribution<std::uint8_t> dist(0, std::numeric_limits<std::uint8_t>::max()); generate(std::default_random_engine &engine) {
std::uniform_int_distribution<std::uint8_t> dist(0, std::numeric_limits<std::uint8_t>::max());
uuid_t buf; uuid_t buf;
for(auto &el : buf.b8) { for (auto &el : buf.b8) {
el = dist(engine); el = dist(engine);
}
buf.b8[7] &= (std::uint8_t) 0b00101111;
buf.b8[9] &= (std::uint8_t) 0b10011111;
return buf;
} }
buf.b8[7] &= (std::uint8_t)0b00101111; static uuid_t
buf.b8[9] &= (std::uint8_t)0b10011111; generate() {
std::random_device r;
return buf; std::default_random_engine engine { r() };
}
static uuid_t generate() { return generate(engine);
std::random_device r;
std::default_random_engine engine { r() };
return generate(engine);
}
[[nodiscard]] std::string string() const {
std::string result;
result.reserve(sizeof(uuid_t) * 2 + 4);
auto hex = util::hex(*this, true);
auto hex_view = hex.to_string_view();
std::string_view slices[] = {
hex_view.substr(0, 8),
hex_view.substr(8, 4),
hex_view.substr(12, 4),
hex_view.substr(16, 4)
};
auto last_slice = hex_view.substr(20, 12);
for(auto &slice : slices) {
std::copy(std::begin(slice), std::end(slice), std::back_inserter(result));
result.push_back('-');
} }
std::copy(std::begin(last_slice), std::end(last_slice), std::back_inserter(result)); [[nodiscard]] std::string
string() const {
std::string result;
return result; result.reserve(sizeof(uuid_t) * 2 + 4);
}
constexpr bool operator==(const uuid_t &other) const { auto hex = util::hex(*this, true);
return b64[0] == other.b64[0] && b64[1] == other.b64[1]; auto hex_view = hex.to_string_view();
}
constexpr bool operator<(const uuid_t &other) const { std::string_view slices[] = {
return (b64[0] < other.b64[0] || (b64[0] == other.b64[0] && b64[1] < other.b64[1])); hex_view.substr(0, 8),
} hex_view.substr(8, 4),
hex_view.substr(12, 4),
hex_view.substr(16, 4)
};
auto last_slice = hex_view.substr(20, 12);
constexpr bool operator>(const uuid_t &other) const { for (auto &slice : slices) {
return (b64[0] > other.b64[0] || (b64[0] == other.b64[0] && b64[1] > other.b64[1])); std::copy(std::begin(slice), std::end(slice), std::back_inserter(result));
}
}; result.push_back('-');
} // namespace uuid_util }
#endif // T_MAN_UUID_H
std::copy(std::begin(last_slice), std::end(last_slice), std::back_inserter(result));
return result;
}
constexpr bool
operator==(const uuid_t &other) const {
return b64[0] == other.b64[0] && b64[1] == other.b64[1];
}
constexpr bool
operator<(const uuid_t &other) const {
return (b64[0] < other.b64[0] || (b64[0] == other.b64[0] && b64[1] < other.b64[1]));
}
constexpr bool
operator>(const uuid_t &other) const {
return (b64[0] > other.b64[0] || (b64[0] == other.b64[0] && b64[1] > other.b64[1]));
}
};
} // namespace uuid_util
#endif // T_MAN_UUID_H

File diff suppressed because it is too large Load diff

View file

@ -14,82 +14,90 @@ extern "C" {
struct AVPacket; struct AVPacket;
namespace video { namespace video {
struct packet_raw_t { struct packet_raw_t {
void init_packet() { void
this->av_packet = av_packet_alloc(); init_packet() {
} this->av_packet = av_packet_alloc();
}
template<class P> template <class P>
explicit packet_raw_t(P *user_data) : channel_data { user_data } { explicit packet_raw_t(P *user_data):
init_packet(); channel_data { user_data } {
} init_packet();
}
explicit packet_raw_t(std::nullptr_t) : channel_data { nullptr } { explicit packet_raw_t(std::nullptr_t):
init_packet(); channel_data { nullptr } {
} init_packet();
}
~packet_raw_t() { ~packet_raw_t() {
av_packet_unref(this->av_packet); av_packet_unref(this->av_packet);
} }
struct replace_t { struct replace_t {
std::string_view old; std::string_view old;
std::string_view _new; std::string_view _new;
KITTY_DEFAULT_CONSTR_MOVE(replace_t) KITTY_DEFAULT_CONSTR_MOVE(replace_t)
replace_t(std::string_view old, std::string_view _new) noexcept : old { std::move(old) }, _new { std::move(_new) } {} replace_t(std::string_view old, std::string_view _new) noexcept:
old { std::move(old) }, _new { std::move(_new) } {}
};
AVPacket *av_packet;
std::vector<replace_t> *replacements;
void *channel_data;
}; };
AVPacket *av_packet; using packet_t = std::unique_ptr<packet_raw_t>;
std::vector<replace_t> *replacements;
void *channel_data;
};
using packet_t = std::unique_ptr<packet_raw_t>; struct hdr_info_raw_t {
explicit hdr_info_raw_t(bool enabled):
enabled { enabled }, metadata {} {};
explicit hdr_info_raw_t(bool enabled, const SS_HDR_METADATA &metadata):
enabled { enabled }, metadata { metadata } {};
struct hdr_info_raw_t { bool enabled;
explicit hdr_info_raw_t(bool enabled) : enabled { enabled }, metadata {} {}; SS_HDR_METADATA metadata;
explicit hdr_info_raw_t(bool enabled, const SS_HDR_METADATA &metadata) : enabled { enabled }, metadata { metadata } {}; };
bool enabled; using hdr_info_t = std::unique_ptr<hdr_info_raw_t>;
SS_HDR_METADATA metadata;
};
using hdr_info_t = std::unique_ptr<hdr_info_raw_t>; struct config_t {
int width;
int height;
int framerate;
int bitrate;
int slicesPerFrame;
int numRefFrames;
int encoderCscMode;
int videoFormat;
int dynamicRange;
};
struct config_t { using float4 = float[4];
int width; using float3 = float[3];
int height; using float2 = float[2];
int framerate;
int bitrate;
int slicesPerFrame;
int numRefFrames;
int encoderCscMode;
int videoFormat;
int dynamicRange;
};
using float4 = float[4]; struct alignas(16) color_t {
using float3 = float[3]; float4 color_vec_y;
using float2 = float[2]; float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
struct alignas(16) color_t { extern color_t colors[6];
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
extern color_t colors[6]; void
capture(
safe::mail_t mail,
config_t config,
void *channel_data);
void capture( int
safe::mail_t mail, init();
config_t config, } // namespace video
void *channel_data);
int init(); #endif // SUNSHINE_VIDEO_H
} // namespace video
#endif // SUNSHINE_VIDEO_H

@ -1 +1 @@
Subproject commit 726404ef5590ea4bfcc7d9fa40cadcb2638a5b82 Subproject commit 9e842ba1c3a6efbb90d9b7e9346a55b1a3d10494

@ -1 +1 @@
Subproject commit 4a29f4eeaf7d207f171b5afb06327a2b6912b14c Subproject commit 22034b2bafd873308a1857da749eabe9b537b33b

@ -1 +1 @@
Subproject commit 05eff5066370a9c223b56ae8c5d0684e0d06a0fe Subproject commit d8f29a064caabdeb78f263a5017a5dbdaa454eb6

@ -1 +1 @@
Subproject commit 645dcc56665a5a3c4f7a5284a1e42abc76cb5278 Subproject commit 5876c4b765670deaeb6927c1785085bbf1a98b96

@ -1 +1 @@
Subproject commit 759f5fc2165ef070bd300edcc691c48ccb8f84f8 Subproject commit d75ce5ffeef371cedb6fbbb23ac5603e34c2994b

@ -1 +1 @@
Subproject commit b05f94a92ee0a3de4742f6b4897556b6b84f5dd5 Subproject commit c4d6360a59d149b1ea097a5658ab1193e20db643

View file

@ -28,11 +28,11 @@
*/ */
#ifndef EGLAPI #ifndef EGLAPI
#define EGLAPI KHRONOS_APICALL #define EGLAPI KHRONOS_APICALL
#endif #endif
#ifndef EGLAPIENTRY #ifndef EGLAPIENTRY
#define EGLAPIENTRY KHRONOS_APIENTRY #define EGLAPIENTRY KHRONOS_APIENTRY
#endif #endif
#define EGLAPIENTRYP EGLAPIENTRY * #define EGLAPIENTRYP EGLAPIENTRY *
@ -55,10 +55,10 @@ typedef void *EGLNativePixmapType;
typedef void *EGLNativeWindowType; typedef void *EGLNativeWindowType;
#elif defined(_WIN32) || defined(__VC32__) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) /* Win32 and WinCE */ #elif defined(_WIN32) || defined(__VC32__) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) /* Win32 and WinCE */
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1 #define WIN32_LEAN_AND_MEAN 1
#endif #endif
#include <windows.h> #include <windows.h>
typedef HDC EGLNativeDisplayType; typedef HDC EGLNativeDisplayType;
typedef HBITMAP EGLNativePixmapType; typedef HBITMAP EGLNativePixmapType;
@ -111,9 +111,9 @@ typedef khronos_uintptr_t EGLNativeWindowType;
#elif defined(__unix__) || defined(USE_X11) #elif defined(__unix__) || defined(USE_X11)
/* X11 (tentative) */ /* X11 (tentative) */
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
typedef Display *EGLNativeDisplayType; typedef Display *EGLNativeDisplayType;
typedef Pixmap EGLNativePixmapType; typedef Pixmap EGLNativePixmapType;
@ -127,7 +127,7 @@ typedef void *EGLNativeWindowType;
#elif defined(__HAIKU__) #elif defined(__HAIKU__)
#include <kernel/image.h> #include <kernel/image.h>
typedef void *EGLNativeDisplayType; typedef void *EGLNativeDisplayType;
typedef khronos_uintptr_t EGLNativePixmapType; typedef khronos_uintptr_t EGLNativePixmapType;
@ -140,7 +140,7 @@ typedef khronos_uintptr_t EGLNativePixmapType;
typedef khronos_uintptr_t EGLNativeWindowType; typedef khronos_uintptr_t EGLNativeWindowType;
#else #else
#error "Platform not recognized" #error "Platform not recognized"
#endif #endif
/* EGL 1.2 types, renamed for consistency in EGL 1.3 */ /* EGL 1.2 types, renamed for consistency in EGL 1.3 */
@ -148,7 +148,6 @@ typedef EGLNativeDisplayType NativeDisplayType;
typedef EGLNativePixmapType NativePixmapType; typedef EGLNativePixmapType NativePixmapType;
typedef EGLNativeWindowType NativeWindowType; typedef EGLNativeWindowType NativeWindowType;
/* Define EGLint. This must be a signed integral type large enough to contain /* Define EGLint. This must be a signed integral type large enough to contain
* all legal attribute names and values passed into and out of EGL, whether * all legal attribute names and values passed into and out of EGL, whether
* their type is boolean, bitmask, enumerant (symbolic constant), integer, * their type is boolean, bitmask, enumerant (symbolic constant), integer,
@ -158,12 +157,11 @@ typedef EGLNativeWindowType NativeWindowType;
*/ */
typedef khronos_int32_t EGLint; typedef khronos_int32_t EGLint;
/* C++ / C typecast macros for special EGL handle values */ /* C++ / C typecast macros for special EGL handle values */
#if defined(__cplusplus) #if defined(__cplusplus)
#define EGL_CAST(type, value) (static_cast<type>(value)) #define EGL_CAST(type, value) (static_cast<type>(value))
#else #else
#define EGL_CAST(type, value) ((type)(value)) #define EGL_CAST(type, value) ((type) (value))
#endif #endif
#endif /* __eglplatform_h */ #endif /* __eglplatform_h */

View file

@ -91,7 +91,7 @@
*/ */
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) #if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
#define KHRONOS_STATIC 1 #define KHRONOS_STATIC 1
#endif #endif
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
@ -100,17 +100,17 @@
* This precedes the return type of the function in the function prototype. * This precedes the return type of the function in the function prototype.
*/ */
#if defined(KHRONOS_STATIC) #if defined(KHRONOS_STATIC)
/* If the preprocessor constant KHRONOS_STATIC is defined, make the /* If the preprocessor constant KHRONOS_STATIC is defined, make the
* header compatible with static linking. */ * header compatible with static linking. */
#define KHRONOS_APICALL #define KHRONOS_APICALL
#elif defined(_WIN32) #elif defined(_WIN32)
#define KHRONOS_APICALL __declspec(dllimport) #define KHRONOS_APICALL __declspec(dllimport)
#elif defined(__SYMBIAN32__) #elif defined(__SYMBIAN32__)
#define KHRONOS_APICALL IMPORT_C #define KHRONOS_APICALL IMPORT_C
#elif defined(__ANDROID__) #elif defined(__ANDROID__)
#define KHRONOS_APICALL __attribute__((visibility("default"))) #define KHRONOS_APICALL __attribute__((visibility("default")))
#else #else
#define KHRONOS_APICALL #define KHRONOS_APICALL
#endif #endif
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
@ -120,10 +120,10 @@
* name in the function prototype. * name in the function prototype.
*/ */
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) #if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
/* Win32 but not WinCE */ /* Win32 but not WinCE */
#define KHRONOS_APIENTRY __stdcall #define KHRONOS_APIENTRY __stdcall
#else #else
#define KHRONOS_APIENTRY #define KHRONOS_APIENTRY
#endif #endif
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
@ -132,40 +132,39 @@
* This follows the closing parenthesis of the function prototype arguments. * This follows the closing parenthesis of the function prototype arguments.
*/ */
#if defined(__ARMCC_2__) #if defined(__ARMCC_2__)
#define KHRONOS_APIATTRIBUTES __softfp #define KHRONOS_APIATTRIBUTES __softfp
#else #else
#define KHRONOS_APIATTRIBUTES #define KHRONOS_APIATTRIBUTES
#endif #endif
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
* basic type definitions * basic type definitions
*-----------------------------------------------------------------------*/ *-----------------------------------------------------------------------*/
#if(defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
/*
/*
* Using <stdint.h> * Using <stdint.h>
*/ */
#include <stdint.h> #include <stdint.h>
typedef int32_t khronos_int32_t; typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t; typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t; typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t; typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1 #define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__VMS) || defined(__sgi) #elif defined(__VMS) || defined(__sgi)
/* /*
* Using <inttypes.h> * Using <inttypes.h>
*/ */
#include <inttypes.h> #include <inttypes.h>
typedef int32_t khronos_int32_t; typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t; typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t; typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t; typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1 #define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__) #elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
@ -176,8 +175,8 @@ typedef __int32 khronos_int32_t;
typedef unsigned __int32 khronos_uint32_t; typedef unsigned __int32 khronos_uint32_t;
typedef __int64 khronos_int64_t; typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t; typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1 #define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__) #elif defined(__sun__) || defined(__digital__)
@ -186,15 +185,15 @@ typedef unsigned __int64 khronos_uint64_t;
*/ */
typedef int khronos_int32_t; typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t; typedef unsigned int khronos_uint32_t;
#if defined(__arch64__) || defined(_LP64) #if defined(__arch64__) || defined(_LP64)
typedef long int khronos_int64_t; typedef long int khronos_int64_t;
typedef unsigned long int khronos_uint64_t; typedef unsigned long int khronos_uint64_t;
#else #else
typedef long long int khronos_int64_t; typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t; typedef unsigned long long int khronos_uint64_t;
#endif /* __arch64__ */ #endif /* __arch64__ */
#define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1 #define KHRONOS_SUPPORT_FLOAT 1
#elif 0 #elif 0
@ -203,25 +202,24 @@ typedef unsigned long long int khronos_uint64_t;
*/ */
typedef int khronos_int32_t; typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t; typedef unsigned int khronos_uint32_t;
#define KHRONOS_SUPPORT_INT64 0 #define KHRONOS_SUPPORT_INT64 0
#define KHRONOS_SUPPORT_FLOAT 0 #define KHRONOS_SUPPORT_FLOAT 0
#else #else
/* /*
* Generic fallback * Generic fallback
*/ */
#include <stdint.h> #include <stdint.h>
typedef int32_t khronos_int32_t; typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t; typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t; typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t; typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1 #define KHRONOS_SUPPORT_FLOAT 1
#endif #endif
/* /*
* Types that are (so far) the same on all platforms * Types that are (so far) the same on all platforms
*/ */
@ -272,7 +270,7 @@ typedef khronos_int64_t khronos_stime_nanoseconds_t;
* Dummy value used to pad enum types to 32 bits. * Dummy value used to pad enum types to 32 bits.
*/ */
#ifndef KHRONOS_MAX_ENUM #ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF #define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif #endif
/* /*
@ -282,8 +280,8 @@ typedef khronos_int64_t khronos_stime_nanoseconds_t;
* comparisons should not be made against KHRONOS_TRUE. * comparisons should not be made against KHRONOS_TRUE.
*/ */
typedef enum { typedef enum {
KHRONOS_FALSE = 0, KHRONOS_FALSE = 0,
KHRONOS_TRUE = 1, KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t; } khronos_boolean_enum_t;

View file

@ -28,7 +28,6 @@
#ifndef GLAD_EGL_H_ #ifndef GLAD_EGL_H_
#define GLAD_EGL_H_ #define GLAD_EGL_H_
#define GLAD_EGL #define GLAD_EGL
#define GLAD_OPTION_EGL_LOADER #define GLAD_OPTION_EGL_LOADER
@ -37,108 +36,108 @@ extern "C" {
#endif #endif
#ifndef GLAD_PLATFORM_H_ #ifndef GLAD_PLATFORM_H_
#define GLAD_PLATFORM_H_ #define GLAD_PLATFORM_H_
#ifndef GLAD_PLATFORM_WIN32 #ifndef GLAD_PLATFORM_WIN32
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__MINGW32__) #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__MINGW32__)
#define GLAD_PLATFORM_WIN32 1 #define GLAD_PLATFORM_WIN32 1
#else #else
#define GLAD_PLATFORM_WIN32 0 #define GLAD_PLATFORM_WIN32 0
#endif #endif
#endif #endif
#ifndef GLAD_PLATFORM_APPLE #ifndef GLAD_PLATFORM_APPLE
#ifdef __APPLE__ #ifdef __APPLE__
#define GLAD_PLATFORM_APPLE 1 #define GLAD_PLATFORM_APPLE 1
#else #else
#define GLAD_PLATFORM_APPLE 0 #define GLAD_PLATFORM_APPLE 0
#endif #endif
#endif #endif
#ifndef GLAD_PLATFORM_EMSCRIPTEN #ifndef GLAD_PLATFORM_EMSCRIPTEN
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
#define GLAD_PLATFORM_EMSCRIPTEN 1 #define GLAD_PLATFORM_EMSCRIPTEN 1
#else #else
#define GLAD_PLATFORM_EMSCRIPTEN 0 #define GLAD_PLATFORM_EMSCRIPTEN 0
#endif #endif
#endif #endif
#ifndef GLAD_PLATFORM_UWP #ifndef GLAD_PLATFORM_UWP
#if defined(_MSC_VER) && !defined(GLAD_INTERNAL_HAVE_WINAPIFAMILY) #if defined(_MSC_VER) && !defined(GLAD_INTERNAL_HAVE_WINAPIFAMILY)
#ifdef __has_include #ifdef __has_include
#if __has_include(<winapifamily.h>) #if __has_include(<winapifamily.h>)
#define GLAD_INTERNAL_HAVE_WINAPIFAMILY 1 #define GLAD_INTERNAL_HAVE_WINAPIFAMILY 1
#endif #endif
#elif _MSC_VER >= 1700 && !_USING_V110_SDK71_ #elif _MSC_VER >= 1700 && !_USING_V110_SDK71_
#define GLAD_INTERNAL_HAVE_WINAPIFAMILY 1 #define GLAD_INTERNAL_HAVE_WINAPIFAMILY 1
#endif #endif
#endif #endif
#ifdef GLAD_INTERNAL_HAVE_WINAPIFAMILY #ifdef GLAD_INTERNAL_HAVE_WINAPIFAMILY
#include <winapifamily.h> #include <winapifamily.h>
#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) #if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)
#define GLAD_PLATFORM_UWP 1 #define GLAD_PLATFORM_UWP 1
#endif #endif
#endif #endif
#ifndef GLAD_PLATFORM_UWP #ifndef GLAD_PLATFORM_UWP
#define GLAD_PLATFORM_UWP 0 #define GLAD_PLATFORM_UWP 0
#endif #endif
#endif #endif
#ifdef __GNUC__ #ifdef __GNUC__
#define GLAD_GNUC_EXTENSION __extension__ #define GLAD_GNUC_EXTENSION __extension__
#else #else
#define GLAD_GNUC_EXTENSION #define GLAD_GNUC_EXTENSION
#endif #endif
#ifndef GLAD_API_CALL #ifndef GLAD_API_CALL
#if defined(GLAD_API_CALL_EXPORT) #if defined(GLAD_API_CALL_EXPORT)
#if GLAD_PLATFORM_WIN32 || defined(__CYGWIN__) #if GLAD_PLATFORM_WIN32 || defined(__CYGWIN__)
#if defined(GLAD_API_CALL_EXPORT_BUILD) #if defined(GLAD_API_CALL_EXPORT_BUILD)
#if defined(__GNUC__) #if defined(__GNUC__)
#define GLAD_API_CALL __attribute__((dllexport)) extern #define GLAD_API_CALL __attribute__((dllexport)) extern
#else #else
#define GLAD_API_CALL __declspec(dllexport) extern #define GLAD_API_CALL __declspec(dllexport) extern
#endif #endif
#else #else
#if defined(__GNUC__) #if defined(__GNUC__)
#define GLAD_API_CALL __attribute__((dllimport)) extern #define GLAD_API_CALL __attribute__((dllimport)) extern
#else #else
#define GLAD_API_CALL __declspec(dllimport) extern #define GLAD_API_CALL __declspec(dllimport) extern
#endif #endif
#endif #endif
#elif defined(__GNUC__) && defined(GLAD_API_CALL_EXPORT_BUILD) #elif defined(__GNUC__) && defined(GLAD_API_CALL_EXPORT_BUILD)
#define GLAD_API_CALL __attribute__((visibility("default"))) extern #define GLAD_API_CALL __attribute__((visibility("default"))) extern
#else #else
#define GLAD_API_CALL extern #define GLAD_API_CALL extern
#endif #endif
#else #else
#define GLAD_API_CALL extern #define GLAD_API_CALL extern
#endif #endif
#endif #endif
#ifdef APIENTRY #ifdef APIENTRY
#define GLAD_API_PTR APIENTRY #define GLAD_API_PTR APIENTRY
#elif GLAD_PLATFORM_WIN32 #elif GLAD_PLATFORM_WIN32
#define GLAD_API_PTR __stdcall #define GLAD_API_PTR __stdcall
#else #else
#define GLAD_API_PTR #define GLAD_API_PTR
#endif #endif
#ifndef GLAPI #ifndef GLAPI
#define GLAPI GLAD_API_CALL #define GLAPI GLAD_API_CALL
#endif #endif
#ifndef GLAPIENTRY #ifndef GLAPIENTRY
#define GLAPIENTRY GLAD_API_PTR #define GLAPIENTRY GLAD_API_PTR
#endif #endif
#define GLAD_MAKE_VERSION(major, minor) (major * 10000 + minor) #define GLAD_MAKE_VERSION(major, minor) (major * 10000 + minor)
#define GLAD_VERSION_MAJOR(version) (version / 10000) #define GLAD_VERSION_MAJOR(version) (version / 10000)
#define GLAD_VERSION_MINOR(version) (version % 10000) #define GLAD_VERSION_MINOR(version) (version % 10000)
#define GLAD_GENERATOR_VERSION "2.0.0-beta" #define GLAD_GENERATOR_VERSION "2.0.0-beta"
typedef void (*GLADapiproc)(void); typedef void (*GLADapiproc)(void);
@ -316,12 +315,10 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
#define EGL_WIDTH 0x3057 #define EGL_WIDTH 0x3057
#define EGL_WINDOW_BIT 0x0004 #define EGL_WINDOW_BIT 0x0004
#include <KHR/khrplatform.h> #include <KHR/khrplatform.h>
#include <EGL/eglplatform.h> #include <EGL/eglplatform.h>
struct AHardwareBuffer; struct AHardwareBuffer;
struct wl_buffer; struct wl_buffer;
@ -330,7 +327,6 @@ struct wl_display;
struct wl_resource; struct wl_resource;
typedef unsigned int EGLBoolean; typedef unsigned int EGLBoolean;
typedef unsigned int EGLenum; typedef unsigned int EGLenum;
@ -410,7 +406,6 @@ typedef void(GLAD_API_PTR *EGLDEBUGPROCKHR)(EGLenum error, const char *command,
#define PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWLPROC #define PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWLPROC
#define EGL_VERSION_1_0 1 #define EGL_VERSION_1_0 1
GLAD_API_CALL int GLAD_EGL_VERSION_1_0; GLAD_API_CALL int GLAD_EGL_VERSION_1_0;
#define EGL_VERSION_1_1 1 #define EGL_VERSION_1_1 1
@ -424,7 +419,6 @@ GLAD_API_CALL int GLAD_EGL_VERSION_1_4;
#define EGL_VERSION_1_5 1 #define EGL_VERSION_1_5 1
GLAD_API_CALL int GLAD_EGL_VERSION_1_5; GLAD_API_CALL int GLAD_EGL_VERSION_1_5;
typedef EGLBoolean(GLAD_API_PTR *PFNEGLBINDAPIPROC)(EGLenum api); typedef EGLBoolean(GLAD_API_PTR *PFNEGLBINDAPIPROC)(EGLenum api);
typedef EGLBoolean(GLAD_API_PTR *PFNEGLBINDTEXIMAGEPROC)(EGLDisplay dpy, EGLSurface surface, EGLint buffer); typedef EGLBoolean(GLAD_API_PTR *PFNEGLBINDTEXIMAGEPROC)(EGLDisplay dpy, EGLSurface surface, EGLint buffer);
typedef EGLBoolean(GLAD_API_PTR *PFNEGLCHOOSECONFIGPROC)(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); typedef EGLBoolean(GLAD_API_PTR *PFNEGLCHOOSECONFIGPROC)(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);
@ -567,15 +561,18 @@ GLAD_API_CALL PFNEGLWAITNATIVEPROC glad_eglWaitNative;
GLAD_API_CALL PFNEGLWAITSYNCPROC glad_eglWaitSync; GLAD_API_CALL PFNEGLWAITSYNCPROC glad_eglWaitSync;
#define eglWaitSync glad_eglWaitSync #define eglWaitSync glad_eglWaitSync
GLAD_API_CALL int
GLAD_API_CALL int gladLoadEGLUserPtr(EGLDisplay display, GLADuserptrloadfunc load, void *userptr); gladLoadEGLUserPtr(EGLDisplay display, GLADuserptrloadfunc load, void *userptr);
GLAD_API_CALL int gladLoadEGL(EGLDisplay display, GLADloadfunc load); GLAD_API_CALL int
gladLoadEGL(EGLDisplay display, GLADloadfunc load);
#ifdef GLAD_EGL #ifdef GLAD_EGL
GLAD_API_CALL int gladLoaderLoadEGL(EGLDisplay display); GLAD_API_CALL int
gladLoaderLoadEGL(EGLDisplay display);
GLAD_API_CALL void gladLoaderUnloadEGL(void); GLAD_API_CALL void
gladLoaderUnloadEGL(void);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus

View file

@ -29,27 +29,27 @@
#define GLAD_GL_H_ #define GLAD_GL_H_
#ifdef __clang__ #ifdef __clang__
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-id-macro" #pragma clang diagnostic ignored "-Wreserved-id-macro"
#endif #endif
#ifdef __gl_h_ #ifdef __gl_h_
#error OpenGL (gl.h) header already included (API: gl), remove previous include! #error OpenGL (gl.h) header already included (API: gl), remove previous include!
#endif #endif
#define __gl_h_ 1 #define __gl_h_ 1
#ifdef __gl3_h_ #ifdef __gl3_h_
#error OpenGL (gl3.h) header already included (API: gl), remove previous include! #error OpenGL (gl3.h) header already included (API: gl), remove previous include!
#endif #endif
#define __gl3_h_ 1 #define __gl3_h_ 1
#ifdef __glext_h_ #ifdef __glext_h_
#error OpenGL (glext.h) header already included (API: gl), remove previous include! #error OpenGL (glext.h) header already included (API: gl), remove previous include!
#endif #endif
#define __glext_h_ 1 #define __glext_h_ 1
#ifdef __gl3ext_h_ #ifdef __gl3ext_h_
#error OpenGL (gl3ext.h) header already included (API: gl), remove previous include! #error OpenGL (gl3ext.h) header already included (API: gl), remove previous include!
#endif #endif
#define __gl3ext_h_ 1 #define __gl3ext_h_ 1
#ifdef __clang__ #ifdef __clang__
#pragma clang diagnostic pop #pragma clang diagnostic pop
#endif #endif
#define GLAD_GL #define GLAD_GL
@ -61,108 +61,108 @@ extern "C" {
#endif #endif
#ifndef GLAD_PLATFORM_H_ #ifndef GLAD_PLATFORM_H_
#define GLAD_PLATFORM_H_ #define GLAD_PLATFORM_H_
#ifndef GLAD_PLATFORM_WIN32 #ifndef GLAD_PLATFORM_WIN32
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__MINGW32__) #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__MINGW32__)
#define GLAD_PLATFORM_WIN32 1 #define GLAD_PLATFORM_WIN32 1
#else #else
#define GLAD_PLATFORM_WIN32 0 #define GLAD_PLATFORM_WIN32 0
#endif #endif
#endif #endif
#ifndef GLAD_PLATFORM_APPLE #ifndef GLAD_PLATFORM_APPLE
#ifdef __APPLE__ #ifdef __APPLE__
#define GLAD_PLATFORM_APPLE 1 #define GLAD_PLATFORM_APPLE 1
#else #else
#define GLAD_PLATFORM_APPLE 0 #define GLAD_PLATFORM_APPLE 0
#endif #endif
#endif #endif
#ifndef GLAD_PLATFORM_EMSCRIPTEN #ifndef GLAD_PLATFORM_EMSCRIPTEN
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
#define GLAD_PLATFORM_EMSCRIPTEN 1 #define GLAD_PLATFORM_EMSCRIPTEN 1
#else #else
#define GLAD_PLATFORM_EMSCRIPTEN 0 #define GLAD_PLATFORM_EMSCRIPTEN 0
#endif #endif
#endif #endif
#ifndef GLAD_PLATFORM_UWP #ifndef GLAD_PLATFORM_UWP
#if defined(_MSC_VER) && !defined(GLAD_INTERNAL_HAVE_WINAPIFAMILY) #if defined(_MSC_VER) && !defined(GLAD_INTERNAL_HAVE_WINAPIFAMILY)
#ifdef __has_include #ifdef __has_include
#if __has_include(<winapifamily.h>) #if __has_include(<winapifamily.h>)
#define GLAD_INTERNAL_HAVE_WINAPIFAMILY 1 #define GLAD_INTERNAL_HAVE_WINAPIFAMILY 1
#endif #endif
#elif _MSC_VER >= 1700 && !_USING_V110_SDK71_ #elif _MSC_VER >= 1700 && !_USING_V110_SDK71_
#define GLAD_INTERNAL_HAVE_WINAPIFAMILY 1 #define GLAD_INTERNAL_HAVE_WINAPIFAMILY 1
#endif #endif
#endif #endif
#ifdef GLAD_INTERNAL_HAVE_WINAPIFAMILY #ifdef GLAD_INTERNAL_HAVE_WINAPIFAMILY
#include <winapifamily.h> #include <winapifamily.h>
#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) #if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)
#define GLAD_PLATFORM_UWP 1 #define GLAD_PLATFORM_UWP 1
#endif #endif
#endif #endif
#ifndef GLAD_PLATFORM_UWP #ifndef GLAD_PLATFORM_UWP
#define GLAD_PLATFORM_UWP 0 #define GLAD_PLATFORM_UWP 0
#endif #endif
#endif #endif
#ifdef __GNUC__ #ifdef __GNUC__
#define GLAD_GNUC_EXTENSION __extension__ #define GLAD_GNUC_EXTENSION __extension__
#else #else
#define GLAD_GNUC_EXTENSION #define GLAD_GNUC_EXTENSION
#endif #endif
#ifndef GLAD_API_CALL #ifndef GLAD_API_CALL
#if defined(GLAD_API_CALL_EXPORT) #if defined(GLAD_API_CALL_EXPORT)
#if GLAD_PLATFORM_WIN32 || defined(__CYGWIN__) #if GLAD_PLATFORM_WIN32 || defined(__CYGWIN__)
#if defined(GLAD_API_CALL_EXPORT_BUILD) #if defined(GLAD_API_CALL_EXPORT_BUILD)
#if defined(__GNUC__) #if defined(__GNUC__)
#define GLAD_API_CALL __attribute__((dllexport)) extern #define GLAD_API_CALL __attribute__((dllexport)) extern
#else #else
#define GLAD_API_CALL __declspec(dllexport) extern #define GLAD_API_CALL __declspec(dllexport) extern
#endif #endif
#else #else
#if defined(__GNUC__) #if defined(__GNUC__)
#define GLAD_API_CALL __attribute__((dllimport)) extern #define GLAD_API_CALL __attribute__((dllimport)) extern
#else #else
#define GLAD_API_CALL __declspec(dllimport) extern #define GLAD_API_CALL __declspec(dllimport) extern
#endif #endif
#endif #endif
#elif defined(__GNUC__) && defined(GLAD_API_CALL_EXPORT_BUILD) #elif defined(__GNUC__) && defined(GLAD_API_CALL_EXPORT_BUILD)
#define GLAD_API_CALL __attribute__((visibility("default"))) extern #define GLAD_API_CALL __attribute__((visibility("default"))) extern
#else #else
#define GLAD_API_CALL extern #define GLAD_API_CALL extern
#endif #endif
#else #else
#define GLAD_API_CALL extern #define GLAD_API_CALL extern
#endif #endif
#endif #endif
#ifdef APIENTRY #ifdef APIENTRY
#define GLAD_API_PTR APIENTRY #define GLAD_API_PTR APIENTRY
#elif GLAD_PLATFORM_WIN32 #elif GLAD_PLATFORM_WIN32
#define GLAD_API_PTR __stdcall #define GLAD_API_PTR __stdcall
#else #else
#define GLAD_API_PTR #define GLAD_API_PTR
#endif #endif
#ifndef GLAPI #ifndef GLAPI
#define GLAPI GLAD_API_CALL #define GLAPI GLAD_API_CALL
#endif #endif
#ifndef GLAPIENTRY #ifndef GLAPIENTRY
#define GLAPIENTRY GLAD_API_PTR #define GLAPIENTRY GLAD_API_PTR
#endif #endif
#define GLAD_MAKE_VERSION(major, minor) (major * 10000 + minor) #define GLAD_MAKE_VERSION(major, minor) (major * 10000 + minor)
#define GLAD_VERSION_MAJOR(version) (version / 10000) #define GLAD_VERSION_MAJOR(version) (version / 10000)
#define GLAD_VERSION_MINOR(version) (version % 10000) #define GLAD_VERSION_MINOR(version) (version % 10000)
#define GLAD_GENERATOR_VERSION "2.0.0-beta" #define GLAD_GENERATOR_VERSION "2.0.0-beta"
typedef void (*GLADapiproc)(void); typedef void (*GLADapiproc)(void);
@ -1983,7 +1983,6 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
#define GL_ZOOM_X 0x0D16 #define GL_ZOOM_X 0x0D16
#define GL_ZOOM_Y 0x0D17 #define GL_ZOOM_Y 0x0D17
#include <KHR/khrplatform.h> #include <KHR/khrplatform.h>
typedef unsigned int GLenum; typedef unsigned int GLenum;
@ -2090,7 +2089,6 @@ typedef GLintptr GLvdpauSurfaceNV;
typedef void(GLAD_API_PTR *GLVULKANPROCNV)(void); typedef void(GLAD_API_PTR *GLVULKANPROCNV)(void);
#define GL_VERSION_1_0 1 #define GL_VERSION_1_0 1
#define GL_VERSION_1_1 1 #define GL_VERSION_1_1 1
#define GL_VERSION_1_2 1 #define GL_VERSION_1_2 1
@ -4235,15 +4233,17 @@ typedef struct GladGLContext {
PFNGLWINDOWPOS3SVPROC WindowPos3sv; PFNGLWINDOWPOS3SVPROC WindowPos3sv;
} GladGLContext; } GladGLContext;
GLAD_API_CALL int
GLAD_API_CALL int gladLoadGLContextUserPtr(GladGLContext *context, GLADuserptrloadfunc load, void *userptr); gladLoadGLContextUserPtr(GladGLContext *context, GLADuserptrloadfunc load, void *userptr);
GLAD_API_CALL int gladLoadGLContext(GladGLContext *context, GLADloadfunc load); GLAD_API_CALL int
gladLoadGLContext(GladGLContext *context, GLADloadfunc load);
#ifdef GLAD_GL #ifdef GLAD_GL
GLAD_API_CALL int gladLoaderLoadGLContext(GladGLContext *context); GLAD_API_CALL int
GLAD_API_CALL void gladLoaderUnloadGL(void); gladLoaderLoadGLContext(GladGLContext *context);
GLAD_API_CALL void
gladLoaderUnloadGL(void);
#endif #endif

@ -1 +1 @@
Subproject commit e439318cf782e30066d430f27a1365e013a5ab94 Subproject commit 014c9df8ee7a36e5bf85aa619062a2d4b95ec8f6

@ -1 +1 @@
Subproject commit c9426a6a71c4162e65dde8c0c71a25f1dbca46ba Subproject commit d3cb8131d12832898af31c4f2484ec1bd6bed0f4

2
third-party/nanors vendored

@ -1 +1 @@
Subproject commit e9e242e98e27037830490b2a752895ca68f75f8b Subproject commit 395e5ada44dd8d5974eaf6bb6b17f23406e3ca72

View file

@ -1525,7 +1525,8 @@ typedef struct _NVFBC_TOGL_GRAB_FRAME_PARAMS {
* A NULL terminated error message, or an empty string. Its maximum length * A NULL terminated error message, or an empty string. Its maximum length
* is NVFBC_ERROR_STR_LEN. * is NVFBC_ERROR_STR_LEN.
*/ */
const char *NVFBCAPI NvFBCGetLastErrorStr(const NVFBC_SESSION_HANDLE sessionHandle); const char *NVFBCAPI
NvFBCGetLastErrorStr(const NVFBC_SESSION_HANDLE sessionHandle);
/*! /*!
* \brief Allocates a new handle for an NvFBC client. * \brief Allocates a new handle for an NvFBC client.
@ -1551,7 +1552,8 @@ const char *NVFBCAPI NvFBCGetLastErrorStr(const NVFBC_SESSION_HANDLE sessionHand
* ::NVFBC_ERR_GL * ::NVFBC_ERR_GL
* *
*/ */
NVFBCSTATUS NVFBCAPI NvFBCCreateHandle(NVFBC_SESSION_HANDLE *pSessionHandle, NVFBC_CREATE_HANDLE_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCCreateHandle(NVFBC_SESSION_HANDLE *pSessionHandle, NVFBC_CREATE_HANDLE_PARAMS *pParams);
/*! /*!
* \brief Destroys the handle of an NvFBC client. * \brief Destroys the handle of an NvFBC client.
@ -1577,7 +1579,8 @@ NVFBCSTATUS NVFBCAPI NvFBCCreateHandle(NVFBC_SESSION_HANDLE *pSessionHandle, NVF
* ::NVFBC_ERR_CONTEXT \n * ::NVFBC_ERR_CONTEXT \n
* ::NVFBC_ERR_X * ::NVFBC_ERR_X
*/ */
NVFBCSTATUS NVFBCAPI NvFBCDestroyHandle(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_DESTROY_HANDLE_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCDestroyHandle(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_DESTROY_HANDLE_PARAMS *pParams);
/*! /*!
* \brief Gets the current status of the display driver. * \brief Gets the current status of the display driver.
@ -1596,7 +1599,8 @@ NVFBCSTATUS NVFBCAPI NvFBCDestroyHandle(const NVFBC_SESSION_HANDLE sessionHandle
* ::NVFBC_ERR_INTERNAL \n * ::NVFBC_ERR_INTERNAL \n
* ::NVFBC_ERR_X * ::NVFBC_ERR_X
*/ */
NVFBCSTATUS NVFBCAPI NvFBCGetStatus(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_GET_STATUS_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCGetStatus(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_GET_STATUS_PARAMS *pParams);
/*! /*!
* \brief Binds the FBC context to the calling thread. * \brief Binds the FBC context to the calling thread.
@ -1630,7 +1634,8 @@ NVFBCSTATUS NVFBCAPI NvFBCGetStatus(const NVFBC_SESSION_HANDLE sessionHandle, NV
* ::NVFBC_ERR_INTERNAL \n * ::NVFBC_ERR_INTERNAL \n
* ::NVFBC_ERR_X * ::NVFBC_ERR_X
*/ */
NVFBCSTATUS NVFBCAPI NvFBCBindContext(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_BIND_CONTEXT_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCBindContext(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_BIND_CONTEXT_PARAMS *pParams);
/*! /*!
* \brief Releases the FBC context from the calling thread. * \brief Releases the FBC context from the calling thread.
@ -1651,7 +1656,8 @@ NVFBCSTATUS NVFBCAPI NvFBCBindContext(const NVFBC_SESSION_HANDLE sessionHandle,
* ::NVFBC_ERR_INTERNAL \n * ::NVFBC_ERR_INTERNAL \n
* ::NVFBC_ERR_X * ::NVFBC_ERR_X
*/ */
NVFBCSTATUS NVFBCAPI NvFBCReleaseContext(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_RELEASE_CONTEXT_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCReleaseContext(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_RELEASE_CONTEXT_PARAMS *pParams);
/*! /*!
* \brief Creates a capture session for an FBC client. * \brief Creates a capture session for an FBC client.
@ -1686,7 +1692,8 @@ NVFBCSTATUS NVFBCAPI NvFBCReleaseContext(const NVFBC_SESSION_HANDLE sessionHandl
* ::NVFBC_ERR_MUST_RECREATE \n * ::NVFBC_ERR_MUST_RECREATE \n
* ::NVFBC_ERR_INTERNAL * ::NVFBC_ERR_INTERNAL
*/ */
NVFBCSTATUS NVFBCAPI NvFBCCreateCaptureSession(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_CREATE_CAPTURE_SESSION_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCCreateCaptureSession(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_CREATE_CAPTURE_SESSION_PARAMS *pParams);
/*! /*!
* \brief Destroys a capture session for an FBC client. * \brief Destroys a capture session for an FBC client.
@ -1710,7 +1717,8 @@ NVFBCSTATUS NVFBCAPI NvFBCCreateCaptureSession(const NVFBC_SESSION_HANDLE sessio
* ::NVFBC_ERR_INTERNAL \n * ::NVFBC_ERR_INTERNAL \n
* ::NVFBC_ERR_X * ::NVFBC_ERR_X
*/ */
NVFBCSTATUS NVFBCAPI NvFBCDestroyCaptureSession(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_DESTROY_CAPTURE_SESSION_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCDestroyCaptureSession(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_DESTROY_CAPTURE_SESSION_PARAMS *pParams);
/*! /*!
* \brief Sets up a capture to system memory session. * \brief Sets up a capture to system memory session.
@ -1742,7 +1750,8 @@ NVFBCSTATUS NVFBCAPI NvFBCDestroyCaptureSession(const NVFBC_SESSION_HANDLE sessi
* ::NVFBC_ERR_OUT_OF_MEMORY \n * ::NVFBC_ERR_OUT_OF_MEMORY \n
* ::NVFBC_ERR_X * ::NVFBC_ERR_X
*/ */
NVFBCSTATUS NVFBCAPI NvFBCToSysSetUp(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOSYS_SETUP_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCToSysSetUp(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOSYS_SETUP_PARAMS *pParams);
/*! /*!
* \brief Captures a frame to a buffer in system memory. * \brief Captures a frame to a buffer in system memory.
@ -1782,7 +1791,8 @@ NVFBCSTATUS NVFBCAPI NvFBCToSysSetUp(const NVFBC_SESSION_HANDLE sessionHandle, N
* \see NvFBCCreateCaptureSession \n * \see NvFBCCreateCaptureSession \n
* \see NvFBCToSysSetUp * \see NvFBCToSysSetUp
*/ */
NVFBCSTATUS NVFBCAPI NvFBCToSysGrabFrame(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOSYS_GRAB_FRAME_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCToSysGrabFrame(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOSYS_GRAB_FRAME_PARAMS *pParams);
/*! /*!
* \brief Sets up a capture to video memory session. * \brief Sets up a capture to video memory session.
@ -1809,7 +1819,8 @@ NVFBCSTATUS NVFBCAPI NvFBCToSysGrabFrame(const NVFBC_SESSION_HANDLE sessionHandl
* ::NVFBC_ERR_GL \n * ::NVFBC_ERR_GL \n
* ::NVFBC_ERR_X * ::NVFBC_ERR_X
*/ */
NVFBCSTATUS NVFBCAPI NvFBCToCudaSetUp(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOCUDA_SETUP_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCToCudaSetUp(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOCUDA_SETUP_PARAMS *pParams);
/*! /*!
* \brief Captures a frame to a CUDA device in video memory. * \brief Captures a frame to a CUDA device in video memory.
@ -1838,7 +1849,8 @@ NVFBCSTATUS NVFBCAPI NvFBCToCudaSetUp(const NVFBC_SESSION_HANDLE sessionHandle,
* \see NvFBCCreateCaptureSession \n * \see NvFBCCreateCaptureSession \n
* \see NvFBCToCudaSetUp * \see NvFBCToCudaSetUp
*/ */
NVFBCSTATUS NVFBCAPI NvFBCToCudaGrabFrame(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOCUDA_GRAB_FRAME_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCToCudaGrabFrame(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOCUDA_GRAB_FRAME_PARAMS *pParams);
/*! /*!
* \brief Sets up a capture to OpenGL buffer in video memory session. * \brief Sets up a capture to OpenGL buffer in video memory session.
@ -1865,7 +1877,8 @@ NVFBCSTATUS NVFBCAPI NvFBCToCudaGrabFrame(const NVFBC_SESSION_HANDLE sessionHand
* ::NVFBC_ERR_GL \n * ::NVFBC_ERR_GL \n
* ::NVFBC_ERR_X * ::NVFBC_ERR_X
*/ */
NVFBCSTATUS NVFBCAPI NvFBCToGLSetUp(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOGL_SETUP_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCToGLSetUp(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOGL_SETUP_PARAMS *pParams);
/*! /*!
* \brief Captures a frame to an OpenGL buffer in video memory. * \brief Captures a frame to an OpenGL buffer in video memory.
@ -1893,7 +1906,8 @@ NVFBCSTATUS NVFBCAPI NvFBCToGLSetUp(const NVFBC_SESSION_HANDLE sessionHandle, NV
* \see NvFBCCreateCaptureSession \n * \see NvFBCCreateCaptureSession \n
* \see NvFBCToCudaSetUp * \see NvFBCToCudaSetUp
*/ */
NVFBCSTATUS NVFBCAPI NvFBCToGLGrabFrame(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOGL_GRAB_FRAME_PARAMS *pParams); NVFBCSTATUS NVFBCAPI
NvFBCToGLGrabFrame(const NVFBC_SESSION_HANDLE sessionHandle, NVFBC_TOGL_GRAB_FRAME_PARAMS *pParams);
/*! /*!
* \cond FBC_PFN * \cond FBC_PFN
@ -1926,28 +1940,28 @@ typedef NVFBCSTATUS(NVFBCAPI *PNVFBCTOGLGRABFRAME)(const NVFBC_SESSION_HANDLE se
*/ */
typedef struct typedef struct
{ {
uint32_t dwVersion; //!< [in] Must be set to NVFBC_VERSION. uint32_t dwVersion; //!< [in] Must be set to NVFBC_VERSION.
PNVFBCGETLASTERRORSTR nvFBCGetLastErrorStr; //!< [out] Pointer to ::NvFBCGetLastErrorStr(). PNVFBCGETLASTERRORSTR nvFBCGetLastErrorStr; //!< [out] Pointer to ::NvFBCGetLastErrorStr().
PNVFBCCREATEHANDLE nvFBCCreateHandle; //!< [out] Pointer to ::NvFBCCreateHandle(). PNVFBCCREATEHANDLE nvFBCCreateHandle; //!< [out] Pointer to ::NvFBCCreateHandle().
PNVFBCDESTROYHANDLE nvFBCDestroyHandle; //!< [out] Pointer to ::NvFBCDestroyHandle(). PNVFBCDESTROYHANDLE nvFBCDestroyHandle; //!< [out] Pointer to ::NvFBCDestroyHandle().
PNVFBCGETSTATUS nvFBCGetStatus; //!< [out] Pointer to ::NvFBCGetStatus(). PNVFBCGETSTATUS nvFBCGetStatus; //!< [out] Pointer to ::NvFBCGetStatus().
PNVFBCCREATECAPTURESESSION nvFBCCreateCaptureSession; //!< [out] Pointer to ::NvFBCCreateCaptureSession(). PNVFBCCREATECAPTURESESSION nvFBCCreateCaptureSession; //!< [out] Pointer to ::NvFBCCreateCaptureSession().
PNVFBCDESTROYCAPTURESESSION nvFBCDestroyCaptureSession; //!< [out] Pointer to ::NvFBCDestroyCaptureSession(). PNVFBCDESTROYCAPTURESESSION nvFBCDestroyCaptureSession; //!< [out] Pointer to ::NvFBCDestroyCaptureSession().
PNVFBCTOSYSSETUP nvFBCToSysSetUp; //!< [out] Pointer to ::NvFBCToSysSetUp(). PNVFBCTOSYSSETUP nvFBCToSysSetUp; //!< [out] Pointer to ::NvFBCToSysSetUp().
PNVFBCTOSYSGRABFRAME nvFBCToSysGrabFrame; //!< [out] Pointer to ::NvFBCToSysGrabFrame(). PNVFBCTOSYSGRABFRAME nvFBCToSysGrabFrame; //!< [out] Pointer to ::NvFBCToSysGrabFrame().
PNVFBCTOCUDASETUP nvFBCToCudaSetUp; //!< [out] Pointer to ::NvFBCToCudaSetUp(). PNVFBCTOCUDASETUP nvFBCToCudaSetUp; //!< [out] Pointer to ::NvFBCToCudaSetUp().
PNVFBCTOCUDAGRABFRAME nvFBCToCudaGrabFrame; //!< [out] Pointer to ::NvFBCToCudaGrabFrame(). PNVFBCTOCUDAGRABFRAME nvFBCToCudaGrabFrame; //!< [out] Pointer to ::NvFBCToCudaGrabFrame().
void *pad1; //!< [out] Retired. Do not use. void *pad1; //!< [out] Retired. Do not use.
void *pad2; //!< [out] Retired. Do not use. void *pad2; //!< [out] Retired. Do not use.
void *pad3; //!< [out] Retired. Do not use. void *pad3; //!< [out] Retired. Do not use.
PNVFBCBINDCONTEXT nvFBCBindContext; //!< [out] Pointer to ::NvFBCBindContext(). PNVFBCBINDCONTEXT nvFBCBindContext; //!< [out] Pointer to ::NvFBCBindContext().
PNVFBCRELEASECONTEXT nvFBCReleaseContext; //!< [out] Pointer to ::NvFBCReleaseContext(). PNVFBCRELEASECONTEXT nvFBCReleaseContext; //!< [out] Pointer to ::NvFBCReleaseContext().
void *pad4; //!< [out] Retired. Do not use. void *pad4; //!< [out] Retired. Do not use.
void *pad5; //!< [out] Retired. Do not use. void *pad5; //!< [out] Retired. Do not use.
void *pad6; //!< [out] Retired. Do not use. void *pad6; //!< [out] Retired. Do not use.
void *pad7; //!< [out] Retired. Do not use. void *pad7; //!< [out] Retired. Do not use.
PNVFBCTOGLSETUP nvFBCToGLSetUp; //!< [out] Pointer to ::nvFBCToGLSetup(). PNVFBCTOGLSETUP nvFBCToGLSetUp; //!< [out] Pointer to ::nvFBCToGLSetup().
PNVFBCTOGLGRABFRAME nvFBCToGLGrabFrame; //!< [out] Pointer to ::nvFBCToGLGrabFrame(). PNVFBCTOGLGRABFRAME nvFBCToGLGrabFrame; //!< [out] Pointer to ::nvFBCToGLGrabFrame().
} NVFBC_API_FUNCTION_LIST; } NVFBC_API_FUNCTION_LIST;
/*! /*!
@ -1966,7 +1980,8 @@ typedef struct
* ::NVFBC_ERR_INVALID_PTR \n * ::NVFBC_ERR_INVALID_PTR \n
* ::NVFBC_ERR_API_VERSION * ::NVFBC_ERR_API_VERSION
*/ */
NVFBCSTATUS NVFBCAPI NvFBCCreateInstance(NVFBC_API_FUNCTION_LIST *pFunctionList); NVFBCSTATUS NVFBCAPI
NvFBCCreateInstance(NVFBC_API_FUNCTION_LIST *pFunctionList);
/*! /*!
* \ingroup FBC_FUNC * \ingroup FBC_FUNC
* *
@ -1978,4 +1993,4 @@ typedef NVFBCSTATUS(NVFBCAPI *PNVFBCCREATEINSTANCE)(NVFBC_API_FUNCTION_LIST *pFu
} }
#endif #endif
#endif // _NVFBC_H_ #endif // _NVFBC_H_

File diff suppressed because it is too large Load diff

View file

@ -16,242 +16,250 @@
#include "src/utility.h" #include "src/utility.h"
DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2); DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2);
using namespace std::literals; using namespace std::literals;
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient); const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
constexpr auto SAMPLE_RATE = 48000; constexpr auto SAMPLE_RATE = 48000;
int device_state_filter = DEVICE_STATE_ACTIVE; int device_state_filter = DEVICE_STATE_ACTIVE;
namespace audio { namespace audio {
template<class T> template <class T>
void Release(T *p) { void
p->Release(); Release(T *p) {
} p->Release();
template<class T>
void co_task_free(T *p) {
CoTaskMemFree((LPVOID)p);
}
using device_enum_t = util::safe_ptr<IMMDeviceEnumerator, Release<IMMDeviceEnumerator>>;
using collection_t = util::safe_ptr<IMMDeviceCollection, Release<IMMDeviceCollection>>;
using prop_t = util::safe_ptr<IPropertyStore, Release<IPropertyStore>>;
using device_t = util::safe_ptr<IMMDevice, Release<IMMDevice>>;
using audio_client_t = util::safe_ptr<IAudioClient, Release<IAudioClient>>;
using audio_capture_t = util::safe_ptr<IAudioCaptureClient, Release<IAudioCaptureClient>>;
using wave_format_t = util::safe_ptr<WAVEFORMATEX, co_task_free<WAVEFORMATEX>>;
using wstring_t = util::safe_ptr<WCHAR, co_task_free<WCHAR>>;
using handle_t = util::safe_ptr_v2<void, BOOL, CloseHandle>;
class prop_var_t {
public:
prop_var_t() {
PropVariantInit(&prop);
} }
~prop_var_t() { template <class T>
PropVariantClear(&prop); void
co_task_free(T *p) {
CoTaskMemFree((LPVOID) p);
} }
PROPVARIANT prop; using device_enum_t = util::safe_ptr<IMMDeviceEnumerator, Release<IMMDeviceEnumerator>>;
}; using collection_t = util::safe_ptr<IMMDeviceCollection, Release<IMMDeviceCollection>>;
using prop_t = util::safe_ptr<IPropertyStore, Release<IPropertyStore>>;
using device_t = util::safe_ptr<IMMDevice, Release<IMMDevice>>;
using audio_client_t = util::safe_ptr<IAudioClient, Release<IAudioClient>>;
using audio_capture_t = util::safe_ptr<IAudioCaptureClient, Release<IAudioCaptureClient>>;
using wave_format_t = util::safe_ptr<WAVEFORMATEX, co_task_free<WAVEFORMATEX>>;
const wchar_t *no_null(const wchar_t *str) { using wstring_t = util::safe_ptr<WCHAR, co_task_free<WCHAR>>;
return str ? str : L"Unknown";
}
struct format_t { using handle_t = util::safe_ptr_v2<void, BOOL, CloseHandle>;
std::string_view name;
int channels;
int channel_mask;
} formats[] {
{ "Mono"sv,
1,
SPEAKER_FRONT_CENTER },
{ "Stereo"sv,
2,
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT },
{ "Surround 5.1"sv,
6,
SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT },
{ "Surround 7.1"sv,
8,
SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT |
SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT }
};
void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) { class prop_var_t {
wave_format->nChannels = format.channels; public:
wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8; prop_var_t() {
wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign; PropVariantInit(&prop);
if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask;
}
}
audio_client_t make_audio_client(device_t &device, const format_t &format) {
audio_client_t audio_client;
auto status = device->Activate(
IID_IAudioClient,
CLSCTX_ALL,
nullptr,
(void **)&audio_client);
if(FAILED(status)) {
std::cout << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return nullptr;
}
wave_format_t wave_format;
status = audio_client->GetMixFormat(&wave_format);
if(FAILED(status)) {
std::cout << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return nullptr;
}
wave_format->wBitsPerSample = 16;
wave_format->nSamplesPerSec = SAMPLE_RATE;
switch(wave_format->wFormatTag) {
case WAVE_FORMAT_PCM:
break;
case WAVE_FORMAT_IEEE_FLOAT:
break;
case WAVE_FORMAT_EXTENSIBLE: {
auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get();
if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) {
wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
wave_ex->Samples.wValidBitsPerSample = 16;
break;
} }
std::cout << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']' << std::endl; ~prop_var_t() {
} PropVariantClear(&prop);
default: }
std::cout << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']' << std::endl;
return nullptr; PROPVARIANT prop;
}; };
set_wave_format(wave_format, format); const wchar_t *
no_null(const wchar_t *str) {
status = audio_client->Initialize( return str ? str : L"Unknown";
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
0, 0,
wave_format.get(),
nullptr);
if(status) {
return nullptr;
} }
return audio_client; struct format_t {
} std::string_view name;
int channels;
int channel_mask;
} formats[] {
{ "Mono"sv,
1,
SPEAKER_FRONT_CENTER },
{ "Stereo"sv,
2,
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT },
{ "Surround 5.1"sv,
6,
SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT },
{ "Surround 7.1"sv,
8,
SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT |
SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT }
};
void print_device(device_t &device) { void
audio::wstring_t wstring; set_wave_format(audio::wave_format_t &wave_format, const format_t &format) {
DWORD device_state; wave_format->nChannels = format.channels;
wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8;
wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign;
device->GetState(&device_state); if (wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
device->GetId(&wstring); ((PWAVEFORMATEXTENSIBLE) wave_format.get())->dwChannelMask = format.channel_mask;
}
audio::prop_t prop;
device->OpenPropertyStore(STGM_READ, &prop);
prop_var_t adapter_friendly_name;
prop_var_t device_friendly_name;
prop_var_t device_desc;
prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop);
prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop);
prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop);
if(!(device_state & device_state_filter)) {
return;
} }
std::wstring device_state_string = L"Unknown"s; audio_client_t
switch(device_state) { make_audio_client(device_t &device, const format_t &format) {
case DEVICE_STATE_ACTIVE: audio_client_t audio_client;
device_state_string = L"Active"s; auto status = device->Activate(
break; IID_IAudioClient,
case DEVICE_STATE_DISABLED: CLSCTX_ALL,
device_state_string = L"Disabled"s; nullptr,
break; (void **) &audio_client);
case DEVICE_STATE_UNPLUGGED:
device_state_string = L"Unplugged"s; if (FAILED(status)) {
break; std::cout << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
case DEVICE_STATE_NOTPRESENT:
device_state_string = L"Not present"s; return nullptr;
break; }
wave_format_t wave_format;
status = audio_client->GetMixFormat(&wave_format);
if (FAILED(status)) {
std::cout << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return nullptr;
}
wave_format->wBitsPerSample = 16;
wave_format->nSamplesPerSec = SAMPLE_RATE;
switch (wave_format->wFormatTag) {
case WAVE_FORMAT_PCM:
break;
case WAVE_FORMAT_IEEE_FLOAT:
break;
case WAVE_FORMAT_EXTENSIBLE: {
auto wave_ex = (PWAVEFORMATEXTENSIBLE) wave_format.get();
if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) {
wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
wave_ex->Samples.wValidBitsPerSample = 16;
break;
}
std::cout << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']' << std::endl;
}
default:
std::cout << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']' << std::endl;
return nullptr;
};
set_wave_format(wave_format, format);
status = audio_client->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
0, 0,
wave_format.get(),
nullptr);
if (status) {
return nullptr;
}
return audio_client;
} }
std::wcout void
<< L"===== Device ====="sv << std::endl print_device(device_t &device) {
<< L"Device ID : "sv << wstring.get() << std::endl audio::wstring_t wstring;
<< L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl DWORD device_state;
<< L"Adapter name : "sv << no_null((LPWSTR)adapter_friendly_name.prop.pszVal) << std::endl
<< L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl
<< L"Device state : "sv << device_state_string << std::endl
<< std::endl;
if(device_state != DEVICE_STATE_ACTIVE) { device->GetState(&device_state);
return; device->GetId(&wstring);
audio::prop_t prop;
device->OpenPropertyStore(STGM_READ, &prop);
prop_var_t adapter_friendly_name;
prop_var_t device_friendly_name;
prop_var_t device_desc;
prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop);
prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop);
prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop);
if (!(device_state & device_state_filter)) {
return;
}
std::wstring device_state_string = L"Unknown"s;
switch (device_state) {
case DEVICE_STATE_ACTIVE:
device_state_string = L"Active"s;
break;
case DEVICE_STATE_DISABLED:
device_state_string = L"Disabled"s;
break;
case DEVICE_STATE_UNPLUGGED:
device_state_string = L"Unplugged"s;
break;
case DEVICE_STATE_NOTPRESENT:
device_state_string = L"Not present"s;
break;
}
std::wcout
<< L"===== Device ====="sv << std::endl
<< L"Device ID : "sv << wstring.get() << std::endl
<< L"Device name : "sv << no_null((LPWSTR) device_friendly_name.prop.pszVal) << std::endl
<< L"Adapter name : "sv << no_null((LPWSTR) adapter_friendly_name.prop.pszVal) << std::endl
<< L"Device description : "sv << no_null((LPWSTR) device_desc.prop.pszVal) << std::endl
<< L"Device state : "sv << device_state_string << std::endl
<< std::endl;
if (device_state != DEVICE_STATE_ACTIVE) {
return;
}
for (const auto &format : formats) {
// Ensure WaveFromat is compatible
auto audio_client = make_audio_client(device, format);
std::cout << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv) << std::endl;
}
} }
} // namespace audio
for(const auto &format : formats) { void
// Ensure WaveFromat is compatible print_help() {
auto audio_client = make_audio_client(device, format);
std::cout << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv) << std::endl;
}
}
} // namespace audio
void print_help() {
std::cout std::cout
<< "==== Help ===="sv << std::endl << "==== Help ===="sv << std::endl
<< "Usage:"sv << std::endl << "Usage:"sv << std::endl
<< " audio-info [Active|Disabled|Unplugged|Not-Present]" << std::endl; << " audio-info [Active|Disabled|Unplugged|Not-Present]" << std::endl;
} }
int main(int argc, char *argv[]) { int
main(int argc, char *argv[]) {
CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY); CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY);
auto fg = util::fail_guard([]() { auto fg = util::fail_guard([]() {
CoUninitialize(); CoUninitialize();
}); });
if(argc > 1) { if (argc > 1) {
device_state_filter = 0; device_state_filter = 0;
} }
for(auto x = 1; x < argc; ++x) { for (auto x = 1; x < argc; ++x) {
for(auto p = argv[x]; *p != '\0'; ++p) { for (auto p = argv[x]; *p != '\0'; ++p) {
if(*p == ' ') { if (*p == ' ') {
*p = '-'; *p = '-';
continue; continue;
@ -260,16 +268,16 @@ int main(int argc, char *argv[]) {
*p = std::tolower(*p); *p = std::tolower(*p);
} }
if(argv[x] == "active"sv) { if (argv[x] == "active"sv) {
device_state_filter |= DEVICE_STATE_ACTIVE; device_state_filter |= DEVICE_STATE_ACTIVE;
} }
else if(argv[x] == "disabled"sv) { else if (argv[x] == "disabled"sv) {
device_state_filter |= DEVICE_STATE_DISABLED; device_state_filter |= DEVICE_STATE_DISABLED;
} }
else if(argv[x] == "unplugged"sv) { else if (argv[x] == "unplugged"sv) {
device_state_filter |= DEVICE_STATE_UNPLUGGED; device_state_filter |= DEVICE_STATE_UNPLUGGED;
} }
else if(argv[x] == "not-present"sv) { else if (argv[x] == "not-present"sv) {
device_state_filter |= DEVICE_STATE_NOTPRESENT; device_state_filter |= DEVICE_STATE_NOTPRESENT;
} }
else { else {
@ -286,9 +294,9 @@ int main(int argc, char *argv[]) {
nullptr, nullptr,
CLSCTX_ALL, CLSCTX_ALL,
IID_IMMDeviceEnumerator, IID_IMMDeviceEnumerator,
(void **)&device_enum); (void **) &device_enum);
if(FAILED(status)) { if (FAILED(status)) {
std::cout << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; std::cout << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return -1; return -1;
@ -297,7 +305,7 @@ int main(int argc, char *argv[]) {
audio::collection_t collection; audio::collection_t collection;
status = device_enum->EnumAudioEndpoints(eRender, device_state_filter, &collection); status = device_enum->EnumAudioEndpoints(eRender, device_state_filter, &collection);
if(FAILED(status)) { if (FAILED(status)) {
std::cout << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; std::cout << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return -1; return -1;
@ -307,7 +315,7 @@ int main(int argc, char *argv[]) {
collection->GetCount(&count); collection->GetCount(&count);
std::cout << "====== Found "sv << count << " audio devices ======"sv << std::endl; std::cout << "====== Found "sv << count << " audio devices ======"sv << std::endl;
for(auto x = 0; x < count; ++x) { for (auto x = 0; x < count; ++x) {
audio::device_t device; audio::device_t device;
collection->Item(x, &device); collection->Item(x, &device);

View file

@ -10,21 +10,23 @@
using namespace std::literals; using namespace std::literals;
namespace dxgi { namespace dxgi {
template<class T> template <class T>
void Release(T *dxgi) { void
dxgi->Release(); Release(T *dxgi) {
} dxgi->Release();
}
using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>; using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>; using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>; using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>; using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>;
using device_t = util::safe_ptr<ID3D11Device, Release<ID3D11Device>>; using device_t = util::safe_ptr<ID3D11Device, Release<ID3D11Device>>;
using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>; using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>;
} // namespace dxgi } // namespace dxgi
LSTATUS set_gpu_preference(int preference) { LSTATUS
set_gpu_preference(int preference) {
// The GPU preferences key uses app path as the value name. // The GPU preferences key uses app path as the value name.
WCHAR executable_path[MAX_PATH]; WCHAR executable_path[MAX_PATH];
GetModuleFileNameW(NULL, executable_path, ARRAYSIZE(executable_path)); GetModuleFileNameW(NULL, executable_path, ARRAYSIZE(executable_path));
@ -38,7 +40,7 @@ LSTATUS set_gpu_preference(int preference) {
REG_SZ, REG_SZ,
value_data, value_data,
(wcslen(value_data) + 1) * sizeof(WCHAR)); (wcslen(value_data) + 1) * sizeof(WCHAR));
if(status != ERROR_SUCCESS) { if (status != ERROR_SUCCESS) {
std::cout << "Failed to set GPU preference: "sv << status << std::endl; std::cout << "Failed to set GPU preference: "sv << status << std::endl;
return status; return status;
} }
@ -46,7 +48,8 @@ LSTATUS set_gpu_preference(int preference) {
return ERROR_SUCCESS; return ERROR_SUCCESS;
} }
HRESULT test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { HRESULT
test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) {
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,
@ -68,41 +71,42 @@ HRESULT test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output)
&device, &device,
nullptr, nullptr,
nullptr); nullptr);
if(FAILED(status)) { if (FAILED(status)) {
std::cout << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; std::cout << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return status; return status;
} }
dxgi::output1_t output1; dxgi::output1_t output1;
status = output->QueryInterface(IID_IDXGIOutput1, (void **)&output1); status = output->QueryInterface(IID_IDXGIOutput1, (void **) &output1);
if(FAILED(status)) { if (FAILED(status)) {
std::cout << "Failed to query IDXGIOutput1 from the output"sv << std::endl; std::cout << "Failed to query IDXGIOutput1 from the output"sv << std::endl;
return status; return status;
} }
// Return the result of DuplicateOutput() to Sunshine // Return the result of DuplicateOutput() to Sunshine
dxgi::dup_t dup; dxgi::dup_t dup;
return output1->DuplicateOutput((IUnknown *)device.get(), &dup); return output1->DuplicateOutput((IUnknown *) device.get(), &dup);
} }
int main(int argc, char *argv[]) { int
main(int argc, char *argv[]) {
HRESULT status; HRESULT status;
// Display name may be omitted // Display name may be omitted
if(argc != 2 && argc != 3) { if (argc != 2 && argc != 3) {
std::cout << "ddprobe.exe [GPU preference value] [display name]"sv << std::endl; std::cout << "ddprobe.exe [GPU preference value] [display name]"sv << std::endl;
return -1; return -1;
} }
std::wstring display_name; std::wstring display_name;
if(argc == 3) { if (argc == 3) {
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter; std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
display_name = converter.from_bytes(argv[2]); display_name = converter.from_bytes(argv[2]);
} }
// We must set the GPU preference before making any DXGI/D3D calls // We must set the GPU preference before making any DXGI/D3D calls
status = set_gpu_preference(atoi(argv[1])); status = set_gpu_preference(atoi(argv[1]));
if(status != ERROR_SUCCESS) { if (status != ERROR_SUCCESS) {
return status; return status;
} }
@ -117,30 +121,30 @@ int main(int argc, char *argv[]) {
}); });
dxgi::factory1_t factory; dxgi::factory1_t factory;
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory); status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **) &factory);
if(FAILED(status)) { if (FAILED(status)) {
std::cout << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; std::cout << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return status; return status;
} }
dxgi::adapter_t::pointer adapter_p {}; dxgi::adapter_t::pointer adapter_p {};
for(int x = 0; factory->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) { for (int x = 0; factory->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) {
dxgi::adapter_t adapter { adapter_p }; dxgi::adapter_t adapter { adapter_p };
dxgi::output_t::pointer output_p {}; dxgi::output_t::pointer output_p {};
for(int y = 0; adapter->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) { for (int y = 0; adapter->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
dxgi::output_t output { output_p }; dxgi::output_t output { output_p };
DXGI_OUTPUT_DESC desc; DXGI_OUTPUT_DESC desc;
output->GetDesc(&desc); output->GetDesc(&desc);
// If a display name was specified and this one doesn't match, skip it // If a display name was specified and this one doesn't match, skip it
if(!display_name.empty() && desc.DeviceName != display_name) { if (!display_name.empty() && desc.DeviceName != display_name) {
continue; continue;
} }
// If this display is not part of the desktop, we definitely can't capture it // If this display is not part of the desktop, we definitely can't capture it
if(!desc.AttachedToDesktop) { if (!desc.AttachedToDesktop) {
continue; continue;
} }

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