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:
parent
28fb6c0309
commit
b9bf9813c5
3 changed files with 218 additions and 19 deletions
|
|
@ -2,6 +2,13 @@ import Cocoa
|
|||
import FlutterMacOS
|
||||
|
||||
class MainFlutterWindow: NSWindow {
|
||||
/// The channel halkyon_app's SecretScreen asks to keep the window out of
|
||||
/// screenshots and screen recordings while something secret shows (the
|
||||
/// recovery phrase, recovery codes, an authenticator's key), and how many
|
||||
/// show: counted, as several may at once.
|
||||
private var screenGuard: FlutterMethodChannel?
|
||||
private var secretsShown = 0
|
||||
|
||||
override func awakeFromNib() {
|
||||
let flutterViewController = FlutterViewController()
|
||||
let windowFrame = self.frame
|
||||
|
|
@ -9,7 +16,34 @@ class MainFlutterWindow: NSWindow {
|
|||
self.setFrame(windowFrame, display: true)
|
||||
|
||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||
guardScreen(messenger: flutterViewController.engine.binaryMessenger)
|
||||
|
||||
super.awakeFromNib()
|
||||
}
|
||||
|
||||
private func guardScreen(messenger: FlutterBinaryMessenger) {
|
||||
let channel = FlutterMethodChannel(
|
||||
name: "app.halkyon/screen_guard", binaryMessenger: messenger)
|
||||
channel.setMethodCallHandler { [weak self] call, result in
|
||||
guard let self = self else {
|
||||
result(nil)
|
||||
return
|
||||
}
|
||||
switch call.method {
|
||||
case "hold":
|
||||
self.secretsShown += 1
|
||||
self.sharingType = .none
|
||||
result(nil)
|
||||
case "release":
|
||||
self.secretsShown = max(0, self.secretsShown - 1)
|
||||
if self.secretsShown == 0 {
|
||||
self.sharingType = .readOnly
|
||||
}
|
||||
result(nil)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
screenGuard = channel
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_
|
||||
|
|
|
|||
Loading…
Reference in a new issue