Emulate back/select gamepad button

This commit is contained in:
loki 2019-12-22 23:34:12 +01:00
commit 29edc88294
12 changed files with 524 additions and 41 deletions

View file

@ -62,6 +62,7 @@ set(SUNSHINE_TARGET_FILES
sunshine/config.h
sunshine/config.cpp
sunshine/main.cpp
sunshine/main.h
sunshine/crypto.cpp
sunshine/crypto.h
sunshine/nvhttp.cpp
@ -78,6 +79,9 @@ set(SUNSHINE_TARGET_FILES
sunshine/platform/common.h
sunshine/process.cpp
sunshine/process.h
sunshine/move_by_copy.h
sunshine/task_pool.h
sunshine/thread_pool.h
${PLATFORM_TARGET_FILES})
include_directories(

View file

@ -25,6 +25,14 @@ ping_timeout = 2000
# The higher fec_percentage, the lower space for the actual data to send per frame there is
fec_percentage = 1
# The back/select button on the controller
# On the Shield, the home and powerbutton are not passed to Moonlight
# If, after the timeout, the back button is still pressed down, Home/Guide button press is emulated.
# If back_button_timeout < 0, then the Home/Guide button will not be emulated
# back_button_timeout = 2000
###############################################
# FFmpeg software encoding parameters
# Honestly, I have no idea what the optimal values would be.

View file

@ -43,6 +43,10 @@ nvhttp_t nvhttp {
"devices.json" // file_devices
};
input_t input {
2s
};
bool whitespace(char ch) {
return ch == ' ' || ch == '\t';
}
@ -143,6 +147,13 @@ void parse_file(const char *file) {
}
string_f(vars, "file_apps", stream.file_apps);
int_f(vars, "fec_percentage", stream.fec_percentage);
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 };
}
}

View file

@ -37,9 +37,14 @@ struct nvhttp_t {
std::string external_ip;
};
struct input_t {
std::chrono::milliseconds back_button_timeout;
};
extern video_t video;
extern stream_t stream;
extern nvhttp_t nvhttp;
extern input_t input;
void parse_file(const char *file);
}

View file

