惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

W
WeLiveSecurity
Jina AI
Jina AI
博客园 - 司徒正美
雷峰网
雷峰网
宝玉的分享
宝玉的分享
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园_首页
WordPress大学
WordPress大学
Google DeepMind News
Google DeepMind News
GbyAI
GbyAI
MyScale Blog
MyScale Blog
Apple Machine Learning Research
Apple Machine Learning Research
美团技术团队
I
InfoQ
博客园 - Franky
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
Cyberwarzone
Cyberwarzone
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Schneier on Security
P
Privacy & Cybersecurity Law Blog
T
Threatpost
Cloudbric
Cloudbric
D
Docker
M
MIT News - Artificial intelligence
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Vercel News
Vercel News
Martin Fowler
Martin Fowler
J
Java Code Geeks
AWS News Blog
AWS News Blog
The Cloudflare Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
L
Lohrmann on Cybersecurity
Hacker News: Ask HN
Hacker News: Ask HN
Last Week in AI
Last Week in AI
S
Security @ Cisco Blogs
Help Net Security
Help Net Security
C
Cisco Blogs
V
V2EX
博客园 - 【当耐特】
I
Intezer
爱范儿
爱范儿
F
Fortinet All Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
P
Privacy International News Feed
IT之家
IT之家
L
LINUX DO - 最新话题
B
Blog RSS Feed
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

Pierce Freeman

A browser for agents | Pierce Freeman The grey market of podcast appearances The way I travel | Pierce Freeman Fixing slow AWS uploads | Pierce Freeman Local tools should still use vaults We solved scratch content first Starting a podcast in 2025 Being late but still being early Automating our home video imports Adding my parents to tailscale A deep dive on agent sandboxes Language servers for AI | Pierce Freeman My simple home podcast studio We need centralized infrastructure | Pierce Freeman Coercing agents to follow conventions using AST validation My unified theory of social selling My personal backup strategy | Pierce Freeman July updates to the homelab How the KV Cache works httpx is the right way to do web requests in Python Reputation is becoming everything | Pierce Freeman Updated knowledge in language models Making an ascii animation | Pierce Freeman How speculative decoding works | Pierce Freeman Under the hood of Claude Code Doing things because they're easy, not hard Speeding up sideeffects with JIT in mountaineer Firehot for hot reloading in Python Misadventures in Python hot reloading How text diffusion works | Pierce Freeman The tenacity of modern LLMs The ergonomics of rails | Pierce Freeman How language servers work | Pierce Freeman Just add eggs | Pierce Freeman Unfortunately SEO still matters | Pierce Freeman The futility of human-only web requirements Setting up Input Leap | Pierce Freeman Checking in on Waymo | Pierce Freeman The react revolution | Pierce Freeman Speeding up many small transfers to a unifi nas Quick notes on swift libraries AI engineering is a different animal San Francisco | Pierce Freeman Debugging a mountaineer rendering segfault Local network config on macOS Building our home network | Pierce Freeman Introducing Envelope.dev | Pierce Freeman Legacy code and AI copilots Typehinting from day-zero | Pierce Freeman Generating database migrations with acyclic graphs Lofoten | Pierce Freeman Mountaineer v0.1: Webapps in Python and React Constraining LLM Outputs | Pierce Freeman Passthrough above all | Pierce Freeman Accuracy in kudos | Pierce Freeman How quick we are to adapt The curious case of LM repetition Costa Rica | Pierce Freeman Debugging chrome extensions with system-level logging Speeding up runpod | Pierce Freeman Inline footnotes with html templates Parsing Common Crawl in a day for $60 An era of rich CLI All or nothing with remote work The Next 10 Years | Pierce Freeman Adding wheels to flash-attention | Pierce Freeman LLMs as interdisciplinary agents | Pierce Freeman New Zealand | Pierce Freeman Representations in autoregressive models | Pierce Freeman Let's talk about Siri | Pierce Freeman Minimum viable public infrastructure | Pierce Freeman Reasoning vs. Memorization in LLMs Automatically migrate enums in alembic Greater sequence lengths will set us free On learning to ski | Pierce Freeman Dolomites | Pierce Freeman Using grpc with node and typescript Opportunity years | Pierce Freeman Buzzword peaks and valleys | Pierce Freeman Buenos Aires | Pierce Freeman Network routing interaction on MacOS Independent work: November recap | Pierce Freeman Debugging slow pytorch training performance The provenance of copy and paste Debugging tips for neural network training Patagonia | Pierce Freeman Santiago | Pierce Freeman My 2022 digital travel kit AWS vs GCP - GPU Availability V2 Independent work: October recap | Pierce Freeman Planning Patagonia | Pierce Freeman Relationship modeling | Pierce Freeman The power of status updates A new chapter | Pierce Freeman Give my library a coffee shop AWS vs GCP - GPU Availability V1 Switzerland | Pierce Freeman Headfull browsers beat headless | Pierce Freeman Webcrawling tradeoffs | Pierce Freeman Copenhagen | Pierce Freeman
Building a (kind of) invisible mac app
2025-07-20 · via Pierce Freeman

It turns out that Cluely doesn't really work. Or at least doesn't really work all of the time. It advertises that it hides itself from all meeting software, so I built a proof-of-concept in Swift that mirrors this behavior so we can dive a bit deeper into its failure modes.

The core takeaway: macOS only gives us the power to hide windows for legacy window-capture APIs like CGWindowListCreateImage(). Modern screen recorders including QuickTime, Zoom, OBS, and apps using ScreenCaptureKit capture the final composited display output and are unaffected by these techniques. As an Apple Engineer clarified:

