refactor: rebuild desktop client with Slint

This commit is contained in:
dijunkun
2026-07-21 16:38:51 +08:00
parent eeb6a2a1ae
commit 59f77c2820
79 changed files with 7373 additions and 1009 deletions
+12 -4
View File
@@ -63,8 +63,10 @@ int main() {
ReadFile(repo_root / "submodules/minirtc/src/api/minirtc.h");
const std::string peer_connection =
ReadFile(repo_root / "submodules/minirtc/src/pc/peer_connection.cpp");
const std::string connection_status_window = ReadFile(
repo_root / "src/gui/views/windows/connection_status_window.cpp");
const std::string main_window =
ReadFile(repo_root / "src/gui/ui/main_window.slint");
const std::string gui_application =
ReadFile(repo_root / "src/gui/application/gui_application.cpp");
const std::string runtime_state_h =
ReadFile(repo_root / "src/gui/runtime/runtime_state.h");
const std::string remote_session_h =
@@ -83,8 +85,14 @@ int main() {
"\"Device offline\"");
ok &= ExpectNotContains("peer_connection.cpp", peer_connection,
"ConnectionStatus::DeviceOffline");
ok &= ExpectContains("connection_status_window.cpp", connection_status_window,
"localization::device_offline");
ok &= ExpectContains("main_window.slint", main_window,
"in property <bool> connection-dialog-open: false;");
ok &= ExpectContains("main_window.slint", main_window,
"text: root.connection-status-text;");
ok &= ExpectContains("gui_application.cpp", gui_application,
"case ConnectionStatus::RemoteUnavailable:");
ok &= ExpectContains("gui_application.cpp", gui_application,
"return localization::device_offline[language];");
ok &= ExpectContains("runtime_state.h", runtime_state_h,
"pending_presence_probe_started_at_");
ok &= ExpectContains("remote_session.h", remote_session_h,
+63 -115
View File
@@ -10,8 +10,7 @@ std::filesystem::path FindRepoRoot() {
std::filesystem::path current = std::filesystem::current_path();
while (!current.empty()) {
if (std::filesystem::exists(current / "xmake.lua") &&
std::filesystem::exists(current /
"src/gui/views/toolbars/control_bar.cpp")) {
std::filesystem::exists(current / "src/gui/ui/stream_window.slint")) {
return current;
}
current = current.parent_path();
@@ -19,88 +18,51 @@ std::filesystem::path FindRepoRoot() {
return {};
}
std::string ReadFile(const std::filesystem::path& path) {
std::string ReadFile(const std::filesystem::path &path) {
std::ifstream file(path, std::ios::binary);
if (!file) {
return {};
}
std::ostringstream stream;
stream << file.rdbuf();
return stream.str();
}
bool ExpectContains(const char* name, const std::string& value,
const std::string& expected) {
bool ExpectContains(const char *name, const std::string &value,
const std::string &expected) {
if (value.find(expected) != std::string::npos) {
return true;
}
std::cerr << name << " missing expected text: " << expected << "\n";
return false;
}
bool ExpectNotContains(const char* name, const std::string& value,
const std::string& unexpected) {
bool ExpectNotContains(const char *name, const std::string &value,
const std::string &unexpected) {
if (value.find(unexpected) == std::string::npos) {
return true;
}
std::cerr << name << " contains unexpected text: " << unexpected << "\n";
return false;
}
bool ExpectContainsAtLeast(const char* name, const std::string& value,
const std::string& expected, size_t min_count) {
bool ExpectContainsAtLeast(const char *name, const std::string &value,
const std::string &expected, size_t min_count) {
size_t count = 0;
size_t pos = 0;
while ((pos = value.find(expected, pos)) != std::string::npos) {
++count;
pos += expected.size();
}
if (count >= min_count) {
return true;
}
std::cerr << name << " expected at least " << min_count
<< " occurrences of: " << expected << ", found " << count << "\n";
return false;
}
bool ExpectResetBeforeDisplayPopup(const std::string& value) {
const std::string reset = "props->display_selectable_hovered_ = false;";
const std::string popup = "ImGui::BeginPopup(\"display\")";
const size_t reset_pos = value.find(reset);
const size_t popup_pos = value.find(popup);
if (reset_pos != std::string::npos && popup_pos != std::string::npos &&
reset_pos < popup_pos) {
return true;
}
std::cerr << "control_bar.cpp must clear display_selectable_hovered_ before "
"checking the display popup\n";
return false;
}
bool ExpectResetBeforeShortcutPopup(const std::string& value) {
const std::string reset = "props->shortcut_selectable_hovered_ = false;";
const std::string popup = "ImGui::BeginPopup(\"shortcut\")";
const size_t reset_pos = value.find(reset);
const size_t popup_pos = value.find(popup);
if (reset_pos != std::string::npos && popup_pos != std::string::npos &&
reset_pos < popup_pos) {
return true;
}
std::cerr << "control_bar.cpp must clear shortcut_selectable_hovered_ before "
"checking the shortcut popup\n";
return false;
}
} // namespace
} // namespace
int main() {
const std::filesystem::path repo_root = FindRepoRoot();
@@ -109,75 +71,61 @@ int main() {
return 1;
}
const std::string control_bar =
ReadFile(repo_root / "src/gui/views/toolbars/control_bar.cpp");
const std::string stream =
ReadFile(repo_root / "src/gui/ui/stream_window.slint");
const std::string common = ReadFile(repo_root / "src/gui/ui/common.slint");
const std::string application =
ReadFile(repo_root / "src/gui/application/gui_application.cpp");
bool ok = true;
ok &= ExpectContains("control_bar.cpp", control_bar,
"props->display_selectable_hovered_ = false;");
ok &= ExpectContains("control_bar.cpp", control_bar,
"ImGui::IsWindowHovered("
"ImGuiHoveredFlags_RootAndChildWindows)");
ok &= ExpectResetBeforeDisplayPopup(control_bar);
ok &= ExpectContains("control_bar.cpp", control_bar,
"props->shortcut_selectable_hovered_ =");
ok &= ExpectResetBeforeShortcutPopup(control_bar);
ok &= ExpectContains("control_bar.cpp", control_bar,
"void ShowControlBarTooltip(const std::string& text)");
ok &= ExpectContainsAtLeast("control_bar.cpp", control_bar,
"ShowControlBarTooltip(", 10);
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::select_display"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::send_shortcut"
"[localization_language_index_]");
ok &= ExpectNotContains("control_bar.cpp", control_bar,
"ShowControlBarTooltip("
"props->mouse_control_button_label_)");
ok &= ExpectNotContains("control_bar.cpp", control_bar,
"ShowControlBarTooltip("
"props->audio_capture_button_label_)");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::select_file"
"[localization_language_index_]");
ok &= ExpectNotContains("control_bar.cpp", control_bar,
"ShowControlBarTooltip("
"props->net_traffic_stats_button_label_)");
ok &= ExpectNotContains("control_bar.cpp", control_bar,
"ShowControlBarTooltip("
"props->fullscreen_button_label_)");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::release_mouse"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::control_mouse"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::audio_capture"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::mute[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::hide_net_traffic_stats"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::show_net_traffic_stats"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::exit_fullscreen"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::fullscreen"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::disconnect"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::expand_control_bar"
"[localization_language_index_]");
ok &= ExpectContains("control_bar.cpp", control_bar,
"localization::collapse_control_bar"
"[localization_language_index_]");
ok &= ExpectContains("stream_window.slint", stream,
"private property <bool> display-menu-open: false;");
ok &= ExpectContains("stream_window.slint", stream,
"root.shortcut-menu-open = false;");
ok &= ExpectContains("stream_window.slint", stream,
"if root.display-menu-open: Rectangle");
ok &= ExpectContains("stream_window.slint", stream,
"display-touch.has-hover");
ok &= ExpectContains("stream_window.slint", stream,
"root.switch-display(index)");
ok &= ExpectContains("stream_window.slint", stream,
"if root.shortcut-menu-open: Rectangle");
ok &= ExpectContains("stream_window.slint", stream,
"shortcut-touch.has-hover");
ok &= ExpectContains("common.slint", common,
"if touch.has-hover && root.tooltip != \"\": Rectangle");
ok &= ExpectContainsAtLeast("stream_window.slint", stream, "tooltip:", 9);
ok &= ExpectContains("stream_window.slint", stream,
"tooltip: StreamStrings.select-display");
ok &= ExpectContains("stream_window.slint", stream,
"tooltip: StreamStrings.send-shortcut");
ok &= ExpectContains("stream_window.slint", stream,
"root.mouse-control-enabled ? StreamStrings.release-mouse : StreamStrings.control-mouse");
ok &= ExpectContains("stream_window.slint", stream,
"root.audio-enabled ? StreamStrings.mute : StreamStrings.audio");
ok &= ExpectContains("stream_window.slint", stream,
"tooltip: StreamStrings.select-file");
ok &= ExpectContains("stream_window.slint", stream,
"root.stats-visible ? StreamStrings.hide-stats : StreamStrings.show-stats");
ok &= ExpectContains("stream_window.slint", stream,
"root.fullscreen-enabled ? StreamStrings.exit-fullscreen : StreamStrings.fullscreen");
ok &= ExpectContains("stream_window.slint", stream,
"tooltip: StreamStrings.disconnect");
ok &= ExpectContains("stream_window.slint", stream,
"root.control-expanded ? StreamStrings.collapse-control : StreamStrings.expand-control");
ok &= ExpectContains("gui_application.cpp", application,
"set_select_display(");
ok &= ExpectContains("gui_application.cpp", application,
"set_show_stats(");
ok &= ExpectContains("gui_application.cpp", application,
"set_expand_control(");
ok &= ExpectContains("gui_application.cpp", application,
"set_collapse_control(");
ok &= ExpectContains("gui_application.cpp", application,
"(*ui_->stream)->global<ui::StreamStrings>()");
ok &= ExpectNotContains("gui_application.cpp", application,
"#include <imgui");
return ok ? 0 : 1;
}
+144
View File
@@ -0,0 +1,144 @@
#include "crossdesk_ui.h"
#include <cassert>
#include <memory>
#include <string>
#include <vector>
int main() {
auto window = crossdesk::ui::MainWindow::create();
auto stream = crossdesk::ui::StreamWindow::create();
auto server = crossdesk::ui::ServerWindow::create();
window->set_local_id("123 456 789");
window->set_local_password("123456");
window->set_connection_dialog_open(true);
window->set_connection_password_required(true);
window->set_settings_session_active(true);
window->set_portable_service_settings_visible(true);
assert(std::string(window->get_local_id()) == "123 456 789");
assert(window->get_connection_dialog_open());
assert(window->get_connection_password_required());
assert(window->get_settings_session_active());
assert(window->get_portable_service_settings_visible());
window->set_settings_open(true);
window->set_about_open(true);
assert(window->get_settings_open());
assert(window->get_about_open());
window->set_remote_id_input("987654321");
window->invoke_reset_remote_id();
assert(std::string(window->get_remote_id_input()).empty());
std::vector<crossdesk::ui::RecentConnection> connections;
crossdesk::ui::RecentConnection connection;
connection.remote_id = "987654321";
connection.display_name = "Office PC";
connection.host_name = "office-pc";
connection.online = true;
connections.emplace_back(std::move(connection));
window->set_recent_connections(
std::make_shared<slint::VectorModel<crossdesk::ui::RecentConnection>>(
std::move(connections)));
assert(window->get_recent_connections()->row_count() == 1);
bool connect_requested = false;
window->on_connect_requested([&](slint::SharedString remote_id) {
connect_requested = std::string(remote_id) == "987654321";
});
window->invoke_connect_requested("987654321");
assert(connect_requested);
bool password_submitted = false;
window->on_connection_submit_password(
[&](slint::SharedString password, bool remember) {
password_submitted = std::string(password) == "654321" && remember;
});
window->invoke_connection_submit_password("654321", true);
assert(password_submitted);
crossdesk::ui::StreamTab tab;
tab.remote_id = "123456789";
tab.title = "Remote host";
tab.connected = true;
stream->set_tabs(
std::make_shared<slint::VectorModel<crossdesk::ui::StreamTab>>(
std::vector{tab}));
assert(stream->get_tabs()->row_count() == 1);
bool tab_reordered = false;
stream->on_reorder_tab([&](int index, float x, float width) {
tab_reordered = index == 0 && x == 120.0f && width == 110.0f;
});
stream->invoke_reorder_tab(0, 120.0f, 110.0f);
assert(tab_reordered);
crossdesk::ui::FileTransferEntry transfer;
transfer.name = "archive.zip";
transfer.status = "Sending";
transfer.progress = 0.5f;
transfer.speed = "1.0 mbps";
transfer.size = "2.00 MB";
stream->set_file_transfers(
std::make_shared<
slint::VectorModel<crossdesk::ui::FileTransferEntry>>(
std::vector{transfer}));
stream->set_file_transfer_visible(true);
crossdesk::ui::NetworkStatsRow video_stats;
video_stats.label = "Video";
video_stats.inbound = "427 kbps";
video_stats.outbound = "5 kbps";
video_stats.loss_rate = "0%";
stream->set_stats_rows(
std::make_shared<
slint::VectorModel<crossdesk::ui::NetworkStatsRow>>(
std::vector{video_stats}));
stream->set_stats_fps("53");
stream->set_stats_resolution("3024x1964");
stream->set_stats_connection_mode("Direct");
stream->set_stats_visible(true);
stream->set_fullscreen_enabled(true);
assert(stream->get_file_transfers()->row_count() == 1);
assert(stream->get_file_transfer_visible());
assert(stream->get_stats_rows()->row_count() == 1);
assert(std::string(stream->get_stats_fps()) == "53");
assert(std::string(stream->get_stats_resolution()) == "3024x1964");
assert(std::string(stream->get_stats_connection_mode()) == "Direct");
assert(stream->get_stats_visible());
assert(stream->get_fullscreen_enabled());
bool display_switched = false;
stream->on_switch_display(
[&](int index) { display_switched = index == 1; });
stream->invoke_switch_display(1);
assert(display_switched);
auto &stream_strings = stream->global<crossdesk::ui::StreamStrings>();
stream_strings.set_select_display("Select Display");
stream_strings.set_expand_control("Expand Control Bar");
assert(std::string(stream_strings.get_select_display()) == "Select Display");
assert(std::string(stream_strings.get_expand_control()) ==
"Expand Control Bar");
crossdesk::ui::ControllerEntry controller;
controller.remote_id = "123456789";
controller.display_name = "Remote host";
server->set_controllers(
std::make_shared<slint::VectorModel<crossdesk::ui::ControllerEntry>>(
std::vector{controller}));
server->set_file_transfer_visible(true);
server->set_sending_file(true);
server->set_file_progress(0.5f);
server->set_current_file_name("archive.zip");
server->set_file_size_text("1.00 MB / 2.00 MB");
assert(server->get_controllers()->row_count() == 1);
assert(server->get_file_transfer_visible());
assert(server->get_sending_file());
assert(server->get_file_progress() == 0.5f);
bool controller_selected = false;
server->on_controller_selected(
[&](int index) { controller_selected = index == 0; });
server->invoke_controller_selected(0);
assert(controller_selected);
return 0;
}
+14 -9
View File
@@ -70,8 +70,10 @@ int main() {
return 1;
}
const std::string control_bar =
ReadFile(repo_root / "src/gui/views/toolbars/control_bar.cpp");
const std::string stream_window =
ReadFile(repo_root / "src/gui/ui/stream_window.slint");
const std::string gui_application =
ReadFile(repo_root / "src/gui/application/gui_application.cpp");
const std::string windows_service_runtime =
ReadFile(repo_root / "src/gui/runtime/windows_service_runtime.cpp");
const std::string runtime_state_h =
@@ -87,13 +89,16 @@ int main() {
ok &= ExpectTrue(
"secure desktop input routing",
crossdesk::IsSecureDesktopInteractionRequired("secure-desktop"));
ok &= ExpectNotContains("control_bar.cpp", control_bar,
"CanSendSecureAttentionSequence("
"props->remote_interactive_stage_)");
ok &= ExpectNotContains("control_bar.cpp", control_bar,
"ImGui::BeginDisabled();\n"
" }\n"
" if (ImGui::Selectable(sas_label.c_str()))");
ok &= ExpectContains("stream_window.slint", stream_window,
"for shortcut in [\"Ctrl+Alt+Del\", \"Win+L\"]");
ok &= ExpectContains("stream_window.slint", stream_window,
"root.send-shortcut(shortcut)");
ok &= ExpectContains("gui_application.cpp", gui_application,
"std::string(shortcut) == \"Ctrl+Alt+Del\"");
ok &= ExpectContains("gui_application.cpp", gui_application,
"ServiceCommandFlag::send_sas");
ok &= ExpectContains("gui_application.cpp", gui_application,
"ServiceCommandFlag::lock_workstation");
ok &= ExpectNotContains("windows_service_runtime.cpp",
windows_service_runtime, "sas_requires_lock_screen");
ok &= ExpectContains("runtime_state.h", runtime_state_h,