Files
Snaggle/README.md
T
2026-09-18 07:17:34 -05:00

163 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.