clang: adjust formatting rules (#1015)

This commit is contained in:
ReenigneArcher
2023-03-27 21:45:29 -04:00
committed by GitHub
parent 79cf382cd9
commit 21eb4eb6dd
103 changed files with 26883 additions and 25173 deletions

View File

@@ -27,222 +27,230 @@ struct AVHWFramesContext;
// 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.
namespace boost {
namespace asio {
namespace ip {
class address;
} // namespace ip
} // namespace asio
namespace filesystem {
class path;
}
namespace process {
class child;
class group;
template<typename Char>
class basic_environment;
typedef basic_environment<char> environment;
} // namespace process
} // namespace boost
namespace asio {
namespace ip {
class address;
} // namespace ip
} // namespace asio
namespace filesystem {
class path;
}
namespace process {
class child;
class group;
template <typename Char>
class basic_environment;
typedef basic_environment<char> environment;
} // namespace process
} // namespace boost
namespace video {
struct config_t;
} // namespace video
struct config_t;
} // namespace video
namespace platf {
constexpr auto MAX_GAMEPADS = 32;
constexpr auto MAX_GAMEPADS = 32;
constexpr std::uint16_t DPAD_UP = 0x0001;
constexpr std::uint16_t DPAD_DOWN = 0x0002;
constexpr std::uint16_t DPAD_LEFT = 0x0004;
constexpr std::uint16_t DPAD_RIGHT = 0x0008;
constexpr std::uint16_t START = 0x0010;
constexpr std::uint16_t BACK = 0x0020;
constexpr std::uint16_t LEFT_STICK = 0x0040;
constexpr std::uint16_t RIGHT_STICK = 0x0080;
constexpr std::uint16_t LEFT_BUTTON = 0x0100;
constexpr std::uint16_t RIGHT_BUTTON = 0x0200;
constexpr std::uint16_t HOME = 0x0400;
constexpr std::uint16_t A = 0x1000;
constexpr std::uint16_t B = 0x2000;
constexpr std::uint16_t X = 0x4000;
constexpr std::uint16_t Y = 0x8000;
constexpr std::uint16_t DPAD_UP = 0x0001;
constexpr std::uint16_t DPAD_DOWN = 0x0002;
constexpr std::uint16_t DPAD_LEFT = 0x0004;
constexpr std::uint16_t DPAD_RIGHT = 0x0008;
constexpr std::uint16_t START = 0x0010;
constexpr std::uint16_t BACK = 0x0020;
constexpr std::uint16_t LEFT_STICK = 0x0040;
constexpr std::uint16_t RIGHT_STICK = 0x0080;
constexpr std::uint16_t LEFT_BUTTON = 0x0100;
constexpr std::uint16_t RIGHT_BUTTON = 0x0200;
constexpr std::uint16_t HOME = 0x0400;
constexpr std::uint16_t A = 0x1000;
constexpr std::uint16_t B = 0x2000;
constexpr std::uint16_t X = 0x4000;
constexpr std::uint16_t Y = 0x8000;
struct rumble_t {
KITTY_DEFAULT_CONSTR(rumble_t)
struct rumble_t {
KITTY_DEFAULT_CONSTR(rumble_t)
rumble_t(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq)
: id { id }, lowfreq { lowfreq }, highfreq { highfreq } {}
rumble_t(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq):
id { id }, lowfreq { lowfreq }, highfreq { highfreq } {}
std::uint16_t id;
std::uint16_t lowfreq;
std::uint16_t highfreq;
};
using rumble_queue_t = safe::mail_raw_t::queue_t<rumble_t>;
std::uint16_t id;
std::uint16_t lowfreq;
std::uint16_t highfreq;
};
using rumble_queue_t = safe::mail_raw_t::queue_t<rumble_t>;
namespace speaker {
enum speaker_e {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
SIDE_LEFT,
SIDE_RIGHT,
MAX_SPEAKERS,
};
namespace speaker {
enum speaker_e {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
SIDE_LEFT,
SIDE_RIGHT,
MAX_SPEAKERS,
};
constexpr std::uint8_t map_stereo[] {
FRONT_LEFT, FRONT_RIGHT
};
constexpr std::uint8_t map_surround51[] {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
};
constexpr std::uint8_t map_surround71[] {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
SIDE_LEFT,
SIDE_RIGHT,
};
} // namespace speaker
constexpr std::uint8_t map_stereo[] {
FRONT_LEFT, FRONT_RIGHT
};
constexpr std::uint8_t map_surround51[] {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
};
constexpr std::uint8_t map_surround71[] {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
SIDE_LEFT,
SIDE_RIGHT,
};
} // namespace speaker
enum class mem_type_e {
system,
vaapi,
dxgi,
cuda,
unknown
};
enum class mem_type_e {
system,
vaapi,
dxgi,
cuda,
unknown
};
enum class pix_fmt_e {
yuv420p,
yuv420p10,
nv12,
p010,
unknown
};
enum class pix_fmt_e {
yuv420p,
yuv420p10,
nv12,
p010,
unknown
};
inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) {
using namespace std::literals;
inline std::string_view
from_pix_fmt(pix_fmt_e pix_fmt) {
using namespace std::literals;
#define _CONVERT(x) \
case pix_fmt_e::x: \
return #x##sv
switch(pix_fmt) {
_CONVERT(yuv420p);
_CONVERT(yuv420p10);
_CONVERT(nv12);
_CONVERT(p010);
_CONVERT(unknown);
}
switch (pix_fmt) {
_CONVERT(yuv420p);
_CONVERT(yuv420p10);
_CONVERT(nv12);
_CONVERT(p010);
_CONVERT(unknown);
}
#undef _CONVERT
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;
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;
}
/**
* implementations must take ownership of 'frame'
*/
virtual int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?";
return -1;
};
virtual int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?";
return -1;
};
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
*/
virtual void init_hwframes(AVHWFramesContext *frames) {};
virtual void
init_hwframes(AVHWFramesContext *frames) {};
/**
/**
* Implementations may make modifications required before context derivation
*/
virtual int prepare_to_derive_context(int hw_device_type) {
return 0;
virtual int
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 {
ok,
reinit,
timeout,
error
};
class display_t {
public:
/**
class display_t {
public:
/**
* 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.
*
@@ -253,11 +261,12 @@ public:
* Returns the image object that should be filled next.
* 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
* 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
@@ -267,66 +276,82 @@ public:
* capture_e::error on error
* 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) {
return std::make_shared<hwdevice_t>();
}
virtual std::shared_ptr<hwdevice_t>
make_hwdevice(pix_fmt_e pix_fmt) {
return std::make_shared<hwdevice_t>();
}
virtual bool is_hdr() {
return false;
}
virtual bool
is_hdr() {
return false;
}
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) {
std::memset(&metadata, 0, sizeof(metadata));
return false;
}
virtual bool
get_hdr_metadata(SS_HDR_METADATA &metadata) {
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.
int offset_x, offset_y;
int env_width, env_height;
// Offsets for when streaming a specific monitor. By default, they are 0.
int offset_x, offset_y;
int env_width, env_height;
int width, height;
};
int width, height;
};
class mic_t {
public:
virtual capture_e sample(std::vector<std::int16_t> &frame_buffer) = 0;
class mic_t {
public:
virtual capture_e
sample(std::vector<std::int16_t> &frame_buffer) = 0;
virtual ~mic_t() = default;
};
virtual ~mic_t() = default;
};
class audio_control_t {
public:
virtual int set_sink(const std::string &sink) = 0;
class audio_control_t {
public:
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::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const);
std::string
from_sockaddr(const sockaddr *const);
std::pair<std::uint16_t, std::string>
from_sockaddr_ex(const sockaddr *const);
std::unique_ptr<audio_control_t> audio_control();
std::unique_ptr<audio_control_t>
audio_control();
/**
/**
* 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 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
*/
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
std::shared_ptr<display_t>
display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
// A list of names of displays accepted as display_name with the mem_type_e
std::vector<std::string> display_names(mem_type_e hwdevice_type);
// 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);
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 {
low,
normal,
high,
critical
};
void adjust_thread_priority(thread_priority_e priority);
enum class thread_priority_e : int {
low,
normal,
high,
critical
};
void
adjust_thread_priority(thread_priority_e priority);
// Allow OS-specific actions to be taken to prepare for streaming
void streaming_will_start();
void streaming_will_stop();
// Allow OS-specific actions to be taken to prepare for streaming
void
streaming_will_start();
void
streaming_will_stop();
bool restart_supported();
bool restart();
bool
restart_supported();
bool
restart();
struct batched_send_info_t {
const char *buffer;
size_t block_size;
size_t block_count;
struct batched_send_info_t {
const char *buffer;
size_t block_size;
size_t block_count;
std::uintptr_t native_socket;
boost::asio::ip::address &target_address;
uint16_t target_port;
};
bool send_batch(batched_send_info_t &send_info);
std::uintptr_t native_socket;
boost::asio::ip::address &target_address;
uint16_t target_port;
};
bool
send_batch(batched_send_info_t &send_info);
enum class qos_data_type_e : int {
audio,
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);
enum class qos_data_type_e : int {
audio,
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);
input_t input();
void move_mouse(input_t &input, int deltaX, int deltaY);
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
void button_mouse(input_t &input, int button, bool release);
void scroll(input_t &input, int distance);
void hscroll(input_t &input, int distance);
void keyboard(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);
input_t
input();
void
move_mouse(input_t &input, int deltaX, int deltaY);
void
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
void
button_mouse(input_t &input, int button, bool release);
void
scroll(input_t &input, int distance);
void
hscroll(input_t &input, int distance);
void
keyboard(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);
void free_gamepad(input_t &input, int nr);
int
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_TYPE "_nvstream._tcp"
namespace publish {
[[nodiscard]] std::unique_ptr<deinit_t> start();
}
namespace publish {
[[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();
} // namespace platf
std::vector<std::string_view> &
supported_gamepads();
} // namespace platf
#endif //SUNSHINE_COMMON_H
#endif //SUNSHINE_COMMON_H

View File

@@ -17,491 +17,509 @@
#include "src/thread_safe.h"
namespace platf {
using namespace std::literals;
using namespace std::literals;
constexpr pa_channel_position_t position_mapping[] {
PA_CHANNEL_POSITION_FRONT_LEFT,
PA_CHANNEL_POSITION_FRONT_RIGHT,
PA_CHANNEL_POSITION_FRONT_CENTER,
PA_CHANNEL_POSITION_LFE,
PA_CHANNEL_POSITION_REAR_LEFT,
PA_CHANNEL_POSITION_REAR_RIGHT,
PA_CHANNEL_POSITION_SIDE_LEFT,
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
constexpr pa_channel_position_t position_mapping[] {
PA_CHANNEL_POSITION_FRONT_LEFT,
PA_CHANNEL_POSITION_FRONT_RIGHT,
PA_CHANNEL_POSITION_FRONT_CENTER,
PA_CHANNEL_POSITION_LFE,
PA_CHANNEL_POSITION_REAR_LEFT,
PA_CHANNEL_POSITION_REAR_RIGHT,
PA_CHANNEL_POSITION_SIDE_LEFT,
PA_CHANNEL_POSITION_SIDE_RIGHT,
};
public:
loop_t loop;
ctx_t ctx;
std::string requested_sink;
std::string
to_string(const char *name, const std::uint8_t *mapping, int channels) {
std::stringstream ss;
struct {
std::uint32_t stereo = PA_INVALID_INDEX;
std::uint32_t surround51 = PA_INVALID_INDEX;
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;
}
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]) << ',';
});
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);
if(status) {
BOOST_LOG(error) << "Couldn't connect to pulseaudio: "sv << pa_strerror(status);
return -1;
}
ss << " sink_properties=device.description="sv << name;
auto result = ss.str();
worker = std::thread {
[](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;
BOOST_LOG(debug) << "null-sink args: "sv << result;
return result;
}
int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
auto alarm = safe::make_alarm<int>();
struct mic_attr_t: public mic_t {
util::safe_ptr<pa_simple, pa_simple_free> mic;
op_t op {
pa_context_load_module(
ctx.get(),
"module-null-sink",
to_string(name, channel_mapping, channels).c_str(),
cb_i,
alarm.get()),
};
capture_e
sample(std::vector<std::int16_t> &sample_buf) override {
auto sample_size = sample_buf.size();
alarm->wait();
return *alarm->status();
}
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);
int unload_null(std::uint32_t i) {
if(i == PA_INVALID_INDEX) {
return 0;
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;
}
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;
return mic;
}
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";
namespace pa {
template <bool B, class T>
struct add_const_helper;
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
int nullcount = 0;
template <class T>
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) {
if(!sink_info) {
if(!eol) {
BOOST_LOG(error) << "Couldn't get pulseaudio sink info: "sv << pa_strerror(pa_context_errno(ctx));
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>>;
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;
}
// 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;
f(ctx, i, eol);
}
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()) {
return std::nullopt;
alarm->ring(i);
}
auto sink_name = get_default_sink_name();
sink.host = sink_name;
void
ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
auto &f = *(std::function<void(ctx_t::pointer)> *) userdata;
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;
}
f(ctx);
}
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;
}
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);
}
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;
}
}
class server_t: public audio_control_t {
enum ctx_event_e : int {
ready,
terminated,
failed
};
if(sink_name.empty()) {
BOOST_LOG(warning) << "Couldn't find an active default sink. Continuing with virtual audio only."sv;
}
public:
loop_t loop;
ctx_t ctx;
std::string requested_sink;
if(nullcount == 3) {
sink.null = std::make_optional(sink_t::null_t { stereo, surround51, surround71 });
}
struct {
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::string sink_name;
auto alarm = safe::make_alarm<int>();
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"));
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);
}
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;
}
});
if(server_info->default_sink_name) {
sink_name = server_info->default_sink_name;
}
alarm->ring(0);
};
pa_context_set_state_callback(ctx.get(), ctx_state_cb, events_cb.get());
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);
auto status = pa_context_connect(ctx.get(), nullptr, PA_CONTEXT_NOFLAGS, nullptr);
if (status) {
BOOST_LOG(error) << "Couldn't connect to pulseaudio: "sv << pa_strerror(status);
return -1;
}
alarm->ring(0);
return;
worker = std::thread {
[](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();
// 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;
if (audio->init()) {
return nullptr;
}
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;
return audio;
}
~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
} // 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)
#define SUNSHINE_PLATFORM_CUDA_H
#define SUNSHINE_PLATFORM_CUDA_H
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace platf {
class hwdevice_t;
class img_t;
} // namespace platf
class hwdevice_t;
class img_t;
} // namespace platf
namespace cuda {
namespace nvfbc {
std::vector<std::string> display_names();
}
std::shared_ptr<platf::hwdevice_t> make_hwdevice(int width, int height, bool vram);
int init();
} // namespace cuda
namespace nvfbc {
std::vector<std::string>
display_names();
}
std::shared_ptr<platf::hwdevice_t>
make_hwdevice(int width, int height, bool vram);
int
init();
} // namespace cuda
typedef struct cudaArray *cudaArray_t;
#if !defined(__CUDACC__)
#if !defined(__CUDACC__)
typedef struct CUstream_st *cudaStream_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) unsigned long long cudaTextureObject_t;
#endif /* !defined(__CUDACC__) */
#endif /* !defined(__CUDACC__) */
namespace cuda {
class freeCudaPtr_t {
public:
void operator()(void *ptr);
};
class freeCudaPtr_t {
public:
void
operator()(void *ptr);
};
class freeCudaStream_t {
public:
void operator()(cudaStream_t ptr);
};
class freeCudaStream_t {
public:
void
operator()(cudaStream_t ptr);
};
using ptr_t = std::unique_ptr<void, freeCudaPtr_t>;
using stream_t = std::unique_ptr<CUstream_st, freeCudaStream_t>;
using ptr_t = std::unique_ptr<void, freeCudaPtr_t>;
using stream_t = std::unique_ptr<CUstream_st, freeCudaStream_t>;
stream_t make_stream(int flags = 0);
stream_t
make_stream(int flags = 0);
struct viewport_t {
int width, height;
int offsetX, offsetY;
};
struct viewport_t {
int width, height;
int offsetX, offsetY;
};
class tex_t {
public:
static std::optional<tex_t> make(int height, int pitch);
class tex_t {
public:
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 {
cudaTextureObject_t point;
cudaTextureObject_t linear;
} texture;
};
struct texture {
cudaTextureObject_t point;
cudaTextureObject_t linear;
} texture;
};
class sws_t {
public:
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);
class sws_t {
public:
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);
/**
/**
* 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
*
* pitch -- The size of a single row of pixels in bytes
*/
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, int pitch);
static std::optional<sws_t>
make(int in_width, int in_height, int out_width, int out_height, int pitch);
// Converts loaded image into a CUDevicePtr
int convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream);
int convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport);
// Converts loaded image into a CUDevicePtr
int
convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream);
int
convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport);
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;
};
} // namespace cuda
float scale;
};
} // namespace cuda
#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 gl_drain_errors_helper(__FILE__ ":" SUNSHINE_STRINGIFY(__LINE__))
extern "C" int close(int __fd);
extern "C" int
close(int __fd);
// X11 Display
extern "C" struct _XDisplay;
struct AVFrame;
void free_frame(AVFrame *frame);
void
free_frame(AVFrame *frame);
using frame_t = util::safe_ptr<AVFrame, free_frame>;
namespace gl {
extern GladGLContext ctx;
void drain_errors(const std::string_view &prefix);
extern GladGLContext ctx;
void
drain_errors(const std::string_view &prefix);
class tex_t : public util::buffer_t<GLuint> {
using util::buffer_t<GLuint>::buffer_t;
class tex_t: public util::buffer_t<GLuint> {
using util::buffer_t<GLuint>::buffer_t;
public:
tex_t(tex_t &&) = default;
tex_t &operator=(tex_t &&) = default;
public:
tex_t(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> {
using util::buffer_t<GLuint>::buffer_t;
class frame_buf_t: public util::buffer_t<GLuint> {
using util::buffer_t<GLuint>::buffer_t;
public:
frame_buf_t(frame_buf_t &&) = default;
frame_buf_t &operator=(frame_buf_t &&) = default;
public:
frame_buf_t(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) {
int x = 0;
for(auto fb : (*this)) {
ctx.BindFramebuffer(GL_FRAMEBUFFER, fb);
ctx.FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, 0, 0);
inline void
bind(std::nullptr_t, std::nullptr_t) {
int x = 0;
for (auto fb : (*this)) {
ctx.BindFramebuffer(GL_FRAMEBUFFER, fb);
ctx.FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, 0, 0);
++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;
++x;
}
return;
}
int x = 0;
std::for_each(it_begin, it_end, [&](auto tex) {
ctx.BindFramebuffer(GL_FRAMEBUFFER, (*this)[x]);
ctx.BindTexture(GL_TEXTURE_2D, tex);
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;
}
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
*/
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 {
KITTY_USING_MOVE_T(shader_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
if(el != std::numeric_limits<GLuint>::max()) {
ctx.DeleteShader(el);
}
});
class shader_t {
KITTY_USING_MOVE_T(shader_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
if (el != std::numeric_limits<GLuint>::max()) {
ctx.DeleteShader(el);
}
});
public:
std::string err_str();
public:
std::string
err_str();
static util::Either<shader_t, std::string> compile(const std::string_view &source, GLenum type);
static util::Either<shader_t, std::string>
compile(const std::string_view &source, GLenum type);
GLuint handle() const;
GLuint
handle() const;
private:
shader_internal_t _shader;
};
private:
shader_internal_t _shader;
};
class buffer_t {
KITTY_USING_MOVE_T(buffer_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
if(el != std::numeric_limits<GLuint>::max()) {
ctx.DeleteBuffers(1, &el);
}
});
class buffer_t {
KITTY_USING_MOVE_T(buffer_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
if (el != std::numeric_limits<GLuint>::max()) {
ctx.DeleteBuffers(1, &el);
}
});
public:
static buffer_t make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data);
public:
static buffer_t
make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data);
GLuint handle() const;
GLuint
handle() const;
const char *block() const;
const char *
block() const;
void update(const std::string_view &view, std::size_t offset = 0);
void update(std::string_view *members, std::size_t count, std::size_t offset = 0);
void
update(const std::string_view &view, std::size_t offset = 0);
void
update(std::string_view *members, std::size_t count, std::size_t offset = 0);
private:
const char *_block;
private:
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 {
KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
if(el != std::numeric_limits<GLuint>::max()) {
ctx.DeleteProgram(el);
}
});
class program_t {
KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
if (el != std::numeric_limits<GLuint>::max()) {
ctx.DeleteProgram(el);
}
});
public:
std::string err_str();
public:
std::string
err_str();
static util::Either<program_t, std::string> link(const shader_t &vert, const shader_t &frag);
static util::Either<program_t, std::string>
link(const shader_t &vert, const shader_t &frag);
void bind(const buffer_t &buffer);
void
bind(const buffer_t &buffer);
std::optional<buffer_t> uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count);
std::optional<buffer_t>
uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count);
GLuint handle() const;
GLuint
handle() const;
private:
program_internal_t _program;
};
} // namespace gl
private:
program_internal_t _program;
};
} // namespace gl
namespace gbm {
struct device;
typedef void (*device_destroy_fn)(device *gbm);
typedef device *(*create_device_fn)(int fd);
struct device;
typedef void (*device_destroy_fn)(device *gbm);
typedef device *(*create_device_fn)(int fd);
extern device_destroy_fn device_destroy;
extern create_device_fn create_device;
extern device_destroy_fn device_destroy;
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 {
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 {
display_t::pointer display;
EGLImage xrgb8;
struct rgb_img_t {
display_t::pointer display;
EGLImage xrgb8;
gl::tex_t tex;
};
gl::tex_t tex;
};
struct nv12_img_t {
display_t::pointer display;
EGLImage r8;
EGLImage bg88;
struct nv12_img_t {
display_t::pointer display;
EGLImage r8;
EGLImage bg88;
gl::tex_t tex;
gl::frame_buf_t buf;
gl::tex_t tex;
gl::frame_buf_t buf;
// sizeof(va::DRMPRIMESurfaceDescriptor::objects) / sizeof(va::DRMPRIMESurfaceDescriptor::objects[0]);
static constexpr std::size_t num_fds = 4;
// sizeof(va::DRMPRIMESurfaceDescriptor::objects) / sizeof(va::DRMPRIMESurfaceDescriptor::objects[0]);
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, , {
if(el.xrgb8) {
eglDestroyImage(el.display, el.xrgb8);
}
});
KITTY_USING_MOVE_T(rgb_t, rgb_img_t, , {
if (el.xrgb8) {
eglDestroyImage(el.display, el.xrgb8);
}
});
KITTY_USING_MOVE_T(nv12_t, nv12_img_t, , {
if(el.r8) {
eglDestroyImage(el.display, el.r8);
}
KITTY_USING_MOVE_T(nv12_t, nv12_img_t, , {
if (el.r8) {
eglDestroyImage(el.display, el.r8);
}
if(el.bg88) {
eglDestroyImage(el.display, el.bg88);
}
});
if (el.bg88) {
eglDestroyImage(el.display, el.bg88);
}
});
KITTY_USING_MOVE_T(ctx_t, (std::tuple<display_t::pointer, EGLContext>), , {
TUPLE_2D_REF(disp, ctx, el);
if(ctx) {
eglMakeCurrent(disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(disp, ctx);
}
});
KITTY_USING_MOVE_T(ctx_t, (std::tuple<display_t::pointer, EGLContext>), , {
TUPLE_2D_REF(disp, ctx, el);
if (ctx) {
eglMakeCurrent(disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(disp, ctx);
}
});
struct surface_descriptor_t {
int width;
int height;
int fds[4];
std::uint32_t fourcc;
std::uint64_t modifier;
std::uint32_t pitches[4];
std::uint32_t offsets[4];
};
struct surface_descriptor_t {
int width;
int height;
int fds[4];
std::uint32_t fourcc;
std::uint64_t modifier;
std::uint32_t pitches[4];
std::uint32_t offsets[4];
};
display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display);
std::optional<ctx_t> make_ctx(display_t::pointer display);
display_t
make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display);
std::optional<ctx_t>
make_ctx(display_t::pointer display);
std::optional<rgb_t> import_source(
display_t::pointer egl_display,
const surface_descriptor_t &xrgb);
std::optional<rgb_t>
import_source(
display_t::pointer egl_display,
const surface_descriptor_t &xrgb);
std::optional<nv12_t> import_target(
display_t::pointer egl_display,
std::array<file_t, nv12_img_t::num_fds> &&fds,
const surface_descriptor_t &r8, const surface_descriptor_t &gr88);
std::optional<nv12_t>
import_target(
display_t::pointer egl_display,
std::array<file_t, nv12_img_t::num_fds> &&fds,
const surface_descriptor_t &r8, const surface_descriptor_t &gr88);
class cursor_t : public platf::img_t {
public:
int x, y;
class cursor_t: public platf::img_t {
public:
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
class img_descriptor_t : public cursor_t {
public:
~img_descriptor_t() {
reset();
}
// Allow cursor and the underlying image to be kept together
class img_descriptor_t: public cursor_t {
public:
~img_descriptor_t() {
reset();
}
void reset() {
for(auto x = 0; x < 4; ++x) {
if(sd.fds[x] >= 0) {
close(sd.fds[x]);
void
reset() {
for (auto x = 0; x < 4; ++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
std::uint64_t sequence;
};
// Increment sequence when new rgb_t needs to be created
std::uint64_t sequence;
};
class sws_t {
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> make(int in_width, int in_height, int out_width, int out_heigth);
class sws_t {
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>
make(int in_width, int in_height, int out_width, int out_heigth);
// Convert the loaded image into the first two framebuffers
int convert(gl::frame_buf_t &fb);
// Convert the loaded image into the first two framebuffers
int
convert(gl::frame_buf_t &fb);
// Make an area of the image black
int blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height);
// Make an area of the image black
int
blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height);
void load_ram(platf::img_t &img);
void load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture);
void
load_ram(platf::img_t &img);
void
load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture);
void 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 second texture is the cursor image
gl::tex_t tex;
// The first texture is the monitor image.
// The second texture is the cursor image
gl::tex_t tex;
// The cursor image will be blended into this framebuffer
gl::frame_buf_t cursor_framebuffer;
gl::frame_buf_t copy_framebuffer;
// The cursor image will be blended into this framebuffer
gl::frame_buf_t cursor_framebuffer;
gl::frame_buf_t copy_framebuffer;
// Y - shader, UV - shader, Cursor - shader
gl::program_t program[3];
gl::buffer_t color_matrix;
// Y - shader, UV - shader, Cursor - shader
gl::program_t program[3];
gl::buffer_t color_matrix;
int out_width, out_height;
int in_width, in_height;
int offsetX, offsetY;
int out_width, out_height;
int in_width, in_height;
int offsetX, offsetY;
// Pointer to the texture to be converted to nv12
int loaded_texture;
// Pointer to the texture to be converted to nv12
int loaded_texture;
// Store latest cursor for load_vram
std::uint64_t serial;
};
// Store latest cursor for load_vram
std::uint64_t serial;
};
bool fail();
} // namespace egl
bool
fail();
} // namespace egl
#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"
KITTY_USING_MOVE_T(file_t, int, -1, {
if(el >= 0) {
if (el >= 0) {
close(el);
}
});
@@ -21,11 +21,13 @@ enum class window_system_e {
extern window_system_e window_system;
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);
void *handle(const std::vector<const char *> &libs);
int
load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
void *
handle(const std::vector<const char *> &libs);
} // namespace dyn
} // namespace dyn
#endif

View File

@@ -12,418 +12,426 @@ using namespace std::literals;
namespace avahi {
/** Error codes used by avahi */
enum err_e {
OK = 0, /**< OK */
ERR_FAILURE = -1, /**< Generic error code */
ERR_BAD_STATE = -2, /**< Object was in a bad state */
ERR_INVALID_HOST_NAME = -3, /**< Invalid host name */
ERR_INVALID_DOMAIN_NAME = -4, /**< Invalid domain name */
ERR_NO_NETWORK = -5, /**< No suitable network protocol available */
ERR_INVALID_TTL = -6, /**< Invalid DNS TTL */
ERR_IS_PATTERN = -7, /**< RR key is pattern */
ERR_COLLISION = -8, /**< Name collision */
ERR_INVALID_RECORD = -9, /**< Invalid RR */
/** Error codes used by avahi */
enum err_e {
OK = 0, /**< OK */
ERR_FAILURE = -1, /**< Generic error code */
ERR_BAD_STATE = -2, /**< Object was in a bad state */
ERR_INVALID_HOST_NAME = -3, /**< Invalid host name */
ERR_INVALID_DOMAIN_NAME = -4, /**< Invalid domain name */
ERR_NO_NETWORK = -5, /**< No suitable network protocol available */
ERR_INVALID_TTL = -6, /**< Invalid DNS TTL */
ERR_IS_PATTERN = -7, /**< RR key is pattern */
ERR_COLLISION = -8, /**< Name collision */
ERR_INVALID_RECORD = -9, /**< Invalid RR */
ERR_INVALID_SERVICE_NAME = -10, /**< Invalid service name */
ERR_INVALID_SERVICE_TYPE = -11, /**< Invalid service type */
ERR_INVALID_PORT = -12, /**< Invalid port number */
ERR_INVALID_KEY = -13, /**< Invalid key */
ERR_INVALID_ADDRESS = -14, /**< Invalid address */
ERR_TIMEOUT = -15, /**< Timeout reached */
ERR_TOO_MANY_CLIENTS = -16, /**< Too many clients */
ERR_TOO_MANY_OBJECTS = -17, /**< Too many objects */
ERR_TOO_MANY_ENTRIES = -18, /**< Too many entries */
ERR_OS = -19, /**< OS error */
ERR_INVALID_SERVICE_NAME = -10, /**< Invalid service name */
ERR_INVALID_SERVICE_TYPE = -11, /**< Invalid service type */
ERR_INVALID_PORT = -12, /**< Invalid port number */
ERR_INVALID_KEY = -13, /**< Invalid key */
ERR_INVALID_ADDRESS = -14, /**< Invalid address */
ERR_TIMEOUT = -15, /**< Timeout reached */
ERR_TOO_MANY_CLIENTS = -16, /**< Too many clients */
ERR_TOO_MANY_OBJECTS = -17, /**< Too many objects */
ERR_TOO_MANY_ENTRIES = -18, /**< Too many entries */
ERR_OS = -19, /**< OS error */
ERR_ACCESS_DENIED = -20, /**< Access denied */
ERR_INVALID_OPERATION = -21, /**< Invalid operation */
ERR_DBUS_ERROR = -22, /**< An unexpected D-Bus error occurred */
ERR_DISCONNECTED = -23, /**< Daemon connection failed */
ERR_NO_MEMORY = -24, /**< Memory exhausted */
ERR_INVALID_OBJECT = -25, /**< The object passed to this function was invalid */
ERR_NO_DAEMON = -26, /**< Daemon not running */
ERR_INVALID_INTERFACE = -27, /**< Invalid interface */
ERR_INVALID_PROTOCOL = -28, /**< Invalid protocol */
ERR_INVALID_FLAGS = -29, /**< Invalid flags */
ERR_ACCESS_DENIED = -20, /**< Access denied */
ERR_INVALID_OPERATION = -21, /**< Invalid operation */
ERR_DBUS_ERROR = -22, /**< An unexpected D-Bus error occurred */
ERR_DISCONNECTED = -23, /**< Daemon connection failed */
ERR_NO_MEMORY = -24, /**< Memory exhausted */
ERR_INVALID_OBJECT = -25, /**< The object passed to this function was invalid */
ERR_NO_DAEMON = -26, /**< Daemon not running */
ERR_INVALID_INTERFACE = -27, /**< Invalid interface */
ERR_INVALID_PROTOCOL = -28, /**< Invalid protocol */
ERR_INVALID_FLAGS = -29, /**< Invalid flags */
ERR_NOT_FOUND = -30, /**< Not found */
ERR_INVALID_CONFIG = -31, /**< Configuration error */
ERR_VERSION_MISMATCH = -32, /**< Verson mismatch */
ERR_INVALID_SERVICE_SUBTYPE = -33, /**< Invalid service subtype */
ERR_INVALID_PACKET = -34, /**< Invalid packet */
ERR_INVALID_DNS_ERROR = -35, /**< Invlaid DNS return code */
ERR_DNS_FORMERR = -36, /**< DNS Error: Form error */
ERR_DNS_SERVFAIL = -37, /**< DNS Error: Server Failure */
ERR_DNS_NXDOMAIN = -38, /**< DNS Error: No such domain */
ERR_DNS_NOTIMP = -39, /**< DNS Error: Not implemented */
ERR_NOT_FOUND = -30, /**< Not found */
ERR_INVALID_CONFIG = -31, /**< Configuration error */
ERR_VERSION_MISMATCH = -32, /**< Verson mismatch */
ERR_INVALID_SERVICE_SUBTYPE = -33, /**< Invalid service subtype */
ERR_INVALID_PACKET = -34, /**< Invalid packet */
ERR_INVALID_DNS_ERROR = -35, /**< Invlaid DNS return code */
ERR_DNS_FORMERR = -36, /**< DNS Error: Form error */
ERR_DNS_SERVFAIL = -37, /**< DNS Error: Server Failure */
ERR_DNS_NXDOMAIN = -38, /**< DNS Error: No such domain */
ERR_DNS_NOTIMP = -39, /**< DNS Error: Not implemented */
ERR_DNS_REFUSED = -40, /**< DNS Error: Operation refused */
ERR_DNS_YXDOMAIN = -41,
ERR_DNS_YXRRSET = -42,
ERR_DNS_NXRRSET = -43,
ERR_DNS_NOTAUTH = -44, /**< DNS Error: Not authorized */
ERR_DNS_NOTZONE = -45,
ERR_INVALID_RDATA = -46, /**< Invalid RDATA */
ERR_INVALID_DNS_CLASS = -47, /**< Invalid DNS class */
ERR_INVALID_DNS_TYPE = -48, /**< Invalid DNS type */
ERR_NOT_SUPPORTED = -49, /**< Not supported */
ERR_DNS_REFUSED = -40, /**< DNS Error: Operation refused */
ERR_DNS_YXDOMAIN = -41,
ERR_DNS_YXRRSET = -42,
ERR_DNS_NXRRSET = -43,
ERR_DNS_NOTAUTH = -44, /**< DNS Error: Not authorized */
ERR_DNS_NOTZONE = -45,
ERR_INVALID_RDATA = -46, /**< Invalid RDATA */
ERR_INVALID_DNS_CLASS = -47, /**< Invalid DNS class */
ERR_INVALID_DNS_TYPE = -48, /**< Invalid DNS type */
ERR_NOT_SUPPORTED = -49, /**< Not supported */
ERR_NOT_PERMITTED = -50, /**< Operation not permitted */
ERR_INVALID_ARGUMENT = -51, /**< Invalid argument */
ERR_IS_EMPTY = -52, /**< Is empty */
ERR_NO_CHANGE = -53, /**< The requested operation is invalid because it is redundant */
ERR_NOT_PERMITTED = -50, /**< Operation not permitted */
ERR_INVALID_ARGUMENT = -51, /**< Invalid argument */
ERR_IS_EMPTY = -52, /**< Is empty */
ERR_NO_CHANGE = -53, /**< The requested operation is invalid because it is redundant */
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" },
ERR_MAX = -54
};
if(dyn::load(handle, funcs)) {
return -1;
}
funcs_loaded = true;
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" },
constexpr auto IF_UNSPEC = -1;
enum proto {
PROTO_INET = 0, /**< IPv4 */
PROTO_INET6 = 1, /**< IPv6 */
PROTO_UNSPEC = -1 /**< Unspecified/all protocol(s) */
};
if(dyn::load(handle, funcs)) {
return -1;
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)) {
return -1;
}
funcs_loaded = true;
return 0;
}
funcs_loaded = true;
return 0;
}
} // namespace avahi
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)) {
return -1;
}
funcs_loaded = true;
return 0;
}
} // namespace avahi
namespace platf::publish {
template<class T>
void 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:;
template <class T>
void
free(T *p) {
avahi::free(p);
}
}
void create_services(avahi::Client *c) {
int ret;
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>;
auto fg = util::fail_guard([]() {
avahi::simple_poll_quit(poll.get());
});
avahi::EntryGroup *group = nullptr;
if(!group) {
if(!(group = avahi::entry_group_new(c, entry_group_callback, nullptr))) {
BOOST_LOG(error) << "avahi::entry_group_new() failed: "sv << avahi::strerror(avahi::client_errno(c));
return;
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:;
}
}
if(avahi::entry_group_is_empty(group)) {
BOOST_LOG(info) << "Adding avahi service "sv << name.get();
void
create_services(avahi::Client *c) {
int ret;
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);
auto fg = util::fail_guard([]() {
avahi::simple_poll_quit(poll.get());
});
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();
if (!group) {
if (!(group = avahi::entry_group_new(c, entry_group_callback, nullptr))) {
BOOST_LOG(error) << "avahi::entry_group_new() failed: "sv << avahi::strerror(avahi::client_errno(c));
return;
}
}
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;
}
BOOST_LOG(error) << "Failed to add "sv << SERVICE_TYPE << " service: "sv << avahi::strerror(ret);
return;
ret = avahi::entry_group_commit(group);
if (ret < 0) {
BOOST_LOG(error) << "Failed to commit entry group: "sv << avahi::strerror(ret);
return;
}
}
ret = avahi::entry_group_commit(group);
if(ret < 0) {
BOOST_LOG(error) << "Failed to commit entry group: "sv << avahi::strerror(ret);
return;
fg.disable();
}
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 *) {
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:;
}
}
deinit_t(std::thread poll_thread):
poll_thread { std::move(poll_thread) } {}
class deinit_t : public ::platf::deinit_t {
public:
std::thread poll_thread;
~deinit_t() override {
if (avahi::simple_poll_quit && poll) {
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 {
if(avahi::simple_poll_quit && poll) {
avahi::simple_poll_quit(poll.get());
[[nodiscard]] std::unique_ptr<::platf::deinit_t>
start() {
if (avahi::init_client()) {
return nullptr;
}
if(poll_thread.joinable()) {
poll_thread.join();
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() });
}
};
[[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
} // namespace platf::publish

File diff suppressed because it is too large Load Diff

View File

@@ -5,23 +5,28 @@
#include "src/platform/common.h"
namespace egl {
struct surface_descriptor_t;
struct surface_descriptor_t;
}
namespace va {
/**
/**
* Width --> Width of the image
* Height --> Height of the image
* offset_x --> Horizontal 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
*/
std::shared_ptr<platf::hwdevice_t> make_hwdevice(int width, int height, 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);
std::shared_ptr<platf::hwdevice_t>
make_hwdevice(int width, int height, 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
bool validate(int fd);
// Ensure the render device pointed to by fd is capable of encoding h264 with the hevc_mode configured
bool
validate(int fd);
int init();
} // namespace va
int
init();
} // namespace va
#endif

View File

@@ -21,45 +21,49 @@ using namespace std::literals;
namespace wl {
// Helper to call C++ method from wayland C callback
template<class T, class Method, Method m, class... Params>
static auto classCall(void *data, Params... params) -> decltype(((*reinterpret_cast<T *>(data)).*m)(params...)) {
return ((*reinterpret_cast<T *>(data)).*m)(params...);
}
// Helper to call C++ method from wayland C callback
template <class T, class Method, Method m, class... Params>
static auto
classCall(void *data, Params... params) -> decltype(((*reinterpret_cast<T *>(data)).*m)(params...)) {
return ((*reinterpret_cast<T *>(data)).*m)(params...);
}
#define CLASS_CALL(c, m) classCall<c, decltype(&c::m), &c::m>
int display_t::init(const char *display_name) {
if(!display_name) {
display_name = std::getenv("WAYLAND_DISPLAY");
int
display_t::init(const char *display_name) {
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) {
BOOST_LOG(error) << "Environment variable WAYLAND_DISPLAY has not been defined"sv;
return -1;
void
display_t::roundtrip() {
wl_display_roundtrip(display_internal.get());
}
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;
wl_registry *
display_t::registry() {
return wl_display_get_registry(display_internal.get());
}
BOOST_LOG(info) << "Found display ["sv << display_name << ']';
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 {
inline monitor_t::monitor_t(wl_output *output):
output { output }, listener {
&CLASS_CALL(monitor_t, xdg_position),
&CLASS_CALL(monitor_t, xdg_size),
&CLASS_CALL(monitor_t, xdg_done),
@@ -67,208 +71,226 @@ inline monitor_t::monitor_t(wl_output *output)
&CLASS_CALL(monitor_t, xdg_description)
} {}
inline void monitor_t::xdg_name(zxdg_output_v1 *, const char *name) {
this->name = name;
inline void
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) {
this->description = description;
void
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) {
viewport.offset_x = x;
viewport.offset_y = y;
void
monitor_t::xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y) {
viewport.offset_x = x;
viewport.offset_y = y;
BOOST_LOG(info) << "Offset: "sv << x << 'x' << y;
}
BOOST_LOG(info) << "Offset: "sv << x << 'x' << y;
}
void monitor_t::xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height) {
viewport.width = width;
viewport.height = height;
void
monitor_t::xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t 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 *) {
BOOST_LOG(info) << "All info about monitor ["sv << name << "] has been send"sv;
}
void
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) {
auto xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, output);
zxdg_output_v1_add_listener(xdg_output, &listener, this);
}
void
monitor_t::listen(zxdg_output_manager_v1 *output_manager) {
auto xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, output);
zxdg_output_v1_add_listener(xdg_output, &listener, this);
}
interface_t::interface_t() noexcept
: output_manager { nullptr }, listener {
interface_t::interface_t() noexcept
:
output_manager { nullptr },
listener {
&CLASS_CALL(interface_t, add_interface),
&CLASS_CALL(interface_t, del_interface)
} {}
void interface_t::listen(wl_registry *registry) {
wl_registry_add_listener(registry, &listener, this);
}
void interface_t::add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version) {
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)));
void
interface_t::listen(wl_registry *registry) {
wl_registry_add_listener(registry, &listener, this);
}
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) {
BOOST_LOG(info) << "Delete: "sv << id;
}
dmabuf_t::dmabuf_t()
: 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, object),
&CLASS_CALL(dmabuf_t, ready),
&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(
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();
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);
next_frame->sd.fourcc = format;
next_frame->sd.width = width;
next_frame->sd.height = height;
next_frame->sd.modifier = (((std::uint64_t)high) << 32) | low;
}
status = WAITING;
}
void dmabuf_t::object(
zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t index,
std::int32_t fd,
std::uint32_t size,
std::uint32_t offset,
std::uint32_t stride,
std::uint32_t plane_index) {
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;
dmabuf_t::~dmabuf_t() {
for (auto &frame : frames) {
frame.destroy();
}
}
}
frame_t::frame_t() {
// File descriptors aren't open
std::fill_n(sd.fds, 4, -1);
};
void
dmabuf_t::frame(
zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t width, std::uint32_t height,
std::uint32_t x, std::uint32_t y,
std::uint32_t buffer_flags, std::uint32_t flags,
std::uint32_t 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) {
display_t display;
if(display.init(display_name)) {
return {};
next_frame->sd.fourcc = format;
next_frame->sd.width = width;
next_frame->sd.height = height;
next_frame->sd.modifier = (((std::uint64_t) high) << 32) | low;
}
interface_t interface;
interface.listen(display.registry());
void
dmabuf_t::object(
zwlr_export_dmabuf_frame_v1 *frame,
std::uint32_t index,
std::int32_t fd,
std::uint32_t size,
std::uint32_t offset,
std::uint32_t stride,
std::uint32_t plane_index) {
auto next_frame = get_next_frame();
display.roundtrip();
if(!interface[interface_t::XDG_OUTPUT]) {
BOOST_LOG(error) << "Missing Wayland wire XDG_OUTPUT"sv;
return {};
next_frame->sd.fds[plane_index] = fd;
next_frame->sd.pitches[plane_index] = stride;
next_frame->sd.offsets[plane_index] = offset;
}
for(auto &monitor : interface.monitors) {
monitor->listen(interface.output_manager);
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;
}
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() {
display_t display;
status = REINIT;
}
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() {
static bool validated = validate();
sd.fds[x] = -1;
}
}
}
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

