[feat] add privacy unlock controls and remote relocking

This commit is contained in:
dijunkun committed 2026-10-27 10:22:25 +08:00
1 parent d1e4a31efb
commit ea3a2e1faa
41 files changed
+470 -72

No files matched your search

@@ -14,6 +14,8 @@ namespace crossdesk {
struct PrivacyScreenText {
std::string unlock_hint = "To turn off the privacy screen, press";
std::string locked_hint = "Privacy screen is on. Local unlocking is disabled.";
bool allow_local_unlock = true;
};
struct PrivacyCapabilities {
@@ -38,6 +40,8 @@ class PrivacyBackend {
virtual PrivacyCapabilities Query() = 0;
virtual bool Enable(bool block_input, const PrivacyScreenText& text,
std::string& error) = 0;
// Update a live cover and its local shortcut policy without exposing the desktop.
virtual void UpdateText(const PrivacyScreenText& text) {}
virtual void Recover() = 0;
virtual bool IsRecovered() const = 0;
virtual bool RecoveryPending() const { return false; }
@@ -43,7 +43,11 @@ PrivacyStatus PrivacyController::Snapshot() const {
}
void PrivacyController::SetText(const PrivacyScreenText& text) {
std::lock_guard lock(mutex_);
if (text_.unlock_hint == text.unlock_hint && text_.locked_hint == text.locked_hint &&
text_.allow_local_unlock == text.allow_local_unlock) return;
text_ = text;
text_pending_ = true;
WakeLocked();
}
void PrivacyController::SetState(PrivacyState state, const std::string& reason) {
status_.state = state;
@@ -54,9 +58,28 @@ void PrivacyController::SetState(PrivacyState state, const std::string& reason)
LOG_INFO("Privacy: state={}, overlay={}, input_blocked={}, reason={}",
static_cast<int>(state), status_.overlay_active, status_.input_blocked, reason);
}
void PrivacyController::Enable(bool block_input) {
void PrivacyController::RejectCommand(const std::string& reason) {
std::lock_guard lock(mutex_);
if (quit_ || engaged_.load() || disable_pending_) return;
if (disable_pending_) {
failure_ = reason;
SetState(PrivacyState::failed, reason);
} else if (engaged_.load()) {
// A rejected command must not remove an already working cover.
SetState(status_.state, reason);
} else {
SetState(PrivacyState::failed, reason);
}
}
void PrivacyController::Enable(bool block_input, bool wait_for_recovery) {
std::lock_guard lock(mutex_);
if (quit_) return;
if (engaged_.load() && !disable_pending_) {
// Another controller may have restored privacy before this lock arrived.
// Acknowledge it so this controller does not wait for a nonexistent transition.
if (wait_for_recovery) SetState(status_.state, status_.reason);
return;
}
if (disable_pending_ && !wait_for_recovery) return;
if (!status_.supported || (block_input && !status_.input_block_supported)) {
SetState(PrivacyState::unsupported, "Privacy screen is unavailable");
return;
@@ -227,9 +250,17 @@ void PrivacyController::Run(Factory factory) {
}
}
}
if (text_pending_ && backend_) {
const auto text = text_;
text_pending_ = false;
lock.unlock();
backend_->UpdateText(text);
lock.lock();
}
const auto health = backend_ ? backend_->Poll() : PrivacyHealth{};
if (engaged_.load() && !disable_pending_) {
if (health.emergency_exit) {
if (health.emergency_exit && text_.allow_local_unlock) {
++status_.local_unlock_revision;
automatic_enable_ = false;
resume_after_unlock_ = false;
failure_.clear();
@@ -23,7 +23,8 @@ class PrivacyController {
PrivacyController& operator=(const PrivacyController&) = delete;
PrivacyStatus Snapshot() const;
void SetText(const PrivacyScreenText& text);
void Enable(bool block_input);
void Enable(bool block_input, bool wait_for_recovery = false);
void RejectCommand(const std::string& reason);
void EnableOnConnection();
// Preserve automatic session intent while Windows shows another desktop.
void SuspendForDesktop();
@@ -50,6 +51,7 @@ class PrivacyController {
std::unique_ptr<PrivacyBackend> backend_;
PrivacyStatus status_{};
PrivacyScreenText text_;
bool text_pending_ = false;
std::atomic<bool> engaged_{false};
bool quit_ = false;
bool enable_pending_ = false;