Compare commits

..
13 Commits
12 changed files with 229 additions and 47 deletions
+1 -1
View File
@@ -260,7 +260,7 @@ jobs:
- name: Build CrossDesk - name: Build CrossDesk
run: | run: |
xmake f --target_minver=${MACOSX_DEPLOYMENT_TARGET} --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=true -y xmake f --target_minver=${MACOSX_DEPLOYMENT_TARGET} --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=false -y
xmake b -vy crossdesk xmake b -vy crossdesk
- name: Package CrossDesk app - name: Package CrossDesk app
+103 -23
View File
@@ -1342,33 +1342,42 @@ void GuiApplication::BindMainCallbacks() {
if (password.size() != 6) { if (password.size() != 6) {
return false; return false;
} }
std::memset(password_saved_, 0, sizeof(password_saved_));
std::strncpy(password_saved_, password.c_str(),
sizeof(password_saved_) - 1);
if (config_center_->IsSelfHosted()) { if (!peer_ || !signal_connected_) {
std::string identity = self_hosted_id_; offline_warning_text_ =
if (const auto at = identity.find('@'); at != std::string::npos) { localization::signal_disconnected[localization_language_index_];
identity.resize(at); show_offline_warning_window_ = true;
return true;
} }
if (identity.empty()) {
identity = client_id_; std::string request_id;
{
std::lock_guard<std::mutex> lock(password_change_mutex_);
if (password_change_pending_) {
return true;
} }
identity += "@" + password; request_id = std::to_string(++next_password_change_request_id_);
std::memset(self_hosted_id_, 0, sizeof(self_hosted_id_)); password_change_pending_ = true;
std::strncpy(self_hosted_id_, identity.c_str(), password_change_result_ready_ = false;
sizeof(self_hosted_id_) - 1); password_change_succeeded_ = false;
} else { password_change_requested_at_ = std::chrono::steady_clock::now();
const std::string identity = std::string(client_id_) + "@" + password; pending_password_change_request_id_ = request_id;
std::memset(client_id_with_password_, 0, pending_local_password_ = password;
sizeof(client_id_with_password_)); password_change_error_.clear();
std::strncpy(client_id_with_password_, identity.c_str(),
sizeof(client_id_with_password_) - 1);
} }
settings_.Save();
if (peer_) { const nlohmann::json request = {{"type", "change_password"},
LeaveConnection(peer_, client_id_); {"request_id", request_id},
DestroyPeer(&peer_); {"new_password", password}};
const std::string message = request.dump();
if (SendSignalMessage(peer_, message.data(), message.size()) != 0) {
std::lock_guard<std::mutex> lock(password_change_mutex_);
password_change_pending_ = false;
pending_password_change_request_id_.clear();
pending_local_password_.clear();
offline_warning_text_ =
localization::signal_disconnected[localization_language_index_];
show_offline_warning_window_ = true;
} }
return true; return true;
}); });
@@ -1778,6 +1787,7 @@ void GuiApplication::Tick() {
slint::quit_event_loop(); slint::quit_event_loop();
return; return;
} }
HandlePasswordChangeResult();
if (!peer_) { if (!peer_) {
CreateConnectionPeer(); CreateConnectionPeer();
} }
@@ -1852,6 +1862,76 @@ void GuiApplication::Tick() {
SyncServerWindow(); SyncServerWindow();
} }
void GuiApplication::HandlePasswordChangeResult() {
bool succeeded = false;
std::string new_password;
std::string error;
{
std::lock_guard<std::mutex> lock(password_change_mutex_);
if (password_change_pending_ && !password_change_result_ready_ &&
std::chrono::steady_clock::now() - password_change_requested_at_ >=
std::chrono::seconds(10)) {
password_change_result_ready_ = true;
password_change_succeeded_ = false;
password_change_error_ = "Server did not respond";
}
if (!password_change_result_ready_) {
return;
}
succeeded = password_change_succeeded_;
new_password = pending_local_password_;
error = password_change_error_;
password_change_pending_ = false;
password_change_result_ready_ = false;
password_change_succeeded_ = false;
pending_password_change_request_id_.clear();
pending_local_password_.clear();
password_change_error_.clear();
}
if (!succeeded) {
LOG_WARN("Password change failed: {}", error);
offline_warning_text_ = localization::failed[localization_language_index_];
if (!error.empty()) {
offline_warning_text_ += ": " + error;
}
show_offline_warning_window_ = true;
return;
}
std::memset(password_saved_, 0, sizeof(password_saved_));
std::strncpy(password_saved_, new_password.c_str(),
sizeof(password_saved_) - 1);
const std::string identity = std::string(client_id_) + "@" + new_password;
if (config_center_->IsSelfHosted()) {
std::memset(self_hosted_id_, 0, sizeof(self_hosted_id_));
std::strncpy(self_hosted_id_, identity.c_str(),
sizeof(self_hosted_id_) - 1);
} else {
std::memset(client_id_with_password_, 0,
sizeof(client_id_with_password_));
std::strncpy(client_id_with_password_, identity.c_str(),
sizeof(client_id_with_password_) - 1);
}
if (settings_.Save() != 0) {
LOG_ERROR("Password changed on server but could not be saved locally");
offline_warning_text_ = localization::failed[localization_language_index_];
show_offline_warning_window_ = true;
return;
}
LOG_INFO("Password changed successfully for [{}]", client_id_);
if (peer_) {
LeaveConnection(peer_, client_id_);
DestroyPeer(&peer_);
}
}
void GuiApplication::UpdateLocalization() { void GuiApplication::UpdateLocalization() {
const int language = const int language =
localization::detail::ClampLanguageIndex(localization_language_index_); localization::detail::ClampLanguageIndex(localization_language_index_);
+1
View File
@@ -31,6 +31,7 @@ private:
void BindStreamCallbacks(); void BindStreamCallbacks();
void BindServerCallbacks(); void BindServerCallbacks();
void Tick(); void Tick();
void HandlePasswordChangeResult();
void SyncMainWindow(); void SyncMainWindow();
void SyncConnectionDialog(); void SyncConnectionDialog();
void SyncPlatformDialogs(); void SyncPlatformDialogs();
@@ -60,6 +60,26 @@ int SessionDeviceManager::InitializeScreenCapturer() {
return; return;
} }
std::vector<std::string> connected_remote_ids;
{
std::shared_lock lock(owner_.connection_status_mutex_);
connected_remote_ids.reserve(owner_.connection_status_.size());
for (const auto &[remote_id, status] : owner_.connection_status_) {
if (status == ConnectionStatus::Connected) {
connected_remote_ids.push_back(remote_id);
}
}
}
// Capture can still deliver frames while ICE is gathering or after
// the final controller disconnects. Do not broadcast those frames to
// MiniRTC: a broadcast also reaches newly joining peers whose ICE
// transport is not ready yet.
if (connected_remote_ids.empty()) {
last_frame_time_ = now_time;
return;
}
const std::string stream_id = display_name ? display_name : ""; const std::string stream_id = display_name ? display_name : "";
const bool resumed_after_gap = const bool resumed_after_gap =
last_frame_time_ != 0 && duration >= kCaptureResumeKeyFrameGapMs; last_frame_time_ != 0 && duration >= kCaptureResumeKeyFrameGapMs;
@@ -79,7 +99,10 @@ int SessionDeviceManager::InitializeScreenCapturer() {
frame.width = width; frame.width = width;
frame.height = height; frame.height = height;
frame.captured_timestamp = GetSystemTimeMicros(owner_.peer_); frame.captured_timestamp = GetSystemTimeMicros(owner_.peer_);
SendVideoFrame(owner_.peer_, &frame, stream_id.c_str()); for (const std::string &remote_id : connected_remote_ids) {
SendVideoFrameToPeer(owner_.peer_, &frame, stream_id.c_str(),
remote_id.data(), remote_id.size());
}
last_video_frame_stream_id_ = stream_id; last_video_frame_stream_id_ = stream_id;
last_frame_time_ = now_time; last_frame_time_ = now_time;
}); });
+23 -8
View File
@@ -134,14 +134,7 @@ int SettingsManager::Load() {
lock.unlock(); lock.unlock();
const char *at_pos = std::strchr(owner_.client_id_with_password_, '@'); ActivateCachedPublicIdentity();
if (at_pos != nullptr) {
const std::string id(owner_.client_id_with_password_,
at_pos - owner_.client_id_with_password_);
const std::string password(at_pos + 1);
CopyString(owner_.client_id_, id.c_str());
CopyString(owner_.password_saved_, password.c_str());
}
owner_.thumbnail_ = owner_.thumbnail_ =
std::make_shared<Thumbnail>(owner_.cache_path_ + "/thumbnails/", std::make_shared<Thumbnail>(owner_.cache_path_ + "/thumbnails/",
@@ -195,6 +188,8 @@ bool SettingsManager::LoadCachedSelfHostedIdentity() {
std::lock_guard<std::mutex> lock(cache_mutex_); std::lock_guard<std::mutex> lock(cache_mutex_);
if (!ReadV2Locked() || cache_v2_.self_hosted_id[0] == '\0') { if (!ReadV2Locked() || cache_v2_.self_hosted_id[0] == '\0') {
std::memset(owner_.self_hosted_id_, 0, sizeof(owner_.self_hosted_id_)); std::memset(owner_.self_hosted_id_, 0, sizeof(owner_.self_hosted_id_));
std::memset(owner_.client_id_, 0, sizeof(owner_.client_id_));
std::memset(owner_.password_saved_, 0, sizeof(owner_.password_saved_));
return false; return false;
} }
@@ -212,6 +207,26 @@ bool SettingsManager::LoadCachedSelfHostedIdentity() {
return true; return true;
} }
bool SettingsManager::ActivateCachedPublicIdentity() {
if (owner_.client_id_with_password_[0] == '\0') {
std::memset(owner_.client_id_, 0, sizeof(owner_.client_id_));
std::memset(owner_.password_saved_, 0, sizeof(owner_.password_saved_));
return false;
}
const char *at_pos = std::strchr(owner_.client_id_with_password_, '@');
if (at_pos == nullptr) {
CopyString(owner_.client_id_, owner_.client_id_with_password_);
std::memset(owner_.password_saved_, 0, sizeof(owner_.password_saved_));
} else {
const std::string id(owner_.client_id_with_password_,
at_pos - owner_.client_id_with_password_);
CopyString(owner_.client_id_, id.c_str());
CopyString(owner_.password_saved_, at_pos + 1);
}
return owner_.client_id_[0] != '\0';
}
void SettingsManager::PersistSelfHostedIdentity(const char *client_id) { void SettingsManager::PersistSelfHostedIdentity(const char *client_id) {
if (!client_id) { if (!client_id) {
return; return;
@@ -33,6 +33,10 @@ public:
// Loads the cached self-hosted identity into the owner's active connection // Loads the cached self-hosted identity into the owner's active connection
// fields. Returns true only when a non-empty identity was restored. // fields. Returns true only when a non-empty identity was restored.
bool LoadCachedSelfHostedIdentity(); bool LoadCachedSelfHostedIdentity();
// Restores the public-server identity kept in the in-memory cache fields.
// This must run when switching away from a self-hosted server so signal
// callbacks are matched against the identity used by the replacement peer.
bool ActivateCachedPublicIdentity();
void PersistSelfHostedIdentity(const char *client_id); void PersistSelfHostedIdentity(const char *client_id);
private: private:
+11 -6
View File
@@ -57,6 +57,7 @@ int GuiRuntime::CreateConnectionPeer() {
signal_server_ip = config_center_->GetDefaultServerHost(); signal_server_ip = config_center_->GetDefaultServerHost();
signal_server_port = config_center_->GetDefaultSignalServerPort(); signal_server_port = config_center_->GetDefaultSignalServerPort();
coturn_server_port = config_center_->GetDefaultCoturnServerPort(); coturn_server_port = config_center_->GetDefaultCoturnServerPort();
settings_.ActivateCachedPublicIdentity();
params_.user_id = client_id_with_password_; params_.user_id = client_id_with_password_;
} }
@@ -94,12 +95,11 @@ int GuiRuntime::CreateConnectionPeer() {
sizeof(params_.turn_server_ip) - 1); sizeof(params_.turn_server_ip) - 1);
params_.turn_server_ip[sizeof(params_.turn_server_ip) - 1] = '\0'; params_.turn_server_ip[sizeof(params_.turn_server_ip) - 1] = '\0';
params_.turn_server_port = coturn_server_port; params_.turn_server_port = coturn_server_port;
strncpy((char *)params_.turn_server_username, "crossdesk", // TURN credentials are issued by the signaling server after login. Keep the
sizeof(params_.turn_server_username) - 1); // initial values empty so a reusable static password is never embedded in
params_.turn_server_username[sizeof(params_.turn_server_username) - 1] = '\0'; // the client binary.
strncpy((char *)params_.turn_server_password, "crossdeskpw", params_.turn_server_username[0] = '\0';
sizeof(params_.turn_server_password) - 1); params_.turn_server_password[0] = '\0';
params_.turn_server_password[sizeof(params_.turn_server_password) - 1] = '\0';
strncpy(params_.log_path, dll_log_path_.c_str(), strncpy(params_.log_path, dll_log_path_.c_str(),
sizeof(params_.log_path) - 1); sizeof(params_.log_path) - 1);
@@ -126,6 +126,11 @@ int GuiRuntime::CreateConnectionPeer() {
params_.user_data = &peer_events_; params_.user_data = &peer_events_;
// The previous peer may have left a terminal status behind. Reset it before
// Init() starts emitting callbacks for the newly selected server.
signal_connected_ = false;
signal_status_ = SignalStatus::SignalConnecting;
peer_ = CreatePeer(&params_); peer_ = CreatePeer(&params_);
if (peer_) { if (peer_) {
LOG_INFO("Create peer instance [{}] successful", client_id_); LOG_INFO("Create peer instance [{}] successful", client_id_);
+32
View File
@@ -85,6 +85,38 @@ void PeerEventHandler::OnSignalMessage(const char* message, size_t size,
} }
} }
} }
} 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;
} }
} }
+13
View File
@@ -152,6 +152,18 @@ struct UserSettingsState {
bool show_file_browser_ = true; bool show_file_browser_ = true;
}; };
struct PasswordChangeState {
std::mutex password_change_mutex_;
uint64_t next_password_change_request_id_ = 0;
bool password_change_pending_ = false;
bool password_change_result_ready_ = false;
bool password_change_succeeded_ = false;
std::chrono::steady_clock::time_point password_change_requested_at_;
std::string pending_password_change_request_id_;
std::string pending_local_password_;
std::string password_change_error_;
};
struct ConnectionState { struct ConnectionState {
using RemoteSessionMap = using RemoteSessionMap =
std::unordered_map<std::string, RemoteSessionPtr>; std::unordered_map<std::string, RemoteSessionPtr>;
@@ -180,6 +192,7 @@ struct RuntimeState : InfrastructureState,
PeerState, PeerState,
PlatformIntegrationState, PlatformIntegrationState,
UserSettingsState, UserSettingsState,
PasswordChangeState,
ConnectionState {}; ConnectionState {};
} // namespace crossdesk::gui_detail } // namespace crossdesk::gui_detail
@@ -335,7 +335,7 @@ int ScreenCapturerSckImpl::SwitchTo(int monitor_index) {
} }
int ScreenCapturerSckImpl::ResetToInitialMonitor() { int ScreenCapturerSckImpl::ResetToInitialMonitor() {
int target = initial_monitor_index_; const int target = initial_monitor_index_;
if (display_info_list_.empty()) return -1; if (display_info_list_.empty()) return -1;
auto display_it = display_id_map_.find(target); auto display_it = display_id_map_.find(target);
if (display_it == display_id_map_.end()) { if (display_it == display_id_map_.end()) {
@@ -343,13 +343,20 @@ int ScreenCapturerSckImpl::ResetToInitialMonitor() {
return -1; return -1;
} }
CGDirectDisplayID target_display = display_it->second; const CGDirectDisplayID target_display = display_it->second;
if (current_display_ == target_display) return 0; bool should_reconfigure = false;
{ {
std::lock_guard<std::mutex> lock(lock_); std::lock_guard<std::mutex> lock(lock_);
if (current_display_ == target_display) return 0;
current_display_ = target_display; current_display_ = target_display;
should_reconfigure = stream_ != nil;
} }
// Resetting session state must not create a capture stream. Preserve the
// selected monitor for the next Start(), and only reconfigure an active one.
if (should_reconfigure) {
StartOrReconfigureCapturer(); StartOrReconfigureCapturer();
}
return 0; return 0;
} }
+2
View File
@@ -80,7 +80,9 @@ function setup_platform_settings()
add_cxflags("-Wno-unused-variable") add_cxflags("-Wno-unused-variable")
elseif is_os("macosx") then elseif is_os("macosx") then
if is_arch("x86_64") then
add_ldflags("-Wl,-ld_classic") add_ldflags("-Wl,-ld_classic")
end
add_cxflags("-Wno-unused-variable") add_cxflags("-Wno-unused-variable")
add_frameworks("Cocoa", "OpenGL", "IOSurface", "ScreenCaptureKit", add_frameworks("Cocoa", "OpenGL", "IOSurface", "ScreenCaptureKit",
"AVFoundation", "CoreMedia", "CoreVideo", "CoreAudio", "AVFoundation", "CoreMedia", "CoreVideo", "CoreAudio",