163 lines
6.7 KiB
Markdown
163 lines
6.7 KiB
Markdown
# Snaggle
|
||
|
||
A native, Apple-silicon macOS screenshot tool modelled on [Greenshot](https://github.com/greenshot/greenshot)
|
||
for Windows. Written in Swift with AppKit, SwiftUI and ScreenCaptureKit — no Mono, no Wine,
|
||
no .NET runtime.
|
||
|
||
The original C# sources are kept in `reference-greenshot/` purely as the behavioural reference
|
||
for the port. Snaggle is a rewrite, not a transpile: Greenshot's Win32 foundations
|
||
(GDI+, WinForms, `RegisterHotKey`, the shell tray API) have no direct macOS counterparts.
|
||
|
||
## Requirements
|
||
|
||
* macOS 14 (Sonoma) or later
|
||
* Xcode 16 or later
|
||
* Apple silicon (the target builds `arm64` only)
|
||
|
||
## Building
|
||
|
||
```bash
|
||
open Snaggle.xcodeproj # then ⌘R
|
||
# or
|
||
xcodebuild -project Snaggle.xcodeproj -scheme Snaggle -configuration Release build
|
||
```
|
||
|
||
The target is configured for ad-hoc signing (`CODE_SIGN_IDENTITY = "-"`) so it builds without a
|
||
developer account. Set your own team in *Signing & Capabilities* if you want a stable signature —
|
||
macOS ties Screen Recording permission to the signature, so an ad-hoc build may ask for permission
|
||
again after some rebuilds.
|
||
|
||
On first launch macOS asks for **Screen Recording** permission
|
||
(System Settings › Privacy & Security › Screen Recording). Nothing can be captured until it is granted.
|
||
|
||
## Installing (for people downloading a release)
|
||
|
||
Snaggle is ad-hoc signed rather than notarised, because notarisation requires a paid Apple
|
||
Developer account. It runs perfectly well; macOS just asks you to confirm it once.
|
||
|
||
1. Drag **Snaggle** to Applications and open it. macOS will refuse the first time, saying the
|
||
developer cannot be verified.
|
||
2. Open **System Settings → Privacy & Security**, scroll to Security, and click **Open Anyway**
|
||
next to the message about Snaggle.
|
||
|
||
Control-clicking an app and choosing Open no longer bypasses Gatekeeper — Apple removed that
|
||
shortcut in macOS 15. If you prefer the terminal, `xattr -dr com.apple.quarantine
|
||
/Applications/Snaggle.app` does the same job in one step.
|
||
|
||
Snaggle then needs **Screen Recording** permission (System Settings → Privacy & Security →
|
||
Screen Recording). Grant it, then quit and reopen the app — macOS only hands the permission to
|
||
a freshly launched process.
|
||
|
||
Snaggle lives in the menu bar, not the Dock: look for the viewfinder icon.
|
||
|
||
## Building a release
|
||
|
||
```bash
|
||
./scripts/release.sh # ad-hoc signed, builds build/Snaggle.dmg
|
||
./scripts/release.sh --developer-id # Developer ID + notarisation (paid account)
|
||
```
|
||
|
||
The DMG contains the app, an Applications symlink and `INSTALL.txt`. For the notarised path,
|
||
set your team ID in `scripts/ExportOptions.plist` and store a notary credential profile first.
|
||
|
||
The app icon is generated, not hand-drawn: edit the colours or shapes in
|
||
`scripts/make_appicon.py` and run `python3 scripts/make_appicon.py` to rewrite every size in
|
||
`Snaggle/Resources/Assets.xcassets/AppIcon.appiconset`.
|
||
|
||
## Using it
|
||
|
||
Snaggle is a menu bar app (`LSUIElement`), so it has no Dock icon. The camera icon in the
|
||
menu bar holds every command.
|
||
|
||
Default global hotkeys — Greenshot's PrintScreen-based defaults don't exist on a Mac keyboard,
|
||
so these use combinations macOS leaves free:
|
||
|
||
| Action | Shortcut |
|
||
| --- | --- |
|
||
| Capture region | ⌥⇧⌘4 |
|
||
| Capture full screen | ⌥⇧⌘3 |
|
||
| Capture window | ⌥⇧⌘5 |
|
||
| Capture last region | ⌥⇧⌘6 |
|
||
|
||
All four are re-recordable in Preferences › Hotkeys.
|
||
|
||
During a capture the desktop freezes under a dimmed overlay: drag to select a region, press
|
||
**Space** to switch to window mode and click a window, **Esc** to cancel. A magnifier and a live
|
||
pixel-size readout follow the cursor.
|
||
|
||
### Editor
|
||
|
||
| Tool | Key |
|
||
| --- | --- |
|
||
| Select | V |
|
||
| Rectangle / Ellipse | R / E |
|
||
| Line / Arrow | L / A |
|
||
| Freehand | F |
|
||
| Text | T |
|
||
| Highlight | H |
|
||
| Obfuscate (pixelate or blur) | O |
|
||
| Step counter | C |
|
||
| Crop | K |
|
||
|
||
Shift constrains shapes to squares/circles and lines to 45° steps. Arrow keys nudge the selection
|
||
(⇧ for 10 px steps). ⌘Z / ⇧⌘Z undo and redo, including crops.
|
||
|
||
### Destinations
|
||
|
||
After a capture the destination picker appears: a preview of what you grabbed, one button per
|
||
destination, ⌘1…⌘9 to pick from the keyboard, Esc to discard, and "Always use this destination"
|
||
to skip the dialog next time. The buttons default to Edit / Save to Disk / Copy to Clipboard /
|
||
Send as Mail, matching Greenshot.
|
||
|
||
The full destination set is clipboard, save with a filename pattern, Save As…, print, email,
|
||
the macOS share sheet, and "open with". Set a destination as the default (skipping the picker)
|
||
in Preferences › General, from the menu bar, or via the picker's checkbox.
|
||
|
||
Filename patterns use Greenshot's syntax, ported verbatim:
|
||
|
||
```
|
||
${capturetime:d"yyyy-MM-dd HH_mm_ss"}-${title}
|
||
```
|
||
|
||
Supported variables: `${capturetime}`, `${now}`, `${title}`, `${appname}`, `${user}`,
|
||
`${hostname}`, `${mode}`, `${NUM}` (with `:d4` for zero padding), plus the `:t`, `:u`, `:l`,
|
||
`:s`, `:r` parameters.
|
||
|
||
## How the port maps onto Greenshot
|
||
|
||
| Greenshot (Windows) | Snaggle |
|
||
| --- | --- |
|
||
| `MainForm` + tray icon | `StatusMenuController` (`NSStatusItem`) |
|
||
| `CaptureForm` | `CaptureOverlay` (one borderless window per display) |
|
||
| `WindowCapture` / GDI+ | `CaptureEngine` (ScreenCaptureKit) |
|
||
| `HotkeyControl` / `RegisterHotKey` | `HotkeyManager` (Carbon `RegisterEventHotKey`) |
|
||
| `Surface` + `DrawableContainer` | `Surface` + `Element` hierarchy |
|
||
| `ImageEditorForm` | `EditorWindowController` + `EditorCanvasView` |
|
||
| `FilenameHelper` | `FilenamePattern` |
|
||
| `ImageIO` | `ImageExporter` |
|
||
| `IDestination` implementations | `Destinations` |
|
||
| `SettingsForm` | `PreferencesView` (SwiftUI) |
|
||
| `IniConfig` / `greenshot.ini` | `Configuration` (`UserDefaults`) |
|
||
|
||
## Not ported
|
||
|
||
The plugin destinations (Imgur, Box, Dropbox, Confluence, Jira, Office, OCR, external command),
|
||
the speech-bubble and SVG/emoji containers, `.gst` templates, and Greenshot's language packs.
|
||
The destination layer is a single `switch` in `Destinations.swift`, so adding an upload target is
|
||
a small, self-contained change.
|
||
|
||
## Licence and attribution
|
||
|
||
Snaggle is a derivative work of [Greenshot](https://github.com/greenshot/greenshot)
|
||
(Copyright © 2007-2026 Thomas Braun, Jens Klingen, Robin Krom), reimplemented in Swift for
|
||
macOS, and is released under the **GNU General Public License v3.0** — the full text is in
|
||
[`LICENSE`](LICENSE).
|
||
|
||
That means anyone may use, study, modify and redistribute Snaggle, including commercially,
|
||
provided the source stays available under the same licence. If you distribute a built copy of
|
||
Snaggle, you must also make this source available to whoever receives it.
|
||
|
||
"Greenshot" is a trademark of the Greenshot project and is used here only to describe Snaggle's
|
||
origin. Snaggle is not affiliated with or endorsed by the Greenshot project, and it does not use
|
||
their name, logo or branding for itself.
|