style: adjust clang-format rules (#2186)

Co-authored-by: Vithorio Polten <reach@vithor.io>
This commit is contained in:
ReenigneArcher
2025-01-19 22:34:47 -05:00
committed by GitHub
parent f57aee9025
commit c2420427b1
158 changed files with 8754 additions and 9994 deletions

View File

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

View File

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

View File

@@ -2,13 +2,15 @@
* @file src/platform/linux/cuda.cpp
* @brief Definitions for CUDA encoding.
*/
// standard includes
#include <bitset>
#include <fcntl.h>
#include <filesystem>
#include <thread>
#include <NvFBC.h>
// lib includes
#include <ffnvcodec/dynlink_loader.h>
#include <NvFBC.h>
extern "C" {
#include <libavcodec/avcodec.h>
@@ -16,6 +18,7 @@ extern "C" {
#include <libavutil/imgutils.h>
}
// local includes
#include "cuda.h"
#include "graphics.h"
#include "src/logging.h"
@@ -27,7 +30,8 @@ extern "C" {
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
#define CU_CHECK(x, y) \
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) return -1
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return -1
#define CU_CHECK_IGNORE(x, y) \
check((x), SUNSHINE_STRINGVIEW(y ": "))
@@ -35,17 +39,16 @@ extern "C" {
namespace fs = std::filesystem;
using namespace std::literals;
namespace cuda {
constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1;
constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39;
void
pass_error(const std::string_view &sv, const char *name, const char *description) {
void pass_error(const std::string_view &sv, const char *name, const char *description) {
BOOST_LOG(error) << sv << name << ':' << description;
}
void
cff(CudaFunctions *cf) {
void cff(CudaFunctions *cf) {
cuda_free_functions(&cf);
}
@@ -53,8 +56,7 @@ namespace cuda {
static cdf_t cdf;
inline static int
check(CUresult result, const std::string_view &sv) {
inline static int check(CUresult result, const std::string_view &sv) {
if (result != CUDA_SUCCESS) {
const char *name;
const char *description;
@@ -69,13 +71,11 @@ namespace cuda {
return 0;
}
void
freeStream(CUstream stream) {
void freeStream(CUstream stream) {
CU_CHECK_IGNORE(cdf->cuStreamDestroy(stream), "Couldn't destroy cuda stream");
}
void
unregisterResource(CUgraphicsResource resource) {
void unregisterResource(CUgraphicsResource resource) {
CU_CHECK_IGNORE(cdf->cuGraphicsUnregisterResource(resource), "Couldn't unregister resource");
}
@@ -86,8 +86,7 @@ namespace cuda {
tex_t tex;
};
int
init() {
int init() {
auto status = cuda_load_functions(&cdf, nullptr);
if (status) {
BOOST_LOG(error) << "Couldn't load cuda: "sv << status;
@@ -102,8 +101,7 @@ namespace cuda {
class cuda_t: public platf::avcodec_encode_device_t {
public:
int
init(int in_width, int in_height) {
int init(int in_width, int in_height) {
if (!cdf) {
BOOST_LOG(warning) << "cuda not initialized"sv;
return -1;
@@ -117,8 +115,7 @@ namespace cuda {
return 0;
}
int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
this->hwframe.reset(frame);
this->frame = frame;
@@ -156,8 +153,7 @@ namespace cuda {
return 0;
}
void
apply_colorspace() override {
void apply_colorspace() override {
sws.apply_colorspace(colorspace);
auto tex = tex_t::make(height, width * 4);
@@ -182,11 +178,10 @@ namespace cuda {
return;
}
sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex->texture.linear, stream.get(), { frame->width, frame->height, 0, 0 });
sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex->texture.linear, stream.get(), {frame->width, frame->height, 0, 0});
}
cudaTextureObject_t
tex_obj(const tex_t &tex) const {
cudaTextureObject_t tex_obj(const tex_t &tex) const {
return linear_interpolation ? tex.texture.linear : tex.texture.point;
}
@@ -203,13 +198,11 @@ namespace cuda {
class cuda_ram_t: public cuda_t {
public:
int
convert(platf::img_t &img) override {
int convert(platf::img_t &img) override {
return sws.load_ram(img, tex.array) || sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(tex), stream.get());
}
int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
if (cuda_t::set_frame(frame, hw_frames_ctx)) {
return -1;
}
@@ -229,8 +222,7 @@ namespace cuda {
class cuda_vram_t: public cuda_t {
public:
int
convert(platf::img_t &img) override {
int convert(platf::img_t &img) override {
return sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(((img_t *) &img)->tex), stream.get());
}
};
@@ -240,8 +232,7 @@ namespace cuda {
* @param index CUDA device index to open.
* @return File descriptor or -1 on failure.
*/
file_t
open_drm_fd_for_cuda_device(int index) {
file_t open_drm_fd_for_cuda_device(int index) {
CUdevice device;
CU_CHECK(cdf->cuDeviceGet(&device, index), "Couldn't get CUDA device");
@@ -252,29 +243,29 @@ namespace cuda {
BOOST_LOG(debug) << "Found CUDA device with PCI bus ID: "sv << pci_bus_id.data();
// Linux uses lowercase hexadecimal while CUDA uses uppercase
std::transform(pci_bus_id.begin(), pci_bus_id.end(), pci_bus_id.begin(),
[](char c) { return std::tolower(c); });
std::transform(pci_bus_id.begin(), pci_bus_id.end(), pci_bus_id.begin(), [](char c) {
return std::tolower(c);
});
// Look for the name of the primary node in sysfs
try {
char sysfs_path[PATH_MAX];
std::snprintf(sysfs_path, sizeof(sysfs_path), "/sys/bus/pci/devices/%s/drm", pci_bus_id.data());
fs::path sysfs_dir { sysfs_path };
for (auto &entry : fs::directory_iterator { sysfs_dir }) {
fs::path sysfs_dir {sysfs_path};
for (auto &entry : fs::directory_iterator {sysfs_dir}) {
auto file = entry.path().filename();
auto filestring = file.generic_string();
if (std::string_view { filestring }.substr(0, 4) != "card"sv) {
if (std::string_view {filestring}.substr(0, 4) != "card"sv) {
continue;
}
BOOST_LOG(debug) << "Found DRM primary node: "sv << filestring;
fs::path dri_path { "/dev/dri"sv };
fs::path dri_path {"/dev/dri"sv};
auto device_path = dri_path / file;
return open(device_path.c_str(), O_RDWR);
}
}
catch (const std::filesystem::filesystem_error &err) {
} catch (const std::filesystem::filesystem_error &err) {
BOOST_LOG(error) << "Failed to read sysfs: "sv << err.what();
}
@@ -292,8 +283,7 @@ namespace cuda {
* @param offset_y Offset of content in captured frame.
* @return 0 on success or -1 on failure.
*/
int
init(int in_width, int in_height, int offset_x, int offset_y) {
int init(int in_width, int in_height, int offset_x, int offset_y) {
// This must be non-zero to tell the video core that it's a hardware encoding device.
data = (void *) 0x1;
@@ -340,8 +330,7 @@ namespace cuda {
* @param hw_frames_ctx_buf FFmpeg hardware frame context.
* @return 0 on success or -1 on failure.
*/
int
set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
this->hwframe.reset(frame);
this->frame = frame;
@@ -377,10 +366,8 @@ namespace cuda {
cuda_ctx->stream = stream.get();
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&y_res, nv12->tex[0], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY),
"Couldn't register Y plane texture");
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&uv_res, nv12->tex[1], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY),
"Couldn't register UV plane texture");
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&y_res, nv12->tex[0], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), "Couldn't register Y plane texture");
CU_CHECK(cdf->cuGraphicsGLRegisterImage(&uv_res, nv12->tex[1], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), "Couldn't register UV plane texture");
return 0;
}
@@ -390,15 +377,13 @@ namespace cuda {
* @param img Captured screen image.
* @return 0 on success or -1 on failure.
*/
int
convert(platf::img_t &img) override {
int convert(platf::img_t &img) override {
auto &descriptor = (egl::img_descriptor_t &) img;
if (descriptor.sequence == 0) {
// For dummy images, use a blank RGB texture instead of importing a DMA-BUF
rgb = egl::create_blank(img);
}
else if (descriptor.sequence > sequence) {
} else if (descriptor.sequence > sequence) {
sequence = descriptor.sequence;
rgb = egl::rgb_t {};
@@ -419,7 +404,7 @@ namespace cuda {
auto fmt_desc = av_pix_fmt_desc_get(sw_format);
// Map the GL textures to read for CUDA
CUgraphicsResource resources[2] = { y_res.get(), uv_res.get() };
CUgraphicsResource resources[2] = {y_res.get(), uv_res.get()};
CU_CHECK(cdf->cuGraphicsMapResources(2, resources, stream.get()), "Couldn't map GL textures in CUDA");
// Copy from the GL textures to the target CUDA frame
@@ -445,8 +430,7 @@ namespace cuda {
/**
* @brief Configures shader parameters for the specified colorspace.
*/
void
apply_colorspace() override {
void apply_colorspace() override {
sws.apply_colorspace(colorspace);
}
@@ -474,8 +458,7 @@ namespace cuda {
int offset_x, offset_y;
};
std::unique_ptr<platf::avcodec_encode_device_t>
make_avcodec_encode_device(int width, int height, bool vram) {
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram) {
if (init()) {
return nullptr;
}
@@ -484,8 +467,7 @@ namespace cuda {
if (vram) {
cuda = std::make_unique<cuda_vram_t>();
}
else {
} else {
cuda = std::make_unique<cuda_ram_t>();
}
@@ -504,8 +486,7 @@ namespace cuda {
* @param offset_y Offset of content in captured frame.
* @return FFmpeg encoding device context.
*/
std::unique_ptr<platf::avcodec_encode_device_t>
make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y) {
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y) {
if (init()) {
return nullptr;
}
@@ -521,29 +502,30 @@ namespace cuda {
namespace nvfbc {
static PNVFBCCREATEINSTANCE createInstance {};
static NVFBC_API_FUNCTION_LIST func { NVFBC_VERSION };
static NVFBC_API_FUNCTION_LIST func {NVFBC_VERSION};
static constexpr inline NVFBC_BOOL
nv_bool(bool b) {
static constexpr inline NVFBC_BOOL nv_bool(bool b) {
return b ? NVFBC_TRUE : NVFBC_FALSE;
}
static void *handle { nullptr };
int
init() {
static void *handle {nullptr};
int init() {
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (funcs_loaded) {
return 0;
}
if (!handle) {
handle = dyn::handle({ "libnvidia-fbc.so.1", "libnvidia-fbc.so" });
handle = dyn::handle({"libnvidia-fbc.so.1", "libnvidia-fbc.so"});
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &createInstance, "NvFBCCreateInstance" },
{(dyn::apiproc *) &createInstance, "NvFBCCreateInstance"},
};
if (dyn::load(handle, funcs)) {
@@ -569,7 +551,7 @@ namespace cuda {
class ctx_t {
public:
ctx_t(NVFBC_SESSION_HANDLE handle) {
NVFBC_BIND_CONTEXT_PARAMS params { NVFBC_BIND_CONTEXT_PARAMS_VER };
NVFBC_BIND_CONTEXT_PARAMS params {NVFBC_BIND_CONTEXT_PARAMS_VER};
if (func.nvFBCBindContext(handle, &params)) {
BOOST_LOG(error) << "Couldn't bind NvFBC context to current thread: " << func.nvFBCGetLastErrorStr(handle);
@@ -579,7 +561,7 @@ namespace cuda {
}
~ctx_t() {
NVFBC_RELEASE_CONTEXT_PARAMS params { NVFBC_RELEASE_CONTEXT_PARAMS_VER };
NVFBC_RELEASE_CONTEXT_PARAMS params {NVFBC_RELEASE_CONTEXT_PARAMS_VER};
if (func.nvFBCReleaseContext(handle, &params)) {
BOOST_LOG(error) << "Couldn't release NvFBC context from current thread: " << func.nvFBCGetLastErrorStr(handle);
}
@@ -597,26 +579,26 @@ namespace cuda {
public:
handle_t() = default;
handle_t(handle_t &&other):
handle_flags { other.handle_flags }, handle { other.handle } {
handle_flags {other.handle_flags},
handle {other.handle} {
other.handle_flags.reset();
}
handle_t &
operator=(handle_t &&other) {
handle_t &operator=(handle_t &&other) {
std::swap(handle_flags, other.handle_flags);
std::swap(handle, other.handle);
return *this;
}
static std::optional<handle_t>
make() {
NVFBC_CREATE_HANDLE_PARAMS params { NVFBC_CREATE_HANDLE_PARAMS_VER };
static std::optional<handle_t> make() {
NVFBC_CREATE_HANDLE_PARAMS params {NVFBC_CREATE_HANDLE_PARAMS_VER};
// Set privateData to allow NvFBC on consumer NVIDIA GPUs.
// Based on https://github.com/keylase/nvidia-patch/blob/3193b4b1cea91527bf09ea9b8db5aade6a3f3c0a/win/nvfbcwrp/nvfbcwrp_main.cpp#L23-L25 .
const unsigned int MAGIC_PRIVATE_DATA[4] = { 0xAEF57AC5, 0x401D1A39, 0x1B856BBE, 0x9ED0CEBA };
const unsigned int MAGIC_PRIVATE_DATA[4] = {0xAEF57AC5, 0x401D1A39, 0x1B856BBE, 0x9ED0CEBA};
params.privateData = MAGIC_PRIVATE_DATA;
params.privateDataSize = sizeof(MAGIC_PRIVATE_DATA);
@@ -633,14 +615,12 @@ namespace cuda {
return handle;
}
const char *
last_error() {
const char *last_error() {
return func.nvFBCGetLastErrorStr(handle);
}
std::optional<NVFBC_GET_STATUS_PARAMS>
status() {
NVFBC_GET_STATUS_PARAMS params { NVFBC_GET_STATUS_PARAMS_VER };
std::optional<NVFBC_GET_STATUS_PARAMS> status() {
NVFBC_GET_STATUS_PARAMS params {NVFBC_GET_STATUS_PARAMS_VER};
auto status = func.nvFBCGetStatus(handle, &params);
if (status) {
@@ -652,8 +632,7 @@ namespace cuda {
return params;
}
int
capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
int capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
if (func.nvFBCCreateCaptureSession(handle, &capture_params)) {
BOOST_LOG(error) << "Failed to start capture session: "sv << last_error();
return -1;
@@ -673,13 +652,12 @@ namespace cuda {
return 0;
}
int
stop() {
int stop() {
if (!handle_flags[SESSION_CAPTURE]) {
return 0;
}
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS params { NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER };
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS params {NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER};
if (func.nvFBCDestroyCaptureSession(handle, &params)) {
BOOST_LOG(error) << "Couldn't destroy capture session: "sv << last_error();
@@ -692,17 +670,16 @@ namespace cuda {
return 0;
}
int
reset() {
int reset() {
if (!handle_flags[SESSION_HANDLE]) {
return 0;
}
stop();
NVFBC_DESTROY_HANDLE_PARAMS params { NVFBC_DESTROY_HANDLE_PARAMS_VER };
NVFBC_DESTROY_HANDLE_PARAMS params {NVFBC_DESTROY_HANDLE_PARAMS_VER};
ctx_t ctx { handle };
ctx_t ctx {handle};
if (func.nvFBCDestroyHandle(handle, &params)) {
BOOST_LOG(error) << "Couldn't destroy session handle: "sv << func.nvFBCGetLastErrorStr(handle);
}
@@ -723,14 +700,13 @@ namespace cuda {
class display_t: public platf::display_t {
public:
int
init(const std::string_view &display_name, const ::video::config_t &config) {
int init(const std::string_view &display_name, const ::video::config_t &config) {
auto handle = handle_t::make();
if (!handle) {
return -1;
}
ctx_t ctx { handle->handle };
ctx_t ctx {handle->handle};
auto status_params = handle->status();
if (!status_params) {
@@ -744,19 +720,17 @@ namespace cuda {
if (monitor_nr < 0 || monitor_nr >= status_params->dwOutputNum) {
BOOST_LOG(warning) << "Can't stream monitor ["sv << monitor_nr << "], it needs to be between [0] and ["sv << status_params->dwOutputNum - 1 << "], defaulting to virtual desktop"sv;
}
else {
} else {
streamedMonitor = monitor_nr;
}
}
else {
} else {
BOOST_LOG(warning) << "XrandR not available, streaming entire virtual desktop"sv;
}
}
delay = std::chrono::nanoseconds { 1s } / config.framerate;
delay = std::chrono::nanoseconds {1s} / config.framerate;
capture_params = NVFBC_CREATE_CAPTURE_SESSION_PARAMS { NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER };
capture_params = NVFBC_CREATE_CAPTURE_SESSION_PARAMS {NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER};
capture_params.eCaptureType = NVFBC_CAPTURE_SHARED_CUDA;
capture_params.bDisableAutoModesetRecovery = nv_bool(true);
@@ -773,8 +747,7 @@ namespace cuda {
capture_params.eTrackingType = NVFBC_TRACKING_OUTPUT;
capture_params.dwOutputId = output.dwId;
}
else {
} else {
capture_params.eTrackingType = NVFBC_TRACKING_SCREEN;
width = status_params->screenSize.w;
@@ -788,8 +761,7 @@ namespace cuda {
return 0;
}
platf::capture_e
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
{
@@ -802,7 +774,7 @@ namespace cuda {
// Force display_t::capture to initialize handle_t::capture
cursor_visible = !*cursor;
ctx_t ctx { handle.handle };
ctx_t ctx {handle.handle};
auto fg = util::fail_guard([&]() {
handle.reset();
});
@@ -849,8 +821,7 @@ namespace cuda {
}
// Reinitialize the capture session.
platf::capture_e
reinit(bool cursor) {
platf::capture_e reinit(bool cursor) {
if (handle.stop()) {
return platf::capture_e::error;
}
@@ -860,8 +831,7 @@ namespace cuda {
capture_params.bPushModel = nv_bool(false);
capture_params.bWithCursor = nv_bool(true);
capture_params.bAllowDirectCapture = nv_bool(false);
}
else {
} else {
capture_params.bPushModel = nv_bool(true);
capture_params.bWithCursor = nv_bool(false);
capture_params.bAllowDirectCapture = nv_bool(true);
@@ -919,8 +889,7 @@ namespace cuda {
return platf::capture_e::ok;
}
platf::capture_e
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
if (cursor != cursor_visible) {
auto status = reinit(cursor);
if (status != platf::capture_e::ok) {
@@ -960,13 +929,11 @@ namespace cuda {
return platf::capture_e::ok;
}
std::unique_ptr<platf::avcodec_encode_device_t>
make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) {
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) {
return ::cuda::make_avcodec_encode_device(width, height, true);
}
std::shared_ptr<platf::img_t>
alloc_img() override {
std::shared_ptr<platf::img_t> alloc_img() override {
auto img = std::make_shared<cuda::img_t>();
img->data = nullptr;
@@ -985,8 +952,7 @@ namespace cuda {
return img;
};
int
dummy_img(platf::img_t *) override {
int dummy_img(platf::img_t *) override {
return 0;
}
@@ -1001,8 +967,7 @@ namespace cuda {
} // namespace cuda
namespace platf {
std::shared_ptr<display_t>
nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (hwdevice_type != mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv;
return nullptr;
@@ -1017,8 +982,7 @@ namespace platf {
return display;
}
std::vector<std::string>
nvfbc_display_names() {
std::vector<std::string> nvfbc_display_names() {
if (cuda::init() || cuda::nvfbc::init()) {
return {};
}

View File

@@ -2,14 +2,17 @@
* @file src/platform/linux/cuda.cu
* @brief CUDA implementation for Linux.
*/
// #include <algorithm>
#include <helper_math.h>
// standard includes
#include <chrono>
#include <limits>
#include <memory>
#include <optional>
#include <string_view>
// platform includes
#include <helper_math.h>
// local includes
#include "cuda.h"
using namespace std::literals;
@@ -18,16 +21,20 @@ using namespace std::literals;
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
#define CU_CHECK(x, y) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return -1
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return -1
#define CU_CHECK_VOID(x, y) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return;
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return;
#define CU_CHECK_PTR(x, y) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return nullptr;
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return nullptr;
#define CU_CHECK_OPT(x, y) \
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return std::nullopt;
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return std::nullopt;
#define CU_CHECK_IGNORE(x, y) \
check((x), SUNSHINE_STRINGVIEW(y ": "))
@@ -42,277 +49,293 @@ using namespace std::literals;
* Not pretty and extremely error-prone, fix at earliest convenience.
*/
namespace platf {
struct img_t: std::enable_shared_from_this<img_t> {
public:
std::uint8_t *data {};
std::int32_t width {};
std::int32_t height {};
std::int32_t pixel_pitch {};
std::int32_t row_pitch {};
struct img_t: std::enable_shared_from_this<img_t> {
public:
std::uint8_t *data {};
std::int32_t width {};
std::int32_t height {};
std::int32_t pixel_pitch {};
std::int32_t row_pitch {};
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
virtual ~img_t() = default;
};
} // namespace platf
virtual ~img_t() = default;
};
} // namespace platf
// End special declarations
namespace cuda {
struct alignas(16) cuda_color_t {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
struct alignas(16) cuda_color_t {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
static_assert(sizeof(video::color_t) == sizeof(cuda::cuda_color_t), "color matrix struct mismatch");
static_assert(sizeof(video::color_t) == sizeof(cuda::cuda_color_t), "color matrix struct mismatch");
auto constexpr INVALID_TEXTURE = std::numeric_limits<cudaTextureObject_t>::max();
auto constexpr INVALID_TEXTURE = std::numeric_limits<cudaTextureObject_t>::max();
template<class T>
inline T div_align(T l, T r) {
return (l + r - 1) / r;
}
void pass_error(const std::string_view &sv, const char *name, const char *description);
inline static int check(cudaError_t result, const std::string_view &sv) {
if(result) {
auto name = cudaGetErrorName(result);
auto description = cudaGetErrorString(result);
pass_error(sv, name, description);
return -1;
template<class T>
inline T div_align(T l, T r) {
return (l + r - 1) / r;
}
return 0;
}
void pass_error(const std::string_view &sv, const char *name, const char *description);
template<class T>
ptr_t make_ptr() {
void *p;
CU_CHECK_PTR(cudaMalloc(&p, sizeof(T)), "Couldn't allocate color matrix");
inline static int check(cudaError_t result, const std::string_view &sv) {
if (result) {
auto name = cudaGetErrorName(result);
auto description = cudaGetErrorString(result);
ptr_t ptr { p };
pass_error(sv, name, description);
return -1;
}
return ptr;
}
void freeCudaPtr_t::operator()(void *ptr) {
CU_CHECK_IGNORE(cudaFree(ptr), "Couldn't free cuda device pointer");
}
void freeCudaStream_t::operator()(cudaStream_t ptr) {
CU_CHECK_IGNORE(cudaStreamDestroy(ptr), "Couldn't free cuda stream");
}
stream_t make_stream(int flags) {
cudaStream_t stream;
if(!flags) {
CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream");
}
else {
CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags");
return 0;
}
return stream_t { stream };
}
template<class T>
ptr_t make_ptr() {
void *p;
CU_CHECK_PTR(cudaMalloc(&p, sizeof(T)), "Couldn't allocate color matrix");
inline __device__ float3 bgra_to_rgb(uchar4 vec) {
return make_float3((float)vec.z, (float)vec.y, (float)vec.x);
}
ptr_t ptr {p};
inline __device__ float3 bgra_to_rgb(float4 vec) {
return make_float3(vec.z, vec.y, vec.x);
}
inline __device__ float2 calcUV(float3 pixel, const cuda_color_t *const color_matrix) {
float4 vec_u = color_matrix->color_vec_u;
float4 vec_v = color_matrix->color_vec_v;
float u = dot(pixel, make_float3(vec_u)) + vec_u.w;
float v = dot(pixel, make_float3(vec_v)) + vec_v.w;
u = u * color_matrix->range_uv.x + color_matrix->range_uv.y;
v = v * color_matrix->range_uv.x + color_matrix->range_uv.y;
return make_float2(u, v);
}
inline __device__ float calcY(float3 pixel, const cuda_color_t *const color_matrix) {
float4 vec_y = color_matrix->color_vec_y;
return (dot(pixel, make_float3(vec_y)) + vec_y.w) * color_matrix->range_y.x + color_matrix->range_y.y;
}
__global__ void RGBA_to_NV12(
cudaTextureObject_t srcImage, std::uint8_t *dstY, std::uint8_t *dstUV,
std::uint32_t dstPitchY, std::uint32_t dstPitchUV,
float scale, const viewport_t viewport, const cuda_color_t *const color_matrix) {
int idX = (threadIdx.x + blockDim.x * blockIdx.x) * 2;
int idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2;
if(idX >= viewport.width) return;
if(idY >= viewport.height) return;
float x = idX * scale;
float y = idY * scale;
idX += viewport.offsetX;
idY += viewport.offsetY;
uint8_t *dstY0 = dstY + idX + idY * dstPitchY;
uint8_t *dstY1 = dstY + idX + (idY + 1) * dstPitchY;
dstUV = dstUV + idX + (idY / 2 * dstPitchUV);
float3 rgb_lt = bgra_to_rgb(tex2D<float4>(srcImage, x, y));
float3 rgb_rt = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y));
float3 rgb_lb = bgra_to_rgb(tex2D<float4>(srcImage, x, y + scale));
float3 rgb_rb = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y + scale));
float2 uv_lt = calcUV(rgb_lt, color_matrix) * 256.0f;
float2 uv_rt = calcUV(rgb_rt, color_matrix) * 256.0f;
float2 uv_lb = calcUV(rgb_lb, color_matrix) * 256.0f;
float2 uv_rb = calcUV(rgb_rb, color_matrix) * 256.0f;
float2 uv = (uv_lt + uv_lb + uv_rt + uv_rb) * 0.25f;
dstUV[0] = uv.x;
dstUV[1] = uv.y;
dstY0[0] = calcY(rgb_lt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY0[1] = calcY(rgb_rt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY1[0] = calcY(rgb_lb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY1[1] = calcY(rgb_rb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
}
int tex_t::copy(std::uint8_t *src, int height, int pitch) {
CU_CHECK(cudaMemcpy2DToArray(array, 0, 0, src, pitch, pitch, height, cudaMemcpyDeviceToDevice), "Couldn't copy to cuda array from deviceptr");
return 0;
}
std::optional<tex_t> tex_t::make(int height, int pitch) {
tex_t tex;
auto format = cudaCreateChannelDesc<uchar4>();
CU_CHECK_OPT(cudaMallocArray(&tex.array, &format, pitch, height, cudaArrayDefault), "Couldn't allocate cuda array");
cudaResourceDesc res {};
res.resType = cudaResourceTypeArray;
res.res.array.array = tex.array;
cudaTextureDesc desc {};
desc.readMode = cudaReadModeNormalizedFloat;
desc.filterMode = cudaFilterModePoint;
desc.normalizedCoords = false;
std::fill_n(std::begin(desc.addressMode), 2, cudaAddressModeClamp);
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.point, &res, &desc, nullptr), "Couldn't create cuda texture that uses point interpolation");
desc.filterMode = cudaFilterModeLinear;
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.linear, &res, &desc, nullptr), "Couldn't create cuda texture that uses linear interpolation");
return tex;
}
tex_t::tex_t() : array {}, texture { INVALID_TEXTURE, INVALID_TEXTURE } {}
tex_t::tex_t(tex_t &&other) : array { other.array }, texture { other.texture } {
other.array = 0;
other.texture.point = INVALID_TEXTURE;
other.texture.linear = INVALID_TEXTURE;
}
tex_t &tex_t::operator=(tex_t &&other) {
std::swap(array, other.array);
std::swap(texture, other.texture);
return *this;
}
tex_t::~tex_t() {
if(texture.point != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.point), "Couldn't deallocate cuda texture that uses point interpolation");
texture.point = INVALID_TEXTURE;
return ptr;
}
if(texture.linear != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.linear), "Couldn't deallocate cuda texture that uses linear interpolation");
texture.linear = INVALID_TEXTURE;
void freeCudaPtr_t::operator()(void *ptr) {
CU_CHECK_IGNORE(cudaFree(ptr), "Couldn't free cuda device pointer");
}
if(array) {
CU_CHECK_IGNORE(cudaFreeArray(array), "Couldn't deallocate cuda array");
array = cudaArray_t {};
}
}
sws_t::sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix)
: threadsPerBlock { threadsPerBlock }, color_matrix { std::move(color_matrix) } {
// Ensure aspect ratio is maintained
auto scalar = std::fminf(out_width / (float)in_width, out_height / (float)in_height);
auto out_width_f = in_width * scalar;
auto out_height_f = in_height * scalar;
// result is always positive
auto offsetX_f = (out_width - out_width_f) / 2;
auto offsetY_f = (out_height - out_height_f) / 2;
viewport.width = out_width_f;
viewport.height = out_height_f;
viewport.offsetX = offsetX_f;
viewport.offsetY = offsetY_f;
scale = 1.0f / scalar;
}
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, int pitch) {
cudaDeviceProp props;
int device;
CU_CHECK_OPT(cudaGetDevice(&device), "Couldn't get cuda device");
CU_CHECK_OPT(cudaGetDeviceProperties(&props, device), "Couldn't get cuda device properties");
auto ptr = make_ptr<cuda_color_t>();
if(!ptr) {
return std::nullopt;
void freeCudaStream_t::operator()(cudaStream_t ptr) {
CU_CHECK_IGNORE(cudaStreamDestroy(ptr), "Couldn't free cuda stream");
}
return std::make_optional<sws_t>(in_width, in_height, out_width, out_height, pitch, props.maxThreadsPerMultiProcessor / props.maxBlocksPerMultiProcessor, std::move(ptr));
}
stream_t make_stream(int flags) {
cudaStream_t stream;
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream) {
return convert(Y, UV, pitchY, pitchUV, texture, stream, viewport);
}
if (!flags) {
CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream");
} else {
CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags");
}
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) {
int threadsX = viewport.width / 2;
int threadsY = viewport.height / 2;
return stream_t {stream};
}
dim3 block(threadsPerBlock);
dim3 grid(div_align(threadsX, threadsPerBlock), threadsY);
inline __device__ float3 bgra_to_rgb(uchar4 vec) {
return make_float3((float) vec.z, (float) vec.y, (float) vec.x);
}
RGBA_to_NV12<<<grid, block, 0, stream>>>(texture, Y, UV, pitchY, pitchUV, scale, viewport, (cuda_color_t *)color_matrix.get());
inline __device__ float3 bgra_to_rgb(float4 vec) {
return make_float3(vec.z, vec.y, vec.x);
}
return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_NV12 failed");
}
inline __device__ float2 calcUV(float3 pixel, const cuda_color_t *const color_matrix) {
float4 vec_u = color_matrix->color_vec_u;
float4 vec_v = color_matrix->color_vec_v;
void sws_t::apply_colorspace(const video::sunshine_colorspace_t& colorspace) {
auto color_p = video::color_vectors_from_colorspace(colorspace);
CU_CHECK_IGNORE(cudaMemcpy(color_matrix.get(), color_p, sizeof(video::color_t), cudaMemcpyHostToDevice), "Couldn't copy color matrix to cuda");
}
float u = dot(pixel, make_float3(vec_u)) + vec_u.w;
float v = dot(pixel, make_float3(vec_v)) + vec_v.w;
int sws_t::load_ram(platf::img_t &img, cudaArray_t array) {
return CU_CHECK_IGNORE(cudaMemcpy2DToArray(array, 0, 0, img.data, img.row_pitch, img.width * img.pixel_pitch, img.height, cudaMemcpyHostToDevice), "Couldn't copy to cuda array");
}
u = u * color_matrix->range_uv.x + color_matrix->range_uv.y;
v = v * color_matrix->range_uv.x + color_matrix->range_uv.y;
} // namespace cuda
return make_float2(u, v);
}
inline __device__ float calcY(float3 pixel, const cuda_color_t *const color_matrix) {
float4 vec_y = color_matrix->color_vec_y;
return (dot(pixel, make_float3(vec_y)) + vec_y.w) * color_matrix->range_y.x + color_matrix->range_y.y;
}
__global__ void RGBA_to_NV12(
cudaTextureObject_t srcImage,
std::uint8_t *dstY,
std::uint8_t *dstUV,
std::uint32_t dstPitchY,
std::uint32_t dstPitchUV,
float scale,
const viewport_t viewport,
const cuda_color_t *const color_matrix
) {
int idX = (threadIdx.x + blockDim.x * blockIdx.x) * 2;
int idY = (threadIdx.y + blockDim.y * blockIdx.y) * 2;
if (idX >= viewport.width) {
return;
}
if (idY >= viewport.height) {
return;
}
float x = idX * scale;
float y = idY * scale;
idX += viewport.offsetX;
idY += viewport.offsetY;
uint8_t *dstY0 = dstY + idX + idY * dstPitchY;
uint8_t *dstY1 = dstY + idX + (idY + 1) * dstPitchY;
dstUV = dstUV + idX + (idY / 2 * dstPitchUV);
float3 rgb_lt = bgra_to_rgb(tex2D<float4>(srcImage, x, y));
float3 rgb_rt = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y));
float3 rgb_lb = bgra_to_rgb(tex2D<float4>(srcImage, x, y + scale));
float3 rgb_rb = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y + scale));
float2 uv_lt = calcUV(rgb_lt, color_matrix) * 256.0f;
float2 uv_rt = calcUV(rgb_rt, color_matrix) * 256.0f;
float2 uv_lb = calcUV(rgb_lb, color_matrix) * 256.0f;
float2 uv_rb = calcUV(rgb_rb, color_matrix) * 256.0f;
float2 uv = (uv_lt + uv_lb + uv_rt + uv_rb) * 0.25f;
dstUV[0] = uv.x;
dstUV[1] = uv.y;
dstY0[0] = calcY(rgb_lt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY0[1] = calcY(rgb_rt, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY1[0] = calcY(rgb_lb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
dstY1[1] = calcY(rgb_rb, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visible
}
int tex_t::copy(std::uint8_t *src, int height, int pitch) {
CU_CHECK(cudaMemcpy2DToArray(array, 0, 0, src, pitch, pitch, height, cudaMemcpyDeviceToDevice), "Couldn't copy to cuda array from deviceptr");
return 0;
}
std::optional<tex_t> tex_t::make(int height, int pitch) {
tex_t tex;
auto format = cudaCreateChannelDesc<uchar4>();
CU_CHECK_OPT(cudaMallocArray(&tex.array, &format, pitch, height, cudaArrayDefault), "Couldn't allocate cuda array");
cudaResourceDesc res {};
res.resType = cudaResourceTypeArray;
res.res.array.array = tex.array;
cudaTextureDesc desc {};
desc.readMode = cudaReadModeNormalizedFloat;
desc.filterMode = cudaFilterModePoint;
desc.normalizedCoords = false;
std::fill_n(std::begin(desc.addressMode), 2, cudaAddressModeClamp);
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.point, &res, &desc, nullptr), "Couldn't create cuda texture that uses point interpolation");
desc.filterMode = cudaFilterModeLinear;
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.linear, &res, &desc, nullptr), "Couldn't create cuda texture that uses linear interpolation");
return tex;
}
tex_t::tex_t():
array {},
texture {INVALID_TEXTURE, INVALID_TEXTURE} {
}
tex_t::tex_t(tex_t &&other):
array {other.array},
texture {other.texture} {
other.array = 0;
other.texture.point = INVALID_TEXTURE;
other.texture.linear = INVALID_TEXTURE;
}
tex_t &tex_t::operator=(tex_t &&other) {
std::swap(array, other.array);
std::swap(texture, other.texture);
return *this;
}
tex_t::~tex_t() {
if (texture.point != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.point), "Couldn't deallocate cuda texture that uses point interpolation");
texture.point = INVALID_TEXTURE;
}
if (texture.linear != INVALID_TEXTURE) {
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.linear), "Couldn't deallocate cuda texture that uses linear interpolation");
texture.linear = INVALID_TEXTURE;
}
if (array) {
CU_CHECK_IGNORE(cudaFreeArray(array), "Couldn't deallocate cuda array");
array = cudaArray_t {};
}
}
sws_t::sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix):
threadsPerBlock {threadsPerBlock},
color_matrix {std::move(color_matrix)} {
// Ensure aspect ratio is maintained
auto scalar = std::fminf(out_width / (float) in_width, out_height / (float) in_height);
auto out_width_f = in_width * scalar;
auto out_height_f = in_height * scalar;
// result is always positive
auto offsetX_f = (out_width - out_width_f) / 2;
auto offsetY_f = (out_height - out_height_f) / 2;
viewport.width = out_width_f;
viewport.height = out_height_f;
viewport.offsetX = offsetX_f;
viewport.offsetY = offsetY_f;
scale = 1.0f / scalar;
}
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, int pitch) {
cudaDeviceProp props;
int device;
CU_CHECK_OPT(cudaGetDevice(&device), "Couldn't get cuda device");
CU_CHECK_OPT(cudaGetDeviceProperties(&props, device), "Couldn't get cuda device properties");
auto ptr = make_ptr<cuda_color_t>();
if (!ptr) {
return std::nullopt;
}
return std::make_optional<sws_t>(in_width, in_height, out_width, out_height, pitch, props.maxThreadsPerMultiProcessor / props.maxBlocksPerMultiProcessor, std::move(ptr));
}
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream) {
return convert(Y, UV, pitchY, pitchUV, texture, stream, viewport);
}
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) {
int threadsX = viewport.width / 2;
int threadsY = viewport.height / 2;
dim3 block(threadsPerBlock);
dim3 grid(div_align(threadsX, threadsPerBlock), threadsY);
RGBA_to_NV12<<<grid, block, 0, stream>>>(texture, Y, UV, pitchY, pitchUV, scale, viewport, (cuda_color_t *) color_matrix.get());
return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_NV12 failed");
}
void sws_t::apply_colorspace(const video::sunshine_colorspace_t &colorspace) {
auto color_p = video::color_vectors_from_colorspace(colorspace);
CU_CHECK_IGNORE(cudaMemcpy(color_matrix.get(), color_p, sizeof(video::color_t), cudaMemcpyHostToDevice), "Couldn't copy color matrix to cuda");
}
int sws_t::load_ram(platf::img_t &img, cudaArray_t array) {
return CU_CHECK_IGNORE(cudaMemcpy2DToArray(array, 0, 0, img.data, img.row_pitch, img.width * img.pixel_pitch, img.height, cudaMemcpyHostToDevice), "Couldn't copy to cuda array");
}
} // namespace cuda

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,18 +2,19 @@
* @file src/platform/linux/input/inputtino_gamepad.cpp
* @brief Definitions for inputtino gamepad input handling.
*/
// lib includes
#include <boost/locale.hpp>
#include <inputtino/input.hpp>
#include <libevdev/libevdev.h>
// local includes
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
#include "src/config.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/utility.h"
#include "inputtino_common.h"
#include "inputtino_gamepad.h"
using namespace std::literals;
namespace platf::gamepad {
@@ -25,69 +26,54 @@ namespace platf::gamepad {
GAMEPAD_STATUS ///< Helper to indicate the number of status
};
auto
create_xbox_one() {
return inputtino::XboxOneJoypad::create({ .name = "Sunshine X-Box One (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
.vendor_id = 0x045E,
.product_id = 0x02EA,
.version = 0x0408 });
auto create_xbox_one() {
return inputtino::XboxOneJoypad::create({.name = "Sunshine X-Box One (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
.vendor_id = 0x045E,
.product_id = 0x02EA,
.version = 0x0408});
}
auto
create_switch() {
return inputtino::SwitchJoypad::create({ .name = "Sunshine Nintendo (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
.vendor_id = 0x057e,
.product_id = 0x2009,
.version = 0x8111 });
auto create_switch() {
return inputtino::SwitchJoypad::create({.name = "Sunshine Nintendo (virtual) pad",
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
.vendor_id = 0x057e,
.product_id = 0x2009,
.version = 0x8111});
}
auto
create_ds5() {
return inputtino::PS5Joypad::create({ .name = "Sunshine DualSense (virtual) pad",
.vendor_id = 0x054C,
.product_id = 0x0CE6,
.version = 0x8111 });
auto create_ds5() {
return inputtino::PS5Joypad::create({.name = "Sunshine DualSense (virtual) pad", .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111});
}
int
alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
ControllerType selectedGamepadType;
if (config::input.gamepad == "xone"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (manual selection)"sv;
selectedGamepadType = XboxOneWired;
}
else if (config::input.gamepad == "ds5"sv) {
} else if (config::input.gamepad == "ds5"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualSense 5 controller (manual selection)"sv;
selectedGamepadType = DualSenseWired;
}
else if (config::input.gamepad == "switch"sv) {
} else if (config::input.gamepad == "switch"sv) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (manual selection)"sv;
selectedGamepadType = SwitchProWired;
}
else if (metadata.type == LI_CTYPE_XBOX) {
} else if (metadata.type == LI_CTYPE_XBOX) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (auto-selected by client-reported type)"sv;
selectedGamepadType = XboxOneWired;
}
else if (metadata.type == LI_CTYPE_PS) {
} else if (metadata.type == LI_CTYPE_PS) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by client-reported type)"sv;
selectedGamepadType = DualSenseWired;
}
else if (metadata.type == LI_CTYPE_NINTENDO) {
} else if (metadata.type == LI_CTYPE_NINTENDO) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Nintendo Pro controller (auto-selected by client-reported type)"sv;
selectedGamepadType = SwitchProWired;
}
else if (config::input.motion_as_ds4 && (metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
} else if (config::input.motion_as_ds4 && (metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by motion sensor presence)"sv;
selectedGamepadType = DualSenseWired;
}
else if (config::input.touchpad_as_ds4 && (metadata.capabilities & LI_CCAP_TOUCHPAD)) {
} else if (config::input.touchpad_as_ds4 && (metadata.capabilities & LI_CCAP_TOUCHPAD)) {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 5 controller (auto-selected by touchpad presence)"sv;
selectedGamepadType = DualSenseWired;
}
else {
} else {
BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox One controller (default)"sv;
selectedGamepadType = XboxOneWired;
}
@@ -102,8 +88,7 @@ namespace platf::gamepad {
if (metadata.capabilities & LI_CCAP_RGB_LED) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " has an RGB LED, but it is not usable when emulating a joypad different from DS5"sv;
}
}
else if (selectedGamepadType == DualSenseWired) {
} else if (selectedGamepadType == DualSenseWired) {
if (!(metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO))) {
BOOST_LOG(warning) << "Gamepad " << id.globalIndex << " is emulating a DualShock 5 controller, but the client gamepad doesn't have motion sensors active"sv;
}
@@ -125,73 +110,71 @@ namespace platf::gamepad {
};
switch (selectedGamepadType) {
case XboxOneWired: {
auto xOne = create_xbox_one();
if (xOne) {
(*xOne).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
case XboxOneWired:
{
auto xOne = create_xbox_one();
if (xOne) {
(*xOne).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*xOne));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
} else {
BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage();
return -1;
}
}
else {
BOOST_LOG(warning) << "Unable to create virtual Xbox One controller: " << xOne.getErrorMessage();
return -1;
case SwitchProWired:
{
auto switchPro = create_switch();
if (switchPro) {
(*switchPro).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
} else {
BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage();
return -1;
}
}
}
case SwitchProWired: {
auto switchPro = create_switch();
if (switchPro) {
(*switchPro).set_on_rumble(on_rumble_fn);
gamepad->joypad = std::make_unique<joypads_t>(std::move(*switchPro));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
}
else {
BOOST_LOG(warning) << "Unable to create virtual Switch Pro controller: " << switchPro.getErrorMessage();
return -1;
}
}
case DualSenseWired: {
auto ds5 = create_ds5();
if (ds5) {
(*ds5).set_on_rumble(on_rumble_fn);
(*ds5).set_on_led([feedback_queue, idx = id.clientRelativeIndex, gamepad](int r, int g, int b) {
// Don't resend duplicate LED data
if (gamepad->last_rgb_led.type == platf::gamepad_feedback_e::set_rgb_led && gamepad->last_rgb_led.data.rgb_led.r == r && gamepad->last_rgb_led.data.rgb_led.g == g && gamepad->last_rgb_led.data.rgb_led.b == b) {
return;
}
case DualSenseWired:
{
auto ds5 = create_ds5();
if (ds5) {
(*ds5).set_on_rumble(on_rumble_fn);
(*ds5).set_on_led([feedback_queue, idx = id.clientRelativeIndex, gamepad](int r, int g, int b) {
// Don't resend duplicate LED data
if (gamepad->last_rgb_led.type == platf::gamepad_feedback_e::set_rgb_led && gamepad->last_rgb_led.data.rgb_led.r == r && gamepad->last_rgb_led.data.rgb_led.g == g && gamepad->last_rgb_led.data.rgb_led.b == b) {
return;
}
auto msg = gamepad_feedback_msg_t::make_rgb_led(idx, r, g, b);
feedback_queue->raise(msg);
gamepad->last_rgb_led = msg;
});
auto msg = gamepad_feedback_msg_t::make_rgb_led(idx, r, g, b);
feedback_queue->raise(msg);
gamepad->last_rgb_led = msg;
});
// Activate the motion sensors
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_ACCEL, 100));
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_GYRO, 100));
// Activate the motion sensors
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_ACCEL, 100));
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(id.clientRelativeIndex, LI_MOTION_TYPE_GYRO, 100));
gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
gamepad->joypad = std::make_unique<joypads_t>(std::move(*ds5));
raw->gamepads[id.globalIndex] = std::move(gamepad);
return 0;
} else {
BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage();
return -1;
}
}
else {
BOOST_LOG(warning) << "Unable to create virtual DualShock 5 controller: " << ds5.getErrorMessage();
return -1;
}
}
}
return -1;
}
void
free(input_raw_t *raw, int nr) {
void free(input_raw_t *raw, int nr) {
// This will call the destructor which in turn will stop the background threads for rumble and LED (and ultimately remove the joypad device)
raw->gamepads[nr]->joypad.reset();
raw->gamepads[nr].reset();
}
void
update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
auto gamepad = raw->gamepads[nr];
if (!gamepad) {
return;
@@ -203,11 +186,10 @@ namespace platf::gamepad {
gc.set_stick(inputtino::Joypad::RS, gamepad_state.rsX, gamepad_state.rsY);
gc.set_triggers(gamepad_state.lt, gamepad_state.rt);
},
*gamepad->joypad);
*gamepad->joypad);
}
void
touch(input_raw_t *raw, const gamepad_touch_t &touch) {
void touch(input_raw_t *raw, const gamepad_touch_t &touch) {
auto gamepad = raw->gamepads[touch.id.globalIndex];
if (!gamepad) {
return;
@@ -216,15 +198,13 @@ namespace platf::gamepad {
if (std::holds_alternative<inputtino::PS5Joypad>(*gamepad->joypad)) {
if (touch.pressure > 0.5) {
std::get<inputtino::PS5Joypad>(*gamepad->joypad).place_finger(touch.pointerId, touch.x * inputtino::PS5Joypad::touchpad_width, touch.y * inputtino::PS5Joypad::touchpad_height);
}
else {
} else {
std::get<inputtino::PS5Joypad>(*gamepad->joypad).release_finger(touch.pointerId);
}
}
}
void
motion(input_raw_t *raw, const gamepad_motion_t &motion) {
void motion(input_raw_t *raw, const gamepad_motion_t &motion) {
auto gamepad = raw->gamepads[motion.id.globalIndex];
if (!gamepad) {
return;
@@ -242,8 +222,7 @@ namespace platf::gamepad {
}
}
void
battery(input_raw_t *raw, const gamepad_battery_t &battery) {
void battery(input_raw_t *raw, const gamepad_battery_t &battery) {
auto gamepad = raw->gamepads[battery.id.globalIndex];
if (!gamepad) {
return;
@@ -272,14 +251,13 @@ namespace platf::gamepad {
}
}
std::vector<supported_gamepad_t> &
supported_gamepads(input_t *input) {
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
if (!input) {
static std::vector gps {
supported_gamepad_t { "auto", true, "" },
supported_gamepad_t { "xone", false, "" },
supported_gamepad_t { "ds5", false, "" },
supported_gamepad_t { "switch", false, "" },
supported_gamepad_t {"auto", true, ""},
supported_gamepad_t {"xone", false, ""},
supported_gamepad_t {"ds5", false, ""},
supported_gamepad_t {"switch", false, ""},
};
return gps;
@@ -290,10 +268,10 @@ namespace platf::gamepad {
auto xOne = create_xbox_one();
static std::vector gps {
supported_gamepad_t { "auto", true, "" },
supported_gamepad_t { "xone", static_cast<bool>(xOne), !xOne ? xOne.getErrorMessage() : "" },
supported_gamepad_t { "ds5", static_cast<bool>(ds5), !ds5 ? ds5.getErrorMessage() : "" },
supported_gamepad_t { "switch", static_cast<bool>(switchPro), !switchPro ? switchPro.getErrorMessage() : "" },
supported_gamepad_t {"auto", true, ""},
supported_gamepad_t {"xone", static_cast<bool>(xOne), !xOne ? xOne.getErrorMessage() : ""},
supported_gamepad_t {"ds5", static_cast<bool>(ds5), !ds5 ? ds5.getErrorMessage() : ""},
supported_gamepad_t {"switch", static_cast<bool>(switchPro), !switchPro ? switchPro.getErrorMessage() : ""},
};
for (auto &[name, is_enabled, reason_disabled] : gps) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,8 +3,10 @@
* @brief Definitions for publishing services on Linux.
* @note Adapted from https://www.avahi.org/doxygen/html/client-publish-service_8c-example.html
*/
// standard includes
#include <thread>
// local includes
#include "misc.h"
#include "src/logging.h"
#include "src/network.h"
@@ -84,6 +86,7 @@ namespace avahi {
};
constexpr auto IF_UNSPEC = -1;
enum proto {
PROTO_INET = 0, ///< IPv4
PROTO_INET6 = 1, ///< IPv6
@@ -164,7 +167,8 @@ namespace avahi {
const char *domain,
const char *host,
uint16_t port,
...);
...
);
typedef int (*entry_group_is_empty_fn)(EntryGroup *);
typedef int (*entry_group_reset_fn)(EntryGroup *);
@@ -199,30 +203,31 @@ namespace avahi {
simple_poll_new_fn simple_poll_new;
simple_poll_free_fn simple_poll_free;
int
init_common() {
static void *handle { nullptr };
int init_common() {
static void *handle {nullptr};
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (funcs_loaded) {
return 0;
}
if (!handle) {
handle = dyn::handle({ "libavahi-common.so.3", "libavahi-common.so" });
handle = dyn::handle({"libavahi-common.so.3", "libavahi-common.so"});
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &alternative_service_name, "avahi_alternative_service_name" },
{ (dyn::apiproc *) &free, "avahi_free" },
{ (dyn::apiproc *) &strdup, "avahi_strdup" },
{ (dyn::apiproc *) &strerror, "avahi_strerror" },
{ (dyn::apiproc *) &simple_poll_get, "avahi_simple_poll_get" },
{ (dyn::apiproc *) &simple_poll_loop, "avahi_simple_poll_loop" },
{ (dyn::apiproc *) &simple_poll_quit, "avahi_simple_poll_quit" },
{ (dyn::apiproc *) &simple_poll_new, "avahi_simple_poll_new" },
{ (dyn::apiproc *) &simple_poll_free, "avahi_simple_poll_free" },
{(dyn::apiproc *) &alternative_service_name, "avahi_alternative_service_name"},
{(dyn::apiproc *) &free, "avahi_free"},
{(dyn::apiproc *) &strdup, "avahi_strdup"},
{(dyn::apiproc *) &strerror, "avahi_strerror"},
{(dyn::apiproc *) &simple_poll_get, "avahi_simple_poll_get"},
{(dyn::apiproc *) &simple_poll_loop, "avahi_simple_poll_loop"},
{(dyn::apiproc *) &simple_poll_quit, "avahi_simple_poll_quit"},
{(dyn::apiproc *) &simple_poll_new, "avahi_simple_poll_new"},
{(dyn::apiproc *) &simple_poll_free, "avahi_simple_poll_free"},
};
if (dyn::load(handle, funcs)) {
@@ -233,34 +238,35 @@ namespace avahi {
return 0;
}
int
init_client() {
int init_client() {
if (init_common()) {
return -1;
}
static void *handle { nullptr };
static void *handle {nullptr};
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (funcs_loaded) {
return 0;
}
if (!handle) {
handle = dyn::handle({ "libavahi-client.so.3", "libavahi-client.so" });
handle = dyn::handle({"libavahi-client.so.3", "libavahi-client.so"});
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &client_new, "avahi_client_new" },
{ (dyn::apiproc *) &client_free, "avahi_client_free" },
{ (dyn::apiproc *) &entry_group_get_client, "avahi_entry_group_get_client" },
{ (dyn::apiproc *) &entry_group_new, "avahi_entry_group_new" },
{ (dyn::apiproc *) &entry_group_add_service, "avahi_entry_group_add_service" },
{ (dyn::apiproc *) &entry_group_is_empty, "avahi_entry_group_is_empty" },
{ (dyn::apiproc *) &entry_group_reset, "avahi_entry_group_reset" },
{ (dyn::apiproc *) &entry_group_commit, "avahi_entry_group_commit" },
{ (dyn::apiproc *) &client_errno, "avahi_client_errno" },
{(dyn::apiproc *) &client_new, "avahi_client_new"},
{(dyn::apiproc *) &client_free, "avahi_client_free"},
{(dyn::apiproc *) &entry_group_get_client, "avahi_entry_group_get_client"},
{(dyn::apiproc *) &entry_group_new, "avahi_entry_group_new"},
{(dyn::apiproc *) &entry_group_add_service, "avahi_entry_group_add_service"},
{(dyn::apiproc *) &entry_group_is_empty, "avahi_entry_group_is_empty"},
{(dyn::apiproc *) &entry_group_reset, "avahi_entry_group_reset"},
{(dyn::apiproc *) &entry_group_commit, "avahi_entry_group_commit"},
{(dyn::apiproc *) &client_errno, "avahi_client_errno"},
};
if (dyn::load(handle, funcs)) {
@@ -274,13 +280,12 @@ namespace avahi {
namespace platf::publish {
template <class T>
void
free(T *p) {
template<class T>
void free(T *p) {
avahi::free(p);
}
template <class T>
template<class T>
using ptr_t = util::safe_ptr<T, free<T>>;
using client_t = util::dyn_safe_ptr<avahi::Client, &avahi::client_free>;
using poll_t = util::dyn_safe_ptr<avahi::SimplePoll, &avahi::simple_poll_free>;
@@ -292,11 +297,9 @@ namespace platf::publish {
ptr_t<char> name;
void
create_services(avahi::Client *c);
void create_services(avahi::Client *c);
void
entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
void entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
group = g;
switch (state) {
@@ -319,8 +322,7 @@ namespace platf::publish {
}
}
void
create_services(avahi::Client *c) {
void create_services(avahi::Client *c) {
int ret;
auto fg = util::fail_guard([]() {
@@ -339,13 +341,16 @@ namespace platf::publish {
ret = avahi::entry_group_add_service(
group,
avahi::IF_UNSPEC, avahi::PROTO_UNSPEC,
avahi::IF_UNSPEC,
avahi::PROTO_UNSPEC,
avahi::PublishFlags(0),
name.get(),
SERVICE_TYPE,
nullptr, nullptr,
nullptr,
nullptr,
net::map_port(nvhttp::PORT_HTTP),
nullptr);
nullptr
);
if (ret < 0) {
if (ret == avahi::ERR_COLLISION) {
@@ -375,8 +380,7 @@ namespace platf::publish {
fg.disable();
}
void
client_callback(avahi::Client *c, avahi::ClientState state, void *) {
void client_callback(avahi::Client *c, avahi::ClientState state, void *) {
switch (state) {
case avahi::CLIENT_S_RUNNING:
create_services(c);
@@ -387,8 +391,9 @@ namespace platf::publish {
break;
case avahi::CLIENT_S_COLLISION:
case avahi::CLIENT_S_REGISTERING:
if (group)
if (group) {
avahi::entry_group_reset(group);
}
break;
case avahi::CLIENT_CONNECTING:;
}
@@ -399,7 +404,8 @@ namespace platf::publish {
std::thread poll_thread;
deinit_t(std::thread poll_thread):
poll_thread { std::move(poll_thread) } {}
poll_thread {std::move(poll_thread)} {
}
~deinit_t() override {
if (avahi::simple_poll_quit && poll) {
@@ -412,8 +418,7 @@ namespace platf::publish {
}
};
[[nodiscard]] std::unique_ptr<::platf::deinit_t>
start() {
[[nodiscard]] std::unique_ptr<::platf::deinit_t> start() {
if (avahi::init_client()) {
return nullptr;
}
@@ -430,13 +435,14 @@ namespace platf::publish {
name.reset(avahi::strdup(instance_name.c_str()));
client.reset(
avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error));
avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error)
);
if (!client) {
BOOST_LOG(error) << "Failed to create client: "sv << avahi::strerror(avhi_error);
return nullptr;
}
return std::make_unique<deinit_t>(std::thread { avahi::simple_poll_loop, poll.get() });
return std::make_unique<deinit_t>(std::thread {avahi::simple_poll_loop, poll.get()});
}
} // namespace platf::publish

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,61 +2,49 @@
* @file src/platform/linux/x11grab.cpp
* @brief Definitions for x11 capture.
*/
#include "src/platform/common.h"
// standard includes
#include <fstream>
#include <thread>
// plaform includes
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/Xrandr.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/Xrandr.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <xcb/shm.h>
#include <xcb/xfixes.h>
#include "src/config.h"
#include "src/globals.h"
#include "src/logging.h"
#include "src/task_pool.h"
#include "src/video.h"
// local includes
#include "cuda.h"
#include "graphics.h"
#include "misc.h"
#include "src/config.h"
#include "src/globals.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/task_pool.h"
#include "src/video.h"
#include "vaapi.h"
#include "x11grab.h"
using namespace std::literals;
namespace platf {
int
load_xcb();
int
load_x11();
int load_xcb();
int load_x11();
namespace x11 {
#define _FN(x, ret, args) \
#define _FN(x, ret, args) \
typedef ret(*x##_fn) args; \
static x##_fn x
_FN(GetImage, XImage *,
(
Display * display,
Drawable d,
int x, int y,
unsigned int width, unsigned int height,
unsigned long plane_mask,
int format));
_FN(GetImage, XImage *, (Display * display, Drawable d, int x, int y, unsigned int width, unsigned int height, unsigned long plane_mask, int format));
_FN(OpenDisplay, Display *, (_Xconst char *display_name));
_FN(GetWindowAttributes, Status,
(
Display * display,
Window w,
XWindowAttributes *window_attributes_return));
_FN(GetWindowAttributes, Status, (Display * display, Window w, XWindowAttributes *window_attributes_return));
_FN(CloseDisplay, int, (Display * display));
_FN(Free, int, (void *data));
@@ -70,27 +58,28 @@ namespace platf {
_FN(FreeOutputInfo, void, (XRROutputInfo * outputInfo));
_FN(FreeCrtcInfo, void, (XRRCrtcInfo * crtcInfo));
static int
init() {
static void *handle { nullptr };
static int init() {
static void *handle {nullptr};
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (funcs_loaded) {
return 0;
}
if (!handle) {
handle = dyn::handle({ "libXrandr.so.2", "libXrandr.so" });
handle = dyn::handle({"libXrandr.so.2", "libXrandr.so"});
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &GetScreenResources, "XRRGetScreenResources" },
{ (dyn::apiproc *) &GetOutputInfo, "XRRGetOutputInfo" },
{ (dyn::apiproc *) &GetCrtcInfo, "XRRGetCrtcInfo" },
{ (dyn::apiproc *) &FreeScreenResources, "XRRFreeScreenResources" },
{ (dyn::apiproc *) &FreeOutputInfo, "XRRFreeOutputInfo" },
{ (dyn::apiproc *) &FreeCrtcInfo, "XRRFreeCrtcInfo" },
{(dyn::apiproc *) &GetScreenResources, "XRRGetScreenResources"},
{(dyn::apiproc *) &GetOutputInfo, "XRRGetOutputInfo"},
{(dyn::apiproc *) &GetCrtcInfo, "XRRGetCrtcInfo"},
{(dyn::apiproc *) &FreeScreenResources, "XRRFreeScreenResources"},
{(dyn::apiproc *) &FreeOutputInfo, "XRRFreeOutputInfo"},
{(dyn::apiproc *) &FreeCrtcInfo, "XRRFreeCrtcInfo"},
};
if (dyn::load(handle, funcs)) {
@@ -102,25 +91,27 @@ namespace platf {
}
} // namespace rr
namespace fix {
_FN(GetCursorImage, XFixesCursorImage *, (Display * dpy));
static int
init() {
static void *handle { nullptr };
static int init() {
static void *handle {nullptr};
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (funcs_loaded) {
return 0;
}
if (!handle) {
handle = dyn::handle({ "libXfixes.so.3", "libXfixes.so" });
handle = dyn::handle({"libXfixes.so.3", "libXfixes.so"});
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &GetCursorImage, "XFixesGetCursorImage" },
{(dyn::apiproc *) &GetCursorImage, "XFixesGetCursorImage"},
};
if (dyn::load(handle, funcs)) {
@@ -132,27 +123,28 @@ namespace platf {
}
} // namespace fix
static int
init() {
static void *handle { nullptr };
static int init() {
static void *handle {nullptr};
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (funcs_loaded) {
return 0;
}
if (!handle) {
handle = dyn::handle({ "libX11.so.6", "libX11.so" });
handle = dyn::handle({"libX11.so.6", "libX11.so"});
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &GetImage, "XGetImage" },
{ (dyn::apiproc *) &OpenDisplay, "XOpenDisplay" },
{ (dyn::apiproc *) &GetWindowAttributes, "XGetWindowAttributes" },
{ (dyn::apiproc *) &Free, "XFree" },
{ (dyn::apiproc *) &CloseDisplay, "XCloseDisplay" },
{ (dyn::apiproc *) &InitThreads, "XInitThreads" },
{(dyn::apiproc *) &GetImage, "XGetImage"},
{(dyn::apiproc *) &OpenDisplay, "XOpenDisplay"},
{(dyn::apiproc *) &GetWindowAttributes, "XGetWindowAttributes"},
{(dyn::apiproc *) &Free, "XFree"},
{(dyn::apiproc *) &CloseDisplay, "XCloseDisplay"},
{(dyn::apiproc *) &InitThreads, "XInitThreads"},
};
if (dyn::load(handle, funcs)) {
@@ -167,31 +159,13 @@ namespace platf {
namespace xcb {
static xcb_extension_t *shm_id;
_FN(shm_get_image_reply, xcb_shm_get_image_reply_t *,
(
xcb_connection_t * c,
xcb_shm_get_image_cookie_t cookie,
xcb_generic_error_t **e));
_FN(shm_get_image_reply, xcb_shm_get_image_reply_t *, (xcb_connection_t * c, xcb_shm_get_image_cookie_t cookie, xcb_generic_error_t **e));
_FN(shm_get_image_unchecked, xcb_shm_get_image_cookie_t,
(
xcb_connection_t * c,
xcb_drawable_t drawable,
int16_t x, int16_t y,
uint16_t width, uint16_t height,
uint32_t plane_mask,
uint8_t format,
xcb_shm_seg_t shmseg,
uint32_t offset));
_FN(shm_get_image_unchecked, xcb_shm_get_image_cookie_t, (xcb_connection_t * c, xcb_drawable_t drawable, int16_t x, int16_t y, uint16_t width, uint16_t height, uint32_t plane_mask, uint8_t format, xcb_shm_seg_t shmseg, uint32_t offset));
_FN(shm_attach, xcb_void_cookie_t,
(xcb_connection_t * c,
xcb_shm_seg_t shmseg,
uint32_t shmid,
uint8_t read_only));
_FN(shm_attach, xcb_void_cookie_t, (xcb_connection_t * c, xcb_shm_seg_t shmseg, uint32_t shmid, uint8_t read_only));
_FN(get_extension_data, xcb_query_extension_reply_t *,
(xcb_connection_t * c, xcb_extension_t *ext));
_FN(get_extension_data, xcb_query_extension_reply_t *, (xcb_connection_t * c, xcb_extension_t *ext));
_FN(get_setup, xcb_setup_t *, (xcb_connection_t * c));
_FN(disconnect, void, (xcb_connection_t * c));
@@ -200,25 +174,26 @@ namespace platf {
_FN(setup_roots_iterator, xcb_screen_iterator_t, (const xcb_setup_t *R));
_FN(generate_id, std::uint32_t, (xcb_connection_t * c));
int
init_shm() {
static void *handle { nullptr };
int init_shm() {
static void *handle {nullptr};
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (funcs_loaded) {
return 0;
}
if (!handle) {
handle = dyn::handle({ "libxcb-shm.so.0", "libxcb-shm.so" });
handle = dyn::handle({"libxcb-shm.so.0", "libxcb-shm.so"});
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &shm_id, "xcb_shm_id" },
{ (dyn::apiproc *) &shm_get_image_reply, "xcb_shm_get_image_reply" },
{ (dyn::apiproc *) &shm_get_image_unchecked, "xcb_shm_get_image_unchecked" },
{ (dyn::apiproc *) &shm_attach, "xcb_shm_attach" },
{(dyn::apiproc *) &shm_id, "xcb_shm_id"},
{(dyn::apiproc *) &shm_get_image_reply, "xcb_shm_get_image_reply"},
{(dyn::apiproc *) &shm_get_image_unchecked, "xcb_shm_get_image_unchecked"},
{(dyn::apiproc *) &shm_attach, "xcb_shm_attach"},
};
if (dyn::load(handle, funcs)) {
@@ -229,28 +204,29 @@ namespace platf {
return 0;
}
int
init() {
static void *handle { nullptr };
int init() {
static void *handle {nullptr};
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (funcs_loaded) {
return 0;
}
if (!handle) {
handle = dyn::handle({ "libxcb.so.1", "libxcb.so" });
handle = dyn::handle({"libxcb.so.1", "libxcb.so"});
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &get_extension_data, "xcb_get_extension_data" },
{ (dyn::apiproc *) &get_setup, "xcb_get_setup" },
{ (dyn::apiproc *) &disconnect, "xcb_disconnect" },
{ (dyn::apiproc *) &connection_has_error, "xcb_connection_has_error" },
{ (dyn::apiproc *) &connect, "xcb_connect" },
{ (dyn::apiproc *) &setup_roots_iterator, "xcb_setup_roots_iterator" },
{ (dyn::apiproc *) &generate_id, "xcb_generate_id" },
{(dyn::apiproc *) &get_extension_data, "xcb_get_extension_data"},
{(dyn::apiproc *) &get_setup, "xcb_get_setup"},
{(dyn::apiproc *) &disconnect, "xcb_disconnect"},
{(dyn::apiproc *) &connection_has_error, "xcb_connection_has_error"},
{(dyn::apiproc *) &connect, "xcb_connect"},
{(dyn::apiproc *) &setup_roots_iterator, "xcb_setup_roots_iterator"},
{(dyn::apiproc *) &generate_id, "xcb_generate_id"},
};
if (dyn::load(handle, funcs)) {
@@ -264,10 +240,8 @@ namespace platf {
#undef _FN
} // namespace xcb
void
freeImage(XImage *);
void
freeX(XFixesCursorImage *);
void freeImage(XImage *);
void freeX(XFixesCursorImage *);
using xcb_connect_t = util::dyn_safe_ptr<xcb_connection_t, &xcb::disconnect>;
using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;
@@ -282,9 +256,13 @@ namespace platf {
class shm_id_t {
public:
shm_id_t():
id { -1 } {}
id {-1} {
}
shm_id_t(int id):
id { id } {}
id {id} {
}
shm_id_t(shm_id_t &&other) noexcept:
id(other.id) {
other.id = -1;
@@ -296,15 +274,19 @@ namespace platf {
id = -1;
}
}
int id;
};
class shm_data_t {
public:
shm_data_t():
data { (void *) -1 } {}
data {(void *) -1} {
}
shm_data_t(void *data):
data { data } {}
data {data} {
}
shm_data_t(shm_data_t &&other) noexcept:
data(other.data) {
@@ -331,9 +313,8 @@ namespace platf {
}
};
static void
blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
xcursor_t overlay { x11::fix::GetCursorImage(display) };
static void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
xcursor_t overlay {x11::fix::GetCursorImage(display)};
if (!overlay) {
BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv;
@@ -370,8 +351,7 @@ namespace platf {
auto alpha = (*(uint *) pixel_p) >> 24u;
if (alpha == 255) {
*pixels_begin = *pixel_p;
}
else {
} else {
auto colors_out = (uint8_t *) pixel_p;
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
@@ -398,18 +378,20 @@ namespace platf {
// int env_width, env_height;
x11_attr_t(mem_type_e mem_type):
xdisplay { x11::OpenDisplay(nullptr) }, xwindow {}, xattr {}, mem_type { mem_type } {
xdisplay {x11::OpenDisplay(nullptr)},
xwindow {},
xattr {},
mem_type {mem_type} {
x11::InitThreads();
}
int
init(const std::string &display_name, const ::video::config_t &config) {
int init(const std::string &display_name, const ::video::config_t &config) {
if (!xdisplay) {
BOOST_LOG(error) << "Could not open X11 display"sv;
return -1;
}
delay = std::chrono::nanoseconds { 1s } / config.framerate;
delay = std::chrono::nanoseconds {1s} / config.framerate;
xwindow = DefaultRootWindow(xdisplay.get());
@@ -422,13 +404,13 @@ namespace platf {
if (streamedMonitor != -1) {
BOOST_LOG(info) << "Configuring selected display ("sv << streamedMonitor << ") to stream"sv;
screen_res_t screenr { x11::rr::GetScreenResources(xdisplay.get(), xwindow) };
screen_res_t screenr {x11::rr::GetScreenResources(xdisplay.get(), xwindow)};
int output = screenr->noutput;
output_info_t result;
int monitor = 0;
for (int x = 0; x < output; ++x) {
output_info_t out_info { x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) };
output_info_t out_info {x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x])};
if (out_info) {
if (monitor++ == streamedMonitor) {
result = std::move(out_info);
@@ -443,7 +425,7 @@ namespace platf {
}
if (result->crtc) {
crtc_info_t crt_info { x11::rr::GetCrtcInfo(xdisplay.get(), screenr.get(), result->crtc) };
crtc_info_t crt_info {x11::rr::GetCrtcInfo(xdisplay.get(), screenr.get(), result->crtc)};
BOOST_LOG(info)
<< "Streaming display: "sv << result->name << " with res "sv << crt_info->width << 'x' << crt_info->height << " offset by "sv << crt_info->x << 'x' << crt_info->y;
@@ -451,14 +433,12 @@ namespace platf {
height = crt_info->height;
offset_x = crt_info->x;
offset_y = crt_info->y;
}
else {
} else {
BOOST_LOG(warning) << "Couldn't get requested display info, defaulting to recording entire virtual desktop"sv;
width = xattr.width;
height = xattr.height;
}
}
else {
} else {
width = xattr.width;
height = xattr.height;
}
@@ -472,13 +452,11 @@ namespace platf {
/**
* Called when the display attributes should change.
*/
void
refresh() {
void refresh() {
x11::GetWindowAttributes(xdisplay.get(), xwindow, &xattr); // Update xattr's
}
capture_e
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
sleep_overshoot_logger.reset();
@@ -523,8 +501,7 @@ namespace platf {
return capture_e::ok;
}
capture_e
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
refresh();
// The whole X server changed, so we must reinit everything
@@ -538,7 +515,7 @@ namespace platf {
}
auto img = (x11_img_t *) img_out.get();
XImage *x_img { x11::GetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) };
XImage *x_img {x11::GetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap)};
img->frame_timestamp = std::chrono::steady_clock::now();
img->width = x_img->width;
@@ -555,13 +532,11 @@ namespace platf {
return capture_e::ok;
}
std::shared_ptr<img_t>
alloc_img() override {
std::shared_ptr<img_t> alloc_img() override {
return std::make_shared<x11_img_t>();
}
std::unique_ptr<avcodec_encode_device_t>
make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == mem_type_e::vaapi) {
return va::make_avcodec_encode_device(width, height, false);
@@ -577,8 +552,7 @@ namespace platf {
return std::make_unique<avcodec_encode_device_t>();
}
int
dummy_img(img_t *img) override {
int dummy_img(img_t *img) override {
// TODO: stop cheating and give black image
if (!img) {
return -1;
@@ -605,15 +579,15 @@ namespace platf {
task_pool_util::TaskPool::task_id_t refresh_task_id;
void
delayed_refresh() {
void delayed_refresh() {
refresh();
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
}
shm_attr_t(mem_type_e mem_type):
x11_attr_t(mem_type), shm_xdisplay { x11::OpenDisplay(nullptr) } {
x11_attr_t(mem_type),
shm_xdisplay {x11::OpenDisplay(nullptr)} {
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
}
@@ -621,8 +595,7 @@ namespace platf {
while (!task_pool.cancel(refresh_task_id));
}
capture_e
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
sleep_overshoot_logger.reset();
@@ -667,18 +640,16 @@ namespace platf {
return capture_e::ok;
}
capture_e
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
// The whole X server changed, so we must reinit everything
if (xattr.width != env_width || xattr.height != env_height) {
BOOST_LOG(warning) << "X dimensions changed in SHM mode, request reinit"sv;
return capture_e::reinit;
}
else {
} else {
auto img_cookie = xcb::shm_get_image_unchecked(xcb.get(), display->root, offset_x, offset_y, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0);
auto frame_timestamp = std::chrono::steady_clock::now();
xcb_img_t img_reply { xcb::shm_get_image_reply(xcb.get(), img_cookie, nullptr) };
xcb_img_t img_reply {xcb::shm_get_image_reply(xcb.get(), img_cookie, nullptr)};
if (!img_reply) {
BOOST_LOG(error) << "Could not get image reply"sv;
return capture_e::reinit;
@@ -699,8 +670,7 @@ namespace platf {
}
}
std::shared_ptr<img_t>
alloc_img() override {
std::shared_ptr<img_t> alloc_img() override {
auto img = std::make_shared<shm_img_t>();
img->width = width;
img->height = height;
@@ -711,13 +681,11 @@ namespace platf {
return img;
}
int
dummy_img(platf::img_t *img) override {
int dummy_img(platf::img_t *img) override {
return 0;
}
int
init(const std::string &display_name, const ::video::config_t &config) {
int init(const std::string &display_name, const ::video::config_t &config) {
if (x11_attr_t::init(display_name, config)) {
return 1;
}
@@ -756,14 +724,12 @@ namespace platf {
return 0;
}
std::uint32_t
frame_size() {
std::uint32_t frame_size() {
return width * height * 4;
}
};
std::shared_ptr<display_t>
x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
std::shared_ptr<display_t> x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize x11 display with the given hw device type"sv;
return nullptr;
@@ -797,8 +763,7 @@ namespace platf {
return x11_disp;
}
std::vector<std::string>
x11_display_names() {
std::vector<std::string> x11_display_names() {
if (load_x11() || load_xcb()) {
BOOST_LOG(error) << "Couldn't init x11 libraries"sv;
@@ -807,18 +772,18 @@ namespace platf {
BOOST_LOG(info) << "Detecting displays"sv;
x11::xdisplay_t xdisplay { x11::OpenDisplay(nullptr) };
x11::xdisplay_t xdisplay {x11::OpenDisplay(nullptr)};
if (!xdisplay) {
return {};
}
auto xwindow = DefaultRootWindow(xdisplay.get());
screen_res_t screenr { x11::rr::GetScreenResources(xdisplay.get(), xwindow) };
screen_res_t screenr {x11::rr::GetScreenResources(xdisplay.get(), xwindow)};
int output = screenr->noutput;
int monitor = 0;
for (int x = 0; x < output; ++x) {
output_info_t out_info { x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) };
output_info_t out_info {x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x])};
if (out_info) {
BOOST_LOG(info) << "Detected display: "sv << out_info->name << " (id: "sv << monitor << ")"sv << out_info->name << " connected: "sv << (out_info->connection == RR_Connected);
++monitor;
@@ -835,25 +800,22 @@ namespace platf {
return names;
}
void
freeImage(XImage *p) {
void freeImage(XImage *p) {
XDestroyImage(p);
}
void
freeX(XFixesCursorImage *p) {
void freeX(XFixesCursorImage *p) {
x11::Free(p);
}
int
load_xcb() {
int load_xcb() {
// This will be called once only
static int xcb_status = xcb::init_shm() || xcb::init();
return xcb_status;
}
int
load_x11() {
int load_x11() {
// This will be called once only
static int x11_status =
window_system == window_system_e::NONE ||
@@ -863,8 +825,7 @@ namespace platf {
}
namespace x11 {
std::optional<cursor_t>
cursor_t::make() {
std::optional<cursor_t> cursor_t::make() {
if (load_x11()) {
return std::nullopt;
}
@@ -876,8 +837,7 @@ namespace platf {
return cursor;
}
void
cursor_t::capture(egl::cursor_t &img) {
void cursor_t::capture(egl::cursor_t &img) {
auto display = (xdisplay_t::pointer) ctx.get();
xcursor_t xcursor = fix::GetCursorImage(display);
@@ -904,23 +864,19 @@ namespace platf {
img.serial = xcursor->cursor_serial;
}
void
cursor_t::blend(img_t &img, int offsetX, int offsetY) {
void cursor_t::blend(img_t &img, int offsetX, int offsetY) {
blend_cursor((xdisplay_t::pointer) ctx.get(), img, offsetX, offsetY);
}
xdisplay_t
make_display() {
xdisplay_t make_display() {
return OpenDisplay(nullptr);
}
void
freeDisplay(_XDisplay *xdisplay) {
void freeDisplay(_XDisplay *xdisplay) {
CloseDisplay(xdisplay);
}
void
freeCursorCtx(cursor_ctx_t::pointer ctx) {
void freeCursorCtx(cursor_ctx_t::pointer ctx) {
CloseDisplay((xdisplay_t::pointer) ctx);
}
} // namespace x11

View File

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

View File

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

View File

@@ -2,12 +2,13 @@
* @file src/platform/macos/av_audio.m
* @brief Definitions for audio capture on macOS.
*/
// local includes
#import "av_audio.h"
@implementation AVAudio
+ (NSArray<AVCaptureDevice *> *)microphones {
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) { 10, 15, 0 })]) {
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) {10, 15, 0})]) {
// This will generate a warning about AVCaptureDeviceDiscoverySession being
// unavailable before macOS 10.15, but we have a guard to prevent it from
// being called on those earlier systems.
@@ -16,14 +17,12 @@
// a different method.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInMicrophone,
AVCaptureDeviceTypeExternalUnknown]
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInMicrophone, AVCaptureDeviceTypeExternalUnknown]
mediaType:AVMediaTypeAudio
position:AVCaptureDevicePositionUnspecified];
return discoverySession.devices;
#pragma clang diagnostic pop
}
else {
} else {
// We're intentionally using a deprecated API here specifically for versions
// of macOS where it's not deprecated, so we can ignore any deprecation
// warnings:
@@ -75,8 +74,7 @@
if ([self.audioCaptureSession canAddInput:audioInput]) {
[self.audioCaptureSession addInput:audioInput];
}
else {
} else {
[audioInput dealloc];
return -1;
}
@@ -92,17 +90,14 @@
(NSString *) AVLinearPCMIsNonInterleaved: @NO
}];
dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,
QOS_CLASS_USER_INITIATED,
DISPATCH_QUEUE_PRIORITY_HIGH);
dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INITIATED, DISPATCH_QUEUE_PRIORITY_HIGH);
dispatch_queue_t recordingQueue = dispatch_queue_create("audioSamplingQueue", qos);
[audioOutput setSampleBufferDelegate:self queue:recordingQueue];
if ([self.audioCaptureSession canAddOutput:audioOutput]) {
[self.audioCaptureSession addOutput:audioOutput];
}
else {
} else {
[audioInput release];
[audioOutput release];
return -1;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,14 +8,15 @@
#pragma once
// platform includes
#include <mmdeviceapi.h>
#ifdef __MINGW32__
#undef DEFINE_GUID
#ifdef __cplusplus
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}}
#else
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}}
#endif
DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8);
@@ -40,64 +41,69 @@ class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
// ----------------------------------------------------------------------------
interface IPolicyConfig: public IUnknown {
public:
virtual HRESULT
GetMixFormat(
virtual HRESULT GetMixFormat(
PCWSTR,
WAVEFORMATEX **);
WAVEFORMATEX **
);
virtual HRESULT STDMETHODCALLTYPE
GetDeviceFormat(
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
PCWSTR,
INT,
WAVEFORMATEX **);
WAVEFORMATEX **
);
virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
PCWSTR);
PCWSTR
);
virtual HRESULT STDMETHODCALLTYPE
SetDeviceFormat(
PCWSTR,
WAVEFORMATEX *,
WAVEFORMATEX *);
SetDeviceFormat(
PCWSTR,
WAVEFORMATEX *,
WAVEFORMATEX *
);
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
PCWSTR,
INT,
PINT64,
PINT64);
PINT64
);
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
PCWSTR,
PINT64);
PINT64
);
virtual HRESULT STDMETHODCALLTYPE
GetShareMode(
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
PCWSTR,
struct DeviceShareMode *);
struct DeviceShareMode *
);
virtual HRESULT STDMETHODCALLTYPE
SetShareMode(
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
PCWSTR,
struct DeviceShareMode *);
struct DeviceShareMode *
);
virtual HRESULT STDMETHODCALLTYPE
GetPropertyValue(
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
PROPVARIANT *
);
virtual HRESULT STDMETHODCALLTYPE
SetPropertyValue(
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
PROPVARIANT *
);
virtual HRESULT STDMETHODCALLTYPE
SetDefaultEndpoint(
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
PCWSTR wszDeviceId,
ERole eRole);
ERole eRole
);
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
PCWSTR,
INT);
INT
);
};

View File

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

View File

@@ -4,16 +4,17 @@
*/
#pragma once
// platform includes
#include <d3d11.h>
#include <d3d11_4.h>
#include <d3dcommon.h>
#include <dwmapi.h>
#include <dxgi.h>
#include <dxgi1_6.h>
#include <Unknwn.h>
#include <winrt/Windows.Graphics.Capture.h>
// local includes
#include "src/platform/common.h"
#include "src/utility.h"
#include "src/video.h"
@@ -25,9 +26,8 @@ namespace platf::dxgi {
// You should have a debugger like WinDbg attached to receive debug messages.
auto constexpr D3D11_CREATE_DEVICE_FLAGS = 0;
template <class T>
void
Release(T *dxgi) {
template<class T>
void Release(T *dxgi) {
dxgi->Release();
}
@@ -72,6 +72,7 @@ namespace platf::dxgi {
} // namespace video
class hwdevice_t;
struct cursor_t {
std::vector<std::uint8_t> img_data;
@@ -83,10 +84,9 @@ namespace platf::dxgi {
class gpu_cursor_t {
public:
gpu_cursor_t():
cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {};
cursor_view {0, 0, 0, 0, 0.0f, 1.0f} {};
void
set_pos(LONG topleft_x, LONG topleft_y, LONG display_width, LONG display_height, DXGI_MODE_ROTATION display_rotation, bool visible) {
void set_pos(LONG topleft_x, LONG topleft_y, LONG display_width, LONG display_height, DXGI_MODE_ROTATION display_rotation, bool visible) {
this->topleft_x = topleft_x;
this->topleft_y = topleft_y;
this->display_width = display_width;
@@ -96,16 +96,14 @@ namespace platf::dxgi {
update_viewport();
}
void
set_texture(LONG texture_width, LONG texture_height, texture2d_t &&texture) {
void set_texture(LONG texture_width, LONG texture_height, texture2d_t &&texture) {
this->texture = std::move(texture);
this->texture_width = texture_width;
this->texture_height = texture_height;
update_viewport();
}
void
update_viewport() {
void update_viewport() {
switch (display_rotation) {
case DXGI_MODE_ROTATION_UNSPECIFIED:
case DXGI_MODE_ROTATION_IDENTITY:
@@ -158,11 +156,9 @@ namespace platf::dxgi {
class display_base_t: public display_t {
public:
int
init(const ::video::config_t &config, const std::string &display_name);
int init(const ::video::config_t &config, const std::string &display_name);
capture_e
capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override;
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override;
factory1_t factory;
adapter_t adapter;
@@ -209,6 +205,7 @@ namespace platf::dxgi {
UINT IndependentVidPnVSyncControl : 1;
UINT Reserved : 28;
};
UINT Value;
};
} D3DKMT_WDDM_2_7_CAPS;
@@ -231,30 +228,21 @@ namespace platf::dxgi {
typedef NTSTATUS(WINAPI *PD3DKMTQueryAdapterInfo)(D3DKMT_QUERYADAPTERINFO *);
typedef NTSTATUS(WINAPI *PD3DKMTCloseAdapter)(D3DKMT_CLOSEADAPTER *);
virtual bool
is_hdr() override;
virtual bool
get_hdr_metadata(SS_HDR_METADATA &metadata) override;
virtual bool is_hdr() override;
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) override;
const char *
dxgi_format_to_string(DXGI_FORMAT format);
const char *
colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
virtual std::vector<DXGI_FORMAT>
get_supported_capture_formats() = 0;
const char *dxgi_format_to_string(DXGI_FORMAT format);
const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
virtual std::vector<DXGI_FORMAT> get_supported_capture_formats() = 0;
protected:
int
get_pixel_pitch() {
int get_pixel_pitch() {
return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4;
}
virtual capture_e
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) = 0;
virtual capture_e
release_snapshot() = 0;
virtual int
complete_img(img_t *img, bool dummy) = 0;
virtual capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) = 0;
virtual capture_e release_snapshot() = 0;
virtual int complete_img(img_t *img, bool dummy) = 0;
};
/**
@@ -262,17 +250,12 @@ namespace platf::dxgi {
*/
class display_ram_t: public display_base_t {
public:
std::shared_ptr<img_t>
alloc_img() override;
int
dummy_img(img_t *img) override;
int
complete_img(img_t *img, bool dummy) override;
std::vector<DXGI_FORMAT>
get_supported_capture_formats() override;
std::shared_ptr<img_t> alloc_img() override;
int dummy_img(img_t *img) override;
int complete_img(img_t *img, bool dummy) override;
std::vector<DXGI_FORMAT> get_supported_capture_formats() override;
std::unique_ptr<avcodec_encode_device_t>
make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
D3D11_MAPPED_SUBRESOURCE img_info;
texture2d_t texture;
@@ -283,23 +266,16 @@ namespace platf::dxgi {
*/
class display_vram_t: public display_base_t, public std::enable_shared_from_this<display_vram_t> {
public:
std::shared_ptr<img_t>
alloc_img() override;
int
dummy_img(img_t *img_base) override;
int
complete_img(img_t *img_base, bool dummy) override;
std::vector<DXGI_FORMAT>
get_supported_capture_formats() override;
std::shared_ptr<img_t> alloc_img() override;
int dummy_img(img_t *img_base) override;
int complete_img(img_t *img_base, bool dummy) override;
std::vector<DXGI_FORMAT> get_supported_capture_formats() override;
bool
is_codec_supported(std::string_view name, const ::video::config_t &config) override;
bool is_codec_supported(std::string_view name, const ::video::config_t &config) override;
std::unique_ptr<avcodec_encode_device_t>
make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
std::unique_ptr<nvenc_encode_device_t>
make_nvenc_encode_device(pix_fmt_e pix_fmt) override;
std::unique_ptr<nvenc_encode_device_t> make_nvenc_encode_device(pix_fmt_e pix_fmt) override;
std::atomic<uint32_t> next_image_id;
};
@@ -313,14 +289,10 @@ namespace platf::dxgi {
bool has_frame {};
std::chrono::steady_clock::time_point last_protected_content_warning_time {};
int
init(display_base_t *display, const ::video::config_t &config);
capture_e
next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p);
capture_e
reset(dup_t::pointer dup_p = dup_t::pointer());
capture_e
release_frame();
int init(display_base_t *display, const ::video::config_t &config);
capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p);
capture_e reset(dup_t::pointer dup_p = dup_t::pointer());
capture_e release_frame();
~duplication_t();
};
@@ -330,12 +302,9 @@ namespace platf::dxgi {
*/
class display_ddup_ram_t: public display_ram_t {
public:
int
init(const ::video::config_t &config, const std::string &display_name);
capture_e
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
capture_e
release_snapshot() override;
int init(const ::video::config_t &config, const std::string &display_name);
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
capture_e release_snapshot() override;
duplication_t dup;
cursor_t cursor;
@@ -346,12 +315,9 @@ namespace platf::dxgi {
*/
class display_ddup_vram_t: public display_vram_t {
public:
int
init(const ::video::config_t &config, const std::string &display_name);
capture_e
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
capture_e
release_snapshot() override;
int init(const ::video::config_t &config, const std::string &display_name);
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
capture_e release_snapshot() override;
duplication_t dup;
sampler_state_t sampler_linear;
@@ -375,29 +341,24 @@ namespace platf::dxgi {
* Display duplicator that uses the Windows.Graphics.Capture API.
*/
class wgc_capture_t {
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice uwp_device { nullptr };
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item { nullptr };
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool frame_pool { nullptr };
winrt::Windows::Graphics::Capture::GraphicsCaptureSession capture_session { nullptr };
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame produced_frame { nullptr }, consumed_frame { nullptr };
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice uwp_device {nullptr};
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item {nullptr};
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool frame_pool {nullptr};
winrt::Windows::Graphics::Capture::GraphicsCaptureSession capture_session {nullptr};
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame produced_frame {nullptr}, consumed_frame {nullptr};
SRWLOCK frame_lock = SRWLOCK_INIT;
CONDITION_VARIABLE frame_present_cv;
void
on_frame_arrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, winrt::Windows::Foundation::IInspectable const &);
void on_frame_arrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, winrt::Windows::Foundation::IInspectable const &);
public:
wgc_capture_t();
~wgc_capture_t();
int
init(display_base_t *display, const ::video::config_t &config);
capture_e
next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time);
capture_e
release_frame();
int
set_cursor_visible(bool);
int init(display_base_t *display, const ::video::config_t &config);
capture_e next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time);
capture_e release_frame();
int set_cursor_visible(bool);
};
/**
@@ -407,12 +368,9 @@ namespace platf::dxgi {
wgc_capture_t dup;
public:
int
init(const ::video::config_t &config, const std::string &display_name);
capture_e
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
capture_e
release_snapshot() override;
int init(const ::video::config_t &config, const std::string &display_name);
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
capture_e release_snapshot() override;
};
/**
@@ -422,11 +380,8 @@ namespace platf::dxgi {
wgc_capture_t dup;
public:
int
init(const ::video::config_t &config, const std::string &display_name);
capture_e
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
capture_e
release_snapshot() override;
int init(const ::video::config_t &config, const std::string &display_name);
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
capture_e release_snapshot() override;
};
} // namespace platf::dxgi

View File

@@ -2,13 +2,16 @@
* @file src/platform/windows/display_base.cpp
* @brief Definitions for the Windows display base code.
*/
// standard includes
#include <cmath>
#include <initguid.h>
#include <thread>
// platform includes
#include <initguid.h>
// lib includes
#include <boost/algorithm/string/join.hpp>
#include <boost/process/v1.hpp>
#include <MinHook.h>
// We have to include boost/process/v1.hpp before display.h due to WinSock.h,
@@ -36,14 +39,14 @@ typedef enum _D3DKMT_GPU_PREFERENCE_QUERY_STATE: DWORD {
namespace platf {
using namespace std::literals;
}
namespace platf::dxgi {
namespace bp = boost::process;
/**
* DDAPI-specific initialization goes here.
*/
int
duplication_t::init(display_base_t *display, const ::video::config_t &config) {
int duplication_t::init(display_base_t *display, const ::video::config_t &config) {
HRESULT status;
// Capture format will be determined from the first call to AcquireNextFrame()
@@ -81,8 +84,7 @@ namespace platf::dxgi {
BOOST_LOG(warning) << "DuplicateOutput1 Failed [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
}
else {
} else {
BOOST_LOG(warning) << "IDXGIOutput5 is not supported by your OS. Capture performance may be reduced."sv;
dxgi::output1_t output1 {};
@@ -124,8 +126,7 @@ namespace platf::dxgi {
return 0;
}
capture_e
duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p) {
capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p) {
auto capture_status = release_frame();
if (capture_status != capture_e::ok) {
return capture_status;
@@ -157,8 +158,7 @@ namespace platf::dxgi {
}
}
capture_e
duplication_t::reset(dup_t::pointer dup_p) {
capture_e duplication_t::reset(dup_t::pointer dup_p) {
auto capture_status = release_frame();
dup.reset(dup_p);
@@ -166,8 +166,7 @@ namespace platf::dxgi {
return capture_status;
}
capture_e
duplication_t::release_frame() {
capture_e duplication_t::release_frame() {
if (!has_frame) {
return capture_e::ok;
}
@@ -195,16 +194,14 @@ namespace platf::dxgi {
release_frame();
}
capture_e
display_base_t::capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) {
capture_e display_base_t::capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) {
auto adjust_client_frame_rate = [&]() -> DXGI_RATIONAL {
// Adjust capture frame interval when display refresh rate is not integral but very close to requested fps.
if (display_refresh_rate.Denominator > 1) {
DXGI_RATIONAL candidate = display_refresh_rate;
if (client_frame_rate % display_refresh_rate_rounded == 0) {
candidate.Numerator *= client_frame_rate / display_refresh_rate_rounded;
}
else if (display_refresh_rate_rounded % client_frame_rate == 0) {
} else if (display_refresh_rate_rounded % client_frame_rate == 0) {
candidate.Denominator *= display_refresh_rate_rounded / client_frame_rate;
}
double candidate_rate = (double) candidate.Numerator / candidate.Denominator;
@@ -215,7 +212,7 @@ namespace platf::dxgi {
}
}
return { (uint32_t) client_frame_rate, 1 };
return {(uint32_t) client_frame_rate, 1};
};
DXGI_RATIONAL client_frame_rate_adjusted = adjust_client_frame_rate();
@@ -258,8 +255,7 @@ namespace platf::dxgi {
frame_pacing_group_start = std::nullopt;
frame_pacing_group_frames = 0;
status = capture_e::timeout;
}
else {
} else {
timer->sleep_for(sleep_period);
sleep_overshoot_logger.first_point(sleep_target);
sleep_overshoot_logger.second_point_now_and_log();
@@ -268,8 +264,7 @@ namespace platf::dxgi {
if (status == capture_e::ok && img_out) {
frame_pacing_group_frames += 1;
}
else {
} else {
frame_pacing_group_start = std::nullopt;
frame_pacing_group_frames = 0;
}
@@ -289,8 +284,7 @@ namespace platf::dxgi {
}
frame_pacing_group_frames = 1;
}
else if (status == platf::capture_e::timeout) {
} else if (status == platf::capture_e::timeout) {
// The D3D11 device is protected by an unfair lock that is held the entire time that
// IDXGIOutputDuplication::AcquireNextFrame() is running. This is normally harmless,
// however sometimes the encoding thread needs to interact with our ID3D11Device to
@@ -348,8 +342,7 @@ namespace platf::dxgi {
* @param output The DXGI output to capture.
* @param enumeration_only Specifies whether this test is occurring for display enumeration.
*/
bool
test_dxgi_duplication(adapter_t &adapter, output_t &output, bool enumeration_only) {
bool test_dxgi_duplication(adapter_t &adapter, output_t &output, bool enumeration_only) {
D3D_FEATURE_LEVEL featureLevels[] {
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
@@ -366,11 +359,13 @@ namespace platf::dxgi {
D3D_DRIVER_TYPE_UNKNOWN,
nullptr,
D3D11_CREATE_DEVICE_FLAGS,
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
featureLevels,
sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
D3D11_SDK_VERSION,
&device,
nullptr,
nullptr);
nullptr
);
if (FAILED(status)) {
BOOST_LOG(error) << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']';
return false;
@@ -403,8 +398,7 @@ namespace platf::dxgi {
// capture the current desktop, just bail immediately. Retrying won't help.
if (enumeration_only && status == E_ACCESSDENIED) {
break;
}
else {
} else {
std::this_thread::sleep_for(200ms);
}
}
@@ -418,8 +412,7 @@ namespace platf::dxgi {
* @param gpuPreference A pointer to the location where the preference will be written.
* @return Always STATUS_SUCCESS if valid arguments are provided.
*/
NTSTATUS
__stdcall NtGdiDdDDIGetCachedHybridQueryValueHook(D3DKMT_GPU_PREFERENCE_QUERY_STATE *gpuPreference) {
NTSTATUS __stdcall NtGdiDdDDIGetCachedHybridQueryValueHook(D3DKMT_GPU_PREFERENCE_QUERY_STATE *gpuPreference) {
// By faking a cached GPU preference state of D3DKMT_GPU_PREFERENCE_STATE_UNSPECIFIED, this will
// prevent DXGI from performing the normal GPU preference resolution that looks at the registry,
// power settings, and the hybrid adapter DDI interface to pick a GPU. Instead, we will not be
@@ -428,14 +421,12 @@ namespace platf::dxgi {
if (gpuPreference) {
*gpuPreference = D3DKMT_GPU_PREFERENCE_STATE_UNSPECIFIED;
return 0; // STATUS_SUCCESS
}
else {
} else {
return STATUS_INVALID_PARAMETER;
}
}
int
display_base_t::init(const ::video::config_t &config, const std::string &display_name) {
int display_base_t::init(const ::video::config_t &config, const std::string &display_name) {
std::once_flag windows_cpp_once_flag;
std::call_once(windows_cpp_once_flag, []() {
@@ -479,7 +470,7 @@ namespace platf::dxgi {
adapter_t::pointer adapter_p;
for (int tries = 0; tries < 2; ++tries) {
for (int x = 0; factory->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) {
dxgi::adapter_t adapter_tmp { adapter_p };
dxgi::adapter_t adapter_tmp {adapter_p};
DXGI_ADAPTER_DESC1 adapter_desc;
adapter_tmp->GetDesc1(&adapter_desc);
@@ -490,7 +481,7 @@ namespace platf::dxgi {
dxgi::output_t::pointer output_p;
for (int y = 0; adapter_tmp->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
dxgi::output_t output_tmp { output_p };
dxgi::output_t output_tmp {output_p};
DXGI_OUTPUT_DESC desc;
output_tmp->GetDesc(&desc);
@@ -512,8 +503,7 @@ namespace platf::dxgi {
display_rotation == DXGI_MODE_ROTATION_ROTATE270) {
width_before_rotation = height;
height_before_rotation = width;
}
else {
} else {
width_before_rotation = width;
height_before_rotation = height;
}
@@ -570,11 +560,13 @@ namespace platf::dxgi {
D3D_DRIVER_TYPE_UNKNOWN,
nullptr,
D3D11_CREATE_DEVICE_FLAGS,
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
featureLevels,
sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
D3D11_SDK_VERSION,
&device,
&feature_level,
&device_ctx);
&device_ctx
);
adapter_p->Release();
@@ -632,7 +624,7 @@ namespace platf::dxgi {
return false;
}
D3DKMT_OPENADAPTERFROMLUID d3dkmt_adapter = { adapter };
D3DKMT_OPENADAPTERFROMLUID d3dkmt_adapter = {adapter};
if (FAILED(d3dkmt_open_adapter(&d3dkmt_adapter))) {
BOOST_LOG(error) << "D3DKMTOpenAdapterFromLuid() failed while trying to determine GPU HAGS status";
return false;
@@ -649,13 +641,12 @@ namespace platf::dxgi {
if (SUCCEEDED(d3dkmt_query_adapter_info(&d3dkmt_adapter_info))) {
result = d3dkmt_adapter_caps.HwSchEnabled;
}
else {
} else {
BOOST_LOG(warning) << "D3DKMTQueryAdapterInfo() failed while trying to determine GPU HAGS status";
result = false;
}
D3DKMT_CLOSEADAPTER d3dkmt_close_adapter_wrap = { d3dkmt_adapter.hAdapter };
D3DKMT_CLOSEADAPTER d3dkmt_close_adapter_wrap = {d3dkmt_adapter.hAdapter};
if (FAILED(d3dkmt_close_adapter(&d3dkmt_close_adapter_wrap))) {
BOOST_LOG(error) << "D3DKMTCloseAdapter() failed while trying to determine GPU HAGS status";
}
@@ -671,15 +662,16 @@ namespace platf::dxgi {
// As of 2023.07, NVIDIA driver has unfixed bug(s) where "realtime" can cause unrecoverable encoding freeze or outright driver crash
// This issue happens more frequently with HAGS, in DX12 games or when VRAM is filled close to max capacity
// Track OBS to see if they find better workaround or NVIDIA fixes it on their end, they seem to be in communication
if (hags_enabled && !config::video.nv_realtime_hags) priority = D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH;
if (hags_enabled && !config::video.nv_realtime_hags) {
priority = D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH;
}
}
BOOST_LOG(info) << "Active GPU has HAGS " << (hags_enabled ? "enabled" : "disabled");
BOOST_LOG(info) << "Using " << (priority == D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH ? "high" : "realtime") << " GPU priority";
if (FAILED(d3dkmt_set_process_priority(GetCurrentProcess(), priority))) {
BOOST_LOG(warning) << "Failed to adjust GPU priority. Please run application as administrator for optimal performance.";
}
}
else {
} else {
BOOST_LOG(error) << "Couldn't load D3DKMTSetProcessSchedulingPriorityClass function from gdi32.dll to adjust GPU priority";
}
}
@@ -740,8 +732,7 @@ namespace platf::dxgi {
return 0;
}
bool
display_base_t::is_hdr() {
bool display_base_t::is_hdr() {
dxgi::output6_t output6 {};
auto status = output->QueryInterface(IID_IDXGIOutput6, (void **) &output6);
@@ -756,8 +747,7 @@ namespace platf::dxgi {
return desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
}
bool
display_base_t::get_hdr_metadata(SS_HDR_METADATA &metadata) {
bool display_base_t::get_hdr_metadata(SS_HDR_METADATA &metadata) {
dxgi::output6_t output6 {};
std::memset(&metadata, 0, sizeof(metadata));
@@ -928,20 +918,31 @@ namespace platf::dxgi {
"DXGI_FORMAT_A8P8",
"DXGI_FORMAT_B4G4R4A4_UNORM",
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"DXGI_FORMAT_P208",
"DXGI_FORMAT_V208",
"DXGI_FORMAT_V408"
};
const char *
display_base_t::dxgi_format_to_string(DXGI_FORMAT format) {
const char *display_base_t::dxgi_format_to_string(DXGI_FORMAT format) {
return format_str[format];
}
const char *
display_base_t::colorspace_to_string(DXGI_COLOR_SPACE_TYPE type) {
const char *display_base_t::colorspace_to_string(DXGI_COLOR_SPACE_TYPE type) {
const char *type_str[] = {
"DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709",
"DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709",
@@ -972,8 +973,7 @@ namespace platf::dxgi {
if (type < ARRAYSIZE(type_str)) {
return type_str[type];
}
else {
} else {
return "UNKNOWN";
}
}
@@ -985,8 +985,7 @@ namespace platf {
* Pick a display adapter and capture method.
* @param hwdevice_type enables possible use of hardware encoder
*/
std::shared_ptr<display_t>
display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (config::video.capture == "ddx" || config::video.capture.empty()) {
if (hwdevice_type == mem_type_e::dxgi) {
auto disp = std::make_shared<dxgi::display_ddup_vram_t>();
@@ -994,8 +993,7 @@ namespace platf {
if (!disp->init(config, display_name)) {
return disp;
}
}
else if (hwdevice_type == mem_type_e::system) {
} else if (hwdevice_type == mem_type_e::system) {
auto disp = std::make_shared<dxgi::display_ddup_ram_t>();
if (!disp->init(config, display_name)) {
@@ -1011,8 +1009,7 @@ namespace platf {
if (!disp->init(config, display_name)) {
return disp;
}
}
else if (hwdevice_type == mem_type_e::system) {
} else if (hwdevice_type == mem_type_e::system) {
auto disp = std::make_shared<dxgi::display_wgc_ram_t>();
if (!disp->init(config, display_name)) {
@@ -1025,8 +1022,7 @@ namespace platf {
return nullptr;
}
std::vector<std::string>
display_names(mem_type_e) {
std::vector<std::string> display_names(mem_type_e) {
std::vector<std::string> display_names;
HRESULT status;
@@ -1066,7 +1062,7 @@ namespace platf {
dxgi::output_t::pointer output_p {};
for (int y = 0; adapter->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
dxgi::output_t output { output_p };
dxgi::output_t output {output_p};
DXGI_OUTPUT_DESC desc;
output->GetDesc(&desc);
@@ -1096,8 +1092,7 @@ namespace platf {
* @brief Returns if GPUs/drivers have changed since the last call to this function.
* @return `true` if a change has occurred or if it is unknown whether a change occurred.
*/
bool
needs_encoder_reenumeration() {
bool needs_encoder_reenumeration() {
// Serialize access to the static DXGI factory
static std::mutex reenumeration_state_lock;
auto lg = std::lock_guard(reenumeration_state_lock);
@@ -1117,8 +1112,7 @@ namespace platf {
// can deal with any initialization races that may occur when the system is booting.
BOOST_LOG(info) << "Encoder reenumeration is required"sv;
return true;
}
else {
} else {
// The DXGI factory from last time is still current, so no encoder changes have occurred.
return false;
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,336 +1,335 @@
/**
* @file src/platform/windows/display_wgc.cpp
* @brief Definitions for WinRT Windows.Graphics.Capture API
*/
#include <dxgi1_2.h>
#include "display.h"
#include "misc.h"
#include "src/logging.h"
// Gross hack to work around MINGW-packages#22160
#define ____FIReference_1_boolean_INTERFACE_DEFINED__
#include <windows.graphics.capture.interop.h>
#include <winrt/windows.foundation.h>
#include <winrt/windows.foundation.metadata.h>
#include <winrt/windows.graphics.directx.direct3d11.h>
namespace platf {
using namespace std::literals;
}
namespace winrt {
using namespace Windows::Foundation;
using namespace Windows::Foundation::Metadata;
using namespace Windows::Graphics::Capture;
using namespace Windows::Graphics::DirectX::Direct3D11;
extern "C" {
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice);
}
/**
* Windows structures sometimes have compile-time GUIDs. GCC supports this, but in a roundabout way.
* If WINRT_IMPL_HAS_DECLSPEC_UUID is true, then the compiler supports adding this attribute to a struct. For example, Visual Studio.
* If not, then MinGW GCC has a workaround to assign a GUID to a structure.
*/
struct
#if WINRT_IMPL_HAS_DECLSPEC_UUID
__declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"))
#endif
IDirect3DDxgiInterfaceAccess: ::IUnknown {
virtual HRESULT __stdcall GetInterface(REFIID id, void **object) = 0;
};
} // namespace winrt
#if !WINRT_IMPL_HAS_DECLSPEC_UUID
static constexpr GUID GUID__IDirect3DDxgiInterfaceAccess = {
0xA9B3D012, 0x3DF2, 0x4EE3, { 0xB8, 0xD1, 0x86, 0x95, 0xF4, 0x57, 0xD3, 0xC1 }
// compare with __declspec(uuid(...)) for the struct above.
};
template <>
constexpr auto
__mingw_uuidof<winrt::IDirect3DDxgiInterfaceAccess>() -> GUID const & {
return GUID__IDirect3DDxgiInterfaceAccess;
}
#endif
namespace platf::dxgi {
wgc_capture_t::wgc_capture_t() {
InitializeConditionVariable(&frame_present_cv);
}
wgc_capture_t::~wgc_capture_t() {
if (capture_session)
capture_session.Close();
if (frame_pool)
frame_pool.Close();
item = nullptr;
capture_session = nullptr;
frame_pool = nullptr;
}
/**
* @brief Initialize the Windows.Graphics.Capture backend.
* @return 0 on success, -1 on failure.
*/
int
wgc_capture_t::init(display_base_t *display, const ::video::config_t &config) {
HRESULT status;
dxgi::dxgi_t dxgi;
winrt::com_ptr<::IInspectable> d3d_comhandle;
try {
if (!winrt::GraphicsCaptureSession::IsSupported()) {
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows!"sv;
return -1;
}
if (FAILED(status = display->device->QueryInterface(IID_IDXGIDevice, (void **) &dxgi))) {
BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
if (FAILED(status = winrt::CreateDirect3D11DeviceFromDXGIDevice(*&dxgi, d3d_comhandle.put()))) {
BOOST_LOG(error) << "Failed to query WinRT DirectX interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
}
catch (winrt::hresult_error &e) {
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to acquire device: [0x"sv << util::hex(e.code()).to_string_view() << ']';
return -1;
}
DXGI_OUTPUT_DESC output_desc;
uwp_device = d3d_comhandle.as<winrt::IDirect3DDevice>();
display->output->GetDesc(&output_desc);
auto monitor_factory = winrt::get_activation_factory<winrt::GraphicsCaptureItem, IGraphicsCaptureItemInterop>();
if (monitor_factory == nullptr ||
FAILED(status = monitor_factory->CreateForMonitor(output_desc.Monitor, winrt::guid_of<winrt::IGraphicsCaptureItem>(), winrt::put_abi(item)))) {
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to acquire display: [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
if (config.dynamicRange)
display->capture_format = DXGI_FORMAT_R16G16B16A16_FLOAT;
else
display->capture_format = DXGI_FORMAT_B8G8R8A8_UNORM;
try {
frame_pool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(uwp_device, static_cast<winrt::Windows::Graphics::DirectX::DirectXPixelFormat>(display->capture_format), 2, item.Size());
capture_session = frame_pool.CreateCaptureSession(item);
frame_pool.FrameArrived({ this, &wgc_capture_t::on_frame_arrived });
}
catch (winrt::hresult_error &e) {
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to create capture session: [0x"sv << util::hex(e.code()).to_string_view() << ']';
return -1;
}
try {
if (winrt::ApiInformation::IsPropertyPresent(L"Windows.Graphics.Capture.GraphicsCaptureSession", L"IsBorderRequired")) {
capture_session.IsBorderRequired(false);
}
else {
BOOST_LOG(warning) << "Can't disable colored border around capture area on this version of Windows";
}
}
catch (winrt::hresult_error &e) {
BOOST_LOG(warning) << "Screen capture may not be fully supported on this device for this release of Windows: failed to disable border around capture area: [0x"sv << util::hex(e.code()).to_string_view() << ']';
}
try {
capture_session.StartCapture();
}
catch (winrt::hresult_error &e) {
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to start capture: [0x"sv << util::hex(e.code()).to_string_view() << ']';
return -1;
}
return 0;
}
/**
* This function runs in a separate thread spawned by the frame pool and is a producer of frames.
* To maintain parity with the original display interface, this frame will be consumed by the capture thread.
* Acquire a read-write lock, make the produced frame available to the capture thread, then wake the capture thread.
*/
void
wgc_capture_t::on_frame_arrived(winrt::Direct3D11CaptureFramePool const &sender, winrt::IInspectable const &) {
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame frame { nullptr };
try {
frame = sender.TryGetNextFrame();
}
catch (winrt::hresult_error &e) {
BOOST_LOG(warning) << "Failed to capture frame: "sv << e.code();
return;
}
if (frame != nullptr) {
AcquireSRWLockExclusive(&frame_lock);
if (produced_frame)
produced_frame.Close();
produced_frame = frame;
ReleaseSRWLockExclusive(&frame_lock);
WakeConditionVariable(&frame_present_cv);
}
}
/**
* @brief Get the next frame from the producer thread.
* If not available, the capture thread blocks until one is, or the wait times out.
* @param timeout how long to wait for the next frame
* @param out a texture containing the frame just captured
* @param out_time the timestamp of the frame just captured
*/
capture_e
wgc_capture_t::next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time) {
// this CONSUMER runs in the capture thread
release_frame();
AcquireSRWLockExclusive(&frame_lock);
if (produced_frame == nullptr && SleepConditionVariableSRW(&frame_present_cv, &frame_lock, timeout.count(), 0) == 0) {
ReleaseSRWLockExclusive(&frame_lock);
if (GetLastError() == ERROR_TIMEOUT)
return capture_e::timeout;
else
return capture_e::error;
}
if (produced_frame) {
consumed_frame = produced_frame;
produced_frame = nullptr;
}
ReleaseSRWLockExclusive(&frame_lock);
if (consumed_frame == nullptr) // spurious wakeup
return capture_e::timeout;
auto capture_access = consumed_frame.Surface().as<winrt::IDirect3DDxgiInterfaceAccess>();
if (capture_access == nullptr)
return capture_e::error;
capture_access->GetInterface(IID_ID3D11Texture2D, (void **) out);
out_time = consumed_frame.SystemRelativeTime().count(); // raw ticks from query performance counter
return capture_e::ok;
}
capture_e
wgc_capture_t::release_frame() {
if (consumed_frame != nullptr) {
consumed_frame.Close();
consumed_frame = nullptr;
}
return capture_e::ok;
}
int
wgc_capture_t::set_cursor_visible(bool x) {
try {
if (capture_session.IsCursorCaptureEnabled() != x)
capture_session.IsCursorCaptureEnabled(x);
return 0;
}
catch (winrt::hresult_error &) {
return -1;
}
}
int
display_wgc_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
if (display_base_t::init(config, display_name) || dup.init(this, config))
return -1;
texture.reset();
return 0;
}
/**
* @brief Get the next frame from the Windows.Graphics.Capture API and copy it into a new snapshot texture.
* @param pull_free_image_cb call this to get a new free image from the video subsystem.
* @param img_out the captured frame is returned here
* @param timeout how long to wait for the next frame
* @param cursor_visible whether to capture the cursor
*/
capture_e
display_wgc_ram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) {
HRESULT status;
texture2d_t src;
uint64_t frame_qpc;
dup.set_cursor_visible(cursor_visible);
auto capture_status = dup.next_frame(timeout, &src, frame_qpc);
if (capture_status != capture_e::ok)
return capture_status;
auto frame_timestamp = std::chrono::steady_clock::now() - qpc_time_difference(qpc_counter(), frame_qpc);
D3D11_TEXTURE2D_DESC desc;
src->GetDesc(&desc);
// Create the staging texture if it doesn't exist. It should match the source in size and format.
if (texture == nullptr) {
capture_format = desc.Format;
BOOST_LOG(info) << "Capture format ["sv << dxgi_format_to_string(capture_format) << ']';
D3D11_TEXTURE2D_DESC t {};
t.Width = width;
t.Height = height;
t.MipLevels = 1;
t.ArraySize = 1;
t.SampleDesc.Count = 1;
t.Usage = D3D11_USAGE_STAGING;
t.Format = capture_format;
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
auto status = device->CreateTexture2D(&t, nullptr, &texture);
if (FAILED(status)) {
BOOST_LOG(error) << "Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
}
// It's possible for our display enumeration to race with mode changes and result in
// mismatched image pool and desktop texture sizes. If this happens, just reinit again.
if (desc.Width != width || desc.Height != height) {
BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']';
return capture_e::reinit;
}
// It's also possible for the capture format to change on the fly. If that happens,
// reinitialize capture to try format detection again and create new images.
if (capture_format != desc.Format) {
BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']';
return capture_e::reinit;
}
// Copy from GPU to CPU
device_ctx->CopyResource(texture.get(), src.get());
if (!pull_free_image_cb(img_out)) {
return capture_e::interrupted;
}
auto img = (img_t *) img_out.get();
// Map the staging texture for CPU access (making it inaccessible for the GPU)
if (FAILED(status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info))) {
BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
// Now that we know the capture format, we can finish creating the image
if (complete_img(img, false)) {
device_ctx->Unmap(texture.get(), 0);
img_info.pData = nullptr;
return capture_e::error;
}
std::copy_n((std::uint8_t *) img_info.pData, height * img_info.RowPitch, (std::uint8_t *) img->data);
// Unmap the staging texture to allow GPU access again
device_ctx->Unmap(texture.get(), 0);
img_info.pData = nullptr;
if (img) {
img->frame_timestamp = frame_timestamp;
}
return capture_e::ok;
}
capture_e
display_wgc_ram_t::release_snapshot() {
return dup.release_frame();
}
} // namespace platf::dxgi
/**
* @file src/platform/windows/display_wgc.cpp
* @brief Definitions for WinRT Windows.Graphics.Capture API
*/
// platform includes
#include <dxgi1_2.h>
// local includes
#include "display.h"
#include "misc.h"
#include "src/logging.h"
// Gross hack to work around MINGW-packages#22160
#define ____FIReference_1_boolean_INTERFACE_DEFINED__
#include <windows.graphics.capture.interop.h>
#include <winrt/windows.foundation.h>
#include <winrt/windows.foundation.metadata.h>
#include <winrt/windows.graphics.directx.direct3d11.h>
namespace platf {
using namespace std::literals;
}
namespace winrt {
using namespace Windows::Foundation;
using namespace Windows::Foundation::Metadata;
using namespace Windows::Graphics::Capture;
using namespace Windows::Graphics::DirectX::Direct3D11;
extern "C" {
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice);
}
/**
* Windows structures sometimes have compile-time GUIDs. GCC supports this, but in a roundabout way.
* If WINRT_IMPL_HAS_DECLSPEC_UUID is true, then the compiler supports adding this attribute to a struct. For example, Visual Studio.
* If not, then MinGW GCC has a workaround to assign a GUID to a structure.
*/
struct
#if WINRT_IMPL_HAS_DECLSPEC_UUID
__declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"))
#endif
IDirect3DDxgiInterfaceAccess: ::IUnknown {
virtual HRESULT __stdcall GetInterface(REFIID id, void **object) = 0;
};
} // namespace winrt
#if !WINRT_IMPL_HAS_DECLSPEC_UUID
static constexpr GUID GUID__IDirect3DDxgiInterfaceAccess = {
0xA9B3D012,
0x3DF2,
0x4EE3,
{0xB8, 0xD1, 0x86, 0x95, 0xF4, 0x57, 0xD3, 0xC1}
// compare with __declspec(uuid(...)) for the struct above.
};
template<>
constexpr auto __mingw_uuidof<winrt::IDirect3DDxgiInterfaceAccess>() -> GUID const & {
return GUID__IDirect3DDxgiInterfaceAccess;
}
#endif
namespace platf::dxgi {
wgc_capture_t::wgc_capture_t() {
InitializeConditionVariable(&frame_present_cv);
}
wgc_capture_t::~wgc_capture_t() {
if (capture_session) {
capture_session.Close();
}
if (frame_pool) {
frame_pool.Close();
}
item = nullptr;
capture_session = nullptr;
frame_pool = nullptr;
}
/**
* @brief Initialize the Windows.Graphics.Capture backend.
* @return 0 on success, -1 on failure.
*/
int wgc_capture_t::init(display_base_t *display, const ::video::config_t &config) {
HRESULT status;
dxgi::dxgi_t dxgi;
winrt::com_ptr<::IInspectable> d3d_comhandle;
try {
if (!winrt::GraphicsCaptureSession::IsSupported()) {
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows!"sv;
return -1;
}
if (FAILED(status = display->device->QueryInterface(IID_IDXGIDevice, (void **) &dxgi))) {
BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
if (FAILED(status = winrt::CreateDirect3D11DeviceFromDXGIDevice(*&dxgi, d3d_comhandle.put()))) {
BOOST_LOG(error) << "Failed to query WinRT DirectX interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
} catch (winrt::hresult_error &e) {
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to acquire device: [0x"sv << util::hex(e.code()).to_string_view() << ']';
return -1;
}
DXGI_OUTPUT_DESC output_desc;
uwp_device = d3d_comhandle.as<winrt::IDirect3DDevice>();
display->output->GetDesc(&output_desc);
auto monitor_factory = winrt::get_activation_factory<winrt::GraphicsCaptureItem, IGraphicsCaptureItemInterop>();
if (monitor_factory == nullptr ||
FAILED(status = monitor_factory->CreateForMonitor(output_desc.Monitor, winrt::guid_of<winrt::IGraphicsCaptureItem>(), winrt::put_abi(item)))) {
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to acquire display: [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
if (config.dynamicRange) {
display->capture_format = DXGI_FORMAT_R16G16B16A16_FLOAT;
} else {
display->capture_format = DXGI_FORMAT_B8G8R8A8_UNORM;
}
try {
frame_pool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(uwp_device, static_cast<winrt::Windows::Graphics::DirectX::DirectXPixelFormat>(display->capture_format), 2, item.Size());
capture_session = frame_pool.CreateCaptureSession(item);
frame_pool.FrameArrived({this, &wgc_capture_t::on_frame_arrived});
} catch (winrt::hresult_error &e) {
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to create capture session: [0x"sv << util::hex(e.code()).to_string_view() << ']';
return -1;
}
try {
if (winrt::ApiInformation::IsPropertyPresent(L"Windows.Graphics.Capture.GraphicsCaptureSession", L"IsBorderRequired")) {
capture_session.IsBorderRequired(false);
} else {
BOOST_LOG(warning) << "Can't disable colored border around capture area on this version of Windows";
}
} catch (winrt::hresult_error &e) {
BOOST_LOG(warning) << "Screen capture may not be fully supported on this device for this release of Windows: failed to disable border around capture area: [0x"sv << util::hex(e.code()).to_string_view() << ']';
}
try {
capture_session.StartCapture();
} catch (winrt::hresult_error &e) {
BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to start capture: [0x"sv << util::hex(e.code()).to_string_view() << ']';
return -1;
}
return 0;
}
/**
* This function runs in a separate thread spawned by the frame pool and is a producer of frames.
* To maintain parity with the original display interface, this frame will be consumed by the capture thread.
* Acquire a read-write lock, make the produced frame available to the capture thread, then wake the capture thread.
*/
void wgc_capture_t::on_frame_arrived(winrt::Direct3D11CaptureFramePool const &sender, winrt::IInspectable const &) {
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame frame {nullptr};
try {
frame = sender.TryGetNextFrame();
} catch (winrt::hresult_error &e) {
BOOST_LOG(warning) << "Failed to capture frame: "sv << e.code();
return;
}
if (frame != nullptr) {
AcquireSRWLockExclusive(&frame_lock);
if (produced_frame) {
produced_frame.Close();
}
produced_frame = frame;
ReleaseSRWLockExclusive(&frame_lock);
WakeConditionVariable(&frame_present_cv);
}
}
/**
* @brief Get the next frame from the producer thread.
* If not available, the capture thread blocks until one is, or the wait times out.
* @param timeout how long to wait for the next frame
* @param out a texture containing the frame just captured
* @param out_time the timestamp of the frame just captured
*/
capture_e wgc_capture_t::next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time) {
// this CONSUMER runs in the capture thread
release_frame();
AcquireSRWLockExclusive(&frame_lock);
if (produced_frame == nullptr && SleepConditionVariableSRW(&frame_present_cv, &frame_lock, timeout.count(), 0) == 0) {
ReleaseSRWLockExclusive(&frame_lock);
if (GetLastError() == ERROR_TIMEOUT) {
return capture_e::timeout;
} else {
return capture_e::error;
}
}
if (produced_frame) {
consumed_frame = produced_frame;
produced_frame = nullptr;
}
ReleaseSRWLockExclusive(&frame_lock);
if (consumed_frame == nullptr) { // spurious wakeup
return capture_e::timeout;
}
auto capture_access = consumed_frame.Surface().as<winrt::IDirect3DDxgiInterfaceAccess>();
if (capture_access == nullptr) {
return capture_e::error;
}
capture_access->GetInterface(IID_ID3D11Texture2D, (void **) out);
out_time = consumed_frame.SystemRelativeTime().count(); // raw ticks from query performance counter
return capture_e::ok;
}
capture_e wgc_capture_t::release_frame() {
if (consumed_frame != nullptr) {
consumed_frame.Close();
consumed_frame = nullptr;
}
return capture_e::ok;
}
int wgc_capture_t::set_cursor_visible(bool x) {
try {
if (capture_session.IsCursorCaptureEnabled() != x) {
capture_session.IsCursorCaptureEnabled(x);
}
return 0;
} catch (winrt::hresult_error &) {
return -1;
}
}
int display_wgc_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
if (display_base_t::init(config, display_name) || dup.init(this, config)) {
return -1;
}
texture.reset();
return 0;
}
/**
* @brief Get the next frame from the Windows.Graphics.Capture API and copy it into a new snapshot texture.
* @param pull_free_image_cb call this to get a new free image from the video subsystem.
* @param img_out the captured frame is returned here
* @param timeout how long to wait for the next frame
* @param cursor_visible whether to capture the cursor
*/
capture_e display_wgc_ram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) {
HRESULT status;
texture2d_t src;
uint64_t frame_qpc;
dup.set_cursor_visible(cursor_visible);
auto capture_status = dup.next_frame(timeout, &src, frame_qpc);
if (capture_status != capture_e::ok) {
return capture_status;
}
auto frame_timestamp = std::chrono::steady_clock::now() - qpc_time_difference(qpc_counter(), frame_qpc);
D3D11_TEXTURE2D_DESC desc;
src->GetDesc(&desc);
// Create the staging texture if it doesn't exist. It should match the source in size and format.
if (texture == nullptr) {
capture_format = desc.Format;
BOOST_LOG(info) << "Capture format ["sv << dxgi_format_to_string(capture_format) << ']';
D3D11_TEXTURE2D_DESC t {};
t.Width = width;
t.Height = height;
t.MipLevels = 1;
t.ArraySize = 1;
t.SampleDesc.Count = 1;
t.Usage = D3D11_USAGE_STAGING;
t.Format = capture_format;
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
auto status = device->CreateTexture2D(&t, nullptr, &texture);
if (FAILED(status)) {
BOOST_LOG(error) << "Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
}
// It's possible for our display enumeration to race with mode changes and result in
// mismatched image pool and desktop texture sizes. If this happens, just reinit again.
if (desc.Width != width || desc.Height != height) {
BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']';
return capture_e::reinit;
}
// It's also possible for the capture format to change on the fly. If that happens,
// reinitialize capture to try format detection again and create new images.
if (capture_format != desc.Format) {
BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']';
return capture_e::reinit;
}
// Copy from GPU to CPU
device_ctx->CopyResource(texture.get(), src.get());
if (!pull_free_image_cb(img_out)) {
return capture_e::interrupted;
}
auto img = (img_t *) img_out.get();
// Map the staging texture for CPU access (making it inaccessible for the GPU)
if (FAILED(status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info))) {
BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
// Now that we know the capture format, we can finish creating the image
if (complete_img(img, false)) {
device_ctx->Unmap(texture.get(), 0);
img_info.pData = nullptr;
return capture_e::error;
}
std::copy_n((std::uint8_t *) img_info.pData, height * img_info.RowPitch, (std::uint8_t *) img->data);
// Unmap the staging texture to allow GPU access again
device_ctx->Unmap(texture.get(), 0);
img_info.pData = nullptr;
if (img) {
img->frame_timestamp = frame_timestamp;
}
return capture_e::ok;
}
capture_e display_wgc_ram_t::release_snapshot() {
return dup.release_frame();
}
} // namespace platf::dxgi

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,26 +21,19 @@ namespace nvprefs {
public:
~driver_settings_t();
bool
init();
bool init();
void
destroy();
void destroy();
bool
load_settings();
bool load_settings();
bool
save_settings();
bool save_settings();
bool
restore_global_profile_to_undo(const undo_data_t &undo_data);
bool restore_global_profile_to_undo(const undo_data_t &undo_data);
bool
check_and_modify_global_profile(std::optional<undo_data_t> &undo_data);
bool check_and_modify_global_profile(std::optional<undo_data_t> &undo_data);
bool
check_and_modify_application_profile(bool &modified);
bool check_and_modify_application_profile(bool &modified);
private:
NvDRSSessionHandle session_handle = 0;

View File

@@ -2,7 +2,7 @@
* @file src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp
* @brief Definitions for the NVAPI wrapper.
*/
// standard library headers
// standard includes
#include <map>
// local includes
@@ -17,9 +17,8 @@ namespace {
std::map<const char *, void *> interfaces;
HMODULE dll = NULL;
template <typename Func, typename... Args>
NvAPI_Status
call_interface(const char *name, Args... args) {
template<typename Func, typename... Args>
NvAPI_Status call_interface(const char *name, Args... args) {
auto func = (Func *) interfaces[name];
if (!func) {
@@ -38,7 +37,9 @@ extern void *__cdecl nvapi_QueryInterface(NvU32 id);
NVAPI_INTERFACE
NvAPI_Initialize() {
if (dll) return NVAPI_OK;
if (dll) {
return NVAPI_OK;
}
#ifdef _WIN64
auto dll_name = "nvapi64.dll";
@@ -59,8 +60,7 @@ NvAPI_Initialize() {
return NVAPI_LIBRARY_NOT_FOUND;
}
NVAPI_INTERFACE
NvAPI_Unload() {
NVAPI_INTERFACE NvAPI_Unload() {
if (dll) {
interfaces.clear();
FreeLibrary(dll);
@@ -69,69 +69,56 @@ NvAPI_Unload() {
return NVAPI_OK;
}
NVAPI_INTERFACE
NvAPI_GetErrorMessage(NvAPI_Status nr, NvAPI_ShortString szDesc) {
NVAPI_INTERFACE NvAPI_GetErrorMessage(NvAPI_Status nr, NvAPI_ShortString szDesc) {
return call_interface<decltype(NvAPI_GetErrorMessage)>("NvAPI_GetErrorMessage", nr, szDesc);
}
// This is only a subset of NvAPI_DRS_* functions, more can be added if needed
NVAPI_INTERFACE
NvAPI_DRS_CreateSession(NvDRSSessionHandle *phSession) {
NVAPI_INTERFACE NvAPI_DRS_CreateSession(NvDRSSessionHandle *phSession) {
return call_interface<decltype(NvAPI_DRS_CreateSession)>("NvAPI_DRS_CreateSession", phSession);
}
NVAPI_INTERFACE
NvAPI_DRS_DestroySession(NvDRSSessionHandle hSession) {
NVAPI_INTERFACE NvAPI_DRS_DestroySession(NvDRSSessionHandle hSession) {
return call_interface<decltype(NvAPI_DRS_DestroySession)>("NvAPI_DRS_DestroySession", hSession);
}
NVAPI_INTERFACE
NvAPI_DRS_LoadSettings(NvDRSSessionHandle hSession) {
NVAPI_INTERFACE NvAPI_DRS_LoadSettings(NvDRSSessionHandle hSession) {
return call_interface<decltype(NvAPI_DRS_LoadSettings)>("NvAPI_DRS_LoadSettings", hSession);
}
NVAPI_INTERFACE
NvAPI_DRS_SaveSettings(NvDRSSessionHandle hSession) {
NVAPI_INTERFACE NvAPI_DRS_SaveSettings(NvDRSSessionHandle hSession) {
return call_interface<decltype(NvAPI_DRS_SaveSettings)>("NvAPI_DRS_SaveSettings", hSession);
}
NVAPI_INTERFACE
NvAPI_DRS_CreateProfile(NvDRSSessionHandle hSession, NVDRS_PROFILE *pProfileInfo, NvDRSProfileHandle *phProfile) {
NVAPI_INTERFACE NvAPI_DRS_CreateProfile(NvDRSSessionHandle hSession, NVDRS_PROFILE *pProfileInfo, NvDRSProfileHandle *phProfile) {
return call_interface<decltype(NvAPI_DRS_CreateProfile)>("NvAPI_DRS_CreateProfile", hSession, pProfileInfo, phProfile);
}
NVAPI_INTERFACE
NvAPI_DRS_FindProfileByName(NvDRSSessionHandle hSession, NvAPI_UnicodeString profileName, NvDRSProfileHandle *phProfile) {
NVAPI_INTERFACE NvAPI_DRS_FindProfileByName(NvDRSSessionHandle hSession, NvAPI_UnicodeString profileName, NvDRSProfileHandle *phProfile) {
return call_interface<decltype(NvAPI_DRS_FindProfileByName)>("NvAPI_DRS_FindProfileByName", hSession, profileName, phProfile);
}
NVAPI_INTERFACE
NvAPI_DRS_CreateApplication(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NVDRS_APPLICATION *pApplication) {
NVAPI_INTERFACE NvAPI_DRS_CreateApplication(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NVDRS_APPLICATION *pApplication) {
return call_interface<decltype(NvAPI_DRS_CreateApplication)>("NvAPI_DRS_CreateApplication", hSession, hProfile, pApplication);
}
NVAPI_INTERFACE
NvAPI_DRS_GetApplicationInfo(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NvAPI_UnicodeString appName, NVDRS_APPLICATION *pApplication) {
NVAPI_INTERFACE NvAPI_DRS_GetApplicationInfo(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NvAPI_UnicodeString appName, NVDRS_APPLICATION *pApplication) {
return call_interface<decltype(NvAPI_DRS_GetApplicationInfo)>("NvAPI_DRS_GetApplicationInfo", hSession, hProfile, appName, pApplication);
}
NVAPI_INTERFACE
NvAPI_DRS_SetSetting(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NVDRS_SETTING *pSetting) {
NVAPI_INTERFACE NvAPI_DRS_SetSetting(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NVDRS_SETTING *pSetting) {
return call_interface<decltype(NvAPI_DRS_SetSetting)>("NvAPI_DRS_SetSetting", hSession, hProfile, pSetting);
}
NVAPI_INTERFACE
NvAPI_DRS_GetSetting(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NvU32 settingId, NVDRS_SETTING *pSetting) {
NVAPI_INTERFACE NvAPI_DRS_GetSetting(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NvU32 settingId, NVDRS_SETTING *pSetting) {
return call_interface<decltype(NvAPI_DRS_GetSetting)>("NvAPI_DRS_GetSetting", hSession, hProfile, settingId, pSetting);
}
NVAPI_INTERFACE
NvAPI_DRS_DeleteProfileSetting(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NvU32 settingId) {
NVAPI_INTERFACE NvAPI_DRS_DeleteProfileSetting(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NvU32 settingId) {
return call_interface<decltype(NvAPI_DRS_DeleteProfileSetting)>("NvAPI_DRS_DeleteProfileSetting", hSession, hProfile, settingId);
}
NVAPI_INTERFACE
NvAPI_DRS_GetBaseProfile(NvDRSSessionHandle hSession, NvDRSProfileHandle *phProfile) {
NVAPI_INTERFACE NvAPI_DRS_GetBaseProfile(NvDRSSessionHandle hSession, NvDRSProfileHandle *phProfile) {
return call_interface<decltype(NvAPI_DRS_GetBaseProfile)>("NvAPI_DRS_GetBaseProfile", hSession, phProfile);
}

View File

@@ -2,37 +2,32 @@
* @file src/platform/windows/nvprefs/nvprefs_common.cpp
* @brief Definitions for common nvidia preferences.
*/
// local includes
// this include
#include "nvprefs_common.h"
#include "src/logging.h"
// read user override preferences from global sunshine config
// local includes
#include "src/config.h"
#include "src/logging.h"
namespace nvprefs {
void
info_message(const std::wstring &message) {
void info_message(const std::wstring &message) {
BOOST_LOG(info) << "nvprefs: " << message;
}
void
info_message(const std::string &message) {
void info_message(const std::string &message) {
BOOST_LOG(info) << "nvprefs: " << message;
}
void
error_message(const std::wstring &message) {
void error_message(const std::wstring &message) {
BOOST_LOG(error) << "nvprefs: " << message;
}
void
error_message(const std::string &message) {
void error_message(const std::string &message) {
BOOST_LOG(error) << "nvprefs: " << message;
}
nvprefs_options
get_nvprefs_options() {
nvprefs_options get_nvprefs_options() {
nvprefs_options options;
options.opengl_vulkan_on_dxgi = config::video.nv_opengl_vulkan_on_dxgi;
options.sunshine_high_power_mode = config::video.nv_sunshine_high_power_mode;

View File

@@ -4,57 +4,51 @@
*/
#pragma once
// sunshine utility header for generic smart pointers
#include "src/utility.h"
// winapi headers
// platform includes
// disable clang-format header reordering
// clang-format off
#include <windows.h>
#include <aclapi.h>
// clang-format on
// local includes
#include "src/utility.h"
namespace nvprefs {
struct safe_handle: public util::safe_ptr_v2<void, BOOL, CloseHandle> {
using util::safe_ptr_v2<void, BOOL, CloseHandle>::safe_ptr_v2;
explicit
operator bool() const {
explicit operator bool() const {
auto handle = get();
return handle != NULL && handle != INVALID_HANDLE_VALUE;
}
};
struct safe_hlocal_deleter {
void
operator()(void *p) {
void operator()(void *p) {
LocalFree(p);
}
};
template <typename T>
template<typename T>
using safe_hlocal = util::uniq_ptr<std::remove_pointer_t<T>, safe_hlocal_deleter>;
using safe_sid = util::safe_ptr_v2<void, PVOID, FreeSid>;
void
info_message(const std::wstring &message);
void info_message(const std::wstring &message);
void
info_message(const std::string &message);
void info_message(const std::string &message);
void
error_message(const std::wstring &message);
void error_message(const std::wstring &message);
void
error_message(const std::string &message);
void error_message(const std::string &message);
struct nvprefs_options {
bool opengl_vulkan_on_dxgi = true;
bool sunshine_high_power_mode = true;
};
nvprefs_options
get_nvprefs_options();
nvprefs_options get_nvprefs_options();
} // namespace nvprefs

View File

@@ -39,8 +39,7 @@ namespace nvprefs {
unload();
}
bool
nvprefs_interface::load() {
bool nvprefs_interface::load() {
if (!pimpl->loaded) {
// Check %ProgramData% variable, need it for storing undo file
wchar_t program_data_env[MAX_PATH];
@@ -61,8 +60,7 @@ namespace nvprefs {
return pimpl->loaded;
}
void
nvprefs_interface::unload() {
void nvprefs_interface::unload() {
if (pimpl->loaded) {
// Unload dynamically loaded nvapi library
pimpl->driver_settings.destroy();
@@ -70,9 +68,10 @@ namespace nvprefs {
}
}
bool
nvprefs_interface::restore_from_and_delete_undo_file_if_exists() {
if (!pimpl->loaded) return false;
bool nvprefs_interface::restore_from_and_delete_undo_file_if_exists() {
if (!pimpl->loaded) {
return false;
}
// Check for undo file from previous improper termination
bool access_denied = false;
@@ -82,12 +81,10 @@ namespace nvprefs {
if (auto undo_data = undo_file->read_undo_data()) {
if (pimpl->driver_settings.restore_global_profile_to_undo(*undo_data) && pimpl->driver_settings.save_settings()) {
info_message("Restored global profile settings from undo file - deleting the file");
}
else {
} else {
error_message("Failed to restore global profile settings from undo file, deleting the file anyway");
}
}
else {
} else {
error_message("Coulnd't read undo file, deleting the file anyway");
}
@@ -95,8 +92,7 @@ namespace nvprefs {
error_message("Couldn't delete undo file");
return false;
}
}
else if (access_denied) {
} else if (access_denied) {
error_message("Couldn't open undo file from previous improper termination, or confirm that there's no such file");
return false;
}
@@ -104,43 +100,41 @@ namespace nvprefs {
return true;
}
bool
nvprefs_interface::modify_application_profile() {
if (!pimpl->loaded) return false;
bool nvprefs_interface::modify_application_profile() {
if (!pimpl->loaded) {
return false;
}
// Modify and save sunshine.exe application profile settings, if needed
bool modified = false;
if (!pimpl->driver_settings.check_and_modify_application_profile(modified)) {
error_message("Failed to modify application profile settings");
return false;
}
else if (modified) {
} else if (modified) {
if (pimpl->driver_settings.save_settings()) {
info_message("Modified application profile settings");
}
else {
} else {
error_message("Couldn't save application profile settings");
return false;
}
}
else {
} else {
info_message("No need to modify application profile settings");
}
return true;
}
bool
nvprefs_interface::modify_global_profile() {
if (!pimpl->loaded) return false;
bool nvprefs_interface::modify_global_profile() {
if (!pimpl->loaded) {
return false;
}
// Modify but not save global profile settings, if needed
std::optional<undo_data_t> undo_data;
if (!pimpl->driver_settings.check_and_modify_global_profile(undo_data)) {
error_message("Couldn't modify global profile settings");
return false;
}
else if (!undo_data) {
} else if (!undo_data) {
info_message("No need to modify global profile settings");
return true;
}
@@ -166,8 +160,7 @@ namespace nvprefs {
if (pimpl->undo_data) {
// Merge undo data if settings has been modified externally since our last modification
pimpl->undo_data->merge(*undo_data);
}
else {
} else {
pimpl->undo_data = undo_data;
}
@@ -198,14 +191,14 @@ namespace nvprefs {
return true;
}
bool
nvprefs_interface::owning_undo_file() {
bool nvprefs_interface::owning_undo_file() {
return pimpl->undo_file.has_value();
}
bool
nvprefs_interface::restore_global_profile() {
if (!pimpl->loaded || !pimpl->undo_data || !pimpl->undo_file) return false;
bool nvprefs_interface::restore_global_profile() {
if (!pimpl->loaded || !pimpl->undo_data || !pimpl->undo_file) {
return false;
}
// Restore global profile settings with undo data
if (pimpl->driver_settings.restore_global_profile_to_undo(*pimpl->undo_data) &&
@@ -217,8 +210,7 @@ namespace nvprefs {
}
pimpl->undo_data = std::nullopt;
pimpl->undo_file = std::nullopt;
}
else {
} else {
error_message("Couldn't restore global profile settings");
return false;
}

View File

@@ -4,7 +4,7 @@
*/
#pragma once
// standard library headers
// standard includes
#include <memory>
namespace nvprefs {
@@ -14,26 +14,19 @@ namespace nvprefs {
nvprefs_interface();
~nvprefs_interface();
bool
load();
bool load();
void
unload();
void unload();
bool
restore_from_and_delete_undo_file_if_exists();
bool restore_from_and_delete_undo_file_if_exists();
bool
modify_application_profile();
bool modify_application_profile();
bool
modify_global_profile();
bool modify_global_profile();
bool
owning_undo_file();
bool owning_undo_file();
bool
restore_global_profile();
bool restore_global_profile();
private:
struct impl;

View File

@@ -2,7 +2,7 @@
* @file src/platform/windows/nvprefs/undo_data.cpp
* @brief Definitions for undoing changes to nvidia preferences.
*/
// external includes
// lib includes
#include <nlohmann/json.hpp>
// local includes
@@ -17,54 +17,46 @@ namespace nlohmann {
using data_t = nvprefs::undo_data_t::data_t;
using opengl_swapchain_t = data_t::opengl_swapchain_t;
template <typename T>
template<typename T>
struct adl_serializer<std::optional<T>> {
static void
to_json(json &j, const std::optional<T> &opt) {
static void to_json(json &j, const std::optional<T> &opt) {
if (opt == std::nullopt) {
j = nullptr;
}
else {
} else {
j = *opt;
}
}
static void
from_json(const json &j, std::optional<T> &opt) {
static void from_json(const json &j, std::optional<T> &opt) {
if (j.is_null()) {
opt = std::nullopt;
}
else {
} else {
opt = j.template get<T>();
}
}
};
template <>
template<>
struct adl_serializer<data_t> {
static void
to_json(json &j, const data_t &data) {
j = json { { "opengl_swapchain", data.opengl_swapchain } };
static void to_json(json &j, const data_t &data) {
j = json {{"opengl_swapchain", data.opengl_swapchain}};
}
static void
from_json(const json &j, data_t &data) {
static void from_json(const json &j, data_t &data) {
j.at("opengl_swapchain").get_to(data.opengl_swapchain);
}
};
template <>
template<>
struct adl_serializer<opengl_swapchain_t> {
static void
to_json(json &j, const opengl_swapchain_t &opengl_swapchain) {
static void to_json(json &j, const opengl_swapchain_t &opengl_swapchain) {
j = json {
{ "our_value", opengl_swapchain.our_value },
{ "undo_value", opengl_swapchain.undo_value }
{"our_value", opengl_swapchain.our_value},
{"undo_value", opengl_swapchain.undo_value}
};
}
static void
from_json(const json &j, opengl_swapchain_t &opengl_swapchain) {
static void from_json(const json &j, opengl_swapchain_t &opengl_swapchain) {
j.at("our_value").get_to(opengl_swapchain.our_value);
j.at("undo_value").get_to(opengl_swapchain.undo_value);
}
@@ -73,46 +65,39 @@ namespace nlohmann {
namespace nvprefs {
void
undo_data_t::set_opengl_swapchain(uint32_t our_value, std::optional<uint32_t> undo_value) {
void undo_data_t::set_opengl_swapchain(uint32_t our_value, std::optional<uint32_t> undo_value) {
data.opengl_swapchain = data_t::opengl_swapchain_t {
our_value,
undo_value
};
}
std::optional<undo_data_t::data_t::opengl_swapchain_t>
undo_data_t::get_opengl_swapchain() const {
std::optional<undo_data_t::data_t::opengl_swapchain_t> undo_data_t::get_opengl_swapchain() const {
return data.opengl_swapchain;
}
std::string
undo_data_t::write() const {
std::string undo_data_t::write() const {
try {
// Keep this assignment otherwise data will be treated as an array due to
// initializer list shenanigangs.
const json json_data = data;
return json_data.dump();
}
catch (const std::exception &err) {
error_message(std::string { "failed to serialize json data" });
} catch (const std::exception &err) {
error_message(std::string {"failed to serialize json data"});
return {};
}
}
void
undo_data_t::read(const std::vector<char> &buffer) {
void undo_data_t::read(const std::vector<char> &buffer) {
try {
data = json::parse(std::begin(buffer), std::end(buffer));
}
catch (const std::exception &err) {
error_message(std::string { "failed to parse json data: " } + err.what());
} catch (const std::exception &err) {
error_message(std::string {"failed to parse json data: "} + err.what());
data = {};
}
}
void
undo_data_t::merge(const undo_data_t &newer_data) {
void undo_data_t::merge(const undo_data_t &newer_data) {
const auto &swapchain_data = newer_data.get_opengl_swapchain();
if (swapchain_data) {
set_opengl_swapchain(swapchain_data->our_value, swapchain_data->undo_value);

View File

@@ -4,7 +4,7 @@
*/
#pragma once
// standard library headers
// standard includes
#include <cstdint>
#include <optional>
#include <string>
@@ -23,20 +23,15 @@ namespace nvprefs {
std::optional<opengl_swapchain_t> opengl_swapchain;
};
void
set_opengl_swapchain(uint32_t our_value, std::optional<uint32_t> undo_value);
void set_opengl_swapchain(uint32_t our_value, std::optional<uint32_t> undo_value);
std::optional<data_t::opengl_swapchain_t>
get_opengl_swapchain() const;
std::optional<data_t::opengl_swapchain_t> get_opengl_swapchain() const;
std::string
write() const;
std::string write() const;
void
read(const std::vector<char> &buffer);
void read(const std::vector<char> &buffer);
void
merge(const undo_data_t &newer_data);
void merge(const undo_data_t &newer_data);
private:
data_t data;

View File

@@ -9,13 +9,14 @@ namespace {
using namespace nvprefs;
DWORD
relax_permissions(HANDLE file_handle) {
DWORD relax_permissions(HANDLE file_handle) {
PACL old_dacl = nullptr;
safe_hlocal<PSECURITY_DESCRIPTOR> sd;
DWORD status = GetSecurityInfo(file_handle, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &old_dacl, nullptr, &sd);
if (status != ERROR_SUCCESS) return status;
if (status != ERROR_SUCCESS) {
return status;
}
safe_sid users_sid;
SID_IDENTIFIER_AUTHORITY nt_authorithy = SECURITY_NT_AUTHORITY;
@@ -32,10 +33,14 @@ namespace {
safe_hlocal<PACL> new_dacl;
status = SetEntriesInAcl(1, &ea, old_dacl, &new_dacl);
if (status != ERROR_SUCCESS) return status;
if (status != ERROR_SUCCESS) {
return status;
}
status = SetSecurityInfo(file_handle, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, new_dacl.get(), nullptr);
if (status != ERROR_SUCCESS) return status;
if (status != ERROR_SUCCESS) {
return status;
}
return 0;
}
@@ -44,23 +49,20 @@ namespace {
namespace nvprefs {
std::optional<undo_file_t>
undo_file_t::open_existing_file(std::filesystem::path file_path, bool &access_denied) {
std::optional<undo_file_t> undo_file_t::open_existing_file(std::filesystem::path file_path, bool &access_denied) {
undo_file_t file;
file.file_handle.reset(CreateFileW(file_path.c_str(), GENERIC_READ | DELETE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL));
if (file.file_handle) {
access_denied = false;
return file;
}
else {
} else {
auto last_error = GetLastError();
access_denied = (last_error != ERROR_FILE_NOT_FOUND && last_error != ERROR_PATH_NOT_FOUND);
return std::nullopt;
}
}
std::optional<undo_file_t>
undo_file_t::create_new_file(std::filesystem::path file_path) {
std::optional<undo_file_t> undo_file_t::create_new_file(std::filesystem::path file_path) {
undo_file_t file;
file.file_handle.reset(CreateFileW(file_path.c_str(), GENERIC_WRITE | STANDARD_RIGHTS_ALL, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL));
@@ -70,35 +72,34 @@ namespace nvprefs {
error_message("Failed to relax permissions on undo file");
}
return file;
}
else {
} else {
return std::nullopt;
}
}
bool
undo_file_t::delete_file() {
if (!file_handle) return false;
bool undo_file_t::delete_file() {
if (!file_handle) {
return false;
}
FILE_DISPOSITION_INFO delete_file_info = { TRUE };
FILE_DISPOSITION_INFO delete_file_info = {TRUE};
if (SetFileInformationByHandle(file_handle.get(), FileDispositionInfo, &delete_file_info, sizeof(delete_file_info))) {
file_handle.reset();
return true;
}
else {
} else {
return false;
}
}
bool
undo_file_t::write_undo_data(const undo_data_t &undo_data) {
if (!file_handle) return false;
bool undo_file_t::write_undo_data(const undo_data_t &undo_data) {
if (!file_handle) {
return false;
}
std::string buffer;
try {
buffer = undo_data.write();
}
catch (...) {
} catch (...) {
error_message("Couldn't serialize undo data");
return false;
}
@@ -121,9 +122,10 @@ namespace nvprefs {
return true;
}
std::optional<undo_data_t>
undo_file_t::read_undo_data() {
if (!file_handle) return std::nullopt;
std::optional<undo_data_t> undo_file_t::read_undo_data() {
if (!file_handle) {
return std::nullopt;
}
LARGE_INTEGER file_size;
if (!GetFileSizeEx(file_handle.get(), &file_size)) {
@@ -146,8 +148,7 @@ namespace nvprefs {
undo_data_t undo_data;
try {
undo_data.read(buffer);
}
catch (...) {
} catch (...) {
error_message("Couldn't parse undo file");
return std::nullopt;
}

View File

@@ -4,7 +4,7 @@
*/
#pragma once
// standard library headers
// standard includes
#include <filesystem>
// local includes
@@ -15,20 +15,15 @@ namespace nvprefs {
class undo_file_t {
public:
static std::optional<undo_file_t>
open_existing_file(std::filesystem::path file_path, bool &access_denied);
static std::optional<undo_file_t> open_existing_file(std::filesystem::path file_path, bool &access_denied);
static std::optional<undo_file_t>
create_new_file(std::filesystem::path file_path);
static std::optional<undo_file_t> create_new_file(std::filesystem::path file_path);
bool
delete_file();
bool delete_file();
bool
write_undo_data(const undo_data_t &undo_data);
bool write_undo_data(const undo_data_t &undo_data);
std::optional<undo_data_t>
read_undo_data();
std::optional<undo_data_t> read_undo_data();
private:
undo_file_t() = default;

View File

@@ -2,13 +2,16 @@
* @file src/platform/windows/publish.cpp
* @brief Definitions for Windows mDNS service registration.
*/
// platform includes
// winsock2.h must be included before windows.h
// clang-format off
#include <winsock2.h>
#include <windows.h>
// clang-format on
#include <windns.h>
#include <winerror.h>
// local includes
#include "misc.h"
#include "src/config.h"
#include "src/logging.h"
@@ -17,7 +20,7 @@
#include "src/platform/common.h"
#include "src/thread_safe.h"
#define _FN(x, ret, args) \
#define _FN(x, ret, args) \
typedef ret(*x##_fn) args; \
static x##_fn x
@@ -28,69 +31,69 @@ using namespace std::literals;
extern "C" {
#ifndef __MINGW32__
constexpr auto DNS_REQUEST_PENDING = 9506L;
constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1;
constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1;
constexpr auto DNS_REQUEST_PENDING = 9506L;
constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1;
constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1;
#endif
#define SERVICE_DOMAIN "local"
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 {
LPWSTR pszInstanceName;
LPWSTR pszHostName;
typedef struct _DNS_SERVICE_INSTANCE {
LPWSTR pszInstanceName;
LPWSTR pszHostName;
IP4_ADDRESS *ip4Address;
IP6_ADDRESS *ip6Address;
IP4_ADDRESS *ip4Address;
IP6_ADDRESS *ip6Address;
WORD wPort;
WORD wPriority;
WORD wWeight;
WORD wPort;
WORD wPriority;
WORD wWeight;
// Property list
DWORD dwPropertyCount;
// Property list
DWORD dwPropertyCount;
PWSTR *keys;
PWSTR *values;
PWSTR *keys;
PWSTR *values;
DWORD dwInterfaceIndex;
} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE;
DWORD dwInterfaceIndex;
} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE;
#endif
typedef VOID WINAPI
DNS_SERVICE_REGISTER_COMPLETE(
_In_ DWORD Status,
_In_ PVOID pQueryContext,
_In_ PDNS_SERVICE_INSTANCE pInstance);
typedef VOID WINAPI
DNS_SERVICE_REGISTER_COMPLETE(
_In_ DWORD Status,
_In_ PVOID pQueryContext,
_In_ PDNS_SERVICE_INSTANCE pInstance
);
typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE;
typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE;
#ifndef __MINGW32__
typedef struct _DNS_SERVICE_CANCEL {
PVOID reserved;
} DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL;
typedef struct _DNS_SERVICE_CANCEL {
PVOID reserved;
} DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL;
typedef struct _DNS_SERVICE_REGISTER_REQUEST {
ULONG Version;
ULONG InterfaceIndex;
PDNS_SERVICE_INSTANCE pServiceInstance;
PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback;
PVOID pQueryContext;
HANDLE hCredentials;
BOOL unicastEnabled;
} DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST;
typedef struct _DNS_SERVICE_REGISTER_REQUEST {
ULONG Version;
ULONG InterfaceIndex;
PDNS_SERVICE_INSTANCE pServiceInstance;
PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback;
PVOID pQueryContext;
HANDLE hCredentials;
BOOL unicastEnabled;
} DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST;
#endif
_FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance));
_FN(_DnsServiceDeRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel));
_FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel));
_FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance));
_FN(_DnsServiceDeRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel));
_FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel));
} /* extern "C" */
namespace platf::publish {
VOID WINAPI
register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) {
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) {
@@ -100,11 +103,10 @@ namespace platf::publish {
alarm->ring(pInstance);
}
static int
service(bool enable, PDNS_SERVICE_INSTANCE &existing_instance) {
static int service(bool enable, PDNS_SERVICE_INSTANCE &existing_instance) {
auto alarm = safe::make_alarm<PDNS_SERVICE_INSTANCE>();
std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() };
std::wstring domain {SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size()};
auto hostname = platf::get_host_name();
auto name = from_utf8(net::mdns_instance_name(hostname) + '.') + domain;
@@ -124,8 +126,8 @@ namespace platf::publish {
// Most clients aren't strictly checking TXT record compliance with RFC 1035,
// but Apple's mDNS resolver does and rejects the entire answer if an invalid
// TXT record is present.
PWCHAR keys[] = { nullptr };
PWCHAR values[] = { nullptr };
PWCHAR keys[] = {nullptr};
PWCHAR values[] = {nullptr};
instance.dwPropertyCount = 1;
instance.keys = keys;
instance.values = values;
@@ -144,8 +146,7 @@ namespace platf::publish {
print_status("DnsServiceRegister()"sv, status);
return -1;
}
}
else {
} else {
status = _DnsServiceDeRegister(&req, nullptr);
if (status != DNS_REQUEST_PENDING) {
print_status("DnsServiceDeRegister()"sv, status);
@@ -159,8 +160,7 @@ namespace platf::publish {
if (enable) {
// Store this instance for later deregistration
existing_instance = registered_instance;
}
else if (registered_instance) {
} else if (registered_instance) {
// Deregistration was successful
_DnsServiceFreeInstance(registered_instance);
existing_instance = nullptr;
@@ -196,8 +196,7 @@ namespace platf::publish {
PDNS_SERVICE_INSTANCE existing_instance;
};
int
load_funcs(HMODULE handle) {
int load_funcs(HMODULE handle) {
auto fg = util::fail_guard([handle]() {
FreeLibrary(handle);
});
@@ -215,8 +214,7 @@ namespace platf::publish {
return 0;
}
std::unique_ptr<::platf::deinit_t>
start() {
std::unique_ptr<::platf::deinit_t> start() {
HMODULE handle = LoadLibrary("dnsapi.dll");
if (!handle || load_funcs(handle)) {