mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-08-18 17:16:01 +08:00
495 lines
19 KiB
C++
495 lines
19 KiB
C++
#include "runtime/peer_event_handler.h"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <shared_mutex>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
|
|
#include "device_controller.h"
|
|
#include "file_transfer.h"
|
|
#include "localization.h"
|
|
#include "platform.h"
|
|
#include "rd_log.h"
|
|
#include "runtime/gui_runtime.h"
|
|
#include "runtime/remote_action_codec.h"
|
|
|
|
#if _WIN32
|
|
#include "interactive_state.h"
|
|
#include "service_host.h"
|
|
#endif
|
|
|
|
namespace crossdesk {
|
|
|
|
PeerEventHandler::PeerEventHandler(GuiRuntime& owner) : owner_(owner) {}
|
|
|
|
void PeerEventHandler::OnSignalMessage(const char* message, size_t size,
|
|
void* user_data) {
|
|
auto* handler = static_cast<PeerEventHandler*>(user_data);
|
|
GuiRuntime* runtime = handler ? &handler->owner_ : nullptr;
|
|
if (!runtime || !message || size == 0) {
|
|
return;
|
|
}
|
|
std::string s(message, size);
|
|
auto j = nlohmann::json::parse(s, nullptr, false);
|
|
if (j.is_discarded() || !j.contains("type") || !j["type"].is_string()) {
|
|
return;
|
|
}
|
|
std::string type = j["type"].get<std::string>();
|
|
if (type == "presence") {
|
|
if (j.contains("devices") && j["devices"].is_array()) {
|
|
for (auto& dev : j["devices"]) {
|
|
if (!dev.is_object()) {
|
|
continue;
|
|
}
|
|
if (!dev.contains("id") || !dev["id"].is_string()) {
|
|
continue;
|
|
}
|
|
if (!dev.contains("online") || !dev["online"].is_boolean()) {
|
|
continue;
|
|
}
|
|
std::string id = dev["id"].get<std::string>();
|
|
bool online = dev["online"].get<bool>();
|
|
runtime->device_presence_cache_.SetOnline(id, online);
|
|
{
|
|
std::lock_guard<std::mutex> lock(
|
|
runtime->pending_presence_probe_mutex_);
|
|
if (runtime->pending_presence_probe_ &&
|
|
runtime->pending_presence_remote_id_ == id) {
|
|
runtime->pending_presence_result_ready_ = true;
|
|
runtime->pending_presence_online_ = online;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (type == "presence_update") {
|
|
if (j.contains("id") && j["id"].is_string() && j.contains("online") &&
|
|
j["online"].is_boolean()) {
|
|
std::string id = j["id"].get<std::string>();
|
|
bool online = j["online"].get<bool>();
|
|
if (!id.empty()) {
|
|
runtime->device_presence_cache_.SetOnline(id, online);
|
|
{
|
|
std::lock_guard<std::mutex> lock(
|
|
runtime->pending_presence_probe_mutex_);
|
|
if (runtime->pending_presence_probe_ &&
|
|
runtime->pending_presence_remote_id_ == id) {
|
|
runtime->pending_presence_result_ready_ = true;
|
|
runtime->pending_presence_online_ = online;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (type == "change_password") {
|
|
std::lock_guard<std::mutex> lock(runtime->password_change_mutex_);
|
|
if (!runtime->password_change_pending_) {
|
|
LOG_WARN("Ignore unexpected password change response");
|
|
return;
|
|
}
|
|
|
|
if (!j.contains("request_id") || !j["request_id"].is_string() ||
|
|
j["request_id"].get<std::string>() !=
|
|
runtime->pending_password_change_request_id_) {
|
|
LOG_WARN("Ignore password change response with unexpected request id");
|
|
return;
|
|
}
|
|
|
|
if (j.contains("user_id") && j["user_id"].is_string()) {
|
|
const std::string response_user_id = j["user_id"].get<std::string>();
|
|
if (!response_user_id.empty() &&
|
|
response_user_id != runtime->client_id_) {
|
|
LOG_WARN("Ignore password change response for unexpected id [{}]",
|
|
response_user_id);
|
|
return;
|
|
}
|
|
}
|
|
|
|
runtime->password_change_succeeded_ =
|
|
j.contains("status") && j["status"].is_string() &&
|
|
j["status"].get<std::string>() == "success";
|
|
runtime->password_change_error_ =
|
|
j.contains("reason") && j["reason"].is_string()
|
|
? j["reason"].get<std::string>()
|
|
: "Password change failed";
|
|
runtime->password_change_result_ready_ = true;
|
|
}
|
|
}
|
|
|
|
void PeerEventHandler::OnSignalStatus(SignalStatus status, const char* user_id,
|
|
size_t user_id_size, void* user_data) {
|
|
auto* handler = static_cast<PeerEventHandler*>(user_data);
|
|
GuiRuntime* runtime = handler ? &handler->owner_ : nullptr;
|
|
if (!runtime) {
|
|
return;
|
|
}
|
|
|
|
std::string client_id(user_id, user_id_size);
|
|
if (client_id == runtime->client_id_) {
|
|
runtime->signal_status_ = status;
|
|
if (SignalStatus::SignalConnecting == status) {
|
|
runtime->signal_connected_ = false;
|
|
} else if (SignalStatus::SignalConnected == status) {
|
|
runtime->signal_connected_ = true;
|
|
runtime->need_to_send_recent_connections_ = true;
|
|
LOG_INFO("[{}] connected to signal server", client_id);
|
|
} else if (SignalStatus::SignalFailed == status) {
|
|
runtime->signal_connected_ = false;
|
|
} else if (SignalStatus::SignalClosed == status) {
|
|
runtime->signal_connected_ = false;
|
|
} else if (SignalStatus::SignalReconnecting == status) {
|
|
runtime->signal_connected_ = false;
|
|
} else if (SignalStatus::SignalServerClosed == status) {
|
|
runtime->signal_connected_ = false;
|
|
} else if (SignalStatus::SignalTlsCertError == status) {
|
|
runtime->signal_connected_ = false;
|
|
}
|
|
} else {
|
|
if (client_id.rfind("C-", 0) != 0) {
|
|
return;
|
|
}
|
|
|
|
std::string remote_id(client_id.begin() + 2, client_id.end());
|
|
// std::shared_lock lock(runtime->remote_sessions_mutex_);
|
|
if (runtime->remote_sessions_.find(remote_id) ==
|
|
runtime->remote_sessions_.end()) {
|
|
return;
|
|
}
|
|
auto props = runtime->remote_sessions_.find(remote_id)->second;
|
|
props->signal_status_ = status;
|
|
if (SignalStatus::SignalConnecting == status) {
|
|
props->signal_connected_ = false;
|
|
} else if (SignalStatus::SignalConnected == status) {
|
|
props->signal_connected_ = true;
|
|
LOG_INFO("[{}] connected to signal server", remote_id);
|
|
} else if (SignalStatus::SignalFailed == status) {
|
|
props->signal_connected_ = false;
|
|
} else if (SignalStatus::SignalClosed == status) {
|
|
props->signal_connected_ = false;
|
|
} else if (SignalStatus::SignalReconnecting == status) {
|
|
props->signal_connected_ = false;
|
|
} else if (SignalStatus::SignalServerClosed == status) {
|
|
props->signal_connected_ = false;
|
|
} else if (SignalStatus::SignalTlsCertError == status) {
|
|
props->signal_connected_ = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|
const char* user_id,
|
|
const size_t user_id_size,
|
|
void* user_data) {
|
|
auto* handler = static_cast<PeerEventHandler*>(user_data);
|
|
GuiRuntime* runtime = handler ? &handler->owner_ : nullptr;
|
|
if (!runtime) return;
|
|
|
|
std::string remote_id(user_id, user_id_size);
|
|
std::shared_ptr<GuiRuntime::RemoteSession> props;
|
|
{
|
|
std::shared_lock lock(runtime->remote_sessions_mutex_);
|
|
auto it = runtime->remote_sessions_.find(remote_id);
|
|
if (it != runtime->remote_sessions_.end()) {
|
|
props = it->second;
|
|
}
|
|
}
|
|
|
|
if (props) {
|
|
runtime->is_client_mode_ = true;
|
|
runtime->show_connection_status_window_ = true;
|
|
props->connection_status_.store(status);
|
|
|
|
switch (status) {
|
|
case ConnectionStatus::Connected: {
|
|
runtime->ResetRemoteServiceStatus(*props);
|
|
{
|
|
RemoteAction remote_action;
|
|
remote_action.i.display_num =
|
|
runtime->devices_.display_info_list().size();
|
|
remote_action.i.display_list =
|
|
(char**)malloc(remote_action.i.display_num * sizeof(char*));
|
|
remote_action.i.left =
|
|
(int*)malloc(remote_action.i.display_num * sizeof(int));
|
|
remote_action.i.top =
|
|
(int*)malloc(remote_action.i.display_num * sizeof(int));
|
|
remote_action.i.right =
|
|
(int*)malloc(remote_action.i.display_num * sizeof(int));
|
|
remote_action.i.bottom =
|
|
(int*)malloc(remote_action.i.display_num * sizeof(int));
|
|
for (int i = 0; i < remote_action.i.display_num; i++) {
|
|
LOG_INFO("Local display [{}:{}]", i + 1,
|
|
runtime->devices_.display_info_list()[i].name);
|
|
remote_action.i.display_list[i] = (char*)malloc(
|
|
runtime->devices_.display_info_list()[i].name.length() + 1);
|
|
strncpy(remote_action.i.display_list[i],
|
|
runtime->devices_.display_info_list()[i].name.c_str(),
|
|
runtime->devices_.display_info_list()[i].name.length());
|
|
remote_action.i
|
|
.display_list[i][runtime->devices_.display_info_list()[i]
|
|
.name.length()] = '\0';
|
|
remote_action.i.left[i] =
|
|
runtime->devices_.display_info_list()[i].left;
|
|
remote_action.i.top[i] =
|
|
runtime->devices_.display_info_list()[i].top;
|
|
remote_action.i.right[i] =
|
|
runtime->devices_.display_info_list()[i].right;
|
|
remote_action.i.bottom[i] =
|
|
runtime->devices_.display_info_list()[i].bottom;
|
|
}
|
|
|
|
std::string host_name = GetHostName();
|
|
remote_action.type = ControlType::host_infomation;
|
|
memcpy(&remote_action.i.host_name, host_name.data(),
|
|
host_name.size());
|
|
remote_action.i.host_name[host_name.size()] = '\0';
|
|
remote_action.i.host_name_size = host_name.size();
|
|
|
|
std::string msg = remote_action.to_json();
|
|
int ret = SendReliableDataFrame(props->peer_, msg.data(), msg.size(),
|
|
runtime->control_data_label_.c_str());
|
|
remote_action_codec::Free(remote_action);
|
|
}
|
|
|
|
if (!runtime->need_to_create_stream_window_ &&
|
|
!runtime->remote_sessions_.empty()) {
|
|
runtime->need_to_create_stream_window_ = true;
|
|
}
|
|
props->connection_established_ = true;
|
|
runtime->start_keyboard_capturer_ = true;
|
|
break;
|
|
}
|
|
case ConnectionStatus::Disconnected:
|
|
case ConnectionStatus::Failed:
|
|
case ConnectionStatus::Closed: {
|
|
runtime->keyboard_.ReleaseRemotePressedKeys(remote_id,
|
|
"connection_closed");
|
|
props->connection_established_ = false;
|
|
props->enable_mouse_control_ = false;
|
|
runtime->ResetRemoteServiceStatus(*props);
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
|
props->front_frame_.reset();
|
|
props->back_frame_.reset();
|
|
props->video_width_ = 0;
|
|
props->video_height_ = 0;
|
|
props->video_size_ = 0;
|
|
props->render_rect_dirty_ = true;
|
|
props->stream_cleanup_pending_ = true;
|
|
}
|
|
|
|
runtime->focus_on_stream_window_ = false;
|
|
|
|
break;
|
|
}
|
|
case ConnectionStatus::IncorrectPassword: {
|
|
runtime->password_validating_ = false;
|
|
runtime->password_validating_time_++;
|
|
if (runtime->connect_button_pressed_) {
|
|
runtime->connect_button_pressed_ = false;
|
|
props->connection_established_ = false;
|
|
runtime->connect_button_label_ =
|
|
localization::connect[runtime->localization_language_index_];
|
|
}
|
|
break;
|
|
}
|
|
case ConnectionStatus::NoSuchTransmissionId:
|
|
case ConnectionStatus::RemoteUnavailable: {
|
|
if (runtime->connect_button_pressed_) {
|
|
props->connection_established_ = false;
|
|
runtime->connect_button_label_ =
|
|
localization::connect[runtime->localization_language_index_];
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
runtime->is_client_mode_ = false;
|
|
runtime->show_connection_status_window_ = true;
|
|
{
|
|
std::unique_lock lock(runtime->connection_status_mutex_);
|
|
runtime->connection_status_[remote_id] = status;
|
|
}
|
|
|
|
switch (status) {
|
|
case ConnectionStatus::Connected: {
|
|
#if _WIN32
|
|
runtime->last_windows_service_status_tick_ = 0;
|
|
#endif
|
|
{
|
|
RemoteAction remote_action;
|
|
remote_action.i.display_num =
|
|
runtime->devices_.display_info_list().size();
|
|
remote_action.i.display_list =
|
|
(char**)malloc(remote_action.i.display_num * sizeof(char*));
|
|
remote_action.i.left =
|
|
(int*)malloc(remote_action.i.display_num * sizeof(int));
|
|
remote_action.i.top =
|
|
(int*)malloc(remote_action.i.display_num * sizeof(int));
|
|
remote_action.i.right =
|
|
(int*)malloc(remote_action.i.display_num * sizeof(int));
|
|
remote_action.i.bottom =
|
|
(int*)malloc(remote_action.i.display_num * sizeof(int));
|
|
for (int i = 0; i < remote_action.i.display_num; i++) {
|
|
LOG_INFO("Local display [{}:{}]", i + 1,
|
|
runtime->devices_.display_info_list()[i].name);
|
|
remote_action.i.display_list[i] = (char*)malloc(
|
|
runtime->devices_.display_info_list()[i].name.length() + 1);
|
|
strncpy(remote_action.i.display_list[i],
|
|
runtime->devices_.display_info_list()[i].name.c_str(),
|
|
runtime->devices_.display_info_list()[i].name.length());
|
|
remote_action.i
|
|
.display_list[i][runtime->devices_.display_info_list()[i]
|
|
.name.length()] = '\0';
|
|
remote_action.i.left[i] =
|
|
runtime->devices_.display_info_list()[i].left;
|
|
remote_action.i.top[i] =
|
|
runtime->devices_.display_info_list()[i].top;
|
|
remote_action.i.right[i] =
|
|
runtime->devices_.display_info_list()[i].right;
|
|
remote_action.i.bottom[i] =
|
|
runtime->devices_.display_info_list()[i].bottom;
|
|
}
|
|
|
|
std::string host_name = GetHostName();
|
|
remote_action.type = ControlType::host_infomation;
|
|
memcpy(&remote_action.i.host_name, host_name.data(),
|
|
host_name.size());
|
|
remote_action.i.host_name[host_name.size()] = '\0';
|
|
remote_action.i.host_name_size = host_name.size();
|
|
|
|
std::string msg = remote_action.to_json();
|
|
int ret =
|
|
SendReliableDataFrame(runtime->peer_, msg.data(), msg.size(),
|
|
runtime->control_data_label_.c_str());
|
|
remote_action_codec::Free(remote_action);
|
|
}
|
|
|
|
runtime->need_to_destroy_server_window_.store(
|
|
false, std::memory_order_release);
|
|
runtime->need_to_create_server_window_.store(true,
|
|
std::memory_order_release);
|
|
runtime->is_server_mode_ = true;
|
|
runtime->start_screen_capturer_ = true;
|
|
runtime->start_speaker_capturer_ = true;
|
|
runtime->remote_client_id_ = remote_id;
|
|
runtime->start_mouse_controller_ = true;
|
|
{
|
|
std::shared_lock lock(runtime->connection_status_mutex_);
|
|
if (std::all_of(runtime->connection_status_.begin(),
|
|
runtime->connection_status_.end(),
|
|
[](const auto& kv) {
|
|
return kv.first.find("web") != std::string::npos;
|
|
})) {
|
|
runtime->show_cursor_ = true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case ConnectionStatus::Disconnected:
|
|
case ConnectionStatus::Failed:
|
|
case ConnectionStatus::Closed: {
|
|
runtime->HandleServerControllerDisconnected(remote_id,
|
|
"connection_closed");
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PeerEventHandler::OnNetStatusReport(
|
|
const char* client_id, size_t client_id_size, TraversalMode mode,
|
|
const XNetTrafficStats* net_traffic_stats, const char* user_id,
|
|
const size_t user_id_size, void* user_data) {
|
|
auto* handler = static_cast<PeerEventHandler*>(user_data);
|
|
GuiRuntime* runtime = handler ? &handler->owner_ : nullptr;
|
|
if (!runtime) {
|
|
return;
|
|
}
|
|
|
|
if (strchr(client_id, '@') != nullptr && strchr(user_id, '-') == nullptr) {
|
|
std::string id, password;
|
|
const char* at_pos = strchr(client_id, '@');
|
|
if (at_pos == nullptr) {
|
|
id = client_id;
|
|
password.clear();
|
|
} else {
|
|
id.assign(client_id, at_pos - client_id);
|
|
password = at_pos + 1;
|
|
}
|
|
|
|
bool is_self_hosted = runtime->config_center_->IsSelfHosted();
|
|
|
|
if (is_self_hosted) {
|
|
memset(&runtime->client_id_, 0, sizeof(runtime->client_id_));
|
|
strncpy(runtime->client_id_, id.c_str(), sizeof(runtime->client_id_) - 1);
|
|
runtime->client_id_[sizeof(runtime->client_id_) - 1] = '\0';
|
|
|
|
memset(&runtime->password_saved_, 0, sizeof(runtime->password_saved_));
|
|
strncpy(runtime->password_saved_, password.c_str(),
|
|
sizeof(runtime->password_saved_) - 1);
|
|
runtime->password_saved_[sizeof(runtime->password_saved_) - 1] = '\0';
|
|
|
|
memset(&runtime->self_hosted_id_, 0, sizeof(runtime->self_hosted_id_));
|
|
strncpy(runtime->self_hosted_id_, client_id,
|
|
sizeof(runtime->self_hosted_id_) - 1);
|
|
runtime->self_hosted_id_[sizeof(runtime->self_hosted_id_) - 1] = '\0';
|
|
|
|
LOG_INFO("Use self-hosted client id [{}] and save to cache file", id);
|
|
|
|
runtime->settings_.PersistSelfHostedIdentity(client_id);
|
|
} else {
|
|
memset(&runtime->client_id_, 0, sizeof(runtime->client_id_));
|
|
strncpy(runtime->client_id_, id.c_str(), sizeof(runtime->client_id_) - 1);
|
|
runtime->client_id_[sizeof(runtime->client_id_) - 1] = '\0';
|
|
|
|
memset(&runtime->password_saved_, 0, sizeof(runtime->password_saved_));
|
|
strncpy(runtime->password_saved_, password.c_str(),
|
|
sizeof(runtime->password_saved_) - 1);
|
|
runtime->password_saved_[sizeof(runtime->password_saved_) - 1] = '\0';
|
|
|
|
memset(&runtime->client_id_with_password_, 0,
|
|
sizeof(runtime->client_id_with_password_));
|
|
strncpy(runtime->client_id_with_password_, client_id,
|
|
sizeof(runtime->client_id_with_password_) - 1);
|
|
runtime
|
|
->client_id_with_password_[sizeof(runtime->client_id_with_password_) -
|
|
1] = '\0';
|
|
|
|
LOG_INFO("Use client id [{}] and save id into cache file", id);
|
|
runtime->settings_.Save();
|
|
}
|
|
}
|
|
|
|
std::string remote_id(user_id, user_id_size);
|
|
// std::shared_lock lock(runtime->remote_sessions_mutex_);
|
|
if (runtime->remote_sessions_.find(remote_id) ==
|
|
runtime->remote_sessions_.end()) {
|
|
return;
|
|
}
|
|
auto props = runtime->remote_sessions_.find(remote_id)->second;
|
|
if (props->traversal_mode_ != mode) {
|
|
props->traversal_mode_ = mode;
|
|
LOG_INFO("Net mode: [{}]", int(props->traversal_mode_));
|
|
}
|
|
|
|
if (!net_traffic_stats) {
|
|
return;
|
|
}
|
|
|
|
// only display client side net status if connected to itself
|
|
if (!(runtime->peer_reserved_ && !strstr(client_id, "C-"))) {
|
|
props->net_traffic_stats_ = *net_traffic_stats;
|
|
}
|
|
}
|
|
} // namespace crossdesk
|