Developer Preview (0.1.1). A Swift package that lets your app's own native fullscreen windows use the whole MacBook display — including the areas left and right of the camera housing ("notch") — while staying real macOS fullscreen Spaces.
A real screen capture of the strip beside the camera housing:
Examples/FullPanelDemo in native fullscreen on a MacBook Pro 14-inch
(macOS 27.0.1, captured with an earlier development build). The hatched middle section lies behind the camera housing.
More captures: docs/INTEGRATION_EVIDENCE.md.
Warning
FullPanelKit relies on private AppKit and SkyLight interfaces. A macOS update can break it; when it can detect that, it falls back to ordinary fullscreen and tells you why. It is not suitable for the Mac App Store. Verified on one Mac (MacBook Pro 14-inch, M1 Pro) running macOS 27.0.1 — see docs/COMPATIBILITY.md.
- Your window enters AppKit's own fullscreen Space (green button, View ▸
Enter Full Screen, Control-Command-F,
toggleFullScreen(_:), or AppKit restoring it at launch). FullPanelKit makes that Space's window cover the whole display and hides the fullscreen menu bar over the housing band, so your content is drawn and clickable beside the housing. - Mission Control, Spaces, Split View, the menu bar (revealed at the very top edge), the Dock and fullscreen exit keep working as for any fullscreen app.
- Works with windows you create (
FullPanelWindow) and windows you don't (FullPanelSession, e.g. SwiftUIWindowGroupwindows). - Falls back to ordinary fullscreen, with a reported reason, on displays without a housing, in Split View, when "Always show menu bar in full screen" is on, or when the private interfaces are missing or changed.
- Reports the housing geometry so you can keep content away from the camera.
- It never enters or leaves fullscreen for you, and never adds overlay or mirror windows.
- It does not lay out your content: you decide what goes beside the housing
(
FullPanelLayouttells you where that is). - It cannot change other applications, plain
NSWindow/NSPanelinstances, or non-native ("borderless") fullscreen. - It does not adopt a window that is already fullscreen; FullPanel applies from that window's next entry.
- It does not make Mission Control transitions seamless.
- It does not make web content notch-aware. A browser or web view gets a window that covers the strip; laying out or scrolling page content around the housing is up to the host application.
Many apps reach the pixels beside the notch with an "old-style" borderless window sized to the screen. That window stays on the desktop Space: there is no fullscreen Space in Mission Control, no swipe between Spaces, and the green button does not apply. FullPanelKit keeps the native fullscreen Space and lets AppKit lay the window out across the display while coordinating the menu bar. Other approaches and prior work (UTM, WezTerm, MacNotchKiller, …): docs/PRIOR_ART.md.
- macOS 13 or later to build (
platforms: [.macOS(.v13)]); verified only on macOS 27.0.1, MacBook Pro 14-inch (M1 Pro). For macOS 15.8.1 the private AppKit method FullPanelKit relies on was only inspected statically (present by name); nothing was run there with the current code. No runtime claim is made for macOS 13, 14, 15 or 26. - A MacBook with a camera housing for FullPanel; elsewhere you get ordinary fullscreen.
- Swift 5.9 or newer. No dependencies.
- Your window must be a
FullPanelWindow, or an instance of a class that is not one of AppKit's own (yourNSWindowsubclass, SwiftUI's windows).
Swift Package Manager (release tags v0.1.0, v0.1.1; use 0.1.1 or later):
// Package.swift
dependencies: [
.package(url: "https://github.com/brianmerchant/FullPanelKit.git", from: "0.1.1"),
],
targets: [
.target(name: "MyApp", dependencies: [
.product(name: "FullPanelKit", package: "FullPanelKit"),
]),
]For local development instead, use .package(path: "../FullPanelKit").
In Xcode: File ▸ Add Package Dependencies… and enter the repository URL;
then add the FullPanelKit library to your app target.
import FullPanelKit
let window = FullPanelWindow(
contentRect: NSRect(x: 0, y: 0, width: 960, height: 620),
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered, defer: false)
window.contentView = MyView()
window.fullPanelEnabled = true // also sets .fullSizeContentView, a transparent titlebar, .fullScreenPrimary
window.makeKeyAndOrderFront(nil)
// The user (or your code) enters fullscreen as usual: window.toggleFullScreen(nil)Already have your own NSWindow subclass, window controller and delegate?
Keep them and attach a session instead:
final class MainWindow: NSWindow {} // your class (not plain NSWindow)
let session = try FullPanelSession.attach(to: window) // idempotent
session.isEnabled = true // before fullscreen is enteredLay out content with the geometry, in top-left content coordinates:
let layout = window.fullPanelLayout // or session.layout
if layout.isActive, let left = layout.leftArea, let right = layout.rightArea {
// put content in `left` and `right`; keep `layout.cameraHousing` clear
}Observe changes with FullPanelWindow.stateDidChangeNotification /
FullPanelSession.stateDidChangeNotification, or KVO on fullPanelState /
state:
switch session.state {
case .disabled, .inactive: break
case .active: break // content beside the housing
case .fallback: print(session.fallbackReason.summary) // ordinary fullscreen, and why
@unknown default: break
}SwiftUI WindowGroup windows are attached where they are created:
import Combine
import FullPanelKit
import SwiftUI
struct WindowAttacher: NSViewRepresentable { // hands you the hosting NSWindow
let onWindow: (NSWindow?) -> Void
func makeNSView(context: Context) -> NSView { let v = V(); v.onWindow = onWindow; return v }
func updateNSView(_ v: NSView, context: Context) { onWindow(v.window) }
final class V: NSView {
var onWindow: ((NSWindow?) -> Void)?
override func viewDidMoveToWindow() { super.viewDidMoveToWindow(); onWindow?(window) }
}
}
@MainActor final class PanelModel: ObservableObject {
@Published private(set) var layout = FullPanelLayout()
private var status: FullPanelStatus?
private var sink: AnyCancellable?
func attach(_ window: NSWindow?) { // synchronous: see below
guard let window, status == nil,
let session = try? FullPanelSession.attach(to: window) else { return }
session.isEnabled = true
let status = FullPanelStatus(session: session)
self.status = status
sink = status.$layout.receive(on: DispatchQueue.main).sink { [weak self] in self?.layout = $0 }
}
}
struct ContentView: View {
@StateObject private var panel = PanelModel()
var body: some View {
ZStack(alignment: .topLeading) {
Color.black
if let left = panel.layout.leftArea {
Text("Left of the camera").frame(width: left.width, height: left.height)
.offset(x: left.minX, y: left.minY)
}
}
.ignoresSafeArea() // let content reach the band
.background(WindowAttacher(onWindow: panel.attach))
}
}Attach synchronously from viewDidMoveToWindow (as above) so that a window
AppKit restores straight into fullscreen is attached before its entry.
Examples/FullPanelAttachDemo is a complete app.
If you create the window yourself, host SwiftUI in a FullPanelWindow and
read @Environment(\.fullPanel):
let window = FullPanelWindow(contentRect: rect) { ContentView() } // reads \.fullPanel
window.fullPanelEnabled = true- Main thread only, like
NSWindow. - Enable before the entry. A window enabled or attached after its
fullscreen entry began stays in ordinary fullscreen for that entry and
reports
.fallbackwithdiagnostics()["adoption"]. - Plain
NSWindow/NSPanelare refused (FullPanelSessionError.unsupportedWindowClass): attaching to AppKit's own window classes is not supported. UseFullPanelWindowor a one-line subclass. - No class changes. Attaching adds no method to the window's class. FullPanelKit replaces one method of AppKit's private fullscreen content controller, process-wide, from the first enable on; it keeps AppKit's answer for every window that is not in a FullPanel entry.
- Full-size content. Enabling adds
.fullSizeContentView; macOS keeps the content rect, so a framed window grows by its titlebar. Create windows with.fullSizeContentViewand a transparent titlebar to avoid the shift. - Ownership. The window owns its session; you don't need to keep a
reference.
detach()restores everything; otherwise the session ends with the window. Closing restores system state but keeps the session (a window can be shown again). FullPanelWindow.delegateis your own delegate object.
Full contract: docs/ATTACHMENT.md.
Each FullPanel window is an ordinary fullscreen Space in Mission Control.
After a return (Escape, the large preview, a Space thumbnail, a Space switch)
the band beside the housing comes back as WindowServer settles. On this
release's code, 100 of 100 Space-thumbnail returns in Mission Control came
back to the window, active and key, with the band filled (macOS 27.0.1,
FullPanelWindow; about 0.5 s from the click inside the Space, 0.65 s from
another app's Space). That bounds the failure rate on this Mac below about
3 % at 95 % confidence; it does not prove it is zero
(docs/TESTING.md).
It is not seamless: during the transition the band can briefly show black or
Mission Control's backdrop. Dock clicks and Command-Tab behave exactly as for native
fullscreen, including the Desktop & Dock setting "When switching to an
application, switch to a Space with open windows for the application".
- Private API: may break with a macOS update; not for the Mac App Store.
- Verified on one Mac and one macOS release; physical external displays and other Macs are not verified. Lock/unlock and display sleep/wake passed manual acceptance M2 on this release (3/3). See docs/COMPATIBILITY.md.
- Already-fullscreen windows are not adopted in place.
- If WindowServer takes the strip away from a fullscreen session, it stays away until the next entry; FullPanelKit never forces a frame.
- A crash while in FullPanel fullscreen can leave that Space's menu bar transparent until the Space closes.
Verified on a MacBook Pro 14-inch (M1 Pro), macOS 27.0.1. "This release" means this repository's code; "earlier build" means a development build with the previous geometry mechanism. Evidence for every entry (source, revision, check lists, real screen captures, gaps): docs/INTEGRATION_EVIDENCE.md.
| Integration | Example | Status |
|---|---|---|
AppKit, FullPanelWindow |
Examples/FullPanelDemo, Tools/SpacesHarness |
Hardware verified. This release: 100 Mission Control returns; Dock, Command-Tab and Space switches; manual M1 8/8 including physical clicks and menu-bar reveal. Earlier build: display-mode changes |
SwiftUI WindowGroup, FullPanelSession |
Examples/FullPanelAttachDemo |
Hardware verified. This release: autotest 82/82, lifecycle 28/28 (also under Zombies), restoration scenarios 77/77 including Mission Control returns |
Your own NSWindow subclass, FullPanelSession |
Examples/FullPanelAttachDemo (AppKit mode), Tools/SpacesHarness |
Hardware verified. This release: autotest 49/49; Mission Control, Dock and Command-Tab via Tools/SpacesHarness |
| Electron 44.7.0 | Integrations/Electron |
Experimental. This release: 63/63 automated checks. Earlier build: real strip capture. Mission Control not verified with the bridge |
| Tauri 2.12.3 | Integrations/Tauri |
Experimental. This release: 101/101 automated checks. Earlier build: real strip capture. Mission Control not verified with the bridge |
| winit 0.30 | Integrations/Winit |
Experimental. This release: 140/140 automated checks. Earlier build: real strip capture. Mission Control not verified with the bridge |
The Electron, Tauri and winit bridges are part of this repository, compile
the same FullPanelKitCore sources, and are not affiliated with or endorsed
by those projects.
Examples/FullPanelDemo—FullPanelWindow, AppKit and SwiftUI variants; the place to start.Examples/FullPanelAttachDemo— SwiftUIWindowGroup+FullPanelSession, with the hardware scenarios.Tools/SpacesHarness— native-fullscreen-vs-FullPanel comparisons (Mission Control, activation, display modes, display migration).Tools/ConsumerCheck— builds a consumer against a clean clone.Integrations/Electron,Integrations/Tauri,Integrations/Winit— bridges with test apps; each README covers build, API and tests.
diagnostics() / fullPanelDiagnostics() return a snapshot (keys may
change). Launch with -FullPanelTrace YES for a timestamped engine log.
Other defaults (FullPanelEarlyApply, FullPanelRevealIntegration,
FullPanelLegacyModes) exist only for diagnosis; the defaults are the tested
configuration.
| ARCHITECTURE.md | engine, lifecycle, Mission Control, private API risks |
| ATTACHMENT.md | FullPanelSession contract |
| COMPATIBILITY.md | what is verified, and what is not |
| TESTING.md | test suites, hardware harnesses, results |
| INTEGRATION_EVIDENCE.md | per-integration evidence and status |
| MANUAL_ACCEPTANCE.md | checks that need a person at the Mac |
| RESTORED_FULLSCREEN.md, WINDOW_LIFECYCLE.md | investigations |
| PRIOR_ART.md | related projects and claims to avoid |
| RELEASE_CHECKLIST.md | what must pass before publication |
| CHANGELOG.md | changes |
FullPanelKit is an open-source project created and directed by Brian Merchant, with development assistance from Anthropic Claude Code and OpenAI Codex.
AI-assisted workflows have been used for implementation, architectural research, debugging, testing, and documentation. Project decisions, integration, and release approval remain under human direction. Details, including what AI assistance does and does not establish: docs/PROVENANCE.md.
FullPanelKit 0.1.0 is the SDK's first public release. Its implementation and integration APIs are maintained separately from the projects acknowledged here. This release contains no UTM, Try Omarchy, or OmacVM source code according to the release-source audit.
UTM is important prior work on native fullscreen around the MacBook camera housing. Try Omarchy PR #361, by themartiano, inspired the AppKit fullscreen menu-bar reservation approach. OmacVM, maintained by Gilles Goetsch, was the original application context for FullPanel development. yabai and CGSInternal were references for platform interfaces.
See Provenance and acknowledgments and Third-party notices for the release's scope and license obligations.
MIT (LICENSE). Copyright (c) 2026 Brian Merchant. This repository contains no third-party source code; the optional integrations' build-time dependencies and their license terms are listed in THIRD_PARTY_NOTICES.md.
