[feat] improve CLI commands, controls and npm packaging

This commit is contained in:
dijunkun
2026-10-30 13:32:34 +08:00
parent 5d0a8f857f
commit e6e7e4c0f0
19 changed files with 359 additions and 102 deletions
@@ -0,0 +1,16 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-10-30
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _CONTROLLER_DIAGNOSTICS_H_
#define _CONTROLLER_DIAGNOSTICS_H_
#include <string>
namespace crossdesk {
void InitializeControllerDiagnostics(const std::string &directory);
}
#endif
@@ -0,0 +1,79 @@
#include "controller_diagnostics.h"
#include <log.h>
#include <string_view>
namespace crossdesk {
namespace {
// Signaling errors and SDP can contain credentials. Only audited transport
// events reach this sink; arbitrary MiniRTC messages remain suppressed.
std::string DiagnosticEvent(const spdlog::details::log_msg &message) {
std::string_view source = message.source.filename ? message.source.filename : "";
const auto slash = source.find_last_of("/\\");
if (slash != std::string_view::npos)
source.remove_prefix(slash + 1);
const std::string_view text(message.payload.data(), message.payload.size());
if (source == "controller_session.cpp")
return std::string(text); // Local states/counters only at these call sites.
if (source == "ice_transport.cpp") {
const auto marker = text.rfind("state_change: ");
if (marker != std::string_view::npos) {
const auto state = text.substr(marker + 14);
for (const auto allowed : {"disconnected", "gathering", "connecting",
"connected", "ready", "failed"})
if (state == allowed)
return "ICE state: " + std::string(state);
}
for (const auto allowed : {"Traversal using relay server", "Traversal using p2p",
"Selected ICE pair is not available yet"})
if (text == allowed)
return std::string(text);
}
if (source == "ice_agent.cpp") {
for (const auto allowed : {"DTLS handshake started, awaiting peer packets...",
"DTLS handshake retransmission failed",
"DTLS peer certificate missing",
"DTLS peer fingerprint mismatch",
"DTLS peer fingerprint verified",
"DTLS peer not verified", "No SRTP profile selected",
"SSL_export_keying_material failed", "StartDtls failed",
"libnice has no relay upgrade extension; rebuild dependencies"})
if (text == allowed)
return std::string(text);
for (const auto prefix : {"DTLS handshake timed out after ",
"DTLS handshake start failed, err=",
"DTLS packet processing failed, err="})
if (text.substr(0, std::char_traits<char>::length(prefix)) == prefix)
return std::string(prefix); // Omit variable payload entirely.
}
return {};
}
class DiagnosticSink final : public spdlog::sinks::base_sink<std::mutex> {
public:
explicit DiagnosticSink(const std::string &directory)
: file_(directory + "/transport.log", 1024 * 1024, 2) {}
private:
void sink_it_(const spdlog::details::log_msg &message) override {
const auto event = DiagnosticEvent(message);
if (event.empty())
return;
auto sanitized = message;
sanitized.payload = spdlog::string_view_t(event.data(), event.size());
file_.log(sanitized);
file_.flush();
}
void flush_() override { file_.flush(); }
spdlog::sinks::rotating_file_sink_mt file_;
};
} // namespace
void InitializeControllerDiagnostics(const std::string &directory) {
auto sink = std::make_shared<DiagnosticSink>(directory);
minirtc::InitLogger(directory);
auto logger = minirtc::get_logger();
// Connect calls this after the previous peer and its callbacks stop.
logger->sinks() = {std::move(sink)};
logger->set_level(spdlog::level::info);
}
} // namespace crossdesk
+10 -3
View File
@@ -1,4 +1,5 @@
#include "controller_session.h"
#include "controller_diagnostics.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
@@ -136,8 +137,8 @@ void ControllerSession::Connect(SessionOptions options, std::string device,
// The server assigns a temporary controller identity on this connection.
// Never register a desktop identity or persist local credentials.
client_id_.clear();
minirtc::InitLogger(options_.log_directory);
minirtc::get_logger()->set_level(spdlog::level::off);
InitializeControllerDiagnostics(options_.log_directory);
SPDLOG_LOGGER_INFO(minirtc::get_logger(), "Session registering");
auto p = Parameters();
peer_ = CreatePeer(&p);
if (!peer_) {
@@ -169,6 +170,7 @@ void ControllerSession::Tick() {
if (Finished())
break;
if (e.kind == 0) {
SPDLOG_LOGGER_INFO(minirtc::get_logger(), "Signal status: {}", e.code);
if (e.code == SignalTlsCertError) {
Fail("tls_certificate_error");
break;
@@ -199,6 +201,8 @@ void ControllerSession::Tick() {
password_.clear();
}
} else if (e.kind == 1) {
SPDLOG_LOGGER_INFO(minirtc::get_logger(), "Connection status: {} (session {})",
e.code, state_);
if (e.code == ::Connected && !Connected()) {
state_ = "connected";
auto hello = MakeHostInformation("CrossDesk CLI", {}, false);
@@ -215,7 +219,7 @@ void ControllerSession::Tick() {
else if (e.code == RemoteUnavailable)
Fail("device_offline_or_unavailable");
else if (e.code == Failed)
Fail("connection_failed");
Fail(Connected() ? "connection_lost" : "connection_failed");
else if (e.code == Closed || e.code == Disconnected)
Fail("remote_disconnected");
} else if (e.kind == 2 && Connected()) {
@@ -275,6 +279,7 @@ void ControllerSession::ClosePeers() {
password_.clear();
}
void ControllerSession::Fail(const char *reason) {
SPDLOG_LOGGER_INFO(minirtc::get_logger(), "Session failed: {}", reason);
ReleaseInput();
error_ = reason;
state_ = "failed";
@@ -345,6 +350,8 @@ void ControllerSession::Preview() {
Send(kControlStream, s.ToJson().dump());
}
void ControllerSession::SetControl(bool enabled) {
if (control_ != enabled)
SPDLOG_LOGGER_INFO(minirtc::get_logger(), "Remote control: {}", enabled);
if (!enabled)
ReleaseInput();
control_ = enabled;
+2 -1
View File
@@ -54,7 +54,7 @@ bool KittyReply(const std::string &response);
// Incremental parser: protocol replies and unknown escapes are consumed
// locally. Key taps always release immediately; paste uses the UTF-8 text
// protocol. In command_mode, control records the mode to restore while the
// session's remote input is suspended.
// session's remote input is suspended until the user explicitly leaves the mode.
class Input {
public:
void Feed(const std::string &bytes, ControllerSession &, const Grid *grid);
@@ -62,6 +62,7 @@ public:
std::string error;
double zoom = 1, center_x = .5, center_y = .5;
uint64_t view_revision = 0;
uint64_t mouse_reports = 0;
private:
std::string pending_;
+15 -14
View File
@@ -31,10 +31,14 @@ bool Input::LocalKey(int key, ControllerSession &s, const Grid *grid) {
s.SetControl(false);
return true;
}
bool command = command_mode;
command_mode = false;
if (key == 'q' || (key == 3 && !command)) {
if (command_mode && (key == 27 || key == 3)) {
command_mode = false;
s.SetControl(control);
return true;
}
if (key == 'q' || key == 3) {
exit = true;
command_mode = false;
control = false;
} else if (key == 'c') {
control = !control;
@@ -42,12 +46,12 @@ bool Input::LocalKey(int key, ControllerSession &s, const Grid *grid) {
key == 'd' || key == 'i' || key == 'j' || key == 'k' ||
key == 'l') {
Navigate(char(key), grid);
} else if (!command) {
} else if (!command_mode) {
return false;
} else if (key != 27 && key != 3) {
error = "Unknown local command; Ctrl+] for help";
} else {
error = "Unknown local command; ? for help";
}
s.SetControl(control);
s.SetControl(control && !command_mode);
return true;
}
void Input::Navigate(char key, const Grid *grid) {
@@ -94,7 +98,6 @@ void Input::Feed(const std::string &bytes, ControllerSession &s,
if (pending_.size() + bytes.size() > 32768) {
pending_.clear();
pasting_ = false;
command_mode = false;
s.SetControl(false);
control = false;
return;
@@ -117,11 +120,8 @@ void Input::Feed(const std::string &bytes, ControllerSession &s,
auto end = pending_.find("\033[201~");
if (end == std::string::npos)
return; // bounded by 32 KiB; one UTF-8 transaction
if (command_mode) {
// Pasted text must never become local commands or leak to the remote.
command_mode = false;
s.SetControl(control);
} else if (control && !s.Text(pending_.substr(0, end)))
// Drop local-mode paste without leaving the mode or running commands.
if (control && !command_mode && !s.Text(pending_.substr(0, end)))
error = "Remote UTF-8 text input unavailable";
pending_.erase(0, end + 6);
pasting_ = false;
@@ -159,7 +159,6 @@ void Input::Feed(const std::string &bytes, ControllerSession &s,
pending_.erase(0, end + 1);
if (seq == "\033[O") {
control = false;
command_mode = false;
s.SetControl(false);
continue;
}
@@ -224,6 +223,8 @@ void Input::Feed(const std::string &bytes, ControllerSession &s,
} else
mouse = mouse && cur == limit;
}
if (mouse)
++mouse_reports;
if (grid && mouse && !command_mode) {
auto pos = MapPointer(*grid, x - 1, y - 1);
if (!control) {