Virtually all screen recording/remote control apps operate below the window level, which means kCGWindowSharingState has no effect.

Damn hardware accelerated compositor. In other words - here be dragons.

The window level hierarchy

macOS organizes windows in a strict hierarchy of levels. These are basically the z-index layers for the entire operating system. Every window belongs to a specific level that determines its stacking order relative to other windows.1

Here are the enums that define the common levels of windows:

  • Normal windows: NSWindow.Level.normal (level 0)
  • Floating palettes: NSWindow.Level.floating (level 3)
  • Modal panels: NSWindow.Level.modalPanel (level 8)
  • Main menu: NSWindow.Level.mainMenu (level 24)

These are in the ascending order of the disruptiveness that we expect: floating palettes above normal windows, but the main menu above all. But there are higher levels designed for system-critical functions.

The level we'll experiment with is .assistiveTechHighWindow with CGWindowLevelForKey. This returns a very high z-index level designed for assistive technology overlays.

Building the "invisible" window

My implementation in the video above really boils down to two functions: one to make the window visible to screen recording, and another to hide it.

func makeVisible() {
    guard let window = window else { return }
    isVisible = true
    
    // Reset to normal floating window behavior
    window.sharingType = .readOnly
    window.level = .floating
    window.collectionBehavior = [.canJoinAllSpaces, .stationary]
    
    window.makeKeyAndOrderFront(nil)
    window.orderFrontRegardless()
}

func makeInvisible() {
    guard let window = window else { return }
    isVisible = false
    
    // Use assistive technology window level - note: this has no documented
    // capture protection properties, just a very high z-index
    window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.assistiveTechHighWindow)))
    window.collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle]
    window.sharingType = .none
    
    window.makeKeyAndOrderFront(nil)
    window.orderFrontRegardless()
}

The window.sharingType = .none prevents legacy window-capture APIs from reading the window content. They'll see a black rectangle instead.

Sharing window

To fully hide this black rectangle, we need to set the window layer to the higher value. Once you do this, the full window disappears.

NSWindow > SwiftUI

The UI itself is straightforward SwiftUI with a twist: we need to bridge between SwiftUI's declarative interface and AppKit's imperative window management.

@main
struct FloatingWindowApp: App {
    @StateObject private var windowController = FloatingWindowController()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(windowController)
        }
        .windowStyle(.hiddenTitleBar)
        .windowResizability(.contentSize)
        .defaultPosition(.topLeading)
    }
}

The FloatingWindowController class manages the NSWindow reference and window state:

class FloatingWindowController: ObservableObject {
    @Published var isVisible = true
    private var window: NSWindow?
    
    func setWindow(_ window: NSWindow) {
        self.window = window
        configureWindow()
    }
    
    private func configureWindow() {
        guard let window = window else { return }
        
        // Configure for floating behavior
        window.level = .floating
        window.collectionBehavior = [.canJoinAllSpaces, .stationary]
        window.isMovableByWindowBackground = true
        window.backgroundColor = NSColor.clear
        window.isOpaque = false
        window.hasShadow = true
        
        window.orderFrontRegardless()
    }
}

The @Published property triggers SwiftUI updates when visibility changes, and orderFrontRegardless() ensures the window stays on top regardless of which application has focus. The bridge between SwiftUI and the underlying NSWindow happens in the view's onAppear:

struct ContentView: View {
    @EnvironmentObject var windowController: FloatingWindowController
    
    var body: some View {
        // ... UI implementation ...
        .onAppear {
            if let window = NSApplication.shared.windows.first {
                windowController.setWindow(window)
            }
        }
    }
}

This retrieves the first (and only) window from the application and hands it to our controller. From that point, we have full AppKit control over window behavior while maintaining SwiftUI's reactive UI updates.

Why this works (and why it doesn't)

The technique reveals how macOS separates different types of screen capture2:

Window-level capture (legacy)

When applications use legacy APIs like CGWindowListCreateImage(), the system:

  1. Enumerates windows using CGWindowListCopyWindowInfo()
  2. Respects the kCGWindowSharingState flag (set via NSWindow.sharingType)
  3. Either omits windows with sharingType = .none or renders them as black rectangles

Display-level capture (modern)

Most modern screen recording operates differently:

  1. Compositor Rendering: The Quartz Compositor merges all visible windows (regardless of sharingType) into the final frame buffer
  2. Final Image Capture: ScreenCaptureKit, QuickTime, and remote desktop apps capture this composited output
  3. No Window Awareness: These systems don't know or care about individual window properties—they just capture what's actually displayed

Conclusion

Cluely pitches itself as being undetectable:

Cluely website

Reports came in after a recent Zoom update that you might be out of luck on that score. I suspect Zoom just upgraded to using CaptureKit - perhaps in part to thwart apps like Cluely?

Google Chrome however still uses the old APIs so any web sharing through the browser can still circumvent these protections. Chromium has a feature flag as part of ticket #40219528 that supports the new APIs. It just isn't the default behavior quite yet because of a few outstanding Sonoma bugs.

It gets to a deeper truth, though. If you don't fully control the system you can't really rely on these hacks to provide any kind of strong guarantees. Cheating is a dubious art for a reason. It might just get you rejected from a job application instead of put into detention.

  1. There's some amazing (though unverified) story of one of the original Apple window engineers getting into a car accident right as they cracked the code for the windowing layer. When they came-to in the hospital they started talking about the algorithm. ↩

  2. Tested on macOS Sequoia 15. NSWindowSharingNone is already marked as legacy by Apple, and I imagine future versions may further limit or remove this behavior. ↩