Expand description
Liquid Glass windows on macOS, that follow the system Icon & widget style.
macOS 26 introduced NSGlassEffectView, the material behind Liquid Glass.
Putting a window on it is the easy part. The hard part — and what this crate
is actually for — is making that window agree with
System Settings ▸ Appearance ▸ Icon & widget style, the setting that
restyles desktop widgets, and keeping it in agreement as the user changes it.
That turns out to involve a preference key with nine tokens behind a
four-option UI, a notification that no notification centre posts, and a
key-value observer that must never read a preference from inside its own
callback. Each of those is documented where it is handled, with the
measurement that established it. MEASUREMENTS.md in the repository is the
long form.
§Features
| feature | default | what it brings |
|---|---|---|
glass | yes | the glass::GlassSurface wrapper and its availability guard |
window | yes | a transparent window hosting one glass surface, titled or borderless |
icon-style | yes | the Icon & widget style tracker — usable on its own |
private-spi | no | two undocumented selectors; see below |
is_dark and accessibility are behind no feature: the first is the
crate’s only light/dark resolver and both halves need it, and the second is
an obligation rather than an option.
§Accessibility
A translucent surface has to answer to Reduce Transparency. This crate
reports that setting through accessibility and does not act on it for
you — NSGlassEffectView has no opacity control, so honouring it means not
using the material at all and substituting opaque content, which is the
caller’s to build. That module states the reasoning in full and shows the
shape to write. Check it before you construct a surface.
private-spi is off because App Store Review Guideline 2.5.1 is “Apps may
only use public APIs”, with no respondsToSelector: exemption — a crate
reaching private selectors by default would hand every consumer a submission
liability they never opted into. Without it icon-style still tracks the
style; only icon_style::WidgetStyle::tint stops reporting a colour, because
the theme colour has no public source.
§Example
Compiled as a doctest, so it cannot drift from the API the way a README snippet can.
The example needs window, glass and icon-style together, so cfg_attr
picks the opening fence — runnable when all three are on, ignore otherwise
— which keeps cargo test green under every other feature set, including
default-features = false, features = ["icon-style"], the standalone-tracker
configuration this crate advertises. Verify a change here with cargo test,
not cargo check: check does not compile doctests.
use macos_liquid_glass::glass::{GlassStyle, GlassSurface};
use macos_liquid_glass::window::GlassWindow;
use objc2_foundation::{MainThreadMarker, NSPoint, NSRect, NSSize};
let mtm = MainThreadMarker::new().expect("main thread");
let size = NSSize::new(560.0, 360.0);
let frame = NSRect::new(NSPoint::new(0.0, 0.0), size);
let window = GlassWindow::new(mtm, size, "example");
let glass = GlassSurface::new(mtm, frame, GlassStyle::Clear, 16.0)?;
window.set_content_view(glass.view());
// Track the Icon & widget style for as long as the window lives. The
// window owns the tracker, so there is nothing to keep hold of.
window.follow_icon_style();
window.show();§Driving your own drawing from the style
GlassWindow::follow_icon_style sets the window’s appearance and
nothing else. To colour your own content from the style — a theme tint, a
dimming layer, per-token drawing — observe it directly and keep the observer
alive for as long as you want changes, because dropping it is what
unregisters:
let _observer = StyleObserver::new(mtm, move |style| {
match style.token() { /* all nine are distinguishable here */ }
let tint = style.tint();
});icon-style carries no dependency on window, so a consumer that already
has an NSWindow takes the tracker by itself and applies the style however
it draws:
macos-liquid-glass = { version = "1.0.0-beta.1", default-features = false, features = ["icon-style"] }§Threads
Everything here is main-thread-only and none of it is Send or Sync —
GlassWindow, GlassSurface and StyleObserver all hold objects that are
MainThreadOnly, so the compiler will not let one cross a thread boundary.
That is enforcement, not convention: you cannot construct any of them
without a MainThreadMarker, and you cannot move one to a thread that has
no such marker.
The observer’s callback is delivered on the main thread too, so it may touch
AppKit freely. It is Fn rather than FnMut because it can re-enter — see
icon_style::StyleObserver::new.
accessibility is the exception, and reads NSWorkspace, which is not
main-thread-only.
§Platform
macOS only. On every other target this crate is empty rather than absent, so
a cross-platform consumer can depend on it unconditionally and gate at the
call site. The glass surface additionally requires macOS 26 at runtime —
NSGlassEffectView does not exist before it — which is checked rather than
assumed.
Re-exports§
pub use objc2;macOS pub use objc2_app_kit;macOS pub use objc2_core_foundation;macOS pub use objc2_foundation;macOS pub use objc2_io_surface;macOS and drawablepub use objc2_quartz_core;macOS and drawable
Modules§
- accessibility
macOS - The accessibility display settings a translucent surface has to answer to.
- drawable
macOS and drawable - A content view something else paints into: an
NSViewwhose layer shows anIOSurface, with a display link, the input events a responder wants, and the backing-scale bookkeeping that AppKit leaves to the view. - glass
macOS and glass - The Liquid Glass material surface.
- icon_
style macOS and icon-style - Tracking System Settings ▸ Appearance ▸ Icon & widget style.
- menu
macOS - The application’s main menu: the three menus every macOS app carries,
wired to the standard responder-chain actions, so
Cmd-Qquits,Cmd-Wcloses the window,Cmd-Mminimises, andCmd-C/Cmd-Vreach the first responder’scopy:andpaste:– whichDrawableViewforwards to itsResponder::command. Both are behind thedrawablefeature, so these are named rather than linked: this module is not gated on it, and an intra-doc link into a module the default features do not build resolves nowhere. - window
macOS and window - A transparent window suitable for hosting a glass surface.
Functions§
- appkit_
version macOS - The AppKit the process is running against, as
NSAppKitVersionNumber– the build number a launch measurement should be recorded beside. Reading it is also the one reference to an AppKit symbol a binary that reaches every class by name through the runtime has, which is what keeps the framework’s load command when the link dead-strips the dylibs nothing references:-needed_frameworkonly works ahead of the-frameworkthe bindings emit, and a build script’s link arguments come after it. - icon_
view macOS - An
NSImageViewshowingimage, scaled to fit — for a titlebar strip or anywhere the caller then positions withsetFrame/autoresizing. - image_
from_ bytes macOS - Decode encoded image bytes (PNG, JPEG, …) into an
NSImage, orNoneif the data is not a decodable image. - is_dark
macOS - Whether an appearance resolves to Dark.
- set_
application_ icon macOS - Set the application’s dock and Cmd-Tab icon.