Record a piece of your Mac screen to a GIF or MP4, from the menu bar.
Native Swift. No accounts, no cloud, no telemetry.
- Pick an area, a whole screen, or a window and record it to a GIF or an H.264 MP4.
- Stay under a size limit. Tell it "max 100 MB" (Signal) or "max 8 MB" (Discord) and every recording is shrunk to fit before it is saved.
- Keep a library. Every clip lands in one folder, shows up in the app with its size, and can be compressed later to "10 MB" or "720p" — replacing the original or as a sibling file.
- Talk over it. MP4 recordings can carry your microphone, with a live level meter so you know it hears you.
- Show your face. An optional round facecam bubble, positioned by dragging a live preview.
- Update itself from GitHub Releases.
brew install --cask note89/tap/screensnapThis installs the same signed, notarized build as the download below, with no dependencies. Screensnap updates itself, so the cask is marked auto_updates and brew upgrade leaves it alone. Then start a recording from the menu bar icon and grant Screen Recording as in step 4 below.
- Grab
Screensnap-x.y.z.zipfrom the latest release. - Unzip it and drag Screensnap.app into your Applications folder.
- Double-click it. Releases are signed with a Developer ID and notarized by Apple, so Gatekeeper opens them without a warning.
- Start a recording from the menu bar icon. macOS asks for Screen Recording permission — grant it, then choose Relaunch in the app when it offers.
That is the whole setup. Screensnap has no Dock icon; it lives in the menu bar.
You need the Xcode command-line tools (xcode-select --install) and macOS 14 or newer.
git clone https://github.com/note89/screensnap.git
cd screensnap
./Scripts/setup-signing.sh # once, optional — keeps permissions across rebuilds (see below)
./Scripts/build-app.sh # builds ./build/Screensnap.app
open build/Screensnap.appUse ./Scripts/build-app.sh release for an optimised build. Copy build/Screensnap.app to /Applications if you want it to stick around.
-
Click the Screensnap icon in the menu bar and choose Record area, Record full screen, or Record window.
-
For an area, drag a rectangle on any screen. For a window or a screen, pick one from the thumbnails.
-
A small pill appears at the bottom of the screen showing the timer, the output format, and the frame size. Click Finish on it — or press ⌘⇧. from anywhere.
-
The pill shows the saved file and its size, and the recording is on your clipboard. Press ⌘V in Slack, Signal, iMessage, Discord, or a browser to paste it.
The ⌘⇧. hotkey does the obvious thing at every moment: starts a recording in the last used mode, cancels a countdown, or finishes the recording in progress.
Open the app (menu bar → Open Screensnap…) and go to Output.
| Format | Audio | Notes |
|---|---|---|
| GIF | no | Plays everywhere. Encoded by the built-in ImageIO encoder, ready in about a second. |
| GIF · best | no | Same GIF, encoded by gifski for smoother colour and smaller files. Releases bundle gifski; a build without it says so on the card. |
| MP4 | optional | H.264, roughly a tenth of the size of a GIF. The only format that can carry your voice. |
Size limit is where Screensnap earns its keep. Choose a preset — 8 MB (Discord free), 25 MB (Gmail, Slack), 100 MB (Signal) — or type your own. A recording that lands over the limit is shrunk right after encoding (smaller frame first, then fewer frames) until it fits, and the pill reports the final size. If it cannot get under the limit it says so instead of guessing.
Every clip is saved to one folder — ~/Movies/Screensnap by default — and the Recordings pane is a view of that folder. Change the folder, and the library follows; drop a file into the folder from Finder, and it appears in the list.
Each row shows a thumbnail, dimensions, duration, size, and date, and has four actions: Copy (to the clipboard), Show (in Finder), Compress…, and Trash. Double-click a name to rename it.
Compress… opens a small popover: pick a target — a size such as 10 MB or a resolution such as 720p — and whether to keep both files (the copy is named original-10MB.mp4) or replace the original (the original goes to the Trash, so it is recoverable).
- Facecam (Facecam pane): turn on the bubble and a live preview appears when recording starts. Drag it anywhere inside the recorded area — the bubble in the file sits exactly where the preview is. If the camera is busy or denied, the recording still happens and the pill tells you why the bubble is missing.
- Voice (Output pane, MP4 only): turn on Record my voice. The pill shows a level meter while recording. GIFs cannot carry audio, so the option only exists for MP4.
| Permission | Needed for | When asked |
|---|---|---|
| Screen Recording | everything | first recording |
| Camera | the facecam bubble | first recording with facecam on |
| Microphone | voice in MP4 | first MP4 recording with voice on |
The Capture pane lists all three with an Open Settings button for each. Screen Recording takes effect after a relaunch; the app offers one.
macOS ties Screen Recording permission to the app's code-signing identity. An ad-hoc signed build gets a new identity every time, so every rebuild would ask for permission again.
On the maintainer's Mac, build-app.sh signs with the same Developer ID as the releases, so local builds and downloaded updates count as one app. Without that certificate, Scripts/setup-signing.sh creates a self-signed certificate called Screensnap Dev in your login keychain, and build-app.sh signs with it automatically from then on. The certificate never leaves your machine and is not committed.
./Scripts/setup-signing.sh # once
security find-identity -v -p codesigning # should list "Screensnap Dev"
security delete-certificate -c "Screensnap Dev" # to remove it laterBuilds signed before the rename with the older GifRecorder Dev certificate keep working; the build script honours both. Every build uses the hardened runtime with Resources/Screensnap.entitlements (camera and microphone), the same as a release.
Screensnap checks GitHub Releases once a day. When a newer version exists the menu shows Update to x.y.z…, and the About pane has an Update and relaunch button that downloads the zip, checks that it is signed by Screensnap's Developer ID team (anything else is refused), swaps the app bundle in place, and relaunches.
If the app sits in a folder you cannot write to, the new build is placed in your Downloads folder instead and the About pane tells you.
gifski produces noticeably better GIFs than the built-in encoder, and GIF · best uses it. Releases carry it inside the app (Contents/Resources/gifski, about 1 MB), built by Scripts/build-gifski.sh from gifski 1.34.0 without its ffmpeg video feature, so it needs nothing else installed.
For a build from source, run ./Scripts/build-gifski.sh (needs Rust) before build-app.sh, or brew install gifski; the app looks in the bundle first, then the usual Homebrew locations. If gifski is missing at recording time the app records with the built-in encoder and says so, rather than failing.
gifski is © Kornel Lesiński and licensed under the AGPL-3.0, separately from Screensnap's MIT licence; Screensnap runs it as its own program. Its source for the bundled version is at ImageOptim/gifski@1.34.0.
Sources/Screensnap/
ScreensnapApp.swift @main: MenuBarExtra, settings Window, app delegate
Coordinator.swift Single owner of app state: phase, session, delivery
Phase.swift The recording state machine as an enum
Output.swift Output (GIF / GIF·best / MP4·voice), SizeLimit, ByteCount, Dimensions
Settings.swift UserDefaults-backed preferences (@Observable)
FilenameTemplate.swift Parsed file name template; new files never reuse a name
MenuView.swift The menu bar menu
MenuBarStatus.swift The menu bar icon: glyph per phase, recording pulse
SettingsWindow.swift Capture / Output / Facecam / Recordings / About panes
HUD/HUDPanel.swift Non-activating floating pill (NSPanel)
HUD/HUDView.swift Pill contents per phase
SourcePicker.swift Display / window picker with live thumbnails
RegionSelector.swift Drag-to-select overlay across all displays
ScreenRecorder.swift ScreenCaptureKit stream → frame sink
Encoders.swift GIF (ImageIO, gifski) and MP4 (AVAssetWriter) encoders
Facecam.swift Camera capture, bubble compositing, draggable preview
Microphone.swift Microphone capture with level metering
Recordings/Recording.swift Recording, MediaInfo, Thumbnail, Clipboard
Recordings/RecordingsStore.swift The library: folder scan + file watcher
Recordings/Compressor.swift Compress to a size or a resolution
Updater.swift GitHub Releases check, download, swap, relaunch
Permissions.swift TCC checks, System Settings deep links, Relaunch
GrantPanel.swift Drag-to-grant tile that follows System Settings
GlobalHotkey.swift Carbon RegisterEventHotKey wrapper
Resources/ Info.plist, AppIcon.icns (generated by Scripts/make-icon.swift)
Scripts/ build-app.sh, release.sh, setup-signing.sh, make-icon.swift
.github/ build.yml — checks that master and pull requests build
docs/ Screenshots used in this README
Releases are built on the Mac that holds the Developer ID key, which never leaves its keychain. It needs the devid-notary notarytool keychain profile.
./Scripts/release.sh 0.3.0 # build, sign, notarize, staple, zip — publishes nothing
./Scripts/release.sh 0.3.0 --publish # the same, then tag v0.3.0, create the GitHub Release, bump the caskThe script stamps the version into Info.plist, signs Screensnap.app with the hardened runtime and a secure timestamp, has Apple notarize it, staples the ticket, zips it as build/Screensnap-0.3.0.zip, and with --publish tags the commit, uploads the zip with generated notes, and sets the new version and SHA-256 in note89/homebrew-tap's Casks/screensnap.rb. --publish checks up front that it can read that cask. Running apps pick it up on their next daily check. Tag pushes no longer build anything in CI, so a release cannot go out unsigned.
MIT




