[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

@@ -200,6 +200,7 @@ class MacPrivacyBackend final : public PrivacyBackend {
const auto caps = Query();
if (!caps.overlay) { error = caps.reason; return false; }
block_input_ = block_input;
allow_local_unlock_ = text.allow_local_unlock;
emergency_ = false;
tap_failed_ = false;
CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) |
@@ -239,7 +240,8 @@ class MacPrivacyBackend final : public PrivacyBackend {
m.frame = screen.frame;
NSView* content = CreateMacPrivacyCoverView(
NSMakeRect(0, 0, m.frame.size.width, m.frame.size.height),
[NSString stringWithUTF8String:text.unlock_hint.c_str()]);
[NSString stringWithUTF8String:(text.allow_local_unlock ? text.unlock_hint : text.locked_hint).c_str()],
text.allow_local_unlock);
if (!content) {
state->failure = "Could not load the CrossDesk privacy screen logo";
break;
@@ -260,6 +262,17 @@ class MacPrivacyBackend final : public PrivacyBackend {
});
return true;
}
void UpdateText(const PrivacyScreenText& text) override {
allow_local_unlock_ = text.allow_local_unlock;
auto state = state_;
OnMain([state, text] {
std::lock_guard lock(state->mutex);
for (const auto& monitor : state->monitors)
UpdateMacPrivacyCoverView(monitor.cover.contentView,
[NSString stringWithUTF8String:(text.allow_local_unlock ? text.unlock_hint : text.locked_hint).c_str()],
text.allow_local_unlock);
});
}
void Recover() override {
if (tap_) { CGEventTapEnable(tap_, false); CFMachPortInvalidate(tap_); }
if (tap_source_) {
@@ -359,7 +372,7 @@ class MacPrivacyBackend final : public PrivacyBackend {
CGEventGetIntegerValueField(event, kCGEventSourceUnixProcessID) == getpid()) return event;
const auto flags = CGEventGetFlags(event);
const auto chord = kCGEventFlagMaskControl | kCGEventFlagMaskAlternate | kCGEventFlagMaskShift;
if (type == kCGEventKeyDown && (flags & chord) == chord &&
if (self->allow_local_unlock_ && type == kCGEventKeyDown && (flags & chord) == chord &&
CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode) == 53) {
self->emergency_ = true;
return nullptr;
@@ -372,6 +385,7 @@ class MacPrivacyBackend final : public PrivacyBackend {
CFMachPortRef tap_ = nullptr;
CFRunLoopSourceRef tap_source_ = nullptr;
bool block_input_ = false;
bool allow_local_unlock_ = true;
bool emergency_ = false;
bool tap_failed_ = false;
};
@@ -11,7 +11,8 @@
namespace crossdesk {
// Returns nil if the CrossDesk icon resource cannot be loaded.
NSView* CreateMacPrivacyCoverView(NSRect frame, NSString* unlock_hint);
NSView* CreateMacPrivacyCoverView(NSRect frame, NSString* hint, bool allow_local_unlock = true);
void UpdateMacPrivacyCoverView(NSView* view, NSString* hint, bool allow_local_unlock);
}
#endif
@@ -52,6 +52,7 @@ CGFloat TextHeight(NSAttributedString* text, CGFloat width) {
@interface CrossDeskPrivacyCoverView : NSView
@property(nonatomic, strong) NSImage* logo;
@property(nonatomic, copy) NSString* unlockHint;
@property(nonatomic) BOOL allowLocalUnlock;
@end
@implementation CrossDeskPrivacyCoverView
@@ -77,7 +78,7 @@ CGFloat TextHeight(NSAttributedString* text, CGFloat width) {
const CGFloat logo_size = std::ceil(brand.size.width);
const CGFloat brand_height = TextHeight(brand, logo_size);
const CGFloat hint_height = TextHeight(hint, text_width);
const CGFloat shortcut_height = TextHeight(shortcut, text_width);
const CGFloat shortcut_height = self.allowLocalUnlock ? TextHeight(shortcut, text_width) : 0;
const CGFloat bottom = height - margin;
const CGFloat logo_top = bottom - brand_height - 6 * scale - logo_size;
[self.logo drawInRect:NSMakeRect(margin, logo_top, logo_size, logo_size)
@@ -86,19 +87,26 @@ CGFloat TextHeight(NSAttributedString* text, CGFloat width) {
[brand drawWithRect:NSMakeRect(margin, bottom - brand_height, logo_size, brand_height)
options:kTextOptions];
const CGFloat text_left = margin + logo_size + 24 * scale;
[hint drawWithRect:NSMakeRect(text_left, bottom - shortcut_height - 12 * scale - hint_height,
[hint drawWithRect:NSMakeRect(text_left, bottom - shortcut_height - (self.allowLocalUnlock ? 12 * scale : 0) - hint_height,
text_width, hint_height) options:kTextOptions];
[shortcut drawWithRect:NSMakeRect(text_left, bottom - shortcut_height,
if (self.allowLocalUnlock) [shortcut drawWithRect:NSMakeRect(text_left, bottom - shortcut_height,
text_width, shortcut_height) options:kTextOptions];
}
@end
namespace crossdesk {
NSView* CreateMacPrivacyCoverView(NSRect frame, NSString* unlock_hint) {
void UpdateMacPrivacyCoverView(NSView* view, NSString* hint, bool allow_local_unlock) {
auto* cover = static_cast<CrossDeskPrivacyCoverView*>(view);
cover.unlockHint = hint;
cover.allowLocalUnlock = allow_local_unlock;
cover.needsDisplay = YES;
}
NSView* CreateMacPrivacyCoverView(NSRect frame, NSString* unlock_hint, bool allow_local_unlock) {
NSImage* logo = LoadPrivacyLogo();
if (!logo) return nil;
auto* view = [[CrossDeskPrivacyCoverView alloc] initWithFrame:frame];
view.logo = logo;
view.allowLocalUnlock = allow_local_unlock;
view.unlockHint = unlock_hint ?: @"To turn off the privacy screen, press";
view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
return view;