mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-09-01 14:33:18 +08:00
[refactor] reorganize platform and wire sources
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
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 /
|
||||
"deps/submodules/minirtc/src/api/minirtc.h")) {
|
||||
return current;
|
||||
}
|
||||
current = current.parent_path();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
if (value.find(unexpected) == std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << name << " contains unexpected text: " << unexpected << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const std::filesystem::path repo_root = FindRepoRoot();
|
||||
if (repo_root.empty()) {
|
||||
std::cerr << "failed to locate repository root\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string minirtc_api =
|
||||
ReadFile(repo_root / "deps/submodules/minirtc/src/api/minirtc.h");
|
||||
const std::string peer_connection =
|
||||
ReadFile(repo_root / "deps/submodules/minirtc/src/pc/peer_connection.cpp");
|
||||
const std::string main_window =
|
||||
ReadFile(repo_root / "apps/desktop/src/gui/ui/main_window.slint");
|
||||
const std::string gui_application =
|
||||
ReadFile(repo_root / "apps/desktop/src/gui/application/gui_application.cpp");
|
||||
const std::string runtime_state_h =
|
||||
ReadFile(repo_root / "apps/desktop/src/gui/runtime/runtime_state.h");
|
||||
const std::string remote_session_h =
|
||||
ReadFile(repo_root / "apps/desktop/src/gui/runtime/remote_session.h");
|
||||
const std::string connection_runtime_cpp =
|
||||
ReadFile(repo_root / "apps/desktop/src/gui/runtime/connection_runtime.cpp");
|
||||
const std::string peer_event_handler_cpp =
|
||||
ReadFile(repo_root / "apps/desktop/src/gui/runtime/peer_event_handler.cpp");
|
||||
const std::string application_state_h =
|
||||
ReadFile(repo_root / "apps/desktop/src/gui/application/application_state.h");
|
||||
|
||||
bool ok = true;
|
||||
ok &= ExpectContains("minirtc.h", minirtc_api, "RemoteUnavailable");
|
||||
ok &= ExpectNotContains("minirtc.h", minirtc_api, "DeviceOffline");
|
||||
ok &= ExpectContains("peer_connection.cpp", peer_connection,
|
||||
"\"Remote unavailable\"");
|
||||
ok &= ExpectContains("peer_connection.cpp", peer_connection,
|
||||
"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,
|
||||
"\"Device offline\"");
|
||||
ok &= ExpectNotContains("peer_connection.cpp", peer_connection,
|
||||
"ConnectionStatus::DeviceOffline");
|
||||
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("connection_runtime.cpp", connection_runtime_cpp,
|
||||
"HandlePresenceProbeTimeout");
|
||||
ok &= ExpectContains("connection_runtime.cpp", connection_runtime_cpp,
|
||||
"kPresenceProbeTimeout");
|
||||
ok &= ExpectNotContains("remote_session.h", remote_session_h,
|
||||
"connection_attempt_started_at_");
|
||||
ok &= ExpectNotContains("remote_session.h", remote_session_h,
|
||||
"connection_attempt_active_");
|
||||
ok &= ExpectNotContains("connection_runtime.cpp", connection_runtime_cpp,
|
||||
"kConnectionAttemptTimeout");
|
||||
ok &= ExpectNotContains("connection_runtime.cpp", connection_runtime_cpp,
|
||||
"ConnectionStatus::Failed");
|
||||
ok &= ExpectContains("peer_event_handler.cpp", peer_event_handler_cpp,
|
||||
"props->connection_status_.store(status);");
|
||||
ok &= ExpectContains("peer_event_handler.cpp", peer_event_handler_cpp,
|
||||
"case ConnectionStatus::Failed:");
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <remote_action.h>
|
||||
|
||||
#include "cursor_position.h"
|
||||
#include "display_info.h"
|
||||
|
||||
namespace {
|
||||
|
||||
bool Expect(bool condition, const char* message) {
|
||||
if (condition) return true;
|
||||
std::cerr << message << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const std::vector<crossdesk::DisplayInfo> displays = {
|
||||
crossdesk::DisplayInfo("Test Display", 0, 0, 1920, 1080)};
|
||||
crossdesk::CursorState normalized_cursor{};
|
||||
const bool ok = Expect(
|
||||
crossdesk::NormalizeCursorPosition(960.0, 540.0, displays, 0,
|
||||
&normalized_cursor) &&
|
||||
normalized_cursor.position_valid &&
|
||||
normalized_cursor.x == 0.5f && normalized_cursor.y == 0.5f,
|
||||
"cursor position should use continuous display extents");
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
|
||||
#include "keyboard_capturer.h"
|
||||
|
||||
int main() {
|
||||
Display* receiver = XOpenDisplay(nullptr);
|
||||
if (!receiver) {
|
||||
std::fprintf(stderr, "open display failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const int screen = DefaultScreen(receiver);
|
||||
Window window = XCreateSimpleWindow(receiver, RootWindow(receiver, screen),
|
||||
0, 0, 100, 100, 0, 0, 0);
|
||||
XSelectInput(receiver, window, KeyPressMask | KeyReleaseMask);
|
||||
XMapWindow(receiver, window);
|
||||
XSync(receiver, False);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
XSetInputFocus(receiver, window, RevertToParent, CurrentTime);
|
||||
XSync(receiver, False);
|
||||
|
||||
crossdesk::PlatformKeyboardCapturer keyboard;
|
||||
// Exercise the scan-code fallback: the controller supplied no usable VK,
|
||||
// but did preserve the set-1 scan code for the A key.
|
||||
const int down = keyboard.SendKeyboardCommand(0, true, 0x1E, false);
|
||||
const int up = keyboard.SendKeyboardCommand(0, false, 0x1E, false);
|
||||
|
||||
bool saw_down = false;
|
||||
bool saw_up = false;
|
||||
const auto deadline = std::chrono::steady_clock::now() +
|
||||
std::chrono::seconds(2);
|
||||
while (std::chrono::steady_clock::now() < deadline &&
|
||||
(!saw_down || !saw_up)) {
|
||||
while (XPending(receiver) > 0) {
|
||||
XEvent event{};
|
||||
XNextEvent(receiver, &event);
|
||||
saw_down = saw_down || event.type == KeyPress;
|
||||
saw_up = saw_up || event.type == KeyRelease;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
XDestroyWindow(receiver, window);
|
||||
XCloseDisplay(receiver);
|
||||
std::printf("down_ret=%d up_ret=%d saw_down=%d saw_up=%d\n", down, up,
|
||||
saw_down, saw_up);
|
||||
return down == 0 && up == 0 && saw_down && saw_up ? 0 : 2;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "macos_keyboard_modifier_state.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
|
||||
bool ExpectEqual(const char* name, uint32_t actual, uint32_t expected) {
|
||||
if (actual == expected) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << name << " mismatch\n"
|
||||
<< " expected: " << expected << "\n"
|
||||
<< " actual: " << actual << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
crossdesk::MacKeyboardModifierState state;
|
||||
|
||||
bool ok = true;
|
||||
ok &= ExpectEqual("initial flags", state.flags(), 0);
|
||||
ok &= ExpectEqual("left shift down", state.Update(0xA0, true),
|
||||
crossdesk::kMacInjectedModifierShift);
|
||||
ok &= ExpectEqual("shifted semicolon keeps shift", state.Update(0xBA, true),
|
||||
crossdesk::kMacInjectedModifierShift);
|
||||
ok &= ExpectEqual("semicolon up keeps shift", state.Update(0xBA, false),
|
||||
crossdesk::kMacInjectedModifierShift);
|
||||
ok &=
|
||||
ExpectEqual("right shift down while left held", state.Update(0xA1, true),
|
||||
crossdesk::kMacInjectedModifierShift);
|
||||
ok &= ExpectEqual("left shift up while right held", state.Update(0xA0, false),
|
||||
crossdesk::kMacInjectedModifierShift);
|
||||
ok &=
|
||||
ExpectEqual("right shift up clears shift", state.Update(0xA1, false), 0);
|
||||
|
||||
ok &= ExpectEqual("left control down", state.Update(0xA2, true),
|
||||
crossdesk::kMacInjectedModifierControl);
|
||||
ok &= ExpectEqual("right alt adds option", state.Update(0xA5, true),
|
||||
crossdesk::kMacInjectedModifierControl |
|
||||
crossdesk::kMacInjectedModifierOption);
|
||||
ok &= ExpectEqual("left command adds command", state.Update(0x5B, true),
|
||||
crossdesk::kMacInjectedModifierControl |
|
||||
crossdesk::kMacInjectedModifierOption |
|
||||
crossdesk::kMacInjectedModifierCommand);
|
||||
ok &= ExpectEqual("left control up leaves option command",
|
||||
state.Update(0xA2, false),
|
||||
crossdesk::kMacInjectedModifierOption |
|
||||
crossdesk::kMacInjectedModifierCommand);
|
||||
ok &= ExpectEqual("right alt up leaves command", state.Update(0xA5, false),
|
||||
crossdesk::kMacInjectedModifierCommand);
|
||||
ok &= ExpectEqual("left command up clears all", state.Update(0x5B, false), 0);
|
||||
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "path_manager.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <limits.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#else
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
std::filesystem::path GetExecutableDirectory() {
|
||||
#ifdef _WIN32
|
||||
wchar_t buffer[MAX_PATH] = {};
|
||||
DWORD length = GetModuleFileNameW(nullptr, buffer, MAX_PATH);
|
||||
if (length == 0 || length == MAX_PATH) {
|
||||
return {};
|
||||
}
|
||||
return std::filesystem::path(buffer).parent_path();
|
||||
#elif defined(__APPLE__)
|
||||
char buffer[PATH_MAX] = {};
|
||||
uint32_t size = sizeof(buffer);
|
||||
if (_NSGetExecutablePath(buffer, &size) != 0) {
|
||||
return {};
|
||||
}
|
||||
return std::filesystem::weakly_canonical(buffer).parent_path();
|
||||
#else
|
||||
char buffer[PATH_MAX] = {};
|
||||
ssize_t length = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1);
|
||||
if (length <= 0) {
|
||||
return {};
|
||||
}
|
||||
buffer[length] = '\0';
|
||||
return std::filesystem::path(buffer).parent_path();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ExpectEqual(const char* name, const std::filesystem::path& actual,
|
||||
const std::filesystem::path& expected) {
|
||||
if (actual.lexically_normal() == expected.lexically_normal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << name << " mismatch\n"
|
||||
<< " expected: " << expected.string() << "\n"
|
||||
<< " actual: " << actual.string() << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const std::filesystem::path exe_dir = GetExecutableDirectory();
|
||||
if (exe_dir.empty()) {
|
||||
std::cerr << "failed to resolve executable directory\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
crossdesk::PathManager path_manager("CrossDesk");
|
||||
const std::filesystem::path expected_data = exe_dir / "data";
|
||||
const std::filesystem::path expected_logs = exe_dir / "logs";
|
||||
|
||||
bool ok = true;
|
||||
ok &= ExpectEqual("config path", path_manager.GetConfigPath(), expected_data);
|
||||
ok &= ExpectEqual("cache path", path_manager.GetCachePath(), expected_data);
|
||||
ok &= ExpectEqual("log path", path_manager.GetLogPath(), expected_logs);
|
||||
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
||||
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 /
|
||||
"libs/wire/include/remote_action.h") &&
|
||||
std::filesystem::exists(
|
||||
current / "apps/desktop/xmake/targets.lua")) {
|
||||
return current;
|
||||
}
|
||||
current = current.parent_path();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string ReadFile(const std::filesystem::path& path) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file) {
|
||||
return {};
|
||||
}
|
||||
std::ostringstream contents;
|
||||
contents << file.rdbuf();
|
||||
return contents.str();
|
||||
}
|
||||
|
||||
bool IsCheckedWireFile(const std::filesystem::path& path) {
|
||||
static constexpr std::array<std::string_view, 8> kExtensions = {
|
||||
".h", ".hpp", ".c", ".cc", ".cpp", ".m", ".mm", ".lua"};
|
||||
const std::string extension = path.extension().string();
|
||||
for (std::string_view candidate : kExtensions) {
|
||||
if (extension == candidate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckWireBoundary(const std::filesystem::path& repo_root) {
|
||||
static constexpr std::array<std::string_view, 12> kForbiddenReferences = {
|
||||
"apps/desktop", "../desktop", "platform/common", "<windows.h>",
|
||||
"<Windows.h>", "<AppKit/", "<Cocoa/", "<CoreGraphics/",
|
||||
"<X11/", "<wayland-", "<linux/", "<d3d"};
|
||||
|
||||
bool ok = true;
|
||||
const std::filesystem::path wire_root = repo_root / "libs/wire";
|
||||
for (const auto& entry :
|
||||
std::filesystem::recursive_directory_iterator(wire_root)) {
|
||||
if (!entry.is_regular_file() || !IsCheckedWireFile(entry.path())) {
|
||||
continue;
|
||||
}
|
||||
const std::string contents = ReadFile(entry.path());
|
||||
for (std::string_view forbidden : kForbiddenReferences) {
|
||||
if (contents.find(forbidden) == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
std::cerr << entry.path().lexically_relative(repo_root).string()
|
||||
<< " crosses the wire/app boundary with: " << forbidden
|
||||
<< '\n';
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool CheckPlatformIncludesArePrivate(
|
||||
const std::filesystem::path& repo_root) {
|
||||
const std::filesystem::path targets_path =
|
||||
repo_root / "apps/desktop/xmake/targets.lua";
|
||||
const std::string targets = ReadFile(targets_path);
|
||||
if (targets.empty()) {
|
||||
std::cerr << "failed to read "
|
||||
<< targets_path.lexically_relative(repo_root).string() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
std::size_t cursor = 0;
|
||||
constexpr std::string_view kCall = "add_includedirs(";
|
||||
constexpr std::string_view kPlatformPath =
|
||||
"apps/desktop/src/platform/";
|
||||
while ((cursor = targets.find(kCall, cursor)) != std::string::npos) {
|
||||
const std::size_t call_end = targets.find(')', cursor + kCall.size());
|
||||
if (call_end == std::string::npos) {
|
||||
std::cerr << "unterminated add_includedirs call in "
|
||||
<< targets_path.lexically_relative(repo_root).string() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string_view call(targets.data() + cursor,
|
||||
call_end - cursor + 1);
|
||||
std::string compact_call;
|
||||
compact_call.reserve(call.size());
|
||||
for (char character : call) {
|
||||
if (!std::isspace(static_cast<unsigned char>(character))) {
|
||||
compact_call.push_back(character);
|
||||
}
|
||||
}
|
||||
if (call.find(kPlatformPath) != std::string_view::npos &&
|
||||
compact_call.find("public=true") != std::string::npos) {
|
||||
std::cerr << "platform implementation include directory is public: "
|
||||
<< call << '\n';
|
||||
ok = false;
|
||||
}
|
||||
cursor = call_end + 1;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool CheckWireLayout(const std::filesystem::path& repo_root) {
|
||||
static constexpr std::array<std::string_view, 5> kForbiddenPaths = {
|
||||
"libs/wire/include/crossdesk", "libs/wire/xmake.lua", "libs/wire/.xmake",
|
||||
"xmake.protocol.lua", "xmake.wire.lua"};
|
||||
bool ok = true;
|
||||
for (std::string_view relative_path : kForbiddenPaths) {
|
||||
if (!std::filesystem::exists(repo_root / relative_path)) {
|
||||
continue;
|
||||
}
|
||||
std::cerr << "forbidden legacy layer, standalone project, or wire cache: "
|
||||
<< relative_path << '\n';
|
||||
ok = false;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const std::filesystem::path repo_root = FindRepoRoot();
|
||||
if (repo_root.empty()) {
|
||||
std::cerr << "failed to locate repository root\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
static constexpr std::array<std::string_view, 11> kLegacyRootDirectories = {
|
||||
"src", "ios", "scripts", "icons",
|
||||
"tests", "xmake", "shared", "platforms",
|
||||
"wire", "submodules", "thirdparty"};
|
||||
for (std::string_view directory : kLegacyRootDirectories) {
|
||||
if (!std::filesystem::exists(repo_root / directory)) {
|
||||
continue;
|
||||
}
|
||||
std::cerr << "legacy root directory still exists: " << directory << '\n';
|
||||
ok = false;
|
||||
}
|
||||
|
||||
ok &= CheckWireBoundary(repo_root);
|
||||
ok &= CheckPlatformIncludesArePrivate(repo_root);
|
||||
ok &= CheckWireLayout(repo_root);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
#include "crossdesk_ui.h"
|
||||
#include "fa_solid_900.h"
|
||||
#include "ui/ui_localization.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <charconv>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
// Set the CROSSDESK_*_UI_SNAPSHOT variables while using Slint's software
|
||||
// renderer to export deterministic active and inactive visual-check images.
|
||||
bool WriteWindowSnapshot(const slint::Window &window, const char *path) {
|
||||
const auto snapshot = window.take_snapshot();
|
||||
if (!snapshot) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream output(path, std::ios::binary);
|
||||
output << "P6\n"
|
||||
<< snapshot->width() << ' ' << snapshot->height() << "\n255\n";
|
||||
for (const auto &pixel : *snapshot) {
|
||||
output.put(static_cast<char>(pixel.r));
|
||||
output.put(static_cast<char>(pixel.g));
|
||||
output.put(static_cast<char>(pixel.b));
|
||||
}
|
||||
return output.good();
|
||||
}
|
||||
|
||||
bool RegisterFontAwesome(slint::Window &window) {
|
||||
return !window.window_handle()
|
||||
.register_font_from_data(fa_solid_900_ttf,
|
||||
fa_solid_900_ttf_len)
|
||||
.has_value();
|
||||
}
|
||||
|
||||
struct CaptureOptions {
|
||||
bool enabled = false;
|
||||
std::string page;
|
||||
int language = 0;
|
||||
std::string snapshot_path;
|
||||
};
|
||||
|
||||
// Compatibility entry point for the former application-level debug capture
|
||||
// mode. CROSSDESK_UI_CAPTURE keeps the interactive workflow, while
|
||||
// CROSSDESK_UI_CAPTURE_SNAPSHOT writes a deterministic PPM and exits so every
|
||||
// page can be exercised in automation. The legacy /tmp overrides still win.
|
||||
std::string ReadFirstLine(const char *path) {
|
||||
std::ifstream input(path);
|
||||
std::string value;
|
||||
std::getline(input, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
CaptureOptions LoadCaptureOptions() {
|
||||
CaptureOptions options;
|
||||
if (const char *capture = std::getenv("CROSSDESK_UI_CAPTURE")) {
|
||||
options.enabled = std::string_view(capture) != "0";
|
||||
}
|
||||
if (!options.enabled) {
|
||||
return options;
|
||||
}
|
||||
|
||||
if (const char *page = std::getenv("CROSSDESK_UI_CAPTURE_PAGE")) {
|
||||
options.page = page;
|
||||
}
|
||||
if (const std::string page_override =
|
||||
ReadFirstLine("/tmp/crossdesk-ui-capture-page");
|
||||
!page_override.empty()) {
|
||||
options.page = page_override;
|
||||
}
|
||||
|
||||
std::string language_value =
|
||||
ReadFirstLine("/tmp/crossdesk-ui-capture-language");
|
||||
if (language_value.empty()) {
|
||||
if (const char *language =
|
||||
std::getenv("CROSSDESK_UI_CAPTURE_LANGUAGE")) {
|
||||
language_value = language;
|
||||
}
|
||||
}
|
||||
if (!language_value.empty()) {
|
||||
int language = 0;
|
||||
const auto [end, error] = std::from_chars(
|
||||
language_value.data(), language_value.data() + language_value.size(),
|
||||
language);
|
||||
if (error == std::errc{} &&
|
||||
end == language_value.data() + language_value.size()) {
|
||||
options.language =
|
||||
crossdesk::localization::detail::ClampLanguageIndex(language);
|
||||
}
|
||||
}
|
||||
if (const char *snapshot =
|
||||
std::getenv("CROSSDESK_UI_CAPTURE_SNAPSHOT")) {
|
||||
options.snapshot_path = snapshot;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
bool HasNonEmptyEnvironmentVariable(const char *name) {
|
||||
const char *value = std::getenv(name);
|
||||
return value != nullptr && value[0] != '\0';
|
||||
}
|
||||
|
||||
void ResetMainCaptureState(
|
||||
const slint::ComponentHandle<crossdesk::ui::MainWindow> &window) {
|
||||
window->set_settings_open(false);
|
||||
window->set_self_host_settings_open(false);
|
||||
window->set_about_open(false);
|
||||
window->set_update_open(false);
|
||||
window->set_update_available(false);
|
||||
window->set_reset_password_open(false);
|
||||
window->set_reset_password_invalid(false);
|
||||
window->set_new_password_input("");
|
||||
window->set_alias_open(false);
|
||||
window->set_alias_input("");
|
||||
window->set_delete_open(false);
|
||||
window->set_offline_warning("");
|
||||
window->set_connection_dialog_open(false);
|
||||
window->set_connection_status_text("");
|
||||
window->set_connection_pending(false);
|
||||
window->set_connection_password_required(false);
|
||||
window->set_connection_validating(false);
|
||||
window->set_permission_dialog_open(false);
|
||||
window->set_screen_recording_granted(true);
|
||||
window->set_accessibility_granted(true);
|
||||
window->set_settings_session_active(false);
|
||||
window->set_portable_service_settings_visible(false);
|
||||
window->set_portable_service_dialog_open(false);
|
||||
window->set_portable_service_suppressed_notice_open(false);
|
||||
}
|
||||
|
||||
void ConfigureMainCapturePage(
|
||||
const slint::ComponentHandle<crossdesk::ui::MainWindow> &window,
|
||||
const CaptureOptions &options) {
|
||||
const int language = crossdesk::ui_localization::ApplyMainWindowStrings(
|
||||
window, options.language);
|
||||
crossdesk::ui_localization::ApplyStreamWindowStrings(window, language);
|
||||
window->set_language_index(language);
|
||||
ResetMainCaptureState(window);
|
||||
|
||||
#if _WIN32
|
||||
window->set_custom_titlebar(true);
|
||||
window->set_wayland_titlebar(false);
|
||||
#if CROSSDESK_PORTABLE
|
||||
window->set_portable_service_settings_visible(true);
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
const bool has_x11 = HasNonEmptyEnvironmentVariable("DISPLAY");
|
||||
const bool use_xwayland =
|
||||
has_x11 && HasNonEmptyEnvironmentVariable("WAYLAND_DISPLAY");
|
||||
window->set_custom_titlebar(has_x11);
|
||||
window->set_wayland_titlebar(use_xwayland);
|
||||
#else
|
||||
window->set_custom_titlebar(false);
|
||||
window->set_wayland_titlebar(false);
|
||||
#endif
|
||||
|
||||
if (options.page == "settings") {
|
||||
window->set_settings_open(true);
|
||||
} else if (options.page == "self-hosted") {
|
||||
window->set_settings_open(true);
|
||||
window->set_self_host_settings_open(true);
|
||||
} else if (options.page == "about") {
|
||||
window->set_current_version("0.0.0");
|
||||
window->set_about_open(true);
|
||||
} else if (options.page == "update") {
|
||||
window->set_update_available(true);
|
||||
window->set_latest_version("v0.0.1");
|
||||
window->set_release_name("CrossDesk Preview");
|
||||
window->set_release_date("2026-08-25");
|
||||
window->set_update_open(true);
|
||||
} else if (options.page == "reset-password") {
|
||||
window->set_reset_password_open(true);
|
||||
} else if (options.page == "reset-password-invalid") {
|
||||
window->set_reset_password_open(true);
|
||||
window->set_reset_password_invalid(true);
|
||||
window->set_new_password_input("123");
|
||||
} else if (options.page == "alias") {
|
||||
window->set_alias_input("Office workstation");
|
||||
window->set_alias_open(true);
|
||||
} else if (options.page == "delete") {
|
||||
window->set_delete_open(true);
|
||||
} else if (options.page == "offline") {
|
||||
window->set_offline_warning(crossdesk::ui_localization::Text(
|
||||
crossdesk::localization::device_offline[language]));
|
||||
} else if (options.page == "connection") {
|
||||
window->set_connection_status_text(crossdesk::ui_localization::Text(
|
||||
crossdesk::localization::p2p_connecting[language]));
|
||||
window->set_connection_pending(true);
|
||||
window->set_connection_dialog_open(true);
|
||||
} else if (options.page == "connection-password") {
|
||||
window->set_connection_status_text(crossdesk::ui_localization::Text(
|
||||
crossdesk::localization::reinput_password[language]));
|
||||
window->set_connection_password_required(true);
|
||||
window->set_connection_dialog_open(true);
|
||||
} else if (options.page == "service") {
|
||||
window->set_portable_service_settings_visible(true);
|
||||
window->set_portable_service_dialog_open(true);
|
||||
} else if (options.page == "service-notice") {
|
||||
window->set_portable_service_settings_visible(true);
|
||||
window->set_portable_service_suppressed_notice_open(true);
|
||||
} else if (options.page == "permission") {
|
||||
window->set_permission_dialog_open(true);
|
||||
window->set_screen_recording_granted(false);
|
||||
window->set_accessibility_granted(false);
|
||||
}
|
||||
}
|
||||
|
||||
slint::Image CreateStreamPreviewImage() {
|
||||
constexpr int preview_width = 640;
|
||||
constexpr int preview_height = 360;
|
||||
slint::SharedPixelBuffer<slint::Rgb8Pixel> pixels(preview_width,
|
||||
preview_height);
|
||||
auto *data = reinterpret_cast<uint8_t *>(pixels.begin());
|
||||
for (int y = 0; y < preview_height; ++y) {
|
||||
for (int x = 0; x < preview_width; ++x) {
|
||||
const bool border = x < 8 || y < 8 || x >= preview_width - 8 ||
|
||||
y >= preview_height - 8;
|
||||
const size_t offset = (static_cast<size_t>(y) * preview_width + x) * 3;
|
||||
data[offset] =
|
||||
border ? 240 : static_cast<uint8_t>(35 + 150 * x / preview_width);
|
||||
data[offset + 1] =
|
||||
border ? 70 : static_cast<uint8_t>(35 + 150 * y / preview_height);
|
||||
data[offset + 2] = border ? 70 : 90;
|
||||
}
|
||||
}
|
||||
return slint::Image(std::move(pixels));
|
||||
}
|
||||
|
||||
void ConfigureStreamCapturePage(
|
||||
const slint::ComponentHandle<crossdesk::ui::StreamWindow> &stream,
|
||||
int language) {
|
||||
language = crossdesk::ui_localization::ApplyStreamWindowStrings(
|
||||
stream, language);
|
||||
#if defined(__linux__)
|
||||
stream->set_custom_titlebar(
|
||||
HasNonEmptyEnvironmentVariable("DISPLAY") &&
|
||||
HasNonEmptyEnvironmentVariable("WAYLAND_DISPLAY"));
|
||||
#else
|
||||
stream->set_custom_titlebar(false);
|
||||
#endif
|
||||
stream->set_native_video_enabled(false);
|
||||
stream->set_window_maximized(false);
|
||||
stream->set_fullscreen_enabled(false);
|
||||
stream->set_stats_visible(false);
|
||||
stream->set_file_transfer_visible(false);
|
||||
stream->set_receiving_text("");
|
||||
stream->set_status_text("");
|
||||
|
||||
crossdesk::ui::StreamTab tab;
|
||||
tab.remote_id = "589173341";
|
||||
tab.title = "Mac";
|
||||
tab.connected = true;
|
||||
stream->set_tabs(
|
||||
std::make_shared<slint::VectorModel<crossdesk::ui::StreamTab>>(
|
||||
std::vector{tab}));
|
||||
stream->set_displays(
|
||||
std::make_shared<slint::VectorModel<slint::SharedString>>(
|
||||
std::vector{slint::SharedString("Display 1")}));
|
||||
stream->set_frame(CreateStreamPreviewImage());
|
||||
stream->set_has_frame(true);
|
||||
const float height = stream->get_custom_titlebar() ? 752.0f : 720.0f;
|
||||
stream->window().set_size(
|
||||
slint::LogicalSize(slint::Size<float>{1280.0f, height}));
|
||||
}
|
||||
|
||||
void ConfigureServerCapturePage(
|
||||
const slint::ComponentHandle<crossdesk::ui::ServerWindow> &server,
|
||||
int language) {
|
||||
language = crossdesk::localization::detail::ClampLanguageIndex(language);
|
||||
crossdesk::ui::ControllerEntry controller;
|
||||
controller.remote_id = "589173341";
|
||||
controller.display_name = "Mac";
|
||||
server->set_controllers(
|
||||
std::make_shared<slint::VectorModel<crossdesk::ui::ControllerEntry>>(
|
||||
std::vector{controller}));
|
||||
server->set_controller_names(
|
||||
std::make_shared<slint::VectorModel<slint::SharedString>>(
|
||||
std::vector{slint::SharedString("Mac")}));
|
||||
server->set_language_index(language);
|
||||
server->set_controller_label(crossdesk::ui_localization::Text(
|
||||
crossdesk::localization::controller[language]));
|
||||
server->set_connection_label(crossdesk::ui_localization::Text(
|
||||
crossdesk::localization::connection_status[language]));
|
||||
server->set_connection_status(crossdesk::ui_localization::Text(
|
||||
crossdesk::localization::p2p_connected[language]));
|
||||
server->set_file_transfer_label(crossdesk::ui_localization::Text(
|
||||
crossdesk::localization::file_transfer[language]));
|
||||
server->set_select_file_label(crossdesk::ui_localization::Text(
|
||||
crossdesk::localization::select_file[language]));
|
||||
server->set_file_transfer_visible(false);
|
||||
server->set_sending_file(false);
|
||||
const float width = language == 0 ? 250.0f : language == 1 ? 330.0f : 430.0f;
|
||||
server->window().set_size(
|
||||
slint::LogicalSize(slint::Size<float>{width, 150.0f}));
|
||||
}
|
||||
|
||||
int RunCaptureMode(
|
||||
const CaptureOptions &options,
|
||||
slint::ComponentHandle<crossdesk::ui::MainWindow> &window,
|
||||
slint::ComponentHandle<crossdesk::ui::StreamWindow> &stream,
|
||||
slint::ComponentHandle<crossdesk::ui::ServerWindow> &server) {
|
||||
ConfigureMainCapturePage(window, options);
|
||||
stream->hide();
|
||||
server->hide();
|
||||
|
||||
slint::Window *capture_window = &window->window();
|
||||
if (options.page == "stream") {
|
||||
ConfigureStreamCapturePage(stream, options.language);
|
||||
window->hide();
|
||||
stream->show();
|
||||
capture_window = &stream->window();
|
||||
} else if (options.page == "server") {
|
||||
ConfigureServerCapturePage(server, options.language);
|
||||
window->hide();
|
||||
server->show();
|
||||
capture_window = &server->window();
|
||||
} else {
|
||||
window->show();
|
||||
}
|
||||
|
||||
if (!options.snapshot_path.empty()) {
|
||||
return WriteWindowSnapshot(*capture_window, options.snapshot_path.c_str())
|
||||
? 0
|
||||
: 7;
|
||||
}
|
||||
|
||||
if (options.page == "stream" || options.page == "server") {
|
||||
slint::run_event_loop();
|
||||
} else {
|
||||
window->run();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const CaptureOptions capture_options = LoadCaptureOptions();
|
||||
auto window = crossdesk::ui::MainWindow::create();
|
||||
auto stream = crossdesk::ui::StreamWindow::create();
|
||||
auto server = crossdesk::ui::ServerWindow::create();
|
||||
if (!RegisterFontAwesome(window->window()) ||
|
||||
!RegisterFontAwesome(stream->window()) ||
|
||||
!RegisterFontAwesome(server->window())) {
|
||||
return 1;
|
||||
}
|
||||
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_custom_titlebar(true);
|
||||
window->set_wayland_titlebar(true);
|
||||
window->set_settings_session_active(true);
|
||||
window->set_hardware_codec_available(false);
|
||||
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_custom_titlebar());
|
||||
assert(window->get_wayland_titlebar());
|
||||
assert(window->get_settings_session_active());
|
||||
assert(!window->get_hardware_codec_available());
|
||||
assert(window->get_portable_service_settings_visible());
|
||||
if (const char *snapshot_path =
|
||||
std::getenv("CROSSDESK_MAIN_UI_SNAPSHOT")) {
|
||||
window->show();
|
||||
const bool snapshot_written =
|
||||
WriteWindowSnapshot(window->window(), snapshot_path);
|
||||
window->hide();
|
||||
if (!snapshot_written) {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
if (const char *snapshot_path =
|
||||
std::getenv("CROSSDESK_MAIN_INACTIVE_UI_SNAPSHOT")) {
|
||||
window->set_window_active(false);
|
||||
window->show();
|
||||
const bool snapshot_written =
|
||||
WriteWindowSnapshot(window->window(), snapshot_path);
|
||||
window->hide();
|
||||
window->set_window_active(true);
|
||||
if (!snapshot_written) {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
window->set_settings_open(true);
|
||||
window->set_about_open(true);
|
||||
assert(window->get_settings_open());
|
||||
assert(window->get_about_open());
|
||||
if (const char *snapshot_path =
|
||||
std::getenv("CROSSDESK_SETTINGS_UI_SNAPSHOT")) {
|
||||
window->set_about_open(false);
|
||||
window->set_connection_dialog_open(false);
|
||||
window->show();
|
||||
const bool snapshot_written =
|
||||
WriteWindowSnapshot(window->window(), snapshot_path);
|
||||
window->hide();
|
||||
if (!snapshot_written) {
|
||||
return 6;
|
||||
}
|
||||
window->set_connection_dialog_open(true);
|
||||
window->set_about_open(true);
|
||||
}
|
||||
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}));
|
||||
stream->set_custom_titlebar(true);
|
||||
stream->set_native_video_enabled(true);
|
||||
stream->set_window_maximized(true);
|
||||
assert(stream->get_tabs()->row_count() == 1);
|
||||
assert(stream->get_custom_titlebar());
|
||||
assert(stream->get_native_video_enabled());
|
||||
assert(stream->get_window_maximized());
|
||||
// Keep snapshots on the CPU-rendered UI path; the native Metal video view
|
||||
// is intentionally outside Slint's component snapshot.
|
||||
stream->set_native_video_enabled(false);
|
||||
if (const char *snapshot_path =
|
||||
std::getenv("CROSSDESK_STREAM_UI_SNAPSHOT")) {
|
||||
stream->set_window_maximized(false);
|
||||
stream->show();
|
||||
const bool snapshot_written =
|
||||
WriteWindowSnapshot(stream->window(), snapshot_path);
|
||||
stream->hide();
|
||||
stream->set_window_maximized(true);
|
||||
if (!snapshot_written) {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
if (const char *snapshot_path =
|
||||
std::getenv("CROSSDESK_STREAM_INACTIVE_UI_SNAPSHOT")) {
|
||||
stream->set_window_maximized(false);
|
||||
stream->set_window_active(false);
|
||||
stream->show();
|
||||
const bool snapshot_written =
|
||||
WriteWindowSnapshot(stream->window(), snapshot_path);
|
||||
stream->hide();
|
||||
stream->set_window_active(true);
|
||||
stream->set_window_maximized(true);
|
||||
if (!snapshot_written) {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
bool maximize_requested = false;
|
||||
stream->on_toggle_maximize_stream_window(
|
||||
[&] { maximize_requested = true; });
|
||||
stream->invoke_toggle_maximize_stream_window();
|
||||
assert(maximize_requested);
|
||||
|
||||
bool keyboard_focus_changed = false;
|
||||
bool keyboard_input_received = false;
|
||||
stream->on_keyboard_focus_changed(
|
||||
[&](bool focused) { keyboard_focus_changed = focused; });
|
||||
stream->on_key_input(
|
||||
[&](slint::SharedString text, bool pressed, bool, bool, bool, bool) {
|
||||
keyboard_input_received = std::string(text) == "a" && pressed;
|
||||
});
|
||||
stream->window().set_size(
|
||||
slint::LogicalSize(slint::Size<float>{1280.0f, 720.0f}));
|
||||
stream->window().dispatch_pointer_press_event(
|
||||
slint::LogicalPosition(slint::Point<float>{640.0f, 360.0f}),
|
||||
slint::PointerEventButton::Left);
|
||||
stream->window().dispatch_pointer_release_event(
|
||||
slint::LogicalPosition(slint::Point<float>{640.0f, 360.0f}),
|
||||
slint::PointerEventButton::Left);
|
||||
stream->window().dispatch_key_press_event("a");
|
||||
assert(keyboard_focus_changed);
|
||||
assert(keyboard_input_received);
|
||||
|
||||
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);
|
||||
|
||||
if (capture_options.enabled) {
|
||||
return RunCaptureMode(capture_options, window, stream, server);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#include "version_checker.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
bool ExpectEqual(const std::string& name, bool actual, bool expected) {
|
||||
if (actual == expected) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << name << " mismatch\n"
|
||||
<< " expected: " << expected << "\n"
|
||||
<< " actual: " << actual << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
bool ok = true;
|
||||
|
||||
ok &= ExpectEqual("new patch-before-date is newer",
|
||||
crossdesk::IsNewerVersionWithMetadata(
|
||||
"v1.3.5-20260529", "v1.3.5-1-20260529", "", -1),
|
||||
true);
|
||||
ok &= ExpectEqual("larger patch wins regardless of date",
|
||||
crossdesk::IsNewerVersionWithMetadata(
|
||||
"v1.3.5-2-20260530", "v1.3.5-3-20260529", "", -1),
|
||||
true);
|
||||
ok &= ExpectEqual("smaller patch loses regardless of date",
|
||||
crossdesk::IsNewerVersionWithMetadata(
|
||||
"v1.3.5-3-20260529", "v1.3.5-2-20260530", "", -1),
|
||||
false);
|
||||
ok &= ExpectEqual("old date-before-patch remains supported",
|
||||
crossdesk::IsNewerVersionWithMetadata(
|
||||
"v1.3.5-20260529-1", "v1.3.5-20260529-2", "", -1),
|
||||
true);
|
||||
ok &= ExpectEqual("metadata patch overrides date",
|
||||
crossdesk::IsNewerVersionWithMetadata(
|
||||
"v1.3.5-9-20260530", "v1.3.5", "2026-05-31", 10),
|
||||
true);
|
||||
ok &= ExpectEqual("date alone does not update same version",
|
||||
crossdesk::IsNewerVersionWithMetadata(
|
||||
"v1.3.5-20260529", "v1.3.5-20260530", "", -1),
|
||||
false);
|
||||
ok &= ExpectEqual("numeric version still wins",
|
||||
crossdesk::IsNewerVersionWithMetadata(
|
||||
"v1.3.5-9-20260529", "v1.3.6-1-20260529", "", -1),
|
||||
true);
|
||||
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
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 / "deps/submodules/minirtc/src/transport/ice_transport_controller.cpp")) {
|
||||
return current;
|
||||
}
|
||||
current = current.parent_path();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
std::string ExtractSection(const std::string& source, const std::string& begin,
|
||||
const std::string& end) {
|
||||
const size_t begin_pos = source.find(begin);
|
||||
if (begin_pos == std::string::npos) {
|
||||
return {};
|
||||
}
|
||||
const size_t end_pos = source.find(end, begin_pos + begin.size());
|
||||
if (end_pos == std::string::npos) {
|
||||
return {};
|
||||
}
|
||||
return source.substr(begin_pos, end_pos - begin_pos);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (value.find(unexpected) == std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << name << " contains unsafe text: " << unexpected << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const std::filesystem::path repo_root = FindRepoRoot();
|
||||
if (repo_root.empty()) {
|
||||
std::cerr << "failed to locate repository root\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string controller = ReadFile(
|
||||
repo_root /
|
||||
"deps/submodules/minirtc/src/transport/ice_transport_controller.cpp");
|
||||
const std::string destroy = ExtractSection(
|
||||
controller, "void IceTransportController::Destroy()",
|
||||
"uint32_t IceTransportController::AddVideoSendChannel");
|
||||
const std::string send_video = ExtractSection(
|
||||
controller, "int IceTransportController::SendVideo(",
|
||||
"void IceTransportController::MaybeDegradeResolutionOnEncodeTime");
|
||||
const std::string on_video_encoded = ExtractSection(
|
||||
controller, "int IceTransportController::OnVideoEncoded(",
|
||||
"int IceTransportController::SendAudio(");
|
||||
|
||||
bool ok = true;
|
||||
ok &= ExpectContains("Destroy", destroy,
|
||||
"std::unique_lock lock(stream_senders_mutex_);");
|
||||
ok &= ExpectContains("Destroy", destroy,
|
||||
"senders.swap(stream_senders_);");
|
||||
ok &= ExpectContains("Destroy", destroy,
|
||||
"codecs.push_back(std::move(context->codec));");
|
||||
ok &= ExpectContains("Destroy", destroy, "senders.clear();");
|
||||
ok &= ExpectContains("Destroy", destroy, "codecs.clear();");
|
||||
ok &= ExpectNotContains("Destroy", destroy,
|
||||
"std::shared_lock lock(stream_senders_mutex_);");
|
||||
|
||||
ok &= ExpectContains("SendVideo", send_video,
|
||||
"std::weak_ptr<IceTransportController> weak_self");
|
||||
ok &= ExpectContains("SendVideo", send_video,
|
||||
"std::weak_ptr<StreamContext> weak_context");
|
||||
ok &= ExpectContains(
|
||||
"SendVideo", send_video,
|
||||
"[weak_self, weak_context, channel_name, queue_delay_ms");
|
||||
ok &= ExpectContains("SendVideo", send_video,
|
||||
"return self->OnVideoEncoded(");
|
||||
ok &= ExpectNotContains("SendVideo", send_video,
|
||||
"[this, channel_name, context");
|
||||
|
||||
ok &= ExpectContains("OnVideoEncoded", on_video_encoded,
|
||||
"if (!is_running_.load())");
|
||||
ok &= ExpectContains("OnVideoEncoded", on_video_encoded,
|
||||
"std::shared_lock lock(stream_senders_mutex_);");
|
||||
ok &= ExpectContains("OnVideoEncoded", on_video_encoded,
|
||||
"it->second != context");
|
||||
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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 / "apps/desktop/resources/windows/crossdesk.rc")) {
|
||||
return current;
|
||||
}
|
||||
current = current.parent_path();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
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) {
|
||||
if (value.find(unexpected) == std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << name << " contains unexpected text: " << unexpected << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
bool ExpectActivationContext(const std::filesystem::path& manifest_path) {
|
||||
ACTCTXW context = {};
|
||||
context.cbSize = sizeof(context);
|
||||
std::wstring source = manifest_path.wstring();
|
||||
context.lpSource = source.c_str();
|
||||
|
||||
HANDLE activation_context = CreateActCtxW(&context);
|
||||
if (activation_context == INVALID_HANDLE_VALUE) {
|
||||
std::cerr << "CreateActCtxW failed for " << manifest_path.string()
|
||||
<< ", error=" << GetLastError() << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
ReleaseActCtx(activation_context);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const std::filesystem::path repo_root = FindRepoRoot();
|
||||
if (repo_root.empty()) {
|
||||
std::cerr << "failed to locate repository root\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string rc = ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk.rc");
|
||||
const std::string portable_rc =
|
||||
ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk_portable.rc");
|
||||
const std::string service_rc =
|
||||
ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk_service.rc");
|
||||
const std::string session_helper_rc =
|
||||
ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk_session_helper.rc");
|
||||
const std::string wgc_plugin_rc =
|
||||
ReadFile(repo_root / "apps/desktop/resources/windows/wgc_plugin.rc");
|
||||
const std::string version_info =
|
||||
ReadFile(repo_root / "apps/desktop/resources/windows/version_info.rcinc");
|
||||
const std::string manifest =
|
||||
ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk.manifest");
|
||||
const std::string debug_manifest =
|
||||
ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk_debug.manifest");
|
||||
const std::string portable_manifest =
|
||||
ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk_portable.manifest");
|
||||
const std::string targets = ReadFile(repo_root / "apps/desktop/xmake/targets.lua");
|
||||
|
||||
bool ok = true;
|
||||
ok &= ExpectContains("crossdesk.rc", rc, "crossdesk.manifest");
|
||||
ok &= ExpectContains("crossdesk.rc", rc, "crossdesk_debug.manifest");
|
||||
ok &= ExpectContains("crossdesk.rc", rc, "CROSSDESK_DEBUG");
|
||||
ok &= ExpectContains("crossdesk.rc", rc, "RT_MANIFEST");
|
||||
ok &= ExpectContains("crossdesk_portable.rc", portable_rc,
|
||||
"crossdesk_portable.manifest");
|
||||
ok &= ExpectContains("crossdesk_portable.rc", portable_rc, "RT_MANIFEST");
|
||||
ok &= ExpectContains("crossdesk.rc", rc, "version_info.rcinc");
|
||||
ok &= ExpectContains("crossdesk_portable.rc", portable_rc,
|
||||
"version_info.rcinc");
|
||||
ok &= ExpectContains("crossdesk_service.rc", service_rc,
|
||||
"crossdesk_service.exe");
|
||||
ok &= ExpectContains("crossdesk_session_helper.rc", session_helper_rc,
|
||||
"crossdesk_session_helper.exe");
|
||||
ok &= ExpectContains("crossdesk_session_helper.rc", session_helper_rc,
|
||||
"CROSSDESK_PORTABLE");
|
||||
ok &= ExpectContains("crossdesk_session_helper.rc", session_helper_rc,
|
||||
"crossdesk_portable.manifest");
|
||||
ok &= ExpectContains("wgc_plugin.rc", wgc_plugin_rc, "wgc_plugin.dll");
|
||||
ok &= ExpectContains("version_info.rcinc", version_info, "ProductName");
|
||||
ok &= ExpectContains("version_info.rcinc", version_info,
|
||||
"ProductVersion");
|
||||
ok &= ExpectContains("version_info.rcinc", version_info,
|
||||
"OriginalFilename");
|
||||
ok &= ExpectContains("apps/desktop/xmake/targets.lua", targets,
|
||||
"apps/desktop/resources/windows/crossdesk_portable.rc");
|
||||
ok &= ExpectContains("apps/desktop/xmake/targets.lua", targets,
|
||||
"apps/desktop/resources/windows/crossdesk_service.rc");
|
||||
ok &= ExpectContains("apps/desktop/xmake/targets.lua", targets,
|
||||
"apps/desktop/resources/windows/crossdesk_session_helper.rc");
|
||||
ok &= ExpectContains("apps/desktop/xmake/targets.lua", targets,
|
||||
"apps/desktop/resources/windows/wgc_plugin.rc");
|
||||
ok &= ExpectContains("apps/desktop/xmake/targets.lua", targets, "CROSSDESK_PORTABLE");
|
||||
ok &= ExpectContains("crossdesk.manifest", manifest,
|
||||
"level=\"requireAdministrator\"");
|
||||
ok &= ExpectContains("crossdesk.manifest", manifest,
|
||||
"http://schemas.microsoft.com/SMI/2005/WindowsSettings");
|
||||
ok &= ExpectContains("crossdesk.manifest", manifest,
|
||||
"http://schemas.microsoft.com/SMI/2016/WindowsSettings");
|
||||
ok &= ExpectNotContains("crossdesk.manifest", manifest,
|
||||
"processorArchitecture=\"*\"");
|
||||
ok &= ExpectContains("crossdesk_debug.manifest", debug_manifest,
|
||||
"level=\"asInvoker\"");
|
||||
ok &= ExpectContains("crossdesk_debug.manifest", debug_manifest,
|
||||
"http://schemas.microsoft.com/SMI/2005/WindowsSettings");
|
||||
ok &= ExpectContains("crossdesk_debug.manifest", debug_manifest,
|
||||
"http://schemas.microsoft.com/SMI/2016/WindowsSettings");
|
||||
ok &= ExpectNotContains("crossdesk_debug.manifest", debug_manifest,
|
||||
"processorArchitecture=\"*\"");
|
||||
ok &= ExpectContains("crossdesk_portable.manifest", portable_manifest,
|
||||
"level=\"asInvoker\"");
|
||||
ok &= ExpectNotContains("crossdesk_portable.manifest", portable_manifest,
|
||||
"level=\"requireAdministrator\"");
|
||||
ok &= ExpectContains("crossdesk_portable.manifest", portable_manifest,
|
||||
"http://schemas.microsoft.com/SMI/2005/WindowsSettings");
|
||||
ok &= ExpectContains("crossdesk_portable.manifest", portable_manifest,
|
||||
"http://schemas.microsoft.com/SMI/2016/WindowsSettings");
|
||||
ok &= ExpectNotContains("crossdesk_portable.manifest", portable_manifest,
|
||||
"processorArchitecture=\"*\"");
|
||||
#ifdef _WIN32
|
||||
ok &=
|
||||
ExpectActivationContext(repo_root / "apps/desktop/resources/windows/crossdesk.manifest");
|
||||
ok &= ExpectActivationContext(repo_root /
|
||||
"apps/desktop/resources/windows/crossdesk_debug.manifest");
|
||||
ok &= ExpectActivationContext(repo_root /
|
||||
"apps/desktop/resources/windows/crossdesk_portable.manifest");
|
||||
#endif
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
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 /
|
||||
"apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp")) {
|
||||
return current;
|
||||
}
|
||||
current = current.parent_path();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
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) {
|
||||
if (value.find(expected) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << name << " missing expected text: " << expected << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const std::filesystem::path repo_root = FindRepoRoot();
|
||||
if (repo_root.empty()) {
|
||||
std::cerr << "failed to locate repository root\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string mouse_controller = ReadFile(
|
||||
repo_root / "apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp");
|
||||
|
||||
bool ok = true;
|
||||
ok &= ExpectContains("mouse_controller.cpp", mouse_controller,
|
||||
"INPUT ip = {0};");
|
||||
ok &= ExpectContains("mouse_controller.cpp", mouse_controller,
|
||||
"SetCursorPos failed");
|
||||
ok &= ExpectContains("mouse_controller.cpp", mouse_controller,
|
||||
"SendInput failed for mouse");
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "interactive_state.h"
|
||||
|
||||
namespace {
|
||||
|
||||
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 /
|
||||
"apps/desktop/src/platform/windows/service/service_host.cpp")) {
|
||||
return current;
|
||||
}
|
||||
current = current.parent_path();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
if (value.find(unexpected) == std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << name << " contains unexpected text: " << unexpected << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExpectTrue(const char *name, bool value) {
|
||||
if (value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << name << " expected true\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const std::filesystem::path repo_root = FindRepoRoot();
|
||||
if (repo_root.empty()) {
|
||||
std::cerr << "failed to locate repository root\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string stream_window =
|
||||
ReadFile(repo_root / "apps/desktop/src/gui/ui/stream_window.slint");
|
||||
const std::string gui_application =
|
||||
ReadFile(repo_root / "apps/desktop/src/gui/application/gui_application.cpp");
|
||||
const std::string windows_service_runtime =
|
||||
ReadFile(repo_root / "apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp");
|
||||
const std::string runtime_state_h =
|
||||
ReadFile(repo_root / "apps/desktop/src/gui/runtime/runtime_state.h");
|
||||
const std::string service_host =
|
||||
ReadFile(repo_root / "apps/desktop/src/platform/windows/service/service_host.cpp");
|
||||
const std::string service_host_h =
|
||||
ReadFile(repo_root / "apps/desktop/src/platform/windows/service/service_host.h");
|
||||
const std::string session_helper =
|
||||
ReadFile(repo_root / "apps/desktop/src/platform/windows/service/session_helper_main.cpp");
|
||||
|
||||
bool ok = true;
|
||||
ok &= ExpectTrue(
|
||||
"secure desktop input routing",
|
||||
crossdesk::IsSecureDesktopInteractionRequired("secure-desktop"));
|
||||
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,
|
||||
"optimistic_windows_secure_desktop_until_tick_");
|
||||
ok &= ExpectContains("windows_service_runtime.cpp", windows_service_runtime,
|
||||
"kWindowsServiceSasSecureDesktopGraceMs");
|
||||
ok &= ExpectContains("windows_service_runtime.cpp", windows_service_runtime,
|
||||
"status->sas_secure_desktop_grace_active");
|
||||
ok &=
|
||||
ExpectContains("windows_service_runtime.cpp", windows_service_runtime,
|
||||
"json.value(\"sas_secure_desktop_grace_active\", false)");
|
||||
ok &= ExpectContains("windows_service_runtime.cpp", windows_service_runtime,
|
||||
"status.sas_secure_desktop_grace_active");
|
||||
ok &= ExpectContains("windows_service_runtime.cpp", windows_service_runtime,
|
||||
"local_interactive_stage_ = \"secure-desktop\"");
|
||||
ok &= ExpectContains("service_host.h", service_host_h,
|
||||
"sas_secure_desktop_until_tick_");
|
||||
ok &= ExpectContains("service_host.h", service_host_h,
|
||||
"sas_secure_desktop_seen_");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"kSasSecureDesktopGraceMs");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"IsSasSecureDesktopGraceActiveLocked()");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"UpdateSasSecureDesktopGraceLocked("
|
||||
"session_helper_report_interactive_stage_)");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"sas_secure_desktop_seen_ = true");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"sas_secure_desktop_until_tick_ = 0");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"sas_secure_desktop_until_tick_ =");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"now + kSasSecureDesktopGraceMs");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"\\\"sas_secure_desktop_grace_active\\\"");
|
||||
ok &=
|
||||
ExpectContains("service_host.cpp", service_host,
|
||||
"raw_interactive_stage = ResolveInteractiveStageLocked()");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"kSessionHelperStatePollMs = 1000");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"EVENT_SYSTEM_DESKTOPSWITCH");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"SetWinEventHook(");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"MsgWaitForMultipleObjects");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"WaitForSessionHelperStateChange(stop_event, "
|
||||
"desktop_switch_event)");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"inaccessible_secure_input_desktop");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"desktop_info.error_code == ERROR_ACCESS_DENIED");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"secure_desktop_active = input_desktop_is_winlogon ||");
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
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 /
|
||||
"apps/desktop/src/platform/windows/service/service_host.cpp")) {
|
||||
return current;
|
||||
}
|
||||
current = current.parent_path();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
if (value.find(unexpected) == std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << name << " contains unexpected text: " << unexpected << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const std::filesystem::path repo_root = FindRepoRoot();
|
||||
if (repo_root.empty()) {
|
||||
std::cerr << "failed to locate repository root\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string service_host =
|
||||
ReadFile(repo_root / "apps/desktop/src/platform/windows/service/service_host.cpp");
|
||||
const std::string service_host_h =
|
||||
ReadFile(repo_root / "apps/desktop/src/platform/windows/service/service_host.h");
|
||||
const std::string session_helper =
|
||||
ReadFile(repo_root / "apps/desktop/src/platform/windows/service/session_helper_main.cpp");
|
||||
const std::string targets = ReadFile(repo_root / "apps/desktop/xmake/targets.lua");
|
||||
const std::string interactive_state =
|
||||
ReadFile(repo_root / "apps/desktop/src/platform/windows/service/interactive_state.h");
|
||||
const std::string gui_input_sources =
|
||||
ReadFile(repo_root / "apps/desktop/src/gui/runtime/peer_data_callbacks.cpp") + "\n" +
|
||||
ReadFile(repo_root / "apps/desktop/src/gui/features/input/keyboard_controller.cpp");
|
||||
const std::string windows_service_runtime =
|
||||
ReadFile(repo_root / "apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp");
|
||||
const std::string screen_capturer_h =
|
||||
ReadFile(repo_root / "apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.h");
|
||||
const std::string screen_capturer_cpp = ReadFile(
|
||||
repo_root / "apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp");
|
||||
|
||||
bool ok = true;
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"ParseSecureDesktopMouseIpcCommand");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"BuildSecureInputHelperMouseCommand");
|
||||
ok &= ExpectContains("targets.lua", targets,
|
||||
"target(\"crossdesk_session_helper\")");
|
||||
ok &= ExpectContains("targets.lua", targets,
|
||||
"add_files(crossdesk_windows_resource)");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"EnablePerMonitorDpiAwareness");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"SetProcessDpiAwarenessContext(\n"
|
||||
" DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"EnablePerMonitorDpiAwareness();\n\n"
|
||||
" InitializeHelperLogger();");
|
||||
ok &= ExpectContains(
|
||||
"service_host.cpp", service_host,
|
||||
"const ULONGLONG deadline_tick = GetTickCount64() + timeout_ms");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"while (GetTickCount64() <= deadline_tick)");
|
||||
ok &= ExpectNotContains("service_host.cpp", service_host,
|
||||
"constexpr int kPipeConnectRetryCount = 3");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"BuildSecureInputHelperKeyboardCommand(");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"const std::string& interactive_stage");
|
||||
ok &= ExpectContains(
|
||||
"service_host.h", service_host_h,
|
||||
"bool LaunchSecureInputHelper(DWORD session_id,\n"
|
||||
" const std::string& interactive_stage,\n"
|
||||
" const std::string& interactive_desktop)");
|
||||
ok &= ExpectContains("service_host.h", service_host_h,
|
||||
"std::string secure_input_helper_interactive_stage_");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"SecureInputHelperDesktopForStage");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"IsConsentUiRunningInSession");
|
||||
ok &= ExpectContains("service_host.cpp", service_host, "L\"Consent.exe\"");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"IsConsentUiRunningInCurrentSession");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"L\"Consent.exe\"");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"desktop_info.available && consent_ui_visible");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"session_helper_report_input_desktop_available_ &&");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"session_helper_report_consent_ui_visible_");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"secure_input_helper_interactive_desktop_");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"SecureInputHelperDesktopForStage("
|
||||
"interactive_stage, interactive_desktop)");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"return L\"winsta0\\\\\" + interactive_desktop_w");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"return L\"winsta0\\\\Winlogon\"");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"return L\"winsta0\\\\default\"");
|
||||
ok &= ExpectContains(
|
||||
"service_host.cpp", service_host,
|
||||
"secure_input_helper_interactive_stage_ == interactive_stage");
|
||||
ok &= ExpectContains(
|
||||
"service_host.cpp", service_host,
|
||||
"secure_input_helper_interactive_stage_ = interactive_stage");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"secure_input_helper_interactive_stage_.clear()");
|
||||
ok &= ExpectContains(
|
||||
"service_host.cpp", service_host,
|
||||
"LaunchSecureInputHelper(target_session_id, interactive_stage,\n"
|
||||
" interactive_desktop)");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"\\\"secure_input_helper_stage\\\":\\\"");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"session_helper_report_interactive_stage_");
|
||||
ok &= ExpectContains("service_host.cpp", service_host,
|
||||
"return SendSecureDesktopMouseInput");
|
||||
ok &= ExpectContains("windows_service_runtime.cpp", windows_service_runtime,
|
||||
"constexpr DWORD kWindowsServiceQueryTimeoutMs = 500");
|
||||
ok &=
|
||||
ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp,
|
||||
"constexpr DWORD kSecureDesktopStatusPipeTimeoutMs = 500");
|
||||
ok &= ExpectContains("windows_service_runtime.cpp", windows_service_runtime,
|
||||
"IsTransientWindowsServiceStatusError(status.error)");
|
||||
ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp,
|
||||
"IsTransientWindowsServiceStatusError(status.error)");
|
||||
ok &= ExpectContains("windows_service_runtime.cpp", windows_service_runtime,
|
||||
"Local Windows service temporarily unavailable");
|
||||
ok &= ExpectContains(
|
||||
"screen_capturer_win.cpp", screen_capturer_cpp,
|
||||
"Windows capturer secure desktop service temporarily unavailable");
|
||||
ok &= ExpectContains(
|
||||
"screen_capturer_win.cpp", screen_capturer_cpp,
|
||||
"Windows capturer secure desktop transient frame query failed");
|
||||
ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp,
|
||||
"if (transient_error) {\n"
|
||||
" LOG_INFO(");
|
||||
ok &= ExpectContains("gui_input_sources.cpp", gui_input_sources,
|
||||
"IsTransientSecureDesktopInputFailure");
|
||||
ok &= ExpectContains("gui_input_sources.cpp", gui_input_sources,
|
||||
"Secure desktop keyboard injection transient failure");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"MOUSEEVENTF_VIRTUALDESK");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"std::vector<INPUT> inputs");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"SendInput(static_cast<UINT>(inputs.size())");
|
||||
ok &= ExpectNotContains("session_helper_main.cpp", session_helper,
|
||||
"SetCursorPos(request.x, request.y)");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"NormalizeAbsoluteMouseCoordinate");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"EnsureThreadInteractiveDesktop");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"OpenInputDesktop");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"DesktopNameForInteractiveStage");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"interactive_stage == \"credential-ui\"");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"return L\"Winlogon\"");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"interactive_stage == \"lock-screen\"");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"return L\"Default\"");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"EnsureThreadInteractiveDesktopForStage");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"switch_interactive_desktop_failed");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"Json BuildInputFailureJson");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"json[\"target_desktop\"]");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"json[\"current_desktop\"]");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"json[\"stage\"]");
|
||||
ok &= ExpectContains(
|
||||
"session_helper_main.cpp", session_helper,
|
||||
"ParseSecureInputKeyboardCommand(command, &key_code, &is_down, "
|
||||
"&scan_code,\n"
|
||||
" &extended, &interactive_stage,\n"
|
||||
" &interactive_desktop)");
|
||||
ok &= ExpectContains(
|
||||
"session_helper_main.cpp", session_helper,
|
||||
"InjectKeyboardInput(key_code, is_down, scan_code, extended,\n"
|
||||
" interactive_stage, interactive_desktop)");
|
||||
ok &= ExpectContains("session_helper_main.cpp", session_helper,
|
||||
"InjectMouseInput(mouse_request)");
|
||||
ok &=
|
||||
ExpectNotContains("session_helper_main.cpp", session_helper,
|
||||
"EnsureThreadDesktop(L\"Winlogon\", &secure_desktop)");
|
||||
ok &= ExpectContains("service_host.cpp", service_host, "winsta0\\\\default");
|
||||
ok &= ExpectNotContains(
|
||||
"service_host.cpp", service_host,
|
||||
"startup_info.lpDesktop = const_cast<LPWSTR>(L\"winsta0\\\\Winlogon\")");
|
||||
ok &= ExpectContains("interactive_state.h", interactive_state,
|
||||
"interactive_stage == \"lock-screen\"");
|
||||
ok &= ExpectContains("gui_input_sources.cpp", gui_input_sources,
|
||||
"RemoteAction remote_action{};");
|
||||
ok &= ExpectContains("windows_service_runtime.cpp", windows_service_runtime,
|
||||
"previous_secure_desktop_interaction");
|
||||
ok &= ExpectNotContains(
|
||||
"gui_input_sources.cpp", gui_input_sources,
|
||||
"runtime->local_service_available_ &&\n"
|
||||
" "
|
||||
"IsSecureDesktopInteractionRequired(runtime->local_interactive_stage_)");
|
||||
ok &= ExpectContains("screen_capturer_win.h", screen_capturer_h,
|
||||
"std::string secure_shared_stage_;");
|
||||
ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp,
|
||||
"const std::string& stage");
|
||||
ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp,
|
||||
"secure_shared_stage_ == stage");
|
||||
ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp,
|
||||
"secure_shared_stage_ = stage");
|
||||
ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp,
|
||||
"secure_shared_stage_.clear()");
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user