Merge with master
This commit is contained in:
@@ -210,6 +210,11 @@ nvhttp_t nvhttp {
|
||||
};
|
||||
|
||||
input_t input {
|
||||
{
|
||||
{ 0x10, 0xA0 },
|
||||
{ 0x11, 0xA2 },
|
||||
{ 0x12, 0xA4 },
|
||||
},
|
||||
2s, // back_button_timeout
|
||||
500ms, // key_repeat_delay
|
||||
std::chrono::duration<double> { 1 / 24.9 }, // key_repeat_period
|
||||
@@ -399,8 +404,20 @@ void int_f(std::unordered_map<std::string, std::string> &vars, const std::string
|
||||
return;
|
||||
}
|
||||
|
||||
auto &val = it->second;
|
||||
input = util::from_chars(&val[0], &val[0] + val.size());
|
||||
std::string_view val = it->second;
|
||||
|
||||
// If value is something like: "756" instead of 756
|
||||
if(val.size() >= 2 && val[0] == '"') {
|
||||
val = val.substr(1, val.size() - 2);
|
||||
}
|
||||
|
||||
// If that integer is in hexadecimal
|
||||
if(val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
|
||||
input = util::from_hex<int>(val.substr(2));
|
||||
}
|
||||
else {
|
||||
input = util::from_view(val);
|
||||
}
|
||||
|
||||
vars.erase(it);
|
||||
}
|
||||
@@ -412,8 +429,20 @@ void int_f(std::unordered_map<std::string, std::string> &vars, const std::string
|
||||
return;
|
||||
}
|
||||
|
||||
auto &val = it->second;
|
||||
input = util::from_chars(&val[0], &val[0] + val.size());
|
||||
std::string_view val = it->second;
|
||||
|
||||
// If value is something like: "756" instead of 756
|
||||
if(val.size() >= 2 && val[0] == '"') {
|
||||
val = val.substr(1, val.size() - 2);
|
||||
}
|
||||
|
||||
// If that integer is in hexadecimal
|
||||
if(val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
|
||||
input = util::from_hex<int>(val.substr(2));
|
||||
}
|
||||
else {
|
||||
input = util::from_view(val);
|
||||
}
|
||||
|
||||
vars.erase(it);
|
||||
}
|
||||
@@ -545,7 +574,42 @@ void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::s
|
||||
list_string_f(vars, name, list);
|
||||
|
||||
for(auto &el : list) {
|
||||
input.emplace_back(util::from_view(el));
|
||||
std::string_view val = el;
|
||||
|
||||
// If value is something like: "756" instead of 756
|
||||
if(val.size() >= 2 && val[0] == '"') {
|
||||
val = val.substr(1, val.size() - 2);
|
||||
}
|
||||
|
||||
int tmp;
|
||||
|
||||
// If the integer is a hexadecimal
|
||||
if(val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
|
||||
tmp = util::from_hex<int>(val.substr(2));
|
||||
}
|
||||
else {
|
||||
tmp = util::from_view(val);
|
||||
}
|
||||
input.emplace_back(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
|
||||
std::vector<int> list;
|
||||
list_int_f(vars, name, list);
|
||||
|
||||
// The list needs to be a multiple of 2
|
||||
if(list.size() % 2) {
|
||||
std::cout << "Warning: expected "sv << name << " to have a multiple of two elements --> not "sv << list.size() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
int x = 0;
|
||||
while(x < list.size()) {
|
||||
auto key = list[x++];
|
||||
auto val = list[x++];
|
||||
|
||||
input.emplace(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,6 +697,17 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
|
||||
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);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace config {
|
||||
struct video_t {
|
||||
// ffmpeg params
|
||||
int qp; // higher == more compression and less quality
|
||||
int qp; // higher == more compression and less quality
|
||||
|
||||
int hevc_mode;
|
||||
|
||||
@@ -73,6 +73,8 @@ struct nvhttp_t {
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
@@ -73,6 +73,15 @@ void send_unauthorized(resp_https_t response, req_https_t request) {
|
||||
response->write(SimpleWeb::StatusCode::client_error_unauthorized, headers);
|
||||
}
|
||||
|
||||
void send_redirect(resp_https_t response, req_https_t request, const char *path) {
|
||||
auto address = request->remote_endpoint_address();
|
||||
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
|
||||
const SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{ "Location", path }
|
||||
};
|
||||
response->write(SimpleWeb::StatusCode::redirection_temporary_redirect, headers);
|
||||
}
|
||||
|
||||
bool authenticate(resp_https_t response, req_https_t request) {
|
||||
auto address = request->remote_endpoint_address();
|
||||
auto ip_type = net::from_address(address);
|
||||
@@ -83,6 +92,12 @@ bool authenticate(resp_https_t response, req_https_t request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//If credentials are shown, redirect the user to a /welcome page
|
||||
if(config::sunshine.username.empty()){
|
||||
send_redirect(response,request,"/welcome");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto fg = util::fail_guard([&]() {
|
||||
send_unauthorized(response, request);
|
||||
});
|
||||
@@ -185,6 +200,17 @@ void getPasswordPage(resp_https_t response, req_https_t request) {
|
||||
response->write(header + content);
|
||||
}
|
||||
|
||||
void getWelcomePage(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
if(!config::sunshine.username.empty()){
|
||||
send_redirect(response,request,"/");
|
||||
return;
|
||||
}
|
||||
std::string header = read_file(WEB_DIR "header-no-nav.html");
|
||||
std::string content = read_file(WEB_DIR "welcome.html");
|
||||
response->write(header + content);
|
||||
}
|
||||
|
||||
void getApps(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
@@ -371,7 +397,7 @@ void saveConfig(resp_https_t response, req_https_t request) {
|
||||
}
|
||||
|
||||
void savePassword(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
if(!config::sunshine.username.empty() && !authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
@@ -390,27 +416,31 @@ void savePassword(resp_https_t response, req_https_t request) {
|
||||
try {
|
||||
//TODO: Input Validation
|
||||
pt::read_json(ss, inputTree);
|
||||
auto username = inputTree.get<std::string>("currentUsername");
|
||||
auto username = inputTree.count("currentUsername") > 0 ? inputTree.get<std::string>("currentUsername") : "";
|
||||
auto newUsername = inputTree.get<std::string>("newUsername");
|
||||
auto password = inputTree.get<std::string>("currentPassword");
|
||||
auto password = inputTree.count("currentPassword") > 0 ? inputTree.get<std::string>("currentPassword") : "";
|
||||
auto newPassword = inputTree.get<std::string>("newPassword");
|
||||
auto confirmPassword = inputTree.get<std::string>("confirmNewPassword");
|
||||
if(newUsername.length() == 0) newUsername = username;
|
||||
|
||||
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
|
||||
if(username == config::sunshine.username && hash == config::sunshine.password) {
|
||||
if(newPassword != confirmPassword) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Password Mismatch");
|
||||
}
|
||||
|
||||
http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword);
|
||||
http::reload_user_creds(config::sunshine.credentials_file);
|
||||
outputTree.put("status", true);
|
||||
}
|
||||
else {
|
||||
if(newUsername.length() == 0){
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Invalid Current Credentials");
|
||||
outputTree.put("error", "Invalid Username");
|
||||
} else {
|
||||
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
|
||||
if(config::sunshine.username.empty() || (username == config::sunshine.username && hash == config::sunshine.password)) {
|
||||
if(newPassword != confirmPassword) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Password Mismatch");
|
||||
} else {
|
||||
http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword);
|
||||
http::reload_user_creds(config::sunshine.credentials_file);
|
||||
outputTree.put("status", true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Invalid Current Credentials");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
@@ -467,6 +497,7 @@ void start() {
|
||||
server.resource["^/clients$"]["GET"] = getClientsPage;
|
||||
server.resource["^/config$"]["GET"] = getConfigPage;
|
||||
server.resource["^/password$"]["GET"] = getPasswordPage;
|
||||
server.resource["^/welcome$"]["GET"] = getWelcomePage;
|
||||
server.resource["^/api/pin"]["POST"] = savePin;
|
||||
server.resource["^/api/apps$"]["GET"] = getApps;
|
||||
server.resource["^/api/apps$"]["POST"] = saveApp;
|
||||
|
||||
@@ -54,15 +54,11 @@ int init() {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if(!user_creds_exist(config::sunshine.credentials_file)) {
|
||||
if(save_user_creds(config::sunshine.credentials_file, "sunshine"s, crypto::rand_alphabet(16), true)) {
|
||||
return -1;
|
||||
}
|
||||
if(user_creds_exist(config::sunshine.credentials_file)) {
|
||||
if(reload_user_creds(config::sunshine.credentials_file)) return -1;
|
||||
} else {
|
||||
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
|
||||
}
|
||||
if(reload_user_creds(config::sunshine.credentials_file)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -92,12 +88,6 @@ int save_user_creds(const std::string &file, const std::string &username, const
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "New credentials have been created"sv;
|
||||
|
||||
if(run_our_mouth) {
|
||||
BOOST_LOG(info) << "Username: "sv << username;
|
||||
BOOST_LOG(info) << "Password: "sv << password;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -409,13 +409,9 @@ void repeat_key(short key_code) {
|
||||
}
|
||||
|
||||
short map_keycode(short keycode) {
|
||||
switch(keycode) {
|
||||
case 0x10:
|
||||
return 0xA0;
|
||||
case 0x11:
|
||||
return 0xA2;
|
||||
case 0x12:
|
||||
return 0xA4;
|
||||
auto it = config::input.keybindings.find(keycode);
|
||||
if(it != std::end(config::input.keybindings)) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return keycode;
|
||||
|
||||
@@ -190,7 +190,7 @@ stream::launch_session_t make_launch_session(bool host_audio, const args_t &args
|
||||
stream::launch_session_t launch_session;
|
||||
|
||||
launch_session.host_audio = host_audio;
|
||||
launch_session.gcm_key = *util::from_hex<crypto::aes_t>(args.at("rikey"s), true);
|
||||
launch_session.gcm_key = util::from_hex<crypto::aes_t>(args.at("rikey"s), true);
|
||||
uint32_t prepend_iv = util::endian::big<uint32_t>(util::from_view(args.at("rikeyid"s)));
|
||||
auto prepend_iv_p = (uint8_t *)&prepend_iv;
|
||||
|
||||
@@ -211,7 +211,7 @@ void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin
|
||||
|
||||
auto salt = util::from_hex<std::array<uint8_t, 16>>(salt_view, true);
|
||||
|
||||
auto key = crypto::gen_aes_key(*salt, pin);
|
||||
auto key = crypto::gen_aes_key(salt, pin);
|
||||
sess.cipher_key = std::make_unique<crypto::aes_t>(key);
|
||||
|
||||
tree.put("root.paired", 1);
|
||||
|
||||
@@ -720,6 +720,42 @@ std::shared_ptr<display_t> display(platf::mem_type_e hwdevice_type, int framerat
|
||||
return x11_disp;
|
||||
}
|
||||
|
||||
std::vector<std::string> display_names() {
|
||||
if(xcb::init_shm() || xcb::init() || x11::init() || x11::rr::init() || x11::fix::init()) {
|
||||
BOOST_LOG(error) << "Couldn't init x11 libraries"sv;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "Detecting connected monitors"sv;
|
||||
|
||||
xdisplay_t xdisplay { x11::OpenDisplay(nullptr) };
|
||||
if(!xdisplay) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto xwindow = DefaultRootWindow(xdisplay.get());
|
||||
screen_res_t screenr { x11::rr::GetScreenResources(xdisplay.get(), xwindow) };
|
||||
int output = screenr->noutput;
|
||||
|
||||
int monitor = 0;
|
||||
for(int x = 0; x < output; ++x) {
|
||||
output_info_t out_info { x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) };
|
||||
if(out_info && out_info->connection == RR_Connected) {
|
||||
++monitor;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> names;
|
||||
names.reserve(monitor);
|
||||
|
||||
for(auto x = 0; x < monitor; ++x) {
|
||||
names.emplace_back(std::to_string(x));
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
void freeImage(XImage *p) {
|
||||
XDestroyImage(p);
|
||||
}
|
||||
|
||||
@@ -268,11 +268,12 @@ std::string hex_vec(C &&c, bool rev = false) {
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::optional<T> from_hex(const std::string_view &hex, bool rev = false) {
|
||||
T from_hex(const std::string_view &hex, bool rev = false) {
|
||||
std::uint8_t buf[sizeof(T)];
|
||||
|
||||
static char constexpr shift_bit = 'a' - 'A';
|
||||
auto is_convertable = [](char ch) -> bool {
|
||||
|
||||
auto is_convertable = [](char ch) -> bool {
|
||||
if(isdigit(ch)) {
|
||||
return true;
|
||||
}
|
||||
@@ -287,9 +288,7 @@ std::optional<T> from_hex(const std::string_view &hex, bool rev = false) {
|
||||
};
|
||||
|
||||
auto buf_size = std::count_if(std::begin(hex), std::end(hex), is_convertable) / 2;
|
||||
if(buf_size != sizeof(T)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto padding = sizeof(T) - buf_size;
|
||||
|
||||
const char *data = hex.data() + hex.size() - 1;
|
||||
|
||||
@@ -301,7 +300,9 @@ std::optional<T> from_hex(const std::string_view &hex, bool rev = false) {
|
||||
return (std::uint8_t)(ch | (char)32) - 'a' + (char)10;
|
||||
};
|
||||
|
||||
for(auto &el : buf) {
|
||||
std::fill_n(buf + buf_size, padding, 0);
|
||||
|
||||
std::for_each_n(buf, buf_size, [&](auto &el) {
|
||||
while(!is_convertable(*data)) { --data; }
|
||||
std::uint8_t ch_r = convert(*data--);
|
||||
|
||||
@@ -309,7 +310,7 @@ std::optional<T> from_hex(const std::string_view &hex, bool rev = false) {
|
||||
std::uint8_t ch_l = convert(*data--);
|
||||
|
||||
el = (ch_l << 4) | ch_r;
|
||||
}
|
||||
});
|
||||
|
||||
if(rev) {
|
||||
std::reverse(std::begin(buf), std::end(buf));
|
||||
|
||||
Reference in New Issue
Block a user