🚀 First official release of ShotScreen with complete feature set: ✨ Core Features: - Advanced screenshot capture system - Multi-monitor support - Professional UI/UX design - Automated update system with Sparkle - Apple notarized & code signed 🛠 Technical Excellence: - Native Swift macOS application - Professional build & deployment pipeline - Comprehensive error handling - Memory optimized performance 📦 Distribution Ready: - Professional DMG packaging - Apple notarization complete - No security warnings for users - Ready for public distribution This is the foundation release that establishes ShotScreen as a premium screenshot tool for macOS.
413 lines
15 KiB
Swift
413 lines
15 KiB
Swift
import AppKit
|
||
import SwiftUI // Needed for CGSize, CaseIterable, Identifiable, Codable
|
||
|
||
// Define Notification Names
|
||
extension Notification.Name {
|
||
static let closeAfterDragSettingChanged = Notification.Name("closeAfterDragSettingChanged")
|
||
static let closeAfterSaveSettingChanged = Notification.Name("closeAfterSaveSettingChanged")
|
||
static let playSoundOnCaptureSettingChanged = Notification.Name("playSoundOnCaptureSettingChanged")
|
||
static let startAppOnLoginSettingChanged = Notification.Name("startAppOnLoginSettingChanged")
|
||
static let thumbnailSizeSettingChanged = Notification.Name("thumbnailSizeSettingChanged")
|
||
static let screenshotFolderChanged = Notification.Name("screenshotFolderChanged")
|
||
static let filenameSettingsChanged = Notification.Name("filenameSettingsChanged")
|
||
static let saveAfterEditSettingChanged = Notification.Name("saveAfterEditSettingChanged")
|
||
static let thumbnailTimerSettingChanged = Notification.Name("thumbnailTimerSettingChanged")
|
||
static let thumbnailPositionSettingChanged = Notification.Name("thumbnailPositionSettingChanged")
|
||
static let showFolderButtonSettingChanged = Notification.Name("showFolderButtonSettingChanged")
|
||
static let autoSaveScreenshotSettingChanged = Notification.Name("autoSaveScreenshotSettingChanged")
|
||
static let stashAlwaysOnTopSettingChanged = Notification.Name("stashAlwaysOnTopSettingChanged")
|
||
static let shortcutSettingChanged = Notification.Name("shortcutSettingChanged")
|
||
static let windowCaptureSettingChanged = Notification.Name("windowCaptureSettingChanged")
|
||
|
||
static let hideDesktopIconsSettingChanged = Notification.Name("hideDesktopIconsSettingChanged")
|
||
static let hideDesktopWidgetsSettingChanged = Notification.Name("hideDesktopWidgetsSettingChanged")
|
||
static let stashPreviewSizeChanged = Notification.Name("stashPreviewSizeChanged")
|
||
// 🔥💥 HYPERMODE GRID NOTIFICATIONS! 💥🔥
|
||
static let stashGridModeChanged = Notification.Name("stashGridModeChanged")
|
||
static let stashGridConfigChanged = Notification.Name("stashGridConfigChanged")
|
||
// 🔥 NIEUW: Persistent stash notification
|
||
static let persistentStashChanged = Notification.Name("persistentStashChanged")
|
||
// Add more here if needed for other live updates
|
||
}
|
||
|
||
// NEW: Enum for Screenshot Save Mode
|
||
enum ScreenshotSaveMode: Int, CaseIterable, Identifiable {
|
||
case automatic = 0
|
||
case manual = 1
|
||
|
||
var id: Int { self.rawValue }
|
||
|
||
var description: String {
|
||
switch self {
|
||
case .automatic: return "Always save screenshot to folder"
|
||
case .manual: return "Manually decide after screenshot"
|
||
}
|
||
}
|
||
}
|
||
|
||
// Settings keys
|
||
enum SettingsKey {
|
||
static let screenshotFolder = "screenshotFolder"
|
||
static let thumbnailTimer = "thumbnailTimer"
|
||
static let closeAfterDrag = "closeAfterDrag"
|
||
static let thumbnailPosition = "thumbnailPosition"
|
||
static let showFolderButton = "showFolderButton"
|
||
static let closeAfterSave = "closeAfterSave"
|
||
static let playSoundOnCapture = "playSoundOnCapture"
|
||
static let filenamePrefix = "filenamePrefix"
|
||
static let filenameFormatPreset = "filenameFormatPreset"
|
||
static let filenameCustomFormat = "filenameCustomFormat"
|
||
static let stashWindowBorderWidth = "stashWindowBorderWidth"
|
||
static let isRenameActionEnabled = "isRenameActionEnabled"
|
||
static let isStashActionEnabled = "isStashActionEnabled"
|
||
static let isOCRActionEnabled = "isOCRActionEnabled"
|
||
static let isClipboardActionEnabled = "isClipboardActionEnabled"
|
||
static let isBackgroundRemoveActionEnabled = "isBackgroundRemoveActionEnabled"
|
||
static let isCancelActionEnabled = "isCancelActionEnabled"
|
||
static let isRemoveActionEnabled = "isRemoveActionEnabled"
|
||
static let isAction3Enabled = "isAction3Enabled"
|
||
static let isAction4Enabled = "isAction4Enabled"
|
||
static let isDeleteActionEnabled = "isDeleteActionEnabled"
|
||
static let isCancelDragActionEnabled = "isCancelDragActionEnabled"
|
||
static let startAppOnLogin = "startAppOnLogin"
|
||
static let autoSaveScreenshot = "autoSaveScreenshot"
|
||
static let saveAfterEdit = "saveAfterEdit"
|
||
static let thumbnailDisplayScreen = "thumbnailDisplayScreen"
|
||
static let stashAlwaysOnTop = "stashAlwaysOnTop"
|
||
static let openOnStartup = "openOnStartup"
|
||
static let hasCompletedFirstLaunch = "hasCompletedFirstLaunch"
|
||
static let customShortcutModifiers = "customShortcutModifiers"
|
||
static let customShortcutKey = "customShortcutKey"
|
||
static let useCustomShortcut = "useCustomShortcut"
|
||
static let windowCaptureIncludeChildWindows = "windowCaptureIncludeChildWindows"
|
||
static let windowCaptureIncludeCursor = "windowCaptureIncludeCursor"
|
||
static let windowCaptureHighResolution = "windowCaptureHighResolution"
|
||
static let windowCaptureShowSelectionUI = "windowCaptureShowSelectionUI"
|
||
|
||
static let hideDesktopIconsDuringScreenshot = "hideDesktopIconsDuringScreenshot"
|
||
static let hideDesktopWidgetsDuringScreenshot = "hideDesktopWidgetsDuringScreenshot"
|
||
static let stashPreviewSize = "stashPreviewSize"
|
||
|
||
// 🔥💥 HYPERMODE STASH GRID KEYS! 💥🔥
|
||
static let stashGridMode = "stashGridMode"
|
||
static let stashMaxColumns = "stashMaxColumns"
|
||
static let stashMaxRows = "stashMaxRows"
|
||
|
||
// 🔥 NIEUW: Persistent stash setting
|
||
static let persistentStash = "persistentStash"
|
||
|
||
// 🔄 UPDATE SETTINGS
|
||
static let automaticUpdates = "automaticUpdates"
|
||
static let includePreReleases = "includePreReleases"
|
||
|
||
// 🔊 SOUND SETTINGS
|
||
static let screenshotSoundVolume = "screenshotSoundVolume"
|
||
static let screenshotSoundType = "screenshotSoundType"
|
||
|
||
// 🗂️ CACHE MANAGEMENT SETTINGS
|
||
static let cacheRetentionTime = "cacheRetentionTime"
|
||
|
||
// 🎨 BACKGROUND REMOVAL METHOD PREFERENCE
|
||
static let preferredBackgroundRemovalMethod = "preferredBackgroundRemovalMethod"
|
||
|
||
}
|
||
|
||
enum ThumbnailPosition: Int, CaseIterable, Codable {
|
||
case rightBottom = 0
|
||
|
||
var description: String {
|
||
switch self {
|
||
case .rightBottom: return "Bottom Right"
|
||
}
|
||
}
|
||
}
|
||
|
||
enum ThumbnailDisplayScreen: String, CaseIterable, Codable {
|
||
case automatic = "automatic"
|
||
case screen1 = "screen1"
|
||
case screen2 = "screen2"
|
||
case screen3 = "screen3"
|
||
case screen4 = "screen4"
|
||
case screen5 = "screen5"
|
||
|
||
var description: String {
|
||
switch self {
|
||
case .automatic: return "Automatic (where mouse is)"
|
||
case .screen1: return "Screen 1"
|
||
case .screen2: return "Screen 2"
|
||
case .screen3: return "Screen 3"
|
||
case .screen4: return "Screen 4"
|
||
case .screen5: return "Screen 5"
|
||
}
|
||
}
|
||
|
||
func getDisplayName(for screenIndex: Int, screenName: String?) -> String {
|
||
switch self {
|
||
case .automatic: return "Automatic (where mouse is)"
|
||
case .screen1, .screen2, .screen3, .screen4, .screen5:
|
||
let screenNumber = screenIndex + 1
|
||
if let name = screenName, !name.isEmpty {
|
||
return "Screen \(screenNumber) (\(name))"
|
||
} else {
|
||
return "Screen \(screenNumber)"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// NEW: Enum for filename format presets
|
||
enum FilenameFormatPreset: Int, CaseIterable, Codable {
|
||
case macOSStyle = 0 // Screenshot {YYYY}-{MM}-{DD} at {hh}.{mm}.{ss}
|
||
case compactDateTime = 1 // {YYYY}-{MM}-{DD}_{hh}-{mm}-{ss}
|
||
case superCompactDateTime = 2 // {YYYYMMDD}_{hhmmss}
|
||
case timestamp = 3 // Unix timestamp
|
||
case prefixOnly = 4 // Prefix only
|
||
case custom = 5 // Custom format
|
||
|
||
var description: String {
|
||
switch self {
|
||
case .macOSStyle: return "macOS Style (Screenshot YYYY-MM-DD at hh.mm.ss)"
|
||
case .compactDateTime: return "Compact (YYYY-MM-DD_hh-mm-ss)"
|
||
case .superCompactDateTime: return "Super Compact (YYYYMMDD_hhmmss)"
|
||
case .timestamp: return "Unix Timestamp"
|
||
case .prefixOnly: return "Prefix Only"
|
||
case .custom: return "Custom..."
|
||
}
|
||
}
|
||
|
||
// Placeholder voor custom format string (alleen relevant voor .custom)
|
||
// De feitelijke custom string wordt apart opgeslagen.
|
||
}
|
||
|
||
// VOEG DEZE ENUM TOE (ergens bovenaan, of logisch gegroepeerd)
|
||
enum ThumbnailFixedSize: String, CaseIterable, Codable {
|
||
case small, medium, large, xLarge, xxLarge, xxxLarge
|
||
|
||
var dimensions: CGSize {
|
||
switch self {
|
||
case .small: return CGSize(width: 120, height: 90)
|
||
case .medium: return CGSize(width: 180, height: 135)
|
||
case .large: return CGSize(width: 240, height: 180)
|
||
case .xLarge: return CGSize(width: 300, height: 225)
|
||
case .xxLarge: return CGSize(width: 360, height: 270)
|
||
case .xxxLarge: return CGSize(width: 420, height: 315)
|
||
}
|
||
}
|
||
|
||
var displayName: String { // Voor de Picker
|
||
switch self {
|
||
case .small: return "Small"
|
||
case .medium: return "Medium"
|
||
case .large: return "Large"
|
||
case .xLarge: return "X-Large"
|
||
case .xxLarge: return "XX-Large"
|
||
case .xxxLarge: return "XXX-Large"
|
||
}
|
||
}
|
||
}
|
||
|
||
// 🔥💎 MEGA NIEUWE STASH PREVIEW SIZE ENUM! 💎🔥
|
||
enum StashPreviewSize: String, CaseIterable, Codable {
|
||
case xSmall = "xSmall" // 25% van origineel
|
||
case small = "small" // 40% van origineel
|
||
case medium = "medium" // 60% van origineel
|
||
case large = "large" // 80% van origineel
|
||
case xLarge = "xLarge" // 100% van origineel (full size!)
|
||
|
||
var percentage: CGFloat {
|
||
switch self {
|
||
case .xSmall: return 0.25
|
||
case .small: return 0.40
|
||
case .medium: return 0.60
|
||
case .large: return 0.80
|
||
case .xLarge: return 1.00
|
||
}
|
||
}
|
||
|
||
var displayName: String {
|
||
switch self {
|
||
case .xSmall: return "Extra Small"
|
||
case .small: return "Small"
|
||
case .medium: return "Medium"
|
||
case .large: return "Large"
|
||
case .xLarge: return "Extra Large"
|
||
}
|
||
}
|
||
}
|
||
|
||
// 🔥💥⚡ HYPERMODE STASH GRID MODE ENUM! ⚡💥🔥
|
||
enum StashGridMode: String, CaseIterable, Codable, Identifiable {
|
||
case fixedColumns = "fixedColumns" // Max kolommen, auto rijen (huidige systeem)
|
||
case fixedRows = "fixedRows" // Max rijen, auto kolommen (horizontale strip!)
|
||
|
||
var id: String { rawValue }
|
||
|
||
var displayName: String {
|
||
switch self {
|
||
case .fixedColumns: return "Fixed Columns, Auto Rows"
|
||
case .fixedRows: return "Fixed Rows, Auto Columns"
|
||
}
|
||
}
|
||
|
||
var description: String {
|
||
switch self {
|
||
case .fixedColumns: return "Grows vertically with more rows"
|
||
case .fixedRows: return "Grows horizontally with more columns"
|
||
}
|
||
}
|
||
|
||
var iconName: String {
|
||
switch self {
|
||
case .fixedColumns: return "rectangle.split.1x3" // Verticale layout (1 kolom, 3 rijen = verticaal groeien)
|
||
case .fixedRows: return "rectangle.split.3x1" // Horizontale layout (3 kolommen, 1 rij = horizontaal groeien)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 🔊 SCREENSHOT SOUND TYPE ENUM
|
||
enum ScreenshotSoundType: String, CaseIterable, Codable, Identifiable {
|
||
case pop = "Pop"
|
||
case glass = "Glass"
|
||
case ping = "Ping"
|
||
case purr = "Purr"
|
||
case tink = "Tink"
|
||
case basso = "Basso"
|
||
case blow = "Blow"
|
||
case bottle = "Bottle"
|
||
case frog = "Frog"
|
||
case funk = "Funk"
|
||
case hero = "Hero"
|
||
case morse = "Morse"
|
||
case sosumi = "Sosumi"
|
||
case submarine = "Submarine"
|
||
|
||
var id: String { rawValue }
|
||
|
||
var displayName: String {
|
||
switch self {
|
||
case .pop: return "Pop"
|
||
case .glass: return "Glass"
|
||
case .ping: return "Ping"
|
||
case .purr: return "Purr"
|
||
case .tink: return "Tink"
|
||
case .basso: return "Basso"
|
||
case .blow: return "Blow"
|
||
case .bottle: return "Bottle"
|
||
case .frog: return "Frog"
|
||
case .funk: return "Funk"
|
||
case .hero: return "Hero"
|
||
case .morse: return "Morse"
|
||
case .sosumi: return "Sosumi"
|
||
case .submarine: return "Submarine"
|
||
}
|
||
}
|
||
|
||
var systemSoundName: String {
|
||
return rawValue
|
||
}
|
||
}
|
||
|
||
// 🗂️ CACHE RETENTION TIME ENUM
|
||
enum CacheRetentionTime: String, CaseIterable, Codable, Identifiable {
|
||
case oneHour = "1h"
|
||
case sixHours = "6h"
|
||
case twelveHours = "12h"
|
||
case oneDay = "1d"
|
||
case threeDays = "3d"
|
||
case oneWeek = "1w"
|
||
case twoWeeks = "2w"
|
||
case oneMonth = "1m"
|
||
case forever = "forever"
|
||
|
||
var id: String { rawValue }
|
||
|
||
var displayName: String {
|
||
switch self {
|
||
case .oneHour: return "1 Hour"
|
||
case .sixHours: return "6 Hours"
|
||
case .twelveHours: return "12 Hours"
|
||
case .oneDay: return "1 Day"
|
||
case .threeDays: return "3 Days"
|
||
case .oneWeek: return "1 Week"
|
||
case .twoWeeks: return "2 Weeks"
|
||
case .oneMonth: return "1 Month"
|
||
case .forever: return "Forever (Manual delete)"
|
||
}
|
||
}
|
||
|
||
var timeInterval: TimeInterval? {
|
||
switch self {
|
||
case .oneHour: return 3600
|
||
case .sixHours: return 3600 * 6
|
||
case .twelveHours: return 3600 * 12
|
||
case .oneDay: return 86400
|
||
case .threeDays: return 86400 * 3
|
||
case .oneWeek: return 86400 * 7
|
||
case .twoWeeks: return 86400 * 14
|
||
case .oneMonth: return 86400 * 30
|
||
case .forever: return nil // Never delete
|
||
}
|
||
}
|
||
}
|
||
|
||
// Add after the existing SettingsKey enum
|
||
enum ActionType: String, CaseIterable, Identifiable {
|
||
case rename = "Rename"
|
||
case stash = "Stash"
|
||
case ocr = "OCR"
|
||
case clipboard = "Clipboard"
|
||
case backgroundRemove = "BackgroundRemove"
|
||
case cancel = "Cancel"
|
||
case remove = "Remove"
|
||
|
||
var id: String { self.rawValue }
|
||
|
||
var settingsKey: String {
|
||
switch self {
|
||
case .rename: return SettingsKey.isRenameActionEnabled
|
||
case .stash: return SettingsKey.isStashActionEnabled
|
||
case .ocr: return SettingsKey.isOCRActionEnabled
|
||
case .clipboard: return SettingsKey.isClipboardActionEnabled
|
||
case .backgroundRemove: return SettingsKey.isBackgroundRemoveActionEnabled
|
||
case .cancel: return SettingsKey.isCancelActionEnabled
|
||
case .remove: return SettingsKey.isRemoveActionEnabled
|
||
}
|
||
}
|
||
|
||
var displayName: String {
|
||
switch self {
|
||
case .rename: return "Rename"
|
||
case .stash: return "Stash"
|
||
case .ocr: return "Text Extract"
|
||
case .clipboard: return "Clipboard"
|
||
case .backgroundRemove: return "Remove Background"
|
||
case .cancel: return "Cancel"
|
||
case .remove: return "Remove"
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - Background Removal Method Preference
|
||
enum BackgroundRemovalMethod: String, CaseIterable, Identifiable {
|
||
case auto = "auto"
|
||
case rmbg = "rmbg"
|
||
case vision = "vision"
|
||
|
||
var id: String { self.rawValue }
|
||
|
||
var displayName: String {
|
||
switch self {
|
||
case .auto: return "Auto (RMBG → Vision fallback)"
|
||
case .rmbg: return "RMBG-1.4 only"
|
||
case .vision: return "Vision Framework only"
|
||
}
|
||
}
|
||
|
||
var description: String {
|
||
switch self {
|
||
case .auto: return "Tries RMBG-1.4 first, falls back to Vision if unavailable"
|
||
case .rmbg: return "Uses only RMBG-1.4 model (requires download)"
|
||
case .vision: return "Uses only Apple's Vision Framework (always available)"
|
||
}
|
||
}
|
||
} |