fix: close server window after controller disconnects

This commit is contained in:
dijunkun
2026-07-27 00:22:33 +08:00
parent 7cd0d6a351
commit 765452e3c9
7 changed files with 374 additions and 343 deletions
+6 -2
View File
@@ -1,6 +1,7 @@
#ifndef CROSSDESK_GUI_APPLICATION_STATE_H_ #ifndef CROSSDESK_GUI_APPLICATION_STATE_H_
#define CROSSDESK_GUI_APPLICATION_STATE_H_ #define CROSSDESK_GUI_APPLICATION_STATE_H_
#include <atomic>
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
@@ -119,8 +120,11 @@ struct StreamWindowState {
}; };
struct ServerWindowState { struct ServerWindowState {
bool need_to_create_server_window_ = false; // Transport callbacks run outside the UI thread. Keep the two lifecycle
bool need_to_destroy_server_window_ = false; // requests atomic so a disconnect cannot be lost while the Slint timer is
// deciding whether to create or destroy the controlled-side window.
std::atomic<bool> need_to_create_server_window_{false};
std::atomic<bool> need_to_destroy_server_window_{false};
bool server_window_created_ = false; bool server_window_created_ = false;
bool server_window_inited_ = false; bool server_window_inited_ = false;
int server_window_width_default_ = 250; int server_window_width_default_ = 250;
+52 -47
View File
@@ -56,7 +56,7 @@ bool HasNonEmptyEnvironmentVariable(const char* name) {
} }
class ScopedUnsetEnvironmentVariable { class ScopedUnsetEnvironmentVariable {
public: public:
explicit ScopedUnsetEnvironmentVariable(const char* name) : name_(name) { explicit ScopedUnsetEnvironmentVariable(const char* name) : name_(name) {
if (const char* value = std::getenv(name_)) { if (const char* value = std::getenv(name_)) {
original_value_ = value; original_value_ = value;
@@ -77,7 +77,7 @@ public:
ScopedUnsetEnvironmentVariable& operator=( ScopedUnsetEnvironmentVariable& operator=(
const ScopedUnsetEnvironmentVariable&) = delete; const ScopedUnsetEnvironmentVariable&) = delete;
private: private:
const char* name_; const char* name_;
std::optional<std::string> original_value_; std::optional<std::string> original_value_;
}; };
@@ -101,8 +101,7 @@ private:
&bytes_after, &data) == Success && &bytes_after, &data) == Success &&
actual_type == XA_WINDOW && actual_format == 32 && item_count == 1 && actual_type == XA_WINDOW && actual_format == 32 && item_count == 1 &&
data) { data) {
const ::Window active_window = const ::Window active_window = *reinterpret_cast<const ::Window*>(data);
*reinterpret_cast<const ::Window*>(data);
XFree(data); XFree(data);
return active_window; return active_window;
} }
@@ -165,9 +164,8 @@ bool X11WindowMatches(Display* display, ::Window x11_window,
int root_x = 0; int root_x = 0;
int root_y = 0; int root_y = 0;
::Window child = 0; ::Window child = 0;
if (!XTranslateCoordinates(display, x11_window, if (!XTranslateCoordinates(display, x11_window, DefaultRootWindow(display), 0,
DefaultRootWindow(display), 0, 0, &root_x, 0, &root_x, &root_y, &child)) {
&root_y, &child)) {
return false; return false;
} }
@@ -269,17 +267,15 @@ std::vector<ParsedReleaseNoteBlock> ParseReleaseNotesMarkdownForSlint(
size_t line_start = 0; size_t line_start = 0;
while (line_start < markdown.size()) { while (line_start < markdown.size()) {
const size_t newline = markdown.find('\n', line_start); const size_t newline = markdown.find('\n', line_start);
const size_t line_end = newline == std::string_view::npos const size_t line_end =
? markdown.size() newline == std::string_view::npos ? markdown.size() : newline;
: newline;
std::string_view line = markdown.substr(line_start, line_end - line_start); std::string_view line = markdown.substr(line_start, line_end - line_start);
if (!line.empty() && line.back() == '\r') { if (!line.empty() && line.back() == '\r') {
line.remove_suffix(1); line.remove_suffix(1);
} }
if (std::all_of(line.begin(), line.end(), [](unsigned char ch) { if (std::all_of(line.begin(), line.end(),
return std::isspace(ch) != 0; [](unsigned char ch) { return std::isspace(ch) != 0; })) {
})) {
section_gap = !blocks.empty(); section_gap = !blocks.empty();
if (newline == std::string_view::npos) { if (newline == std::string_view::npos) {
break; break;
@@ -306,11 +302,11 @@ std::vector<ParsedReleaseNoteBlock> ParseReleaseNotesMarkdownForSlint(
const std::string block_markdown(line); const std::string block_markdown(line);
ParsedReleaseNoteBlock block; ParsedReleaseNoteBlock block;
if (const auto parsed = if (const auto parsed = slint::StyledText::from_markdown(block_markdown)) {
slint::StyledText::from_markdown(block_markdown)) {
block.content = *parsed; block.content = *parsed;
} else { } else {
LOG_WARN("Failed to parse a release-note Markdown block; using plain text"); LOG_WARN(
"Failed to parse a release-note Markdown block; using plain text");
block.content = slint::StyledText::from_plain_text(block_markdown); block.content = slint::StyledText::from_plain_text(block_markdown);
} }
block.section_gap = section_gap; block.section_gap = section_gap;
@@ -958,13 +954,14 @@ bool GuiApplication::InitializeSDL() {
const bool video_driver_allows_x11 = const bool video_driver_allows_x11 =
requested_video_driver == nullptr || requested_video_driver[0] == '\0' || requested_video_driver == nullptr || requested_video_driver[0] == '\0' ||
std::strcmp(requested_video_driver, "x11") == 0; std::strcmp(requested_video_driver, "x11") == 0;
use_xwayland_gui_ = use_xwayland_gui_ = IsWaylandSession() &&
IsWaylandSession() && HasNonEmptyEnvironmentVariable("DISPLAY") && HasNonEmptyEnvironmentVariable("DISPLAY") &&
video_driver_allows_x11; video_driver_allows_x11;
if (use_xwayland_gui_) { if (use_xwayland_gui_) {
setenv("SDL_VIDEODRIVER", "x11", 1); setenv("SDL_VIDEODRIVER", "x11", 1);
LOG_INFO("Wayland session detected; using XWayland for positioned GUI " LOG_INFO(
"windows while retaining Wayland portal capture and input"); "Wayland session detected; using XWayland for positioned GUI "
"windows while retaining Wayland portal capture and input");
} }
#endif #endif
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) { if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) {
@@ -1011,8 +1008,7 @@ void GuiApplication::InitializeUi() {
} }
#endif #endif
std::vector<ui::ReleaseNoteBlock> release_note_blocks; std::vector<ui::ReleaseNoteBlock> release_note_blocks;
for (const auto& parsed : for (const auto& parsed : ParseReleaseNotesMarkdownForSlint(release_notes_)) {
ParseReleaseNotesMarkdownForSlint(release_notes_)) {
ui::ReleaseNoteBlock block; ui::ReleaseNoteBlock block;
block.content = parsed.content; block.content = parsed.content;
block.section_gap = parsed.section_gap; block.section_gap = parsed.section_gap;
@@ -1159,10 +1155,10 @@ void GuiApplication::InitializeUi() {
#endif #endif
BindStreamCallbacks(); BindStreamCallbacks();
(*ui_->stream)->show(); (*ui_->stream)->show();
PositionWindowAtCenter((*ui_->stream)->window(), ui_->main->window(), PositionWindowAtCenter(
kStreamWindowLogicalWidth, (*ui_->stream)->window(), ui_->main->window(),
StreamWindowLogicalHeight( kStreamWindowLogicalWidth,
(*ui_->stream)->get_custom_titlebar())); StreamWindowLogicalHeight((*ui_->stream)->get_custom_titlebar()));
ui_->main->hide(); ui_->main->hide();
} else if (ui_->capture_mode && ui_->capture_page == "server") { } else if (ui_->capture_mode && ui_->capture_page == "server") {
ui_->server.emplace(ui::ServerWindow::create()); ui_->server.emplace(ui::ServerWindow::create());
@@ -2235,8 +2231,7 @@ void GuiApplication::SyncXWaylandWindowActivation() {
return; return;
} }
if (const auto process_id = if (const auto process_id = X11GetWindowProcessId(display, active_window);
X11GetWindowProcessId(display, active_window);
process_id && *process_id != static_cast<unsigned long>(getpid())) { process_id && *process_id != static_cast<unsigned long>(getpid())) {
ui_->main->set_window_active(false); ui_->main->set_window_active(false);
if (ui_->stream) { if (ui_->stream) {
@@ -2249,8 +2244,8 @@ void GuiApplication::SyncXWaylandWindowActivation() {
X11WindowMatches(display, active_window, ui_->main->window())); X11WindowMatches(display, active_window, ui_->main->window()));
if (ui_->stream) { if (ui_->stream) {
(*ui_->stream) (*ui_->stream)
->set_window_active(X11WindowMatches( ->set_window_active(
display, active_window, (*ui_->stream)->window())); X11WindowMatches(display, active_window, (*ui_->stream)->window()));
} }
} }
#endif #endif
@@ -2289,8 +2284,7 @@ void GuiApplication::SyncStreamWindow() {
if (!ui_->stream) { if (!ui_->stream) {
return; return;
} }
(*ui_->stream) (*ui_->stream)->set_window_maximized((*ui_->stream)->window().is_maximized());
->set_window_maximized((*ui_->stream)->window().is_maximized());
(*ui_->stream)->set_srtp_enabled(enable_srtp_); (*ui_->stream)->set_srtp_enabled(enable_srtp_);
#if defined(__APPLE__) #if defined(__APPLE__)
if (ui_->stream_live_resize_configuration_attempts > 0) { if (ui_->stream_live_resize_configuration_attempts > 0) {
@@ -2302,10 +2296,10 @@ void GuiApplication::SyncStreamWindow() {
} }
#endif #endif
if (ui_->stream_initial_position_attempts > 0) { if (ui_->stream_initial_position_attempts > 0) {
if (PositionWindowAtCenter((*ui_->stream)->window(), ui_->main->window(), if (PositionWindowAtCenter(
kStreamWindowLogicalWidth, (*ui_->stream)->window(), ui_->main->window(),
StreamWindowLogicalHeight( kStreamWindowLogicalWidth,
(*ui_->stream)->get_custom_titlebar()))) { StreamWindowLogicalHeight((*ui_->stream)->get_custom_titlebar()))) {
--ui_->stream_initial_position_attempts; --ui_->stream_initial_position_attempts;
} else { } else {
ui_->stream_initial_position_attempts = 0; ui_->stream_initial_position_attempts = 0;
@@ -2539,7 +2533,22 @@ void GuiApplication::SyncStreamWindow() {
} }
void GuiApplication::SyncServerWindow() { void GuiApplication::SyncServerWindow() {
if (need_to_create_server_window_ && !ui_->server) { // The connection map is authoritative. Lifecycle flags record callback
// intent, but callbacks for different controllers can cross each other, so
// the final decision must reflect the current connected-controller set.
need_to_create_server_window_.exchange(false, std::memory_order_acq_rel);
need_to_destroy_server_window_.exchange(false, std::memory_order_acq_rel);
bool has_connected_controller = false;
{
std::shared_lock lock(connection_status_mutex_);
has_connected_controller =
std::any_of(connection_status_.begin(), connection_status_.end(),
[](const auto& entry) {
return entry.second == ConnectionStatus::Connected;
});
}
if (has_connected_controller && !ui_->server) {
ui_->server.emplace(ui::ServerWindow::create()); ui_->server.emplace(ui::ServerWindow::create());
RegisterFontAwesome((*ui_->server)->window()); RegisterFontAwesome((*ui_->server)->window());
(*ui_->server)->set_controllers(ui_->controller_model); (*ui_->server)->set_controllers(ui_->controller_model);
@@ -2559,15 +2568,13 @@ void GuiApplication::SyncServerWindow() {
ui_->server_initial_position_attempts = 2; ui_->server_initial_position_attempts = 2;
server_window_created_ = true; server_window_created_ = true;
server_window_inited_ = true; server_window_inited_ = true;
need_to_create_server_window_ = false;
} }
if (need_to_destroy_server_window_ && ui_->server) { if (!has_connected_controller && ui_->server) {
(*ui_->server)->hide(); (*ui_->server)->hide();
ui_->server.reset(); ui_->server.reset();
server_window_created_ = false; server_window_created_ = false;
server_window_inited_ = false; server_window_inited_ = false;
ui_->server_initial_position_attempts = 0; ui_->server_initial_position_attempts = 0;
need_to_destroy_server_window_ = false;
} }
if (!ui_->server) { if (!ui_->server) {
return; return;
@@ -2868,9 +2875,8 @@ void GuiApplication::SendPointerInput(int button, int kind, float x, float y) {
: 0.0f; : 0.0f;
const float available_height = const float available_height =
size.height / scale - titlebar_height - size.height / scale - titlebar_height -
(fullscreen_button_pressed_ (fullscreen_button_pressed_ ? 0.0f
? 0.0f : (ui_->tab_ids.size() > 1 ? 30.0f : 0.0f));
: (ui_->tab_ids.size() > 1 ? 30.0f : 0.0f));
float render_width = available_width; float render_width = available_width;
float render_height = available_height; float render_height = available_height;
float offset_x = 0; float offset_x = 0;
@@ -2944,9 +2950,8 @@ void GuiApplication::SendScrollInput(float delta_x, float delta_y, float x,
: 0.0f; : 0.0f;
const float height = const float height =
size.height / scale - titlebar_height - size.height / scale - titlebar_height -
(fullscreen_button_pressed_ (fullscreen_button_pressed_ ? 0.0f
? 0.0f : (ui_->tab_ids.size() > 1 ? 30.0f : 0.0f));
: (ui_->tab_ids.size() > 1 ? 30.0f : 0.0f));
if (width <= 0 || height <= 0) { if (width <= 0 || height <= 0) {
return; return;
} }
+75 -23
View File
@@ -1,5 +1,3 @@
#include "runtime/gui_runtime.h"
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <cstring> #include <cstring>
@@ -12,6 +10,7 @@
#include "localization.h" #include "localization.h"
#include "rd_log.h" #include "rd_log.h"
#include "runtime/gui_runtime.h"
namespace crossdesk { namespace crossdesk {
@@ -22,7 +21,7 @@ bool IsConnectionAttemptPending(ConnectionStatus status) {
return status == ConnectionStatus::Connecting || return status == ConnectionStatus::Connecting ||
status == ConnectionStatus::Gathering; status == ConnectionStatus::Gathering;
} }
} // namespace } // namespace
void GuiRuntime::HandleConnectionStatusChange() { void GuiRuntime::HandleConnectionStatusChange() {
if (signal_connected_ && peer_ && need_to_send_recent_connections_) { if (signal_connected_ && peer_ && need_to_send_recent_connections_) {
@@ -31,7 +30,7 @@ void GuiRuntime::HandleConnectionStatusChange() {
j["type"] = "recent_connections_presence"; j["type"] = "recent_connections_presence";
j["user_id"] = client_id_; j["user_id"] = client_id_;
j["devices"] = nlohmann::json::array(); j["devices"] = nlohmann::json::array();
for (const auto &id : recent_connection_ids_) { for (const auto& id : recent_connection_ids_) {
std::string pure_id = id; std::string pure_id = id;
size_t pos_y = pure_id.find('Y'); size_t pos_y = pure_id.find('Y');
size_t pos_n = pure_id.find('N'); size_t pos_n = pure_id.find('N');
@@ -123,7 +122,7 @@ void GuiRuntime::HandleConnectionTimeouts() {
} }
bool rejoin_state_changed = false; bool rejoin_state_changed = false;
for (auto &[_, props] : remote_sessions_) { for (auto& [_, props] : remote_sessions_) {
if (!props || !props->connection_attempt_active_.load()) { if (!props || !props->connection_attempt_active_.load()) {
continue; continue;
} }
@@ -152,7 +151,7 @@ void GuiRuntime::HandleConnectionTimeouts() {
if (rejoin_state_changed) { if (rejoin_state_changed) {
need_to_rejoin_ = false; need_to_rejoin_ = false;
for (const auto &[_, props] : remote_sessions_) { for (const auto& [_, props] : remote_sessions_) {
if (props && props->rejoin_) { if (props && props->rejoin_) {
need_to_rejoin_ = true; need_to_rejoin_ = true;
break; break;
@@ -161,8 +160,65 @@ void GuiRuntime::HandleConnectionTimeouts() {
} }
} }
int GuiRuntime::RequestSingleDevicePresence(const std::string &remote_id, void GuiRuntime::HandleServerControllerDisconnected(
const char *password, const std::string& remote_id, const char* reason) {
keyboard_.ReleaseRemotePressedKeys(remote_id, reason);
bool has_connected_controller = false;
bool has_web_controller = false;
std::string remaining_controller_id;
{
std::unique_lock lock(connection_status_mutex_);
connection_status_.erase(remote_id);
connection_host_names_.erase(remote_id);
for (const auto& [id, status] : connection_status_) {
if (status != ConnectionStatus::Connected) {
continue;
}
has_connected_controller = true;
has_web_controller =
has_web_controller || id.find("web") != std::string::npos;
if (remaining_controller_id.empty()) {
remaining_controller_id = id;
}
}
}
show_cursor_ = has_web_controller;
if (has_connected_controller) {
remote_client_id_ = remaining_controller_id;
return;
}
need_to_create_server_window_.store(false, std::memory_order_release);
need_to_destroy_server_window_.store(true, std::memory_order_release);
is_server_mode_ = false;
#if defined(__linux__) && !defined(__APPLE__)
if (IsWaylandSession()) {
// Keep Wayland capture session warm to avoid black screen on subsequent
// reconnects.
start_screen_capturer_ = true;
LOG_INFO(
"Keeping Wayland screen capturer running after disconnect to "
"preserve reconnect stability");
} else {
start_screen_capturer_ = false;
}
#else
start_screen_capturer_ = false;
#endif
start_speaker_capturer_ = false;
start_mouse_controller_ = false;
start_keyboard_capturer_ = false;
remote_client_id_.clear();
if (audio_capture_) {
devices_.StopSpeakerCapturer();
audio_capture_ = false;
}
devices_.ResetToInitialDisplay();
}
int GuiRuntime::RequestSingleDevicePresence(const std::string& remote_id,
const char* password,
bool remember_password) { bool remember_password) {
if (!signal_connected_ || !peer_) { if (!signal_connected_ || !peer_) {
return -1; return -1;
@@ -221,7 +277,7 @@ void GuiRuntime::CloseRemoteSession(std::shared_ptr<RemoteSession> props) {
std::thread save_thread([buffer_copy, video_width, video_height, remote_id, std::thread save_thread([buffer_copy, video_width, video_height, remote_id,
remote_host_name, password, remote_host_name, password,
thumbnail = thumbnail_]() { thumbnail = thumbnail_]() {
thumbnail->SaveToThumbnail((char *)buffer_copy.data(), video_width, thumbnail->SaveToThumbnail((char*)buffer_copy.data(), video_width,
video_height, remote_id, remote_host_name, video_height, remote_id, remote_host_name,
password); password);
}); });
@@ -255,7 +311,7 @@ void GuiRuntime::CloseAllRemoteSessions() {
{ {
// std::shared_lock lock(remote_sessions_mutex_); // std::shared_lock lock(remote_sessions_mutex_);
for (auto &it : remote_sessions_) { for (auto& it : remote_sessions_) {
auto props = it.second; auto props = it.second;
CloseRemoteSession(props); CloseRemoteSession(props);
} }
@@ -279,7 +335,7 @@ void GuiRuntime::WaitForThumbnailSaveTasks() {
return; return;
} }
for (auto &thread : threads_to_join) { for (auto& thread : threads_to_join) {
if (thread.joinable()) { if (thread.joinable()) {
thread.join(); thread.join();
} }
@@ -300,9 +356,8 @@ void GuiRuntime::ResetRemoteSessionResources(
} }
} }
std::shared_ptr<GuiRuntime::RemoteSession> std::shared_ptr<GuiRuntime::RemoteSession> GuiRuntime::FindRemoteSession(
GuiRuntime::FindRemoteSession( const std::string& remote_id) {
const std::string &remote_id) {
if (remote_id.empty()) { if (remote_id.empty()) {
return nullptr; return nullptr;
} }
@@ -315,8 +370,7 @@ GuiRuntime::FindRemoteSession(
return it->second; return it->second;
} }
int GuiRuntime::ConnectTo(const std::string& remote_id, const char* password,
int GuiRuntime::ConnectTo(const std::string &remote_id, const char *password,
bool remember_password, bool bypass_presence_check) { bool remember_password, bool bypass_presence_check) {
if (!bypass_presence_check && !device_presence_cache_.IsOnline(remote_id)) { if (!bypass_presence_check && !device_presence_cache_.IsOnline(remote_id)) {
int ret = int ret =
@@ -336,19 +390,17 @@ int GuiRuntime::ConnectTo(const std::string &remote_id, const char *password,
focused_remote_id_ = remote_id; focused_remote_id_ = remote_id;
// std::shared_lock shared_lock(remote_sessions_mutex_); // std::shared_lock shared_lock(remote_sessions_mutex_);
bool exists = bool exists = (remote_sessions_.find(remote_id) != remote_sessions_.end());
(remote_sessions_.find(remote_id) != remote_sessions_.end());
// shared_lock.unlock(); // shared_lock.unlock();
if (!exists) { if (!exists) {
PeerPtr *peer_to_init = nullptr; PeerPtr* peer_to_init = nullptr;
std::string local_id; std::string local_id;
{ {
// std::unique_lock unique_lock(remote_sessions_mutex_); // std::unique_lock unique_lock(remote_sessions_mutex_);
if (remote_sessions_.find(remote_id) == remote_sessions_.end()) { if (remote_sessions_.find(remote_id) == remote_sessions_.end()) {
remote_sessions_[remote_id] = remote_sessions_[remote_id] = std::make_shared<RemoteSession>();
std::make_shared<RemoteSession>();
auto props = remote_sessions_[remote_id]; auto props = remote_sessions_[remote_id];
props->local_id_ = "C-" + std::string(client_id_); props->local_id_ = "C-" + std::string(client_id_);
props->remote_id_ = remote_id; props->remote_id_ = remote_id;
@@ -371,7 +423,7 @@ int GuiRuntime::ConnectTo(const std::string &remote_id, const char *password,
return -1; return -1;
} }
for (const auto &display_info : devices_.display_info_list()) { for (const auto& display_info : devices_.display_info_list()) {
AddVideoStream(props->peer_, display_info.name.c_str()); AddVideoStream(props->peer_, display_info.name.c_str());
} }
AddAudioStream(props->peer_, props->audio_label_.c_str()); AddAudioStream(props->peer_, props->audio_label_.c_str());
@@ -429,4 +481,4 @@ int GuiRuntime::ConnectTo(const std::string &remote_id, const char *password,
return 0; return 0;
} }
} // namespace crossdesk } // namespace crossdesk
+19 -19
View File
@@ -8,16 +8,16 @@
#include "features/devices/session_device_manager.h" #include "features/devices/session_device_manager.h"
#include "features/file_transfer/file_transfer_manager.h" #include "features/file_transfer/file_transfer_manager.h"
#include "features/input/keyboard_controller.h" #include "features/input/keyboard_controller.h"
#include "features/settings/settings_manager.h"
#include "runtime/gui_state.h" #include "runtime/gui_state.h"
#include "runtime/peer_event_handler.h" #include "runtime/peer_event_handler.h"
#include "features/settings/settings_manager.h"
namespace crossdesk { namespace crossdesk {
// Shared GUI runtime. It owns subsystem controllers and cross-cutting session // Shared GUI runtime. It owns subsystem controllers and cross-cutting session
// state, but no window lifecycle, ImGui view, or transport callback methods. // state, but no window lifecycle, ImGui view, or transport callback methods.
class GuiRuntime : protected gui_detail::GuiState { class GuiRuntime : protected gui_detail::GuiState {
protected: protected:
using FileTransferState = gui_detail::FileTransferState; using FileTransferState = gui_detail::FileTransferState;
using RemoteSession = gui_detail::RemoteSession; using RemoteSession = gui_detail::RemoteSession;
@@ -32,35 +32,35 @@ protected:
GuiRuntime(); GuiRuntime();
~GuiRuntime(); ~GuiRuntime();
static void SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len); static void SdlCaptureAudioIn(void* userdata, Uint8* stream, int len);
static void SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len); static void SdlCaptureAudioOut(void* userdata, Uint8* stream, int len);
int CreateConnectionPeer(); int CreateConnectionPeer();
int ConnectTo(const std::string &remote_id, const char *password, int ConnectTo(const std::string& remote_id, const char* password,
bool remember_password, bool bypass_presence_check = false); bool remember_password, bool bypass_presence_check = false);
int RequestSingleDevicePresence(const std::string &remote_id, int RequestSingleDevicePresence(const std::string& remote_id,
const char *password, bool remember_password); const char* password, bool remember_password);
void UpdateLabels(); void UpdateLabels();
void HandleRecentConnections(); void HandleRecentConnections();
void HandleConnectionStatusChange(); void HandleConnectionStatusChange();
void HandlePendingPresenceProbe(); void HandlePendingPresenceProbe();
void HandleConnectionTimeouts(); void HandleConnectionTimeouts();
void HandleServerControllerDisconnected(const std::string& remote_id,
const char* reason);
void HandleWindowsServiceIntegration(); void HandleWindowsServiceIntegration();
void CloseRemoteSession(std::shared_ptr<RemoteSession> props); void CloseRemoteSession(std::shared_ptr<RemoteSession> props);
void CloseAllRemoteSessions(); void CloseAllRemoteSessions();
void ResetRemoteSessionResources( void ResetRemoteSessionResources(std::shared_ptr<RemoteSession> props);
std::shared_ptr<RemoteSession> props);
void WaitForThumbnailSaveTasks(); void WaitForThumbnailSaveTasks();
std::shared_ptr<RemoteSession> std::shared_ptr<RemoteSession> FindRemoteSession(
FindRemoteSession(const std::string &remote_id); const std::string& remote_id);
void ResetRemoteServiceStatus(RemoteSession &props); void ResetRemoteServiceStatus(RemoteSession& props);
void ApplyRemoteServiceStatus(RemoteSession &props, void ApplyRemoteServiceStatus(RemoteSession& props,
const ServiceStatus &status); const ServiceStatus& status);
RemoteUnlockState RemoteUnlockState GetRemoteUnlockState(const RemoteSession& props) const;
GetRemoteUnlockState(const RemoteSession &props) const;
#if _WIN32 #if _WIN32
void ResetLocalWindowsServiceState(bool clear_pending_sas); void ResetLocalWindowsServiceState(bool clear_pending_sas);
#endif #endif
@@ -82,7 +82,7 @@ protected:
KeyboardController keyboard_; KeyboardController keyboard_;
PeerEventHandler peer_events_; PeerEventHandler peer_events_;
private: private:
friend class ClipboardController; friend class ClipboardController;
friend class SessionDeviceManager; friend class SessionDeviceManager;
friend class FileTransferManager; friend class FileTransferManager;
@@ -91,6 +91,6 @@ private:
friend class PeerEventHandler; friend class PeerEventHandler;
}; };
} // namespace crossdesk } // namespace crossdesk
#endif // CROSSDESK_GUI_RUNTIME_H_ #endif // CROSSDESK_GUI_RUNTIME_H_
+199 -246
View File
@@ -26,12 +26,12 @@
namespace crossdesk { namespace crossdesk {
PeerEventHandler::PeerEventHandler(GuiRuntime &owner) : owner_(owner) {} PeerEventHandler::PeerEventHandler(GuiRuntime& owner) : owner_(owner) {}
void PeerEventHandler::OnSignalMessage(const char *message, size_t size, void PeerEventHandler::OnSignalMessage(const char* message, size_t size,
void *user_data) { void* user_data) {
auto *handler = static_cast<PeerEventHandler *>(user_data); auto* handler = static_cast<PeerEventHandler*>(user_data);
GuiRuntime *runtime = handler ? &handler->owner_ : nullptr; GuiRuntime* runtime = handler ? &handler->owner_ : nullptr;
if (!runtime || !message || size == 0) { if (!runtime || !message || size == 0) {
return; return;
} }
@@ -43,7 +43,7 @@ void PeerEventHandler::OnSignalMessage(const char *message, size_t size,
std::string type = j["type"].get<std::string>(); std::string type = j["type"].get<std::string>();
if (type == "presence") { if (type == "presence") {
if (j.contains("devices") && j["devices"].is_array()) { if (j.contains("devices") && j["devices"].is_array()) {
for (auto &dev : j["devices"]) { for (auto& dev : j["devices"]) {
if (!dev.is_object()) { if (!dev.is_object()) {
continue; continue;
} }
@@ -88,11 +88,10 @@ void PeerEventHandler::OnSignalMessage(const char *message, size_t size,
} }
} }
void PeerEventHandler::OnSignalStatus(SignalStatus status, const char* user_id,
void PeerEventHandler::OnSignalStatus(SignalStatus status, const char *user_id, size_t user_id_size, void* user_data) {
size_t user_id_size, void *user_data) { auto* handler = static_cast<PeerEventHandler*>(user_data);
auto *handler = static_cast<PeerEventHandler *>(user_data); GuiRuntime* runtime = handler ? &handler->owner_ : nullptr;
GuiRuntime *runtime = handler ? &handler->owner_ : nullptr;
if (!runtime) { if (!runtime) {
return; return;
} }
@@ -150,13 +149,12 @@ void PeerEventHandler::OnSignalStatus(SignalStatus status, const char *user_id,
} }
void PeerEventHandler::OnConnectionStatus(ConnectionStatus status, void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
const char *user_id, const char* user_id,
const size_t user_id_size, const size_t user_id_size,
void *user_data) { void* user_data) {
auto *handler = static_cast<PeerEventHandler *>(user_data); auto* handler = static_cast<PeerEventHandler*>(user_data);
GuiRuntime *runtime = handler ? &handler->owner_ : nullptr; GuiRuntime* runtime = handler ? &handler->owner_ : nullptr;
if (!runtime) if (!runtime) return;
return;
std::string remote_id(user_id, user_id_size); std::string remote_id(user_id, user_id_size);
std::shared_ptr<GuiRuntime::RemoteSession> props; std::shared_ptr<GuiRuntime::RemoteSession> props;
@@ -178,107 +176,110 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
} }
switch (status) { switch (status) {
case ConnectionStatus::Connected: { case ConnectionStatus::Connected: {
runtime->ResetRemoteServiceStatus(*props); runtime->ResetRemoteServiceStatus(*props);
{ {
RemoteAction remote_action; RemoteAction remote_action;
remote_action.i.display_num = remote_action.i.display_num =
runtime->devices_.display_info_list().size(); runtime->devices_.display_info_list().size();
remote_action.i.display_list = remote_action.i.display_list =
(char **)malloc(remote_action.i.display_num * sizeof(char *)); (char**)malloc(remote_action.i.display_num * sizeof(char*));
remote_action.i.left = remote_action.i.left =
(int *)malloc(remote_action.i.display_num * sizeof(int)); (int*)malloc(remote_action.i.display_num * sizeof(int));
remote_action.i.top = remote_action.i.top =
(int *)malloc(remote_action.i.display_num * sizeof(int)); (int*)malloc(remote_action.i.display_num * sizeof(int));
remote_action.i.right = remote_action.i.right =
(int *)malloc(remote_action.i.display_num * sizeof(int)); (int*)malloc(remote_action.i.display_num * sizeof(int));
remote_action.i.bottom = remote_action.i.bottom =
(int *)malloc(remote_action.i.display_num * sizeof(int)); (int*)malloc(remote_action.i.display_num * sizeof(int));
for (int i = 0; i < remote_action.i.display_num; i++) { for (int i = 0; i < remote_action.i.display_num; i++) {
LOG_INFO("Local display [{}:{}]", i + 1, LOG_INFO("Local display [{}:{}]", i + 1,
runtime->devices_.display_info_list()[i].name); runtime->devices_.display_info_list()[i].name);
remote_action.i.display_list[i] = (char *)malloc( remote_action.i.display_list[i] = (char*)malloc(
runtime->devices_.display_info_list()[i].name.length() + 1); runtime->devices_.display_info_list()[i].name.length() + 1);
strncpy(remote_action.i.display_list[i], strncpy(remote_action.i.display_list[i],
runtime->devices_.display_info_list()[i].name.c_str(), runtime->devices_.display_info_list()[i].name.c_str(),
runtime->devices_.display_info_list()[i].name.length()); runtime->devices_.display_info_list()[i].name.length());
remote_action.i.display_list remote_action.i
[i][runtime->devices_.display_info_list()[i].name.length()] = '\0'; .display_list[i][runtime->devices_.display_info_list()[i]
remote_action.i.left[i] = .name.length()] = '\0';
runtime->devices_.display_info_list()[i].left; remote_action.i.left[i] =
remote_action.i.top[i] = runtime->devices_.display_info_list()[i].top; runtime->devices_.display_info_list()[i].left;
remote_action.i.right[i] = remote_action.i.top[i] =
runtime->devices_.display_info_list()[i].right; runtime->devices_.display_info_list()[i].top;
remote_action.i.bottom[i] = remote_action.i.right[i] =
runtime->devices_.display_info_list()[i].bottom; 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);
} }
std::string host_name = GetHostName(); if (!runtime->need_to_create_stream_window_ &&
remote_action.type = ControlType::host_infomation; !runtime->remote_sessions_.empty()) {
memcpy(&remote_action.i.host_name, host_name.data(), host_name.size()); runtime->need_to_create_stream_window_ = true;
remote_action.i.host_name[host_name.size()] = '\0'; }
remote_action.i.host_name_size = host_name.size(); props->connection_established_ = true;
runtime->start_keyboard_capturer_ = true;
std::string msg = remote_action.to_json(); break;
int ret = SendReliableDataFrame(props->peer_, msg.data(), msg.size(),
runtime->control_data_label_.c_str());
remote_action_codec::Free(remote_action);
} }
case ConnectionStatus::Disconnected:
if (!runtime->need_to_create_stream_window_ && case ConnectionStatus::Failed:
!runtime->remote_sessions_.empty()) { case ConnectionStatus::Closed: {
runtime->need_to_create_stream_window_ = true; runtime->keyboard_.ReleaseRemotePressedKeys(remote_id,
} "connection_closed");
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; props->connection_established_ = false;
runtime->connect_button_label_ = props->enable_mouse_control_ = false;
localization::connect[runtime->localization_language_index_]; 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;
} }
break; case ConnectionStatus::IncorrectPassword: {
} runtime->password_validating_ = false;
case ConnectionStatus::NoSuchTransmissionId: runtime->password_validating_time_++;
case ConnectionStatus::RemoteUnavailable: { if (runtime->connect_button_pressed_) {
if (runtime->connect_button_pressed_) { runtime->connect_button_pressed_ = false;
props->connection_established_ = false; props->connection_established_ = false;
runtime->connect_button_label_ = runtime->connect_button_label_ =
localization::connect[runtime->localization_language_index_]; localization::connect[runtime->localization_language_index_];
}
break;
} }
break; case ConnectionStatus::NoSuchTransmissionId:
} case ConnectionStatus::RemoteUnavailable: {
default: if (runtime->connect_button_pressed_) {
break; props->connection_established_ = false;
runtime->connect_button_label_ =
localization::connect[runtime->localization_language_index_];
}
break;
}
default:
break;
} }
} else { } else {
runtime->is_client_mode_ = false; runtime->is_client_mode_ = false;
@@ -289,155 +290,107 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
} }
switch (status) { switch (status) {
case ConnectionStatus::Connected: { case ConnectionStatus::Connected: {
#if _WIN32 #if _WIN32
runtime->last_windows_service_status_tick_ = 0; runtime->last_windows_service_status_tick_ = 0;
#endif #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_create_server_window_ = true;
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->keyboard_.ReleaseRemotePressedKeys(remote_id,
"connection_closed");
bool all_disconnected = false;
{
std::shared_lock lock(runtime->connection_status_mutex_);
all_disconnected =
std::all_of(runtime->connection_status_.begin(),
runtime->connection_status_.end(), [](const auto &kv) {
return kv.second == ConnectionStatus::Closed ||
kv.second == ConnectionStatus::Failed ||
kv.second == ConnectionStatus::Disconnected;
});
}
if (all_disconnected) {
runtime->need_to_destroy_server_window_ = true;
runtime->is_server_mode_ = false;
#if defined(__linux__) && !defined(__APPLE__)
if (IsWaylandSession()) {
// Keep Wayland capture session warm to avoid black screen on
// subsequent reconnects.
runtime->start_screen_capturer_ = true;
LOG_INFO("Keeping Wayland screen capturer running after "
"disconnect to preserve reconnect stability");
} else {
runtime->start_screen_capturer_ = false;
}
#else
runtime->start_screen_capturer_ = false;
#endif
runtime->start_speaker_capturer_ = false;
runtime->start_mouse_controller_ = false;
runtime->start_keyboard_capturer_ = false;
runtime->remote_client_id_ = "";
if (props)
props->connection_established_ = false;
if (runtime->audio_capture_) {
runtime->devices_.StopSpeakerCapturer();
runtime->audio_capture_ = false;
}
{ {
std::unique_lock lock(runtime->connection_status_mutex_); RemoteAction remote_action;
runtime->connection_status_.erase(remote_id); remote_action.i.display_num =
runtime->connection_host_names_.erase(remote_id); runtime->devices_.display_info_list().size();
} remote_action.i.display_list =
runtime->devices_.ResetToInitialDisplay(); (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();
std::shared_lock lock(runtime->connection_status_mutex_); remote_action.type = ControlType::host_infomation;
if (std::all_of(runtime->connection_status_.begin(), memcpy(&remote_action.i.host_name, host_name.data(),
runtime->connection_status_.end(), [](const auto &kv) { host_name.size());
return kv.first.find("web") == std::string::npos; remote_action.i.host_name[host_name.size()] = '\0';
})) { remote_action.i.host_name_size = host_name.size();
runtime->show_cursor_ = false;
}
}
break; std::string msg = remote_action.to_json();
} int ret =
default: SendReliableDataFrame(runtime->peer_, msg.data(), msg.size(),
break; 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( void PeerEventHandler::OnNetStatusReport(
const char *client_id, size_t client_id_size, TraversalMode mode, const char* client_id, size_t client_id_size, TraversalMode mode,
const XNetTrafficStats *net_traffic_stats, const char *user_id, const XNetTrafficStats* net_traffic_stats, const char* user_id,
const size_t user_id_size, void *user_data) { const size_t user_id_size, void* user_data) {
auto *handler = static_cast<PeerEventHandler *>(user_data); auto* handler = static_cast<PeerEventHandler*>(user_data);
GuiRuntime *runtime = handler ? &handler->owner_ : nullptr; GuiRuntime* runtime = handler ? &handler->owner_ : nullptr;
if (!runtime) { if (!runtime) {
return; return;
} }
if (strchr(client_id, '@') != nullptr && strchr(user_id, '-') == nullptr) { if (strchr(client_id, '@') != nullptr && strchr(user_id, '-') == nullptr) {
std::string id, password; std::string id, password;
const char *at_pos = strchr(client_id, '@'); const char* at_pos = strchr(client_id, '@');
if (at_pos == nullptr) { if (at_pos == nullptr) {
id = client_id; id = client_id;
password.clear(); password.clear();
@@ -510,4 +463,4 @@ void PeerEventHandler::OnNetStatusReport(
props->net_traffic_stats_ = *net_traffic_stats; props->net_traffic_stats_ = *net_traffic_stats;
} }
} }
} // namespace crossdesk } // namespace crossdesk
+22 -5
View File
@@ -19,7 +19,7 @@ std::filesystem::path FindRepoRoot() {
return {}; return {};
} }
std::string ReadFile(const std::filesystem::path &path) { std::string ReadFile(const std::filesystem::path& path) {
std::ifstream file(path, std::ios::binary); std::ifstream file(path, std::ios::binary);
if (!file) { if (!file) {
return {}; return {};
@@ -30,8 +30,8 @@ std::string ReadFile(const std::filesystem::path &path) {
return stream.str(); return stream.str();
} }
bool ExpectContains(const char *name, const std::string &value, bool ExpectContains(const char* name, const std::string& value,
const std::string &expected) { const std::string& expected) {
if (value.find(expected) != std::string::npos) { if (value.find(expected) != std::string::npos) {
return true; return true;
} }
@@ -40,8 +40,8 @@ bool ExpectContains(const char *name, const std::string &value,
return false; return false;
} }
bool ExpectNotContains(const char *name, const std::string &value, bool ExpectNotContains(const char* name, const std::string& value,
const std::string &unexpected) { const std::string& unexpected) {
if (value.find(unexpected) == std::string::npos) { if (value.find(unexpected) == std::string::npos) {
return true; return true;
} }
@@ -73,6 +73,10 @@ int main() {
ReadFile(repo_root / "src/gui/runtime/remote_session.h"); ReadFile(repo_root / "src/gui/runtime/remote_session.h");
const std::string connection_runtime_cpp = const std::string connection_runtime_cpp =
ReadFile(repo_root / "src/gui/runtime/connection_runtime.cpp"); ReadFile(repo_root / "src/gui/runtime/connection_runtime.cpp");
const std::string peer_event_handler_cpp =
ReadFile(repo_root / "src/gui/runtime/peer_event_handler.cpp");
const std::string application_state_h =
ReadFile(repo_root / "src/gui/application/application_state.h");
bool ok = true; bool ok = true;
ok &= ExpectContains("minirtc.h", minirtc_api, "RemoteUnavailable"); ok &= ExpectContains("minirtc.h", minirtc_api, "RemoteUnavailable");
@@ -81,6 +85,11 @@ int main() {
"\"Remote unavailable\""); "\"Remote unavailable\"");
ok &= ExpectContains("peer_connection.cpp", peer_connection, ok &= ExpectContains("peer_connection.cpp", peer_connection,
"ConnectionStatus::RemoteUnavailable"); "ConnectionStatus::RemoteUnavailable");
ok &= ExpectContains("peer_connection.cpp", peer_connection,
"IsCurrentPeerConnection");
ok &= ExpectContains(
"peer_connection.cpp", peer_connection,
"on_connection_status_(ConnectionStatus::Closed, remote_user_id.data()");
ok &= ExpectNotContains("peer_connection.cpp", peer_connection, ok &= ExpectNotContains("peer_connection.cpp", peer_connection,
"\"Device offline\""); "\"Device offline\"");
ok &= ExpectNotContains("peer_connection.cpp", peer_connection, ok &= ExpectNotContains("peer_connection.cpp", peer_connection,
@@ -105,6 +114,14 @@ int main() {
"kConnectionAttemptTimeout"); "kConnectionAttemptTimeout");
ok &= ExpectContains("connection_runtime.cpp", connection_runtime_cpp, ok &= ExpectContains("connection_runtime.cpp", connection_runtime_cpp,
"connection_attempt_started_at_"); "connection_attempt_started_at_");
ok &= ExpectContains("connection_runtime.cpp", connection_runtime_cpp,
"HandleServerControllerDisconnected");
ok &= ExpectContains("peer_event_handler.cpp", peer_event_handler_cpp,
"HandleServerControllerDisconnected(remote_id");
ok &= ExpectNotContains("connection_runtime.cpp", connection_runtime_cpp,
"ControllerHeartbeat");
ok &= ExpectContains("application_state.h", application_state_h,
"std::atomic<bool> need_to_destroy_server_window_");
return ok ? 0 : 1; return ok ? 0 : 1;
} }