Initial commit: Snipping Tool menu bar screenshot app

This commit is contained in:
2026-07-01 15:27:57 +02:00
commit 1c2b52fa30
25 changed files with 718 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
build/
Distribution/
Generated/
SnippingTool.xcodeproj/
.DS_Store
xcuserdata/
*.xcuserstate
+38
View File
@@ -0,0 +1,38 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#5B5CF0"/>
<stop offset="55%" stop-color="#8B5CF6"/>
<stop offset="100%" stop-color="#D946A8"/>
</linearGradient>
<radialGradient id="gloss" cx="50%" cy="18%" r="55%">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.30"/>
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0"/>
</radialGradient>
<radialGradient id="lens" cx="38%" cy="35%" r="70%">
<stop offset="0%" stop-color="#6B7BAF"/>
<stop offset="55%" stop-color="#2B3358"/>
<stop offset="100%" stop-color="#12162B"/>
</radialGradient>
<linearGradient id="ring" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.95"/>
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0.55"/>
</linearGradient>
</defs>
<rect x="0" y="0" width="1024" height="1024" rx="224" ry="224" fill="url(#bg)"/>
<rect x="0" y="0" width="1024" height="1024" rx="224" ry="224" fill="url(#gloss)"/>
<!-- viewfinder corner brackets -->
<g fill="none" stroke="#FFFFFF" stroke-width="46" stroke-linecap="round" stroke-linejoin="round">
<path d="M 246 402 L 246 246 L 402 246"/>
<path d="M 622 246 L 778 246 L 778 402"/>
<path d="M 246 622 L 246 778 L 402 778"/>
<path d="M 778 622 L 778 778 L 622 778"/>
</g>
<!-- camera lens -->
<circle cx="512" cy="512" r="136" fill="none" stroke="url(#ring)" stroke-width="12"/>
<circle cx="512" cy="512" r="118" fill="url(#lens)"/>
<ellipse cx="466" cy="466" rx="34" ry="20" fill="#FFFFFF" fill-opacity="0.55" transform="rotate(-35 466 466)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+152
View File
@@ -0,0 +1,152 @@
import AppKit
import Foundation
/// Central state + logic for the app: persisted settings, hot key
/// registration, and running screencapture to save/copy screenshots.
final class AppCoordinator: ObservableObject {
private enum Keys {
static let outputAction = "outputAction"
static let imageFormat = "imageFormat"
static let saveFolder = "saveFolder"
static func hotKeyCode(_ mode: CaptureMode) -> String { "hotkey.\(mode.rawValue).keyCode" }
static func hotKeyMods(_ mode: CaptureMode) -> String { "hotkey.\(mode.rawValue).modifiers" }
}
@Published var outputAction: OutputAction {
didSet { UserDefaults.standard.set(outputAction.rawValue, forKey: Keys.outputAction) }
}
@Published var imageFormat: ImageFormat {
didSet { UserDefaults.standard.set(imageFormat.rawValue, forKey: Keys.imageFormat) }
}
@Published var saveFolder: URL {
didSet { UserDefaults.standard.set(saveFolder.path, forKey: Keys.saveFolder) }
}
@Published var hotKeyFullScreen: HotKeyCombo {
didSet { persistAndRegister(mode: .fullScreen, combo: hotKeyFullScreen) }
}
@Published var hotKeyWindow: HotKeyCombo {
didSet { persistAndRegister(mode: .window, combo: hotKeyWindow) }
}
@Published var hotKeyRegion: HotKeyCombo {
didSet { persistAndRegister(mode: .region, combo: hotKeyRegion) }
}
init() {
let defaults = UserDefaults.standard
outputAction = OutputAction(rawValue: defaults.string(forKey: Keys.outputAction) ?? "") ?? .both
imageFormat = ImageFormat(rawValue: defaults.string(forKey: Keys.imageFormat) ?? "") ?? .png
if let path = defaults.string(forKey: Keys.saveFolder) {
saveFolder = URL(fileURLWithPath: path)
} else {
saveFolder = Self.defaultSaveFolder()
}
hotKeyFullScreen = Self.loadCombo(mode: .fullScreen, default: .defaultFullScreen)
hotKeyWindow = Self.loadCombo(mode: .window, default: .defaultWindow)
hotKeyRegion = Self.loadCombo(mode: .region, default: .defaultRegion)
registerHotKey(mode: .fullScreen, combo: hotKeyFullScreen)
registerHotKey(mode: .window, combo: hotKeyWindow)
registerHotKey(mode: .region, combo: hotKeyRegion)
}
private static func defaultSaveFolder() -> URL {
let pictures = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask).first
?? FileManager.default.homeDirectoryForCurrentUser
return pictures.appendingPathComponent("Screenshots", isDirectory: true)
}
private static func loadCombo(mode: CaptureMode, default defaultValue: HotKeyCombo) -> HotKeyCombo {
let defaults = UserDefaults.standard
let codeKey = Keys.hotKeyCode(mode)
let modsKey = Keys.hotKeyMods(mode)
guard defaults.object(forKey: codeKey) != nil, defaults.object(forKey: modsKey) != nil else {
return defaultValue
}
let code = UInt32(defaults.integer(forKey: codeKey))
let mods = UInt32(defaults.integer(forKey: modsKey))
return HotKeyCombo(keyCode: code, carbonModifiers: mods)
}
private func persistAndRegister(mode: CaptureMode, combo: HotKeyCombo) {
let defaults = UserDefaults.standard
defaults.set(Int(combo.keyCode), forKey: Keys.hotKeyCode(mode))
defaults.set(Int(combo.carbonModifiers), forKey: Keys.hotKeyMods(mode))
registerHotKey(mode: mode, combo: combo)
}
private func registerHotKey(mode: CaptureMode, combo: HotKeyCombo) {
HotKeyManager.shared.register(
id: mode.hotKeyID,
keyCode: combo.keyCode,
modifiers: combo.carbonModifiers
) { [weak self] in
self?.capture(mode: mode)
}
}
// MARK: - Capture
func capture(mode: CaptureMode) {
let saves = outputAction.savesToFile
let copies = outputAction.copiesToClipboard
let ext = imageFormat.fileExtension
let targetURL: URL
if saves {
try? FileManager.default.createDirectory(at: saveFolder, withIntermediateDirectories: true)
targetURL = saveFolder.appendingPathComponent(Self.filename(ext: ext))
} else {
targetURL = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(Self.filename(ext: ext))
}
var arguments = mode.screencaptureArguments
arguments += ["-t", imageFormat.rawValue, targetURL.path]
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/sbin/screencapture")
process.arguments = arguments
process.terminationHandler = { [weak self] _ in
DispatchQueue.main.async {
self?.handleCaptured(at: targetURL, saves: saves, copies: copies)
}
}
do {
try process.run()
} catch {
NSLog("SnippingTool: failed to launch screencapture: \(error)")
}
}
private func handleCaptured(at url: URL, saves: Bool, copies: Bool) {
guard FileManager.default.fileExists(atPath: url.path) else {
// User cancelled the interactive capture (Esc).
return
}
if copies, let image = NSImage(contentsOf: url) {
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.writeObjects([image])
}
if !saves {
try? FileManager.default.removeItem(at: url)
}
}
private static func filename(ext: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd 'um' HH.mm.ss"
return "Bildschirmfoto \(formatter.string(from: Date())).\(ext)"
}
}
@@ -0,0 +1,68 @@
{
"images" : [
{
"filename" : "icon_16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "icon_32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "icon_32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "icon_64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "icon_128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "icon_256.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "icon_256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "icon_512.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "icon_512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "icon_1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

+6
View File
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
+25
View File
@@ -0,0 +1,25 @@
import Foundation
enum CaptureMode: String, CaseIterable {
case fullScreen
case window
case region
/// Stable id used to register/unregister the matching global hot key.
var hotKeyID: UInt32 {
switch self {
case .fullScreen: return 1
case .window: return 2
case .region: return 3
}
}
/// Extra arguments passed to /usr/sbin/screencapture for this mode.
var screencaptureArguments: [String] {
switch self {
case .fullScreen: return []
case .window: return ["-i", "-W"]
case .region: return ["-i", "-s"]
}
}
}
+23
View File
@@ -0,0 +1,23 @@
import Carbon.HIToolbox
/// A recorded global keyboard shortcut: a virtual key code plus Carbon-style modifier flags.
struct HotKeyCombo: Codable, Equatable {
var keyCode: UInt32
var carbonModifiers: UInt32
var displayString: String {
var symbol = ""
if carbonModifiers & UInt32(controlKey) != 0 { symbol += "\u{2303}" }
if carbonModifiers & UInt32(optionKey) != 0 { symbol += "\u{2325}" }
if carbonModifiers & UInt32(shiftKey) != 0 { symbol += "\u{21E7}" }
if carbonModifiers & UInt32(cmdKey) != 0 { symbol += "\u{2318}" }
symbol += KeyCodeSymbols.symbol(for: keyCode)
return symbol
}
static let defaultModifiers = UInt32(cmdKey) | UInt32(optionKey) | UInt32(shiftKey)
static let defaultFullScreen = HotKeyCombo(keyCode: UInt32(kVK_ANSI_3), carbonModifiers: defaultModifiers)
static let defaultWindow = HotKeyCombo(keyCode: UInt32(kVK_ANSI_4), carbonModifiers: defaultModifiers)
static let defaultRegion = HotKeyCombo(keyCode: UInt32(kVK_ANSI_5), carbonModifiers: defaultModifiers)
}
+86
View File
@@ -0,0 +1,86 @@
import Carbon.HIToolbox
import Foundation
/// Thin wrapper around the Carbon RegisterEventHotKey API to register
/// system-wide keyboard shortcuts without requiring Accessibility/Input
/// Monitoring permission.
final class HotKeyManager {
static let shared = HotKeyManager()
private var hotKeyRefs: [UInt32: EventHotKeyRef] = [:]
private var handlers: [UInt32: () -> Void] = [:]
private var eventHandlerRef: EventHandlerRef?
private static let signature: FourCharCode = {
var result: FourCharCode = 0
for byte in "SNAP".utf8 {
result = (result << 8) + FourCharCode(byte)
}
return result
}()
private init() {
installEventHandler()
}
private func installEventHandler() {
var eventType = EventTypeSpec(
eventClass: OSType(kEventClassKeyboard),
eventKind: UInt32(kEventHotKeyPressed)
)
let callback: EventHandlerUPP = { _, event, userData in
guard let event, let userData else { return noErr }
var hotKeyID = EventHotKeyID()
GetEventParameter(
event,
EventParamName(kEventParamDirectObject),
EventParamType(typeEventHotKeyID),
nil,
MemoryLayout<EventHotKeyID>.size,
nil,
&hotKeyID
)
let manager = Unmanaged<HotKeyManager>.fromOpaque(userData).takeUnretainedValue()
manager.handlers[hotKeyID.id]?()
return noErr
}
InstallEventHandler(
GetEventDispatcherTarget(),
callback,
1,
&eventType,
Unmanaged.passUnretained(self).toOpaque(),
&eventHandlerRef
)
}
@discardableResult
func register(id: UInt32, keyCode: UInt32, modifiers: UInt32, handler: @escaping () -> Void) -> Bool {
unregister(id: id)
var hotKeyRef: EventHotKeyRef?
let hotKeyID = EventHotKeyID(signature: Self.signature, id: id)
let status = RegisterEventHotKey(
keyCode,
modifiers,
hotKeyID,
GetEventDispatcherTarget(),
0,
&hotKeyRef
)
guard status == noErr, let hotKeyRef else { return false }
hotKeyRefs[id] = hotKeyRef
handlers[id] = handler
return true
}
func unregister(id: UInt32) {
if let ref = hotKeyRefs.removeValue(forKey: id) {
UnregisterEventHotKey(ref)
}
handlers.removeValue(forKey: id)
}
}
+27
View File
@@ -0,0 +1,27 @@
import SwiftUI
struct HotKeyRecorderView: View {
let title: String
@Binding var combo: HotKeyCombo
@State private var isRecording = false
var body: some View {
HStack {
Text(title)
Spacer()
ZStack {
RoundedRectangle(cornerRadius: 6)
.fill(Color.secondary.opacity(isRecording ? 0.3 : 0.15))
Text(isRecording ? "Taste drücken… (Esc = Abbrechen)" : combo.displayString)
.font(isRecording ? .callout : .body)
.lineLimit(1)
KeyCaptureRepresentable(isRecording: $isRecording) { keyCode, modifiers in
combo = HotKeyCombo(keyCode: keyCode, carbonModifiers: modifiers)
}
}
.frame(width: 190, height: 24)
.onTapGesture { isRecording = true }
}
}
}
+10
View File
@@ -0,0 +1,10 @@
import Foundation
enum ImageFormat: String, CaseIterable, Identifiable, Codable {
case png
case jpg
var id: String { rawValue }
var label: String { rawValue.uppercased() }
var fileExtension: String { rawValue }
}
+54
View File
@@ -0,0 +1,54 @@
import AppKit
import Carbon.HIToolbox
import SwiftUI
/// Invisible NSView overlay that becomes first responder while recording
/// and reports the next key combo (key code + modifiers) it sees.
final class KeyCaptureView: NSView {
var onCapture: ((UInt32, UInt32) -> Void)?
var onCancel: (() -> Void)?
override var acceptsFirstResponder: Bool { true }
override func keyDown(with event: NSEvent) {
if Int(event.keyCode) == kVK_Escape {
onCancel?()
return
}
var modifiers: UInt32 = 0
if event.modifierFlags.contains(.control) { modifiers |= UInt32(controlKey) }
if event.modifierFlags.contains(.option) { modifiers |= UInt32(optionKey) }
if event.modifierFlags.contains(.shift) { modifiers |= UInt32(shiftKey) }
if event.modifierFlags.contains(.command) { modifiers |= UInt32(cmdKey) }
// Require at least one modifier so plain letters still work normally elsewhere.
guard modifiers != 0 else { return }
onCapture?(UInt32(event.keyCode), modifiers)
}
}
struct KeyCaptureRepresentable: NSViewRepresentable {
@Binding var isRecording: Bool
let onCapture: (UInt32, UInt32) -> Void
func makeNSView(context: Context) -> KeyCaptureView {
let view = KeyCaptureView()
view.onCapture = { keyCode, modifiers in
onCapture(keyCode, modifiers)
isRecording = false
}
view.onCancel = {
isRecording = false
}
return view
}
func updateNSView(_ nsView: KeyCaptureView, context: Context) {
if isRecording {
DispatchQueue.main.async {
nsView.window?.makeFirstResponder(nsView)
}
}
}
}
+27
View File
@@ -0,0 +1,27 @@
import Carbon.HIToolbox
/// Maps macOS virtual key codes to a short display symbol, for showing
/// recorded hot keys in the settings UI (e.g. "3", "F5", "Space").
enum KeyCodeSymbols {
static let map: [UInt32: String] = [
UInt32(kVK_ANSI_0): "0", UInt32(kVK_ANSI_1): "1", UInt32(kVK_ANSI_2): "2", UInt32(kVK_ANSI_3): "3",
UInt32(kVK_ANSI_4): "4", UInt32(kVK_ANSI_5): "5", UInt32(kVK_ANSI_6): "6", UInt32(kVK_ANSI_7): "7",
UInt32(kVK_ANSI_8): "8", UInt32(kVK_ANSI_9): "9",
UInt32(kVK_ANSI_A): "A", UInt32(kVK_ANSI_B): "B", UInt32(kVK_ANSI_C): "C", UInt32(kVK_ANSI_D): "D",
UInt32(kVK_ANSI_E): "E", UInt32(kVK_ANSI_F): "F", UInt32(kVK_ANSI_G): "G", UInt32(kVK_ANSI_H): "H",
UInt32(kVK_ANSI_I): "I", UInt32(kVK_ANSI_J): "J", UInt32(kVK_ANSI_K): "K", UInt32(kVK_ANSI_L): "L",
UInt32(kVK_ANSI_M): "M", UInt32(kVK_ANSI_N): "N", UInt32(kVK_ANSI_O): "O", UInt32(kVK_ANSI_P): "P",
UInt32(kVK_ANSI_Q): "Q", UInt32(kVK_ANSI_R): "R", UInt32(kVK_ANSI_S): "S", UInt32(kVK_ANSI_T): "T",
UInt32(kVK_ANSI_U): "U", UInt32(kVK_ANSI_V): "V", UInt32(kVK_ANSI_W): "W", UInt32(kVK_ANSI_X): "X",
UInt32(kVK_ANSI_Y): "Y", UInt32(kVK_ANSI_Z): "Z",
UInt32(kVK_F1): "F1", UInt32(kVK_F2): "F2", UInt32(kVK_F3): "F3", UInt32(kVK_F4): "F4",
UInt32(kVK_F5): "F5", UInt32(kVK_F6): "F6", UInt32(kVK_F7): "F7", UInt32(kVK_F8): "F8",
UInt32(kVK_F9): "F9", UInt32(kVK_F10): "F10", UInt32(kVK_F11): "F11", UInt32(kVK_F12): "F12",
UInt32(kVK_Space): "Space", UInt32(kVK_Return): "Return", UInt32(kVK_Tab): "Tab",
UInt32(kVK_Delete): "Delete", UInt32(kVK_Escape): "Esc"
]
static func symbol(for keyCode: UInt32) -> String {
map[keyCode] ?? "Code \(keyCode)"
}
}
+26
View File
@@ -0,0 +1,26 @@
import AppKit
import SwiftUI
struct MenuBarContentView: View {
@EnvironmentObject var coordinator: AppCoordinator
var body: some View {
Button("Ganzer Bildschirm") { coordinator.capture(mode: .fullScreen) }
Button("Fenster") { coordinator.capture(mode: .window) }
Button("Bereich auswählen") { coordinator.capture(mode: .region) }
Divider()
Button("Einstellungen…") {
SettingsWindowController.shared.show(coordinator: coordinator)
}
.keyboardShortcut(",")
Divider()
Button("Beenden") {
NSApplication.shared.terminate(nil)
}
.keyboardShortcut("q")
}
}
+20
View File
@@ -0,0 +1,20 @@
import Foundation
enum OutputAction: String, CaseIterable, Identifiable, Codable {
case saveOnly
case clipboardOnly
case both
var id: String { rawValue }
var label: String {
switch self {
case .saveOnly: return "Nur speichern"
case .clipboardOnly: return "Nur in Zwischenablage kopieren"
case .both: return "Speichern und in Zwischenablage kopieren"
}
}
var savesToFile: Bool { self == .saveOnly || self == .both }
var copiesToClipboard: Bool { self == .clipboardOnly || self == .both }
}
+59
View File
@@ -0,0 +1,59 @@
import AppKit
import SwiftUI
struct SettingsView: View {
@EnvironmentObject var coordinator: AppCoordinator
var body: some View {
Form {
Section("Nach der Aufnahme") {
Picker("Aktion:", selection: $coordinator.outputAction) {
ForEach(OutputAction.allCases) { action in
Text(action.label).tag(action)
}
}
.pickerStyle(.radioGroup)
Picker("Format:", selection: $coordinator.imageFormat) {
ForEach(ImageFormat.allCases) { format in
Text(format.label).tag(format)
}
}
.pickerStyle(.segmented)
.frame(width: 160)
HStack {
Text("Speicherort:")
Text(coordinator.saveFolder.path)
.foregroundStyle(.secondary)
.lineLimit(1)
.truncationMode(.middle)
Spacer()
Button("Ändern…") { chooseFolder() }
}
}
Section("Tastenkürzel") {
HotKeyRecorderView(title: "Ganzer Bildschirm", combo: $coordinator.hotKeyFullScreen)
HotKeyRecorderView(title: "Fenster", combo: $coordinator.hotKeyWindow)
HotKeyRecorderView(title: "Bereich auswählen", combo: $coordinator.hotKeyRegion)
}
}
.padding(20)
.frame(width: 440)
}
private func chooseFolder() {
let panel = NSOpenPanel()
panel.canChooseDirectories = true
panel.canChooseFiles = false
panel.canCreateDirectories = true
panel.allowsMultipleSelection = false
panel.directoryURL = coordinator.saveFolder
panel.prompt = "Auswählen"
if panel.runModal() == .OK, let url = panel.url {
coordinator.saveFolder = url
}
}
}
+38
View File
@@ -0,0 +1,38 @@
import AppKit
import SwiftUI
/// Manages the settings window manually instead of relying on SwiftUI's
/// `Settings` scene, which is unreliable for LSUIElement (menu bar-only,
/// no dock icon) apps: there's no existing window for the app to focus,
/// so the scene sometimes never becomes key.
final class SettingsWindowController: NSWindowController, NSWindowDelegate {
static let shared = SettingsWindowController()
private convenience init() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 440, height: 420),
styleMask: [.titled, .closable, .miniaturizable],
backing: .buffered,
defer: false
)
window.title = "Einstellungen"
window.isReleasedWhenClosed = false
window.center()
self.init(window: window)
window.delegate = self
}
func show(coordinator: AppCoordinator) {
window?.contentView = NSHostingView(
rootView: SettingsView().environmentObject(coordinator)
)
NSApplication.shared.setActivationPolicy(.regular)
NSApplication.shared.activate(ignoringOtherApps: true)
window?.makeKeyAndOrderFront(nil)
}
func windowWillClose(_ notification: Notification) {
// Menu bar-only apps should not keep a Dock icon after the window closes.
NSApplication.shared.setActivationPolicy(.accessory)
}
}
+14
View File
@@ -0,0 +1,14 @@
import SwiftUI
@main
struct SnippingToolApp: App {
@StateObject private var coordinator = AppCoordinator()
var body: some Scene {
MenuBarExtra("Snipping Tool", systemImage: "camera.viewfinder") {
MenuBarContentView()
.environmentObject(coordinator)
}
.menuBarExtraStyle(.menu)
}
}
+38
View File
@@ -0,0 +1,38 @@
name: SnippingTool
options:
bundleIdPrefix: it.wende
deploymentTarget:
macOS: "14.0"
createIntermediateGroups: true
targets:
SnippingTool:
type: application
platform: macOS
deploymentTarget: "14.0"
sources:
- path: Sources
info:
path: Generated/Info.plist
properties:
CFBundleDisplayName: Snipping Tool
CFBundleName: Snipping Tool
LSUIElement: true
NSHumanReadableCopyright: "Copyright © 2026"
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: it.wende.snippingtool
PRODUCT_NAME: Snipping Tool
MARKETING_VERSION: "1.0"
CURRENT_PROJECT_VERSION: "1"
SWIFT_VERSION: "5.0"
MACOSX_DEPLOYMENT_TARGET: "14.0"
ENABLE_HARDENED_RUNTIME: false
CODE_SIGN_IDENTITY: "-"
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
schemes:
SnippingTool:
build:
targets:
SnippingTool: all
run:
config: Debug