Merge with master
This commit is contained in:
13
README.md
13
README.md
@@ -87,7 +87,8 @@ sunshine needs access to uinput to create mouse and gamepad events:
|
|||||||
- When Moonlight request you insert the correct pin on sunshine:
|
- When Moonlight request you insert the correct pin on sunshine:
|
||||||
- Type in the URL bar of your browser: `https://xxx.xxx.xxx.xxx:47990` where `xxx.xxx.xxx.xxx` is the IP address of your computer
|
- Type in the URL bar of your browser: `https://xxx.xxx.xxx.xxx:47990` where `xxx.xxx.xxx.xxx` is the IP address of your computer
|
||||||
- Ignore any warning given by your browser about "insecure website"
|
- Ignore any warning given by your browser about "insecure website"
|
||||||
- Type in the username and password shown the first time you run Sunshine
|
- You should compile the next page with a new username and a password, needed to login into the next step
|
||||||
|
- Press "Save" and log in using the credentials given above
|
||||||
- Go to "PIN" in the Header
|
- Go to "PIN" in the Header
|
||||||
- Type in your PIN and press Enter, you should get a Success Message
|
- Type in your PIN and press Enter, you should get a Success Message
|
||||||
- Click on one of the Applications listed
|
- Click on one of the Applications listed
|
||||||
@@ -99,16 +100,6 @@ All shortcuts start with CTRL + ALT + SHIFT, just like Moonlight
|
|||||||
- CTRL + ALT + SHIFT + N --> Hide/Unhide the cursor (This may be usefull for Remote Desktop Mode for Moonlight)
|
- CTRL + ALT + SHIFT + N --> Hide/Unhide the cursor (This may be usefull for Remote Desktop Mode for Moonlight)
|
||||||
- CTRL + ALT + SHIFT + F1/F13 --> Switch to different monitor for Streaming
|
- CTRL + ALT + SHIFT + F1/F13 --> Switch to different monitor for Streaming
|
||||||
|
|
||||||
## Note:
|
|
||||||
- The Windows key is not passed through by Moonlight, therefore Sunshine maps Right-Alt key to the Windows key
|
|
||||||
- If you set Video Bitrate to 0.5Mb/s:
|
|
||||||
- Sunshine will use CRF or QP to controll the quality of the stream. (See example configuration file for more details)
|
|
||||||
- This is less CPU intensive and it has lower average bandwith requirements compared to manually setting bitrate to acceptable quality
|
|
||||||
- However, it has higher peak bitrates, forcing Sunshine to drop entire frames when streaming 1080P due to their size.
|
|
||||||
- When this happens, the video portion of the stream appears to be frozen.
|
|
||||||
- This is rare enough that using this for the desktop environment is tolerable (in my opinion), however for gaming not so much.
|
|
||||||
|
|
||||||
|
|
||||||
## Credits:
|
## Credits:
|
||||||
- [Simple-Web-Server](https://gitlab.com/eidheim/Simple-Web-Server)
|
- [Simple-Web-Server](https://gitlab.com/eidheim/Simple-Web-Server)
|
||||||
- [Moonlight](https://github.com/moonlight-stream)
|
- [Moonlight](https://github.com/moonlight-stream)
|
||||||
|
|||||||
@@ -77,6 +77,20 @@
|
|||||||
# 3840x1600,
|
# 3840x1600,
|
||||||
# ]
|
# ]
|
||||||
|
|
||||||
|
# Sometimes it may be usefull to map keybindings.
|
||||||
|
# Wayland won't allow clients to capture the Win Key for example
|
||||||
|
#
|
||||||
|
# See https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||||
|
#
|
||||||
|
# Note:
|
||||||
|
# keybindings needs to have a multiple of two elements
|
||||||
|
# keybindings = [
|
||||||
|
# 0x10, 0xA0,
|
||||||
|
# 0x11, 0xA2,
|
||||||
|
# 0x12, 0xA4,
|
||||||
|
# 0x4A, 0x4B
|
||||||
|
# ]
|
||||||
|
|
||||||
# How long to wait in milliseconds for data from moonlight before shutting down the stream
|
# How long to wait in milliseconds for data from moonlight before shutting down the stream
|
||||||
# ping_timeout = 10000
|
# ping_timeout = 10000
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,18 @@
|
|||||||
are supported.
|
are supported.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Mapping Key AltRight to Key Windows -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="mapkey" class="form-label">Map Right Alt key to Windows key</label>
|
||||||
|
<select id="mapkey" class="form-select" v-model="config.key_rightalt_to_key_win">
|
||||||
|
<option value="disabled">Disabled</option>
|
||||||
|
<option value="enabled">Enabled</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
It may be possible that you cannot send the Windows Key from Moonlight directly.<br />
|
||||||
|
In those cases it may be usefull to make Sunshine think the Right Alt key is the Windows key
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--Files Tab-->
|
<!--Files Tab-->
|
||||||
<div v-if="currentTab === 'files'" class="config-page">
|
<div v-if="currentTab === 'files'" class="config-page">
|
||||||
@@ -533,6 +545,7 @@
|
|||||||
delete this.config.status;
|
delete this.config.status;
|
||||||
delete this.config.platform;
|
delete this.config.platform;
|
||||||
//Populate default values if not present in config
|
//Populate default values if not present in config
|
||||||
|
this.config.key_rightalt_to_key_win = this.config.key_rightalt_to_key_win || "disabled";
|
||||||
this.config.gamepad = this.config.gamepad || 'x360';
|
this.config.gamepad = this.config.gamepad || 'x360';
|
||||||
this.config.upnp = this.config.upnp || 'disabled';
|
this.config.upnp = this.config.upnp || 'disabled';
|
||||||
this.config.min_log_level = this.config.min_log_level || 2;
|
this.config.min_log_level = this.config.min_log_level || 2;
|
||||||
@@ -561,6 +574,7 @@
|
|||||||
let nl = this.config === 'windows' ? "\r\n" : "\n";
|
let nl = this.config === 'windows' ? "\r\n" : "\n";
|
||||||
this.config.resolutions = "[" + nl + " " + this.resolutions.join("," + nl + " ") + nl + "]";
|
this.config.resolutions = "[" + nl + " " + this.resolutions.join("," + nl + " ") + nl + "]";
|
||||||
this.config.fps = JSON.stringify(this.fps);
|
this.config.fps = JSON.stringify(this.fps);
|
||||||
|
|
||||||
fetch("/api/config", {
|
fetch("/api/config", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(this.config)
|
body: JSON.stringify(this.config)
|
||||||
@@ -588,4 +602,4 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
17
assets/web/header-no-nav.html
Normal file
17
assets/web/header-no-nav.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Sunshine</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0" crossorigin="anonymous">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-p34f1UUtsS3wqzfto5wAAmdvj+osOnFyQFpp4Ua3gs/ZVWx6oOypYoCJhGGScy+8" crossorigin="anonymous">
|
||||||
|
</script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
69
assets/web/welcome.html
Normal file
69
assets/web/welcome.html
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<main role="main" id="app" style="max-width: 600px;margin: 0 auto;">
|
||||||
|
<div class="container-parent">
|
||||||
|
<div class="container py-3">
|
||||||
|
<h1 class="mb-0">Welcome to Sunshine!</h1>
|
||||||
|
<p class="mb-0 align-self-start">Before Getting Started, write down below these credentials</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-warning">These Credentials down below are needed to access the rest of the application.<br> Keep them safe, since <b>you will never see them again!</b></div>
|
||||||
|
<form @submit.prevent="save" class="card p-4" style="width: 100%;">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="" class="form-label">Username: </label>
|
||||||
|
<input type="text" class="form-control" v-model="passwordData.newUsername">
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="" class="form-label">Password: </label>
|
||||||
|
<input type="password" class="form-control" v-model="passwordData.newPassword">
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="" class="form-label">Password: </label>
|
||||||
|
<input type="password" class="form-control" v-model="passwordData.confirmNewPassword">
|
||||||
|
</div>
|
||||||
|
<button class="mb-2 btn btn-primary" style="margin: 1em auto;">Login</button>
|
||||||
|
<div class="alert alert-danger" v-if="error"><b>Error: </b>{{error}}</div>
|
||||||
|
<div class="alert alert-success" v-if="success"><b>Success! </b>This page will reload soon, your browser will ask you for the new credentials</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
error: null,
|
||||||
|
success: false,
|
||||||
|
passwordData: {
|
||||||
|
newUsername: 'sunshine',
|
||||||
|
newPassword: '',
|
||||||
|
confirmNewPassword: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
save() {
|
||||||
|
this.error = null;
|
||||||
|
fetch("/api/password", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(this.passwordData)
|
||||||
|
}).then((r) => {
|
||||||
|
if (r.status == 200){
|
||||||
|
r.json().then((rj) => {
|
||||||
|
if(rj.status.toString() === "true"){
|
||||||
|
this.success = true;
|
||||||
|
setTimeout(()=>{
|
||||||
|
document.location.reload();
|
||||||
|
},5000);
|
||||||
|
} else {
|
||||||
|
this.error = rj.error;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.error = "Internal Server Error"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -210,6 +210,11 @@ nvhttp_t nvhttp {
|
|||||||
};
|
};
|
||||||
|
|
||||||
input_t input {
|
input_t input {
|
||||||
|
{
|
||||||
|
{ 0x10, 0xA0 },
|
||||||
|
{ 0x11, 0xA2 },
|
||||||
|
{ 0x12, 0xA4 },
|
||||||
|
},
|
||||||
2s, // back_button_timeout
|
2s, // back_button_timeout
|
||||||
500ms, // key_repeat_delay
|
500ms, // key_repeat_delay
|
||||||
std::chrono::duration<double> { 1 / 24.9 }, // key_repeat_period
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &val = it->second;
|
std::string_view val = it->second;
|
||||||
input = util::from_chars(&val[0], &val[0] + val.size());
|
|
||||||
|
// 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);
|
vars.erase(it);
|
||||||
}
|
}
|
||||||
@@ -412,8 +429,20 @@ void int_f(std::unordered_map<std::string, std::string> &vars, const std::string
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &val = it->second;
|
std::string_view val = it->second;
|
||||||
input = util::from_chars(&val[0], &val[0] + val.size());
|
|
||||||
|
// 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);
|
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);
|
list_string_f(vars, name, list);
|
||||||
|
|
||||||
for(auto &el : 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);
|
path_f(vars, "file_apps", stream.file_apps);
|
||||||
int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 255 });
|
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();
|
to = std::numeric_limits<int>::min();
|
||||||
int_f(vars, "back_button_timeout", to);
|
int_f(vars, "back_button_timeout", to);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
namespace config {
|
namespace config {
|
||||||
struct video_t {
|
struct video_t {
|
||||||
// ffmpeg params
|
// ffmpeg params
|
||||||
int qp; // higher == more compression and less quality
|
int qp; // higher == more compression and less quality
|
||||||
|
|
||||||
int hevc_mode;
|
int hevc_mode;
|
||||||
|
|
||||||
@@ -73,6 +73,8 @@ struct nvhttp_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct input_t {
|
struct input_t {
|
||||||
|
std::unordered_map<int, int> keybindings;
|
||||||
|
|
||||||
std::chrono::milliseconds back_button_timeout;
|
std::chrono::milliseconds back_button_timeout;
|
||||||
std::chrono::milliseconds key_repeat_delay;
|
std::chrono::milliseconds key_repeat_delay;
|
||||||
std::chrono::duration<double> key_repeat_period;
|
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);
|
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) {
|
bool authenticate(resp_https_t response, req_https_t request) {
|
||||||
auto address = request->remote_endpoint_address();
|
auto address = request->remote_endpoint_address();
|
||||||
auto ip_type = net::from_address(address);
|
auto ip_type = net::from_address(address);
|
||||||
@@ -83,6 +92,12 @@ bool authenticate(resp_https_t response, req_https_t request) {
|
|||||||
return false;
|
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([&]() {
|
auto fg = util::fail_guard([&]() {
|
||||||
send_unauthorized(response, request);
|
send_unauthorized(response, request);
|
||||||
});
|
});
|
||||||
@@ -185,6 +200,17 @@ void getPasswordPage(resp_https_t response, req_https_t request) {
|
|||||||
response->write(header + content);
|
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) {
|
void getApps(resp_https_t response, req_https_t request) {
|
||||||
if(!authenticate(response, request)) return;
|
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) {
|
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);
|
print_req(request);
|
||||||
|
|
||||||
@@ -390,27 +416,31 @@ void savePassword(resp_https_t response, req_https_t request) {
|
|||||||
try {
|
try {
|
||||||
//TODO: Input Validation
|
//TODO: Input Validation
|
||||||
pt::read_json(ss, inputTree);
|
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 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 newPassword = inputTree.get<std::string>("newPassword");
|
||||||
auto confirmPassword = inputTree.get<std::string>("confirmNewPassword");
|
auto confirmPassword = inputTree.get<std::string>("confirmNewPassword");
|
||||||
if(newUsername.length() == 0) newUsername = username;
|
if(newUsername.length() == 0) newUsername = username;
|
||||||
|
if(newUsername.length() == 0){
|
||||||
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 {
|
|
||||||
outputTree.put("status", false);
|
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) {
|
catch(std::exception &e) {
|
||||||
@@ -467,6 +497,7 @@ void start() {
|
|||||||
server.resource["^/clients$"]["GET"] = getClientsPage;
|
server.resource["^/clients$"]["GET"] = getClientsPage;
|
||||||
server.resource["^/config$"]["GET"] = getConfigPage;
|
server.resource["^/config$"]["GET"] = getConfigPage;
|
||||||
server.resource["^/password$"]["GET"] = getPasswordPage;
|
server.resource["^/password$"]["GET"] = getPasswordPage;
|
||||||
|
server.resource["^/welcome$"]["GET"] = getWelcomePage;
|
||||||
server.resource["^/api/pin"]["POST"] = savePin;
|
server.resource["^/api/pin"]["POST"] = savePin;
|
||||||
server.resource["^/api/apps$"]["GET"] = getApps;
|
server.resource["^/api/apps$"]["GET"] = getApps;
|
||||||
server.resource["^/api/apps$"]["POST"] = saveApp;
|
server.resource["^/api/apps$"]["POST"] = saveApp;
|
||||||
|
|||||||
@@ -54,15 +54,11 @@ int init() {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!user_creds_exist(config::sunshine.credentials_file)) {
|
if(user_creds_exist(config::sunshine.credentials_file)) {
|
||||||
if(save_user_creds(config::sunshine.credentials_file, "sunshine"s, crypto::rand_alphabet(16), true)) {
|
if(reload_user_creds(config::sunshine.credentials_file)) return -1;
|
||||||
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;
|
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;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -409,13 +409,9 @@ void repeat_key(short key_code) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
short map_keycode(short keycode) {
|
short map_keycode(short keycode) {
|
||||||
switch(keycode) {
|
auto it = config::input.keybindings.find(keycode);
|
||||||
case 0x10:
|
if(it != std::end(config::input.keybindings)) {
|
||||||
return 0xA0;
|
return it->second;
|
||||||
case 0x11:
|
|
||||||
return 0xA2;
|
|
||||||
case 0x12:
|
|
||||||
return 0xA4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return keycode;
|
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;
|
stream::launch_session_t launch_session;
|
||||||
|
|
||||||
launch_session.host_audio = host_audio;
|
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)));
|
uint32_t prepend_iv = util::endian::big<uint32_t>(util::from_view(args.at("rikeyid"s)));
|
||||||
auto prepend_iv_p = (uint8_t *)&prepend_iv;
|
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 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);
|
sess.cipher_key = std::make_unique<crypto::aes_t>(key);
|
||||||
|
|
||||||
tree.put("root.paired", 1);
|
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;
|
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) {
|
void freeImage(XImage *p) {
|
||||||
XDestroyImage(p);
|
XDestroyImage(p);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,11 +268,12 @@ std::string hex_vec(C &&c, bool rev = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
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)];
|
std::uint8_t buf[sizeof(T)];
|
||||||
|
|
||||||
static char constexpr shift_bit = 'a' - 'A';
|
static char constexpr shift_bit = 'a' - 'A';
|
||||||
auto is_convertable = [](char ch) -> bool {
|
|
||||||
|
auto is_convertable = [](char ch) -> bool {
|
||||||
if(isdigit(ch)) {
|
if(isdigit(ch)) {
|
||||||
return true;
|
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;
|
auto buf_size = std::count_if(std::begin(hex), std::end(hex), is_convertable) / 2;
|
||||||
if(buf_size != sizeof(T)) {
|
auto padding = sizeof(T) - buf_size;
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *data = hex.data() + hex.size() - 1;
|
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;
|
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; }
|
while(!is_convertable(*data)) { --data; }
|
||||||
std::uint8_t ch_r = convert(*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--);
|
std::uint8_t ch_l = convert(*data--);
|
||||||
|
|
||||||
el = (ch_l << 4) | ch_r;
|
el = (ch_l << 4) | ch_r;
|
||||||
}
|
});
|
||||||
|
|
||||||
if(rev) {
|
if(rev) {
|
||||||
std::reverse(std::begin(buf), std::end(buf));
|
std::reverse(std::begin(buf), std::end(buf));
|
||||||
|
|||||||
Reference in New Issue
Block a user