View File

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

View File

@@ -7,372 +7,386 @@
using namespace std::literals;
namespace wl {
static int env_width;
static int env_height;
static int env_width;
static int env_height;
struct img_t : public platf::img_t {
~img_t() override {
delete[] data;
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;
struct img_t: public platf::img_t {
~img_t() override {
delete[] data;
data = nullptr;
}
};
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(!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();
if (display.init()) {
return -1;
}
}
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();
if(to < std::chrono::steady_clock::now()) {
return platf::capture_e::timeout;
if (!interface[wl::interface_t::XDG_OUTPUT]) {
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(
dmabuf.status == dmabuf_t::REINIT ||
current_frame->sd.width != width ||
current_frame->sd.height != height) {
auto monitor = interface.monitors[0].get();
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;
interface_t interface;
dmabuf_t dmabuf;
if (to < std::chrono::steady_clock::now()) {
return platf::capture_e::timeout;
}
} while (dmabuf.status == dmabuf_t::WAITING);
wl_output *output;
};
auto current_frame = dmabuf.current_frame;
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);
if (
dmabuf.status == dmabuf_t::REINIT ||
current_frame->sd.width != width ||
current_frame->sd.height != height) {
return platf::capture_e::reinit;
}
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 platf::capture_e::ok;
}
platf::mem_type_e mem_type;
std::chrono::nanoseconds delay;
wl::display_t display;
interface_t interface;
dmabuf_t dmabuf;
wl_output *output;
};
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 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 status = wlr_t::snapshot(img_out_base, timeout, cursor);
if(status != platf::capture_e::ok) {
return status;
}
auto rgb_opt = egl::import_source(egl_display.get(), current_frame->sd);
auto current_frame = dmabuf.current_frame;
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);
if (!rgb_opt) {
return platf::capture_e::reinit;
}
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 << ']';
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);
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;
}
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) {
auto status = wlr_t::snapshot(img_out_base, timeout, cursor);
if(status != platf::capture_e::ok) {
return status;
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;
}
auto img = (egl::img_descriptor_t *)img_out_base;
img->reset();
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);
}
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;
}
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>();
}
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 {
return snapshot(img, 1000ms, false) != platf::capture_e::ok;
}
std::uint64_t sequence {};
};
std::uint64_t sequence {};
};
} // namespace wl
} // namespace wl
namespace platf {
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if(hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
return nullptr;
}
std::shared_ptr<display_t>
wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
return nullptr;
}
if(hwdevice_type == platf::mem_type_e::vaapi) {
auto wlr = std::make_shared<wl::wlr_vram_t>();
if(wlr->init(hwdevice_type, display_name, config)) {
if (hwdevice_type == platf::mem_type_e::vaapi) {
auto wlr = std::make_shared<wl::wlr_vram_t>();
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 wlr;
}
auto wlr = std::make_shared<wl::wlr_ram_t>();
if(wlr->init(hwdevice_type, display_name, config)) {
return nullptr;
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;
}
return wlr;
}
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
} // namespace platf

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,189 +14,197 @@
namespace fs = std::filesystem;
namespace platf {
using namespace std::literals;
using namespace std::literals;
av_img_t::~av_img_t() {
if(pixel_buffer != NULL) {
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>();
av_img_t::~av_img_t() {
if (pixel_buffer != NULL) {
CVPixelBufferUnlockBaseAddress(pixel_buffer, 0);
}
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;
}
else {
BOOST_LOG(error) << "Unsupported Pixel Format."sv;
return nullptr;
}
data = nullptr;
}
int dummy_img(img_t *img) override {
auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) {
auto av_img = (av_img_t *)img;
struct av_display_t: public display_t {
AVVideo *av_capture;
CGDirectDisplayID display_id;
CFRetain(sampleBuffer);
~av_display_t() {
[av_capture release];
}
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
capture_e
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
// the CFRelease as custom deallocator
if(av_img->pixel_buffer != nullptr)
CVPixelBufferUnlockBaseAddress(((av_img_t *)img)->pixel_buffer, 0);
auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) {
auto av_img_next = std::static_pointer_cast<av_img_t>(img_next);
if(av_img->sample_buffer != nullptr)
CFRelease(av_img->sample_buffer);
CFRetain(sampleBuffer);
av_img->sample_buffer = sampleBuffer;
av_img->pixel_buffer = pixelBuffer;
img->data = (uint8_t *)CVPixelBufferGetBaseAddress(pixelBuffer);
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
size_t extraPixels[4];
CVPixelBufferGetExtendedPixels(pixelBuffer, &extraPixels[0], &extraPixels[1], &extraPixels[2], &extraPixels[3]);
if (av_img_next->pixel_buffer != nullptr)
CVPixelBufferUnlockBaseAddress(av_img_next->pixel_buffer, 0);
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;
if (av_img_next->sample_buffer != nullptr)
CFRelease(av_img_next->sample_buffer);
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.
*
* display --> an opaque pointer to an object of this class
* width --> the intended capture width
* height --> the intended capture height
*/
static void setResolution(void *display, int width, int height) {
[static_cast<AVVideo *>(display) setFrameWidth:width frameHeight:height];
}
static void
setResolution(void *display, int width, int height) {
[static_cast<AVVideo *>(display) setFrameWidth:width frameHeight:height];
}
static void setPixelFormat(void *display, OSType pixelFormat) {
static_cast<AVVideo *>(display).pixelFormat = pixelFormat;
}
};
static void
setPixelFormat(void *display, OSType pixelFormat) {
static_cast<AVVideo *>(display).pixelFormat = pixelFormat;
}
};
std::shared_ptr<display_t> display(platf::mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if(hwdevice_type != platf::mem_type_e::system) {
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
return nullptr;
}
std::shared_ptr<display_t>
display(platf::mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (hwdevice_type != platf::mem_type_e::system) {
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();
if(!display_name.empty()) {
auto display_array = [AVVideo displayNames];
display->display_id = CGMainDisplayID();
if (!display_name.empty()) {
auto display_array = [AVVideo displayNames];
for(NSDictionary *item in display_array) {
NSString *name = item[@"name"];
if(name.UTF8String == display_name) {
NSNumber *display_id = item[@"id"];
display->display_id = [display_id unsignedIntValue];
for (NSDictionary *item in display_array) {
NSString *name = item[@"name"];
if (name.UTF8String == display_name) {
NSNumber *display_id = item[@"id"];
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) {
BOOST_LOG(error) << "Video setup failed."sv;
return nullptr;
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;
}
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
} // namespace platf

View File

@@ -11,36 +11,37 @@
#define MULTICLICK_DELAY_NS 500000000
namespace platf {
using namespace std::literals;
using namespace std::literals;
struct macos_input_t {
public:
CGDirectDisplayID display;
CGFloat displayScaling;
CGEventSourceRef source;
struct macos_input_t {
public:
CGDirectDisplayID display;
CGFloat displayScaling;
CGEventSourceRef source;
// keyboard related stuff
CGEventRef kb_event;
CGEventFlags kb_flags;
// keyboard related stuff
CGEventRef kb_event;
CGEventFlags kb_flags;
// mouse related stuff
CGEventRef mouse_event; // mouse event source
bool mouse_down[3]; // mouse button status
uint64_t last_mouse_event[3][2]; // timestamp of last mouse events
};
// mouse related stuff
CGEventRef mouse_event; // mouse event source
bool mouse_down[3]; // mouse button status
uint64_t last_mouse_event[3][2]; // timestamp of last mouse events
};
// A struct to hold a Windows keycode to Mac virtual keycode mapping.
struct KeyCodeMap {
int win_keycode;
int mac_keycode;
};
// A struct to hold a Windows keycode to Mac virtual keycode mapping.
struct KeyCodeMap {
int win_keycode;
int mac_keycode;
};
// Customized less operator for using std::lower_bound() on a KeyCodeMap array.
bool operator<(const KeyCodeMap &a, const KeyCodeMap &b) {
return a.win_keycode < b.win_keycode;
}
// Customized less operator for using std::lower_bound() on a KeyCodeMap array.
bool
operator<(const KeyCodeMap &a, const KeyCodeMap &b) {
return a.win_keycode < b.win_keycode;
}
// clang-format off
// clang-format off
const KeyCodeMap kKeyCodesMap[] = {
{ 0x08 /* VKEY_BACK */, kVK_Delete },
{ 0x09 /* VKEY_TAB */, kVK_Tab },
@@ -210,264 +211,281 @@ const KeyCodeMap kKeyCodesMap[] = {
{ 0xFD /* VKEY_PA1 */, -1 },
{ 0xFE /* VKEY_OEM_CLEAR */, kVK_ANSI_KeypadClear }
};
// clang-format on
// clang-format on
int keysym(int keycode) {
KeyCodeMap key_map;
int
keysym(int keycode) {
KeyCodeMap key_map;
key_map.win_keycode = keycode;
const KeyCodeMap *temp_map = std::lower_bound(
kKeyCodesMap, kKeyCodesMap + sizeof(kKeyCodesMap) / sizeof(kKeyCodesMap[0]), key_map);
key_map.win_keycode = keycode;
const KeyCodeMap *temp_map = std::lower_bound(
kKeyCodesMap, kKeyCodesMap + sizeof(kKeyCodesMap) / sizeof(kKeyCodesMap[0]), key_map);
if(temp_map >= kKeyCodesMap + sizeof(kKeyCodesMap) / sizeof(kKeyCodesMap[0]) ||
temp_map->win_keycode != keycode || temp_map->mac_keycode == -1) {
if (temp_map >= kKeyCodesMap + sizeof(kKeyCodesMap) / sizeof(kKeyCodesMap[0]) ||
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 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;
void
free_gamepad(input_t &input, int nr) {
BOOST_LOG(info) << "free_gamepad: Gamepad not yet implemented for MacOS."sv;
}
auto macos_input = ((macos_input_t *)input.get());
auto event = macos_input->kb_event;
void
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 ||
key == kVK_Command || key == kVK_RightCommand ||
key == kVK_Option || key == kVK_RightOption ||
key == kVK_Control || key == kVK_RightControl) {
// returns current mouse location:
inline CGPoint
get_mouse_loc(input_t &input) {
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) {
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;
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;
}
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);
mouse->mouse_down[mac_button] = !release;
// 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) {
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();
}
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;
}
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;
void
scroll(input_t &input, int high_res_distance) {
CGEventRef upEvent = CGEventCreateScrollWheelEvent(
NULL,
kCGScrollEventUnitLine,
2, high_res_distance > 0 ? 1 : -1, high_res_distance);
CGEventPost(kCGHIDEventTap, upEvent);
CFRelease(upEvent);
}
mouse->mouse_down[mac_button] = !release;
// 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) {
post_mouse(input, mac_button, event, get_mouse_loc(input), 2);
}
else {
post_mouse(input, mac_button, event, get_mouse_loc(input), 1);
void
hscroll(input_t &input, int high_res_distance) {
// Unimplemented
}
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) {
CGEventRef upEvent = CGEventCreateScrollWheelEvent(
NULL,
kCGScrollEventUnitLine,
2, high_res_distance > 0 ? 1 : -1, high_res_distance);
CGEventPost(kCGHIDEventTap, upEvent);
CFRelease(upEvent);
}
auto macos_input = (macos_input_t *) result.get();
void hscroll(input_t &input, int high_res_distance) {
// Unimplemented
}
// If we don't use the main display in the future, this has to be adapted
macos_input->display = CGMainDisplayID();
input_t input() {
input_t result { new macos_input_t() };
// Input coordinates are based on the virtual resolution not the physical, so we need the scaling factor
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->display = CGMainDisplayID();
macos_input->kb_event = CGEventCreate(macos_input->source);
macos_input->kb_flags = 0;
// Input coordinates are based on the virtual resolution not the physical, so we need the scaling factor
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(macos_input->display);
macos_input->displayScaling = ((CGFloat)CGDisplayPixelsWide(macos_input->display)) / ((CGFloat)CGDisplayModeGetPixelWidth(mode));
CFRelease(mode);
macos_input->mouse_event = CGEventCreate(macos_input->source);
macos_input->mouse_down[0] = false;
macos_input->mouse_down[1] = false;
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);
macos_input->kb_flags = 0;
return result;
}
macos_input->mouse_event = CGEventCreate(macos_input->source);
macos_input->mouse_down[0] = false;
macos_input->mouse_down[1] = false;
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;
void
freeInput(void *p) {
auto *input = (macos_input_t *) p;
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) {
auto *input = (macos_input_t *)p;
std::vector<std::string_view> &
supported_gamepads() {
static std::vector<std::string_view> gamepads { ""sv };
CFRelease(input->source);
CFRelease(input->kb_event);
CFRelease(input->mouse_event);
delete input;
}
std::vector<std::string_view> &supported_gamepads() {
static std::vector<std::string_view> gamepads { ""sv };
return gamepads;
}
} // namespace platf
return gamepads;
}
} // namespace platf

View File

@@ -5,83 +5,88 @@
#include "src/main.h"
namespace platf {
using namespace std::literals;
using namespace std::literals;
struct av_mic_t : public mic_t {
AVAudio *av_audio_capture;
struct av_mic_t: public mic_t {
AVAudio *av_audio_capture;
~av_mic_t() {
[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);
~av_mic_t() {
[av_audio_capture release];
}
const int16_t *sampleBuffer = (int16_t *)byteSampleBuffer;
std::vector<int16_t> vectorBuffer(sampleBuffer, sampleBuffer + sample_size);
capture_e
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));
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;
}
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];
while (length < sample_size * sizeof(std::int16_t)) {
[av_audio_capture.samplesArrivedSignal wait];
byteSampleBuffer = TPCircularBufferTail(&av_audio_capture->audioSampleBuffer, &length);
}
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]) {
BOOST_LOG(error) << "Failed to setup microphone."sv;
return nullptr;
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;
}
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>();
}
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
} // namespace platf

View File

@@ -6,11 +6,13 @@
#include <CoreGraphics/CoreGraphics.h>
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);
void *handle(const std::vector<const char *> &libs);
int
load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
void *
handle(const std::vector<const char *> &libs);
} // namespace dyn
} // namespace dyn
#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
// 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 they're not in the SDK then we can use our own function definitions.
// Need to use weak import so that this will link in macOS 10.14 and earlier
extern "C" bool CGPreflightScreenCaptureAccess(void) __attribute__((weak_import));
extern "C" bool CGRequestScreenCaptureAccess(void) __attribute__((weak_import));
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 110000 // __MAC_11_0
// If they're not in the SDK then we can use our own function definitions.
// Need to use weak import so that this will link in macOS 10.14 and earlier
extern "C" bool
CGPreflightScreenCaptureAccess(void) __attribute__((weak_import));
extern "C" bool
CGRequestScreenCaptureAccess(void) __attribute__((weak_import));
#endif
std::unique_ptr<deinit_t> init() {
// This will generate a warning about CGPreflightScreenCaptureAccess and
// CGRequestScreenCaptureAccess being unavailable before macOS 10.15, but
// we have a guard to prevent it from being called on those earlier systems.
// Unfortunately the supported way to silence this warning, using @available,
// produces linker errors for __isPlatformVersionAtLeast, so we have to use
// a different method.
// We also ignore "tautological-pointer-compare" because when compiling with
// Xcode 12.2 and later, these functions are not weakly linked and will never
// be null, and therefore generate this warning. Since we are weakly linking
// when compiling with earlier Xcode versions, the check for null is
// necessary and so we ignore the warning.
std::unique_ptr<deinit_t>
init() {
// This will generate a warning about CGPreflightScreenCaptureAccess and
// CGRequestScreenCaptureAccess being unavailable before macOS 10.15, but
// we have a guard to prevent it from being called on those earlier systems.
// Unfortunately the supported way to silence this warning, using @available,
// produces linker errors for __isPlatformVersionAtLeast, so we have to use
// a different method.
// We also ignore "tautological-pointer-compare" because when compiling with
// Xcode 12.2 and later, these functions are not weakly linked and will never
// be null, and therefore generate this warning. Since we are weakly linking
// 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 ignored "-Wunguarded-availability-new"
#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
if([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) { 10, 15, 0 })] &&
// Double check that these weakly-linked symbols have been loaded:
CGPreflightScreenCaptureAccess != nullptr && CGRequestScreenCaptureAccess != nullptr &&
!CGPreflightScreenCaptureAccess()) {
BOOST_LOG(error) << "No screen capture permission!"sv;
BOOST_LOG(error) << "Please activate it in 'System Preferences' -> 'Privacy' -> 'Screen Recording'"sv;
CGRequestScreenCaptureAccess();
return nullptr;
}
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) { 10, 15, 0 })] &&
// Double check that these weakly-linked symbols have been loaded:
CGPreflightScreenCaptureAccess != nullptr && CGRequestScreenCaptureAccess != nullptr &&
!CGPreflightScreenCaptureAccess()) {
BOOST_LOG(error) << "No screen capture permission!"sv;
BOOST_LOG(error) << "Please activate it in 'System Preferences' -> 'Privacy' -> 'Screen Recording'"sv;
CGRequestScreenCaptureAccess();
return nullptr;
}
#pragma clang diagnostic pop
return std::make_unique<deinit_t>();
}
fs::path appdata() {
const char *homedir;
if((homedir = getenv("HOME")) == nullptr) {
homedir = getpwuid(geteuid())->pw_dir;
return std::make_unique<deinit_t>();
}
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>;
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);
return fs::path { homedir } / ".config/sunshine"sv;
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
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) {
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);
}
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);
}
return std::string { data };
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
port = ((sockaddr_in *)ip_addr)->sin_port;
std::pair<std::uint16_t, std::string>
from_sockaddr_ex(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family;
std::uint16_t port;
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) {
auto ifaddrs = get_ifaddrs();
for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) {
BOOST_LOG(verbose) << "Looking for MAC of "sv << pos->ifa_name;
for(auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
if(pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) {
BOOST_LOG(verbose) << "Looking for MAC of "sv << pos->ifa_name;
struct ifaddrs *ifap, *ifaptr;
unsigned char *ptr;
std::string mac_address;
struct ifaddrs *ifap, *ifaptr;
unsigned char *ptr;
std::string mac_address;
if (getifaddrs(&ifap) == 0) {
for (ifaptr = ifap; ifaptr != NULL; ifaptr = (ifaptr)->ifa_next) {
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) {
for(ifaptr = ifap; ifaptr != NULL; ifaptr = (ifaptr)->ifa_next) {
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];
snprintf(buff, sizeof(buff), "%02x:%02x:%02x:%02x:%02x:%02x",
*ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4), *(ptr + 5));
mac_address = buff;
break;
}
}
snprintf(buff, sizeof(buff), "%02x:%02x:%02x:%02x:%02x:%02x",
*ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4), *(ptr + 5));
mac_address = buff;
break;
freeifaddrs(ifap);
if (ifaptr != NULL) {
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) {
BOOST_LOG(verbose) << "Found MAC of "sv << pos->ifa_name << ": "sv << mac_address;
return mac_address;
}
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) {
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;
return "00:00:00:00:00:00"s;
}
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);
}
void
adjust_thread_priority(thread_priority_e priority) {
// Unimplemented
}
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);
}
void
streaming_will_start() {
// Nothing to do
}
}
void adjust_thread_priority(thread_priority_e priority) {
// Unimplemented
}
void
streaming_will_stop() {
// Nothing to do
}
void streaming_will_start() {
// Nothing to do
}
bool
restart_supported() {
// Restart not supported yet
return false;
}
void streaming_will_stop() {
// Nothing to do
}
bool
restart() {
// Restart not supported yet
return false;
}
bool restart_supported() {
// Restart not supported yet
return false;
}
bool
send_batch(batched_send_info_t &send_info) {
// Fall back to unbatched send calls
return false;
}
bool restart() {
// Restart not supported yet
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;
}
bool send_batch(batched_send_info_t &send_info) {
// 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 platf
namespace dyn {
void *handle(const std::vector<const char *> &libs) {
void *handle;
void *
handle(const std::vector<const char *> &libs) {
void *handle;
for(auto lib : libs) {
handle = dlopen(lib, RTLD_LAZY | RTLD_LOCAL);
if(handle) {
return handle;
for (auto lib : libs) {
handle = dlopen(lib, RTLD_LAZY | RTLD_LOCAL);
if (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;
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;
});
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);
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;
}
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;
err = -1;
}
}
}
return err;
}
} // namespace dyn
return err;
}
} // namespace dyn

