diff --git a/assets/sunshine.conf b/assets/sunshine.conf
index bb846029..7ada209f 100644
--- a/assets/sunshine.conf
+++ b/assets/sunshine.conf
@@ -77,6 +77,20 @@
# 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
# ping_timeout = 10000
diff --git a/assets/web/config.html b/assets/web/config.html
index d7b67b98..c14a24b3 100644
--- a/assets/web/config.html
+++ b/assets/web/config.html
@@ -111,6 +111,18 @@
are supported.
+
+
+
+
+
+
+ It may be possible that you cannot send the Windows Key from Moonlight directly.
+ In those cases it may be usefull to make Sunshine think the Right Alt key is the Windows key
+
@@ -533,6 +545,7 @@
delete this.config.status;
delete this.config.platform;
//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.upnp = this.config.upnp || 'disabled';
this.config.min_log_level = this.config.min_log_level || 2;
@@ -561,6 +574,7 @@
let nl = this.config === 'windows' ? "\r\n" : "\n";
this.config.resolutions = "[" + nl + " " + this.resolutions.join("," + nl + " ") + nl + "]";
this.config.fps = JSON.stringify(this.fps);
+
fetch("/api/config", {
method: "POST",
body: JSON.stringify(this.config)
@@ -588,4 +602,4 @@
font-size: 12px;
font-weight: bold;
}
-
+
\ No newline at end of file
diff --git a/sunshine/config.cpp b/sunshine/config.cpp
index 02a5438a..3008047f 100644
--- a/sunshine/config.cpp
+++ b/sunshine/config.cpp
@@ -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 { 1 / 24.9 }, // key_repeat_period
@@ -399,8 +404,20 @@ void int_f(std::unordered_map &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(val.substr(2));
+ }
+ else {
+ input = util::from_view(val);
+ }
vars.erase(it);
}
@@ -412,8 +429,20 @@ void int_f(std::unordered_map &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(val.substr(2));
+ }
+ else {
+ input = util::from_view(val);
+ }
vars.erase(it);
}
@@ -545,7 +574,42 @@ void list_int_f(std::unordered_map &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(val.substr(2));
+ }
+ else {
+ tmp = util::from_view(val);
+ }
+ input.emplace_back(tmp);
+ }
+}
+
+void map_int_int_f(std::unordered_map &vars, const std::string &name, std::unordered_map &input) {
+ std::vector 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 &&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::min();
int_f(vars, "back_button_timeout", to);
diff --git a/sunshine/config.h b/sunshine/config.h
index 3c0537dd..80d4052d 100644
--- a/sunshine/config.h
+++ b/sunshine/config.h
@@ -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 keybindings;
+
std::chrono::milliseconds back_button_timeout;
std::chrono::milliseconds key_repeat_delay;
std::chrono::duration key_repeat_period;
diff --git a/sunshine/input.cpp b/sunshine/input.cpp
index fa3241f0..6568ad7c 100644
--- a/sunshine/input.cpp
+++ b/sunshine/input.cpp
@@ -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;
diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp
index cffc65dd..68806106 100644
--- a/sunshine/nvhttp.cpp
+++ b/sunshine/nvhttp.cpp
@@ -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(args.at("rikey"s), true);
+ launch_session.gcm_key = util::from_hex(args.at("rikey"s), true);
uint32_t prepend_iv = util::endian::big(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>(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(key);
tree.put("root.paired", 1);
diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp
index f6a89511..133e5905 100644
--- a/sunshine/platform/linux/display.cpp
+++ b/sunshine/platform/linux/display.cpp
@@ -495,7 +495,6 @@ std::vector display_names() {
names.reserve(monitor);
for(auto x = 0; x < monitor; ++x) {
- BOOST_LOG(fatal) << x;
names.emplace_back(std::to_string(x));
}
diff --git a/sunshine/utility.h b/sunshine/utility.h
index e827d441..4740ae6a 100644
--- a/sunshine/utility.h
+++ b/sunshine/utility.h
@@ -268,11 +268,12 @@ std::string hex_vec(C &&c, bool rev = false) {
}
template
-std::optional 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 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 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 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));