Cleaning a MacBook is two problems at once. A lit screen hides the very smudges you are trying to remove, so you wipe blind and find the streaks an hour later in a dark room. And a cloth dragged across the keyboard types into whatever is open — renames files, fires shortcuts, occasionally deletes something you will miss.
Closing the lid solves neither, since you cannot clean the glass with the lid shut. Locking the screen leaves the keyboard live too — now you are just wiping crumbs into a password field.
WashMyMac blacks out every display, kills the keyboard, trackpad and mouse, and gives the Mac back when you hold a combination that a cloth cannot press.
- Pure black on every display. Not a screensaver, not a dimmed desktop — a flat black field where every smudge, fingerprint and dust speck shows up.
- Input goes dead. Keyboard, trackpad and mouse, system shortcuts included: ⌘Space, ⌘Tab, ⌘Q, volume, brightness, trackpad gestures.
- An exit a cloth cannot trigger. ⌃⌥⌘U, held — and only while not a single other key is down.
- Auto-exit, always on. One to thirty minutes. There is deliberately no "never": it is the one guaranteed way out.
- The display cannot doze off. A power assertion plus a heartbeat, because with every event swallowed macOS is convinced nobody is home.
- A beacon, so you know it is alive. Once the hint fades, a dim pulsing dot keeps the exit combination on screen — an empty black display looks exactly like a dead one.
- Kiosk mode. Dock and menu bar stay hidden even if you shove the cloth into the edge of the screen.
- Nothing can get stuck. Both layers live only as long as the process: kill the app and the lock lifts itself.
- English and Russian, following your system language.
- Plain Swift and AppKit. No dependencies, no daemon, no telemetry, no network.
- Universal binary, Apple Silicon and Intel.
Download the .dmg from the latest release,
open it and drag WashMyMac.app onto the Applications folder in the window.
Note
The app is ad-hoc signed rather than signed with an Apple Developer certificate, so macOS refuses the first launch. On macOS 15 and newer, Control-click no longer bypasses that: open the app once, let it be blocked, then go to System Settings → Privacy & Security and press Open Anyway. One-time.
Important
Move it to /Applications before granting Accessibility. macOS ties that permission to
the app's path and code hash, so relocating the app afterwards silently revokes it — which
is exactly what happens if you run it once out of ~/Downloads.
The app lives in the menu bar and has no Dock icon. First time you start cleaning mode it will ask for Accessibility access — see below for what changes if you decline.
| Action | How |
|---|---|
| Start cleaning mode | ⌃⌥⌘W, or menu bar → Wipe My Mac |
| Exit | ⌃⌥⌘U, held for 1.5 s |
| Bring the hint back | ⌃⌥⌘ without U, held briefly |
| Exit when nothing else works | wait for auto-exit — five minutes out of the box |
For the first few seconds the ring in the middle counts down to auto-exit. Then the hint dissolves and the screen goes properly black, leaving only the beacon at the bottom.
Hold ⌃⌥⌘U and the same ring fills up over 1.5 seconds. Let go early and it drops to zero.
The combination counts only when exactly three modifiers and the U key are down — not one extra key. A cloth always presses neighbours, so it can never satisfy the rule no matter where you drag it. Shift held, or any second letter down, and the countdown resets.
The same logic guards the hint gesture: ⌃⌥⌘ with nothing else brings the text back, so you can remind yourself of the combination without ending the session.
Three layers, because macOS hands out input at three different depths:
| Layer | Blocks | Needs permission |
|---|---|---|
CGEventTap at session level |
Everything — keys, mouse, scroll, gestures, media keys, system shortcuts | Accessibility |
Window at CGShieldingWindowLevel() |
Everything routed to the app; covers the menu bar, the Dock and the notch | No |
NSApplicationPresentationOptions |
⌘Tab, ⌘⌥⎋, Dock, menu bar, shutdown from the Apple menu | No |
The tap sits at the head of the event stream, ahead of the WindowServer, so it consumes
system hotkeys before anything else sees them. It also means macOS stops seeing user
activity entirely — hence DisplayKeeper, which holds a NoDisplaySleep assertion and
declares user activity every 20 seconds so the screen never dims mid-wipe.
Every layer is owned by the process. If the app crashes, the tap dies, the presentation options revert and the window disappears — the lock cannot outlive the thing that created it.
Full input blocking needs one permission:
System Settings → Privacy & Security → Accessibility → WashMyMac
The app offers to open that pane the first time you start cleaning mode.
Without it WashMyMac still works — the screen goes black, the shield window covers everything, ordinary keystrokes do nothing. But system shortcuts are handled by the WindowServer before any app sees them, so ⌘Space opens Spotlight behind the black screen and Ctrl+↑ throws you into Mission Control, blind. The overlay shows an orange warning while running in that mode.
Important
The app is ad-hoc signed, so its code hash changes on every rebuild — and macOS keys the
permission to that hash. After building a new version, remove the old entry with − and
grant it again. tccutil reset Accessibility com.ganin.washmymac clears any leftovers.
macOS limits, not missing features:
- Power button and Touch ID put the Mac to sleep or lock it. Handled in hardware, no API intercepts it — and that makes it your emergency exit.
- Holding the power button force-restarts, same story.
- Secure Input. If the frontmost app has secure input enabled — an open password field, some terminals — the tap never receives keystrokes. Close the password field first.
- Caps Lock toggles its LED; the keystroke itself is swallowed.
./build.sh --install # → /Applications/WashMyMac.app
./build.sh # → .build/bundle/WashMyMac.app
./tools/make-dmg.sh # → dist/WashMyMac-v<version>.dmgXcode or the Command Line Tools is the only requirement. The universal binary, the bundle,
the localizations and the icon are all assembled by that one script — the icon is drawn in
code by tools/make_icon.swift, so no binary asset ships in the
source tree. A missing translation fails the build:
tools/check-localization.sh diffs the L.t("…") keys in the
sources against every Localizable.strings.
To look at the design without locking anything up:
/Applications/WashMyMac.app/Contents/MacOS/WashMyMac --previewAdd --unlocking for the unlock ring, --beacon for the state after the hint fades,
--degraded for the missing-permission warning, and -AppleLanguages '(en)' to force a
language.
Releases are cut by release-please from
Conventional Commits: merge a feat: or fix:
commit to main, and it opens a release PR that bumps the version and writes the changelog.
Merging that PR tags the release and attaches the built .zip.
macOS 13 Ventura or newer. Apple Silicon and Intel. The interface follows your system language — English or Russian.