View File

@@ -9,74 +9,79 @@ extern "C" {
namespace platf {
void free_frame(AVFrame *frame) {
av_frame_free(&frame);
}
void
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) {
av_frame_make_writable(av_frame.get());
int
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;
CVPixelBufferGetExtendedPixels(av_img->pixel_buffer, &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);
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.
// 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
// 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.
//
// XXX: Improve the algorithm to take into account the outer pixels
// 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
// 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 outer content pixels, which should introduce only a minor performance hit.
//
// 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) {
for(int l = 0; l < uv_plane_height + (top_pad / 2); l++) {
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 + img.width - right_pad], 128, right_pad);
if (left_pad || right_pad) {
for (int l = 0; l < uv_plane_height + (top_pad / 2); l++) {
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 + 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) {
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]);
int
nv12_zero_device::set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
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) {
this->frame = frame;
int
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) {
}
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
} // namespace platf

View File

@@ -5,25 +5,29 @@
namespace platf {
class nv12_zero_device : public hwdevice_t {
// 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
void *display;
class nv12_zero_device: public hwdevice_t {
// 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
void *display;
public:
// 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
using resolution_fn_t = std::function<void(void *display, int width, int height)>;
resolution_fn_t resolution_fn;
using pixel_format_fn_t = std::function<void(void *display, int pixelFormat)>;
public:
// 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
using resolution_fn_t = std::function<void(void *display, int width, int height)>;
resolution_fn_t resolution_fn;
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 set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx);
void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range);
};
int
convert(img_t &img);
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 */

