mirror of
https://github.com/thinkonmay/sunshine-sdk.git
synced 2026-08-13 04:29:29 +00:00
remove modules
This commit is contained in:
parent
9fc9790689
commit
95bc97d09b
@ -45,31 +45,16 @@ set(SUNSHINE_TARGET_FILES
|
||||
src/main.cpp
|
||||
src/main.h
|
||||
src/dll.h
|
||||
src/crypto.cpp
|
||||
src/crypto.h
|
||||
src/rtsp.cpp
|
||||
src/rtsp.h
|
||||
src/stream.cpp
|
||||
src/stream.h
|
||||
src/video.cpp
|
||||
src/video.h
|
||||
src/video_colorspace.cpp
|
||||
src/video_colorspace.h
|
||||
src/input.cpp
|
||||
src/input.h
|
||||
src/audio.cpp
|
||||
src/audio.h
|
||||
src/platform/common.h
|
||||
src/process.cpp
|
||||
src/process.h
|
||||
src/network.cpp
|
||||
src/network.h
|
||||
src/move_by_copy.h
|
||||
src/task_pool.h
|
||||
src/thread_pool.h
|
||||
src/thread_safe.h
|
||||
src/sync.h
|
||||
src/round_robin.h
|
||||
src/stat_trackers.h
|
||||
src/stat_trackers.cpp
|
||||
${PLATFORM_TARGET_FILES})
|
||||
|
||||
@ -193,7 +193,6 @@ if (${SUNSHINE_TRAY} EQUAL 0 AND SUNSHINE_REQUIRE_TRAY)
|
||||
endif()
|
||||
|
||||
list(APPEND PLATFORM_TARGET_FILES
|
||||
src/platform/linux/publish.cpp
|
||||
src/platform/linux/vaapi.h
|
||||
src/platform/linux/vaapi.cpp
|
||||
src/platform/linux/cuda.h
|
||||
@ -202,7 +201,6 @@ list(APPEND PLATFORM_TARGET_FILES
|
||||
src/platform/linux/misc.h
|
||||
src/platform/linux/misc.cpp
|
||||
src/platform/linux/audio.cpp
|
||||
src/platform/linux/input.cpp
|
||||
src/platform/linux/x11grab.h
|
||||
src/platform/linux/wayland.h
|
||||
third-party/glad/src/egl.c
|
||||
|
||||
@ -30,13 +30,11 @@ set(PLATFORM_TARGET_FILES
|
||||
src/platform/macos/av_video.h
|
||||
src/platform/macos/av_video.m
|
||||
src/platform/macos/display.mm
|
||||
src/platform/macos/input.cpp
|
||||
src/platform/macos/microphone.mm
|
||||
src/platform/macos/misc.mm
|
||||
src/platform/macos/misc.h
|
||||
src/platform/macos/nv12_zero_device.cpp
|
||||
src/platform/macos/nv12_zero_device.h
|
||||
src/platform/macos/publish.cpp
|
||||
third-party/TPCircularBuffer/TPCircularBuffer.c
|
||||
third-party/TPCircularBuffer/TPCircularBuffer.h
|
||||
${APPLE_PLIST_FILE})
|
||||
|
||||
@ -39,7 +39,6 @@ set(PLATFORM_TARGET_FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/windows.rc"
|
||||
src/platform/windows/misc.h
|
||||
src/platform/windows/misc.cpp
|
||||
src/platform/windows/input.cpp
|
||||
src/platform/windows/display.h
|
||||
src/platform/windows/display_base.cpp
|
||||
src/platform/windows/display_vram.cpp
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "main.h"
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include "platform/common.h"
|
||||
@ -379,26 +378,6 @@ namespace config {
|
||||
};
|
||||
|
||||
|
||||
input_t input {
|
||||
{
|
||||
{ 0x10, 0xA0 },
|
||||
{ 0x11, 0xA2 },
|
||||
{ 0x12, 0xA4 },
|
||||
},
|
||||
-1ms, // back_button_timeout
|
||||
500ms, // key_repeat_delay
|
||||
std::chrono::duration<double> { 1 / 24.9 }, // key_repeat_period
|
||||
|
||||
{
|
||||
platf::supported_gamepads().front().data(),
|
||||
platf::supported_gamepads().front().size(),
|
||||
}, // Default gamepad
|
||||
|
||||
true, // keyboard enabled
|
||||
true, // mouse enabled
|
||||
true, // controller enabled
|
||||
true, // always send scancodes
|
||||
};
|
||||
|
||||
sunshine_t sunshine {
|
||||
2, // min_log_level
|
||||
@ -948,44 +927,12 @@ namespace config {
|
||||
path_f(vars, "file_apps", stream.file_apps);
|
||||
int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 255 });
|
||||
|
||||
map_int_int_f(vars, "keybindings"s, input.keybindings);
|
||||
|
||||
// This config option will only be used by the UI
|
||||
// When editing in the config file itself, use "keybindings"
|
||||
bool map_rightalt_to_win = false;
|
||||
bool_f(vars, "key_rightalt_to_key_win", map_rightalt_to_win);
|
||||
|
||||
if (map_rightalt_to_win) {
|
||||
input.keybindings.emplace(0xA5, 0x5B);
|
||||
}
|
||||
|
||||
to = std::numeric_limits<int>::min();
|
||||
int_f(vars, "back_button_timeout", to);
|
||||
|
||||
if (to > std::numeric_limits<int>::min()) {
|
||||
input.back_button_timeout = std::chrono::milliseconds { to };
|
||||
}
|
||||
|
||||
double repeat_frequency { 0 };
|
||||
double_between_f(vars, "key_repeat_frequency", repeat_frequency, { 0, std::numeric_limits<double>::max() });
|
||||
|
||||
if (repeat_frequency > 0) {
|
||||
config::input.key_repeat_period = std::chrono::duration<double> { 1 / repeat_frequency };
|
||||
}
|
||||
|
||||
to = -1;
|
||||
int_f(vars, "key_repeat_delay", to);
|
||||
if (to >= 0) {
|
||||
input.key_repeat_delay = std::chrono::milliseconds { to };
|
||||
}
|
||||
|
||||
string_restricted_f(vars, "gamepad"s, input.gamepad, platf::supported_gamepads());
|
||||
|
||||
bool_f(vars, "mouse", input.mouse);
|
||||
bool_f(vars, "keyboard", input.keyboard);
|
||||
bool_f(vars, "controller", input.controller);
|
||||
|
||||
bool_f(vars, "always_send_scancodes", input.always_send_scancodes);
|
||||
|
||||
int port = sunshine.port;
|
||||
sunshine.port = (std::uint16_t) port;
|
||||
|
||||
15
src/config.h
15
src/config.h
@ -104,21 +104,7 @@ namespace config {
|
||||
std::vector<int> fps;
|
||||
};
|
||||
|
||||
struct input_t {
|
||||
std::unordered_map<int, int> keybindings;
|
||||
|
||||
std::chrono::milliseconds back_button_timeout;
|
||||
std::chrono::milliseconds key_repeat_delay;
|
||||
std::chrono::duration<double> key_repeat_period;
|
||||
|
||||
std::string gamepad;
|
||||
|
||||
bool keyboard;
|
||||
bool mouse;
|
||||
bool controller;
|
||||
|
||||
bool always_send_scancodes;
|
||||
};
|
||||
|
||||
namespace flag {
|
||||
enum flag_e : std::size_t {
|
||||
@ -169,6 +155,5 @@ namespace config {
|
||||
extern audio_t audio;
|
||||
extern stream_t stream;
|
||||
extern nvhttp_t nvhttp;
|
||||
extern input_t input;
|
||||
extern sunshine_t sunshine;
|
||||
} // namespace config
|
||||
|
||||
516
src/crypto.cpp
516
src/crypto.cpp
@ -1,516 +0,0 @@
|
||||
/**
|
||||
* @file src/crypto.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include "crypto.h"
|
||||
#include <openssl/pem.h>
|
||||
|
||||
namespace crypto {
|
||||
using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>;
|
||||
|
||||
cert_chain_t::cert_chain_t():
|
||||
_certs {}, _cert_ctx { X509_STORE_CTX_new() } {}
|
||||
void
|
||||
cert_chain_t::add(x509_t &&cert) {
|
||||
x509_store_t x509_store { X509_STORE_new() };
|
||||
|
||||
X509_STORE_add_cert(x509_store.get(), cert.get());
|
||||
_certs.emplace_back(std::make_pair(std::move(cert), std::move(x509_store)));
|
||||
}
|
||||
|
||||
static int
|
||||
openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
|
||||
int err_code = X509_STORE_CTX_get_error(ctx);
|
||||
|
||||
switch (err_code) {
|
||||
// Expired or not-yet-valid certificates are fine. Sometimes Moonlight is running on embedded devices
|
||||
// that don't have accurate clocks (or haven't yet synchronized by the time Moonlight first runs).
|
||||
// This behavior also matches what GeForce Experience does.
|
||||
// FIXME: Checking for X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY is a temporary workaround to get moonlight-embedded to work on the raspberry pi
|
||||
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
|
||||
case X509_V_ERR_CERT_NOT_YET_VALID:
|
||||
case X509_V_ERR_CERT_HAS_EXPIRED:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When certificates from two or more instances of Moonlight have been added to x509_store_t,
|
||||
* only one of them will be verified by X509_verify_cert, resulting in only a single instance of
|
||||
* Moonlight to be able to use Sunshine
|
||||
*
|
||||
* To circumvent this, x509_store_t instance will be created for each instance of the certificates.
|
||||
*/
|
||||
const char *
|
||||
cert_chain_t::verify(x509_t::element_type *cert) {
|
||||
int err_code = 0;
|
||||
for (auto &[_, x509_store] : _certs) {
|
||||
auto fg = util::fail_guard([this]() {
|
||||
X509_STORE_CTX_cleanup(_cert_ctx.get());
|
||||
});
|
||||
|
||||
X509_STORE_CTX_init(_cert_ctx.get(), x509_store.get(), cert, nullptr);
|
||||
X509_STORE_CTX_set_verify_cb(_cert_ctx.get(), openssl_verify_cb);
|
||||
|
||||
// We don't care to validate the entire chain for the purposes of client auth.
|
||||
// Some versions of clients forked from Moonlight Embedded produce client certs
|
||||
// that OpenSSL doesn't detect as self-signed due to some X509v3 extensions.
|
||||
X509_STORE_CTX_set_flags(_cert_ctx.get(), X509_V_FLAG_PARTIAL_CHAIN);
|
||||
|
||||
auto err = X509_verify_cert(_cert_ctx.get());
|
||||
|
||||
if (err == 1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
err_code = X509_STORE_CTX_get_error(_cert_ctx.get());
|
||||
|
||||
if (err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) {
|
||||
return X509_verify_cert_error_string(err_code);
|
||||
}
|
||||
}
|
||||
|
||||
return X509_verify_cert_error_string(err_code);
|
||||
}
|
||||
|
||||
namespace cipher {
|
||||
|
||||
static int
|
||||
init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
ctx.reset(EVP_CIPHER_CTX_new());
|
||||
|
||||
if (!ctx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
ctx.reset(EVP_CIPHER_CTX_new());
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
ctx.reset(EVP_CIPHER_CTX_new());
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_cbc(), nullptr, key->data(), iv->data()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
|
||||
if (!decrypt_ctx && init_decrypt_gcm(decrypt_ctx, &key, iv, padding)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calling with cipher == nullptr results in a parameter change
|
||||
// without requiring a reallocation of the internal cipher ctx.
|
||||
if (EVP_DecryptInit_ex(decrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cipher = tagged_cipher.substr(tag_size);
|
||||
auto tag = tagged_cipher.substr(0, tag_size);
|
||||
|
||||
plaintext.resize((cipher.size() + 15) / 16 * 16);
|
||||
|
||||
int size;
|
||||
if (EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *) cipher.data(), cipher.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(decrypt_ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.data())) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int len = size;
|
||||
if (EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data() + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
plaintext.resize(size + len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
|
||||
if (!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, iv, padding)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calling with cipher == nullptr results in a parameter change
|
||||
// without requiring a reallocation of the internal cipher ctx.
|
||||
if (EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto tag = tagged_cipher;
|
||||
auto cipher = tag + tag_size;
|
||||
|
||||
int len;
|
||||
int size = round_to_pkcs7_padded(plaintext.size());
|
||||
|
||||
// Encrypt into the caller's buffer
|
||||
if (EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *) plaintext.data(), plaintext.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// GCM encryption won't ever fill ciphertext here but we have to call it anyway
|
||||
if (EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(encrypt_ctx.get(), EVP_CTRL_GCM_GET_TAG, tag_size, tag) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return len + size;
|
||||
}
|
||||
|
||||
int
|
||||
ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
|
||||
int len;
|
||||
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(decrypt_ctx.get());
|
||||
});
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if (EVP_DecryptInit_ex(decrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(decrypt_ctx.get(), padding);
|
||||
|
||||
plaintext.resize((cipher.size() + 15) / 16 * 16);
|
||||
auto size = (int) plaintext.size();
|
||||
// Decrypt into the caller's buffer, leaving room for the auth tag to be prepended
|
||||
if (EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *) cipher.data(), cipher.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data(), &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
plaintext.resize(len + size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(encrypt_ctx.get());
|
||||
});
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if (EVP_EncryptInit_ex(encrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(encrypt_ctx.get(), padding);
|
||||
|
||||
int len;
|
||||
|
||||
cipher.resize((plaintext.size() + 15) / 16 * 16);
|
||||
auto size = (int) cipher.size();
|
||||
|
||||
// Encrypt into the caller's buffer
|
||||
if (EVP_EncryptUpdate(encrypt_ctx.get(), cipher.data(), &size, (const std::uint8_t *) plaintext.data(), plaintext.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher.data() + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cipher.resize(len + size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
|
||||
if (!encrypt_ctx && init_encrypt_cbc(encrypt_ctx, &key, iv, padding)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calling with cipher == nullptr results in a parameter change
|
||||
// without requiring a reallocation of the internal cipher ctx.
|
||||
if (EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int len;
|
||||
|
||||
int size = plaintext.size(); // round_to_pkcs7_padded(plaintext.size());
|
||||
|
||||
// Encrypt into the caller's buffer
|
||||
if (EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *) plaintext.data(), plaintext.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return size + len;
|
||||
}
|
||||
|
||||
ecb_t::ecb_t(const aes_t &key, bool padding):
|
||||
cipher_t { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding } {}
|
||||
|
||||
cbc_t::cbc_t(const aes_t &key, bool padding):
|
||||
cipher_t { nullptr, nullptr, key, padding } {}
|
||||
|
||||
gcm_t::gcm_t(const crypto::aes_t &key, bool padding):
|
||||
cipher_t { nullptr, nullptr, key, padding } {}
|
||||
|
||||
} // namespace cipher
|
||||
|
||||
aes_t
|
||||
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
|
||||
aes_t key;
|
||||
|
||||
std::string salt_pin;
|
||||
salt_pin.reserve(salt.size() + pin.size());
|
||||
|
||||
salt_pin.insert(std::end(salt_pin), std::begin(salt), std::end(salt));
|
||||
salt_pin.insert(std::end(salt_pin), std::begin(pin), std::end(pin));
|
||||
|
||||
auto hsh = hash(salt_pin);
|
||||
|
||||
std::copy(std::begin(hsh), std::begin(hsh) + key.size(), std::begin(key));
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
sha256_t
|
||||
hash(const std::string_view &plaintext) {
|
||||
sha256_t hsh;
|
||||
EVP_Digest(plaintext.data(), plaintext.size(), hsh.data(), nullptr, EVP_sha256(), nullptr);
|
||||
return hsh;
|
||||
}
|
||||
|
||||
x509_t
|
||||
x509(const std::string_view &x) {
|
||||
bio_t io { BIO_new(BIO_s_mem()) };
|
||||
|
||||
BIO_write(io.get(), x.data(), x.size());
|
||||
|
||||
x509_t p;
|
||||
PEM_read_bio_X509(io.get(), &p, nullptr, nullptr);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
pkey_t
|
||||
pkey(const std::string_view &k) {
|
||||
bio_t io { BIO_new(BIO_s_mem()) };
|
||||
|
||||
BIO_write(io.get(), k.data(), k.size());
|
||||
|
||||
pkey_t p = nullptr;
|
||||
PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
std::string
|
||||
pem(x509_t &x509) {
|
||||
bio_t bio { BIO_new(BIO_s_mem()) };
|
||||
|
||||
PEM_write_bio_X509(bio.get(), x509.get());
|
||||
BUF_MEM *mem_ptr;
|
||||
BIO_get_mem_ptr(bio.get(), &mem_ptr);
|
||||
|
||||
return { mem_ptr->data, mem_ptr->length };
|
||||
}
|
||||
|
||||
std::string
|
||||
pem(pkey_t &pkey) {
|
||||
bio_t bio { BIO_new(BIO_s_mem()) };
|
||||
|
||||
PEM_write_bio_PrivateKey(bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
|
||||
BUF_MEM *mem_ptr;
|
||||
BIO_get_mem_ptr(bio.get(), &mem_ptr);
|
||||
|
||||
return { mem_ptr->data, mem_ptr->length };
|
||||
}
|
||||
|
||||
std::string_view
|
||||
signature(const x509_t &x) {
|
||||
// X509_ALGOR *_ = nullptr;
|
||||
|
||||
const ASN1_BIT_STRING *asn1 = nullptr;
|
||||
X509_get0_signature(&asn1, nullptr, x.get());
|
||||
|
||||
return { (const char *) asn1->data, (std::size_t) asn1->length };
|
||||
}
|
||||
|
||||
std::string
|
||||
rand(std::size_t bytes) {
|
||||
std::string r;
|
||||
r.resize(bytes);
|
||||
|
||||
RAND_bytes((uint8_t *) r.data(), r.size());
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
std::vector<uint8_t>
|
||||
sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
|
||||
if (EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, (EVP_PKEY *) pkey.get()) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (EVP_DigestSignUpdate(ctx.get(), data.data(), data.size()) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t slen = digest_size;
|
||||
|
||||
std::vector<uint8_t> digest;
|
||||
digest.resize(slen);
|
||||
|
||||
if (EVP_DigestSignFinal(ctx.get(), digest.data(), &slen) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return digest;
|
||||
}
|
||||
|
||||
creds_t
|
||||
gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
|
||||
x509_t x509 { X509_new() };
|
||||
pkey_ctx_t ctx { EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr) };
|
||||
pkey_t pkey;
|
||||
|
||||
EVP_PKEY_keygen_init(ctx.get());
|
||||
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), key_bits);
|
||||
EVP_PKEY_keygen(ctx.get(), &pkey);
|
||||
|
||||
X509_set_version(x509.get(), 2);
|
||||
|
||||
// Generate a real serial number to avoid SEC_ERROR_REUSED_ISSUER_AND_SERIAL with Firefox
|
||||
bignum_t serial { BN_new() };
|
||||
BN_rand(serial.get(), 159, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); // 159 bits to fit in 20 bytes in DER format
|
||||
BN_set_negative(serial.get(), 0); // Serial numbers must be positive
|
||||
BN_to_ASN1_INTEGER(serial.get(), X509_get_serialNumber(x509.get()));
|
||||
|
||||
constexpr auto year = 60 * 60 * 24 * 365;
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(x509.get()), 20 * year);
|
||||
#else
|
||||
asn1_string_t not_before { ASN1_STRING_dup(X509_get0_notBefore(x509.get())) };
|
||||
asn1_string_t not_after { ASN1_STRING_dup(X509_get0_notAfter(x509.get())) };
|
||||
|
||||
X509_gmtime_adj(not_before.get(), 0);
|
||||
X509_gmtime_adj(not_after.get(), 20 * year);
|
||||
|
||||
X509_set1_notBefore(x509.get(), not_before.get());
|
||||
X509_set1_notAfter(x509.get(), not_after.get());
|
||||
#endif
|
||||
|
||||
X509_set_pubkey(x509.get(), pkey.get());
|
||||
|
||||
auto name = X509_get_subject_name(x509.get());
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
|
||||
(const std::uint8_t *) cn.data(), cn.size(),
|
||||
-1, 0);
|
||||
|
||||
X509_set_issuer_name(x509.get(), name);
|
||||
X509_sign(x509.get(), pkey.get(), EVP_sha256());
|
||||
|
||||
return { pem(x509), pem(pkey) };
|
||||
}
|
||||
|
||||
std::vector<uint8_t>
|
||||
sign256(const pkey_t &pkey, const std::string_view &data) {
|
||||
return sign(pkey, data, EVP_sha256());
|
||||
}
|
||||
|
||||
bool
|
||||
verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
|
||||
auto pkey = X509_get0_pubkey(x509.get());
|
||||
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
|
||||
if (EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EVP_DigestVerifyUpdate(ctx.get(), data.data(), data.size()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EVP_DigestVerifyFinal(ctx.get(), (const uint8_t *) signature.data(), signature.size()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
|
||||
return verify(x509, data, signature, EVP_sha256());
|
||||
}
|
||||
|
||||
void
|
||||
md_ctx_destroy(EVP_MD_CTX *ctx) {
|
||||
EVP_MD_CTX_destroy(ctx);
|
||||
}
|
||||
|
||||
std::string
|
||||
rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
|
||||
auto value = rand(bytes);
|
||||
|
||||
for (std::size_t i = 0; i != value.size(); ++i) {
|
||||
value[i] = alphabet[value[i] % alphabet.length()];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace crypto
|
||||
158
src/crypto.h
158
src/crypto.h
@ -1,158 +0,0 @@
|
||||
/**
|
||||
* @file src/crypto.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
namespace crypto {
|
||||
struct creds_t {
|
||||
std::string x509;
|
||||
std::string pkey;
|
||||
};
|
||||
constexpr std::size_t digest_size = 256;
|
||||
|
||||
void
|
||||
md_ctx_destroy(EVP_MD_CTX *);
|
||||
|
||||
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
|
||||
|
||||
using aes_t = std::array<std::uint8_t, 16>;
|
||||
using x509_t = util::safe_ptr<X509, X509_free>;
|
||||
using x509_store_t = util::safe_ptr<X509_STORE, X509_STORE_free>;
|
||||
using x509_store_ctx_t = util::safe_ptr<X509_STORE_CTX, X509_STORE_CTX_free>;
|
||||
using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
|
||||
using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>;
|
||||
using bio_t = util::safe_ptr<BIO, BIO_free_all>;
|
||||
using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>;
|
||||
using pkey_ctx_t = util::safe_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
|
||||
using bignum_t = util::safe_ptr<BIGNUM, BN_free>;
|
||||
|
||||
sha256_t
|
||||
hash(const std::string_view &plaintext);
|
||||
|
||||
aes_t
|
||||
gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
|
||||
|
||||
x509_t
|
||||
x509(const std::string_view &x);
|
||||
pkey_t
|
||||
pkey(const std::string_view &k);
|
||||
std::string
|
||||
pem(x509_t &x509);
|
||||
std::string
|
||||
pem(pkey_t &pkey);
|
||||
|
||||
std::vector<uint8_t>
|
||||
sign256(const pkey_t &pkey, const std::string_view &data);
|
||||
bool
|
||||
verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
|
||||
|
||||
creds_t
|
||||
gen_creds(const std::string_view &cn, std::uint32_t key_bits);
|
||||
|
||||
std::string_view
|
||||
signature(const x509_t &x);
|
||||
|
||||
std::string
|
||||
rand(std::size_t bytes);
|
||||
std::string
|
||||
rand_alphabet(std::size_t bytes,
|
||||
const std::string_view &alphabet = std::string_view { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-" });
|
||||
|
||||
class cert_chain_t {
|
||||
public:
|
||||
KITTY_DECL_CONSTR(cert_chain_t)
|
||||
|
||||
void
|
||||
add(x509_t &&cert);
|
||||
|
||||
const char *
|
||||
verify(x509_t::element_type *cert);
|
||||
|
||||
private:
|
||||
std::vector<std::pair<x509_t, x509_store_t>> _certs;
|
||||
x509_store_ctx_t _cert_ctx;
|
||||
};
|
||||
|
||||
namespace cipher {
|
||||
constexpr std::size_t tag_size = 16;
|
||||
constexpr std::size_t
|
||||
round_to_pkcs7_padded(std::size_t size) {
|
||||
return ((size + 15) / 16) * 16;
|
||||
}
|
||||
|
||||
class cipher_t {
|
||||
public:
|
||||
cipher_ctx_t decrypt_ctx;
|
||||
cipher_ctx_t encrypt_ctx;
|
||||
|
||||
aes_t key;
|
||||
|
||||
bool padding;
|
||||
};
|
||||
|
||||
class ecb_t: public cipher_t {
|
||||
public:
|
||||
ecb_t() = default;
|
||||
ecb_t(ecb_t &&) noexcept = default;
|
||||
ecb_t &
|
||||
operator=(ecb_t &&) noexcept = default;
|
||||
|
||||
ecb_t(const aes_t &key, bool padding = true);
|
||||
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
|
||||
int
|
||||
decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
|
||||
};
|
||||
|
||||
class gcm_t: public cipher_t {
|
||||
public:
|
||||
gcm_t() = default;
|
||||
gcm_t(gcm_t &&) noexcept = default;
|
||||
gcm_t &
|
||||
operator=(gcm_t &&) noexcept = default;
|
||||
|
||||
gcm_t(const crypto::aes_t &key, bool padding = true);
|
||||
|
||||
/**
|
||||
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size()) + crypto::cipher::tag_size
|
||||
*
|
||||
* return -1 on error
|
||||
* return bytes written on success
|
||||
*/
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
|
||||
|
||||
int
|
||||
decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
|
||||
};
|
||||
|
||||
class cbc_t: public cipher_t {
|
||||
public:
|
||||
cbc_t() = default;
|
||||
cbc_t(cbc_t &&) noexcept = default;
|
||||
cbc_t &
|
||||
operator=(cbc_t &&) noexcept = default;
|
||||
|
||||
cbc_t(const crypto::aes_t &key, bool padding = true);
|
||||
|
||||
/**
|
||||
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size())
|
||||
*
|
||||
* return -1 on error
|
||||
* return bytes written on success
|
||||
*/
|
||||
int
|
||||
encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
|
||||
};
|
||||
} // namespace cipher
|
||||
} // namespace crypto
|
||||
1647
src/input.cpp
1647
src/input.cpp
File diff suppressed because it is too large
Load Diff
36
src/input.h
36
src/input.h
@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @file src/input.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "platform/common.h"
|
||||
#include "thread_safe.h"
|
||||
|
||||
namespace input {
|
||||
struct input_t;
|
||||
|
||||
void
|
||||
print(void *input);
|
||||
void
|
||||
reset(std::shared_ptr<input_t> &input);
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
|
||||
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t>
|
||||
init();
|
||||
|
||||
std::shared_ptr<input_t>
|
||||
alloc(safe::mail_t mail);
|
||||
|
||||
struct touch_port_t: public platf::touch_port_t {
|
||||
int env_width, env_height;
|
||||
|
||||
// Offset x and y coordinates of the client
|
||||
float client_offsetX, client_offsetY;
|
||||
|
||||
float scalar_inv;
|
||||
};
|
||||
} // namespace input
|
||||
@ -22,10 +22,7 @@
|
||||
#include "main.h"
|
||||
#include "dll.h"
|
||||
#include "platform/common.h"
|
||||
#include "rtsp.h"
|
||||
#include "thread_pool.h"
|
||||
#include "video.h"
|
||||
#include "stream.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/log.h>
|
||||
@ -38,8 +35,6 @@ extern "C" {
|
||||
|
||||
safe::mail_t mail::man;
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using asio::ip::udp;
|
||||
using namespace std::literals;
|
||||
namespace bl = boost::log;
|
||||
|
||||
@ -48,7 +43,6 @@ namespace bl = boost::log;
|
||||
nvprefs::nvprefs_interface nvprefs_instance;
|
||||
#endif
|
||||
|
||||
thread_pool_util::ThreadPool task_pool;
|
||||
bl::sources::severity_logger<int> verbose(0); // Dominating output
|
||||
bl::sources::severity_logger<int> debug(1); // Follow what is happening
|
||||
bl::sources::severity_logger<int> info(2); // Should be informed about
|
||||
@ -389,7 +383,7 @@ PopFromQueue(void* data,int* duration)
|
||||
if(packet->frame_timestamp) {
|
||||
auto duration_to_latency = [](const std::chrono::steady_clock::duration &duration) {
|
||||
const auto duration_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count();
|
||||
return std::clamp<decltype(duration_ns)>((duration_ns + 50) / 100, 0, std::numeric_limits<uint>::max());
|
||||
return std::clamp<decltype(duration_ns)>((duration_ns + 50) / 100, 0, std::numeric_limits<int>::max());
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
#include <boost/log/common.hpp>
|
||||
|
||||
// local includes
|
||||
#include "thread_pool.h"
|
||||
#include "thread_safe.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -23,7 +22,6 @@
|
||||
extern nvprefs::nvprefs_interface nvprefs_instance;
|
||||
#endif
|
||||
|
||||
extern thread_pool_util::ThreadPool task_pool;
|
||||
extern bool display_cursor;
|
||||
|
||||
extern boost::log::sources::severity_logger<int> verbose;
|
||||
|
||||
198
src/network.cpp
198
src/network.cpp
@ -1,198 +0,0 @@
|
||||
/**
|
||||
* @file src/network.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include "network.h"
|
||||
#include "utility.h"
|
||||
#include <algorithm>
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace ip = boost::asio::ip;
|
||||
|
||||
namespace net {
|
||||
std::vector<ip::network_v4> pc_ips_v4 {
|
||||
ip::make_network_v4("127.0.0.0/8"sv),
|
||||
};
|
||||
std::vector<ip::network_v4> lan_ips_v4 {
|
||||
ip::make_network_v4("192.168.0.0/16"sv),
|
||||
ip::make_network_v4("172.16.0.0/12"sv),
|
||||
ip::make_network_v4("10.0.0.0/8"sv),
|
||||
ip::make_network_v4("100.64.0.0/10"sv),
|
||||
ip::make_network_v4("169.254.0.0/16"sv),
|
||||
};
|
||||
|
||||
std::vector<ip::network_v6> pc_ips_v6 {
|
||||
ip::make_network_v6("::1/128"sv),
|
||||
};
|
||||
std::vector<ip::network_v6> lan_ips_v6 {
|
||||
ip::make_network_v6("fc00::/7"sv),
|
||||
ip::make_network_v6("fe80::/64"sv),
|
||||
};
|
||||
|
||||
net_e
|
||||
from_enum_string(const std::string_view &view) {
|
||||
if (view == "wan") {
|
||||
return WAN;
|
||||
}
|
||||
if (view == "lan") {
|
||||
return LAN;
|
||||
}
|
||||
|
||||
return PC;
|
||||
}
|
||||
|
||||
net_e
|
||||
from_address(const std::string_view &view) {
|
||||
auto addr = ip::make_address(view);
|
||||
|
||||
if (addr.is_v6()) {
|
||||
for (auto &range : pc_ips_v6) {
|
||||
if (range.hosts().find(addr.to_v6()) != range.hosts().end()) {
|
||||
return PC;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &range : lan_ips_v6) {
|
||||
if (range.hosts().find(addr.to_v6()) != range.hosts().end()) {
|
||||
return LAN;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (auto &range : pc_ips_v4) {
|
||||
if (range.hosts().find(addr.to_v4()) != range.hosts().end()) {
|
||||
return PC;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &range : lan_ips_v4) {
|
||||
if (range.hosts().find(addr.to_v4()) != range.hosts().end()) {
|
||||
return LAN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return WAN;
|
||||
}
|
||||
|
||||
std::string_view
|
||||
to_enum_string(net_e net) {
|
||||
switch (net) {
|
||||
case PC:
|
||||
return "pc"sv;
|
||||
case LAN:
|
||||
return "lan"sv;
|
||||
case WAN:
|
||||
return "wan"sv;
|
||||
}
|
||||
|
||||
// avoid warning
|
||||
return "wan"sv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the `af_e` enum value for the `address_family` config option value.
|
||||
* @param view The config option value.
|
||||
* @return The `af_e` enum value.
|
||||
*/
|
||||
af_e
|
||||
af_from_enum_string(const std::string_view &view) {
|
||||
if (view == "ipv4") {
|
||||
return IPV4;
|
||||
}
|
||||
if (view == "both") {
|
||||
return BOTH;
|
||||
}
|
||||
|
||||
// avoid warning
|
||||
return BOTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the wildcard binding address for a given address family.
|
||||
* @param af Address family.
|
||||
* @return Normalized address.
|
||||
*/
|
||||
std::string_view
|
||||
af_to_any_address_string(af_e af) {
|
||||
switch (af) {
|
||||
case IPV4:
|
||||
return "0.0.0.0"sv;
|
||||
case BOTH:
|
||||
return "::"sv;
|
||||
}
|
||||
|
||||
// avoid warning
|
||||
return "::"sv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts an address to a normalized form.
|
||||
* @details Normalization converts IPv4-mapped IPv6 addresses into IPv4 addresses.
|
||||
* @param address The address to normalize.
|
||||
* @return Normalized address.
|
||||
*/
|
||||
boost::asio::ip::address
|
||||
normalize_address(boost::asio::ip::address address) {
|
||||
// Convert IPv6-mapped IPv4 addresses into regular IPv4 addresses
|
||||
if (address.is_v6()) {
|
||||
auto v6 = address.to_v6();
|
||||
if (v6.is_v4_mapped()) {
|
||||
return boost::asio::ip::make_address_v4(boost::asio::ip::v4_mapped, v6);
|
||||
}
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the given address in normalized string form.
|
||||
* @details Normalization converts IPv4-mapped IPv6 addresses into IPv4 addresses.
|
||||
* @param address The address to normalize.
|
||||
* @return Normalized address in string form.
|
||||
*/
|
||||
std::string
|
||||
addr_to_normalized_string(boost::asio::ip::address address) {
|
||||
return normalize_address(address).to_string();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the given address in a normalized form for in the host portion of a URL.
|
||||
* @details Normalization converts IPv4-mapped IPv6 addresses into IPv4 addresses.
|
||||
* @param address The address to normalize and escape.
|
||||
* @return Normalized address in URL-escaped string.
|
||||
*/
|
||||
std::string
|
||||
addr_to_url_escaped_string(boost::asio::ip::address address) {
|
||||
address = normalize_address(address);
|
||||
if (address.is_v6()) {
|
||||
return "["s + address.to_string() + ']';
|
||||
}
|
||||
else {
|
||||
return address.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
host_t
|
||||
host_create(af_e af, ENetAddress &addr, std::size_t peers, std::uint16_t port) {
|
||||
auto any_addr = net::af_to_any_address_string(af);
|
||||
enet_address_set_host(&addr, any_addr.data());
|
||||
enet_address_set_port(&addr, port);
|
||||
|
||||
return host_t { enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, peers, 0, 0, 0) };
|
||||
}
|
||||
|
||||
void
|
||||
free_host(ENetHost *host) {
|
||||
std::for_each(host->peers, host->peers + host->peerCount, [](ENetPeer &peer_ref) {
|
||||
ENetPeer *peer = &peer_ref;
|
||||
|
||||
if (peer) {
|
||||
enet_peer_disconnect_now(peer, 0);
|
||||
}
|
||||
});
|
||||
|
||||
enet_host_destroy(host);
|
||||
}
|
||||
} // namespace net
|
||||
@ -1,78 +0,0 @@
|
||||
/**
|
||||
* @file src/network.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include <enet/enet.h>
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
namespace net {
|
||||
void
|
||||
free_host(ENetHost *host);
|
||||
|
||||
using host_t = util::safe_ptr<ENetHost, free_host>;
|
||||
using peer_t = ENetPeer *;
|
||||
using packet_t = util::safe_ptr<ENetPacket, enet_packet_destroy>;
|
||||
|
||||
enum net_e : int {
|
||||
PC,
|
||||
LAN,
|
||||
WAN
|
||||
};
|
||||
|
||||
enum af_e : int {
|
||||
IPV4,
|
||||
BOTH
|
||||
};
|
||||
|
||||
net_e
|
||||
from_enum_string(const std::string_view &view);
|
||||
std::string_view
|
||||
to_enum_string(net_e net);
|
||||
|
||||
net_e
|
||||
from_address(const std::string_view &view);
|
||||
|
||||
host_t
|
||||
host_create(af_e af, ENetAddress &addr, std::size_t peers, std::uint16_t port);
|
||||
|
||||
/**
|
||||
* @brief Returns the `af_e` enum value for the `address_family` config option value.
|
||||
* @param view The config option value.
|
||||
* @return The `af_e` enum value.
|
||||
*/
|
||||
af_e
|
||||
af_from_enum_string(const std::string_view &view);
|
||||
|
||||
/**
|
||||
* @brief Returns the wildcard binding address for a given address family.
|
||||
* @param af Address family.
|
||||
* @return Normalized address.
|
||||
*/
|
||||
std::string_view
|
||||
af_to_any_address_string(af_e af);
|
||||
|
||||
/**
|
||||
* @brief Returns the given address in normalized string form.
|
||||
* @details Normalization converts IPv4-mapped IPv6 addresses into IPv4 addresses.
|
||||
* @param address The address to normalize.
|
||||
* @return Normalized address in string form.
|
||||
*/
|
||||
std::string
|
||||
addr_to_normalized_string(boost::asio::ip::address address);
|
||||
|
||||
/**
|
||||
* @brief Returns the given address in a normalized form for in the host portion of a URL.
|
||||
* @details Normalization converts IPv4-mapped IPv6 addresses into IPv4 addresses.
|
||||
* @param address The address to normalize and escape.
|
||||
* @return Normalized address in URL-escaped string.
|
||||
*/
|
||||
std::string
|
||||
addr_to_url_escaped_string(boost::asio::ip::address address);
|
||||
} // namespace net
|
||||
@ -526,7 +526,6 @@ namespace platf {
|
||||
void
|
||||
freeInput(void *);
|
||||
|
||||
using input_t = util::safe_ptr<void, freeInput>;
|
||||
|
||||
std::filesystem::path
|
||||
appdata();
|
||||
@ -618,34 +617,10 @@ namespace platf {
|
||||
void
|
||||
open_url(const std::string &url);
|
||||
|
||||
input_t
|
||||
input();
|
||||
void
|
||||
move_mouse(input_t &input, int deltaX, int deltaY);
|
||||
void
|
||||
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
|
||||
void
|
||||
button_mouse(input_t &input, int button, bool release);
|
||||
void
|
||||
scroll(input_t &input, int distance);
|
||||
void
|
||||
hscroll(input_t &input, int distance);
|
||||
void
|
||||
keyboard(input_t &input, uint16_t modcode, bool release, uint8_t flags);
|
||||
void
|
||||
gamepad(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;
|
||||
|
||||
/**
|
||||
* @brief Allocates a context to store per-client input data.
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @brief Sends a touch event to the OS.
|
||||
@ -665,42 +640,6 @@ namespace platf {
|
||||
void
|
||||
pen(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen);
|
||||
|
||||
/**
|
||||
* @brief Sends 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);
|
||||
|
||||
/**
|
||||
* @brief Sends 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);
|
||||
|
||||
/**
|
||||
* @brief Sends 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);
|
||||
|
||||
/**
|
||||
* @brief Creates a new virtual gamepad.
|
||||
* @param input The global input context.
|
||||
* @param id The gamepad ID.
|
||||
* @param metadata Controller metadata from client (empty if none provided).
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @brief Returns the supported platform capabilities to advertise to the client.
|
||||
@ -719,7 +658,4 @@ namespace platf {
|
||||
|
||||
[[nodiscard]] std::unique_ptr<deinit_t>
|
||||
init();
|
||||
|
||||
std::vector<std::string_view> &
|
||||
supported_gamepads();
|
||||
} // namespace platf
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,575 +0,0 @@
|
||||
/**
|
||||
* @file src/platform/macos/input.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#import <Carbon/Carbon.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
#include "src/main.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
// Delay for a double click
|
||||
// FIXME: we probably want to make this configurable
|
||||
#define MULTICLICK_DELAY_NS 500000000
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
|
||||
struct macos_input_t {
|
||||
public:
|
||||
CGDirectDisplayID display;
|
||||
CGFloat displayScaling;
|
||||
CGEventSourceRef source;
|
||||
|
||||
// keyboard related stuff
|
||||
CGEventRef kb_event;
|
||||
CGEventFlags kb_flags;
|
||||
|
||||
// mouse related stuff
|
||||
CGEventRef mouse_event; // mouse event source
|
||||
bool mouse_down[3]; // mouse button status
|
||||
uint64_t last_mouse_event[3][2]; // timestamp of last mouse events
|
||||
};
|
||||
|
||||
// A struct to hold a Windows keycode to Mac virtual keycode mapping.
|
||||
struct KeyCodeMap {
|
||||
int win_keycode;
|
||||
int mac_keycode;
|
||||
};
|
||||
|
||||
// Customized less operator for using std::lower_bound() on a KeyCodeMap array.
|
||||
bool
|
||||
operator<(const KeyCodeMap &a, const KeyCodeMap &b) {
|
||||
return a.win_keycode < b.win_keycode;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
const KeyCodeMap kKeyCodesMap[] = {
|
||||
{ 0x08 /* VKEY_BACK */, kVK_Delete },
|
||||
{ 0x09 /* VKEY_TAB */, kVK_Tab },
|
||||
{ 0x0A /* VKEY_BACKTAB */, 0x21E4 },
|
||||
{ 0x0C /* VKEY_CLEAR */, kVK_ANSI_KeypadClear },
|
||||
{ 0x0D /* VKEY_RETURN */, kVK_Return },
|
||||
{ 0x10 /* VKEY_SHIFT */, kVK_Shift },
|
||||
{ 0x11 /* VKEY_CONTROL */, kVK_Control },
|
||||
{ 0x12 /* VKEY_MENU */, kVK_Option },
|
||||
{ 0x13 /* VKEY_PAUSE */, -1 },
|
||||
{ 0x14 /* VKEY_CAPITAL */, kVK_CapsLock },
|
||||
{ 0x15 /* VKEY_KANA */, kVK_JIS_Kana },
|
||||
{ 0x15 /* VKEY_HANGUL */, -1 },
|
||||
{ 0x17 /* VKEY_JUNJA */, -1 },
|
||||
{ 0x18 /* VKEY_FINAL */, -1 },
|
||||
{ 0x19 /* VKEY_HANJA */, -1 },
|
||||
{ 0x19 /* VKEY_KANJI */, -1 },
|
||||
{ 0x1B /* VKEY_ESCAPE */, kVK_Escape },
|
||||
{ 0x1C /* VKEY_CONVERT */, -1 },
|
||||
{ 0x1D /* VKEY_NONCONVERT */, -1 },
|
||||
{ 0x1E /* VKEY_ACCEPT */, -1 },
|
||||
{ 0x1F /* VKEY_MODECHANGE */, -1 },
|
||||
{ 0x20 /* VKEY_SPACE */, kVK_Space },
|
||||
{ 0x21 /* VKEY_PRIOR */, kVK_PageUp },
|
||||
{ 0x22 /* VKEY_NEXT */, kVK_PageDown },
|
||||
{ 0x23 /* VKEY_END */, kVK_End },
|
||||
{ 0x24 /* VKEY_HOME */, kVK_Home },
|
||||
{ 0x25 /* VKEY_LEFT */, kVK_LeftArrow },
|
||||
{ 0x26 /* VKEY_UP */, kVK_UpArrow },
|
||||
{ 0x27 /* VKEY_RIGHT */, kVK_RightArrow },
|
||||
{ 0x28 /* VKEY_DOWN */, kVK_DownArrow },
|
||||
{ 0x29 /* VKEY_SELECT */, -1 },
|
||||
{ 0x2A /* VKEY_PRINT */, -1 },
|
||||
{ 0x2B /* VKEY_EXECUTE */, -1 },
|
||||
{ 0x2C /* VKEY_SNAPSHOT */, -1 },
|
||||
{ 0x2D /* VKEY_INSERT */, kVK_Help },
|
||||
{ 0x2E /* VKEY_DELETE */, kVK_ForwardDelete },
|
||||
{ 0x2F /* VKEY_HELP */, kVK_Help },
|
||||
{ 0x30 /* VKEY_0 */, kVK_ANSI_0 },
|
||||
{ 0x31 /* VKEY_1 */, kVK_ANSI_1 },
|
||||
{ 0x32 /* VKEY_2 */, kVK_ANSI_2 },
|
||||
{ 0x33 /* VKEY_3 */, kVK_ANSI_3 },
|
||||
{ 0x34 /* VKEY_4 */, kVK_ANSI_4 },
|
||||
{ 0x35 /* VKEY_5 */, kVK_ANSI_5 },
|
||||
{ 0x36 /* VKEY_6 */, kVK_ANSI_6 },
|
||||
{ 0x37 /* VKEY_7 */, kVK_ANSI_7 },
|
||||
{ 0x38 /* VKEY_8 */, kVK_ANSI_8 },
|
||||
{ 0x39 /* VKEY_9 */, kVK_ANSI_9 },
|
||||
{ 0x41 /* VKEY_A */, kVK_ANSI_A },
|
||||
{ 0x42 /* VKEY_B */, kVK_ANSI_B },
|
||||
{ 0x43 /* VKEY_C */, kVK_ANSI_C },
|
||||
{ 0x44 /* VKEY_D */, kVK_ANSI_D },
|
||||
{ 0x45 /* VKEY_E */, kVK_ANSI_E },
|
||||
{ 0x46 /* VKEY_F */, kVK_ANSI_F },
|
||||
{ 0x47 /* VKEY_G */, kVK_ANSI_G },
|
||||
{ 0x48 /* VKEY_H */, kVK_ANSI_H },
|
||||
{ 0x49 /* VKEY_I */, kVK_ANSI_I },
|
||||
{ 0x4A /* VKEY_J */, kVK_ANSI_J },
|
||||
{ 0x4B /* VKEY_K */, kVK_ANSI_K },
|
||||
{ 0x4C /* VKEY_L */, kVK_ANSI_L },
|
||||
{ 0x4D /* VKEY_M */, kVK_ANSI_M },
|
||||
{ 0x4E /* VKEY_N */, kVK_ANSI_N },
|
||||
{ 0x4F /* VKEY_O */, kVK_ANSI_O },
|
||||
{ 0x50 /* VKEY_P */, kVK_ANSI_P },
|
||||
{ 0x51 /* VKEY_Q */, kVK_ANSI_Q },
|
||||
{ 0x52 /* VKEY_R */, kVK_ANSI_R },
|
||||
{ 0x53 /* VKEY_S */, kVK_ANSI_S },
|
||||
{ 0x54 /* VKEY_T */, kVK_ANSI_T },
|
||||
{ 0x55 /* VKEY_U */, kVK_ANSI_U },
|
||||
{ 0x56 /* VKEY_V */, kVK_ANSI_V },
|
||||
{ 0x57 /* VKEY_W */, kVK_ANSI_W },
|
||||
{ 0x58 /* VKEY_X */, kVK_ANSI_X },
|
||||
{ 0x59 /* VKEY_Y */, kVK_ANSI_Y },
|
||||
{ 0x5A /* VKEY_Z */, kVK_ANSI_Z },
|
||||
{ 0x5B /* VKEY_LWIN */, kVK_Command },
|
||||
{ 0x5C /* VKEY_RWIN */, kVK_RightCommand },
|
||||
{ 0x5D /* VKEY_APPS */, kVK_RightCommand },
|
||||
{ 0x5F /* VKEY_SLEEP */, -1 },
|
||||
{ 0x60 /* VKEY_NUMPAD0 */, kVK_ANSI_Keypad0 },
|
||||
{ 0x61 /* VKEY_NUMPAD1 */, kVK_ANSI_Keypad1 },
|
||||
{ 0x62 /* VKEY_NUMPAD2 */, kVK_ANSI_Keypad2 },
|
||||
{ 0x63 /* VKEY_NUMPAD3 */, kVK_ANSI_Keypad3 },
|
||||
{ 0x64 /* VKEY_NUMPAD4 */, kVK_ANSI_Keypad4 },
|
||||
{ 0x65 /* VKEY_NUMPAD5 */, kVK_ANSI_Keypad5 },
|
||||
{ 0x66 /* VKEY_NUMPAD6 */, kVK_ANSI_Keypad6 },
|
||||
{ 0x67 /* VKEY_NUMPAD7 */, kVK_ANSI_Keypad7 },
|
||||
{ 0x68 /* VKEY_NUMPAD8 */, kVK_ANSI_Keypad8 },
|
||||
{ 0x69 /* VKEY_NUMPAD9 */, kVK_ANSI_Keypad9 },
|
||||
{ 0x6A /* VKEY_MULTIPLY */, kVK_ANSI_KeypadMultiply },
|
||||
{ 0x6B /* VKEY_ADD */, kVK_ANSI_KeypadPlus },
|
||||
{ 0x6C /* VKEY_SEPARATOR */, -1 },
|
||||
{ 0x6D /* VKEY_SUBTRACT */, kVK_ANSI_KeypadMinus },
|
||||
{ 0x6E /* VKEY_DECIMAL */, kVK_ANSI_KeypadDecimal },
|
||||
{ 0x6F /* VKEY_DIVIDE */, kVK_ANSI_KeypadDivide },
|
||||
{ 0x70 /* VKEY_F1 */, kVK_F1 },
|
||||
{ 0x71 /* VKEY_F2 */, kVK_F2 },
|
||||
{ 0x72 /* VKEY_F3 */, kVK_F3 },
|
||||
{ 0x73 /* VKEY_F4 */, kVK_F4 },
|
||||
{ 0x74 /* VKEY_F5 */, kVK_F5 },
|
||||
{ 0x75 /* VKEY_F6 */, kVK_F6 },
|
||||
{ 0x76 /* VKEY_F7 */, kVK_F7 },
|
||||
{ 0x77 /* VKEY_F8 */, kVK_F8 },
|
||||
{ 0x78 /* VKEY_F9 */, kVK_F9 },
|
||||
{ 0x79 /* VKEY_F10 */, kVK_F10 },
|
||||
{ 0x7A /* VKEY_F11 */, kVK_F11 },
|
||||
{ 0x7B /* VKEY_F12 */, kVK_F12 },
|
||||
{ 0x7C /* VKEY_F13 */, kVK_F13 },
|
||||
{ 0x7D /* VKEY_F14 */, kVK_F14 },
|
||||
{ 0x7E /* VKEY_F15 */, kVK_F15 },
|
||||
{ 0x7F /* VKEY_F16 */, kVK_F16 },
|
||||
{ 0x80 /* VKEY_F17 */, kVK_F17 },
|
||||
{ 0x81 /* VKEY_F18 */, kVK_F18 },
|
||||
{ 0x82 /* VKEY_F19 */, kVK_F19 },
|
||||
{ 0x83 /* VKEY_F20 */, kVK_F20 },
|
||||
{ 0x84 /* VKEY_F21 */, -1 },
|
||||
{ 0x85 /* VKEY_F22 */, -1 },
|
||||
{ 0x86 /* VKEY_F23 */, -1 },
|
||||
{ 0x87 /* VKEY_F24 */, -1 },
|
||||
{ 0x90 /* VKEY_NUMLOCK */, -1 },
|
||||
{ 0x91 /* VKEY_SCROLL */, -1 },
|
||||
{ 0xA0 /* VKEY_LSHIFT */, kVK_Shift },
|
||||
{ 0xA1 /* VKEY_RSHIFT */, kVK_RightShift },
|
||||
{ 0xA2 /* VKEY_LCONTROL */, kVK_Control },
|
||||
{ 0xA3 /* VKEY_RCONTROL */, kVK_RightControl },
|
||||
{ 0xA4 /* VKEY_LMENU */, kVK_Option },
|
||||
{ 0xA5 /* VKEY_RMENU */, kVK_RightOption },
|
||||
{ 0xA6 /* VKEY_BROWSER_BACK */, -1 },
|
||||
{ 0xA7 /* VKEY_BROWSER_FORWARD */, -1 },
|
||||
{ 0xA8 /* VKEY_BROWSER_REFRESH */, -1 },
|
||||
{ 0xA9 /* VKEY_BROWSER_STOP */, -1 },
|
||||
{ 0xAA /* VKEY_BROWSER_SEARCH */, -1 },
|
||||
{ 0xAB /* VKEY_BROWSER_FAVORITES */, -1 },
|
||||
{ 0xAC /* VKEY_BROWSER_HOME */, -1 },
|
||||
{ 0xAD /* VKEY_VOLUME_MUTE */, -1 },
|
||||
{ 0xAE /* VKEY_VOLUME_DOWN */, -1 },
|
||||
{ 0xAF /* VKEY_VOLUME_UP */, -1 },
|
||||
{ 0xB0 /* VKEY_MEDIA_NEXT_TRACK */, -1 },
|
||||
{ 0xB1 /* VKEY_MEDIA_PREV_TRACK */, -1 },
|
||||
{ 0xB2 /* VKEY_MEDIA_STOP */, -1 },
|
||||
{ 0xB3 /* VKEY_MEDIA_PLAY_PAUSE */, -1 },
|
||||
{ 0xB4 /* VKEY_MEDIA_LAUNCH_MAIL */, -1 },
|
||||
{ 0xB5 /* VKEY_MEDIA_LAUNCH_MEDIA_SELECT */, -1 },
|
||||
{ 0xB6 /* VKEY_MEDIA_LAUNCH_APP1 */, -1 },
|
||||
{ 0xB7 /* VKEY_MEDIA_LAUNCH_APP2 */, -1 },
|
||||
{ 0xBA /* VKEY_OEM_1 */, kVK_ANSI_Semicolon },
|
||||
{ 0xBB /* VKEY_OEM_PLUS */, kVK_ANSI_Equal },
|
||||
{ 0xBC /* VKEY_OEM_COMMA */, kVK_ANSI_Comma },
|
||||
{ 0xBD /* VKEY_OEM_MINUS */, kVK_ANSI_Minus },
|
||||
{ 0xBE /* VKEY_OEM_PERIOD */, kVK_ANSI_Period },
|
||||
{ 0xBF /* VKEY_OEM_2 */, kVK_ANSI_Slash },
|
||||
{ 0xC0 /* VKEY_OEM_3 */, kVK_ANSI_Grave },
|
||||
{ 0xDB /* VKEY_OEM_4 */, kVK_ANSI_LeftBracket },
|
||||
{ 0xDC /* VKEY_OEM_5 */, kVK_ANSI_Backslash },
|
||||
{ 0xDD /* VKEY_OEM_6 */, kVK_ANSI_RightBracket },
|
||||
{ 0xDE /* VKEY_OEM_7 */, kVK_ANSI_Quote },
|
||||
{ 0xDF /* VKEY_OEM_8 */, -1 },
|
||||
{ 0xE2 /* VKEY_OEM_102 */, -1 },
|
||||
{ 0xE5 /* VKEY_PROCESSKEY */, -1 },
|
||||
{ 0xE7 /* VKEY_PACKET */, -1 },
|
||||
{ 0xF6 /* VKEY_ATTN */, -1 },
|
||||
{ 0xF7 /* VKEY_CRSEL */, -1 },
|
||||
{ 0xF8 /* VKEY_EXSEL */, -1 },
|
||||
{ 0xF9 /* VKEY_EREOF */, -1 },
|
||||
{ 0xFA /* VKEY_PLAY */, -1 },
|
||||
{ 0xFB /* VKEY_ZOOM */, -1 },
|
||||
{ 0xFC /* VKEY_NONAME */, -1 },
|
||||
{ 0xFD /* VKEY_PA1 */, -1 },
|
||||
{ 0xFE /* VKEY_OEM_CLEAR */, kVK_ANSI_KeypadClear }
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
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);
|
||||
|
||||
if (temp_map >= kKeyCodesMap + sizeof(kKeyCodesMap) / sizeof(kKeyCodesMap[0]) ||
|
||||
temp_map->win_keycode != keycode || temp_map->mac_keycode == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return temp_map->mac_keycode;
|
||||
}
|
||||
|
||||
void
|
||||
keyboard(input_t &input, uint16_t modcode, bool release, 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;
|
||||
|
||||
if (key < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto macos_input = ((macos_input_t *) input.get());
|
||||
auto event = macos_input->kb_event;
|
||||
|
||||
if (key == kVK_Shift || key == kVK_RightShift ||
|
||||
key == kVK_Command || key == kVK_RightCommand ||
|
||||
key == kVK_Option || key == kVK_RightOption ||
|
||||
key == kVK_Control || key == kVK_RightControl) {
|
||||
CGEventFlags mask;
|
||||
|
||||
switch (key) {
|
||||
case kVK_Shift:
|
||||
case kVK_RightShift:
|
||||
mask = kCGEventFlagMaskShift;
|
||||
break;
|
||||
case kVK_Command:
|
||||
case kVK_RightCommand:
|
||||
mask = kCGEventFlagMaskCommand;
|
||||
break;
|
||||
case kVK_Option:
|
||||
case kVK_RightOption:
|
||||
mask = kCGEventFlagMaskAlternate;
|
||||
break;
|
||||
case kVK_Control:
|
||||
case kVK_RightControl:
|
||||
mask = kCGEventFlagMaskControl;
|
||||
break;
|
||||
}
|
||||
|
||||
macos_input->kb_flags = release ? macos_input->kb_flags & ~mask : macos_input->kb_flags | mask;
|
||||
CGEventSetType(event, kCGEventFlagsChanged);
|
||||
CGEventSetFlags(event, macos_input->kb_flags);
|
||||
}
|
||||
else {
|
||||
CGEventSetIntegerValueField(event, kCGKeyboardEventKeycode, key);
|
||||
CGEventSetType(event, release ? kCGEventKeyUp : kCGEventKeyDown);
|
||||
}
|
||||
|
||||
CGEventPost(kCGHIDEventTap, event);
|
||||
}
|
||||
|
||||
void
|
||||
unicode(input_t &input, char *utf8, int size) {
|
||||
BOOST_LOG(info) << "unicode: Unicode input not yet implemented for MacOS."sv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new virtual gamepad.
|
||||
* @param input The input context.
|
||||
* @param id The gamepad ID.
|
||||
* @param metadata Controller metadata from client (empty if none provided).
|
||||
* @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) {
|
||||
BOOST_LOG(info) << "alloc_gamepad: Gamepad not yet implemented for MacOS."sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
free_gamepad(input_t &input, int nr) {
|
||||
BOOST_LOG(info) << "free_gamepad: Gamepad not yet implemented for MacOS."sv;
|
||||
}
|
||||
|
||||
void
|
||||
gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
|
||||
BOOST_LOG(info) << "gamepad: Gamepad not yet implemented for MacOS."sv;
|
||||
}
|
||||
|
||||
// returns current mouse location:
|
||||
inline CGPoint
|
||||
get_mouse_loc(input_t &input) {
|
||||
return CGEventGetLocation(((macos_input_t *) input.get())->mouse_event);
|
||||
}
|
||||
|
||||
void
|
||||
post_mouse(input_t &input, CGMouseButton button, CGEventType type, CGPoint location, int click_count) {
|
||||
BOOST_LOG(debug) << "mouse_event: "sv << button << ", type: "sv << type << ", location:"sv << location.x << ":"sv << location.y << " click_count: "sv << click_count;
|
||||
|
||||
auto macos_input = (macos_input_t *) input.get();
|
||||
auto display = macos_input->display;
|
||||
auto event = macos_input->mouse_event;
|
||||
|
||||
if (location.x < 0)
|
||||
location.x = 0;
|
||||
if (location.x >= CGDisplayPixelsWide(display))
|
||||
location.x = CGDisplayPixelsWide(display) - 1;
|
||||
|
||||
if (location.y < 0)
|
||||
location.y = 0;
|
||||
if (location.y >= CGDisplayPixelsHigh(display))
|
||||
location.y = CGDisplayPixelsHigh(display) - 1;
|
||||
|
||||
CGEventSetType(event, type);
|
||||
CGEventSetLocation(event, location);
|
||||
CGEventSetIntegerValueField(event, kCGMouseEventButtonNumber, button);
|
||||
CGEventSetIntegerValueField(event, kCGMouseEventClickState, click_count);
|
||||
|
||||
CGEventPost(kCGHIDEventTap, event);
|
||||
|
||||
// For why this is here, see:
|
||||
// https://stackoverflow.com/questions/15194409/simulated-mouseevent-not-working-properly-osx
|
||||
CGWarpMouseCursorPosition(location);
|
||||
}
|
||||
|
||||
inline CGEventType
|
||||
event_type_mouse(input_t &input) {
|
||||
auto macos_input = ((macos_input_t *) input.get());
|
||||
|
||||
if (macos_input->mouse_down[0]) {
|
||||
return kCGEventLeftMouseDragged;
|
||||
}
|
||||
else if (macos_input->mouse_down[1]) {
|
||||
return kCGEventOtherMouseDragged;
|
||||
}
|
||||
else if (macos_input->mouse_down[2]) {
|
||||
return kCGEventRightMouseDragged;
|
||||
}
|
||||
else {
|
||||
return kCGEventMouseMoved;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
move_mouse(input_t &input, int deltaX, int deltaY) {
|
||||
auto current = get_mouse_loc(input);
|
||||
|
||||
CGPoint location = CGPointMake(current.x + deltaX, current.y + deltaY);
|
||||
|
||||
post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, 0);
|
||||
}
|
||||
|
||||
void
|
||||
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
|
||||
auto scaling = ((macos_input_t *) input.get())->displayScaling;
|
||||
|
||||
CGPoint location = CGPointMake(x * scaling, y * scaling);
|
||||
|
||||
post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, 0);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
time_diff(uint64_t start) {
|
||||
uint64_t elapsed;
|
||||
Nanoseconds elapsedNano;
|
||||
|
||||
elapsed = mach_absolute_time() - start;
|
||||
elapsedNano = AbsoluteToNanoseconds(*(AbsoluteTime *) &elapsed);
|
||||
|
||||
return *(uint64_t *) &elapsedNano;
|
||||
}
|
||||
|
||||
void
|
||||
button_mouse(input_t &input, int button, bool release) {
|
||||
CGMouseButton mac_button;
|
||||
CGEventType event;
|
||||
|
||||
auto mouse = ((macos_input_t *) input.get());
|
||||
|
||||
switch (button) {
|
||||
case 1:
|
||||
mac_button = kCGMouseButtonLeft;
|
||||
event = release ? kCGEventLeftMouseUp : kCGEventLeftMouseDown;
|
||||
break;
|
||||
case 2:
|
||||
mac_button = kCGMouseButtonCenter;
|
||||
event = release ? kCGEventOtherMouseUp : kCGEventOtherMouseDown;
|
||||
break;
|
||||
case 3:
|
||||
mac_button = kCGMouseButtonRight;
|
||||
event = release ? kCGEventRightMouseUp : kCGEventRightMouseDown;
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(warning) << "Unsupported mouse button for MacOS: "sv << button;
|
||||
return;
|
||||
}
|
||||
|
||||
mouse->mouse_down[mac_button] = !release;
|
||||
|
||||
// if the last mouse down was less than MULTICLICK_DELAY_NS, we send a double click event
|
||||
if (time_diff(mouse->last_mouse_event[mac_button][release]) < MULTICLICK_DELAY_NS) {
|
||||
post_mouse(input, mac_button, event, get_mouse_loc(input), 2);
|
||||
}
|
||||
else {
|
||||
post_mouse(input, mac_button, event, get_mouse_loc(input), 1);
|
||||
}
|
||||
|
||||
mouse->last_mouse_event[mac_button][release] = mach_absolute_time();
|
||||
}
|
||||
|
||||
void
|
||||
scroll(input_t &input, int high_res_distance) {
|
||||
CGEventRef upEvent = CGEventCreateScrollWheelEvent(
|
||||
NULL,
|
||||
kCGScrollEventUnitLine,
|
||||
2, high_res_distance > 0 ? 1 : -1, high_res_distance);
|
||||
CGEventPost(kCGHIDEventTap, upEvent);
|
||||
CFRelease(upEvent);
|
||||
}
|
||||
|
||||
void
|
||||
hscroll(input_t &input, int high_res_distance) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocates a context to store per-client input data.
|
||||
* @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) {
|
||||
// Unused
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends a touch event to the OS.
|
||||
* @param input The client-specific input context.
|
||||
* @param touch_port The current viewport for translating to screen coordinates.
|
||||
* @param touch The touch event.
|
||||
*/
|
||||
void
|
||||
touch(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
|
||||
// Unimplemented feature - platform_caps::pen_touch
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends a pen event to the OS.
|
||||
* @param input The client-specific input context.
|
||||
* @param touch_port The current viewport for translating to screen coordinates.
|
||||
* @param pen The pen event.
|
||||
*/
|
||||
void
|
||||
pen(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
|
||||
// Unimplemented feature - platform_caps::pen_touch
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends 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) {
|
||||
// Unimplemented feature - platform_caps::controller_touch
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends 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) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends 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) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
input_t
|
||||
input() {
|
||||
input_t result { new macos_input_t() };
|
||||
|
||||
auto macos_input = (macos_input_t *) result.get();
|
||||
|
||||
// If we don't use the main display in the future, this has to be adapted
|
||||
macos_input->display = CGMainDisplayID();
|
||||
|
||||
// Input coordinates are based on the virtual resolution not the physical, so we need the scaling factor
|
||||
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(macos_input->display);
|
||||
macos_input->displayScaling = ((CGFloat) CGDisplayPixelsWide(macos_input->display)) / ((CGFloat) CGDisplayModeGetPixelWidth(mode));
|
||||
CFRelease(mode);
|
||||
|
||||
macos_input->source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
|
||||
|
||||
macos_input->kb_event = CGEventCreate(macos_input->source);
|
||||
macos_input->kb_flags = 0;
|
||||
|
||||
macos_input->mouse_event = CGEventCreate(macos_input->source);
|
||||
macos_input->mouse_down[0] = false;
|
||||
macos_input->mouse_down[1] = false;
|
||||
macos_input->mouse_down[2] = false;
|
||||
macos_input->last_mouse_event[0][0] = 0;
|
||||
macos_input->last_mouse_event[0][1] = 0;
|
||||
macos_input->last_mouse_event[1][0] = 0;
|
||||
macos_input->last_mouse_event[1][1] = 0;
|
||||
macos_input->last_mouse_event[2][0] = 0;
|
||||
macos_input->last_mouse_event[2][1] = 0;
|
||||
|
||||
BOOST_LOG(debug) << "Display "sv << macos_input->display << ", pixel dimension: " << CGDisplayPixelsWide(macos_input->display) << "x"sv << CGDisplayPixelsHigh(macos_input->display);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
freeInput(void *p) {
|
||||
auto *input = (macos_input_t *) p;
|
||||
|
||||
CFRelease(input->source);
|
||||
CFRelease(input->kb_event);
|
||||
CFRelease(input->mouse_event);
|
||||
|
||||
delete input;
|
||||
}
|
||||
|
||||
std::vector<std::string_view> &
|
||||
supported_gamepads() {
|
||||
static std::vector<std::string_view> gamepads { ""sv };
|
||||
|
||||
return gamepads;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the supported platform capabilities to advertise to the client.
|
||||
* @return Capability flags.
|
||||
*/
|
||||
platform_caps::caps_t
|
||||
get_capabilities() {
|
||||
return 0;
|
||||
}
|
||||
} // namespace platf
|
||||
@ -1,445 +0,0 @@
|
||||
/**
|
||||
* @file src/platform/macos/publish.cpp
|
||||
* @brief todo
|
||||
* @note Adapted from https://www.avahi.org/doxygen/html/client-publish-service_8c-example.html
|
||||
* @todo Use a common file for this and src/platform/linux/publish.cpp
|
||||
*/
|
||||
#include <thread>
|
||||
|
||||
#include "misc.h"
|
||||
#include "src/main.h"
|
||||
#include "src/nvhttp.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace avahi {
|
||||
|
||||
/**
|
||||
* @brief Error codes used by avahi.
|
||||
*/
|
||||
enum err_e {
|
||||
OK = 0, /**< OK */
|
||||
ERR_FAILURE = -1, /**< Generic error code */
|
||||
ERR_BAD_STATE = -2, /**< Object was in a bad state */
|
||||
ERR_INVALID_HOST_NAME = -3, /**< Invalid host name */
|
||||
ERR_INVALID_DOMAIN_NAME = -4, /**< Invalid domain name */
|
||||
ERR_NO_NETWORK = -5, /**< No suitable network protocol available */
|
||||
ERR_INVALID_TTL = -6, /**< Invalid DNS TTL */
|
||||
ERR_IS_PATTERN = -7, /**< RR key is pattern */
|
||||
ERR_COLLISION = -8, /**< Name collision */
|
||||
ERR_INVALID_RECORD = -9, /**< Invalid RR */
|
||||
|
||||
ERR_INVALID_SERVICE_NAME = -10, /**< Invalid service name */
|
||||
ERR_INVALID_SERVICE_TYPE = -11, /**< Invalid service type */
|
||||
ERR_INVALID_PORT = -12, /**< Invalid port number */
|
||||
ERR_INVALID_KEY = -13, /**< Invalid key */
|
||||
ERR_INVALID_ADDRESS = -14, /**< Invalid address */
|
||||
ERR_TIMEOUT = -15, /**< Timeout reached */
|
||||
ERR_TOO_MANY_CLIENTS = -16, /**< Too many clients */
|
||||
ERR_TOO_MANY_OBJECTS = -17, /**< Too many objects */
|
||||
ERR_TOO_MANY_ENTRIES = -18, /**< Too many entries */
|
||||
ERR_OS = -19, /**< OS error */
|
||||
|
||||
ERR_ACCESS_DENIED = -20, /**< Access denied */
|
||||
ERR_INVALID_OPERATION = -21, /**< Invalid operation */
|
||||
ERR_DBUS_ERROR = -22, /**< An unexpected D-Bus error occurred */
|
||||
ERR_DISCONNECTED = -23, /**< Daemon connection failed */
|
||||
ERR_NO_MEMORY = -24, /**< Memory exhausted */
|
||||
ERR_INVALID_OBJECT = -25, /**< The object passed to this function was invalid */
|
||||
ERR_NO_DAEMON = -26, /**< Daemon not running */
|
||||
ERR_INVALID_INTERFACE = -27, /**< Invalid interface */
|
||||
ERR_INVALID_PROTOCOL = -28, /**< Invalid protocol */
|
||||
ERR_INVALID_FLAGS = -29, /**< Invalid flags */
|
||||
|
||||
ERR_NOT_FOUND = -30, /**< Not found */
|
||||
ERR_INVALID_CONFIG = -31, /**< Configuration error */
|
||||
ERR_VERSION_MISMATCH = -32, /**< Version mismatch */
|
||||
ERR_INVALID_SERVICE_SUBTYPE = -33, /**< Invalid service subtype */
|
||||
ERR_INVALID_PACKET = -34, /**< Invalid packet */
|
||||
ERR_INVALID_DNS_ERROR = -35, /**< Invalid DNS return code */
|
||||
ERR_DNS_FORMERR = -36, /**< DNS Error: Form error */
|
||||
ERR_DNS_SERVFAIL = -37, /**< DNS Error: Server Failure */
|
||||
ERR_DNS_NXDOMAIN = -38, /**< DNS Error: No such domain */
|
||||
ERR_DNS_NOTIMP = -39, /**< DNS Error: Not implemented */
|
||||
|
||||
ERR_DNS_REFUSED = -40, /**< DNS Error: Operation refused */
|
||||
ERR_DNS_YXDOMAIN = -41,
|
||||
ERR_DNS_YXRRSET = -42,
|
||||
ERR_DNS_NXRRSET = -43,
|
||||
ERR_DNS_NOTAUTH = -44, /**< DNS Error: Not authorized */
|
||||
ERR_DNS_NOTZONE = -45,
|
||||
ERR_INVALID_RDATA = -46, /**< Invalid RDATA */
|
||||
ERR_INVALID_DNS_CLASS = -47, /**< Invalid DNS class */
|
||||
ERR_INVALID_DNS_TYPE = -48, /**< Invalid DNS type */
|
||||
ERR_NOT_SUPPORTED = -49, /**< Not supported */
|
||||
|
||||
ERR_NOT_PERMITTED = -50, /**< Operation not permitted */
|
||||
ERR_INVALID_ARGUMENT = -51, /**< Invalid argument */
|
||||
ERR_IS_EMPTY = -52, /**< Is empty */
|
||||
ERR_NO_CHANGE = -53, /**< The requested operation is invalid because it is redundant */
|
||||
|
||||
ERR_MAX = -54
|
||||
};
|
||||
|
||||
constexpr auto IF_UNSPEC = -1;
|
||||
enum proto {
|
||||
PROTO_INET = 0, /**< IPv4 */
|
||||
PROTO_INET6 = 1, /**< IPv6 */
|
||||
PROTO_UNSPEC = -1 /**< Unspecified/all protocol(s) */
|
||||
};
|
||||
|
||||
enum ServerState {
|
||||
SERVER_INVALID, /**< Invalid state (initial) */
|
||||
SERVER_REGISTERING, /**< Host RRs are being registered */
|
||||
SERVER_RUNNING, /**< All host RRs have been established */
|
||||
SERVER_COLLISION, /**< There is a collision with a host RR. All host RRs have been withdrawn, the user should set a new host name via avahi_server_set_host_name() */
|
||||
SERVER_FAILURE /**< Some fatal failure happened, the server is unable to proceed */
|
||||
};
|
||||
|
||||
enum ClientState {
|
||||
CLIENT_S_REGISTERING = SERVER_REGISTERING, /**< Server state: REGISTERING */
|
||||
CLIENT_S_RUNNING = SERVER_RUNNING, /**< Server state: RUNNING */
|
||||
CLIENT_S_COLLISION = SERVER_COLLISION, /**< Server state: COLLISION */
|
||||
CLIENT_FAILURE = 100, /**< Some kind of error happened on the client side */
|
||||
CLIENT_CONNECTING = 101 /**< We're still connecting. This state is only entered when AVAHI_CLIENT_NO_FAIL has been passed to avahi_client_new() and the daemon is not yet available. */
|
||||
};
|
||||
|
||||
enum EntryGroupState {
|
||||
ENTRY_GROUP_UNCOMMITED, /**< The group has not yet been committed, the user must still call avahi_entry_group_commit() */
|
||||
ENTRY_GROUP_REGISTERING, /**< The entries of the group are currently being registered */
|
||||
ENTRY_GROUP_ESTABLISHED, /**< The entries have successfully been established */
|
||||
ENTRY_GROUP_COLLISION, /**< A name collision for one of the entries in the group has been detected, the entries have been withdrawn */
|
||||
ENTRY_GROUP_FAILURE /**< Some kind of failure happened, the entries have been withdrawn */
|
||||
};
|
||||
|
||||
enum ClientFlags {
|
||||
CLIENT_IGNORE_USER_CONFIG = 1, /**< Don't read user configuration */
|
||||
CLIENT_NO_FAIL = 2 /**< Don't fail if the daemon is not available when avahi_client_new() is called, instead enter CLIENT_CONNECTING state and wait for the daemon to appear */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Flags for publishing functions.
|
||||
*/
|
||||
enum PublishFlags {
|
||||
PUBLISH_UNIQUE = 1, /**< For raw records: The RRset is intended to be unique */
|
||||
PUBLISH_NO_PROBE = 2, /**< For raw records: Though the RRset is intended to be unique no probes shall be sent */
|
||||
PUBLISH_NO_ANNOUNCE = 4, /**< For raw records: Do not announce this RR to other hosts */
|
||||
PUBLISH_ALLOW_MULTIPLE = 8, /**< For raw records: Allow multiple local records of this type, even if they are intended to be unique */
|
||||
/** \cond fulldocs */
|
||||
PUBLISH_NO_REVERSE = 16, /**< For address records: don't create a reverse (PTR) entry */
|
||||
PUBLISH_NO_COOKIE = 32, /**< For service records: do not implicitly add the local service cookie to TXT data */
|
||||
/** \endcond */
|
||||
PUBLISH_UPDATE = 64, /**< Update existing records instead of adding new ones */
|
||||
/** \cond fulldocs */
|
||||
PUBLISH_USE_WIDE_AREA = 128, /**< Register the record using wide area DNS (i.e. unicast DNS update) */
|
||||
PUBLISH_USE_MULTICAST = 256 /**< Register the record using multicast DNS */
|
||||
/** \endcond */
|
||||
};
|
||||
|
||||
using IfIndex = int;
|
||||
using Protocol = int;
|
||||
|
||||
struct EntryGroup;
|
||||
struct Poll;
|
||||
struct SimplePoll;
|
||||
struct Client;
|
||||
|
||||
typedef void (*ClientCallback)(Client *, ClientState, void *userdata);
|
||||
typedef void (*EntryGroupCallback)(EntryGroup *g, EntryGroupState state, void *userdata);
|
||||
|
||||
typedef void (*free_fn)(void *);
|
||||
|
||||
typedef Client *(*client_new_fn)(const Poll *poll_api, ClientFlags flags, ClientCallback callback, void *userdata, int *error);
|
||||
typedef void (*client_free_fn)(Client *);
|
||||
typedef char *(*alternative_service_name_fn)(char *);
|
||||
|
||||
typedef Client *(*entry_group_get_client_fn)(EntryGroup *);
|
||||
|
||||
typedef EntryGroup *(*entry_group_new_fn)(Client *, EntryGroupCallback, void *userdata);
|
||||
typedef int (*entry_group_add_service_fn)(
|
||||
EntryGroup *group,
|
||||
IfIndex interface,
|
||||
Protocol protocol,
|
||||
PublishFlags flags,
|
||||
const char *name,
|
||||
const char *type,
|
||||
const char *domain,
|
||||
const char *host,
|
||||
uint16_t port,
|
||||
...);
|
||||
|
||||
typedef int (*entry_group_is_empty_fn)(EntryGroup *);
|
||||
typedef int (*entry_group_reset_fn)(EntryGroup *);
|
||||
typedef int (*entry_group_commit_fn)(EntryGroup *);
|
||||
|
||||
typedef char *(*strdup_fn)(const char *);
|
||||
typedef char *(*strerror_fn)(int);
|
||||
typedef int (*client_errno_fn)(Client *);
|
||||
|
||||
typedef Poll *(*simple_poll_get_fn)(SimplePoll *);
|
||||
typedef int (*simple_poll_loop_fn)(SimplePoll *);
|
||||
typedef void (*simple_poll_quit_fn)(SimplePoll *);
|
||||
typedef SimplePoll *(*simple_poll_new_fn)();
|
||||
typedef void (*simple_poll_free_fn)(SimplePoll *);
|
||||
|
||||
free_fn free;
|
||||
client_new_fn client_new;
|
||||
client_free_fn client_free;
|
||||
alternative_service_name_fn alternative_service_name;
|
||||
entry_group_get_client_fn entry_group_get_client;
|
||||
entry_group_new_fn entry_group_new;
|
||||
entry_group_add_service_fn entry_group_add_service;
|
||||
entry_group_is_empty_fn entry_group_is_empty;
|
||||
entry_group_reset_fn entry_group_reset;
|
||||
entry_group_commit_fn entry_group_commit;
|
||||
strdup_fn strdup;
|
||||
strerror_fn strerror;
|
||||
client_errno_fn client_errno;
|
||||
simple_poll_get_fn simple_poll_get;
|
||||
simple_poll_loop_fn simple_poll_loop;
|
||||
simple_poll_quit_fn simple_poll_quit;
|
||||
simple_poll_new_fn simple_poll_new;
|
||||
simple_poll_free_fn simple_poll_free;
|
||||
|
||||
int
|
||||
init_common() {
|
||||
static void *handle { nullptr };
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
if (funcs_loaded) return 0;
|
||||
|
||||
if (!handle) {
|
||||
handle = dyn::handle({ "libavahi-common.3.dylib", "libavahi-common.dylib" });
|
||||
if (!handle) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
|
||||
{ (dyn::apiproc *) &alternative_service_name, "avahi_alternative_service_name" },
|
||||
{ (dyn::apiproc *) &free, "avahi_free" },
|
||||
{ (dyn::apiproc *) &strdup, "avahi_strdup" },
|
||||
{ (dyn::apiproc *) &strerror, "avahi_strerror" },
|
||||
{ (dyn::apiproc *) &simple_poll_get, "avahi_simple_poll_get" },
|
||||
{ (dyn::apiproc *) &simple_poll_loop, "avahi_simple_poll_loop" },
|
||||
{ (dyn::apiproc *) &simple_poll_quit, "avahi_simple_poll_quit" },
|
||||
{ (dyn::apiproc *) &simple_poll_new, "avahi_simple_poll_new" },
|
||||
{ (dyn::apiproc *) &simple_poll_free, "avahi_simple_poll_free" },
|
||||
};
|
||||
|
||||
if (dyn::load(handle, funcs)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
funcs_loaded = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
init_client() {
|
||||
if (init_common()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void *handle { nullptr };
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
if (funcs_loaded) return 0;
|
||||
|
||||
if (!handle) {
|
||||
handle = dyn::handle({ "libavahi-client.3.dylib", "libavahi-client.dylib" });
|
||||
if (!handle) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
|
||||
{ (dyn::apiproc *) &client_new, "avahi_client_new" },
|
||||
{ (dyn::apiproc *) &client_free, "avahi_client_free" },
|
||||
{ (dyn::apiproc *) &entry_group_get_client, "avahi_entry_group_get_client" },
|
||||
{ (dyn::apiproc *) &entry_group_new, "avahi_entry_group_new" },
|
||||
{ (dyn::apiproc *) &entry_group_add_service, "avahi_entry_group_add_service" },
|
||||
{ (dyn::apiproc *) &entry_group_is_empty, "avahi_entry_group_is_empty" },
|
||||
{ (dyn::apiproc *) &entry_group_reset, "avahi_entry_group_reset" },
|
||||
{ (dyn::apiproc *) &entry_group_commit, "avahi_entry_group_commit" },
|
||||
{ (dyn::apiproc *) &client_errno, "avahi_client_errno" },
|
||||
};
|
||||
|
||||
if (dyn::load(handle, funcs)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
funcs_loaded = true;
|
||||
return 0;
|
||||
}
|
||||
} // namespace avahi
|
||||
|
||||
namespace platf::publish {
|
||||
|
||||
template <class T>
|
||||
void
|
||||
free(T *p) {
|
||||
avahi::free(p);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
using ptr_t = util::safe_ptr<T, free<T>>;
|
||||
using client_t = util::dyn_safe_ptr<avahi::Client, &avahi::client_free>;
|
||||
using poll_t = util::dyn_safe_ptr<avahi::SimplePoll, &avahi::simple_poll_free>;
|
||||
|
||||
avahi::EntryGroup *group = nullptr;
|
||||
|
||||
poll_t poll;
|
||||
client_t client;
|
||||
|
||||
ptr_t<char> name;
|
||||
|
||||
void
|
||||
create_services(avahi::Client *c);
|
||||
|
||||
void
|
||||
entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
|
||||
group = g;
|
||||
|
||||
switch (state) {
|
||||
case avahi::ENTRY_GROUP_ESTABLISHED:
|
||||
BOOST_LOG(info) << "Avahi service " << name.get() << " successfully established.";
|
||||
break;
|
||||
case avahi::ENTRY_GROUP_COLLISION:
|
||||
name.reset(avahi::alternative_service_name(name.get()));
|
||||
|
||||
BOOST_LOG(info) << "Avahi service name collision, renaming service to " << name.get();
|
||||
|
||||
create_services(avahi::entry_group_get_client(g));
|
||||
break;
|
||||
case avahi::ENTRY_GROUP_FAILURE:
|
||||
BOOST_LOG(error) << "Avahi entry group failure: " << avahi::strerror(avahi::client_errno(avahi::entry_group_get_client(g)));
|
||||
avahi::simple_poll_quit(poll.get());
|
||||
break;
|
||||
case avahi::ENTRY_GROUP_UNCOMMITED:
|
||||
case avahi::ENTRY_GROUP_REGISTERING:;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
create_services(avahi::Client *c) {
|
||||
int ret;
|
||||
|
||||
auto fg = util::fail_guard([]() {
|
||||
avahi::simple_poll_quit(poll.get());
|
||||
});
|
||||
|
||||
if (!group) {
|
||||
if (!(group = avahi::entry_group_new(c, entry_group_callback, nullptr))) {
|
||||
BOOST_LOG(error) << "avahi::entry_group_new() failed: "sv << avahi::strerror(avahi::client_errno(c));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (avahi::entry_group_is_empty(group)) {
|
||||
BOOST_LOG(info) << "Adding avahi service "sv << name.get();
|
||||
|
||||
ret = avahi::entry_group_add_service(
|
||||
group,
|
||||
avahi::IF_UNSPEC, avahi::PROTO_UNSPEC,
|
||||
avahi::PublishFlags(0),
|
||||
name.get(),
|
||||
SERVICE_TYPE,
|
||||
nullptr, nullptr,
|
||||
map_port(nvhttp::PORT_HTTP),
|
||||
nullptr);
|
||||
|
||||
if (ret < 0) {
|
||||
if (ret == avahi::ERR_COLLISION) {
|
||||
// A service name collision with a local service happened. Let's pick a new name
|
||||
name.reset(avahi::alternative_service_name(name.get()));
|
||||
BOOST_LOG(info) << "Service name collision, renaming service to "sv << name.get();
|
||||
|
||||
avahi::entry_group_reset(group);
|
||||
|
||||
create_services(c);
|
||||
|
||||
fg.disable();
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG(error) << "Failed to add "sv << SERVICE_TYPE << " service: "sv << avahi::strerror(ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = avahi::entry_group_commit(group);
|
||||
if (ret < 0) {
|
||||
BOOST_LOG(error) << "Failed to commit entry group: "sv << avahi::strerror(ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fg.disable();
|
||||
}
|
||||
|
||||
void
|
||||
client_callback(avahi::Client *c, avahi::ClientState state, void *) {
|
||||
switch (state) {
|
||||
case avahi::CLIENT_S_RUNNING:
|
||||
create_services(c);
|
||||
break;
|
||||
case avahi::CLIENT_FAILURE:
|
||||
BOOST_LOG(error) << "Client failure: "sv << avahi::strerror(avahi::client_errno(c));
|
||||
avahi::simple_poll_quit(poll.get());
|
||||
break;
|
||||
case avahi::CLIENT_S_COLLISION:
|
||||
case avahi::CLIENT_S_REGISTERING:
|
||||
if (group)
|
||||
avahi::entry_group_reset(group);
|
||||
break;
|
||||
case avahi::CLIENT_CONNECTING:;
|
||||
}
|
||||
}
|
||||
|
||||
class deinit_t: public ::platf::deinit_t {
|
||||
public:
|
||||
std::thread poll_thread;
|
||||
|
||||
deinit_t(std::thread poll_thread):
|
||||
poll_thread { std::move(poll_thread) } {}
|
||||
|
||||
~deinit_t() override {
|
||||
if (avahi::simple_poll_quit && poll) {
|
||||
avahi::simple_poll_quit(poll.get());
|
||||
}
|
||||
|
||||
if (poll_thread.joinable()) {
|
||||
poll_thread.join();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<::platf::deinit_t>
|
||||
start() {
|
||||
if (avahi::init_client()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int avhi_error;
|
||||
|
||||
poll.reset(avahi::simple_poll_new());
|
||||
if (!poll) {
|
||||
BOOST_LOG(error) << "Failed to create simple poll object."sv;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
name.reset(avahi::strdup(SERVICE_NAME));
|
||||
|
||||
client.reset(
|
||||
avahi::client_new(avahi::simple_poll_get(poll.get()), avahi::ClientFlags(0), client_callback, nullptr, &avhi_error));
|
||||
|
||||
if (!client) {
|
||||
BOOST_LOG(error) << "Failed to create client: "sv << avahi::strerror(avhi_error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<deinit_t>(std::thread { avahi::simple_poll_loop, poll.get() });
|
||||
}
|
||||
} // namespace platf::publish
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,218 +0,0 @@
|
||||
/**
|
||||
* @file src/platform/windows/publish.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#include <winsock2.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <windns.h>
|
||||
#include <winerror.h>
|
||||
|
||||
#include <boost/asio/ip/host_name.hpp>
|
||||
|
||||
#include "misc.h"
|
||||
#include "src/config.h"
|
||||
#include "src/main.h"
|
||||
#include "src/network.h"
|
||||
#include "src/nvhttp.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/thread_safe.h"
|
||||
|
||||
#define _FN(x, ret, args) \
|
||||
typedef ret(*x##_fn) args; \
|
||||
static x##_fn x
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
#define __SV(quote) L##quote##sv
|
||||
#define SV(quote) __SV(quote)
|
||||
|
||||
extern "C" {
|
||||
#ifndef __MINGW32__
|
||||
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_INSTANCE_NAME = SV(SERVICE_NAME "." 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;
|
||||
|
||||
IP4_ADDRESS *ip4Address;
|
||||
IP6_ADDRESS *ip6Address;
|
||||
|
||||
WORD wPort;
|
||||
WORD wPriority;
|
||||
WORD wWeight;
|
||||
|
||||
// Property list
|
||||
DWORD dwPropertyCount;
|
||||
|
||||
PWSTR *keys;
|
||||
PWSTR *values;
|
||||
|
||||
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 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_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));
|
||||
} /* extern "C" */
|
||||
|
||||
namespace platf::publish {
|
||||
VOID WINAPI
|
||||
register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) {
|
||||
auto alarm = (safe::alarm_t<PDNS_SERVICE_INSTANCE>::element_type *) pQueryContext;
|
||||
|
||||
if (status) {
|
||||
print_status("register_cb()"sv, status);
|
||||
}
|
||||
|
||||
alarm->ring(pInstance);
|
||||
}
|
||||
|
||||
static int
|
||||
service(bool enable, PDNS_SERVICE_INSTANCE &existing_instance) {
|
||||
auto alarm = safe::make_alarm<PDNS_SERVICE_INSTANCE>();
|
||||
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
||||
|
||||
std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() };
|
||||
std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() };
|
||||
|
||||
auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local");
|
||||
|
||||
DNS_SERVICE_INSTANCE instance {};
|
||||
instance.pszInstanceName = name.data();
|
||||
instance.wPort = map_port(nvhttp::PORT_HTTP);
|
||||
instance.pszHostName = host.data();
|
||||
|
||||
DNS_SERVICE_REGISTER_REQUEST req {};
|
||||
req.Version = DNS_QUERY_REQUEST_VERSION1;
|
||||
req.pQueryContext = alarm.get();
|
||||
req.pServiceInstance = enable ? &instance : existing_instance;
|
||||
req.pRegisterCompletionCallback = register_cb;
|
||||
|
||||
DNS_STATUS status {};
|
||||
|
||||
if (enable) {
|
||||
status = _DnsServiceRegister(&req, nullptr);
|
||||
if (status != DNS_REQUEST_PENDING) {
|
||||
print_status("DnsServiceRegister()"sv, status);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
status = _DnsServiceDeRegister(&req, nullptr);
|
||||
if (status != DNS_REQUEST_PENDING) {
|
||||
print_status("DnsServiceDeRegister()"sv, status);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
alarm->wait();
|
||||
|
||||
auto registered_instance = alarm->status();
|
||||
if (enable) {
|
||||
// Store this instance for later deregistration
|
||||
existing_instance = registered_instance;
|
||||
}
|
||||
else if (registered_instance) {
|
||||
// Deregistration was successful
|
||||
_DnsServiceFreeInstance(registered_instance);
|
||||
existing_instance = nullptr;
|
||||
}
|
||||
|
||||
return registered_instance ? 0 : -1;
|
||||
}
|
||||
|
||||
class mdns_registration_t: public ::platf::deinit_t {
|
||||
public:
|
||||
mdns_registration_t():
|
||||
existing_instance(nullptr) {
|
||||
if (service(true, existing_instance)) {
|
||||
BOOST_LOG(error) << "Unable to register Sunshine mDNS service"sv;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "Registered Sunshine mDNS service"sv;
|
||||
}
|
||||
|
||||
~mdns_registration_t() override {
|
||||
if (existing_instance) {
|
||||
if (service(false, existing_instance)) {
|
||||
BOOST_LOG(error) << "Unable to unregister Sunshine mDNS service"sv;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "Unregistered Sunshine mDNS service"sv;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
PDNS_SERVICE_INSTANCE existing_instance;
|
||||
};
|
||||
|
||||
int
|
||||
load_funcs(HMODULE handle) {
|
||||
auto fg = util::fail_guard([handle]() {
|
||||
FreeLibrary(handle);
|
||||
});
|
||||
|
||||
_DnsServiceFreeInstance = (_DnsServiceFreeInstance_fn) GetProcAddress(handle, "DnsServiceFreeInstance");
|
||||
_DnsServiceDeRegister = (_DnsServiceDeRegister_fn) GetProcAddress(handle, "DnsServiceDeRegister");
|
||||
_DnsServiceRegister = (_DnsServiceRegister_fn) GetProcAddress(handle, "DnsServiceRegister");
|
||||
|
||||
if (!(_DnsServiceFreeInstance && _DnsServiceDeRegister && _DnsServiceRegister)) {
|
||||
BOOST_LOG(error) << "mDNS service not available in dnsapi.dll"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
fg.disable();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<::platf::deinit_t>
|
||||
start() {
|
||||
HMODULE handle = LoadLibrary("dnsapi.dll");
|
||||
|
||||
if (!handle || load_funcs(handle)) {
|
||||
BOOST_LOG(error) << "Couldn't load dnsapi.dll, You'll need to add PC manually from Moonlight"sv;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<mdns_registration_t>();
|
||||
}
|
||||
} // namespace platf::publish
|
||||
651
src/process.cpp
651
src/process.cpp
@ -1,651 +0,0 @@
|
||||
/**
|
||||
* @file src/process.cpp
|
||||
* @brief Handles the startup and shutdown of the apps started by a streaming Session.
|
||||
*/
|
||||
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
|
||||
|
||||
#include "process.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/crc.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/program_options/parsers.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "crypto.h"
|
||||
#include "main.h"
|
||||
#include "platform/common.h"
|
||||
#include "utility.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
// _SH constants for _wfsopen()
|
||||
#include <share.h>
|
||||
#endif
|
||||
|
||||
#define DEFAULT_APP_IMAGE_PATH SUNSHINE_ASSETS_DIR "/box.png"
|
||||
|
||||
namespace proc {
|
||||
using namespace std::literals;
|
||||
namespace bp = boost::process;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
proc_t proc;
|
||||
|
||||
class deinit_t: public platf::deinit_t {
|
||||
public:
|
||||
~deinit_t() {
|
||||
proc.terminate();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initializes proc functions
|
||||
* @return Unique pointer to `deinit_t` to manage cleanup
|
||||
*/
|
||||
std::unique_ptr<platf::deinit_t>
|
||||
init() {
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
void
|
||||
process_end(bp::child &proc, bp::group &proc_handle) {
|
||||
if (!proc.running()) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << "Force termination Child-Process"sv;
|
||||
proc_handle.terminate();
|
||||
|
||||
// avoid zombie process
|
||||
proc.wait();
|
||||
}
|
||||
|
||||
boost::filesystem::path
|
||||
find_working_directory(const std::string &cmd, bp::environment &env) {
|
||||
// Parse the raw command string into parts to get the actual command portion
|
||||
#ifdef _WIN32
|
||||
auto parts = boost::program_options::split_winmain(cmd);
|
||||
#else
|
||||
auto parts = boost::program_options::split_unix(cmd);
|
||||
#endif
|
||||
if (parts.empty()) {
|
||||
BOOST_LOG(error) << "Unable to parse command: "sv << cmd;
|
||||
return boost::filesystem::path();
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << "Parsed executable ["sv << parts.at(0) << "] from command ["sv << cmd << ']';
|
||||
|
||||
// If the cmd path is not an absolute path, resolve it using our PATH variable
|
||||
boost::filesystem::path cmd_path(parts.at(0));
|
||||
if (!cmd_path.is_absolute()) {
|
||||
cmd_path = boost::process::search_path(parts.at(0));
|
||||
if (cmd_path.empty()) {
|
||||
BOOST_LOG(error) << "Unable to find executable ["sv << parts.at(0) << "]. Is it in your PATH?"sv;
|
||||
return boost::filesystem::path();
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << "Resolved executable ["sv << parts.at(0) << "] to path ["sv << cmd_path << ']';
|
||||
|
||||
// Now that we have a complete path, we can just use parent_path()
|
||||
return cmd_path.parent_path();
|
||||
}
|
||||
|
||||
int
|
||||
proc_t::execute(int app_id, rtsp_stream::launch_session_t launch_session) {
|
||||
// Ensure starting from a clean slate
|
||||
terminate();
|
||||
|
||||
auto iter = std::find_if(_apps.begin(), _apps.end(), [&app_id](const auto app) {
|
||||
return app.id == std::to_string(app_id);
|
||||
});
|
||||
|
||||
if (iter == _apps.end()) {
|
||||
BOOST_LOG(error) << "Couldn't find app with ID ["sv << app_id << ']';
|
||||
return 404;
|
||||
}
|
||||
|
||||
_app_id = app_id;
|
||||
_app = *iter;
|
||||
_app_prep_begin = std::begin(_app.prep_cmds);
|
||||
_app_prep_it = _app_prep_begin;
|
||||
|
||||
// Add Stream-specific environment variables
|
||||
_env["SUNSHINE_APP_ID"] = std::to_string(_app_id);
|
||||
_env["SUNSHINE_APP_NAME"] = _app.name;
|
||||
_env["SUNSHINE_CLIENT_WIDTH"] = std::to_string(launch_session.width);
|
||||
_env["SUNSHINE_CLIENT_HEIGHT"] = std::to_string(launch_session.height);
|
||||
_env["SUNSHINE_CLIENT_FPS"] = std::to_string(launch_session.fps);
|
||||
_env["SUNSHINE_CLIENT_HDR"] = launch_session.enable_hdr ? "true" : "false";
|
||||
_env["SUNSHINE_CLIENT_GCMAP"] = std::to_string(launch_session.gcmap);
|
||||
_env["SUNSHINE_CLIENT_HOST_AUDIO"] = launch_session.host_audio ? "true" : "false";
|
||||
_env["SUNSHINE_CLIENT_ENABLE_SOPS"] = launch_session.enable_sops ? "true" : "false";
|
||||
int channelCount = launch_session.surround_info & (65535);
|
||||
switch (channelCount) {
|
||||
case 2:
|
||||
_env["SUNSHINE_CLIENT_AUDIO_CONFIGURATION"] = "2.0";
|
||||
break;
|
||||
case 6:
|
||||
_env["SUNSHINE_CLIENT_AUDIO_CONFIGURATION"] = "5.1";
|
||||
break;
|
||||
case 8:
|
||||
_env["SUNSHINE_CLIENT_AUDIO_CONFIGURATION"] = "7.1";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_app.output.empty() && _app.output != "null"sv) {
|
||||
#ifdef _WIN32
|
||||
// fopen() interprets the filename as an ANSI string on Windows, so we must convert it
|
||||
// to UTF-16 and use the wchar_t variants for proper Unicode log file path support.
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
||||
auto woutput = converter.from_bytes(_app.output);
|
||||
|
||||
// Use _SH_DENYNO to allow us to open this log file again for writing even if it is
|
||||
// still open from a previous execution. This is required to handle the case of a
|
||||
// detached process executing again while the previous process is still running.
|
||||
_pipe.reset(_wfsopen(woutput.c_str(), L"a", _SH_DENYNO));
|
||||
#else
|
||||
_pipe.reset(fopen(_app.output.c_str(), "a"));
|
||||
#endif
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
// Executed when returning from function
|
||||
auto fg = util::fail_guard([&]() {
|
||||
terminate();
|
||||
});
|
||||
|
||||
for (; _app_prep_it != std::end(_app.prep_cmds); ++_app_prep_it) {
|
||||
auto &cmd = *_app_prep_it;
|
||||
|
||||
// Skip empty commands
|
||||
if (cmd.do_cmd.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boost::filesystem::path working_dir = _app.working_dir.empty() ?
|
||||
find_working_directory(cmd.do_cmd, _env) :
|
||||
boost::filesystem::path(_app.working_dir);
|
||||
BOOST_LOG(info) << "Executing Do Cmd: ["sv << cmd.do_cmd << ']';
|
||||
auto child = platf::run_command(cmd.elevated, true, cmd.do_cmd, working_dir, _env, _pipe.get(), ec, nullptr);
|
||||
|
||||
if (ec) {
|
||||
BOOST_LOG(error) << "Couldn't run ["sv << cmd.do_cmd << "]: System: "sv << ec.message();
|
||||
// We don't want any prep commands failing launch of the desktop.
|
||||
// This is to prevent the issue where users reboot their PC and need to log in with Sunshine.
|
||||
// permission_denied is typically returned when the user impersonation fails, which can happen when user is not signed in yet.
|
||||
if (!(_app.cmd.empty() && ec == std::errc::permission_denied)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
child.wait();
|
||||
auto ret = child.exit_code();
|
||||
if (ret != 0 && ec != std::errc::permission_denied) {
|
||||
BOOST_LOG(error) << '[' << cmd.do_cmd << "] failed with code ["sv << ret << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &cmd : _app.detached) {
|
||||
boost::filesystem::path working_dir = _app.working_dir.empty() ?
|
||||
find_working_directory(cmd, _env) :
|
||||
boost::filesystem::path(_app.working_dir);
|
||||
BOOST_LOG(info) << "Spawning ["sv << cmd << "] in ["sv << working_dir << ']';
|
||||
auto child = platf::run_command(_app.elevated, true, cmd, working_dir, _env, _pipe.get(), ec, nullptr);
|
||||
if (ec) {
|
||||
BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd << "]: System: "sv << ec.message();
|
||||
}
|
||||
else {
|
||||
child.detach();
|
||||
}
|
||||
}
|
||||
|
||||
if (_app.cmd.empty()) {
|
||||
BOOST_LOG(info) << "Executing [Desktop]"sv;
|
||||
placebo = true;
|
||||
}
|
||||
else {
|
||||
boost::filesystem::path working_dir = _app.working_dir.empty() ?
|
||||
find_working_directory(_app.cmd, _env) :
|
||||
boost::filesystem::path(_app.working_dir);
|
||||
BOOST_LOG(info) << "Executing: ["sv << _app.cmd << "] in ["sv << working_dir << ']';
|
||||
_process = platf::run_command(_app.elevated, true, _app.cmd, working_dir, _env, _pipe.get(), ec, &_process_handle);
|
||||
if (ec) {
|
||||
BOOST_LOG(warning) << "Couldn't run ["sv << _app.cmd << "]: System: "sv << ec.message();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
_app_launch_time = std::chrono::steady_clock::now();
|
||||
|
||||
fg.disable();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
proc_t::running() {
|
||||
if (placebo || _process.running()) {
|
||||
return _app_id;
|
||||
}
|
||||
else if (_app.auto_detach && _process.native_exit_code() == 0 &&
|
||||
std::chrono::steady_clock::now() - _app_launch_time < 5s) {
|
||||
BOOST_LOG(info) << "App exited gracefully within 5 seconds of launch. Treating the app as a detached command."sv;
|
||||
BOOST_LOG(info) << "Adjust this behavior in the Applications tab or apps.json if this is not what you want."sv;
|
||||
placebo = true;
|
||||
return _app_id;
|
||||
}
|
||||
|
||||
// Perform cleanup actions now if needed
|
||||
if (_process) {
|
||||
BOOST_LOG(info) << "App exited with code ["sv << _process.native_exit_code() << ']';
|
||||
terminate();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
proc_t::terminate() {
|
||||
bool has_run = _app_id > 0;
|
||||
std::error_code ec;
|
||||
placebo = false;
|
||||
process_end(_process, _process_handle);
|
||||
_process = bp::child();
|
||||
_process_handle = bp::group();
|
||||
_app_id = -1;
|
||||
|
||||
for (; _app_prep_it != _app_prep_begin; --_app_prep_it) {
|
||||
auto &cmd = *(_app_prep_it - 1);
|
||||
|
||||
if (cmd.undo_cmd.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boost::filesystem::path working_dir = _app.working_dir.empty() ?
|
||||
find_working_directory(cmd.undo_cmd, _env) :
|
||||
boost::filesystem::path(_app.working_dir);
|
||||
BOOST_LOG(info) << "Executing Undo Cmd: ["sv << cmd.undo_cmd << ']';
|
||||
auto child = platf::run_command(cmd.elevated, true, cmd.undo_cmd, working_dir, _env, _pipe.get(), ec, nullptr);
|
||||
|
||||
if (ec) {
|
||||
BOOST_LOG(warning) << "System: "sv << ec.message();
|
||||
}
|
||||
|
||||
child.wait();
|
||||
auto ret = child.exit_code();
|
||||
|
||||
if (ret != 0) {
|
||||
BOOST_LOG(warning) << "Return code ["sv << ret << ']';
|
||||
}
|
||||
}
|
||||
|
||||
_pipe.reset();
|
||||
}
|
||||
|
||||
const std::vector<ctx_t> &
|
||||
proc_t::get_apps() const {
|
||||
return _apps;
|
||||
}
|
||||
std::vector<ctx_t> &
|
||||
proc_t::get_apps() {
|
||||
return _apps;
|
||||
}
|
||||
|
||||
// Gets application image from application list.
|
||||
// Returns image from assets directory if found there.
|
||||
// Returns default image if image configuration is not set.
|
||||
// Returns http content-type header compatible image type.
|
||||
std::string
|
||||
proc_t::get_app_image(int app_id) {
|
||||
auto iter = std::find_if(_apps.begin(), _apps.end(), [&app_id](const auto app) {
|
||||
return app.id == std::to_string(app_id);
|
||||
});
|
||||
auto app_image_path = iter == _apps.end() ? std::string() : iter->image_path;
|
||||
|
||||
return validate_app_image_path(app_image_path);
|
||||
}
|
||||
|
||||
std::string
|
||||
proc_t::get_last_run_app_name() {
|
||||
return _app.name;
|
||||
}
|
||||
|
||||
proc_t::~proc_t() {
|
||||
// It's not safe to call terminate() here because our proc_t is a static variable
|
||||
// that may be destroyed after the Boost loggers have been destroyed. Instead,
|
||||
// we return a deinit_t to main() to handle termination when we're exiting.
|
||||
// Once we reach this point here, termination must have already happened.
|
||||
assert(!placebo);
|
||||
assert(!_process.running());
|
||||
}
|
||||
|
||||
std::string_view::iterator
|
||||
find_match(std::string_view::iterator begin, std::string_view::iterator end) {
|
||||
int stack = 0;
|
||||
|
||||
--begin;
|
||||
do {
|
||||
++begin;
|
||||
switch (*begin) {
|
||||
case '(':
|
||||
++stack;
|
||||
break;
|
||||
case ')':
|
||||
--stack;
|
||||
}
|
||||
} while (begin != end && stack != 0);
|
||||
|
||||
if (begin == end) {
|
||||
throw std::out_of_range("Missing closing bracket \')\'");
|
||||
}
|
||||
return begin;
|
||||
}
|
||||
|
||||
std::string
|
||||
parse_env_val(bp::native_environment &env, const std::string_view &val_raw) {
|
||||
auto pos = std::begin(val_raw);
|
||||
auto dollar = std::find(pos, std::end(val_raw), '$');
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
while (dollar != std::end(val_raw)) {
|
||||
auto next = dollar + 1;
|
||||
if (next != std::end(val_raw)) {
|
||||
switch (*next) {
|
||||
case '(': {
|
||||
ss.write(pos, (dollar - pos));
|
||||
auto var_begin = next + 1;
|
||||
auto var_end = find_match(next, std::end(val_raw));
|
||||
auto var_name = std::string { var_begin, var_end };
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows treats environment variable names in a case-insensitive manner,
|
||||
// so we look for a case-insensitive match here. This is critical for
|
||||
// correctly appending to PATH on Windows.
|
||||
auto itr = std::find_if(env.cbegin(), env.cend(),
|
||||
[&](const auto &e) { return boost::iequals(e.get_name(), var_name); });
|
||||
if (itr != env.cend()) {
|
||||
// Use an existing case-insensitive match
|
||||
var_name = itr->get_name();
|
||||
}
|
||||
#endif
|
||||
|
||||
ss << env[var_name].to_string();
|
||||
|
||||
pos = var_end + 1;
|
||||
next = var_end;
|
||||
|
||||
break;
|
||||
}
|
||||
case '$':
|
||||
ss.write(pos, (next - pos));
|
||||
pos = next + 1;
|
||||
++next;
|
||||
break;
|
||||
}
|
||||
|
||||
dollar = std::find(next, std::end(val_raw), '$');
|
||||
}
|
||||
else {
|
||||
dollar = next;
|
||||
}
|
||||
}
|
||||
|
||||
ss.write(pos, (dollar - pos));
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string
|
||||
validate_app_image_path(std::string app_image_path) {
|
||||
if (app_image_path.empty()) {
|
||||
return DEFAULT_APP_IMAGE_PATH;
|
||||
}
|
||||
|
||||
// get the image extension and convert it to lowercase
|
||||
auto image_extension = std::filesystem::path(app_image_path).extension().string();
|
||||
boost::to_lower(image_extension);
|
||||
|
||||
// return the default box image if extension is not "png"
|
||||
if (image_extension != ".png") {
|
||||
return DEFAULT_APP_IMAGE_PATH;
|
||||
}
|
||||
|
||||
// check if image is in assets directory
|
||||
auto full_image_path = std::filesystem::path(SUNSHINE_ASSETS_DIR) / app_image_path;
|
||||
if (std::filesystem::exists(full_image_path)) {
|
||||
return full_image_path.string();
|
||||
}
|
||||
else if (app_image_path == "./assets/steam.png") {
|
||||
// handle old default steam image definition
|
||||
return SUNSHINE_ASSETS_DIR "/steam.png";
|
||||
}
|
||||
|
||||
// check if specified image exists
|
||||
std::error_code code;
|
||||
if (!std::filesystem::exists(app_image_path, code)) {
|
||||
// return default box image if image does not exist
|
||||
BOOST_LOG(warning) << "Couldn't find app image at path ["sv << app_image_path << ']';
|
||||
return DEFAULT_APP_IMAGE_PATH;
|
||||
}
|
||||
|
||||
// image is a png, and not in assets directory
|
||||
// return only "content-type" http header compatible image type
|
||||
return app_image_path;
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
calculate_sha256(const std::string &filename) {
|
||||
crypto::md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
if (!ctx) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Read file and update calculated SHA
|
||||
char buf[1024 * 16];
|
||||
std::ifstream file(filename, std::ifstream::binary);
|
||||
while (file.good()) {
|
||||
file.read(buf, sizeof(buf));
|
||||
if (!EVP_DigestUpdate(ctx.get(), buf, file.gcount())) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
|
||||
unsigned char result[SHA256_DIGEST_LENGTH];
|
||||
if (!EVP_DigestFinal_ex(ctx.get(), result, nullptr)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Transform byte-array to string
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::setfill('0');
|
||||
for (const auto &byte : result) {
|
||||
ss << std::setw(2) << (int) byte;
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
calculate_crc32(const std::string &input) {
|
||||
boost::crc_32_type result;
|
||||
result.process_bytes(input.data(), input.length());
|
||||
return result.checksum();
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string>
|
||||
calculate_app_id(const std::string &app_name, std::string app_image_path, int index) {
|
||||
// Generate id by hashing name with image data if present
|
||||
std::vector<std::string> to_hash;
|
||||
to_hash.push_back(app_name);
|
||||
auto file_path = validate_app_image_path(app_image_path);
|
||||
if (file_path != DEFAULT_APP_IMAGE_PATH) {
|
||||
auto file_hash = calculate_sha256(file_path);
|
||||
if (file_hash) {
|
||||
to_hash.push_back(file_hash.value());
|
||||
}
|
||||
else {
|
||||
// Fallback to just hashing image path
|
||||
to_hash.push_back(file_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Create combined strings for hash
|
||||
std::stringstream ss;
|
||||
for_each(to_hash.begin(), to_hash.end(), [&ss](const std::string &s) { ss << s; });
|
||||
auto input_no_index = ss.str();
|
||||
ss << index;
|
||||
auto input_with_index = ss.str();
|
||||
|
||||
// CRC32 then truncate to signed 32-bit range due to client limitations
|
||||
auto id_no_index = std::to_string(abs((int32_t) calculate_crc32(input_no_index)));
|
||||
auto id_with_index = std::to_string(abs((int32_t) calculate_crc32(input_with_index)));
|
||||
|
||||
return std::make_tuple(id_no_index, id_with_index);
|
||||
}
|
||||
|
||||
std::optional<proc::proc_t>
|
||||
parse(const std::string &file_name) {
|
||||
pt::ptree tree;
|
||||
|
||||
try {
|
||||
pt::read_json(file_name, tree);
|
||||
|
||||
auto &apps_node = tree.get_child("apps"s);
|
||||
auto &env_vars = tree.get_child("env"s);
|
||||
|
||||
auto this_env = boost::this_process::environment();
|
||||
|
||||
for (auto &[name, val] : env_vars) {
|
||||
this_env[name] = parse_env_val(this_env, val.get_value<std::string>());
|
||||
}
|
||||
|
||||
std::set<std::string> ids;
|
||||
std::vector<proc::ctx_t> apps;
|
||||
int i = 0;
|
||||
for (auto &[_, app_node] : apps_node) {
|
||||
proc::ctx_t ctx;
|
||||
|
||||
auto prep_nodes_opt = app_node.get_child_optional("prep-cmd"s);
|
||||
auto detached_nodes_opt = app_node.get_child_optional("detached"s);
|
||||
auto exclude_global_prep = app_node.get_optional<bool>("exclude-global-prep-cmd"s);
|
||||
auto output = app_node.get_optional<std::string>("output"s);
|
||||
auto name = parse_env_val(this_env, app_node.get<std::string>("name"s));
|
||||
auto cmd = app_node.get_optional<std::string>("cmd"s);
|
||||
auto image_path = app_node.get_optional<std::string>("image-path"s);
|
||||
auto working_dir = app_node.get_optional<std::string>("working-dir"s);
|
||||
auto elevated = app_node.get_optional<bool>("elevated"s);
|
||||
auto auto_detach = app_node.get_optional<bool>("auto-detach"s);
|
||||
|
||||
std::vector<proc::cmd_t> prep_cmds;
|
||||
if (!exclude_global_prep.value_or(false)) {
|
||||
prep_cmds.reserve(config::sunshine.prep_cmds.size());
|
||||
for (auto &prep_cmd : config::sunshine.prep_cmds) {
|
||||
auto do_cmd = parse_env_val(this_env, prep_cmd.do_cmd);
|
||||
auto undo_cmd = parse_env_val(this_env, prep_cmd.undo_cmd);
|
||||
|
||||
prep_cmds.emplace_back(
|
||||
std::move(do_cmd),
|
||||
std::move(undo_cmd),
|
||||
std::move(prep_cmd.elevated));
|
||||
}
|
||||
}
|
||||
|
||||
if (prep_nodes_opt) {
|
||||
auto &prep_nodes = *prep_nodes_opt;
|
||||
|
||||
prep_cmds.reserve(prep_cmds.size() + prep_nodes.size());
|
||||
for (auto &[_, prep_node] : prep_nodes) {
|
||||
auto do_cmd = prep_node.get_optional<std::string>("do"s);
|
||||
auto undo_cmd = prep_node.get_optional<std::string>("undo"s);
|
||||
auto elevated = prep_node.get_optional<bool>("elevated");
|
||||
|
||||
prep_cmds.emplace_back(
|
||||
parse_env_val(this_env, do_cmd.value_or("")),
|
||||
parse_env_val(this_env, undo_cmd.value_or("")),
|
||||
std::move(elevated.value_or(false)));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> detached;
|
||||
if (detached_nodes_opt) {
|
||||
auto &detached_nodes = *detached_nodes_opt;
|
||||
|
||||
detached.reserve(detached_nodes.size());
|
||||
for (auto &[_, detached_val] : detached_nodes) {
|
||||
detached.emplace_back(parse_env_val(this_env, detached_val.get_value<std::string>()));
|
||||
}
|
||||
}
|
||||
|
||||
if (output) {
|
||||
ctx.output = parse_env_val(this_env, *output);
|
||||
}
|
||||
|
||||
if (cmd) {
|
||||
ctx.cmd = parse_env_val(this_env, *cmd);
|
||||
}
|
||||
|
||||
if (working_dir) {
|
||||
ctx.working_dir = parse_env_val(this_env, *working_dir);
|
||||
}
|
||||
|
||||
if (image_path) {
|
||||
ctx.image_path = parse_env_val(this_env, *image_path);
|
||||
}
|
||||
|
||||
ctx.elevated = elevated.value_or(false);
|
||||
ctx.auto_detach = auto_detach.value_or(true);
|
||||
|
||||
auto possible_ids = calculate_app_id(name, ctx.image_path, i++);
|
||||
if (ids.count(std::get<0>(possible_ids)) == 0) {
|
||||
// Avoid using index to generate id if possible
|
||||
ctx.id = std::get<0>(possible_ids);
|
||||
}
|
||||
else {
|
||||
// Fallback to include index on collision
|
||||
ctx.id = std::get<1>(possible_ids);
|
||||
}
|
||||
ids.insert(ctx.id);
|
||||
|
||||
ctx.name = std::move(name);
|
||||
ctx.prep_cmds = std::move(prep_cmds);
|
||||
ctx.detached = std::move(detached);
|
||||
|
||||
apps.emplace_back(std::move(ctx));
|
||||
}
|
||||
|
||||
return proc::proc_t {
|
||||
std::move(this_env), std::move(apps)
|
||||
};
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(error) << e.what();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void
|
||||
refresh(const std::string &file_name) {
|
||||
auto proc_opt = proc::parse(file_name);
|
||||
|
||||
if (proc_opt) {
|
||||
proc = std::move(*proc_opt);
|
||||
}
|
||||
}
|
||||
} // namespace proc
|
||||
128
src/process.h
128
src/process.h
@ -1,128 +0,0 @@
|
||||
/**
|
||||
* @file src/process.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifndef __kernel_entry
|
||||
#define __kernel_entry
|
||||
#endif
|
||||
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/process.hpp>
|
||||
|
||||
#include "config.h"
|
||||
#include "platform/common.h"
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
|
||||
namespace proc {
|
||||
using file_t = util::safe_ptr_v2<FILE, int, fclose>;
|
||||
|
||||
typedef config::prep_cmd_t cmd_t;
|
||||
/**
|
||||
* pre_cmds -- guaranteed to be executed unless any of the commands fail.
|
||||
* detached -- commands detached from Sunshine
|
||||
* cmd -- Runs indefinitely until:
|
||||
* No session is running and a different set of commands it to be executed
|
||||
* Command exits
|
||||
* working_dir -- the process working directory. This is required for some games to run properly.
|
||||
* cmd_output --
|
||||
* empty -- The output of the commands are appended to the output of sunshine
|
||||
* "null" -- The output of the commands are discarded
|
||||
* filename -- The output of the commands are appended to filename
|
||||
*/
|
||||
struct ctx_t {
|
||||
std::vector<cmd_t> prep_cmds;
|
||||
|
||||
/**
|
||||
* Some applications, such as Steam,
|
||||
* either exit quickly, or keep running indefinitely.
|
||||
* Steam.exe is one such application.
|
||||
* That is why some applications need be run and forgotten about
|
||||
*/
|
||||
std::vector<std::string> detached;
|
||||
|
||||
std::string name;
|
||||
std::string cmd;
|
||||
std::string working_dir;
|
||||
std::string output;
|
||||
std::string image_path;
|
||||
std::string id;
|
||||
bool elevated;
|
||||
bool auto_detach;
|
||||
};
|
||||
|
||||
class proc_t {
|
||||
public:
|
||||
KITTY_DEFAULT_CONSTR_MOVE_THROW(proc_t)
|
||||
|
||||
proc_t(
|
||||
boost::process::environment &&env,
|
||||
std::vector<ctx_t> &&apps):
|
||||
_app_id(0),
|
||||
_env(std::move(env)),
|
||||
_apps(std::move(apps)) {}
|
||||
|
||||
int
|
||||
execute(int app_id, rtsp_stream::launch_session_t launch_session);
|
||||
|
||||
/**
|
||||
* @return _app_id if a process is running, otherwise returns 0
|
||||
*/
|
||||
int
|
||||
running();
|
||||
|
||||
~proc_t();
|
||||
|
||||
const std::vector<ctx_t> &
|
||||
get_apps() const;
|
||||
std::vector<ctx_t> &
|
||||
get_apps();
|
||||
std::string
|
||||
get_app_image(int app_id);
|
||||
std::string
|
||||
get_last_run_app_name();
|
||||
void
|
||||
terminate();
|
||||
|
||||
private:
|
||||
int _app_id;
|
||||
|
||||
boost::process::environment _env;
|
||||
std::vector<ctx_t> _apps;
|
||||
ctx_t _app;
|
||||
std::chrono::steady_clock::time_point _app_launch_time;
|
||||
|
||||
// If no command associated with _app_id, yet it's still running
|
||||
bool placebo {};
|
||||
|
||||
boost::process::child _process;
|
||||
boost::process::group _process_handle;
|
||||
|
||||
file_t _pipe;
|
||||
std::vector<cmd_t>::const_iterator _app_prep_it;
|
||||
std::vector<cmd_t>::const_iterator _app_prep_begin;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate a stable id based on name and image data
|
||||
* @return tuple of id calculated without index (for use if no collision) and one with
|
||||
*/
|
||||
std::tuple<std::string, std::string>
|
||||
calculate_app_id(const std::string &app_name, std::string app_image_path, int index);
|
||||
|
||||
std::string
|
||||
validate_app_image_path(std::string app_image_path);
|
||||
void
|
||||
refresh(const std::string &file_name);
|
||||
std::optional<proc::proc_t>
|
||||
parse(const std::string &file_name);
|
||||
|
||||
std::unique_ptr<platf::deinit_t>
|
||||
init();
|
||||
|
||||
extern proc_t proc;
|
||||
} // namespace proc
|
||||
@ -1,187 +0,0 @@
|
||||
/**
|
||||
* @file src/round_robin.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
|
||||
namespace round_robin_util {
|
||||
template <class V, class T>
|
||||
class it_wrap_t {
|
||||
public:
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
using value_type = V;
|
||||
using difference_type = V;
|
||||
using pointer = V *;
|
||||
using const_pointer = V const *;
|
||||
using reference = V &;
|
||||
using const_reference = V const &;
|
||||
|
||||
typedef T iterator;
|
||||
typedef std::ptrdiff_t diff_t;
|
||||
|
||||
iterator
|
||||
operator+=(diff_t step) {
|
||||
while (step-- > 0) {
|
||||
++_this();
|
||||
}
|
||||
|
||||
return _this();
|
||||
}
|
||||
|
||||
iterator
|
||||
operator-=(diff_t step) {
|
||||
while (step-- > 0) {
|
||||
--_this();
|
||||
}
|
||||
|
||||
return _this();
|
||||
}
|
||||
|
||||
iterator
|
||||
operator+(diff_t step) {
|
||||
iterator new_ = _this();
|
||||
|
||||
return new_ += step;
|
||||
}
|
||||
|
||||
iterator
|
||||
operator-(diff_t step) {
|
||||
iterator new_ = _this();
|
||||
|
||||
return new_ -= step;
|
||||
}
|
||||
|
||||
diff_t
|
||||
operator-(iterator first) {
|
||||
diff_t step = 0;
|
||||
while (first != _this()) {
|
||||
++step;
|
||||
++first;
|
||||
}
|
||||
|
||||
return step;
|
||||
}
|
||||
|
||||
iterator
|
||||
operator++() {
|
||||
_this().inc();
|
||||
return _this();
|
||||
}
|
||||
iterator
|
||||
operator--() {
|
||||
_this().dec();
|
||||
return _this();
|
||||
}
|
||||
|
||||
iterator
|
||||
operator++(int) {
|
||||
iterator new_ = _this();
|
||||
|
||||
++_this();
|
||||
|
||||
return new_;
|
||||
}
|
||||
|
||||
iterator
|
||||
operator--(int) {
|
||||
iterator new_ = _this();
|
||||
|
||||
--_this();
|
||||
|
||||
return new_;
|
||||
}
|
||||
|
||||
reference
|
||||
operator*() { return *_this().get(); }
|
||||
const_reference
|
||||
operator*() const { return *_this().get(); }
|
||||
|
||||
pointer
|
||||
operator->() { return &*_this(); }
|
||||
const_pointer
|
||||
operator->() const { return &*_this(); }
|
||||
|
||||
bool
|
||||
operator!=(const iterator &other) const {
|
||||
return !(_this() == other);
|
||||
}
|
||||
|
||||
bool
|
||||
operator<(const iterator &other) const {
|
||||
return !(_this() >= other);
|
||||
}
|
||||
|
||||
bool
|
||||
operator>=(const iterator &other) const {
|
||||
return _this() == other || _this() > other;
|
||||
}
|
||||
|
||||
bool
|
||||
operator<=(const iterator &other) const {
|
||||
return _this() == other || _this() < other;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(const iterator &other) const { return _this().eq(other); };
|
||||
bool
|
||||
operator>(const iterator &other) const { return _this().gt(other); }
|
||||
|
||||
private:
|
||||
iterator &
|
||||
_this() { return *static_cast<iterator *>(this); }
|
||||
const iterator &
|
||||
_this() const { return *static_cast<const iterator *>(this); }
|
||||
};
|
||||
|
||||
template <class V, class It>
|
||||
class round_robin_t: public it_wrap_t<V, round_robin_t<V, It>> {
|
||||
public:
|
||||
using iterator = It;
|
||||
using pointer = V *;
|
||||
|
||||
round_robin_t(iterator begin, iterator end):
|
||||
_begin(begin), _end(end), _pos(begin) {}
|
||||
|
||||
void
|
||||
inc() {
|
||||
++_pos;
|
||||
|
||||
if (_pos == _end) {
|
||||
_pos = _begin;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
dec() {
|
||||
if (_pos == _begin) {
|
||||
_pos = _end;
|
||||
}
|
||||
|
||||
--_pos;
|
||||
}
|
||||
|
||||
bool
|
||||
eq(const round_robin_t &other) const {
|
||||
return *_pos == *other._pos;
|
||||
}
|
||||
|
||||
pointer
|
||||
get() const {
|
||||
return &*_pos;
|
||||
}
|
||||
|
||||
private:
|
||||
It _begin;
|
||||
It _end;
|
||||
|
||||
It _pos;
|
||||
};
|
||||
|
||||
template <class V, class It>
|
||||
round_robin_t<V, It>
|
||||
make_round_robin(It begin, It end) {
|
||||
return round_robin_t<V, It>(begin, end);
|
||||
}
|
||||
} // namespace round_robin_util
|
||||
835
src/rtsp.cpp
835
src/rtsp.cpp
@ -1,835 +0,0 @@
|
||||
/**
|
||||
* @file src/rstp.cpp
|
||||
* @brief todo
|
||||
*/
|
||||
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
|
||||
|
||||
extern "C" {
|
||||
#include <moonlight-common-c/src/Rtsp.h>
|
||||
}
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include "config.h"
|
||||
#include "input.h"
|
||||
#include "main.h"
|
||||
#include "network.h"
|
||||
#include "rtsp.h"
|
||||
#include "stream.h"
|
||||
#include "sync.h"
|
||||
#include "video.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
using asio::ip::tcp;
|
||||
using asio::ip::udp;
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace rtsp_stream {
|
||||
void
|
||||
free_msg(PRTSP_MESSAGE msg) {
|
||||
freeMessage(msg);
|
||||
|
||||
delete msg;
|
||||
}
|
||||
|
||||
class rtsp_server_t;
|
||||
|
||||
using msg_t = util::safe_ptr<RTSP_MESSAGE, free_msg>;
|
||||
using cmd_func_t = std::function<void(rtsp_server_t *server, tcp::socket &, msg_t &&)>;
|
||||
|
||||
void
|
||||
print_msg(PRTSP_MESSAGE msg);
|
||||
void
|
||||
cmd_not_found(tcp::socket &sock, msg_t &&req);
|
||||
void
|
||||
respond(tcp::socket &sock, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload);
|
||||
|
||||
class socket_t: public std::enable_shared_from_this<socket_t> {
|
||||
public:
|
||||
socket_t(boost::asio::io_service &ios, std::function<void(tcp::socket &sock, msg_t &&)> &&handle_data_fn):
|
||||
handle_data_fn { std::move(handle_data_fn) }, sock { ios } {}
|
||||
|
||||
void
|
||||
read() {
|
||||
if (begin == std::end(msg_buf)) {
|
||||
BOOST_LOG(error) << "RTSP: read(): Exceeded maximum rtsp packet size: "sv << msg_buf.size();
|
||||
|
||||
respond(sock, nullptr, 400, "BAD REQUEST", 0, {});
|
||||
|
||||
sock.close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sock.async_read_some(
|
||||
boost::asio::buffer(begin, (std::size_t)(std::end(msg_buf) - begin)),
|
||||
boost::bind(
|
||||
&socket_t::handle_read, shared_from_this(),
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void
|
||||
read_payload() {
|
||||
if (begin == std::end(msg_buf)) {
|
||||
BOOST_LOG(error) << "RTSP: read_payload(): Exceeded maximum rtsp packet size: "sv << msg_buf.size();
|
||||
|
||||
respond(sock, nullptr, 400, "BAD REQUEST", 0, {});
|
||||
|
||||
sock.close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sock.async_read_some(
|
||||
boost::asio::buffer(begin, (std::size_t)(std::end(msg_buf) - begin)),
|
||||
boost::bind(
|
||||
&socket_t::handle_payload, shared_from_this(),
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
static void
|
||||
handle_payload(std::shared_ptr<socket_t> &socket, const boost::system::error_code &ec, std::size_t bytes) {
|
||||
BOOST_LOG(debug) << "handle_payload(): Handle read of size: "sv << bytes << " bytes"sv;
|
||||
|
||||
auto sock_close = util::fail_guard([&socket]() {
|
||||
boost::system::error_code ec;
|
||||
socket->sock.close(ec);
|
||||
|
||||
if (ec) {
|
||||
BOOST_LOG(error) << "RTSP: handle_payload(): Couldn't close tcp socket: "sv << ec.message();
|
||||
}
|
||||
});
|
||||
|
||||
if (ec) {
|
||||
BOOST_LOG(error) << "RTSP: handle_payload(): Couldn't read from tcp socket: "sv << ec.message();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto end = socket->begin + bytes;
|
||||
msg_t req { new msg_t::element_type {} };
|
||||
if (auto status = parseRtspMessage(req.get(), socket->msg_buf.data(), (std::size_t)(end - socket->msg_buf.data()))) {
|
||||
BOOST_LOG(error) << "Malformed RTSP message: ["sv << status << ']';
|
||||
|
||||
respond(socket->sock, nullptr, 400, "BAD REQUEST", req->sequenceNumber, {});
|
||||
return;
|
||||
}
|
||||
|
||||
sock_close.disable();
|
||||
|
||||
auto fg = util::fail_guard([&socket]() {
|
||||
socket->read_payload();
|
||||
});
|
||||
|
||||
auto content_length = 0;
|
||||
for (auto option = req->options; option != nullptr; option = option->next) {
|
||||
if ("Content-length"sv == option->option) {
|
||||
BOOST_LOG(debug) << "Found Content-Length: "sv << option->content << " bytes"sv;
|
||||
|
||||
// If content_length > bytes read, then we need to store current data read,
|
||||
// to be appended by the next read.
|
||||
std::string_view content { option->content };
|
||||
auto begin = std::find_if(std::begin(content), std::end(content), [](auto ch) { return (bool) std::isdigit(ch); });
|
||||
|
||||
content_length = util::from_chars(begin, std::end(content));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (end - socket->crlf >= content_length) {
|
||||
if (end - socket->crlf > content_length) {
|
||||
BOOST_LOG(warning) << "(end - socket->crlf) > content_length -- "sv << (std::size_t)(end - socket->crlf) << " > "sv << content_length;
|
||||
}
|
||||
|
||||
fg.disable();
|
||||
print_msg(req.get());
|
||||
|
||||
socket->handle_data(std::move(req));
|
||||
}
|
||||
|
||||
socket->begin = end;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_read(std::shared_ptr<socket_t> &socket, const boost::system::error_code &ec, std::size_t bytes) {
|
||||
BOOST_LOG(debug) << "handle_read(): Handle read of size: "sv << bytes << " bytes"sv;
|
||||
|
||||
if (ec) {
|
||||
BOOST_LOG(error) << "RTSP: handle_read(): Couldn't read from tcp socket: "sv << ec.message();
|
||||
|
||||
boost::system::error_code ec;
|
||||
socket->sock.close(ec);
|
||||
|
||||
if (ec) {
|
||||
BOOST_LOG(error) << "RTSP: handle_read(): Couldn't close tcp socket: "sv << ec.message();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto fg = util::fail_guard([&socket]() {
|
||||
socket->read();
|
||||
});
|
||||
|
||||
auto begin = std::max(socket->begin - 4, socket->begin);
|
||||
auto buf_size = bytes + (begin - socket->begin);
|
||||
auto end = begin + buf_size;
|
||||
|
||||
constexpr auto needle = "\r\n\r\n"sv;
|
||||
|
||||
auto it = std::search(begin, begin + buf_size, std::begin(needle), std::end(needle));
|
||||
if (it == end) {
|
||||
socket->begin = end;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Emulate read completion for payload data
|
||||
socket->begin = it + needle.size();
|
||||
socket->crlf = socket->begin;
|
||||
buf_size = end - socket->begin;
|
||||
|
||||
fg.disable();
|
||||
handle_payload(socket, ec, buf_size);
|
||||
}
|
||||
|
||||
void
|
||||
handle_data(msg_t &&req) {
|
||||
handle_data_fn(sock, std::move(req));
|
||||
}
|
||||
|
||||
std::function<void(tcp::socket &sock, msg_t &&)> handle_data_fn;
|
||||
|
||||
tcp::socket sock;
|
||||
|
||||
std::array<char, 2048> msg_buf;
|
||||
|
||||
char *crlf;
|
||||
char *begin = msg_buf.data();
|
||||
};
|
||||
|
||||
class rtsp_server_t {
|
||||
public:
|
||||
~rtsp_server_t() {
|
||||
clear();
|
||||
}
|
||||
|
||||
int
|
||||
bind(net::af_e af, std::uint16_t port, boost::system::error_code &ec) {
|
||||
{
|
||||
auto lg = _session_slots.lock();
|
||||
|
||||
_session_slots->resize(config::stream.channels);
|
||||
_slot_count = config::stream.channels;
|
||||
}
|
||||
|
||||
acceptor.open(af == net::IPV4 ? tcp::v4() : tcp::v6(), ec);
|
||||
if (ec) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
acceptor.set_option(boost::asio::socket_base::reuse_address { true });
|
||||
|
||||
acceptor.bind(tcp::endpoint(af == net::IPV4 ? tcp::v4() : tcp::v6(), port), ec);
|
||||
if (ec) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
acceptor.listen(4096, ec);
|
||||
if (ec) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
next_socket = std::make_shared<socket_t>(ios, [this](tcp::socket &sock, msg_t &&msg) {
|
||||
handle_msg(sock, std::move(msg));
|
||||
});
|
||||
|
||||
acceptor.async_accept(next_socket->sock, [this](const auto &ec) {
|
||||
handle_accept(ec);
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <class T, class X>
|
||||
void
|
||||
iterate(std::chrono::duration<T, X> timeout) {
|
||||
ios.run_one_for(timeout);
|
||||
}
|
||||
|
||||
void
|
||||
handle_msg(tcp::socket &sock, msg_t &&req) {
|
||||
auto func = _map_cmd_cb.find(req->message.request.command);
|
||||
if (func != std::end(_map_cmd_cb)) {
|
||||
func->second(this, sock, std::move(req));
|
||||
}
|
||||
else {
|
||||
cmd_not_found(sock, std::move(req));
|
||||
}
|
||||
|
||||
sock.shutdown(boost::asio::socket_base::shutdown_type::shutdown_both);
|
||||
}
|
||||
|
||||
void
|
||||
handle_accept(const boost::system::error_code &ec) {
|
||||
if (ec) {
|
||||
BOOST_LOG(error) << "Couldn't accept incoming connections: "sv << ec.message();
|
||||
|
||||
// Stop server
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
auto socket = std::move(next_socket);
|
||||
socket->read();
|
||||
|
||||
next_socket = std::make_shared<socket_t>(ios, [this](tcp::socket &sock, msg_t &&msg) {
|
||||
handle_msg(sock, std::move(msg));
|
||||
});
|
||||
|
||||
acceptor.async_accept(next_socket->sock, [this](const auto &ec) {
|
||||
handle_accept(ec);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
map(const std::string_view &type, cmd_func_t cb) {
|
||||
_map_cmd_cb.emplace(type, std::move(cb));
|
||||
}
|
||||
|
||||
void
|
||||
session_raise(rtsp_stream::launch_session_t launch_session) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// If a launch event is still pending, don't overwrite it.
|
||||
if (raised_timeout > now && launch_event.peek()) {
|
||||
return;
|
||||
}
|
||||
raised_timeout = now + 10s;
|
||||
|
||||
--_slot_count;
|
||||
launch_event.raise(launch_session);
|
||||
}
|
||||
|
||||
int
|
||||
session_count() const {
|
||||
return config::stream.channels - _slot_count;
|
||||
}
|
||||
|
||||
safe::event_t<rtsp_stream::launch_session_t> launch_event;
|
||||
|
||||
void
|
||||
clear(bool all = true) {
|
||||
// if a launch event timed out --> Remove it.
|
||||
if (raised_timeout < std::chrono::steady_clock::now()) {
|
||||
auto discarded = launch_event.pop(0s);
|
||||
if (discarded) {
|
||||
++_slot_count;
|
||||
}
|
||||
}
|
||||
|
||||
auto lg = _session_slots.lock();
|
||||
|
||||
for (auto &slot : *_session_slots) {
|
||||
if (slot && (all || stream::session::state(*slot) == stream::session::state_e::STOPPING)) {
|
||||
stream::session::stop(*slot);
|
||||
stream::session::join(*slot);
|
||||
|
||||
slot.reset();
|
||||
|
||||
++_slot_count;
|
||||
}
|
||||
}
|
||||
|
||||
if (all && !ios.stopped()) {
|
||||
ios.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
clear(std::shared_ptr<stream::session_t> *session_p) {
|
||||
auto lg = _session_slots.lock();
|
||||
|
||||
session_p->reset();
|
||||
|
||||
++_slot_count;
|
||||
}
|
||||
|
||||
std::shared_ptr<stream::session_t> *
|
||||
accept(std::shared_ptr<stream::session_t> &session) {
|
||||
auto lg = _session_slots.lock();
|
||||
|
||||
for (auto &slot : *_session_slots) {
|
||||
if (!slot) {
|
||||
slot = session;
|
||||
return &slot;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string_view, cmd_func_t> _map_cmd_cb;
|
||||
|
||||
sync_util::sync_t<std::vector<std::shared_ptr<stream::session_t>>> _session_slots;
|
||||
|
||||
std::chrono::steady_clock::time_point raised_timeout;
|
||||
int _slot_count;
|
||||
|
||||
boost::asio::io_service ios;
|
||||
tcp::acceptor acceptor { ios };
|
||||
|
||||
std::shared_ptr<socket_t> next_socket;
|
||||
};
|
||||
|
||||
rtsp_server_t server {};
|
||||
|
||||
void
|
||||
launch_session_raise(rtsp_stream::launch_session_t launch_session) {
|
||||
server.session_raise(launch_session);
|
||||
}
|
||||
|
||||
int
|
||||
session_count() {
|
||||
// Ensure session_count is up-to-date
|
||||
server.clear(false);
|
||||
|
||||
return server.session_count();
|
||||
}
|
||||
|
||||
int
|
||||
send(tcp::socket &sock, const std::string_view &sv) {
|
||||
std::size_t bytes_send = 0;
|
||||
|
||||
while (bytes_send != sv.size()) {
|
||||
boost::system::error_code ec;
|
||||
bytes_send += sock.send(boost::asio::buffer(sv.substr(bytes_send)), 0, ec);
|
||||
|
||||
if (ec) {
|
||||
BOOST_LOG(error) << "RTSP: Couldn't send data over tcp socket: "sv << ec.message();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
respond(tcp::socket &sock, msg_t &resp) {
|
||||
auto payload = std::make_pair(resp->payload, resp->payloadLength);
|
||||
|
||||
// Restore response message for proper destruction
|
||||
auto lg = util::fail_guard([&]() {
|
||||
resp->payload = payload.first;
|
||||
resp->payloadLength = payload.second;
|
||||
});
|
||||
|
||||
resp->payload = nullptr;
|
||||
resp->payloadLength = 0;
|
||||
|
||||
int serialized_len;
|
||||
util::c_ptr<char> raw_resp { serializeRtspMessage(resp.get(), &serialized_len) };
|
||||
BOOST_LOG(debug)
|
||||
<< "---Begin Response---"sv << std::endl
|
||||
<< std::string_view { raw_resp.get(), (std::size_t) serialized_len } << std::endl
|
||||
<< std::string_view { payload.first, (std::size_t) payload.second } << std::endl
|
||||
<< "---End Response---"sv << std::endl;
|
||||
|
||||
std::string_view tmp_resp { raw_resp.get(), (size_t) serialized_len };
|
||||
|
||||
if (send(sock, tmp_resp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
send(sock, std::string_view { payload.first, (std::size_t) payload.second });
|
||||
}
|
||||
|
||||
void
|
||||
respond(tcp::socket &sock, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload) {
|
||||
msg_t resp { new msg_t::element_type };
|
||||
createRtspResponse(resp.get(), nullptr, 0, const_cast<char *>("RTSP/1.0"), statuscode, const_cast<char *>(status_msg), seqn, options, const_cast<char *>(payload.data()), (int) payload.size());
|
||||
|
||||
respond(sock, resp);
|
||||
}
|
||||
|
||||
void
|
||||
cmd_not_found(tcp::socket &sock, msg_t &&req) {
|
||||
respond(sock, nullptr, 404, "NOT FOUND", req->sequenceNumber, {});
|
||||
}
|
||||
|
||||
void
|
||||
cmd_option(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) {
|
||||
OPTION_ITEM option {};
|
||||
|
||||
// I know these string literals will not be modified
|
||||
option.option = const_cast<char *>("CSeq");
|
||||
|
||||
auto seqn_str = std::to_string(req->sequenceNumber);
|
||||
option.content = const_cast<char *>(seqn_str.c_str());
|
||||
|
||||
respond(sock, &option, 200, "OK", req->sequenceNumber, {});
|
||||
}
|
||||
|
||||
void
|
||||
cmd_describe(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) {
|
||||
OPTION_ITEM option {};
|
||||
|
||||
// I know these string literals will not be modified
|
||||
option.option = const_cast<char *>("CSeq");
|
||||
|
||||
auto seqn_str = std::to_string(req->sequenceNumber);
|
||||
option.content = const_cast<char *>(seqn_str.c_str());
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
// Tell the client about our supported features
|
||||
ss << "a=x-ss-general.featureFlags: " << (uint32_t) platf::get_capabilities() << std::endl;
|
||||
|
||||
if (video::active_hevc_mode != 1) {
|
||||
ss << "sprop-parameter-sets=AAAAAU"sv << std::endl;
|
||||
}
|
||||
|
||||
if (video::last_encoder_probe_supported_ref_frames_invalidation) {
|
||||
ss << "x-nv-video[0].refPicInvalidation=1"sv << std::endl;
|
||||
}
|
||||
|
||||
if (video::active_av1_mode != 1) {
|
||||
ss << "a=rtpmap:98 AV1/90000"sv << std::endl;
|
||||
}
|
||||
|
||||
for (int x = 0; x < audio::MAX_STREAM_CONFIG; ++x) {
|
||||
auto &stream_config = audio::stream_configs[x];
|
||||
std::uint8_t mapping[platf::speaker::MAX_SPEAKERS];
|
||||
|
||||
auto mapping_p = stream_config.mapping;
|
||||
|
||||
/**
|
||||
* GFE advertises incorrect mapping for normal quality configurations,
|
||||
* as a result, Moonlight rotates all channels from index '3' to the right
|
||||
* To work around this, rotate channels to the left from index '3'
|
||||
*/
|
||||
if (x == audio::SURROUND51 || x == audio::SURROUND71) {
|
||||
std::copy_n(mapping_p, stream_config.channelCount, mapping);
|
||||
std::rotate(mapping + 3, mapping + 4, mapping + audio::MAX_STREAM_CONFIG);
|
||||
|
||||
mapping_p = mapping;
|
||||
}
|
||||
|
||||
ss << "a=fmtp:97 surround-params="sv << stream_config.channelCount << stream_config.streams << stream_config.coupledStreams;
|
||||
|
||||
std::for_each_n(mapping_p, stream_config.channelCount, [&ss](std::uint8_t digit) {
|
||||
ss << (char) (digit + '0');
|
||||
});
|
||||
|
||||
ss << std::endl;
|
||||
}
|
||||
|
||||
respond(sock, &option, 200, "OK", req->sequenceNumber, ss.str());
|
||||
}
|
||||
|
||||
void
|
||||
cmd_setup(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) {
|
||||
OPTION_ITEM options[3] {};
|
||||
|
||||
auto &seqn = options[0];
|
||||
auto &session_option = options[1];
|
||||
auto &port_option = options[2];
|
||||
|
||||
seqn.option = const_cast<char *>("CSeq");
|
||||
|
||||
auto seqn_str = std::to_string(req->sequenceNumber);
|
||||
seqn.content = const_cast<char *>(seqn_str.c_str());
|
||||
|
||||
std::string_view target { req->message.request.target };
|
||||
auto begin = std::find(std::begin(target), std::end(target), '=') + 1;
|
||||
auto end = std::find(begin, std::end(target), '/');
|
||||
std::string_view type { begin, (size_t) std::distance(begin, end) };
|
||||
|
||||
std::uint16_t port;
|
||||
if (type == "audio"sv) {
|
||||
port = map_port(stream::AUDIO_STREAM_PORT);
|
||||
}
|
||||
else if (type == "video"sv) {
|
||||
port = map_port(stream::VIDEO_STREAM_PORT);
|
||||
}
|
||||
else if (type == "control"sv) {
|
||||
port = map_port(stream::CONTROL_PORT);
|
||||
}
|
||||
else {
|
||||
cmd_not_found(sock, std::move(req));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
seqn.next = &session_option;
|
||||
|
||||
session_option.option = const_cast<char *>("Session");
|
||||
session_option.content = const_cast<char *>("DEADBEEFCAFE;timeout = 90");
|
||||
|
||||
session_option.next = &port_option;
|
||||
|
||||
// Moonlight merely requires 'server_port=<port>'
|
||||
auto port_value = "server_port=" + std::to_string(port);
|
||||
|
||||
port_option.option = const_cast<char *>("Transport");
|
||||
port_option.content = port_value.data();
|
||||
|
||||
respond(sock, &seqn, 200, "OK", req->sequenceNumber, {});
|
||||
}
|
||||
|
||||
void
|
||||
cmd_announce(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) {
|
||||
OPTION_ITEM option {};
|
||||
|
||||
// I know these string literals will not be modified
|
||||
option.option = const_cast<char *>("CSeq");
|
||||
|
||||
auto seqn_str = std::to_string(req->sequenceNumber);
|
||||
option.content = const_cast<char *>(seqn_str.c_str());
|
||||
|
||||
if (!server->launch_event.peek()) {
|
||||
// /launch has not been used
|
||||
|
||||
respond(sock, &option, 503, "Service Unavailable", req->sequenceNumber, {});
|
||||
return;
|
||||
}
|
||||
auto launch_session { server->launch_event.pop() };
|
||||
|
||||
std::string_view payload { req->payload, (size_t) req->payloadLength };
|
||||
|
||||
std::vector<std::string_view> lines;
|
||||
|
||||
auto whitespace = [](char ch) {
|
||||
return ch == '\n' || ch == '\r';
|
||||
};
|
||||
|
||||
{
|
||||
auto pos = std::begin(payload);
|
||||
auto begin = pos;
|
||||
while (pos != std::end(payload)) {
|
||||
if (whitespace(*pos++)) {
|
||||
lines.emplace_back(begin, pos - begin - 1);
|
||||
|
||||
while (pos != std::end(payload) && whitespace(*pos)) { ++pos; }
|
||||
begin = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view client;
|
||||
std::unordered_map<std::string_view, std::string_view> args;
|
||||
|
||||
for (auto line : lines) {
|
||||
auto type = line.substr(0, 2);
|
||||
if (type == "s="sv) {
|
||||
client = line.substr(2);
|
||||
}
|
||||
else if (type == "a=") {
|
||||
auto pos = line.find(':');
|
||||
|
||||
auto name = line.substr(2, pos - 2);
|
||||
auto val = line.substr(pos + 1);
|
||||
|
||||
if (val[val.size() - 1] == ' ') {
|
||||
val = val.substr(0, val.size() - 1);
|
||||
}
|
||||
args.emplace(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize any omitted parameters to defaults
|
||||
args.try_emplace("x-nv-video[0].encoderCscMode"sv, "0"sv);
|
||||
args.try_emplace("x-nv-vqos[0].bitStreamFormat"sv, "0"sv);
|
||||
args.try_emplace("x-nv-video[0].dynamicRangeMode"sv, "0"sv);
|
||||
args.try_emplace("x-nv-aqos.packetDuration"sv, "5"sv);
|
||||
args.try_emplace("x-nv-general.useReliableUdp"sv, "1"sv);
|
||||
args.try_emplace("x-nv-vqos[0].fec.minRequiredFecPackets"sv, "0"sv);
|
||||
args.try_emplace("x-nv-general.featureFlags"sv, "135"sv);
|
||||
args.try_emplace("x-nv-vqos[0].qosTrafficType"sv, "5"sv);
|
||||
args.try_emplace("x-nv-aqos.qosTrafficType"sv, "4"sv);
|
||||
|
||||
stream::config_t config;
|
||||
|
||||
config.audio.flags[audio::config_t::HOST_AUDIO] = launch_session->host_audio;
|
||||
try {
|
||||
config.audio.channels = util::from_view(args.at("x-nv-audio.surround.numChannels"sv));
|
||||
config.audio.mask = util::from_view(args.at("x-nv-audio.surround.channelMask"sv));
|
||||
config.audio.packetDuration = util::from_view(args.at("x-nv-aqos.packetDuration"sv));
|
||||
|
||||
config.audio.flags[audio::config_t::HIGH_QUALITY] =
|
||||
util::from_view(args.at("x-nv-audio.surround.AudioQuality"sv));
|
||||
|
||||
config.controlProtocolType = util::from_view(args.at("x-nv-general.useReliableUdp"sv));
|
||||
config.packetsize = util::from_view(args.at("x-nv-video[0].packetSize"sv));
|
||||
config.minRequiredFecPackets = util::from_view(args.at("x-nv-vqos[0].fec.minRequiredFecPackets"sv));
|
||||
config.featureFlags = util::from_view(args.at("x-nv-general.featureFlags"sv));
|
||||
config.audioQosType = util::from_view(args.at("x-nv-aqos.qosTrafficType"sv));
|
||||
config.videoQosType = util::from_view(args.at("x-nv-vqos[0].qosTrafficType"sv));
|
||||
|
||||
config.monitor.height = util::from_view(args.at("x-nv-video[0].clientViewportHt"sv));
|
||||
config.monitor.width = util::from_view(args.at("x-nv-video[0].clientViewportWd"sv));
|
||||
config.monitor.framerate = util::from_view(args.at("x-nv-video[0].maxFPS"sv));
|
||||
config.monitor.bitrate = util::from_view(args.at("x-nv-vqos[0].bw.maximumBitrateKbps"sv));
|
||||
config.monitor.slicesPerFrame = util::from_view(args.at("x-nv-video[0].videoEncoderSlicesPerFrame"sv));
|
||||
config.monitor.numRefFrames = util::from_view(args.at("x-nv-video[0].maxNumReferenceFrames"sv));
|
||||
config.monitor.encoderCscMode = util::from_view(args.at("x-nv-video[0].encoderCscMode"sv));
|
||||
config.monitor.videoFormat = util::from_view(args.at("x-nv-vqos[0].bitStreamFormat"sv));
|
||||
config.monitor.dynamicRange = util::from_view(args.at("x-nv-video[0].dynamicRangeMode"sv));
|
||||
}
|
||||
catch (std::out_of_range &) {
|
||||
respond(sock, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
|
||||
return;
|
||||
}
|
||||
|
||||
// When using stereo audio, the audio quality is (strangely) indicated by whether the Host field
|
||||
// in the RTSP message matches a local interface's IP address. Fortunately, Moonlight always sends
|
||||
// 0.0.0.0 when it wants low quality, so it is easy to check without enumerating interfaces.
|
||||
if (config.audio.channels == 2) {
|
||||
for (auto option = req->options; option != nullptr; option = option->next) {
|
||||
if ("Host"sv == option->option) {
|
||||
std::string_view content { option->content };
|
||||
BOOST_LOG(debug) << "Found Host: "sv << content;
|
||||
config.audio.flags[audio::config_t::HIGH_QUALITY] = (content.find("0.0.0.0"sv) == std::string::npos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.monitor.videoFormat == 1 && video::active_hevc_mode == 1) {
|
||||
BOOST_LOG(warning) << "HEVC is disabled, yet the client requested HEVC"sv;
|
||||
|
||||
respond(sock, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.monitor.videoFormat == 2 && video::active_av1_mode == 1) {
|
||||
BOOST_LOG(warning) << "AV1 is disabled, yet the client requested AV1"sv;
|
||||
|
||||
respond(sock, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
|
||||
return;
|
||||
}
|
||||
|
||||
auto session = stream::session::alloc(config, launch_session->gcm_key, launch_session->iv);
|
||||
|
||||
auto slot = server->accept(session);
|
||||
if (!slot) {
|
||||
BOOST_LOG(info) << "Ran out of slots for client from ["sv << ']';
|
||||
|
||||
respond(sock, &option, 503, "Service Unavailable", req->sequenceNumber, {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (stream::session::start(*session, sock.remote_endpoint().address().to_string())) {
|
||||
BOOST_LOG(error) << "Failed to start a streaming session"sv;
|
||||
|
||||
server->clear(slot);
|
||||
respond(sock, &option, 500, "Internal Server Error", req->sequenceNumber, {});
|
||||
return;
|
||||
}
|
||||
|
||||
respond(sock, &option, 200, "OK", req->sequenceNumber, {});
|
||||
}
|
||||
|
||||
void
|
||||
cmd_play(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) {
|
||||
OPTION_ITEM option {};
|
||||
|
||||
// I know these string literals will not be modified
|
||||
option.option = const_cast<char *>("CSeq");
|
||||
|
||||
auto seqn_str = std::to_string(req->sequenceNumber);
|
||||
option.content = const_cast<char *>(seqn_str.c_str());
|
||||
|
||||
respond(sock, &option, 200, "OK", req->sequenceNumber, {});
|
||||
}
|
||||
|
||||
void
|
||||
rtpThread() {
|
||||
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
||||
auto broadcast_shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
|
||||
|
||||
server.map("OPTIONS"sv, &cmd_option);
|
||||
server.map("DESCRIBE"sv, &cmd_describe);
|
||||
server.map("SETUP"sv, &cmd_setup);
|
||||
server.map("ANNOUNCE"sv, &cmd_announce);
|
||||
|
||||
server.map("PLAY"sv, &cmd_play);
|
||||
|
||||
boost::system::error_code ec;
|
||||
if (server.bind(net::af_from_enum_string(config::sunshine.address_family), map_port(rtsp_stream::RTSP_SETUP_PORT), ec)) {
|
||||
BOOST_LOG(fatal) << "Couldn't bind RTSP server to port ["sv << map_port(rtsp_stream::RTSP_SETUP_PORT) << "], " << ec.message();
|
||||
shutdown_event->raise(true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
while (!shutdown_event->peek()) {
|
||||
server.iterate(std::min(500ms, config::stream.ping_timeout));
|
||||
|
||||
if (broadcast_shutdown_event->peek()) {
|
||||
server.clear();
|
||||
}
|
||||
else {
|
||||
// cleanup all stopped sessions
|
||||
server.clear(false);
|
||||
}
|
||||
}
|
||||
|
||||
server.clear();
|
||||
}
|
||||
|
||||
void
|
||||
print_msg(PRTSP_MESSAGE msg) {
|
||||
std::string_view type = msg->type == TYPE_RESPONSE ? "RESPONSE"sv : "REQUEST"sv;
|
||||
|
||||
std::string_view payload { msg->payload, (size_t) msg->payloadLength };
|
||||
std::string_view protocol { msg->protocol };
|
||||
auto seqnm = msg->sequenceNumber;
|
||||
std::string_view messageBuffer { msg->messageBuffer };
|
||||
|
||||
BOOST_LOG(debug) << "type ["sv << type << ']';
|
||||
BOOST_LOG(debug) << "sequence number ["sv << seqnm << ']';
|
||||
BOOST_LOG(debug) << "protocol :: "sv << protocol;
|
||||
BOOST_LOG(debug) << "payload :: "sv << payload;
|
||||
|
||||
if (msg->type == TYPE_RESPONSE) {
|
||||
auto &resp = msg->message.response;
|
||||
|
||||
auto statuscode = resp.statusCode;
|
||||
std::string_view status { resp.statusString };
|
||||
|
||||
BOOST_LOG(debug) << "statuscode :: "sv << statuscode;
|
||||
BOOST_LOG(debug) << "status :: "sv << status;
|
||||
}
|
||||
else {
|
||||
auto &req = msg->message.request;
|
||||
|
||||
std::string_view command { req.command };
|
||||
std::string_view target { req.target };
|
||||
|
||||
BOOST_LOG(debug) << "command :: "sv << command;
|
||||
BOOST_LOG(debug) << "target :: "sv << target;
|
||||
}
|
||||
|
||||
for (auto option = msg->options; option != nullptr; option = option->next) {
|
||||
std::string_view content { option->content };
|
||||
std::string_view name { option->option };
|
||||
|
||||
BOOST_LOG(debug) << name << " :: "sv << content;
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << "---Begin MessageBuffer---"sv << std::endl
|
||||
<< messageBuffer << std::endl
|
||||
<< "---End MessageBuffer---"sv << std::endl;
|
||||
}
|
||||
} // namespace rtsp_stream
|
||||
39
src/rtsp.h
39
src/rtsp.h
@ -1,39 +0,0 @@
|
||||
/**
|
||||
* @file src/rtsp.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "crypto.h"
|
||||
#include "thread_safe.h"
|
||||
|
||||
namespace rtsp_stream {
|
||||
constexpr auto RTSP_SETUP_PORT = 21;
|
||||
|
||||
struct launch_session_t {
|
||||
crypto::aes_t gcm_key;
|
||||
crypto::aes_t iv;
|
||||
|
||||
bool host_audio;
|
||||
std::string unique_id;
|
||||
int width;
|
||||
int height;
|
||||
int fps;
|
||||
int gcmap;
|
||||
int appid;
|
||||
int surround_info;
|
||||
bool enable_hdr;
|
||||
bool enable_sops;
|
||||
};
|
||||
|
||||
void
|
||||
launch_session_raise(launch_session_t launch_session);
|
||||
int
|
||||
session_count();
|
||||
|
||||
void
|
||||
rtpThread();
|
||||
|
||||
} // namespace rtsp_stream
|
||||
1850
src/stream.cpp
1850
src/stream.cpp
File diff suppressed because it is too large
Load Diff
53
src/stream.h
53
src/stream.h
@ -1,53 +0,0 @@
|
||||
/**
|
||||
* @file src/stream.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include "audio.h"
|
||||
#include "crypto.h"
|
||||
#include "video.h"
|
||||
|
||||
namespace stream {
|
||||
constexpr auto VIDEO_STREAM_PORT = 9;
|
||||
constexpr auto CONTROL_PORT = 10;
|
||||
constexpr auto AUDIO_STREAM_PORT = 11;
|
||||
|
||||
struct session_t;
|
||||
struct config_t {
|
||||
audio::config_t audio;
|
||||
video::config_t monitor;
|
||||
|
||||
int packetsize;
|
||||
int minRequiredFecPackets;
|
||||
int featureFlags;
|
||||
int controlProtocolType;
|
||||
int audioQosType;
|
||||
int videoQosType;
|
||||
|
||||
std::optional<int> gcmap;
|
||||
};
|
||||
void videoThread(session_t *session);
|
||||
|
||||
namespace session {
|
||||
enum class state_e : int {
|
||||
STOPPED,
|
||||
STOPPING,
|
||||
STARTING,
|
||||
RUNNING,
|
||||
};
|
||||
|
||||
std::shared_ptr<session_t>
|
||||
alloc(config_t &config, crypto::aes_t &gcm_key, crypto::aes_t &iv);
|
||||
int
|
||||
start(session_t &session, const std::string &addr_string);
|
||||
void
|
||||
stop(session_t &session);
|
||||
void
|
||||
join(session_t &session);
|
||||
state_e
|
||||
state(session_t &session);
|
||||
} // namespace session
|
||||
} // namespace stream
|
||||
262
src/task_pool.h
262
src/task_pool.h
@ -1,262 +0,0 @@
|
||||
/**
|
||||
* @file src/task_pool.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "move_by_copy.h"
|
||||
#include "utility.h"
|
||||
namespace task_pool_util {
|
||||
|
||||
class _ImplBase {
|
||||
public:
|
||||
// _unique_base_type _this_ptr;
|
||||
|
||||
inline virtual ~_ImplBase() = default;
|
||||
|
||||
virtual void
|
||||
run() = 0;
|
||||
};
|
||||
|
||||
template <class Function>
|
||||
class _Impl: public _ImplBase {
|
||||
Function _func;
|
||||
|
||||
public:
|
||||
_Impl(Function &&f):
|
||||
_func(std::forward<Function>(f)) {}
|
||||
|
||||
void
|
||||
run() override {
|
||||
_func();
|
||||
}
|
||||
};
|
||||
|
||||
class TaskPool {
|
||||
public:
|
||||
typedef std::unique_ptr<_ImplBase> __task;
|
||||
typedef _ImplBase *task_id_t;
|
||||
|
||||
typedef std::chrono::steady_clock::time_point __time_point;
|
||||
|
||||
template <class R>
|
||||
class timer_task_t {
|
||||
public:
|
||||
task_id_t task_id;
|
||||
std::future<R> future;
|
||||
|
||||
timer_task_t(task_id_t task_id, std::future<R> &future):
|
||||
task_id { task_id }, future { std::move(future) } {}
|
||||
};
|
||||
|
||||
protected:
|
||||
std::deque<__task> _tasks;
|
||||
std::vector<std::pair<__time_point, __task>> _timer_tasks;
|
||||
std::mutex _task_mutex;
|
||||
|
||||
public:
|
||||
TaskPool() = default;
|
||||
TaskPool(TaskPool &&other) noexcept:
|
||||
_tasks { std::move(other._tasks) }, _timer_tasks { std::move(other._timer_tasks) } {}
|
||||
|
||||
TaskPool &
|
||||
operator=(TaskPool &&other) noexcept {
|
||||
std::swap(_tasks, other._tasks);
|
||||
std::swap(_timer_tasks, other._timer_tasks);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class Function, class... Args>
|
||||
auto
|
||||
push(Function &&newTask, Args &&...args) {
|
||||
static_assert(std::is_invocable_v<Function, Args &&...>, "arguments don't match the function");
|
||||
|
||||
using __return = std::invoke_result_t<Function, Args &&...>;
|
||||
using task_t = std::packaged_task<__return()>;
|
||||
|
||||
auto bind = [task = std::forward<Function>(newTask), tuple_args = std::make_tuple(std::forward<Args>(args)...)]() mutable {
|
||||
return std::apply(task, std::move(tuple_args));
|
||||
};
|
||||
|
||||
task_t task(std::move(bind));
|
||||
|
||||
auto future = task.get_future();
|
||||
|
||||
std::lock_guard<std::mutex> lg(_task_mutex);
|
||||
_tasks.emplace_back(toRunnable(std::move(task)));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
void
|
||||
pushDelayed(std::pair<__time_point, __task> &&task) {
|
||||
std::lock_guard lg(_task_mutex);
|
||||
|
||||
auto it = _timer_tasks.cbegin();
|
||||
for (; it < _timer_tasks.cend(); ++it) {
|
||||
if (std::get<0>(*it) < task.first) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_timer_tasks.emplace(it, task.first, std::move(task.second));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an id to potentially delay the task.
|
||||
*/
|
||||
template <class Function, class X, class Y, class... Args>
|
||||
auto
|
||||
pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&...args) {
|
||||
static_assert(std::is_invocable_v<Function, Args &&...>, "arguments don't match the function");
|
||||
|
||||
using __return = std::invoke_result_t<Function, Args &&...>;
|
||||
using task_t = std::packaged_task<__return()>;
|
||||
|
||||
__time_point time_point;
|
||||
if constexpr (std::is_floating_point_v<X>) {
|
||||
time_point = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
|
||||
}
|
||||
else {
|
||||
time_point = std::chrono::steady_clock::now() + duration;
|
||||
}
|
||||
|
||||
auto bind = [task = std::forward<Function>(newTask), tuple_args = std::make_tuple(std::forward<Args>(args)...)]() mutable {
|
||||
return std::apply(task, std::move(tuple_args));
|
||||
};
|
||||
|
||||
task_t task(std::move(bind));
|
||||
|
||||
auto future = task.get_future();
|
||||
auto runnable = toRunnable(std::move(task));
|
||||
|
||||
task_id_t task_id = &*runnable;
|
||||
|
||||
pushDelayed(std::pair { time_point, std::move(runnable) });
|
||||
|
||||
return timer_task_t<__return> { task_id, future };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param task_id The id of the task to delay.
|
||||
* @param duration The delay before executing the task.
|
||||
*/
|
||||
template <class X, class Y>
|
||||
void
|
||||
delay(task_id_t task_id, std::chrono::duration<X, Y> duration) {
|
||||
std::lock_guard<std::mutex> lg(_task_mutex);
|
||||
|
||||
auto it = _timer_tasks.begin();
|
||||
for (; it < _timer_tasks.cend(); ++it) {
|
||||
const __task &task = std::get<1>(*it);
|
||||
|
||||
if (&*task == task_id) {
|
||||
std::get<0>(*it) = std::chrono::steady_clock::now() + duration;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (it == _timer_tasks.cend()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// smaller time goes to the back
|
||||
auto prev = it - 1;
|
||||
while (it > _timer_tasks.cbegin()) {
|
||||
if (std::get<0>(*it) > std::get<0>(*prev)) {
|
||||
std::swap(*it, *prev);
|
||||
}
|
||||
|
||||
--prev;
|
||||
--it;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
cancel(task_id_t task_id) {
|
||||
std::lock_guard lg(_task_mutex);
|
||||
|
||||
auto it = _timer_tasks.begin();
|
||||
for (; it < _timer_tasks.cend(); ++it) {
|
||||
const __task &task = std::get<1>(*it);
|
||||
|
||||
if (&*task == task_id) {
|
||||
_timer_tasks.erase(it);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<std::pair<__time_point, __task>>
|
||||
pop(task_id_t task_id) {
|
||||
std::lock_guard lg(_task_mutex);
|
||||
|
||||
auto pos = std::find_if(std::begin(_timer_tasks), std::end(_timer_tasks), [&task_id](const auto &t) { return t.second.get() == task_id; });
|
||||
|
||||
if (pos == std::end(_timer_tasks)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::move(*pos);
|
||||
}
|
||||
|
||||
std::optional<__task>
|
||||
pop() {
|
||||
std::lock_guard lg(_task_mutex);
|
||||
|
||||
if (!_tasks.empty()) {
|
||||
__task task = std::move(_tasks.front());
|
||||
_tasks.pop_front();
|
||||
return task;
|
||||
}
|
||||
|
||||
if (!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now()) {
|
||||
__task task = std::move(std::get<1>(_timer_tasks.back()));
|
||||
_timer_tasks.pop_back();
|
||||
return task;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool
|
||||
ready() {
|
||||
std::lock_guard<std::mutex> lg(_task_mutex);
|
||||
|
||||
return !_tasks.empty() || (!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now());
|
||||
}
|
||||
|
||||
std::optional<__time_point>
|
||||
next() {
|
||||
std::lock_guard<std::mutex> lg(_task_mutex);
|
||||
|
||||
if (_timer_tasks.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::get<0>(_timer_tasks.back());
|
||||
}
|
||||
|
||||
private:
|
||||
template <class Function>
|
||||
std::unique_ptr<_ImplBase>
|
||||
toRunnable(Function &&f) {
|
||||
return std::make_unique<_Impl<Function>>(std::forward<Function &&>(f));
|
||||
}
|
||||
};
|
||||
} // namespace task_pool_util
|
||||
@ -1,131 +0,0 @@
|
||||
/**
|
||||
* @file src/thread_pool.h
|
||||
* @brief todo
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "task_pool.h"
|
||||
#include <thread>
|
||||
|
||||
namespace thread_pool_util {
|
||||
/**
|
||||
* Allow threads to execute unhindered while keeping full control over the threads.
|
||||
*/
|
||||
class ThreadPool: public task_pool_util::TaskPool {
|
||||
public:
|
||||
typedef TaskPool::__task __task;
|
||||
|
||||
private:
|
||||
std::vector<std::thread> _thread;
|
||||
|
||||
std::condition_variable _cv;
|
||||
std::mutex _lock;
|
||||
|
||||
bool _continue;
|
||||
|
||||
public:
|
||||
ThreadPool():
|
||||
_continue { false } {}
|
||||
|
||||
explicit ThreadPool(int threads):
|
||||
_thread(threads), _continue { true } {
|
||||
for (auto &t : _thread) {
|
||||
t = std::thread(&ThreadPool::_main, this);
|
||||
}
|
||||
}
|
||||
|
||||
~ThreadPool() noexcept {
|
||||
if (!_continue) return;
|
||||
|
||||
stop();
|
||||
join();
|
||||
}
|
||||
|
||||
template <class Function, class... Args>
|
||||
auto
|
||||
push(Function &&newTask, Args &&...args) {
|
||||
std::lock_guard lg(_lock);
|
||||
auto future = TaskPool::push(std::forward<Function>(newTask), std::forward<Args>(args)...);
|
||||
|
||||
_cv.notify_one();
|
||||
return future;
|
||||
}
|
||||
|
||||
void
|
||||
pushDelayed(std::pair<__time_point, __task> &&task) {
|
||||
std::lock_guard lg(_lock);
|
||||
|
||||
TaskPool::pushDelayed(std::move(task));
|
||||
}
|
||||
|
||||
template <class Function, class X, class Y, class... Args>
|
||||
auto
|
||||
pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&...args) {
|
||||
std::lock_guard lg(_lock);
|
||||
auto future = TaskPool::pushDelayed(std::forward<Function>(newTask), duration, std::forward<Args>(args)...);
|
||||
|
||||
// Update all timers for wait_until
|
||||
_cv.notify_all();
|
||||
return future;
|
||||
}
|
||||
|
||||
void
|
||||
start(int threads) {
|
||||
_continue = true;
|
||||
|
||||
_thread.resize(threads);
|
||||
|
||||
for (auto &t : _thread) {
|
||||
t = std::thread(&ThreadPool::_main, this);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
stop() {
|
||||
std::lock_guard lg(_lock);
|
||||
|
||||
_continue = false;
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
void
|
||||
join() {
|
||||
for (auto &t : _thread) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
_main() {
|
||||
while (_continue) {
|
||||
if (auto task = this->pop()) {
|
||||
(*task)->run();
|
||||
}
|
||||
else {
|
||||
std::unique_lock uniq_lock(_lock);
|
||||
|
||||
if (ready()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_continue) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (auto tp = next()) {
|
||||
_cv.wait_until(uniq_lock, *tp);
|
||||
}
|
||||
else {
|
||||
_cv.wait(uniq_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute remaining tasks
|
||||
while (auto task = this->pop()) {
|
||||
(*task)->run();
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace thread_pool_util
|
||||
@ -4,6 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
|
||||
@ -16,7 +16,6 @@ extern "C" {
|
||||
|
||||
#include "cbs.h"
|
||||
#include "config.h"
|
||||
#include "input.h"
|
||||
#include "main.h"
|
||||
#include "nvenc/nvenc_base.h"
|
||||
#include "platform/common.h"
|
||||
@ -522,7 +521,6 @@ namespace video {
|
||||
safe::mail_raw_t::queue_t<packet_t> packets;
|
||||
safe::mail_raw_t::event_t<bool> idr_events;
|
||||
safe::mail_raw_t::event_t<hdr_info_t> hdr_events;
|
||||
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_events;
|
||||
|
||||
config_t config;
|
||||
int frame_nr;
|
||||
@ -1805,36 +1803,6 @@ namespace video {
|
||||
}
|
||||
}
|
||||
|
||||
input::touch_port_t
|
||||
make_port(platf::display_t *display, const config_t &config) {
|
||||
float wd = display->width;
|
||||
float hd = display->height;
|
||||
|
||||
float wt = config.width;
|
||||
float ht = config.height;
|
||||
|
||||
auto scalar = std::fminf(wt / wd, ht / hd);
|
||||
|
||||
auto w2 = scalar * wd;
|
||||
auto h2 = scalar * hd;
|
||||
|
||||
auto offsetX = (config.width - w2) * 0.5f;
|
||||
auto offsetY = (config.height - h2) * 0.5f;
|
||||
|
||||
return input::touch_port_t {
|
||||
{
|
||||
display->offset_x,
|
||||
display->offset_y,
|
||||
config.width,
|
||||
config.height,
|
||||
},
|
||||
display->env_width,
|
||||
display->env_height,
|
||||
offsetX,
|
||||
offsetY,
|
||||
1.0f / scalar,
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<platf::encode_device_t>
|
||||
make_encode_device(platf::display_t &disp, const encoder_t &encoder, const config_t &config) {
|
||||
@ -1868,9 +1836,6 @@ namespace video {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// absolute mouse coordinates require that the dimensions of the screen are known
|
||||
ctx.touch_port_events->raise(make_port(disp, ctx.config));
|
||||
|
||||
// Update client with our current HDR display state
|
||||
hdr_info_t hdr_info = std::make_unique<hdr_info_raw_t>(false);
|
||||
if (colorspace_is_hdr(encode_device->colorspace)) {
|
||||
@ -2110,7 +2075,6 @@ namespace video {
|
||||
|
||||
int frame_nr = 1;
|
||||
|
||||
auto touch_port_event = mail->event<input::touch_port_t>(mail::touch_port);
|
||||
auto hdr_event = mail->event<hdr_info_t>(mail::hdr);
|
||||
|
||||
// Encoding takes place on this thread
|
||||
@ -2140,8 +2104,6 @@ namespace video {
|
||||
return;
|
||||
}
|
||||
|
||||
// absolute mouse coordinates require that the dimensions of the screen are known
|
||||
touch_port_event->raise(make_port(display.get(), config));
|
||||
|
||||
// Update client with our current HDR display state
|
||||
hdr_info_t hdr_info = std::make_unique<hdr_info_raw_t>(false);
|
||||
@ -2185,7 +2147,6 @@ namespace video {
|
||||
mail::man->queue<packet_t>(mail::video_packets),
|
||||
std::move(idr_events),
|
||||
mail->event<hdr_info_t>(mail::hdr),
|
||||
mail->event<input::touch_port_t>(mail::touch_port),
|
||||
config,
|
||||
1,
|
||||
channel_data,
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "input.h"
|
||||
#include "platform/common.h"
|
||||
#include "thread_safe.h"
|
||||
#include "video_colorspace.h"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user