@ -9,6 +9,8 @@ extern "C" {
#include <cstring>
#include <iostream>
#include "main.h"
#include "config.h"
#include "input.h"
#include "utility.h"
@ -132,7 +134,7 @@ void passthrough(platf::input_t &input, PNV_SCROLL_PACKET packet) {
platf::scroll(input, util::endian::big(packet->scrollAmt1));
}
void passthrough(input_t &input, PNV_MULTI_CONTROLLER_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
std::uint16_t bf;
std::memcpy(&bf, &packet->buttonFlags, sizeof(std::uint16_t));
@ -146,7 +148,7 @@ void passthrough(input_t &input, PNV_MULTI_CONTROLLER_PACKET packet) {
packet->rightStickY
};
bf = gamepad_state.buttonFlags ^ input.gamepad_state.buttonFlags;
bf = gamepad_state.buttonFlags ^ input->gamepad_state.buttonFlags;
auto bf_new = gamepad_state.buttonFlags;
if(bf) {
@ -154,73 +156,94 @@ void passthrough(input_t &input, PNV_MULTI_CONTROLLER_PACKET packet) {
if((DPAD_UP | DPAD_DOWN) & bf) {
int val = bf_new & DPAD_UP ? -1 : (bf_new & DPAD_DOWN ? 1 : 0);
platf::gp::dpad_y(input.input, val);
platf::gp::dpad_y(input->input, val);
}
if((DPAD_LEFT | DPAD_RIGHT) & bf) {
int val = bf_new & DPAD_LEFT ? -1 : (bf_new & DPAD_RIGHT ? 1 : 0);
platf::gp::dpad_x(input.input, val);
platf::gp::dpad_x(input->input, val);
}
if(START & bf) platf::gp::start(input.input, bf_new & START ? 1 : 0);
if(BACK & bf) platf::gp::back(input.input, bf_new & BACK ? 1 : 0);
if(LEFT_STICK & bf) platf::gp::left_stick(input.input, bf_new & LEFT_STICK ? 1 : 0);
if(RIGHT_STICK & bf) platf::gp::right_stick(input.input, bf_new & RIGHT_STICK ? 1 : 0);
if(LEFT_BUTTON & bf) platf::gp::left_button(input.input, bf_new & LEFT_BUTTON ? 1 : 0);
if(RIGHT_BUTTON & bf) platf::gp::right_button(input.input, bf_new & RIGHT_BUTTON ? 1 : 0);
if(HOME & bf) platf::gp::home(input.input, bf_new & HOME ? 1 : 0);
if(A & bf) platf::gp::a(input.input, bf_new & A ? 1 : 0);
if(B & bf) platf::gp::b(input.input, bf_new & B ? 1 : 0);
if(X & bf) platf::gp::x(input.input, bf_new & X ? 1 : 0);
if(Y & bf) platf::gp::y(input.input, bf_new & Y ? 1 : 0);
if(START & bf) platf::gp::start(input->input, bf_new & START ? 1 : 0);
if(LEFT_STICK & bf) platf::gp::left_stick(input->input, bf_new & LEFT_STICK ? 1 : 0);
if(RIGHT_STICK & bf) platf::gp::right_stick(input->input, bf_new & RIGHT_STICK ? 1 : 0);
if(LEFT_BUTTON & bf) platf::gp::left_button(input->input, bf_new & LEFT_BUTTON ? 1 : 0);
if(RIGHT_BUTTON & bf) platf::gp::right_button(input->input, bf_new & RIGHT_BUTTON ? 1 : 0);
if(HOME & bf) platf::gp::home(input->input, bf_new & HOME ? 1 : 0);
if(A & bf) platf::gp::a(input->input, bf_new & A ? 1 : 0);
if(B & bf) platf::gp::b(input->input, bf_new & B ? 1 : 0);
if(X & bf) platf::gp::x(input->input, bf_new & X ? 1 : 0);
if(Y & bf) platf::gp::y(input->input, bf_new & Y ? 1 : 0);
if(BACK & bf) {
if(BACK & bf_new) {
platf::gp::back(input->input,1);
input->back_timeout_id = task_pool.pushDelayed([input]() {
platf::gp::back(input->input, 0);
platf::gp::home(input->input,1);
platf::gp::home(input->input,0);
input->back_timeout_id = nullptr;
}, config::input.back_button_timeout).task_id;
}
else if(input->back_timeout_id) {
platf::gp::back(input->input, 0);
task_pool.cancel(input->back_timeout_id);
input->back_timeout_id = nullptr;
}
}
}
if(input.gamepad_state.lt != gamepad_state.lt) {
platf::gp::left_trigger(input.input, gamepad_state.lt);
if(input->gamepad_state.lt != gamepad_state.lt) {
platf::gp::left_trigger(input->input, gamepad_state.lt);
}
if(input.gamepad_state.rt != gamepad_state.rt) {
platf::gp::right_trigger(input.input, gamepad_state.rt);
if(input->gamepad_state.rt != gamepad_state.rt) {
platf::gp::right_trigger(input->input, gamepad_state.rt);
}
if(input.gamepad_state.lsX != gamepad_state.lsX) {
platf::gp::left_stick_x(input.input, gamepad_state.lsX);
if(input->gamepad_state.lsX != gamepad_state.lsX) {
platf::gp::left_stick_x(input->input, gamepad_state.lsX);
}
if(input.gamepad_state.lsY != gamepad_state.lsY) {
platf::gp::left_stick_y(input.input, gamepad_state.lsY);
if(input->gamepad_state.lsY != gamepad_state.lsY) {
platf::gp::left_stick_y(input->input, gamepad_state.lsY);
}
if(input.gamepad_state.rsX != gamepad_state.rsX) {
platf::gp::right_stick_x(input.input, gamepad_state.rsX);
if(input->gamepad_state.rsX != gamepad_state.rsX) {
platf::gp::right_stick_x(input->input, gamepad_state.rsX);
}
if(input.gamepad_state.rsY != gamepad_state.rsY) {
platf::gp::right_stick_y(input.input, gamepad_state.rsY);
if(input->gamepad_state.rsY != gamepad_state.rsY) {
platf::gp::right_stick_y(input->input, gamepad_state.rsY);
}
input.gamepad_state = gamepad_state;
platf::gp::sync(input.input);
input->gamepad_state = gamepad_state;
platf::gp::sync(input->input);
}
void passthrough(input_t &input, void *payload) {
void passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t> &&input_data) {
void *payload = input_data.data();
int input_type = util::endian::big(*(int*)payload);
switch(input_type) {
case PACKET_TYPE_MOUSE_MOVE:
passthrough(input.input, (PNV_MOUSE_MOVE_PACKET)payload);
passthrough(input->input, (PNV_MOUSE_MOVE_PACKET)payload);
break;
case PACKET_TYPE_MOUSE_BUTTON:
passthrough(input.input, (PNV_MOUSE_BUTTON_PACKET)payload);
passthrough(input->input, (PNV_MOUSE_BUTTON_PACKET)payload);
break;
case PACKET_TYPE_SCROLL_OR_KEYBOARD:
{
char *tmp_input = (char*)payload + 4;
if(tmp_input[0] == 0x0A) {
passthrough(input.input, (PNV_SCROLL_PACKET)payload);
passthrough(input->input, (PNV_SCROLL_PACKET)payload);
}
else {
passthrough(input.input, (PNV_KEYBOARD_PACKET)payload);
passthrough(input->input, (PNV_KEYBOARD_PACKET)payload);
}
break;
@ -231,5 +254,9 @@ void passthrough(input_t &input, void *payload) {
}
}
input_t::input_t() : gamepad_state { 0 }, input { platf::input() } {}
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data) {
task_pool.push(passthrough_helper, input, util::cmove(input_data));
}
input_t::input_t() : gamepad_state { 0 }, back_timeout_id { nullptr }, input { platf::input() } {}
}

View file

@ -6,6 +6,7 @@
#define SUNSHINE_INPUT_H
#include "platform/common.h"
#include "thread_pool.h"
namespace input {
struct gamepad_state_t {
@ -22,11 +23,13 @@ struct input_t {
input_t();
gamepad_state_t gamepad_state;
util::ThreadPool::task_id_t back_timeout_id;
platf::input_t input;
};
void print(void *input);
void passthrough(input_t &, void *input);
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
}
#endif //SUNSHINE_INPUT_H

View file

@ -10,12 +10,15 @@
#include "stream.h"
#include "config.h"
#include "process.h"
#include "thread_pool.h"
extern "C" {
#include <rs.h>
}
using namespace std::literals;
util::ThreadPool task_pool;
int main(int argc, char *argv[]) {
if(argc > 1) {
if(!std::filesystem::exists(argv[1])) {
@ -34,11 +37,12 @@ int main(int argc, char *argv[]) {
reed_solomon_init();
std::thread httpThread { nvhttp::start };
std::thread rtpThread { stream::rtpThread };
task_pool.start(1);
std::thread httpThread { nvhttp::start };
stream::rtpThread();
httpThread.join();
rtpThread.join();
return 0;
}

12
sunshine/main.h Normal file
View file

@ -0,0 +1,12 @@
//
// Created by loki on 12/22/19.
//
#ifndef SUNSHINE_MAIN_H
#define SUNSHINE_MAIN_H
#include "thread_pool.h"
extern util::ThreadPool task_pool;
#endif //SUNSHINE_MAIN_H

50
sunshine/move_by_copy.h Executable file
View file

@ -0,0 +1,50 @@
#ifndef DOSSIER_MOVE_BY_COPY_H
#define DOSSIER_MOVE_BY_COPY_H
#include <utility>
namespace util {
/*
* When a copy is made, it moves the object
* This allows you to move an object when a move can't be done.
*/
template<class T>
class MoveByCopy {
public:
typedef T move_type;
private:
move_type _to_move;
public:
explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) { }
MoveByCopy(MoveByCopy &&other) = default;
MoveByCopy(const MoveByCopy &other) {
*this = other;
}
MoveByCopy& operator=(MoveByCopy &&other) = default;
MoveByCopy& operator=(const MoveByCopy &other) {
this->_to_move = std::move(const_cast<MoveByCopy&>(other)._to_move);
return *this;
}
operator move_type() {
return std::move(_to_move);
}
};
template<class T>
MoveByCopy<T> cmove(T &movable) {
return MoveByCopy<T>(std::move(movable));
}
// Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
template<class T>
MoveByCopy<T> const_cmove(const T &movable) {
return MoveByCopy<T>(std::move(const_cast<T&>(movable)));
}
}
#endif

View file

@ -468,7 +468,7 @@ void control_server_t::send(const std::string_view & payload) {
void controlThread(video::idr_event_t idr_events) {
control_server_t server { CONTROL_PORT };
input::input_t input;
auto input = std::make_shared<input::input_t>();
server.map(packetTypes[IDX_START_A], [](const std::string_view &payload) {
session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
@ -539,7 +539,7 @@ void controlThread(video::idr_event_t idr_events) {
}
input::print(plaintext.data());
input::passthrough(input, plaintext.data());
input::passthrough(input, std::move(plaintext));
});
while(has_session) {

238
sunshine/task_pool.h Executable file
View file

@ -0,0 +1,238 @@
#ifndef KITTY_TASK_POOL_H
#define KITTY_TASK_POOL_H
#include <deque>
#include <vector>
#include <future>
#include <chrono>
#include <utility>
#include <functional>
#include <mutex>
#include <type_traits>
#include <optional>
#include "utility.h"
#include "move_by_copy.h"
namespace 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 = 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 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 std::move(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 std::move(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));
}
};
}
#endif

121
sunshine/thread_pool.h Executable file
View file

@ -0,0 +1,121 @@
#ifndef KITTY_THREAD_POOL_H
#define KITTY_THREAD_POOL_H
#include <thread>
#include "task_pool.h"
namespace util {
/*
* Allow threads to execute unhindered
* while keeping full controll over the threads.
*/
class ThreadPool : public 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();
}
}
};
}
#endif