Initial commit
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
// Configuration.swift
|
||||
// GreenshotMac
|
||||
//
|
||||
// Port of Greenshot's ICoreConfiguration / IEditorConfiguration onto UserDefaults.
|
||||
|
||||
import AppKit
|
||||
|
||||
enum OutputFormat: String, CaseIterable, Codable {
|
||||
case png, jpg, tiff, bmp, gif
|
||||
|
||||
var displayName: String { rawValue.uppercased() }
|
||||
|
||||
var fileType: NSBitmapImageRep.FileType {
|
||||
switch self {
|
||||
case .png: return .png
|
||||
case .jpg: return .jpeg
|
||||
case .tiff: return .tiff
|
||||
case .bmp: return .bmp
|
||||
case .gif: return .gif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum WindowCaptureMode: String, CaseIterable, Codable {
|
||||
case screen // crop the window rectangle out of a full screen grab
|
||||
case window // ask ScreenCaptureKit for the window itself (no shadow)
|
||||
case windowWithShadow
|
||||
}
|
||||
|
||||
/// Identifiers of the destinations a capture can be sent to.
|
||||
enum DestinationID: String, CaseIterable, Codable {
|
||||
case picker
|
||||
case editor
|
||||
case clipboard
|
||||
case file
|
||||
case fileWithDialog
|
||||
case printer
|
||||
case share
|
||||
case email
|
||||
case openWith
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .picker: return "Show destination picker"
|
||||
case .editor: return "Open in image editor"
|
||||
case .clipboard: return "Copy to clipboard"
|
||||
case .file: return "Save (auto-named)"
|
||||
case .fileWithDialog: return "Save as…"
|
||||
case .printer: return "Print"
|
||||
case .share: return "Share…"
|
||||
case .email: return "Send as email"
|
||||
case .openWith: return "Open with…"
|
||||
}
|
||||
}
|
||||
|
||||
var symbolName: String {
|
||||
switch self {
|
||||
case .picker: return "list.bullet"
|
||||
case .editor: return "pencil.tip.crop.circle"
|
||||
case .clipboard: return "doc.on.clipboard"
|
||||
case .file: return "square.and.arrow.down"
|
||||
case .fileWithDialog: return "folder"
|
||||
case .printer: return "printer"
|
||||
case .share: return "square.and.arrow.up"
|
||||
case .email: return "envelope"
|
||||
case .openWith: return "arrow.up.forward.app"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A modifier + key combination used for a global hotkey.
|
||||
struct HotkeySpec: Codable, Equatable {
|
||||
var keyCode: UInt32
|
||||
var modifiers: UInt32 // Carbon modifier mask
|
||||
|
||||
static let none = HotkeySpec(keyCode: 0, modifiers: 0)
|
||||
var isEmpty: Bool { keyCode == 0 && modifiers == 0 }
|
||||
}
|
||||
|
||||
final class Configuration {
|
||||
static let shared = Configuration()
|
||||
|
||||
private let defaults = UserDefaults.standard
|
||||
|
||||
private init() {
|
||||
defaults.register(defaults: [
|
||||
Keys.outputFileFilenamePattern: "${capturetime:d\"yyyy-MM-dd HH_mm_ss\"}-${title}",
|
||||
Keys.outputFileFormat: OutputFormat.png.rawValue,
|
||||
Keys.outputFileJpegQuality: 80,
|
||||
Keys.outputFilePath: FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask).first?.path
|
||||
?? NSHomeDirectory(),
|
||||
Keys.captureMousepointer: false,
|
||||
Keys.captureDelayMillis: 0,
|
||||
Keys.playCameraSound: true,
|
||||
Keys.showFlashAnimation: true,
|
||||
Keys.windowCaptureMode: WindowCaptureMode.window.rawValue,
|
||||
Keys.defaultDestination: DestinationID.picker.rawValue,
|
||||
Keys.pickerDestinations: [DestinationID.editor.rawValue,
|
||||
DestinationID.file.rawValue,
|
||||
DestinationID.clipboard.rawValue,
|
||||
DestinationID.email.rawValue],
|
||||
Keys.copyPathToClipboard: false,
|
||||
Keys.editorLineThickness: 2,
|
||||
Keys.editorLineColor: "#FF0000",
|
||||
Keys.editorFillColor: "#00000000",
|
||||
Keys.editorFontFamily: "Helvetica",
|
||||
Keys.editorFontSize: 14,
|
||||
Keys.editorBlurRadius: 12,
|
||||
Keys.editorPixelSize: 8,
|
||||
Keys.editorHighlightColor: "#FFFF00",
|
||||
Keys.editorDropShadow: false,
|
||||
Keys.showMagnifier: true,
|
||||
Keys.showSelectionSize: true
|
||||
])
|
||||
}
|
||||
|
||||
enum Keys {
|
||||
static let outputFileFilenamePattern = "OutputFileFilenamePattern"
|
||||
static let outputFileFormat = "OutputFileFormat"
|
||||
static let outputFileJpegQuality = "OutputFileJpegQuality"
|
||||
static let outputFilePath = "OutputFilePath"
|
||||
static let captureMousepointer = "CaptureMousepointer"
|
||||
static let captureDelayMillis = "CaptureDelayMillis"
|
||||
static let playCameraSound = "PlayCameraSound"
|
||||
static let showFlashAnimation = "ShowFlashAnimation"
|
||||
static let windowCaptureMode = "WindowCaptureMode"
|
||||
static let defaultDestination = "DefaultDestination"
|
||||
static let pickerDestinations = "PickerDestinations"
|
||||
static let copyPathToClipboard = "CopyPathToClipboard"
|
||||
static let editorLineThickness = "EditorLineThickness"
|
||||
static let editorLineColor = "EditorLineColor"
|
||||
static let editorFillColor = "EditorFillColor"
|
||||
static let editorFontFamily = "EditorFontFamily"
|
||||
static let editorFontSize = "EditorFontSize"
|
||||
static let editorBlurRadius = "EditorBlurRadius"
|
||||
static let editorPixelSize = "EditorPixelSize"
|
||||
static let editorHighlightColor = "EditorHighlightColor"
|
||||
static let editorDropShadow = "EditorDropShadow"
|
||||
static let showMagnifier = "ShowMagnifier"
|
||||
static let showSelectionSize = "ShowSelectionSize"
|
||||
static let hotkeyRegion = "HotkeyRegion"
|
||||
static let hotkeyFullscreen = "HotkeyFullscreen"
|
||||
static let hotkeyWindow = "HotkeyWindow"
|
||||
static let hotkeyLastRegion = "HotkeyLastRegion"
|
||||
}
|
||||
|
||||
// MARK: - Output
|
||||
|
||||
var outputFileFilenamePattern: String {
|
||||
get { defaults.string(forKey: Keys.outputFileFilenamePattern) ?? "" }
|
||||
set { defaults.set(newValue, forKey: Keys.outputFileFilenamePattern) }
|
||||
}
|
||||
|
||||
var outputFileFormat: OutputFormat {
|
||||
get { OutputFormat(rawValue: defaults.string(forKey: Keys.outputFileFormat) ?? "png") ?? .png }
|
||||
set { defaults.set(newValue.rawValue, forKey: Keys.outputFileFormat) }
|
||||
}
|
||||
|
||||
var outputFileJpegQuality: Int {
|
||||
get { defaults.integer(forKey: Keys.outputFileJpegQuality) }
|
||||
set { defaults.set(newValue, forKey: Keys.outputFileJpegQuality) }
|
||||
}
|
||||
|
||||
var outputFilePath: String {
|
||||
get { defaults.string(forKey: Keys.outputFilePath) ?? NSHomeDirectory() }
|
||||
set { defaults.set(newValue, forKey: Keys.outputFilePath) }
|
||||
}
|
||||
|
||||
var copyPathToClipboard: Bool {
|
||||
get { defaults.bool(forKey: Keys.copyPathToClipboard) }
|
||||
set { defaults.set(newValue, forKey: Keys.copyPathToClipboard) }
|
||||
}
|
||||
|
||||
// MARK: - Capture
|
||||
|
||||
var captureMousepointer: Bool {
|
||||
get { defaults.bool(forKey: Keys.captureMousepointer) }
|
||||
set { defaults.set(newValue, forKey: Keys.captureMousepointer) }
|
||||
}
|
||||
|
||||
var captureDelayMillis: Int {
|
||||
get { defaults.integer(forKey: Keys.captureDelayMillis) }
|
||||
set { defaults.set(newValue, forKey: Keys.captureDelayMillis) }
|
||||
}
|
||||
|
||||
var playCameraSound: Bool {
|
||||
get { defaults.bool(forKey: Keys.playCameraSound) }
|
||||
set { defaults.set(newValue, forKey: Keys.playCameraSound) }
|
||||
}
|
||||
|
||||
var showFlashAnimation: Bool {
|
||||
get { defaults.bool(forKey: Keys.showFlashAnimation) }
|
||||
set { defaults.set(newValue, forKey: Keys.showFlashAnimation) }
|
||||
}
|
||||
|
||||
var windowCaptureMode: WindowCaptureMode {
|
||||
get { WindowCaptureMode(rawValue: defaults.string(forKey: Keys.windowCaptureMode) ?? "window") ?? .window }
|
||||
set { defaults.set(newValue.rawValue, forKey: Keys.windowCaptureMode) }
|
||||
}
|
||||
|
||||
var showMagnifier: Bool {
|
||||
get { defaults.bool(forKey: Keys.showMagnifier) }
|
||||
set { defaults.set(newValue, forKey: Keys.showMagnifier) }
|
||||
}
|
||||
|
||||
var showSelectionSize: Bool {
|
||||
get { defaults.bool(forKey: Keys.showSelectionSize) }
|
||||
set { defaults.set(newValue, forKey: Keys.showSelectionSize) }
|
||||
}
|
||||
|
||||
// MARK: - Destinations
|
||||
|
||||
var defaultDestination: DestinationID {
|
||||
get { DestinationID(rawValue: defaults.string(forKey: Keys.defaultDestination) ?? "editor") ?? .editor }
|
||||
set { defaults.set(newValue.rawValue, forKey: Keys.defaultDestination) }
|
||||
}
|
||||
|
||||
var pickerDestinations: [DestinationID] {
|
||||
get { (defaults.stringArray(forKey: Keys.pickerDestinations) ?? []).compactMap(DestinationID.init(rawValue:)) }
|
||||
set { defaults.set(newValue.map(\.rawValue), forKey: Keys.pickerDestinations) }
|
||||
}
|
||||
|
||||
// MARK: - Editor defaults
|
||||
|
||||
var editorLineThickness: Int {
|
||||
get { defaults.integer(forKey: Keys.editorLineThickness) }
|
||||
set { defaults.set(newValue, forKey: Keys.editorLineThickness) }
|
||||
}
|
||||
|
||||
var editorLineColor: NSColor {
|
||||
get { NSColor(hexString: defaults.string(forKey: Keys.editorLineColor) ?? "#FF0000") ?? .systemRed }
|
||||
set { defaults.set(newValue.hexString, forKey: Keys.editorLineColor) }
|
||||
}
|
||||
|
||||
var editorFillColor: NSColor {
|
||||
get { NSColor(hexString: defaults.string(forKey: Keys.editorFillColor) ?? "#00000000") ?? .clear }
|
||||
set { defaults.set(newValue.hexString, forKey: Keys.editorFillColor) }
|
||||
}
|
||||
|
||||
var editorHighlightColor: NSColor {
|
||||
get { NSColor(hexString: defaults.string(forKey: Keys.editorHighlightColor) ?? "#FFFF00") ?? .systemYellow }
|
||||
set { defaults.set(newValue.hexString, forKey: Keys.editorHighlightColor) }
|
||||
}
|
||||
|
||||
var editorFontFamily: String {
|
||||
get { defaults.string(forKey: Keys.editorFontFamily) ?? "Helvetica" }
|
||||
set { defaults.set(newValue, forKey: Keys.editorFontFamily) }
|
||||
}
|
||||
|
||||
var editorFontSize: Int {
|
||||
get { defaults.integer(forKey: Keys.editorFontSize) }
|
||||
set { defaults.set(newValue, forKey: Keys.editorFontSize) }
|
||||
}
|
||||
|
||||
var editorBlurRadius: Int {
|
||||
get { defaults.integer(forKey: Keys.editorBlurRadius) }
|
||||
set { defaults.set(newValue, forKey: Keys.editorBlurRadius) }
|
||||
}
|
||||
|
||||
var editorPixelSize: Int {
|
||||
get { defaults.integer(forKey: Keys.editorPixelSize) }
|
||||
set { defaults.set(newValue, forKey: Keys.editorPixelSize) }
|
||||
}
|
||||
|
||||
var editorDropShadow: Bool {
|
||||
get { defaults.bool(forKey: Keys.editorDropShadow) }
|
||||
set { defaults.set(newValue, forKey: Keys.editorDropShadow) }
|
||||
}
|
||||
|
||||
// MARK: - Hotkeys
|
||||
|
||||
func hotkey(forKey key: String, default fallback: HotkeySpec) -> HotkeySpec {
|
||||
guard let data = defaults.data(forKey: key),
|
||||
let spec = try? JSONDecoder().decode(HotkeySpec.self, from: data) else { return fallback }
|
||||
return spec
|
||||
}
|
||||
|
||||
func setHotkey(_ spec: HotkeySpec, forKey key: String) {
|
||||
if let data = try? JSONEncoder().encode(spec) {
|
||||
defaults.set(data, forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSColor {
|
||||
/// #RRGGBB or #RRGGBBAA
|
||||
convenience init?(hexString: String) {
|
||||
var hex = hexString.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if hex.hasPrefix("#") { hex.removeFirst() }
|
||||
guard hex.count == 6 || hex.count == 8, let value = UInt64(hex, radix: 16) else { return nil }
|
||||
let r, g, b, a: CGFloat
|
||||
if hex.count == 6 {
|
||||
r = CGFloat((value >> 16) & 0xFF) / 255
|
||||
g = CGFloat((value >> 8) & 0xFF) / 255
|
||||
b = CGFloat(value & 0xFF) / 255
|
||||
a = 1
|
||||
} else {
|
||||
r = CGFloat((value >> 24) & 0xFF) / 255
|
||||
g = CGFloat((value >> 16) & 0xFF) / 255
|
||||
b = CGFloat((value >> 8) & 0xFF) / 255
|
||||
a = CGFloat(value & 0xFF) / 255
|
||||
}
|
||||
self.init(srgbRed: r, green: g, blue: b, alpha: a)
|
||||
}
|
||||
|
||||
var hexString: String {
|
||||
guard let rgb = usingColorSpace(.sRGB) else { return "#000000FF" }
|
||||
let r = Int((rgb.redComponent * 255).rounded())
|
||||
let g = Int((rgb.greenComponent * 255).rounded())
|
||||
let b = Int((rgb.blueComponent * 255).rounded())
|
||||
let a = Int((rgb.alphaComponent * 255).rounded())
|
||||
return String(format: "#%02X%02X%02X%02X", r, g, b, a)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user