Phase 2
This commit is contained in:
parent
bbc1f724e1
commit
0c16e913da
11 changed files with 277 additions and 0 deletions
|
|
@ -1235,6 +1235,8 @@ namespace config {
|
||||||
bool_f(vars, "high_resolution_scrolling", input.high_resolution_scrolling);
|
bool_f(vars, "high_resolution_scrolling", input.high_resolution_scrolling);
|
||||||
bool_f(vars, "native_pen_touch", input.native_pen_touch);
|
bool_f(vars, "native_pen_touch", input.native_pen_touch);
|
||||||
|
|
||||||
|
list_string_f(vars, "owner_client_uuids", input.owner_client_uuids);
|
||||||
|
|
||||||
bool_f(vars, "notify_pre_releases", sunshine.notify_pre_releases);
|
bool_f(vars, "notify_pre_releases", sunshine.notify_pre_releases);
|
||||||
bool_f(vars, "system_tray", sunshine.system_tray);
|
bool_f(vars, "system_tray", sunshine.system_tray);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,8 @@ namespace config {
|
||||||
|
|
||||||
bool high_resolution_scrolling;
|
bool high_resolution_scrolling;
|
||||||
bool native_pen_touch;
|
bool native_pen_touch;
|
||||||
|
|
||||||
|
std::vector<std::string> owner_client_uuids;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace flag {
|
namespace flag {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
#include "nvhttp.h"
|
#include "nvhttp.h"
|
||||||
#include "platform/common.h"
|
#include "platform/common.h"
|
||||||
#include "process.h"
|
#include "process.h"
|
||||||
|
#include "stream.h"
|
||||||
#include "utility.h"
|
#include "utility.h"
|
||||||
#include "uuid.h"
|
#include "uuid.h"
|
||||||
|
|
||||||
|
|
@ -805,6 +806,19 @@ namespace confighttp {
|
||||||
send_response(response, output_tree);
|
send_response(response, output_tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getActiveSessions(resp_https_t response, req_https_t request) {
|
||||||
|
if (!authenticate(response, request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print_req(request);
|
||||||
|
|
||||||
|
nlohmann::json output_tree;
|
||||||
|
output_tree["sessions"] = stream::get_active_sessions_info();
|
||||||
|
output_tree["status"] = true;
|
||||||
|
send_response(response, output_tree);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Unpair a client.
|
* @brief Unpair a client.
|
||||||
* @param response The HTTP response object.
|
* @param response The HTTP response object.
|
||||||
|
|
@ -1454,6 +1468,7 @@ namespace confighttp {
|
||||||
server.resource["^/api/clients/unpair-all$"]["POST"] = unpairAll;
|
server.resource["^/api/clients/unpair-all$"]["POST"] = unpairAll;
|
||||||
server.resource["^/api/clients/list$"]["GET"] = getClients;
|
server.resource["^/api/clients/list$"]["GET"] = getClients;
|
||||||
server.resource["^/api/clients/unpair$"]["POST"] = unpair;
|
server.resource["^/api/clients/unpair$"]["POST"] = unpair;
|
||||||
|
server.resource["^/api/sessions/active$"]["GET"] = getActiveSessions;
|
||||||
server.resource["^/api/apps/close$"]["POST"] = closeApp;
|
server.resource["^/api/apps/close$"]["POST"] = closeApp;
|
||||||
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
|
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
|
||||||
server.resource["^/api/covers/([0-9]+)$"]["GET"] = getCover;
|
server.resource["^/api/covers/([0-9]+)$"]["GET"] = getCover;
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,18 @@ namespace input {
|
||||||
#define DISABLE_LEFT_BUTTON_DELAY ((thread_pool_util::ThreadPool::task_id_t) 0x01)
|
#define DISABLE_LEFT_BUTTON_DELAY ((thread_pool_util::ThreadPool::task_id_t) 0x01)
|
||||||
#define ENABLE_LEFT_BUTTON_DELAY nullptr
|
#define ENABLE_LEFT_BUTTON_DELAY nullptr
|
||||||
|
|
||||||
|
static uint64_t now_ms() {
|
||||||
|
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch()
|
||||||
|
).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void touch_activity(std::atomic<uint64_t> *ts) {
|
||||||
|
if (ts) {
|
||||||
|
ts->store(now_ms(), std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto VKEY_SHIFT = 0x10;
|
constexpr auto VKEY_SHIFT = 0x10;
|
||||||
constexpr auto VKEY_LSHIFT = 0xA0;
|
constexpr auto VKEY_LSHIFT = 0xA0;
|
||||||
constexpr auto VKEY_RSHIFT = 0xA1;
|
constexpr auto VKEY_RSHIFT = 0xA1;
|
||||||
|
|
@ -193,6 +205,10 @@ namespace input {
|
||||||
|
|
||||||
int32_t accumulated_vscroll_delta;
|
int32_t accumulated_vscroll_delta;
|
||||||
int32_t accumulated_hscroll_delta;
|
int32_t accumulated_hscroll_delta;
|
||||||
|
|
||||||
|
std::atomic<uint64_t> *activity_keyboard_ms = nullptr;
|
||||||
|
std::atomic<uint64_t> *activity_mouse_ms = nullptr;
|
||||||
|
std::atomic<uint64_t> *activity_gamepad_ms = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -446,6 +462,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_mouse_ms);
|
||||||
|
|
||||||
input->mouse_left_button_timeout = DISABLE_LEFT_BUTTON_DELAY;
|
input->mouse_left_button_timeout = DISABLE_LEFT_BUTTON_DELAY;
|
||||||
platf::move_mouse(platf_input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
|
platf::move_mouse(platf_input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
|
||||||
}
|
}
|
||||||
|
|
@ -543,6 +561,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_mouse_ms);
|
||||||
|
|
||||||
if (input->mouse_left_button_timeout == DISABLE_LEFT_BUTTON_DELAY) {
|
if (input->mouse_left_button_timeout == DISABLE_LEFT_BUTTON_DELAY) {
|
||||||
input->mouse_left_button_timeout = ENABLE_LEFT_BUTTON_DELAY;
|
input->mouse_left_button_timeout = ENABLE_LEFT_BUTTON_DELAY;
|
||||||
}
|
}
|
||||||
|
|
@ -593,6 +613,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_mouse_ms);
|
||||||
|
|
||||||
auto release = util::endian::little(packet->header.magic) == MOUSE_BUTTON_UP_EVENT_MAGIC_GEN5;
|
auto release = util::endian::little(packet->header.magic) == MOUSE_BUTTON_UP_EVENT_MAGIC_GEN5;
|
||||||
auto button = util::endian::big(packet->button);
|
auto button = util::endian::big(packet->button);
|
||||||
if (button > 0 && button < mouse_press.size()) {
|
if (button > 0 && button < mouse_press.size()) {
|
||||||
|
|
@ -757,6 +779,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_keyboard_ms);
|
||||||
|
|
||||||
auto release = util::endian::little(packet->header.magic) == KEY_UP_EVENT_MAGIC;
|
auto release = util::endian::little(packet->header.magic) == KEY_UP_EVENT_MAGIC;
|
||||||
auto keyCode = packet->keyCode & 0x00FF;
|
auto keyCode = packet->keyCode & 0x00FF;
|
||||||
|
|
||||||
|
|
@ -817,6 +841,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_mouse_ms);
|
||||||
|
|
||||||
if (config::input.high_resolution_scrolling) {
|
if (config::input.high_resolution_scrolling) {
|
||||||
platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
|
platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -840,6 +866,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_mouse_ms);
|
||||||
|
|
||||||
if (config::input.high_resolution_scrolling) {
|
if (config::input.high_resolution_scrolling) {
|
||||||
platf::hscroll(platf_input, util::endian::big(packet->scrollAmount));
|
platf::hscroll(platf_input, util::endian::big(packet->scrollAmount));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -872,6 +900,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_gamepad_ms);
|
||||||
|
|
||||||
if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) {
|
if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) {
|
||||||
BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']';
|
BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']';
|
||||||
return;
|
return;
|
||||||
|
|
@ -912,6 +942,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_mouse_ms);
|
||||||
|
|
||||||
// Convert the client normalized coordinates to touchport coordinates
|
// Convert the client normalized coordinates to touchport coordinates
|
||||||
auto coords = client_to_touchport(input, {from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f, from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f}, {65535.f, 65535.f});
|
auto coords = client_to_touchport(input, {from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f, from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f}, {65535.f, 65535.f});
|
||||||
if (!coords) {
|
if (!coords) {
|
||||||
|
|
@ -968,6 +1000,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_mouse_ms);
|
||||||
|
|
||||||
// Convert the client normalized coordinates to touchport coordinates
|
// Convert the client normalized coordinates to touchport coordinates
|
||||||
auto coords = client_to_touchport(input, {from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f, from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f}, {65535.f, 65535.f});
|
auto coords = client_to_touchport(input, {from_clamped_netfloat(packet->x, 0.0f, 1.0f) * 65535.f, from_clamped_netfloat(packet->y, 0.0f, 1.0f) * 65535.f}, {65535.f, 65535.f});
|
||||||
if (!coords) {
|
if (!coords) {
|
||||||
|
|
@ -1026,6 +1060,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_gamepad_ms);
|
||||||
|
|
||||||
if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) {
|
if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) {
|
||||||
BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']';
|
BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']';
|
||||||
return;
|
return;
|
||||||
|
|
@ -1059,6 +1095,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_gamepad_ms);
|
||||||
|
|
||||||
if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) {
|
if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) {
|
||||||
BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']';
|
BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']';
|
||||||
return;
|
return;
|
||||||
|
|
@ -1091,6 +1129,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_gamepad_ms);
|
||||||
|
|
||||||
if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) {
|
if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) {
|
||||||
BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']';
|
BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']';
|
||||||
return;
|
return;
|
||||||
|
|
@ -1116,6 +1156,8 @@ namespace input {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
touch_activity(input->activity_gamepad_ms);
|
||||||
|
|
||||||
if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) {
|
if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) {
|
||||||
BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']';
|
BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -782,6 +782,15 @@ namespace nvhttp {
|
||||||
return named_cert_nodes;
|
return named_cert_nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string get_client_name(const std::string &uuid) {
|
||||||
|
for (auto &named_cert : client_root.named_devices) {
|
||||||
|
if (named_cert.uuid == uuid) {
|
||||||
|
return named_cert.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
void applist(resp_https_t response, req_https_t request) {
|
void applist(resp_https_t response, req_https_t request) {
|
||||||
print_req<SunshineHTTPS>(request);
|
print_req<SunshineHTTPS>(request);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,8 @@ namespace nvhttp {
|
||||||
*/
|
*/
|
||||||
nlohmann::json get_all_clients();
|
nlohmann::json get_all_clients();
|
||||||
|
|
||||||
|
std::string get_client_name(const std::string &uuid);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Remove all paired clients.
|
* @brief Remove all paired clients.
|
||||||
* @examples
|
* @examples
|
||||||
|
|
|
||||||
105
src/stream.cpp
105
src/stream.cpp
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// standard includes
|
// standard includes
|
||||||
|
#include <algorithm>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
@ -26,6 +27,7 @@ extern "C" {
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
|
#include "nvhttp.h"
|
||||||
#include "platform/common.h"
|
#include "platform/common.h"
|
||||||
#include "process.h"
|
#include "process.h"
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
|
|
@ -340,6 +342,34 @@ namespace stream {
|
||||||
control_server_t control_server;
|
control_server_t control_server;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Per-session input policy (which remote input types are allowed).
|
||||||
|
*/
|
||||||
|
struct session_input_policy_t {
|
||||||
|
bool allow_gamepad = true;
|
||||||
|
bool allow_keyboard = false;
|
||||||
|
bool allow_mouse = false;
|
||||||
|
bool is_owner_session = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Per-session input activity timestamps (monotonic ms).
|
||||||
|
*/
|
||||||
|
struct session_input_activity_t {
|
||||||
|
std::atomic<uint64_t> last_keyboard_ms {0};
|
||||||
|
std::atomic<uint64_t> last_mouse_ms {0};
|
||||||
|
std::atomic<uint64_t> last_gamepad_ms {0};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return current monotonic time in milliseconds.
|
||||||
|
*/
|
||||||
|
static uint64_t now_monotonic_ms() {
|
||||||
|
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch()
|
||||||
|
).count();
|
||||||
|
}
|
||||||
|
|
||||||
struct session_t {
|
struct session_t {
|
||||||
config_t config;
|
config_t config;
|
||||||
|
|
||||||
|
|
@ -347,6 +377,12 @@ namespace stream {
|
||||||
|
|
||||||
std::shared_ptr<input::input_t> input;
|
std::shared_ptr<input::input_t> input;
|
||||||
|
|
||||||
|
// Parsec-style per-session state
|
||||||
|
std::string client_unique_id;
|
||||||
|
std::string client_name;
|
||||||
|
session_input_policy_t input_policy;
|
||||||
|
session_input_activity_t input_activity;
|
||||||
|
|
||||||
std::thread audioThread;
|
std::thread audioThread;
|
||||||
std::thread videoThread;
|
std::thread videoThread;
|
||||||
|
|
||||||
|
|
@ -1896,6 +1932,57 @@ namespace stream {
|
||||||
audio::capture(session->mail, session->config.audio, session);
|
audio::capture(session->mail, session->config.audio, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nlohmann::json get_active_sessions_info() {
|
||||||
|
auto result = nlohmann::json::array();
|
||||||
|
|
||||||
|
auto ref = broadcast.ref();
|
||||||
|
if (!ref) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto now = now_monotonic_ms();
|
||||||
|
|
||||||
|
auto lg = ref->control_server._sessions.lock();
|
||||||
|
for (auto *session : *ref->control_server._sessions) {
|
||||||
|
if (session->state.load(std::memory_order_relaxed) != session::state_e::RUNNING) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto last_kb = session->input_activity.last_keyboard_ms.load(std::memory_order_relaxed);
|
||||||
|
auto last_mouse = session->input_activity.last_mouse_ms.load(std::memory_order_relaxed);
|
||||||
|
auto last_gp = session->input_activity.last_gamepad_ms.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
|
nlohmann::json entry;
|
||||||
|
entry["session_id"] = session->launch_session_id;
|
||||||
|
entry["client_uuid"] = session->client_unique_id;
|
||||||
|
entry["client_name"] = session->client_name;
|
||||||
|
entry["is_owner_session"] = session->input_policy.is_owner_session;
|
||||||
|
|
||||||
|
entry["policy"] = {
|
||||||
|
{"allow_keyboard", session->input_policy.allow_keyboard},
|
||||||
|
{"allow_mouse", session->input_policy.allow_mouse},
|
||||||
|
{"allow_gamepad", session->input_policy.allow_gamepad},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto kb_ago = last_kb > 0 ? now - last_kb : UINT64_MAX;
|
||||||
|
auto mouse_ago = last_mouse > 0 ? now - last_mouse : UINT64_MAX;
|
||||||
|
auto gp_ago = last_gp > 0 ? now - last_gp : UINT64_MAX;
|
||||||
|
|
||||||
|
entry["activity"] = {
|
||||||
|
{"keyboard_active", kb_ago <= KB_ACTIVE_WINDOW_MS},
|
||||||
|
{"mouse_active", mouse_ago <= MOUSE_ACTIVE_WINDOW_MS},
|
||||||
|
{"gamepad_active", gp_ago <= GAMEPAD_ACTIVE_WINDOW_MS},
|
||||||
|
{"last_keyboard_ms_ago", last_kb > 0 ? kb_ago : -1},
|
||||||
|
{"last_mouse_ms_ago", last_mouse > 0 ? mouse_ago : -1},
|
||||||
|
{"last_gamepad_ms_ago", last_gp > 0 ? gp_ago : -1},
|
||||||
|
};
|
||||||
|
|
||||||
|
result.push_back(std::move(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
namespace session {
|
namespace session {
|
||||||
std::atomic_uint running_sessions;
|
std::atomic_uint running_sessions;
|
||||||
|
|
||||||
|
|
@ -1964,6 +2051,10 @@ namespace stream {
|
||||||
int start(session_t &session, const std::string &addr_string) {
|
int start(session_t &session, const std::string &addr_string) {
|
||||||
session.input = input::alloc(session.mail);
|
session.input = input::alloc(session.mail);
|
||||||
|
|
||||||
|
session.input->activity_keyboard_ms = &session.input_activity.last_keyboard_ms;
|
||||||
|
session.input->activity_mouse_ms = &session.input_activity.last_mouse_ms;
|
||||||
|
session.input->activity_gamepad_ms = &session.input_activity.last_gamepad_ms;
|
||||||
|
|
||||||
session.broadcast_ref = broadcast.ref();
|
session.broadcast_ref = broadcast.ref();
|
||||||
if (!session.broadcast_ref) {
|
if (!session.broadcast_ref) {
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -2010,6 +2101,20 @@ namespace stream {
|
||||||
|
|
||||||
session->shutdown_event = mail->event<bool>(mail::shutdown);
|
session->shutdown_event = mail->event<bool>(mail::shutdown);
|
||||||
session->launch_session_id = launch_session.id;
|
session->launch_session_id = launch_session.id;
|
||||||
|
session->client_unique_id = launch_session.unique_id;
|
||||||
|
session->client_name = nvhttp::get_client_name(launch_session.unique_id);
|
||||||
|
|
||||||
|
auto &owner_uuids = config::input.owner_client_uuids;
|
||||||
|
bool is_owner = std::find(owner_uuids.begin(), owner_uuids.end(), launch_session.unique_id) != owner_uuids.end();
|
||||||
|
|
||||||
|
session->input_policy.is_owner_session = is_owner;
|
||||||
|
session->input_policy.allow_gamepad = true;
|
||||||
|
session->input_policy.allow_keyboard = is_owner;
|
||||||
|
session->input_policy.allow_mouse = is_owner;
|
||||||
|
|
||||||
|
if (is_owner) {
|
||||||
|
BOOST_LOG(info) << "Owner session detected for client: "sv << launch_session.unique_id;
|
||||||
|
}
|
||||||
|
|
||||||
session->config = config;
|
session->config = config;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// standard includes
|
// standard includes
|
||||||
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
// lib includes
|
// lib includes
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
// local includes
|
// local includes
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
|
@ -20,8 +23,14 @@ namespace stream {
|
||||||
constexpr auto CONTROL_PORT = 10;
|
constexpr auto CONTROL_PORT = 10;
|
||||||
constexpr auto AUDIO_STREAM_PORT = 11;
|
constexpr auto AUDIO_STREAM_PORT = 11;
|
||||||
|
|
||||||
|
constexpr uint64_t KB_ACTIVE_WINDOW_MS = 500;
|
||||||
|
constexpr uint64_t MOUSE_ACTIVE_WINDOW_MS = 500;
|
||||||
|
constexpr uint64_t GAMEPAD_ACTIVE_WINDOW_MS = 500;
|
||||||
|
|
||||||
struct session_t;
|
struct session_t;
|
||||||
|
|
||||||
|
nlohmann::json get_active_sessions_info();
|
||||||
|
|
||||||
struct config_t {
|
struct config_t {
|
||||||
audio::config_t audio;
|
audio::config_t audio;
|
||||||
video::config_t monitor;
|
video::config_t monitor;
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,7 @@
|
||||||
"mouse": "disabled",
|
"mouse": "disabled",
|
||||||
"high_resolution_scrolling": "enabled",
|
"high_resolution_scrolling": "enabled",
|
||||||
"native_pen_touch": "enabled",
|
"native_pen_touch": "enabled",
|
||||||
|
"owner_client_uuids": "",
|
||||||
"keybindings": "[0x10,0xA0,0x11,0xA2,0x12,0xA4]", // todo: add this to UI
|
"keybindings": "[0x10,0xA0,0x11,0xA2,0x12,0xA4]", // todo: add this to UI
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,15 @@ const config = ref(props.config)
|
||||||
v-model="config.native_pen_touch"
|
v-model="config.native_pen_touch"
|
||||||
default="true"
|
default="true"
|
||||||
></Checkbox>
|
></Checkbox>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="owner_client_uuids" class="form-label">Owner Client UUIDs</label>
|
||||||
|
<input type="text" class="form-control" id="owner_client_uuids"
|
||||||
|
placeholder=""
|
||||||
|
v-model="config.owner_client_uuids" />
|
||||||
|
<div class="form-text">Comma-separated UUIDs of owner clients. Owner sessions start with keyboard/mouse enabled. Find UUIDs in the Troubleshooting page under paired clients.</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,49 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Live Input Status -->
|
||||||
|
<div class="card my-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 id="input_status">Live Input Status</h2>
|
||||||
|
<p>Per-session input activity and policy. Indicators update every second.</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="activeSessions.length === 0" class="card-body pt-0">
|
||||||
|
<em>No active streaming sessions.</em>
|
||||||
|
</div>
|
||||||
|
<ul class="list-group list-group-flush" v-else>
|
||||||
|
<li v-for="s in activeSessions" :key="s.session_id" class="list-group-item">
|
||||||
|
<div class="d-flex align-items-center justify-content-between">
|
||||||
|
<div>
|
||||||
|
<strong>{{ s.client_name || s.client_uuid || 'Unknown' }}</strong>
|
||||||
|
<span v-if="s.is_owner_session" class="badge bg-primary ms-2">Owner</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-3 align-items-center">
|
||||||
|
<span class="d-flex align-items-center gap-1" title="Keyboard">
|
||||||
|
<span :class="['activity-dot', s.activity.keyboard_active ? 'dot-green' : 'dot-gray']"></span>
|
||||||
|
KB
|
||||||
|
<span :class="['badge', s.policy.allow_keyboard ? 'bg-success' : 'bg-secondary']" style="font-size: 0.7em">
|
||||||
|
{{ s.policy.allow_keyboard ? 'ON' : 'OFF' }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="d-flex align-items-center gap-1" title="Mouse">
|
||||||
|
<span :class="['activity-dot', s.activity.mouse_active ? 'dot-green' : 'dot-gray']"></span>
|
||||||
|
Mouse
|
||||||
|
<span :class="['badge', s.policy.allow_mouse ? 'bg-success' : 'bg-secondary']" style="font-size: 0.7em">
|
||||||
|
{{ s.policy.allow_mouse ? 'ON' : 'OFF' }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="d-flex align-items-center gap-1" title="Gamepad">
|
||||||
|
<span :class="['activity-dot', s.activity.gamepad_active ? 'dot-green' : 'dot-gray']"></span>
|
||||||
|
Gamepad
|
||||||
|
<span :class="['badge', s.policy.allow_gamepad ? 'bg-success' : 'bg-secondary']" style="font-size: 0.7em">
|
||||||
|
{{ s.policy.allow_gamepad ? 'ON' : 'OFF' }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<!-- Logs -->
|
<!-- Logs -->
|
||||||
<div class="card my-4">
|
<div class="card my-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
@ -229,6 +272,7 @@
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
activeSessions: [],
|
||||||
clients: [],
|
clients: [],
|
||||||
closeAppPressed: false,
|
closeAppPressed: false,
|
||||||
closeAppStatus: null,
|
closeAppStatus: null,
|
||||||
|
|
@ -237,6 +281,7 @@
|
||||||
logs: 'Loading...',
|
logs: 'Loading...',
|
||||||
logFilter: null,
|
logFilter: null,
|
||||||
logInterval: null,
|
logInterval: null,
|
||||||
|
sessionInterval: null,
|
||||||
restartPressed: false,
|
restartPressed: false,
|
||||||
showApplyMessage: false,
|
showApplyMessage: false,
|
||||||
platform: "",
|
platform: "",
|
||||||
|
|
@ -389,13 +434,32 @@
|
||||||
this.logInterval = setInterval(() => {
|
this.logInterval = setInterval(() => {
|
||||||
this.refreshLogs();
|
this.refreshLogs();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
this.sessionInterval = setInterval(() => {
|
||||||
|
this.refreshActiveSessions();
|
||||||
|
}, 1000);
|
||||||
this.refreshLogs();
|
this.refreshLogs();
|
||||||
this.refreshClients();
|
this.refreshClients();
|
||||||
|
this.refreshActiveSessions();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
clearInterval(this.logInterval);
|
clearInterval(this.logInterval);
|
||||||
|
clearInterval(this.sessionInterval);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
refreshActiveSessions() {
|
||||||
|
fetch("./api/sessions/active")
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((r) => {
|
||||||
|
if (r.status === true && r.sessions) {
|
||||||
|
this.activeSessions = r.sessions;
|
||||||
|
} else {
|
||||||
|
this.activeSessions = [];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.activeSessions = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
refreshLogs() {
|
refreshLogs() {
|
||||||
fetch("./api/logs",)
|
fetch("./api/logs",)
|
||||||
.then((r) => r.text())
|
.then((r) => r.text())
|
||||||
|
|
@ -610,4 +674,21 @@
|
||||||
initApp(app);
|
initApp(app);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.activity-dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
.dot-green {
|
||||||
|
background-color: #22c55e;
|
||||||
|
box-shadow: 0 0 4px #22c55e;
|
||||||
|
}
|
||||||
|
.dot-gray {
|
||||||
|
background-color: #6b7280;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue