mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-10 17:49:20 +08:00
[feat] add privacy unlock controls and remote relocking
This commit is contained in:
@@ -190,6 +190,10 @@ class Windows {
|
||||
return TRUE;
|
||||
}
|
||||
if (message == WM_CLOSE) return 0;
|
||||
if (message == kPrivacyTextChanged) {
|
||||
InvalidateRect(hwnd, nullptr, TRUE);
|
||||
return 0;
|
||||
}
|
||||
if ((message == WM_DISPLAYCHANGE || message == WM_DPICHANGED) && self) {
|
||||
self->changed = true;
|
||||
InvalidateRect(hwnd, nullptr, TRUE);
|
||||
@@ -200,8 +204,12 @@ class Windows {
|
||||
HDC dc = BeginPaint(hwnd, &paint);
|
||||
RECT rect{};
|
||||
GetClientRect(hwnd, &rect);
|
||||
const bool allow_unlock = self &&
|
||||
InterlockedCompareExchange(&self->shared_->allow_local_unlock, 0, 0);
|
||||
if (self && !DrawPrivacyCover(dc, rect, PrivacyWindowDpi(hwnd), module,
|
||||
self->shared_->unlock_hint)) {
|
||||
allow_unlock ? self->shared_->unlock_hint
|
||||
: self->shared_->locked_hint,
|
||||
allow_unlock)) {
|
||||
self->paint_failed_ = true;
|
||||
self->Fail(L"Paint privacy logo and unlock instructions");
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ bool Select(HDC dc, HGDIOBJ object) {
|
||||
} // namespace
|
||||
|
||||
bool DrawPrivacyCover(HDC dc, const RECT& rect, UINT dpi, HINSTANCE module,
|
||||
const wchar_t* unlock_hint) {
|
||||
const wchar_t* unlock_hint, bool allow_local_unlock) {
|
||||
if (!FillRect(dc, &rect, static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH))))
|
||||
return false;
|
||||
const int width = rect.right - rect.left;
|
||||
@@ -68,6 +68,7 @@ bool DrawPrivacyCover(HDC dc, const RECT& rect, UINT dpi, HINSTANCE module,
|
||||
}};
|
||||
constexpr UINT flags = DT_LEFT | DT_WORDBREAK | DT_NOPREFIX;
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
if (i == 1 && !allow_local_unlock) continue;
|
||||
auto& line = lines[i];
|
||||
resources.fonts[i] =
|
||||
CreateFontW(-px(line.size), 0, 0, 0, line.weight, FALSE, FALSE, FALSE,
|
||||
@@ -81,7 +82,7 @@ bool DrawPrivacyCover(HDC dc, const RECT& rect, UINT dpi, HINSTANCE module,
|
||||
}
|
||||
const int content_height =
|
||||
static_cast<int>(lines[0].bounds.bottom + lines[1].bounds.bottom) +
|
||||
px(12);
|
||||
(allow_local_unlock ? px(12) : 0);
|
||||
// Match the icon width to the name; each side keeps its own height.
|
||||
const int brand_gap = px(6);
|
||||
const int brand_width = static_cast<int>(lines[kBrandLine].bounds.right);
|
||||
@@ -110,6 +111,7 @@ bool DrawPrivacyCover(HDC dc, const RECT& rect, UINT dpi, HINSTANCE module,
|
||||
DT_CENTER | DT_SINGLELINE | DT_NOPREFIX))
|
||||
return false;
|
||||
for (size_t i = 0; i < kInstructionLines; ++i) {
|
||||
if (i == 1 && !allow_local_unlock) continue;
|
||||
const auto& line = lines[i];
|
||||
if (!Select(dc, resources.fonts[i])) return false;
|
||||
RECT bounds{left, top, left + text_width, top + line.bounds.bottom};
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace crossdesk {
|
||||
|
||||
// Paints the local, opaque cover. The caller owns the HWND and its DPI context.
|
||||
bool DrawPrivacyCover(HDC dc, const RECT& rect, UINT dpi, HINSTANCE module,
|
||||
const wchar_t* unlock_hint);
|
||||
const wchar_t* unlock_hint, bool allow_local_unlock = true);
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
|
||||
@@ -120,6 +120,7 @@ class WindowsPrivacyBackend final : public PrivacyBackend {
|
||||
}
|
||||
if (!Initialize(error)) return false;
|
||||
emergency_ = false;
|
||||
allow_local_unlock_ = text.allow_local_unlock;
|
||||
health_error_.clear();
|
||||
if (!RegisterHotKey(sink_, kHotkeyId,
|
||||
MOD_CONTROL | MOD_ALT | MOD_SHIFT | MOD_NOREPEAT,
|
||||
@@ -149,6 +150,10 @@ class WindowsPrivacyBackend final : public PrivacyBackend {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateText(const PrivacyScreenText& text) override {
|
||||
allow_local_unlock_ = text.allow_local_unlock;
|
||||
band_guard_.SetLocalUnlockAllowed(text.allow_local_unlock);
|
||||
}
|
||||
void Recover() override {
|
||||
for (auto& hook : window_events_) {
|
||||
if (UnhookWinEvent(hook))
|
||||
@@ -475,7 +480,8 @@ class WindowsPrivacyBackend final : public PrivacyBackend {
|
||||
auto self = reinterpret_cast<WindowsPrivacyBackend*>(
|
||||
GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||
if (msg == WM_HOTKEY || msg == kEmergency) {
|
||||
if (self && hwnd == self->sink_) self->emergency_ = true;
|
||||
if (self && hwnd == self->sink_ && self->allow_local_unlock_)
|
||||
self->emergency_ = true;
|
||||
return 0;
|
||||
}
|
||||
if (msg == WM_MOUSEACTIVATE) return MA_NOACTIVATE;
|
||||
@@ -522,6 +528,7 @@ class WindowsPrivacyBackend final : public PrivacyBackend {
|
||||
std::unique_ptr<ScopedWindowsPhysicalCoordinates> dpi_scope_;
|
||||
bool registered_ = false;
|
||||
bool hotkey_ = false;
|
||||
bool allow_local_unlock_ = true;
|
||||
bool emergency_ = false;
|
||||
bool layout_changed_ = false;
|
||||
bool topology_pending_ = false;
|
||||
|
||||
@@ -153,6 +153,11 @@ bool PrivacyBandGuard::Start(const PrivacyScreenText& text,
|
||||
text.unlock_hint.c_str(), -1, shared_->unlock_hint,
|
||||
static_cast<int>(std::size(shared_->unlock_hint))))
|
||||
return Failed("Encode privacy screen text", error);
|
||||
if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
||||
text.locked_hint.c_str(), -1, shared_->locked_hint,
|
||||
static_cast<int>(std::size(shared_->locked_hint))))
|
||||
return Failed("Encode locked privacy screen text", error);
|
||||
shared_->allow_local_unlock = text.allow_local_unlock;
|
||||
shared_->parent_pid = GetCurrentProcessId();
|
||||
HANDLE handles[] = {parent.value, stop_, ready_, mapping_};
|
||||
std::wstring bootstrap;
|
||||
@@ -299,6 +304,16 @@ bool PrivacyBandGuard::Windows(std::vector<PrivacyBandWindow>& windows,
|
||||
return retry();
|
||||
}
|
||||
|
||||
void PrivacyBandGuard::SetLocalUnlockAllowed(bool allowed) {
|
||||
if (!shared_ || InterlockedExchange(&shared_->allow_local_unlock, allowed) == allowed) return;
|
||||
std::vector<PrivacyBandWindow> windows;
|
||||
std::string error;
|
||||
if (Windows(windows, error))
|
||||
for (const auto& window : windows)
|
||||
PostMessageW(reinterpret_cast<HWND>(static_cast<uintptr_t>(window.hwnd)),
|
||||
kPrivacyTextChanged, 0, 0);
|
||||
}
|
||||
|
||||
bool PrivacyBandGuard::Stop(std::string& error) {
|
||||
bool ok = true;
|
||||
if (process_) {
|
||||
|
||||
@@ -27,6 +27,7 @@ class PrivacyBandGuard {
|
||||
~PrivacyBandGuard();
|
||||
static bool Available(std::string& error);
|
||||
bool Start(const PrivacyScreenText& text, std::string& error);
|
||||
void SetLocalUnlockAllowed(bool allowed);
|
||||
bool Stop(std::string& error);
|
||||
bool Healthy(std::string& error);
|
||||
bool Windows(std::vector<PrivacyBandWindow>& windows, std::string& error);
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
namespace crossdesk {
|
||||
|
||||
constexpr uint32_t kPrivacyBandMagic = 0x43534231;
|
||||
constexpr uint32_t kPrivacyBandVersion = 3;
|
||||
constexpr uint32_t kPrivacyBandVersion = 4;
|
||||
constexpr UINT kPrivacyTextChanged = WM_APP + 72;
|
||||
constexpr DWORD kPrivacyWindowBand = 18; // ZBID_ABOVELOCK_UX, current desktop.
|
||||
constexpr size_t kPrivacyMaxMonitors = 64;
|
||||
constexpr wchar_t kPrivacyBandBootstrap[] = L"CROSSDESK_PRIVACY_BAND_BOOTSTRAP";
|
||||
@@ -40,6 +41,8 @@ struct PrivacyBandShared {
|
||||
alignas(8) volatile LONG64 heartbeat = 0;
|
||||
wchar_t operation[128]{};
|
||||
wchar_t unlock_hint[128]{};
|
||||
wchar_t locked_hint[128]{};
|
||||
volatile LONG allow_local_unlock = 1;
|
||||
uint32_t count = 0;
|
||||
PrivacyBandWindow windows[kPrivacyMaxMonitors]{};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user