diff --git a/assets/sunshine.conf b/assets/sunshine.conf index 3fb5cf17..3fabb634 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -65,6 +65,14 @@ # If back_button_timeout < 0, then the Home/Guide button will not be emulated # back_button_timeout = 2000 +# !! Windows only !! +# Control how fast keys will repeat themselves +# The initial delay in milliseconds before repeating keys +# key_repeat_delay = 500 +# +# How often keys repeat every second +# This configurable option supports decimals +# key_repeat_frequency = 24.9 # The name of the audio sink used for Audio Loopback # If you do not specify this variable, pulseaudio will select the default monitor device. diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 2b027e1c..83c7db9b 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -129,7 +129,9 @@ nvhttp_t nvhttp { }; input_t input { - 2s + 2s, // back_button_timeout + 500ms, // key_repeat_delay + std::chrono::duration { 1 / 24.9 } // key_repeat_period }; sunshine_t sunshine { @@ -271,9 +273,7 @@ bool to_bool(std::string &boolean) { } void bool_f(std::unordered_map &vars, const std::string &name, int &input) { std::string tmp; - string_restricted_f(vars, name, tmp, { - "enable"sv, "dis" - }); + string_f(vars, name, tmp); if(tmp.empty()) { return; @@ -282,6 +282,35 @@ void bool_f(std::unordered_map &vars, const std::strin input = to_bool(tmp) ? 1 : 0; } +void double_f(std::unordered_map &vars, const std::string &name, double &input) { + std::string tmp; + string_f(vars, name, tmp); + + if(tmp.empty()) { + return; + } + + char *c_str_p; + auto val = std::strtod(tmp.c_str(), &c_str_p); + + if(c_str_p == tmp.c_str()) { + return; + } + + input = val; +} + +void double_between_f(std::unordered_map &vars, const std::string &name, double &input, const std::pair &range) { + double temp = input; + + double_f(vars, name, temp); + + TUPLE_2D_REF(lower, upper, range); + if(temp >= lower && temp <= upper) { + input = temp; + } +} + void print_help(const char *name) { std::cout << "Usage: "sv << name << " [options] [/path/to/configuration_file]"sv << std::endl << @@ -370,6 +399,21 @@ void apply_config(std::unordered_map &&vars) { 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::max() + }); + + if(repeat_frequency > 0) { + config::input.key_repeat_period = std::chrono::duration {1 / repeat_frequency }; + } + + to = -1; + int_f(vars, "key_repeat_delay", to); + if(to >= 0) { + input.key_repeat_delay = std::chrono::milliseconds { to }; + } + std::string log_level_string; string_restricted_f(vars, "min_log_level", log_level_string, { "verbose"sv, "debug"sv, "info"sv, "warning"sv, "error"sv, "fatal"sv, "none"sv diff --git a/sunshine/config.h b/sunshine/config.h index 419a5933..9c14b1fe 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -63,6 +63,8 @@ struct nvhttp_t { struct input_t { std::chrono::milliseconds back_button_timeout; + std::chrono::milliseconds key_repeat_delay; + std::chrono::duration key_repeat_period; }; namespace flag { diff --git a/sunshine/input.cpp b/sunshine/input.cpp index 2159c4bd..7d9b7733 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -40,6 +40,7 @@ void free_id(std::bitset &gamepad_mask, int id) { gamepad_mask[id] = false; } +static util::TaskPool::task_id_t task_id {}; static std::unordered_map key_press {}; static std::array mouse_press {}; @@ -180,11 +181,46 @@ void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet platf::button_mouse(platf_input, button, packet->action == BUTTON_RELEASED); } +void repeat_key(short key_code) { + // If key no longer pressed, stop repeating + if(!key_press[key_code]) { + task_id = nullptr; + return; + } + + platf::keyboard(platf_input, key_code & 0x00FF, false); + + task_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code).task_id; +} + void passthrough(std::shared_ptr &input, PNV_KEYBOARD_PACKET packet) { auto constexpr BUTTON_RELEASED = 0x04; - key_press[packet->keyCode] = packet->keyAction != BUTTON_RELEASED; - platf::keyboard(platf_input, packet->keyCode & 0x00FF, packet->keyAction == BUTTON_RELEASED); + auto release = packet->keyAction == BUTTON_RELEASED; + + auto &pressed = key_press[packet->keyCode]; + if(!pressed) { + if(!release) { + if(task_id) { + task_pool.cancel(task_id); + } + + if(config::input.key_repeat_delay.count() > 0) { + task_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, packet->keyCode).task_id; + } + } + else { + // Already released + return; + } + } + else if(!release) { + // Already pressed down key + return; + } + + pressed = !release; + platf::keyboard(platf_input, packet->keyCode & 0x00FF, release); } void passthrough(platf::input_t &input, PNV_SCROLL_PACKET packet) { diff --git a/sunshine/task_pool.h b/sunshine/task_pool.h index 02e0f250..83fe8596 100644 --- a/sunshine/task_pool.h +++ b/sunshine/task_pool.h @@ -112,8 +112,14 @@ public: using __return = std::invoke_result_t; using task_t = std::packaged_task<__return()>; - - __time_point time_point = std::chrono::steady_clock::now() + duration; + + __time_point time_point; + if constexpr (std::is_floating_point_v) { + time_point = std::chrono::steady_clock::now() + std::chrono::duration_cast(duration); + } + else { + time_point = std::chrono::steady_clock::now() + duration; + } auto bind = [task = std::forward(newTask), tuple_args = std::make_tuple(std::forward(args)...)]() mutable { return std::apply(task, std::move(tuple_args));