feat(app): secrets kept out of screenshots on macOS and Windows too

The reading of the full reading's fixes noted that macOS and Windows can
keep a window out of screen captures, which SecretScreen didn't ask.

PlatformScreenGuard now asks the app's window on Android, macOS and
Windows (asksOn), and both apps' runners answer app.halkyon/screen_guard,
counting what shows: macOS's MainFlutterWindow sets sharingType = .none,
Windows' FlutterWindow WDA_EXCLUDEFROMCAPTURE (Windows 10 2004 on;
before, WDA_MONITOR, black in captures). Neither runner was built here
(Linux): to be tried on each system.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-21 16:40:43 +02:00
commit b9bf9813c5
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 218 additions and 19 deletions

View file

@ -1,9 +1,17 @@
#include "flutter_window.h"
#include <flutter/standard_method_codec.h>
#include <optional>
#include "flutter/generated_plugin_registrant.h"
// Kept out of captures altogether (Windows 10 2004 and later); older SDKs
// don't name it.
#ifndef WDA_EXCLUDEFROMCAPTURE
#define WDA_EXCLUDEFROMCAPTURE 0x00000011
#endif
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
@ -25,6 +33,7 @@ bool FlutterWindow::OnCreate() {
return false;
}
RegisterPlugins(flutter_controller_->engine());
GuardScreen();
SetChildContent(flutter_controller_->view()->GetNativeWindow());
flutter_controller_->engine()->SetNextFrameCallback([&]() {
@ -39,7 +48,38 @@ bool FlutterWindow::OnCreate() {
return true;
}
void FlutterWindow::GuardScreen() {
screen_guard_ =
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
flutter_controller_->engine()->messenger(),
"app.halkyon/screen_guard",
&flutter::StandardMethodCodec::GetInstance());
screen_guard_->SetMethodCallHandler(
[this](const flutter::MethodCall<flutter::EncodableValue>& call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>
result) {
if (call.method_name() == "hold") {
secrets_shown_++;
// Before Windows 10 2004 the exclusion fails: the window shows
// black in captures instead.
if (!SetWindowDisplayAffinity(GetHandle(), WDA_EXCLUDEFROMCAPTURE)) {
SetWindowDisplayAffinity(GetHandle(), WDA_MONITOR);
}
result->Success();
} else if (call.method_name() == "release") {
secrets_shown_ = secrets_shown_ > 0 ? secrets_shown_ - 1 : 0;
if (secrets_shown_ == 0) {
SetWindowDisplayAffinity(GetHandle(), WDA_NONE);
}
result->Success();
} else {
result->NotImplemented();
}
});
}
void FlutterWindow::OnDestroy() {
screen_guard_ = nullptr;
if (flutter_controller_) {
flutter_controller_ = nullptr;
}

View file

@ -2,7 +2,9 @@
#define RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/encodable_value.h>
#include <flutter/flutter_view_controller.h>
#include <flutter/method_channel.h>
#include <memory>
@ -28,6 +30,17 @@ class FlutterWindow : public Win32Window {
// The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
// The channel halkyon_app's SecretScreen asks to keep the window out of
// screen captures while something secret shows (the recovery phrase,
// recovery codes, an authenticator's key), and how many show: counted, as
// several may at once.
std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>>
screen_guard_;
int secrets_shown_ = 0;
// Answers |screen_guard_|.
void GuardScreen();
};
#endif // RUNNER_FLUTTER_WINDOW_H_