mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-04 04:52:39 +08:00
[feat] add privacy unlock controls and remote relocking
This commit is contained in:
@@ -1504,6 +1504,7 @@ void GuiApplication::ResetSettingsUi(bool gallery_window) {
|
||||
main->set_save_remote_previews(config_center_->IsSaveRemotePreviews());
|
||||
main->set_privacy_on_connect_enabled(
|
||||
config_center_->IsEnablePrivacyScreen());
|
||||
main->set_privacy_local_unlock_allowed(config_center_->IsPrivacyLocalUnlockAllowed());
|
||||
main->set_lock_on_disconnect_enabled(config_center_->IsLockOnDisconnect());
|
||||
#ifdef _WIN32
|
||||
main->set_capture_method_visible(true);
|
||||
@@ -2155,9 +2156,9 @@ void GuiApplication::BindStreamCallbacks() {
|
||||
SendReliableDataFrame(props->peer_, message.c_str(), message.size(),
|
||||
props->control_data_label_.c_str());
|
||||
});
|
||||
stream->on_toggle_privacy([this] {
|
||||
const auto send_privacy = [this](bool relock) {
|
||||
auto props = SelectedSession();
|
||||
if (!props || !props->peer_ || !props->control_mouse_ ||
|
||||
if (!props || !props->peer_ || (!relock && !props->control_mouse_) ||
|
||||
props->connection_status_.load() != ConnectionStatus::Connected) return;
|
||||
RemoteAction action{};
|
||||
action.type = ControlType::privacy_command;
|
||||
@@ -2168,9 +2169,12 @@ void GuiApplication::BindStreamCallbacks() {
|
||||
const auto controls = GetPrivacyControlState(
|
||||
status, props->privacy_status_received_, props->privacy_command_pending_,
|
||||
now - props->privacy_status_tick_, now - props->privacy_command_tick_);
|
||||
if (!props->privacy_desktop_available_ || !controls.can_toggle) return;
|
||||
action.pc.flag = controls.active ? PrivacyCommandFlag::disable
|
||||
: PrivacyCommandFlag::enable;
|
||||
const bool can_lock = props->privacy_status_received_ && status.supported &&
|
||||
now - props->privacy_status_tick_ <= 5000 &&
|
||||
(!props->privacy_command_pending_ || now - props->privacy_command_tick_ >= 15000);
|
||||
if (!props->privacy_desktop_available_ || !(relock ? can_lock : controls.can_toggle)) return;
|
||||
action.pc.flag = relock ? PrivacyCommandFlag::lock
|
||||
: controls.active ? PrivacyCommandFlag::disable : PrivacyCommandFlag::enable;
|
||||
action.pc.block_local_input = true;
|
||||
props->privacy_command_pending_ = true;
|
||||
props->privacy_command_tick_ = now;
|
||||
@@ -2181,9 +2185,17 @@ void GuiApplication::BindStreamCallbacks() {
|
||||
props->control_data_label_.c_str()) != 0) {
|
||||
std::lock_guard lock(props->privacy_status_mutex_);
|
||||
props->privacy_command_pending_ = false;
|
||||
props->privacy_status_received_ = false;
|
||||
if (!relock) props->privacy_status_received_ = false;
|
||||
LOG_WARN("Privacy command send failed, remote_id={}", props->remote_id_);
|
||||
}
|
||||
};
|
||||
stream->on_toggle_privacy([send_privacy] { send_privacy(false); });
|
||||
stream->on_lock_privacy([send_privacy] { send_privacy(true); });
|
||||
stream->on_dismiss_privacy_unlock([this] {
|
||||
if (auto props = SelectedSession()) {
|
||||
std::lock_guard lock(props->privacy_status_mutex_);
|
||||
props->privacy_unlock_notice_ = false;
|
||||
}
|
||||
});
|
||||
stream->on_toggle_mouse_control([this] {
|
||||
auto props = SelectedSession();
|
||||
@@ -2590,6 +2602,11 @@ void GuiApplication::Tick() {
|
||||
HandlePendingPresenceProbe();
|
||||
HandlePresenceProbeTimeout();
|
||||
HandlePrivacy();
|
||||
if (privacy_settings_changed_) {
|
||||
for (const auto& window : {ui_->main, ui_->wall})
|
||||
window->set_privacy_local_unlock_allowed(config_center_->IsPrivacyLocalUnlockAllowed());
|
||||
privacy_settings_changed_ = false;
|
||||
}
|
||||
HandleScreenLock();
|
||||
HandleWindowsServiceIntegration();
|
||||
#if (defined(__linux__) && !defined(__OHOS__)) && !defined(__APPLE__)
|
||||
@@ -3569,9 +3586,9 @@ void GuiApplication::SyncXWaylandWindowActivation() {
|
||||
#endif
|
||||
|
||||
void GuiApplication::SetStreamKeyboardFocus(bool focused) {
|
||||
// The local upgrade dialog owns input even while the native window is active.
|
||||
// Local dialogs own input even while the native window is active.
|
||||
if (focused && ui_ && ui_->stream &&
|
||||
(*ui_->stream)->get_remote_update_visible()) {
|
||||
(*ui_->stream)->get_modal_visible()) {
|
||||
focused = false;
|
||||
}
|
||||
if (focus_on_stream_window_ == focused) {
|
||||
@@ -3826,6 +3843,11 @@ void GuiApplication::SyncStreamWindow() {
|
||||
now - props->privacy_status_tick_, now - props->privacy_command_tick_);
|
||||
const bool connected =
|
||||
props->connection_status_.load() == ConnectionStatus::Connected;
|
||||
(*ui_->stream)->set_privacy_unlock_visible(connected && props->privacy_unlock_notice_);
|
||||
(*ui_->stream)->set_privacy_can_lock(connected && props->privacy_desktop_available_ &&
|
||||
props->privacy_status_received_ && status.supported &&
|
||||
now - props->privacy_status_tick_ <= 5000 &&
|
||||
(!props->privacy_command_pending_ || now - props->privacy_command_tick_ >= 15000));
|
||||
(*ui_->stream)->set_privacy_active(controls.active);
|
||||
(*ui_->stream)->set_privacy_off(connected && controls.off);
|
||||
(*ui_->stream)->set_privacy_unsupported(controls.unsupported);
|
||||
@@ -4580,6 +4602,14 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (config_center_->SetPrivacyLocalUnlock(main->get_privacy_local_unlock_allowed()) != 0) {
|
||||
if (wall_changed) ui_->gallery.Save(ui_->gallery_path);
|
||||
main->set_settings_save_error(UiText(
|
||||
localization_language_index_ == 0 ? "无法保存隐私屏解锁设置,请重试。"
|
||||
: localization_language_index_ == 2 ? "Не удалось сохранить настройку разблокировки. Повторите попытку."
|
||||
: "Could not save privacy unlock settings. Try again."));
|
||||
return false;
|
||||
}
|
||||
language_button_value_ =
|
||||
localization::detail::ClampLanguageIndex(main->get_language_index());
|
||||
video_encode_format_button_value_ = std::clamp(main->get_codec_index(), 0, 1);
|
||||
@@ -4594,7 +4624,9 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
|
||||
localization_language_index_ = language_button_value_;
|
||||
config_center_->SetLanguage(localization_language_);
|
||||
privacy_.SetText(
|
||||
{localization::privacy_screen_unlock_hint[localization_language_index_]});
|
||||
{localization::privacy_screen_unlock_hint[localization_language_index_],
|
||||
localization::privacy_screen_locked_hint[localization_language_index_],
|
||||
config_center_->IsPrivacyLocalUnlockAllowed()});
|
||||
config_center_->SetVideoEncodeFormat(
|
||||
static_cast<ConfigCenter::VIDEO_ENCODE_FORMAT>(
|
||||
video_encode_format_button_value_));
|
||||
@@ -4667,7 +4699,9 @@ void GuiApplication::ApplySettingsFromConfig(bool reconnect) {
|
||||
enable_self_hosted_ = config_center_->IsSelfHosted();
|
||||
enable_autostart_ = config_center_->IsEnableAutostart();
|
||||
enable_daemon_ = config_center_->IsEnableDaemon();
|
||||
privacy_.SetText({localization::privacy_screen_unlock_hint[localization_language_index_]});
|
||||
privacy_.SetText({localization::privacy_screen_unlock_hint[localization_language_index_],
|
||||
localization::privacy_screen_locked_hint[localization_language_index_],
|
||||
config_center_->IsPrivacyLocalUnlockAllowed()});
|
||||
const auto path = config_center_->GetFileTransferSavePath();
|
||||
std::snprintf(file_transfer_save_path_buf_, sizeof(file_transfer_save_path_buf_), "%s", path.c_str());
|
||||
const auto host = config_center_->GetSignalServerHost();
|
||||
|
||||
@@ -404,6 +404,12 @@ struct TranslationRow {
|
||||
X(wayland_notice_acknowledge, u8"我知道了", "Got it", u8"Понятно") \
|
||||
X(show_main_window, u8"显示主界面", "Show Main Window", \
|
||||
u8"Показать главное окно") \
|
||||
X(privacy_local_unlock, u8"允许被控时解锁隐私屏:", "Allow privacy screen unlock while controlled:", u8"Разрешить разблокировку приватного экрана при удалённом управлении:") \
|
||||
X(privacy_screen_locked_hint, u8"隐私屏已开启,本机解锁已禁用", "Privacy screen is on. Local unlocking is disabled.", u8"Приватный экран включён. Локальная разблокировка отключена.") \
|
||||
X(privacy_unlocked_title, u8"隐私屏已被解锁", "Privacy screen unlocked", u8"Приватный экран разблокирован") \
|
||||
X(privacy_unlocked_message, u8"被控端已通过组合键解除隐私屏。点击“锁定”将重新开启隐私屏,并禁止本机使用组合键解锁。", "The remote privacy screen was unlocked using the local shortcut. Lock it again to restore privacy and disable local shortcut unlocking.", u8"Приватный экран отключён локальным сочетанием клавиш. Заблокируйте его снова, чтобы восстановить защиту и запретить локальную разблокировку.") \
|
||||
X(privacy_unlock_confirm, u8"确认", "Confirm", u8"Подтвердить") \
|
||||
X(privacy_unlock_lock, u8"锁定", "Lock", u8"Заблокировать") \
|
||||
X(privacy_screen_unlock_hint, u8"按下快捷键,解除隐私屏", \
|
||||
"To turn off the privacy screen, press", \
|
||||
u8"Чтобы отключить приватный экран, нажмите") \
|
||||
|
||||
@@ -162,6 +162,7 @@ class GuiRuntime : protected gui_detail::GuiState {
|
||||
DisconnectLockPolicy disconnect_lock_;
|
||||
std::mutex privacy_commands_mutex_;
|
||||
std::deque<std::pair<std::string, PrivacyCommand>> privacy_commands_;
|
||||
bool privacy_settings_changed_ = false;
|
||||
uint32_t last_privacy_revision_ = 0;
|
||||
uint64_t last_privacy_status_tick_ = 0;
|
||||
ClipboardController clipboard_;
|
||||
|
||||
@@ -316,6 +316,12 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
std::lock_guard lock(props->privacy_status_mutex_);
|
||||
if (!props->privacy_status_received_ ||
|
||||
static_cast<int32_t>(remote_action.ps.revision - props->privacy_status_.revision) >= 0) {
|
||||
if (props->privacy_status_received_ &&
|
||||
static_cast<int32_t>(remote_action.ps.local_unlock_revision -
|
||||
props->privacy_status_.local_unlock_revision) > 0)
|
||||
props->privacy_unlock_notice_ = true;
|
||||
if (remote_action.ps.state == PrivacyState::on)
|
||||
props->privacy_unlock_notice_ = false;
|
||||
props->privacy_status_ = remote_action.ps;
|
||||
props->privacy_status_received_ = true;
|
||||
props->privacy_status_tick_ = SDL_GetTicks();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <shared_mutex>
|
||||
|
||||
#include "rd_log.h"
|
||||
#include "localization.h"
|
||||
#include "runtime/gui_runtime.h"
|
||||
|
||||
namespace crossdesk {
|
||||
@@ -71,6 +72,14 @@ void GuiRuntime::QueuePrivacyCommand(const std::string& remote_id,
|
||||
}
|
||||
|
||||
void GuiRuntime::HandlePrivacy() {
|
||||
const auto sync_text = [this] {
|
||||
const int language = localization::detail::ClampLanguageIndex(
|
||||
static_cast<int>(config_center_->GetLanguage()));
|
||||
privacy_.SetText({localization::privacy_screen_unlock_hint[language],
|
||||
localization::privacy_screen_locked_hint[language],
|
||||
config_center_->IsPrivacyLocalUnlockAllowed()});
|
||||
};
|
||||
sync_text();
|
||||
std::deque<std::pair<std::string, PrivacyCommand>> commands;
|
||||
{
|
||||
std::lock_guard lock(privacy_commands_mutex_);
|
||||
@@ -87,6 +96,16 @@ void GuiRuntime::HandlePrivacy() {
|
||||
if (command.flag == PrivacyCommandFlag::enable) {
|
||||
if (privacy_sessions_.CanEnable())
|
||||
privacy_.Enable(command.block_local_input);
|
||||
} else if (command.flag == PrivacyCommandFlag::lock) {
|
||||
if (privacy_sessions_.CanEnable()) {
|
||||
if (config_center_->SetPrivacyLocalUnlock(false) == 0) {
|
||||
privacy_settings_changed_ = true;
|
||||
sync_text();
|
||||
privacy_.Enable(true, true);
|
||||
} else {
|
||||
privacy_.RejectCommand("Could not save the local privacy unlock setting; try again");
|
||||
}
|
||||
}
|
||||
} else if (command.flag == PrivacyCommandFlag::disable) {
|
||||
privacy_.Disable();
|
||||
}
|
||||
|
||||
@@ -193,6 +193,7 @@ struct RemoteSession {
|
||||
std::mutex privacy_status_mutex_;
|
||||
PrivacyStatus privacy_status_{};
|
||||
bool privacy_status_received_ = false;
|
||||
bool privacy_unlock_notice_ = false;
|
||||
bool privacy_desktop_available_ = true;
|
||||
bool privacy_command_pending_ = false;
|
||||
uint64_t privacy_command_tick_ = 0;
|
||||
|
||||
@@ -81,6 +81,7 @@ export global UiStrings {
|
||||
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-local-unlock: "Allow privacy screen unlock while controlled:";
|
||||
in-out property <string> privacy-on-connect: "Privacy screen on connect";
|
||||
in-out property <string> lock-on-disconnect: "Lock screen on disconnect";
|
||||
in-out property <string> file-save-path: "File save path";
|
||||
@@ -651,6 +652,7 @@ export component MainWindow inherits Window {
|
||||
// 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;
|
||||
in-out property <bool> privacy-local-unlock-allowed: true;
|
||||
in property <bool> lock-on-disconnect-available: false;
|
||||
in-out property <bool> lock-on-disconnect-enabled: true;
|
||||
in-out property <string> file-save-path: "";
|
||||
@@ -2728,8 +2730,9 @@ export component MainWindow inherits Window {
|
||||
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||
}
|
||||
SettingsGroup {
|
||||
title: GalleryStrings.tr("隐私保护", "Privacy", "Приватность"); rows: 2;
|
||||
title: GalleryStrings.tr("隐私保护", "Privacy", "Приватность"); rows: 3;
|
||||
SettingsToggleRow { label: UiStrings.privacy-on-connect; checked <=> root.privacy-on-connect-enabled; enabled: root.privacy-setting-available; }
|
||||
SettingsToggleRow { label: UiStrings.privacy-local-unlock; checked <=> root.privacy-local-unlock-allowed; enabled: root.privacy-setting-available; }
|
||||
SettingsToggleRow { label: UiStrings.lock-on-disconnect; checked <=> root.lock-on-disconnect-enabled; enabled: root.lock-on-disconnect-available; divider: false; }
|
||||
}
|
||||
Text {
|
||||
|
||||
@@ -47,6 +47,10 @@ export global StreamStrings {
|
||||
|
||||
in-out property <string> select-display: "Select Display";
|
||||
in-out property <string> send-shortcut: "Send Shortcut";
|
||||
in-out property <string> privacy-unlocked-title: "Privacy screen unlocked";
|
||||
in-out property <string> privacy-unlocked-message: "The remote privacy screen was unlocked using the local shortcut. Lock it again to restore privacy and disable local shortcut unlocking.";
|
||||
in-out property <string> privacy-unlock-confirm: "Confirm";
|
||||
in-out property <string> privacy-unlock-lock: "Lock";
|
||||
in-out property <string> privacy-enable: "Enable privacy screen";
|
||||
in-out property <string> privacy-disable: "Turn privacy off / recover";
|
||||
in-out property <string> control-mouse: "Control Mouse";
|
||||
@@ -296,6 +300,11 @@ export component StreamWindow inherits Window {
|
||||
in property <string> status-text: "";
|
||||
in property <string> receiving-text: "";
|
||||
in property <bool> remote-update-visible: false;
|
||||
in property <bool> privacy-unlock-visible: false;
|
||||
in property <bool> privacy-can-lock: false;
|
||||
out property <bool> modal-visible: root.remote-update-visible || root.privacy-unlock-visible;
|
||||
callback dismiss-privacy-unlock;
|
||||
callback lock-privacy;
|
||||
callback dismiss-remote-update;
|
||||
in property <[string]> displays;
|
||||
in-out property <int> selected-display: 0;
|
||||
@@ -491,8 +500,8 @@ export component StreamWindow inherits Window {
|
||||
changed mouse-control-enabled => {
|
||||
if !root.mouse-control-enabled { root.cancel-remote-pointer(); }
|
||||
}
|
||||
changed remote-update-visible => {
|
||||
if root.remote-update-visible {
|
||||
changed modal-visible => {
|
||||
if root.modal-visible {
|
||||
root.close-control-menus();
|
||||
root.control-drag-pending = false;
|
||||
root.end-control-drag();
|
||||
@@ -502,7 +511,7 @@ export component StreamWindow inherits Window {
|
||||
// adapter and aborts the process; defer focus to the event loop.
|
||||
input-focus-timer.running = true;
|
||||
input-focus-timer.restart();
|
||||
root.keyboard-focus-changed(!root.remote-update-visible);
|
||||
root.keyboard-focus-changed(!root.modal-visible);
|
||||
}
|
||||
input-focus-timer := Timer {
|
||||
interval: 1ms;
|
||||
@@ -617,7 +626,7 @@ export component StreamWindow inherits Window {
|
||||
root.set-remote-button(button, false);
|
||||
root.pointer-input(button, kind, x, y);
|
||||
}
|
||||
} else if root.window-active && root.mouse-control-enabled && !root.remote-update-visible {
|
||||
} else if root.window-active && root.mouse-control-enabled && !root.modal-visible {
|
||||
let remote-target = root.remote-pointer-active
|
||||
|| (!root.control-menu-open && !root.is-local-control-area(x, y));
|
||||
if kind == PointerEventKind.down && remote-target
|
||||
@@ -653,7 +662,7 @@ export component StreamWindow inherits Window {
|
||||
// so a captured drag can never also toggle the toolbar.
|
||||
let toggle-expanded = kind == PointerEventKind.up && root.control-drag-pending
|
||||
&& !root.control-dragging
|
||||
&& root.window-active && !root.remote-update-visible
|
||||
&& root.window-active && !root.modal-visible
|
||||
&& x >= control.x + logo-button.x && x < control.x + logo-button.x + logo-button.width
|
||||
&& y >= control.y + logo-button.y && y < control.y + logo-button.y + logo-button.height;
|
||||
root.control-drag-pending = false;
|
||||
@@ -702,7 +711,7 @@ export component StreamWindow inherits Window {
|
||||
}
|
||||
|
||||
is-local-control-area(pointer-x, pointer-y) => {
|
||||
if root.remote-update-visible { return true; }
|
||||
if root.modal-visible { return true; }
|
||||
let over-control = pointer-x >= root.control-surface-x
|
||||
&& pointer-x <= root.control-surface-x + root.control-surface-width
|
||||
&& pointer-y >= control.y
|
||||
@@ -1015,11 +1024,11 @@ export component StreamWindow inherits Window {
|
||||
|
||||
drop-zone := DropArea {
|
||||
can-drop(event) => {
|
||||
return !root.remote-update-visible && root.can-drop-file(event.data)
|
||||
return !root.modal-visible && root.can-drop-file(event.data)
|
||||
? DragAction.copy : DragAction.none;
|
||||
}
|
||||
dropped(event) => {
|
||||
if root.remote-update-visible { return DragAction.none; }
|
||||
if root.modal-visible { return DragAction.none; }
|
||||
root.file-dropped(event.data);
|
||||
return DragAction.copy;
|
||||
}
|
||||
@@ -1030,16 +1039,16 @@ export component StreamWindow inherits Window {
|
||||
focus-gained => { root.keyboard-focus-changed(true); }
|
||||
focus-lost => { root.keyboard-focus-changed(false); }
|
||||
key-pressed(event) => {
|
||||
if !root.remote-update-visible {
|
||||
if !root.modal-visible {
|
||||
root.key-input(event.text, true, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta);
|
||||
}
|
||||
accept
|
||||
}
|
||||
key-released(event) => {
|
||||
if root.remote-update-visible {
|
||||
if root.modal-visible {
|
||||
// Dismiss on release so the same key cannot reach the peer.
|
||||
if event.text == Key.Escape || event.text == Key.Return {
|
||||
root.dismiss-remote-update();
|
||||
if root.privacy-unlock-visible { root.dismiss-privacy-unlock(); } else { root.dismiss-remote-update(); }
|
||||
}
|
||||
} else {
|
||||
root.key-input(event.text, false, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta);
|
||||
@@ -1743,7 +1752,7 @@ export component StreamWindow inherits Window {
|
||||
}
|
||||
}
|
||||
|
||||
if root.remote-update-visible: Rectangle {
|
||||
if root.modal-visible: Rectangle {
|
||||
width: parent.width; height: parent.height;
|
||||
background: #0c162c52;
|
||||
z: 13;
|
||||
@@ -1752,8 +1761,43 @@ export component StreamWindow inherits Window {
|
||||
scroll-event(event) => { accept }
|
||||
}
|
||||
}
|
||||
if root.privacy-unlock-visible: DialogSurface {
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: max(16px, (parent.height - self.height) / 2);
|
||||
width: min(460px, parent.width - 32px);
|
||||
height: unlock-message.y + unlock-message.height + 84px;
|
||||
outline-color: UiSurface.edge;
|
||||
border-radius: UiRadius.surface;
|
||||
z: 14;
|
||||
TouchArea { scroll-event(event) => { accept } }
|
||||
unlock-title := Text {
|
||||
x: 24px; y: 24px; width: parent.width - 48px;
|
||||
height: max(28px, self.preferred-height);
|
||||
text: StreamStrings.privacy-unlocked-title;
|
||||
color: #1b2433; font-size: 20px; font-weight: 600; wrap: word-wrap;
|
||||
}
|
||||
unlock-message := Text {
|
||||
x: 24px; y: unlock-title.y + unlock-title.height + 8px;
|
||||
width: parent.width - 48px; height: max(22px, self.preferred-height);
|
||||
text: StreamStrings.privacy-unlocked-message;
|
||||
color: #667085; font-size: 15px; wrap: word-wrap;
|
||||
}
|
||||
SecondaryButton {
|
||||
x: parent.width - 244px; y: parent.height - 60px;
|
||||
width: 104px; height: 36px;
|
||||
text: StreamStrings.privacy-unlock-confirm;
|
||||
clicked => { root.dismiss-privacy-unlock(); }
|
||||
}
|
||||
PrimaryButton {
|
||||
x: parent.width - 128px; y: parent.height - 60px;
|
||||
width: 104px; height: 36px;
|
||||
text: StreamStrings.privacy-unlock-lock;
|
||||
enabled: root.privacy-can-lock;
|
||||
clicked => { root.lock-privacy(); }
|
||||
}
|
||||
}
|
||||
remote-update-banner := DialogSurface {
|
||||
visible: root.remote-update-visible;
|
||||
visible: root.remote-update-visible && !root.privacy-unlock-visible;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: min(max(64px, parent.height * 0.18), max(16px, parent.height - self.height - 16px));
|
||||
width: min(400px, parent.width - 48px);
|
||||
|
||||
@@ -105,6 +105,7 @@ inline int ApplyMainWindowStrings(
|
||||
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_local_unlock(Text(localization::privacy_local_unlock[language]));
|
||||
strings.set_privacy_on_connect(Text(localization::privacy_on_connect[language]));
|
||||
strings.set_lock_on_disconnect(Text(localization::lock_on_disconnect[language]));
|
||||
strings.set_file_save_path(
|
||||
@@ -206,6 +207,10 @@ inline int ApplyStreamWindowStrings(
|
||||
strings.set_remote_update_dismiss(Text(localization::remote_update_dismiss[language]));
|
||||
strings.set_select_display(Text(localization::select_display[language]));
|
||||
strings.set_send_shortcut(Text(localization::send_shortcut[language]));
|
||||
strings.set_privacy_unlocked_title(Text(localization::privacy_unlocked_title[language]));
|
||||
strings.set_privacy_unlocked_message(Text(localization::privacy_unlocked_message[language]));
|
||||
strings.set_privacy_unlock_confirm(Text(localization::privacy_unlock_confirm[language]));
|
||||
strings.set_privacy_unlock_lock(Text(localization::privacy_unlock_lock[language]));
|
||||
strings.set_privacy_enable(Text(localization::privacy_enable[language]));
|
||||
strings.set_privacy_disable(Text(localization::privacy_disable[language]));
|
||||
strings.set_control_mouse(Text(localization::control_mouse[language]));
|
||||
|
||||
Reference in New Issue
Block a user