clang: adjust formatting rules (#1015)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user