mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-20 11:06:46 +08:00
[feat] add desktop preview settings and platform placeholders
This commit is contained in:
@@ -1037,6 +1037,9 @@ void GuiApplication::InitializeLogger() { InitLogger(exec_log_path_); }
|
||||
|
||||
void GuiApplication::InitializeSettings() {
|
||||
settings_.Load();
|
||||
if (thumbnail_->SetSavePreviews(config_center_->IsSaveRemotePreviews()) != 0) {
|
||||
LOG_ERROR("Failed to clear saved remote previews");
|
||||
}
|
||||
settings_.LoadRecentConnectionAliases();
|
||||
localization_language_index_ =
|
||||
localization::detail::ClampLanguageIndex(language_button_value_);
|
||||
@@ -1240,6 +1243,7 @@ void GuiApplication::ResetSettingsUi() {
|
||||
ui_->main->set_self_hosted_enabled(enable_self_hosted_);
|
||||
ui_->main->set_autostart_enabled(enable_autostart_);
|
||||
ui_->main->set_daemon_enabled(enable_daemon_);
|
||||
ui_->main->set_save_remote_previews(config_center_->IsSaveRemotePreviews());
|
||||
ui_->main->set_privacy_on_connect_enabled(
|
||||
config_center_->IsEnablePrivacyScreen());
|
||||
#ifdef _WIN32
|
||||
@@ -2334,9 +2338,11 @@ void GuiApplication::SyncMainWindow() {
|
||||
}
|
||||
|
||||
std::ostringstream signature_builder;
|
||||
signature_builder << config_center_->IsSaveRemotePreviews() << '\n';
|
||||
for (const auto& [key, connection] : recent_connections_) {
|
||||
signature_builder << key << '\0' << connection.remote_id << '\0'
|
||||
<< connection.remote_host_name << '\0'
|
||||
<< static_cast<int>(connection.platform) << '\0'
|
||||
<< settings_.RecentConnectionDisplayName(connection)
|
||||
<< '\0'
|
||||
<< device_presence_cache_.IsOnline(connection.remote_id)
|
||||
@@ -2351,22 +2357,27 @@ void GuiApplication::SyncMainWindow() {
|
||||
std::vector<ui::RecentConnection> model;
|
||||
model.reserve(recent_connections_.size());
|
||||
for (const auto& [_, connection] : recent_connections_) {
|
||||
ui::RecentConnection item;
|
||||
// Slint-generated scalar fields have no defaults. With no image,
|
||||
// has_thumbnail must remain false so the remote platform is rendered.
|
||||
ui::RecentConnection item{};
|
||||
item.remote_id = UiText(connection.remote_id);
|
||||
item.host_name = UiText(connection.remote_host_name);
|
||||
item.display_name =
|
||||
UiText(settings_.RecentConnectionDisplayName(connection));
|
||||
item.online = device_presence_cache_.IsOnline(connection.remote_id);
|
||||
item.platform = UiText(HostPlatformName(connection.platform));
|
||||
std::vector<unsigned char> rgba;
|
||||
int image_width = 0;
|
||||
int image_height = 0;
|
||||
if (!connection.image_path.empty() &&
|
||||
if (config_center_->IsSaveRemotePreviews() &&
|
||||
!connection.image_path.empty() &&
|
||||
thumbnail_->DecodeImage(connection.image_path, &rgba, &image_width,
|
||||
&image_height)) {
|
||||
slint::SharedPixelBuffer<slint::Rgba8Pixel> pixels(image_width,
|
||||
image_height);
|
||||
std::memcpy(pixels.begin(), rgba.data(), rgba.size());
|
||||
item.thumbnail = slint::Image(std::move(pixels));
|
||||
item.has_thumbnail = true;
|
||||
}
|
||||
model.push_back(std::move(item));
|
||||
}
|
||||
@@ -3259,6 +3270,13 @@ void GuiApplication::SaveSettingsFromUi() {
|
||||
main->set_force_relay_enabled(config_center_->IsForceRelay());
|
||||
config_center_->SetAutostart(enable_autostart_);
|
||||
config_center_->SetDaemon(enable_daemon_);
|
||||
if (config_center_->SetSaveRemotePreviews(main->get_save_remote_previews()) != 0) {
|
||||
LOG_ERROR("Failed to save remote preview preference");
|
||||
}
|
||||
if (thumbnail_->SetSavePreviews(config_center_->IsSaveRemotePreviews()) != 0) {
|
||||
LOG_ERROR("Failed to clear saved remote previews");
|
||||
}
|
||||
reload_recent_connections_ = true;
|
||||
#ifdef _WIN32
|
||||
if (!HasActiveSession()) {
|
||||
const auto capture_method = static_cast<ScreenCaptureMethod>(
|
||||
|
||||
@@ -79,6 +79,8 @@ struct TranslationRow {
|
||||
X(console_setting_files, u8"文件保存目录", "Received file directory", u8"Каталог получаемых файлов") \
|
||||
X(console_setting_autostart, u8"登录桌面时自启", "Start at desktop login", u8"Запуск при входе в систему") \
|
||||
X(console_setting_daemon, u8"守护进程(无头控制台中不生效)", "Daemon (inactive in headless console)", u8"Фоновый режим (не действует в консоли)") \
|
||||
X(save_remote_previews, u8"保存远程画面预览", "Save remote screen previews", u8"Сохранять превью экрана") \
|
||||
X(unknown_platform, u8"未知平台", "Unknown platform", u8"Неизвестная платформа") \
|
||||
X(privacy_screen, u8"隐私屏", "Privacy screen", u8"Приватный экран") \
|
||||
X(privacy_enable, u8"开启隐私屏", "Enable privacy screen", u8"Включить приватный экран") \
|
||||
X(privacy_disable, u8"关闭隐私屏", "Disable privacy screen", u8"Выключить приватный экран") \
|
||||
|
||||
@@ -236,7 +236,8 @@ void GuiRuntime::CloseRemoteSession(std::shared_ptr<RemoteSession> props) {
|
||||
std::shared_ptr<std::vector<unsigned char>> frame_snapshot;
|
||||
int video_width = 0;
|
||||
int video_height = 0;
|
||||
{
|
||||
const bool save_preview = config_center_->IsSaveRemotePreviews();
|
||||
if (save_preview) {
|
||||
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||
frame_snapshot = props->front_frame_;
|
||||
video_width = props->video_width_;
|
||||
@@ -249,7 +250,8 @@ void GuiRuntime::CloseRemoteSession(std::shared_ptr<RemoteSession> props) {
|
||||
}
|
||||
}
|
||||
auto* native_renderer = video_renderer_.get();
|
||||
if ((!frame_snapshot || frame_snapshot->empty()) && native_renderer) {
|
||||
if (save_preview && (!frame_snapshot || frame_snapshot->empty()) &&
|
||||
native_renderer) {
|
||||
auto native_snapshot = std::make_shared<std::vector<unsigned char>>();
|
||||
if (native_renderer->CopyLatestNv12(props->remote_id_,
|
||||
native_snapshot.get(), &video_width,
|
||||
@@ -269,9 +271,14 @@ void GuiRuntime::CloseRemoteSession(std::shared_ptr<RemoteSession> props) {
|
||||
std::unique_lock lock(remote_sessions_mutex_);
|
||||
peer = std::exchange(props->peer_, nullptr);
|
||||
}
|
||||
HostPlatform platform;
|
||||
{
|
||||
std::lock_guard lock(props->remote_version_mutex_);
|
||||
platform = props->recent_platform_;
|
||||
}
|
||||
const auto queued_at = std::chrono::steady_clock::now();
|
||||
session_cleanup_tasks_[props->remote_id_] = session_cleanup_queue_.PostTask(
|
||||
[props, peer, frame_snapshot, video_width, video_height,
|
||||
[props, peer, frame_snapshot, video_width, video_height, platform,
|
||||
thumbnail = thumbnail_, queued_at]() mutable {
|
||||
if (peer) {
|
||||
LOG_INFO("[{}] Background leave connection [{}]", props->local_id_,
|
||||
@@ -279,12 +286,16 @@ void GuiRuntime::CloseRemoteSession(std::shared_ptr<RemoteSession> props) {
|
||||
LeaveConnection(peer, props->remote_id_.c_str());
|
||||
DestroyPeer(&peer);
|
||||
}
|
||||
if (thumbnail && frame_snapshot && !frame_snapshot->empty() &&
|
||||
video_width > 0 && video_height > 0) {
|
||||
thumbnail->SaveToThumbnail(
|
||||
reinterpret_cast<char*>(frame_snapshot->data()), video_width,
|
||||
video_height, props->remote_id_, props->remote_host_name_,
|
||||
props->remember_password_ ? props->remote_password_ : "");
|
||||
if (thumbnail && props->was_connected_) {
|
||||
const char* nv12 = frame_snapshot && !frame_snapshot->empty()
|
||||
? reinterpret_cast<const char*>(frame_snapshot->data()) : nullptr;
|
||||
if (thumbnail->SaveToThumbnail(
|
||||
nv12, video_width, video_height, props->remote_id_,
|
||||
props->remote_host_name_,
|
||||
props->remember_password_ ? props->remote_password_ : "",
|
||||
platform) != 0) {
|
||||
LOG_ERROR("[{}] Failed to save recent connection", props->remote_id_);
|
||||
}
|
||||
}
|
||||
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - queued_at).count();
|
||||
|
||||
@@ -323,6 +323,7 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
std::lock_guard lock(props->remote_version_mutex_);
|
||||
props->remote_host_info_received_ = true;
|
||||
props->remote_platform_ = remote_action.i.platform;
|
||||
props->recent_platform_ = remote_action.i.platform;
|
||||
props->remote_app_version_ = IsValidAppVersion(remote_action.i.app_version)
|
||||
? remote_action.i.app_version : "";
|
||||
}
|
||||
|
||||
@@ -295,6 +295,7 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
||||
runtime->need_to_create_stream_window_ = true;
|
||||
}
|
||||
props->connection_established_ = true;
|
||||
props->was_connected_ = true;
|
||||
runtime->start_keyboard_capturer_ = true;
|
||||
break;
|
||||
}
|
||||
@@ -336,7 +337,8 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
||||
needs_native_snapshot =
|
||||
!props->thumbnail_frame_ || props->thumbnail_frame_->empty();
|
||||
}
|
||||
if (needs_native_snapshot) {
|
||||
if (needs_native_snapshot &&
|
||||
runtime->config_center_->IsSaveRemotePreviews()) {
|
||||
auto* native_renderer = runtime->video_renderer_.get();
|
||||
auto snapshot = std::make_shared<std::vector<unsigned char>>();
|
||||
if (native_renderer &&
|
||||
|
||||
@@ -87,6 +87,7 @@ struct RemoteSession {
|
||||
bool signal_connected_ = false;
|
||||
SignalStatus signal_status_ = SignalStatus::SignalClosed;
|
||||
bool connection_established_ = false;
|
||||
std::atomic<bool> was_connected_{false};
|
||||
bool rejoin_ = false;
|
||||
bool net_traffic_stats_button_pressed_ = false;
|
||||
bool enable_mouse_control_ = true;
|
||||
@@ -130,6 +131,8 @@ struct RemoteSession {
|
||||
bool remote_host_info_received_ = false;
|
||||
std::string remote_app_version_;
|
||||
HostPlatform remote_platform_ = HostPlatform::Unknown;
|
||||
// Retain history metadata after disconnect resets the live capabilities.
|
||||
HostPlatform recent_platform_ = HostPlatform::Unknown;
|
||||
bool remote_update_dismissed_ = false;
|
||||
bool remote_service_status_received_ = false;
|
||||
std::mutex video_settings_mutex_;
|
||||
|
||||
@@ -7,6 +7,8 @@ export struct RecentConnection {
|
||||
host-name: string,
|
||||
online: bool,
|
||||
thumbnail: image,
|
||||
has-thumbnail: bool,
|
||||
platform: string,
|
||||
}
|
||||
|
||||
export struct ReleaseNoteBlock {
|
||||
@@ -49,6 +51,8 @@ export global UiStrings {
|
||||
in-out property <string> self-hosted: "Self-hosted server";
|
||||
in-out property <string> autostart: "Start with system";
|
||||
in-out property <string> daemon: "Run in background";
|
||||
in-out property <string> save-remote-previews: "Save remote screen previews";
|
||||
in-out property <string> unknown-platform: "Unknown platform";
|
||||
in-out property <string> privacy-on-connect: "Privacy screen on connect";
|
||||
in-out property <string> file-save-path: "File save path";
|
||||
in-out property <string> default-desktop: "Desktop";
|
||||
@@ -306,6 +310,7 @@ export component MainWindow inherits Window {
|
||||
in-out property <bool> self-hosted-enabled: false;
|
||||
in-out property <bool> autostart-enabled: false;
|
||||
in-out property <bool> daemon-enabled: false;
|
||||
in-out property <bool> save-remote-previews: true;
|
||||
// Show the preference on every platform; only adapted hosts can change it.
|
||||
in property <bool> privacy-setting-available: false;
|
||||
in-out property <bool> privacy-on-connect-enabled: true;
|
||||
@@ -864,13 +869,37 @@ export component MainWindow inherits Window {
|
||||
private property <length> toolbar-width: 79px;
|
||||
private property <length> toolbar-right-padding: 3px;
|
||||
|
||||
Image {
|
||||
Rectangle {
|
||||
x: 7px;
|
||||
y: 7px;
|
||||
width: connection-card.content-width;
|
||||
height: 84px;
|
||||
source: connection.thumbnail;
|
||||
image-fit: cover;
|
||||
background: connection.platform == "windows" ? @linear-gradient(135deg, #1f61b3, #337ac4) :
|
||||
connection.platform == "macos" ? @linear-gradient(135deg, #6e4aad, #8c63c4) :
|
||||
connection.platform == "linux" ? @linear-gradient(135deg, #a14f14, #ba631f) :
|
||||
connection.platform == "ios" ? @linear-gradient(135deg, #146e66, #1f8278) :
|
||||
@linear-gradient(135deg, #596675, #737d8c);
|
||||
if connection.has-thumbnail: Image {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
source: connection.thumbnail;
|
||||
image-fit: cover;
|
||||
}
|
||||
if !connection.has-thumbnail: Text {
|
||||
x: 8px;
|
||||
width: parent.width - 16px;
|
||||
height: parent.height;
|
||||
text: connection.platform == "windows" ? "Windows" :
|
||||
connection.platform == "macos" ? "macOS" :
|
||||
connection.platform == "linux" ? "Linux" :
|
||||
connection.platform == "ios" ? "iOS" : UiStrings.unknown-platform;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
x: 7px;
|
||||
@@ -1513,6 +1542,11 @@ export component MainWindow inherits Window {
|
||||
Text { x: 8px; y: 3px; width: root.compact-language ? 145px : 212px; height: 24px; text: UiStrings.force-relay; color: root.turn-enabled ? #202124 : #8b8f95; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
|
||||
ImGuiCheckBox { x: root.compact-language ? 157px : 224px; y: 3px; checked <=> root.force-relay-enabled; enabled: root.turn-enabled && !root.settings-session-active; }
|
||||
}
|
||||
Rectangle {
|
||||
height: 30px;
|
||||
Text { x: 8px; y: 3px; width: root.compact-language ? 145px : 212px; height: 24px; text: UiStrings.save-remote-previews; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
|
||||
ImGuiCheckBox { x: root.compact-language ? 157px : 224px; y: 3px; checked <=> root.save-remote-previews; }
|
||||
}
|
||||
Rectangle {
|
||||
height: 30px;
|
||||
Text { x: 8px; y: 3px; width: root.compact-language ? 145px : 212px; height: 24px; text: UiStrings.privacy-on-connect; color: root.privacy-setting-available ? #202124 : #8b8f95; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
|
||||
|
||||
@@ -65,6 +65,8 @@ inline int ApplyMainWindowStrings(
|
||||
Text(localization::self_hosted_server_config[language]));
|
||||
strings.set_autostart(Text(localization::enable_autostart[language]));
|
||||
strings.set_daemon(Text(localization::enable_daemon[language]));
|
||||
strings.set_save_remote_previews(Text(localization::save_remote_previews[language]));
|
||||
strings.set_unknown_platform(Text(localization::unknown_platform[language]));
|
||||
strings.set_privacy_on_connect(Text(localization::privacy_on_connect[language]));
|
||||
strings.set_file_save_path(
|
||||
Text(localization::file_transfer_save_path[language]));
|
||||
|
||||
Reference in New Issue
Block a user