View File

@@ -12,418 +12,426 @@ using namespace std::literals;
namespace avahi {
/** Error codes used by avahi */
enum err_e {
OK = 0, /**< OK */
ERR_FAILURE = -1, /**< Generic error code */
ERR_BAD_STATE = -2, /**< Object was in a bad state */
ERR_INVALID_HOST_NAME = -3, /**< Invalid host name */
ERR_INVALID_DOMAIN_NAME = -4, /**< Invalid domain name */
ERR_NO_NETWORK = -5, /**< No suitable network protocol available */
ERR_INVALID_TTL = -6, /**< Invalid DNS TTL */
ERR_IS_PATTERN = -7, /**< RR key is pattern */
ERR_COLLISION = -8, /**< Name collision */
ERR_INVALID_RECORD = -9, /**< Invalid RR */
/** Error codes used by avahi */
enum err_e {
OK = 0, /**< OK */
ERR_FAILURE = -1, /**< Generic error code */
ERR_BAD_STATE = -2, /**< Object was in a bad state */
ERR_INVALID_HOST_NAME = -3, /**< Invalid host name */
ERR_INVALID_DOMAIN_NAME = -4, /**< Invalid domain name */
ERR_NO_NETWORK = -5, /**< No suitable network protocol available */
ERR_INVALID_TTL = -6, /**< Invalid DNS TTL */
ERR_IS_PATTERN = -7, /**< RR key is pattern */
ERR_COLLISION = -8, /**< Name collision */
ERR_INVALID_RECORD = -9, /**< Invalid RR */
ERR_INVALID_SERVICE_NAME = -10, /**< Invalid service name */
ERR_INVALID_SERVICE_TYPE = -11, /**< Invalid service type */
ERR_INVALID_PORT = -12, /**< Invalid port number */
ERR_INVALID_KEY = -13, /**< Invalid key */
ERR_INVALID_ADDRESS = -14, /**< Invalid address */
ERR_TIMEOUT = -15, /**< Timeout reached */
ERR_TOO_MANY_CLIENTS = -16, /**< Too many clients */
ERR_TOO_MANY_OBJECTS = -17, /**< Too many objects */
ERR_TOO_MANY_ENTRIES = -18, /**< Too many entries */
ERR_OS = -19, /**< OS error */
ERR_INVALID_SERVICE_NAME = -10, /**< Invalid service name */
ERR_INVALID_SERVICE_TYPE = -11, /**< Invalid service type */
ERR_INVALID_PORT = -12, /**< Invalid port number */
ERR_INVALID_KEY = -13, /**< Invalid key */
ERR_INVALID_ADDRESS = -14, /**< Invalid address */
ERR_TIMEOUT = -15, /**< Timeout reached */
ERR_TOO_MANY_CLIENTS = -16, /**< Too many clients */
ERR_TOO_MANY_OBJECTS = -17, /**< Too many objects */
ERR_TOO_MANY_ENTRIES = -18, /**< Too many entries */
ERR_OS = -19, /**< OS error */
ERR_ACCESS_DENIED = -20, /**< Access denied */
ERR_INVALID_OPERATION = -21, /**< Invalid operation */
ERR_DBUS_ERROR = -22, /**< An unexpected D-Bus error occurred */
ERR_DISCONNECTED = -23, /**< Daemon connection failed */
ERR_NO_MEMORY = -24, /**< Memory exhausted */
ERR_INVALID_OBJECT = -25, /**< The object passed to this function was invalid */
ERR_NO_DAEMON = -26, /**< Daemon not running */
ERR_INVALID_INTERFACE = -27, /**< Invalid interface */
ERR_INVALID_PROTOCOL = -28, /**< Invalid protocol */
ERR_INVALID_FLAGS = -29, /**< Invalid flags */
ERR_ACCESS_DENIED = -20, /**< Access denied */
ERR_INVALID_OPERATION = -21, /**< Invalid operation */
ERR_DBUS_ERROR = -22, /**< An unexpected D-Bus error occurred */
ERR_DISCONNECTED = -23, /**< Daemon connection failed */
ERR_NO_MEMORY = -24, /**< Memory exhausted */
ERR_INVALID_OBJECT = -25, /**< The object passed to this function was invalid */
ERR_NO_DAEMON = -26, /**< Daemon not running */
ERR_INVALID_INTERFACE = -27, /**< Invalid interface */
ERR_INVALID_PROTOCOL = -28, /**< Invalid protocol */
ERR_INVALID_FLAGS = -29, /**< Invalid flags */
ERR_NOT_FOUND = -30, /**< Not found */
ERR_INVALID_CONFIG = -31, /**< Configuration error */
ERR_VERSION_MISMATCH = -32, /**< Verson mismatch */
ERR_INVALID_SERVICE_SUBTYPE = -33, /**< Invalid service subtype */
ERR_INVALID_PACKET = -34, /**< Invalid packet */
ERR_INVALID_DNS_ERROR = -35, /**< Invlaid DNS return code */
ERR_DNS_FORMERR = -36, /**< DNS Error: Form error */
ERR_DNS_SERVFAIL = -37, /**< DNS Error: Server Failure */
ERR_DNS_NXDOMAIN = -38, /**< DNS Error: No such domain */
ERR_DNS_NOTIMP = -39, /**< DNS Error: Not implemented */
ERR_NOT_FOUND = -30, /**< Not found */
ERR_INVALID_CONFIG = -31, /**< Configuration error */
ERR_VERSION_MISMATCH = -32, /**< Verson mismatch */
ERR_INVALID_SERVICE_SUBTYPE = -33, /**< Invalid service subtype */
ERR_INVALID_PACKET = -34, /**< Invalid packet */
ERR_INVALID_DNS_ERROR = -35, /**< Invlaid DNS return code */
ERR_DNS_FORMERR = -36, /**< DNS Error: Form error */
ERR_DNS_SERVFAIL = -37, /**< DNS Error: Server Failure */
ERR_DNS_NXDOMAIN = -38, /**< DNS Error: No such domain */
ERR_DNS_NOTIMP = -39, /**< DNS Error: Not implemented */
ERR_DNS_REFUSED = -40, /**< DNS Error: Operation refused */
ERR_DNS_YXDOMAIN = -41,
ERR_DNS_YXRRSET = -42,
ERR_DNS_NXRRSET = -43,
ERR_DNS_NOTAUTH = -44, /**< DNS Error: Not authorized */
ERR_DNS_NOTZONE = -45,
ERR_INVALID_RDATA = -46, /**< Invalid RDATA */
ERR_INVALID_DNS_CLASS = -47, /**< Invalid DNS class */
ERR_INVALID_DNS_TYPE = -48, /**< Invalid DNS type */
ERR_NOT_SUPPORTED = -49, /**< Not supported */
ERR_DNS_REFUSED = -40, /**< DNS Error: Operation refused */
ERR_DNS_YXDOMAIN = -41,
ERR_DNS_YXRRSET = -42,
ERR_DNS_NXRRSET = -43,
ERR_DNS_NOTAUTH = -44, /**< DNS Error: Not authorized */
ERR_DNS_NOTZONE = -45,
ERR_INVALID_RDATA = -46, /**< Invalid RDATA */
ERR_INVALID_DNS_CLASS = -47, /**< Invalid DNS class */
ERR_INVALID_DNS_TYPE = -48, /**< Invalid DNS type */
ERR_NOT_SUPPORTED = -49, /**< Not supported */
ERR_NOT_PERMITTED = -50, /**< Operation not permitted */
ERR_INVALID_ARGUMENT = -51, /**< Invalid argument */
ERR_IS_EMPTY = -52, /**< Is empty */
ERR_NO_CHANGE = -53, /**< The requested operation is invalid because it is redundant */
ERR_NOT_PERMITTED = -50, /**< Operation not permitted */
ERR_INVALID_ARGUMENT = -51, /**< Invalid argument */
ERR_IS_EMPTY = -52, /**< Is empty */
ERR_NO_CHANGE = -53, /**< The requested operation is invalid because it is redundant */
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" },
ERR_MAX = -54
};
if(dyn::load(handle, funcs)) {
return -1;
}
funcs_loaded = true;
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" },
constexpr auto IF_UNSPEC = -1;
enum proto {
PROTO_INET = 0, /**< IPv4 */
PROTO_INET6 = 1, /**< IPv6 */
PROTO_UNSPEC = -1 /**< Unspecified/all protocol(s) */
};
if(dyn::load(handle, funcs)) {
return -1;
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)) {
return -1;
}
funcs_loaded = true;
return 0;
}
funcs_loaded = true;
return 0;
}
} // namespace avahi
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)) {
return -1;
}
funcs_loaded = true;
return 0;
}
} // namespace avahi
namespace platf::publish {
template<class T>
void 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:;
template <class T>
void
free(T *p) {
avahi::free(p);
}
}
void create_services(avahi::Client *c) {
int ret;
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>;
auto fg = util::fail_guard([]() {
avahi::simple_poll_quit(poll.get());
});
avahi::EntryGroup *group = nullptr;
if(!group) {
if(!(group = avahi::entry_group_new(c, entry_group_callback, nullptr))) {
BOOST_LOG(error) << "avahi::entry_group_new() failed: "sv << avahi::strerror(avahi::client_errno(c));
return;
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:;
}
}
if(avahi::entry_group_is_empty(group)) {
BOOST_LOG(info) << "Adding avahi service "sv << name.get();
void
create_services(avahi::Client *c) {
int ret;
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);
auto fg = util::fail_guard([]() {
avahi::simple_poll_quit(poll.get());
});
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();
if (!group) {
if (!(group = avahi::entry_group_new(c, entry_group_callback, nullptr))) {
BOOST_LOG(error) << "avahi::entry_group_new() failed: "sv << avahi::strerror(avahi::client_errno(c));
return;
}
}
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;
}
BOOST_LOG(error) << "Failed to add "sv << SERVICE_TYPE << " service: "sv << avahi::strerror(ret);
return;
ret = avahi::entry_group_commit(group);
if (ret < 0) {
BOOST_LOG(error) << "Failed to commit entry group: "sv << avahi::strerror(ret);
return;
}
}
ret = avahi::entry_group_commit(group);
if(ret < 0) {
BOOST_LOG(error) << "Failed to commit entry group: "sv << avahi::strerror(ret);
return;
fg.disable();
}
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 *) {
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:;
}
}
deinit_t(std::thread poll_thread):
poll_thread { std::move(poll_thread) } {}
class deinit_t : public ::platf::deinit_t {
public:
std::thread poll_thread;
~deinit_t() override {
if (avahi::simple_poll_quit && poll) {
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 {
if(avahi::simple_poll_quit && poll) {
avahi::simple_poll_quit(poll.get());
[[nodiscard]] std::unique_ptr<::platf::deinit_t>
start() {
if (avahi::init_client()) {
return nullptr;
}
if(poll_thread.joinable()) {
poll_thread.join();
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() });
}
};
[[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
}; // namespace platf::publish

View File

@@ -6,16 +6,15 @@
// http://eretik.omegahg.com/
// ----------------------------------------------------------------------------
#pragma once
#ifdef __MINGW32__
#undef DEFINE_GUID
#ifdef __cplusplus
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#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 } }
#endif
#undef DEFINE_GUID
#ifdef __cplusplus
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#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 } }
#endif
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);
@@ -37,13 +36,15 @@ class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
//
// @compatible: Windows 7 and Later
// ----------------------------------------------------------------------------
interface IPolicyConfig : public IUnknown {
interface IPolicyConfig: public IUnknown {
public:
virtual HRESULT GetMixFormat(
virtual HRESULT
GetMixFormat(
PCWSTR,
WAVEFORMATEX **);
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
virtual HRESULT STDMETHODCALLTYPE
GetDeviceFormat(
PCWSTR,
INT,
WAVEFORMATEX **);
@@ -51,7 +52,8 @@ public:
virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
PCWSTR);
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
virtual HRESULT STDMETHODCALLTYPE
SetDeviceFormat(
PCWSTR,
WAVEFORMATEX *,
WAVEFORMATEX *);
@@ -66,25 +68,30 @@ public:
PCWSTR,
PINT64);
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
virtual HRESULT STDMETHODCALLTYPE
GetShareMode(
PCWSTR,
struct DeviceShareMode *);
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
virtual HRESULT STDMETHODCALLTYPE
SetShareMode(
PCWSTR,
struct DeviceShareMode *);
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
virtual HRESULT STDMETHODCALLTYPE
GetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
virtual HRESULT STDMETHODCALLTYPE
SetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
virtual HRESULT STDMETHODCALLTYPE
SetDefaultEndpoint(
PCWSTR wszDeviceId,
ERole eRole);
@@ -108,18 +115,21 @@ class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaCl
//
// @compatible: Windows Vista and Later
// ----------------------------------------------------------------------------
interface IPolicyConfigVista : public IUnknown {
interface IPolicyConfigVista: public IUnknown {
public:
virtual HRESULT GetMixFormat(
virtual HRESULT
GetMixFormat(
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,
INT,
WAVEFORMATEX **);
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
virtual HRESULT STDMETHODCALLTYPE
SetDeviceFormat(
PCWSTR,
WAVEFORMATEX *,
WAVEFORMATEX *);
@@ -128,37 +138,42 @@ public:
PCWSTR,
INT,
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(
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,
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,
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,
const PROPERTYKEY &,
PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
virtual HRESULT STDMETHODCALLTYPE
SetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
virtual HRESULT STDMETHODCALLTYPE
SetDefaultEndpoint(
PCWSTR wszDeviceId,
ERole eRole);
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
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"
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.
// You should have a debugger like WinDbg attached to receive debug messages.
auto constexpr D3D11_CREATE_DEVICE_FLAGS = D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
// Add D3D11_CREATE_DEVICE_DEBUG here to enable the D3D11 debug runtime.
// You should have a debugger like WinDbg attached to receive debug messages.
auto constexpr D3D11_CREATE_DEVICE_FLAGS = D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
template<class T>
void 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;
template <class T>
void
Release(T *dxgi) {
dxgi->Release();
}
void set_texture(LONG width, LONG height, texture2d_t &&texture) {
cursor_view.Width = width;
cursor_view.Height = height;
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>>;
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;
shader_res_t input_res;
class hwdevice_t;
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 {
public:
dup_t dup;
bool has_frame {};
bool use_dwmflush {};
this->visible = visible;
}
capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p);
capture_e reset(dup_t::pointer dup_p = dup_t::pointer());
capture_e release_frame();
void
set_texture(LONG width, LONG height, texture2d_t &&texture) {
cursor_view.Width = width;
cursor_view.Height = height;
~duplication_t();
};
this->texture = std::move(texture);
}
class display_base_t : public display_t {
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;
texture2d_t texture;
shader_res_t input_res;
std::chrono::nanoseconds delay;
D3D11_VIEWPORT cursor_view;
factory1_t factory;
adapter_t adapter;
output_t output;
device_t device;
device_ctx_t device_ctx;
duplication_t dup;
bool visible;
};
DXGI_FORMAT capture_format;
D3D_FEATURE_LEVEL feature_level;
class duplication_t {
public:
dup_t dup;
bool has_frame {};
bool use_dwmflush {};
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS {
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH,
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
} D3DKMT_SCHEDULINGPRIORITYCLASS;
capture_e
next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p);
capture_e
reset(dup_t::pointer dup_p = dup_t::pointer());
capture_e
release_frame();
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
~duplication_t();
};
virtual bool is_hdr() override;
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) override;
class display_base_t: public display_t {
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:
int get_pixel_pitch() {
return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4;
}
std::chrono::nanoseconds delay;
const char *dxgi_format_to_string(DXGI_FORMAT format);
const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
factory1_t factory;
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;
virtual int complete_img(img_t *img, bool dummy) = 0;
virtual std::vector<DXGI_FORMAT> get_supported_sdr_capture_formats() = 0;
virtual std::vector<DXGI_FORMAT> get_supported_hdr_capture_formats() = 0;
};
DXGI_FORMAT capture_format;
D3D_FEATURE_LEVEL feature_level;
class display_ram_t : public display_base_t {
public:
virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override;
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS {
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
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;
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;
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
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;
D3D11_MAPPED_SUBRESOURCE img_info;
texture2d_t texture;
};
protected:
int
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> {
public:
virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override;
const char *
dxgi_format_to_string(DXGI_FORMAT format);
const char *
colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
std::shared_ptr<img_t> alloc_img() override;
int dummy_img(img_t *img_base) override;
int complete_img(img_t *img_base, bool dummy) override;
std::vector<DXGI_FORMAT> get_supported_sdr_capture_formats() override;
std::vector<DXGI_FORMAT> get_supported_hdr_capture_formats() override;
virtual capture_e
snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) = 0;
virtual int
complete_img(img_t *img, bool dummy) = 0;
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;
blend_t blend_invert;
blend_t blend_disable;
cursor_t cursor;
D3D11_MAPPED_SUBRESOURCE img_info;
texture2d_t texture;
};
ps_t scene_ps;
vs_t scene_vs;
class display_vram_t: public display_base_t, public std::enable_shared_from_this<display_vram_t> {
public:
virtual capture_e
snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override;
gpu_cursor_t cursor_alpha;
gpu_cursor_t cursor_xor;
std::shared_ptr<img_t>
alloc_img() override;
int
dummy_img(img_t *img_base) override;
int
complete_img(img_t *img_base, bool dummy) override;
std::vector<DXGI_FORMAT>
get_supported_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;
};
} // namespace platf::dxgi
std::shared_ptr<platf::hwdevice_t>
make_hwdevice(pix_fmt_e pix_fmt) override;
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

File diff suppressed because it is too large Load Diff

View File

@@ -2,366 +2,378 @@
#include "src/main.h"
namespace platf {
using namespace std::literals;
using namespace std::literals;
}
namespace platf::dxgi {
struct img_t : public ::platf::img_t {
~img_t() override {
delete[] data;
data = nullptr;
}
};
struct img_t: public ::platf::img_t {
~img_t() override {
delete[] data;
data = nullptr;
}
};
void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
int height = cursor.shape_info.Height / 2;
int width = cursor.shape_info.Width;
int pitch = cursor.shape_info.Pitch;
void
blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
int height = cursor.shape_info.Height / 2;
int width = cursor.shape_info.Width;
int pitch = cursor.shape_info.Pitch;
// img cursor.{x,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} < 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);
// 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 cursor_width = width - cursor_skip_x - cursor_truncate_x;
auto cursor_height = height - cursor_skip_y - cursor_truncate_y;
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;
}
if (cursor_height > height || cursor_width > width) {
return;
}
auto img_skip_y = std::max(0, cursor.y);
auto img_skip_x = std::max(0, cursor.x);
auto img_skip_y = std::max(0, cursor.y);
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_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x));
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 pixels_per_byte = width / pitch;
auto bytes_per_row = delta_width / pixels_per_byte;
auto pixels_per_byte = width / pitch;
auto bytes_per_row = delta_width / pixels_per_byte;
auto img_data = (int *)img.data;
for(int i = 0; i < delta_height; ++i) {
auto and_mask = &cursor_img_data[i * pitch];
auto xor_mask = &cursor_img_data[(i + height) * pitch];
auto img_data = (int *) img.data;
for (int i = 0; i < delta_height; ++i) {
auto and_mask = &cursor_img_data[i * 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;
for(int x = 0; x < bytes_per_row; ++x) {
for(auto bit = 0u; bit < 8; ++bit) {
if(skip_x > 0) {
--skip_x;
auto skip_x = cursor_skip_x;
for (int x = 0; x < bytes_per_row; ++x) {
for (auto bit = 0u; bit < 8; ++bit) {
if (skip_x > 0) {
--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;
int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0;
++and_mask;
++xor_mask;
}
}
}
*img_pixel_p &= and_;
*img_pixel_p ^= xor_;
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;
}
++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) {
cursor.x = frame_info.PointerPosition.Position.x;
cursor.y = frame_info.PointerPosition.Position.y;
cursor.visible = frame_info.PointerPosition.Visible;
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 << ']';
}
}
if(frame_update_flag) {
{
texture2d_t src {};
status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src);
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) {
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;
}
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;
}
// Now that we know the capture format, we can finish creating the image
if (complete_img(img, false)) {
device_ctx->Unmap(texture.get(), 0);
img_info.pData = nullptr;
return capture_e::error;
}
// 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;
}
std::copy_n((std::uint8_t *) img_info.pData, height * img_info.RowPitch, (std::uint8_t *) img->data);
// 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() << ']';
return capture_e::error;
}
// Now that we know the capture format, we can finish creating the image
if(complete_img(img, false)) {
// Unmap the staging texture to allow GPU access again
device_ctx->Unmap(texture.get(), 0);
img_info.pData = nullptr;
return capture_e::error;
}
std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data);
if (cursor_visible && cursor.visible) {
blend_cursor(cursor, *img);
}
// Unmap the staging texture to allow GPU access again
device_ctx->Unmap(texture.get(), 0);
img_info.pData = nullptr;
return capture_e::ok;
}
if(cursor_visible && cursor.visible) {
blend_cursor(cursor, *img);
std::shared_ptr<platf::img_t>
display_ram_t::alloc_img() {
auto img = std::make_shared<img_t>();
// Initialize fields that are format-independent
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() {
auto img = std::make_shared<img_t>();
img->pixel_pitch = get_pixel_pitch();
// Initialize fields that are format-independent
img->width = width;
img->height = height;
if (dummy && !img->row_pitch) {
// Assume our dummy image will have no padding
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 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;
if (!img->data) {
img->data = new std::uint8_t[img->row_pitch * height];
}
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) {
// Assume our dummy image will have no padding
img->row_pitch = img->pixel_pitch * img->width;
std::fill_n((std::uint8_t *) img->data, height * img->row_pitch, 0);
return 0;
}
// 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;
std::vector<DXGI_FORMAT>
display_ram_t::get_supported_sdr_capture_formats() {
return { DXGI_FORMAT_B8G8R8A8_UNORM };
}
if(!img->data) {
img->data = new std::uint8_t[img->row_pitch * height];
std::vector<DXGI_FORMAT>
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) {
if(complete_img(img, true)) {
return -1;
return 0;
}
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
} // 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>
namespace platf {
void print_status(const std::string_view &prefix, HRESULT status);
HDESK syncThreadDesktop();
} // namespace platf
void
print_status(const std::string_view &prefix, HRESULT status);
HDESK
syncThreadDesktop();
} // namespace platf
#endif

View File

@@ -35,7 +35,7 @@ constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1;
#define SERVICE_DOMAIN "local"
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__
typedef struct _DNS_SERVICE_INSTANCE {
@@ -59,7 +59,8 @@ typedef struct _DNS_SERVICE_INSTANCE {
} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE;
#endif
typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE(
typedef VOID WINAPI
DNS_SERVICE_REGISTER_COMPLETE(
_In_ DWORD Status,
_In_ PVOID pQueryContext,
_In_ PDNS_SERVICE_INSTANCE pInstance);
@@ -88,122 +89,127 @@ _FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _I
} /* extern "C" */
namespace platf::publish {
VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) {
auto alarm = (safe::alarm_t<PDNS_SERVICE_INSTANCE>::element_type *)pQueryContext;
VOID WINAPI
register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) {
auto alarm = (safe::alarm_t<PDNS_SERVICE_INSTANCE>::element_type *) pQueryContext;
if(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;
if (status) {
print_status("register_cb()"sv, status);
}
BOOST_LOG(info) << "Registered Sunshine mDNS service"sv;
alarm->ring(pInstance);
}
~mdns_registration_t() override {
if(existing_instance) {
if(service(false, existing_instance)) {
BOOST_LOG(error) << "Unable to unregister Sunshine mDNS service"sv;
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) << "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:
PDNS_SERVICE_INSTANCE existing_instance;
};
std::unique_ptr<::platf::deinit_t>
start() {
HMODULE handle = LoadLibrary("dnsapi.dll");
int load_funcs(HMODULE handle) {
auto fg = util::fail_guard([handle]() {
FreeLibrary(handle);
});
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;
}
_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;
return std::make_unique<mdns_registration_t>();
}
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
} // namespace platf::publish