Showing preview only (848K chars total). Download the full file or copy to clipboard to get everything.
Repository: p0deje/Maccy
Branch: master
Commit: 4cd1f979bdf5
Files: 502
Total size: 31.3 MB
Directory structure:
gitextract_l43svsy0/
├── .bartycrouch.toml
├── .github/
│ ├── FUNDING.yml
│ └── ISSUE_TEMPLATE/
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
├── .gitignore
├── .periphery.yml
├── .swiftlint.yml
├── Designs/
│ ├── App-Store/
│ │ ├── 01-Main.pxd
│ │ ├── 02-Types.pxd
│ │ ├── 03-Search.pxd
│ │ ├── 04-Pin.pxd
│ │ ├── 05-Shortcuts.pxd
│ │ ├── 06-Languages.pxd
│ │ └── Promo/
│ │ └── Maccy_1527619437_20240124_MacAppStore_SupportingImagery.psd
│ ├── Copies.txt
│ ├── Icons.sketch
│ ├── Instructions.pxd/
│ │ ├── QuickLook/
│ │ │ ├── Icon.tiff
│ │ │ └── Thumbnail.tiff
│ │ ├── data/
│ │ │ ├── CAE20F62-9DFC-4BD2-AE79-BE620A2ADE55
│ │ │ ├── DF48A8C2-D105-49EF-BF24-87B87BAECAC7-OriginalContentSource
│ │ │ └── selectionForContentTransform/
│ │ │ ├── meta
│ │ │ └── shapeSelection/
│ │ │ ├── meta
│ │ │ └── path
│ │ └── metadata.info
│ └── Storage-Types.pxd/
│ ├── QuickLook/
│ │ ├── Icon.tiff
│ │ └── Thumbnail.tiff
│ ├── data/
│ │ ├── 460814B1-E0F9-4E2C-B4CA-32C0CD866260-OriginalContentSource
│ │ ├── A2CD8EA6-822D-4757-B972-8653B2AABD6B
│ │ ├── selection/
│ │ │ ├── meta
│ │ │ └── shapeSelection/
│ │ │ ├── meta
│ │ │ └── path
│ │ └── selectionForContentTransform/
│ │ ├── meta
│ │ └── shapeSelection/
│ │ ├── meta
│ │ └── path
│ └── metadata.info
├── LICENSE
├── Maccy/
│ ├── About.swift
│ ├── Accessibility.swift
│ ├── AppDelegate.swift
│ ├── AppStoreReview.swift
│ ├── ApplicationImage.swift
│ ├── ApplicationImageCache.swift
│ ├── Assets.xcassets/
│ │ ├── AppIcon.appiconset/
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── StatusBarMenuImage.imageset/
│ │ │ └── Contents.json
│ │ ├── clipboard.fill.imageset/
│ │ │ └── Contents.json
│ │ ├── paperclip.imageset/
│ │ │ └── Contents.json
│ │ └── scissors.imageset/
│ │ └── Contents.json
│ ├── Clipboard.swift
│ ├── ColorImage.swift
│ ├── Extensions/
│ │ ├── Collection+Surrounding.swift
│ │ ├── Color+Random.swift
│ │ ├── Defaults.Keys+Names.swift
│ │ ├── Dictionary+RemoveItem.swift
│ │ ├── KeyEquivalent+Keys.swift
│ │ ├── KeyboardShortcuts.Name+Shortcuts.swift
│ │ ├── ModifierFlags+Description.swift
│ │ ├── NSApplication+Windows.swift
│ │ ├── NSImage+Names.swift
│ │ ├── NSImage+Resized.swift
│ │ ├── NSPasteboard.PasteboardType+Types.swift
│ │ ├── NSPoint+DefaultsSerializable.swift
│ │ ├── NSRect+Centered.swift
│ │ ├── NSRunningApplication+WindowFrame.swift
│ │ ├── NSScreen+ForPopup.swift
│ │ ├── NSSize+DefaultsSerializable.swift
│ │ ├── NSSound+Named.swift
│ │ ├── NSWorkspace+ApplicationName.swift
│ │ ├── Sauce+KeyboardShortcuts.swift
│ │ ├── Settings.PaneIdentifier+Panes.swift
│ │ ├── String+Identifiable.swift
│ │ └── String+Shortened.swift
│ ├── FloatingPanel.swift
│ ├── GlobalHotKey.swift
│ ├── HighlightMatch.swift
│ ├── History.xcdatamodeld/
│ │ └── History.xcdatamodel/
│ │ └── contents
│ ├── HistoryItemAction.swift
│ ├── Info.plist
│ ├── Intents/
│ │ ├── AppIntentError.swift
│ │ ├── Clear.swift
│ │ ├── Delete.swift
│ │ ├── Get.swift
│ │ ├── HistoryItemAppEntity.swift
│ │ └── Select.swift
│ ├── ItemsProtocol.swift
│ ├── KeyChord.swift
│ ├── KeyShortcut.swift
│ ├── KeyboardLayout.swift
│ ├── Maccy.entitlements
│ ├── MaccyApp.swift
│ ├── MenuIcon.swift
│ ├── Models/
│ │ ├── HistoryItem.swift
│ │ └── HistoryItemContent.swift
│ ├── Notifier.swift
│ ├── Observables/
│ │ ├── AppState.swift
│ │ ├── Footer.swift
│ │ ├── FooterItem.swift
│ │ ├── History.swift
│ │ ├── HistoryItemDecorator.swift
│ │ ├── ModifierFlags.swift
│ │ ├── NavigationManager.swift
│ │ ├── Popup.swift
│ │ └── SlideoutController.swift
│ ├── PasteStack.swift
│ ├── PinsPosition.swift
│ ├── PopupPosition.swift
│ ├── Search.swift
│ ├── SearchVisibility.swift
│ ├── Selection.swift
│ ├── Settings/
│ │ ├── AdvancedSettingsPane.swift
│ │ ├── AppearanceSettingsPane.swift
│ │ ├── GeneralSettingsPane.swift
│ │ ├── IgnoreSettingsPane/
│ │ │ ├── IgnoreApplicationsSettingsView.swift
│ │ │ ├── IgnorePasteboardTypesSettingsView.swift
│ │ │ └── IgnoreRegexpsSettingsView.swift
│ │ ├── IgnoreSettingsPane.swift
│ │ ├── PinsSettingsPane.swift
│ │ ├── StorageSettingsPane.swift
│ │ ├── ar.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── be.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── bn.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── bs.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ca.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ckb.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── cs.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── de.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── el.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── en.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── eo.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── es.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── fa.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── fr.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── he.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── hi.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── hr.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── hu.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── id.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── it.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ja.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ko.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── lt.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── lv.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── nb.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── nl.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── pl.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── pt-BR.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── pt.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ro.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ru.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── sl.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── sv.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ta.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── th.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── tr.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── uk.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── uz.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── vi.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── zh-Hans.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ └── zh-Hant.lproj/
│ │ ├── AdvancedSettings.strings
│ │ ├── AppearanceSettings.strings
│ │ ├── GeneralSettings.strings
│ │ ├── IgnoreSettings.strings
│ │ ├── PinsSettings.strings
│ │ └── StorageSettings.strings
│ ├── SoftwareUpdater.swift
│ ├── Sorter.swift
│ ├── Sounds/
│ │ ├── Knock.caf
│ │ └── Write.caf
│ ├── Storage.swift
│ ├── Storage.xcdatamodeld/
│ │ └── Storage.xcdatamodel/
│ │ └── contents
│ ├── Throttler.swift
│ ├── Views/
│ │ ├── AppImageView.swift
│ │ ├── AsyncView.swift
│ │ ├── ConfirmationView.swift
│ │ ├── ContentView.swift
│ │ ├── FooterItemView.swift
│ │ ├── FooterView.swift
│ │ ├── HeaderView.swift
│ │ ├── HeightReaderModifier.swift
│ │ ├── HistoryItemView.swift
│ │ ├── HistoryListView.swift
│ │ ├── HoverSelectionModifier.swift
│ │ ├── KeyHandlingView.swift
│ │ ├── KeyboardShortcutView.swift
│ │ ├── ListHeaderView.swift
│ │ ├── ListItemTitleView.swift
│ │ ├── ListItemView.swift
│ │ ├── MouseMovedViewModifer.swift
│ │ ├── MultipleSelectionListView.swift
│ │ ├── PasteStackItemView.swift
│ │ ├── PasteStackPreviewView.swift
│ │ ├── PasteStackView.swift
│ │ ├── PinsView.swift
│ │ ├── PreviewItemView.swift
│ │ ├── SearchFieldView.swift
│ │ ├── SlideoutContentView.swift
│ │ ├── SlideoutView.swift
│ │ ├── ToolbarView.swift
│ │ ├── VisualEffectView.swift
│ │ ├── WrappingTextView.swift
│ │ ├── ar.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── be.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── bn.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── bs.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ca.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ckb.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── cs.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── de.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── el.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── en.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── eo.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── es.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── fa.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── fr.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── he.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── hi.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── hr.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── hu.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── id.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── it.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ja.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ko.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── lt.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── lv.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── nb.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── nl.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── pl.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── pt-BR.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── pt.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ro.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ru.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── sl.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── sv.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ta.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── th.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── tr.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── uk.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── uz.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── vi.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── zh-Hans.lproj/
│ │ │ └── PreviewItemView.strings
│ │ └── zh-Hant.lproj/
│ │ └── PreviewItemView.strings
│ ├── ar.lproj/
│ │ └── Localizable.strings
│ ├── be.lproj/
│ │ └── Localizable.strings
│ ├── bn.lproj/
│ │ └── Localizable.strings
│ ├── bs.lproj/
│ │ └── Localizable.strings
│ ├── ca.lproj/
│ │ └── Localizable.strings
│ ├── ckb.lproj/
│ │ └── Localizable.strings
│ ├── cs.lproj/
│ │ ├── Localizable.strings
│ │ └── Preview.strings
│ ├── de.lproj/
│ │ └── Localizable.strings
│ ├── el.lproj/
│ │ └── Localizable.strings
│ ├── en.lproj/
│ │ └── Localizable.strings
│ ├── eo.lproj/
│ │ └── Localizable.strings
│ ├── es.lproj/
│ │ └── Localizable.strings
│ ├── fa.lproj/
│ │ └── Localizable.strings
│ ├── fr.lproj/
│ │ └── Localizable.strings
│ ├── he.lproj/
│ │ └── Localizable.strings
│ ├── hi.lproj/
│ │ └── Localizable.strings
│ ├── hr.lproj/
│ │ └── Localizable.strings
│ ├── hu.lproj/
│ │ └── Localizable.strings
│ ├── id.lproj/
│ │ └── Localizable.strings
│ ├── it.lproj/
│ │ └── Localizable.strings
│ ├── ja.lproj/
│ │ └── Localizable.strings
│ ├── ko.lproj/
│ │ └── Localizable.strings
│ ├── lt.lproj/
│ │ └── Localizable.strings
│ ├── lv.lproj/
│ │ └── Localizable.strings
│ ├── nb.lproj/
│ │ └── Localizable.strings
│ ├── nl.lproj/
│ │ └── Localizable.strings
│ ├── pl.lproj/
│ │ └── Localizable.strings
│ ├── pt-BR.lproj/
│ │ ├── Localizable.strings
│ │ └── Preview.strings
│ ├── pt.lproj/
│ │ └── Localizable.strings
│ ├── ro.lproj/
│ │ └── Localizable.strings
│ ├── ru.lproj/
│ │ └── Localizable.strings
│ ├── sl.lproj/
│ │ └── Localizable.strings
│ ├── sv.lproj/
│ │ └── Localizable.strings
│ ├── ta.lproj/
│ │ └── Localizable.strings
│ ├── th.lproj/
│ │ └── Localizable.strings
│ ├── tr.lproj/
│ │ └── Localizable.strings
│ ├── uk.lproj/
│ │ └── Localizable.strings
│ ├── uz.lproj/
│ │ └── Localizable.strings
│ ├── vi.lproj/
│ │ └── Localizable.strings
│ ├── zh-Hans.lproj/
│ │ └── Localizable.strings
│ └── zh-Hant.lproj/
│ └── Localizable.strings
├── Maccy.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ └── contents.xcworkspacedata
│ └── xcshareddata/
│ └── xcschemes/
│ └── Maccy.xcscheme
├── Maccy.xctestplan
├── MaccyTests/
│ ├── ClipboardTests.swift
│ ├── ColorImageTests.swift
│ ├── HistoryDecoratorTests.swift
│ ├── HistoryItemTests.swift
│ ├── HistoryTests.swift
│ ├── Info.plist
│ ├── SearchTests.swift
│ └── SorterTests.swift
├── MaccyUITests/
│ ├── Info.plist
│ └── MaccyUITests.swift
├── README.md
├── appcast.xml
└── docs/
└── keyboard-shortcut-password-fields.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .bartycrouch.toml
================================================
[update]
tasks = ["interfaces", "translate", "normalize"]
[update.interfaces]
paths = ["Maccy"]
subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"]
defaultToBase = false
ignoreEmptyStrings = true
unstripped = false
ignoreKeys = ["#bartycrouch-ignore!", "#bc-ignore!", "#i!"]
[update.translate]
paths = ["Maccy"]
subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"]
translator = "deepL"
secret = "<replace>"
sourceLocale = "en"
separateWithEmptyLine = false
[update.normalize]
paths = ["Maccy"]
subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"]
separateWithEmptyLine = false
sortByKeys = false
[lint]
paths = ["Maccy"]
subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"]
duplicateKeys = true
emptyValues = true
================================================
FILE: .github/FUNDING.yml
================================================
buy_me_a_coffee: p0deje
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
---
name: 🐛 Bug Report
description: Submit a Bug Report
labels: [bug]
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to complete this bug report!
This will help to find the cause of the issue faster and requires fewer follow-ups.
- type: checkboxes
attributes:
label: Before Submitting Your Bug Report
options:
- label: I have verified that there isn't already an issue reporting the same bug to prevent duplication.
required: false
- label: I have seen the [FAQ](https://github.com/p0deje/Maccy?tab=readme-ov-file#faq).
required: false
- type: input
attributes:
label: Maccy Version (see 'About' window)
placeholder: e.g. 0.29.0
validations:
required: false
- type: input
attributes:
label: macOS Version
placeholder: e.g. 13.5.2
validations:
required: false
- type: textarea
attributes:
label: Maccy Settings
description: Provide the output from running 'defaults read org.p0deje.Maccy' in Terminal.app.
render: Shell
validations:
required: false
- type: textarea
attributes:
label: Description
description: Please provide a clear and concise description of the bug.
placeholder: Short description
validations:
required: false
- type: textarea
attributes:
label: Steps to Reproduce
description: |
Provide the steps to consistently reproduce the issue. If possible, record a screen video
demonstrating the problem using QuickTime.app.
placeholder: |
1. Navigate to …
2. Click on …
3. Scroll down to …
4. Observe …
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
---
name: 💪 Feature request
description: Suggest an idea for this project
labels: [enhancement]
body:
- type: checkboxes
attributes:
label: Before Submitting Your Feature Request
options:
- label: Check that there isn't already a similar feature request to avoid creating a duplicate.
required: false
- label: I have seen the [FAQ](https://github.com/p0deje/Maccy?tab=readme-ov-file#faq).
required: false
- type: textarea
attributes:
label: Problem
description:
Please add a clear and concise description of the problem you are
seeking to solve with this feature request.
placeholder: |
Description
- type: textarea
attributes:
label: Solution
description: Please describe what you might want to happen to address this issue. If applicable, add a screenshot, gif, or video to better convey your idea.
placeholder: |
Short description
validations:
required: false
================================================
FILE: .gitignore
================================================
xcuserdata/
Maccy.xcodeproj/project.xcworkspace/xcshareddata
.idea
.DS_Store
Maccy/.DS_Store
================================================
FILE: .periphery.yml
================================================
project: Maccy.xcodeproj
retain_objc_accessible: true
schemes:
- Maccy
targets:
- Maccy
================================================
FILE: .swiftlint.yml
================================================
disabled_rules:
- multiple_closures_with_trailing_closure
- non_optional_string_data_conversion
- todo
line_length:
ignores_comments: true
================================================
FILE: Designs/App-Store/Promo/Maccy_1527619437_20240124_MacAppStore_SupportingImagery.psd
================================================
[File too large to display: 30.6 MB]
================================================
FILE: Designs/Copies.txt
================================================
Hello! 👋
This is Maccy.
Clipboard manager for Mac. 💻
Search as you type. 🔍
Open source. ⚙️
No fluff! 😛
================================================
FILE: Designs/Instructions.pxd/data/selectionForContentTransform/meta
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>backingScale</key>
<real>1</real>
<key>mode</key>
<integer>0</integer>
<key>shapeSelectionFilename</key>
<string>shapeSelection</string>
<key>size</key>
<data>
NC10UHpTVFAQAAAAQKQAAAAAAABAmQAAAAAAAA==
</data>
<key>softness</key>
<real>0.0</real>
<key>timestamp</key>
<real>682897674.67681396</real>
<key>transform</key>
<array>
<real>1.2718749999999999</real>
<real>0.0</real>
<real>0.0</real>
<real>1.2718750000000003</real>
<real>303.99999999999943</real>
<real>-115.00000000000023</real>
<real>0.0</real>
<real>0.0</real>
</array>
<key>version</key>
<integer>2</integer>
</dict>
</plist>
================================================
FILE: Designs/Instructions.pxd/data/selectionForContentTransform/shapeSelection/meta
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>backingScale</key>
<real>1</real>
<key>pathFilename</key>
<string>path</string>
<key>version</key>
<integer>1</integer>
</dict>
</plist>
================================================
FILE: Designs/Storage-Types.pxd/data/selection/meta
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>backingScale</key>
<real>1</real>
<key>mode</key>
<integer>0</integer>
<key>shapeSelectionFilename</key>
<string>shapeSelection</string>
<key>size</key>
<data>
NC10UHpTVFAQAAAAQKQAAAAAAABAmQAAAAAAAA==
</data>
<key>softness</key>
<real>0.0</real>
<key>timestamp</key>
<real>682898643.54011703</real>
<key>transform</key>
<array>
<real>1</real>
<real>0.0</real>
<real>0.0</real>
<real>1</real>
<real>0.0</real>
<real>0.0</real>
<real>0.0</real>
<real>0.0</real>
</array>
<key>version</key>
<integer>2</integer>
</dict>
</plist>
================================================
FILE: Designs/Storage-Types.pxd/data/selection/shapeSelection/meta
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>backingScale</key>
<real>1</real>
<key>pathFilename</key>
<string>path</string>
<key>version</key>
<integer>1</integer>
</dict>
</plist>
================================================
FILE: Designs/Storage-Types.pxd/data/selectionForContentTransform/meta
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>backingScale</key>
<real>1</real>
<key>mode</key>
<integer>0</integer>
<key>shapeSelectionFilename</key>
<string>shapeSelection</string>
<key>size</key>
<data>
NC10UHpTVFAQAAAAQKQAAAAAAABAmQAAAAAAAA==
</data>
<key>softness</key>
<real>0.0</real>
<key>timestamp</key>
<real>682898669.47036195</real>
<key>transform</key>
<array>
<real>1</real>
<real>0.0</real>
<real>0.0</real>
<real>1</real>
<real>0.0</real>
<real>0.0</real>
<real>0.0</real>
<real>0.0</real>
</array>
<key>version</key>
<integer>2</integer>
</dict>
</plist>
================================================
FILE: Designs/Storage-Types.pxd/data/selectionForContentTransform/shapeSelection/meta
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>backingScale</key>
<real>1</real>
<key>pathFilename</key>
<string>path</string>
<key>version</key>
<integer>1</integer>
</dict>
</plist>
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2025 Alex Rodionov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: Maccy/About.swift
================================================
import Cocoa
class About {
private let familyCredits = NSAttributedString(
string: "Special thank you to Tonia, Anna & Guy! ❤️",
attributes: [NSAttributedString.Key.foregroundColor: NSColor.labelColor]
)
private var kossCredits: NSMutableAttributedString {
let string = NSMutableAttributedString(string: "Kudos to Sasha Koss for help! 🏂",
attributes: [NSAttributedString.Key.foregroundColor: NSColor.labelColor])
string.addAttribute(.link, value: "https://koss.nocorp.me", range: NSRange(location: 9, length: 10))
return string
}
private var links: NSMutableAttributedString {
let string = NSMutableAttributedString(string: "Website│GitHub│Support",
attributes: [NSAttributedString.Key.foregroundColor: NSColor.labelColor])
string.addAttribute(.link, value: "https://maccy.app", range: NSRange(location: 0, length: 7))
string.addAttribute(.link, value: "https://github.com/p0deje/Maccy", range: NSRange(location: 8, length: 6))
string.addAttribute(.link, value: "mailto:support@maccy.app", range: NSRange(location: 15, length: 7))
return string
}
private var credits: NSMutableAttributedString {
let credits = NSMutableAttributedString(string: "",
attributes: [NSAttributedString.Key.foregroundColor: NSColor.labelColor])
credits.append(links)
credits.append(NSAttributedString(string: "\n\n"))
credits.append(kossCredits)
credits.append(NSAttributedString(string: "\n"))
credits.append(familyCredits)
credits.setAlignment(.center, range: NSRange(location: 0, length: credits.length))
return credits
}
@objc
func openAbout(_ sender: NSMenuItem?) {
NSApp.activate(ignoringOtherApps: true)
NSApp.orderFrontStandardAboutPanel(options: [NSApplication.AboutPanelOptionKey.credits: credits])
}
}
================================================
FILE: Maccy/Accessibility.swift
================================================
import AppKit
struct Accessibility {
private static var allowed: Bool { AXIsProcessTrustedWithOptions(nil) }
static func check() {
guard !allowed else {
return
}
}
}
================================================
FILE: Maccy/AppDelegate.swift
================================================
import Defaults
import KeyboardShortcuts
import Sparkle
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
var panel: FloatingPanel<ContentView>!
@objc
private lazy var statusItem: NSStatusItem = {
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem.behavior = .removalAllowed
statusItem.button?.action = #selector(performStatusItemClick)
statusItem.button?.image = Defaults[.menuIcon].image
statusItem.button?.imagePosition = .imageLeft
statusItem.button?.target = self
return statusItem
}()
private var isStatusItemDisabled: Bool {
Defaults[.ignoreEvents] || Defaults[.enabledPasteboardTypes].isEmpty
}
private var statusItemVisibilityObserver: NSKeyValueObservation?
func applicationWillFinishLaunching(_ notification: Notification) { // swiftlint:disable:this function_body_length
#if DEBUG
if CommandLine.arguments.contains("enable-testing") {
SPUUpdater(hostBundle: Bundle.main,
applicationBundle: Bundle.main,
userDriver: SPUStandardUserDriver(hostBundle: Bundle.main, delegate: nil),
delegate: nil)
.automaticallyChecksForUpdates = false
}
#endif
// Bridge FloatingPanel via AppDelegate.
AppState.shared.appDelegate = self
Clipboard.shared.onNewCopy { History.shared.add($0) }
Clipboard.shared.start()
Task {
for await _ in Defaults.updates(.clipboardCheckInterval, initial: false) {
Clipboard.shared.restart()
}
}
statusItemVisibilityObserver = observe(\.statusItem.isVisible, options: .new) { _, change in
if let newValue = change.newValue, Defaults[.showInStatusBar] != newValue {
Defaults[.showInStatusBar] = newValue
}
}
Task {
for await value in Defaults.updates(.showInStatusBar) {
statusItem.isVisible = value
}
}
Task {
for await value in Defaults.updates(.menuIcon, initial: false) {
statusItem.button?.image = value.image
}
}
synchronizeMenuIconText()
Task {
for await value in Defaults.updates(.showRecentCopyInMenuBar) {
if value {
statusItem.button?.title = AppState.shared.menuIconText
} else {
statusItem.button?.title = ""
}
}
}
Task {
for await _ in Defaults.updates(.ignoreEvents) {
statusItem.button?.appearsDisabled = isStatusItemDisabled
}
}
Task {
for await _ in Defaults.updates(.enabledPasteboardTypes) {
statusItem.button?.appearsDisabled = isStatusItemDisabled
}
}
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
migrateUserDefaults()
disableUnusedGlobalHotkeys()
panel = FloatingPanel(
contentRect: NSRect(origin: .zero, size: Defaults[.windowSize]),
identifier: Bundle.main.bundleIdentifier ?? "org.p0deje.Maccy",
statusBarButton: statusItem.button,
onClose: { AppState.shared.popup.reset() }
) {
ContentView()
}
}
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
panel.toggle(height: AppState.shared.popup.height)
return true
}
func applicationWillTerminate(_ notification: Notification) {
if Defaults[.clearOnQuit] {
AppState.shared.history.clear()
}
}
private func migrateUserDefaults() {
if Defaults[.migrations]["2024-07-01-version-2"] != true {
// Start 2.x from scratch.
Defaults.reset(.migrations)
// Inverse hide* configuration keys.
Defaults[.showFooter] = !UserDefaults.standard.bool(forKey: "hideFooter")
Defaults[.showSearch] = !UserDefaults.standard.bool(forKey: "hideSearch")
Defaults[.showTitle] = !UserDefaults.standard.bool(forKey: "hideTitle")
UserDefaults.standard.removeObject(forKey: "hideFooter")
UserDefaults.standard.removeObject(forKey: "hideSearch")
UserDefaults.standard.removeObject(forKey: "hideTitle")
Defaults[.migrations]["2024-07-01-version-2"] = true
}
// The following defaults are not used in Maccy 2.x
// and should be removed in 3.x.
// - LaunchAtLogin__hasMigrated
// - avoidTakingFocus
// - saratovSeparator
// - maxMenuItemLength
// - maxMenuItems
}
@objc
private func performStatusItemClick() {
if let event = NSApp.currentEvent {
let modifierFlags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
if modifierFlags.contains(.option) {
Defaults[.ignoreEvents].toggle()
if modifierFlags.contains(.shift) {
Defaults[.ignoreOnlyNextEvent] = Defaults[.ignoreEvents]
}
return
}
}
panel.toggle(height: AppState.shared.popup.height, at: .statusItem)
}
private func synchronizeMenuIconText() {
_ = withObservationTracking {
AppState.shared.menuIconText
} onChange: {
DispatchQueue.main.async {
if Defaults[.showRecentCopyInMenuBar] {
self.statusItem.button?.title = AppState.shared.menuIconText
}
self.synchronizeMenuIconText()
}
}
}
private func disableUnusedGlobalHotkeys() {
let names: [KeyboardShortcuts.Name] = [.delete, .pin]
KeyboardShortcuts.disable(names)
NotificationCenter.default.addObserver(
forName: Notification.Name("KeyboardShortcuts_shortcutByNameDidChange"),
object: nil,
queue: nil
) { notification in
if let name = notification.userInfo?["name"] as? KeyboardShortcuts.Name, names.contains(name) {
KeyboardShortcuts.disable(name)
}
}
}
}
================================================
FILE: Maccy/AppStoreReview.swift
================================================
import StoreKit
import Defaults
class AppStoreReview {
class func ask() {
Defaults[.numberOfUsages] += 1
guard Defaults[.numberOfUsages] > 50 else { return }
let today = Date()
let lastReviewRequestDate = Defaults[.lastReviewRequestedAt]
guard let minimumRequestDate = Calendar.current.date(byAdding: .month, value: 1, to: lastReviewRequestDate),
today > minimumRequestDate else {
return
}
Defaults[.lastReviewRequestedAt] = today
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
SKStoreReviewController.requestReview()
}
}
}
================================================
FILE: Maccy/ApplicationImage.swift
================================================
import Defaults
import SwiftUI
class ApplicationImage {
fileprivate static let fallbackImage = NSImage(
systemSymbolName: "questionmark.app.dashed",
accessibilityDescription: nil
)!
private static let retryInterval: TimeInterval = 60 * 60
let bundleIdentifier: String?
private var image: NSImage?
private var lastChecked: Date?
private var eventSource: (any DispatchSourceFileSystemObject)?
init(bundleIdentifier: String?, image: NSImage? = nil) {
self.bundleIdentifier = bundleIdentifier
self.image = image
}
var nsImage: NSImage {
guard let bundleIdentifier else {
return Self.fallbackImage
}
if let image {
return image
}
// The image has been queried before but since the application has been deleted.
// Check from time to time if the application has returned.
if let lastChecked,
Date().timeIntervalSince(lastChecked) < Self.retryInterval {
return Self.fallbackImage
}
lastChecked = .now
if let appURL = NSWorkspace.shared.urlForApplication(
withBundleIdentifier: bundleIdentifier
) {
let img = NSWorkspace.shared.icon(forFile: appURL.path)
image = img
let descriptor = open(appURL.path, O_EVTONLY)
if descriptor == -1 {
let errorCode = errno
print("Error code: \(errorCode)")
print("Error message: \(String(cString: strerror(errorCode)))")
} else if descriptor > 0 {
let source = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: descriptor,
eventMask: [.write, .delete],
queue: DispatchQueue.global()
)
eventSource = source
source.setEventHandler {
DispatchQueue.main.async {
let event = source.data
if event.contains(.delete) {
// File was deleted.
print("Deleted", appURL.path)
source.cancel()
self.image = nil
} else if event.contains(.write) {
// File was modified. Fetch new icon
print("Modified", appURL.path)
self.image = NSWorkspace.shared.icon(forFile: appURL.path)
}
}
}
source.setCancelHandler {
close(descriptor)
}
source.resume()
}
return img
}
return Self.fallbackImage
}
}
================================================
FILE: Maccy/ApplicationImageCache.swift
================================================
class ApplicationImageCache {
static let shared = ApplicationImageCache()
private let universalClipboardIdentifier: String =
"com.apple.finder.Open-iCloudDrive"
private let fallback = ApplicationImage(bundleIdentifier: nil)
private var cache: [String: ApplicationImage] = [:]
func getImage(item: HistoryItem) -> ApplicationImage {
guard let bundleIdentifier = bundleIdentifier(for: item) else {
return fallback
}
if let image = cache[bundleIdentifier] {
return image
}
let image = ApplicationImage(bundleIdentifier: bundleIdentifier)
cache[bundleIdentifier] = image
return image
}
private func bundleIdentifier(for item: HistoryItem) -> String? {
if item.universalClipboard {
return universalClipboardIdentifier
}
if let bundleIdentifier = item.application {
return bundleIdentifier
}
return nil
}
}
================================================
FILE: Maccy/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"filename" : "AppIcon (Big Sur)-16w.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "AppIcon (Big Sur)-32w.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "AppIcon (Big Sur)-32w-1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "AppIcon (Big Sur)-64w.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "AppIcon (Big Sur)-128w.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "AppIcon (Big Sur)-256w.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "AppIcon (Big Sur)-256w-1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "AppIcon (Big Sur)-512w.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "AppIcon (Big Sur)-512w-1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "AppIcon (Big Sur)-1024w.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Maccy/Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Maccy/Assets.xcassets/StatusBarMenuImage.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "mac",
"filename" : "DarkMenuBar-16w.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"filename" : "LightMenuBar-16w.png",
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"scale" : "1x"
},
{
"idiom" : "mac",
"filename" : "DarkMenuBar-32w.png",
"scale" : "2x"
},
{
"idiom" : "mac",
"filename" : "LightMenuBar-32w.png",
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
================================================
FILE: Maccy/Assets.xcassets/clipboard.fill.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "clipboard.fill.light_16.png",
"idiom" : "mac",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "clipboard.fill.dark_16.png",
"idiom" : "mac",
"scale" : "1x"
},
{
"filename" : "clipboard.fill.light_32.png",
"idiom" : "mac",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "clipboard.fill.dark_32.png",
"idiom" : "mac",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
================================================
FILE: Maccy/Assets.xcassets/paperclip.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "clip.fill.light_16.png",
"idiom" : "mac",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "clip.fill.dark_16 1.png",
"idiom" : "mac",
"scale" : "1x"
},
{
"filename" : "clip.fill.light_32.png",
"idiom" : "mac",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "clip.fill.dark_32 1.png",
"idiom" : "mac",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Maccy/Assets.xcassets/scissors.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "scissors_light_16.png",
"idiom" : "mac",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "scissors_dark_16.png",
"idiom" : "mac",
"scale" : "1x"
},
{
"filename" : "scissors_light_32.png",
"idiom" : "mac",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "scissors_dark_32.png",
"idiom" : "mac",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
================================================
FILE: Maccy/Clipboard.swift
================================================
import AppKit
import Defaults
import Sauce
class Clipboard {
static let shared = Clipboard()
typealias OnNewCopyHook = (HistoryItem) -> Void
private var onNewCopyHooks: [OnNewCopyHook] = []
var changeCount: Int
private let pasteboard = NSPasteboard.general
private var timer: Timer?
private let dynamicTypePrefix = "dyn."
private let microsoftSourcePrefix = "com.microsoft.ole.source."
private let supportedTypes: Set<NSPasteboard.PasteboardType> = [
.fileURL,
.html,
.png,
.rtf,
.string,
.tiff
]
private let ignoredTypes: Set<NSPasteboard.PasteboardType> = [
.autoGenerated,
.concealed,
.transient
]
private var enabledTypes: Set<NSPasteboard.PasteboardType> { Defaults[.enabledPasteboardTypes] }
private var disabledTypes: Set<NSPasteboard.PasteboardType> { supportedTypes.subtracting(enabledTypes) }
private var sourceApp: NSRunningApplication? { NSWorkspace.shared.frontmostApplication }
init() {
changeCount = pasteboard.changeCount
}
func onNewCopy(_ hook: @escaping OnNewCopyHook) {
onNewCopyHooks.append(hook)
}
func clearHooks() {
onNewCopyHooks = []
}
func start() {
timer = Timer.scheduledTimer(
timeInterval: Defaults[.clipboardCheckInterval],
target: self,
selector: #selector(checkForChangesInPasteboard),
userInfo: nil,
repeats: true
)
}
func restart() {
timer?.invalidate()
start()
}
@MainActor
func copy(_ string: String) {
pasteboard.clearContents()
pasteboard.setString(string, forType: .string)
sync()
checkForChangesInPasteboard()
}
@MainActor
func copy(_ item: HistoryItem?, removeFormatting: Bool = false) {
guard let item else { return }
pasteboard.clearContents()
var contents = item.contents
if removeFormatting {
contents = clearFormatting(contents)
}
for content in contents {
guard content.type != NSPasteboard.PasteboardType.fileURL.rawValue else { continue }
pasteboard.setData(content.value, forType: NSPasteboard.PasteboardType(content.type))
}
// Use writeObjects for file URLs so that multiple files that are copied actually work.
// Only do this for file URLs because it causes an issue with some other data types (like formatted text)
// where the item is pasted more than once.
let fileURLItems: [NSPasteboardItem] = contents.compactMap { item in
guard item.type == NSPasteboard.PasteboardType.fileURL.rawValue else { return nil }
guard let value = item.value else { return nil }
let pasteItem = NSPasteboardItem()
pasteItem.setData(value, forType: NSPasteboard.PasteboardType(item.type))
return pasteItem
}
pasteboard.writeObjects(fileURLItems)
pasteboard.setString("", forType: .fromMaccy)
pasteboard.setString(item.application ?? "", forType: .source)
sync()
Task {
Notifier.notify(body: item.title, sound: .knock)
checkForChangesInPasteboard()
}
}
// Based on https://github.com/Clipy/Clipy/blob/develop/Clipy/Sources/Services/PasteService.swift.
func paste() {
Accessibility.check()
// Add flag that left/right modifier key has been pressed.
// See https://github.com/TermiT/Flycut/pull/18 for details.
let cmdFlag = CGEventFlags(rawValue: UInt64(KeyChord.pasteKeyModifiers.rawValue) | 0x000008)
var vCode = Sauce.shared.keyCode(for: KeyChord.pasteKey)
// Force QWERTY keycode when keyboard layout switches to
// QWERTY upon pressing ⌘ key (e.g. "Dvorak - QWERTY ⌘").
// See https://github.com/p0deje/Maccy/issues/482 for details.
if KeyboardLayout.current.commandSwitchesToQWERTY && cmdFlag.contains(.maskCommand) {
vCode = KeyChord.pasteKey.QWERTYKeyCode
}
let source = CGEventSource(stateID: .combinedSessionState)
// Disable local keyboard events while pasting
source?.setLocalEventsFilterDuringSuppressionState([.permitLocalMouseEvents, .permitSystemDefinedEvents],
state: .eventSuppressionStateSuppressionInterval)
let keyVDown = CGEvent(keyboardEventSource: source, virtualKey: vCode, keyDown: true)
let keyVUp = CGEvent(keyboardEventSource: source, virtualKey: vCode, keyDown: false)
keyVDown?.flags = cmdFlag
keyVUp?.flags = cmdFlag
keyVDown?.post(tap: .cgSessionEventTap)
keyVUp?.post(tap: .cgSessionEventTap)
}
func clear() {
guard Defaults[.clearSystemClipboard] else {
return
}
pasteboard.clearContents()
}
@objc
@MainActor
func checkForChangesInPasteboard() { // swiftlint:disable:this cyclomatic_complexity
guard pasteboard.changeCount != changeCount else {
return
}
changeCount = pasteboard.changeCount
if pasteboard.pasteboardItems?.contains(where: { $0.types.contains(.fromMaccy) }) != true {
// External copy occurred. Stop the current paste stack.
// Maybe queue it into the paste stack? Configurable behaviour?
AppState.shared.history.interruptPasteStack()
}
if Defaults[.ignoreEvents] {
if Defaults[.ignoreOnlyNextEvent] {
Defaults[.ignoreEvents] = false
Defaults[.ignoreOnlyNextEvent] = false
}
return
}
// Reading types on NSPasteboard gives all the available
// types - even the ones that are not present on the NSPasteboardItem.
// See https://github.com/p0deje/Maccy/issues/241.
if shouldIgnore(Set(pasteboard.types ?? [])) {
return
}
if let sourceAppBundle = sourceApp?.bundleIdentifier, shouldIgnore(sourceAppBundle) {
return
}
// Some applications (BBEdit, Edge) add 2 items to pasteboard when copying
// so it's better to merge all data into a single record.
// - https://github.com/p0deje/Maccy/issues/78
// - https://github.com/p0deje/Maccy/issues/472
var contents = [HistoryItemContent]()
pasteboard.pasteboardItems?.forEach({ item in
var types = Set(item.types)
if types.contains(.string) && isEmptyString(item) && !richText(item) {
return
}
if shouldIgnore(item) {
return
}
types = types
.subtracting(disabledTypes)
.filter { !$0.rawValue.starts(with: dynamicTypePrefix) }
.filter { !$0.rawValue.starts(with: microsoftSourcePrefix) }
// Avoid reading Microsoft Word links from bookmarks and cross-references.
// https://github.com/p0deje/Maccy/issues/613
// https://github.com/p0deje/Maccy/issues/770
if types.isSuperset(of: [.microsoftLinkSource, .microsoftObjectLink]) {
types = types.subtracting([.microsoftLinkSource, .microsoftObjectLink, .pdf])
}
types.forEach { type in
contents.append(HistoryItemContent(type: type.rawValue, value: item.data(forType: type)))
}
})
guard !contents.isEmpty else {
return
}
let historyItem = HistoryItem(contents: contents)
if #unavailable(macOS 15.0) {
// On macOS 14 the history item needs to be inserted into storage directly after creating it.
try? History.shared.insertIntoStorage(historyItem)
}
historyItem.application = sourceApp?.bundleIdentifier
historyItem.title = historyItem.generateTitle()
onNewCopyHooks.forEach({ $0(historyItem) })
}
private func shouldIgnore(_ types: Set<NSPasteboard.PasteboardType>) -> Bool {
let ignoredTypes = self.ignoredTypes
.union(Defaults[.ignoredPasteboardTypes].map({ NSPasteboard.PasteboardType($0) }))
return types.isDisjoint(with: enabledTypes) ||
!types.isDisjoint(with: ignoredTypes)
}
private func shouldIgnore(_ sourceAppBundle: String) -> Bool {
if Defaults[.ignoreAllAppsExceptListed] {
return !Defaults[.ignoredApps].contains(sourceAppBundle)
} else {
return Defaults[.ignoredApps].contains(sourceAppBundle)
}
}
private func shouldIgnore(_ item: NSPasteboardItem) -> Bool {
for regexp in Defaults[.ignoreRegexp] {
if let string = item.string(forType: .string) {
do {
let regex = try NSRegularExpression(pattern: regexp)
if regex.numberOfMatches(in: string, range: NSRange(string.startIndex..., in: string)) > 0 {
return true
}
} catch {
return false
}
}
}
return false
}
private func isEmptyString(_ item: NSPasteboardItem) -> Bool {
guard let string = item.string(forType: .string) else {
return true
}
return string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
private func richText(_ item: NSPasteboardItem) -> Bool {
if let rtf = item.data(forType: .rtf) {
if let attributedString = NSAttributedString(rtf: rtf, documentAttributes: nil) {
return !attributedString.string.isEmpty
}
}
if let html = item.data(forType: .html) {
if let attributedString = NSAttributedString(html: html, documentAttributes: nil) {
return !attributedString.string.isEmpty
}
}
return false
}
// Some applications requires window be unfocused and focused back to sync the clipboard.
// - Chrome Remote Desktop (https://github.com/p0deje/Maccy/issues/948)
// - Netbeans (https://github.com/p0deje/Maccy/issues/879)
private func sync() {
guard let app = sourceApp,
app.bundleURL?.lastPathComponent == "Chrome Remote Desktop.app" ||
app.localizedName?.contains("NetBeans") == true else {
return
}
NSApp.activate(ignoringOtherApps: true)
NSApp.hide(self)
}
private func clearFormatting(_ contents: [HistoryItemContent]) -> [HistoryItemContent] {
var newContents: [HistoryItemContent] = contents
let stringContents = contents.filter { NSPasteboard.PasteboardType($0.type) == .string }
// If there is no string representation of data,
// behave like we didn't have to remove formatting.
if !stringContents.isEmpty {
newContents = stringContents
// Preserve file URLs.
// https://github.com/p0deje/Maccy/issues/962
let fileURLContents = contents.filter { NSPasteboard.PasteboardType($0.type) == .fileURL }
if !fileURLContents.isEmpty {
newContents += fileURLContents
}
}
return newContents
}
}
================================================
FILE: Maccy/ColorImage.swift
================================================
import AppKit
import SwiftHEXColors
class ColorImage {
static func from(_ colorHex: String) -> NSImage? {
guard let color = NSColor(hexString: colorHex) else {
return nil
}
let image = NSImage(size: NSSize(width: 12, height: 12))
image.lockFocus()
color.drawSwatch(in: NSRect(x: 0, y: 0, width: 12, height: 12))
image.unlockFocus()
return image
}
}
================================================
FILE: Maccy/Extensions/Collection+Surrounding.swift
================================================
extension Collection where Element: Equatable {
func item(after: Element, where predicate: (Element) -> Bool) -> Element? {
guard let currentIndex = firstIndex(of: after) else {
return nil
}
var nextIndex = index(currentIndex, offsetBy: 1)
while nextIndex < endIndex {
let item = self[nextIndex]
if predicate(item) {
return item
}
nextIndex = index(nextIndex, offsetBy: 1)
}
return nil
}
func item(before: Element, where predicate: (Element) -> Bool) -> Element? {
guard let currentIndex = firstIndex(of: before) else {
return nil
}
var prevIndex = index(currentIndex, offsetBy: -1)
while prevIndex >= startIndex {
let item = self[prevIndex]
if predicate(item) {
return item
}
prevIndex = index(prevIndex, offsetBy: -1)
}
return nil
}
func between(from fromElement: Element, to toElement: Element, inOrder: Bool = false) -> [Element]? {
guard let fromIndex = firstIndex(of: fromElement) else {
return nil
}
guard let toIndex = firstIndex(of: toElement) else {
return nil
}
let startIndex = Swift.min(fromIndex, toIndex)
let endIndex = Swift.max(fromIndex, toIndex)
let items = self[startIndex...endIndex]
if !inOrder && fromIndex > toIndex {
return items.reversed()
} else {
return Array(items)
}
}
}
extension Array where Element: Equatable {
func nearest(to element: Element, where condition: (Element) -> Bool) -> Element? {
guard let currentIndex = firstIndex(of: element) else {
return nil
}
let nextNearest = self[currentIndex...].firstIndex(where: { condition($0) })
let previousNearest = self[...currentIndex].lastIndex(where: { condition($0) })
switch (nextNearest, previousNearest) {
case (nil, nil):
return nil
case (.some(let index), .none):
return self[currentIndex + index]
case (.none, .some(let index)):
return self[index]
case (.some(let index1), .some(let index2)):
let pos1 = currentIndex + index1
let pos2 = index2
return abs(pos1 - currentIndex) < abs(pos2 - currentIndex)
? self[pos1]
: self[pos2]
}
}
}
================================================
FILE: Maccy/Extensions/Color+Random.swift
================================================
import SwiftUI
// Useful to debug SwiftUI view redraws: .background(.random).
extension ShapeStyle where Self == Color {
static var random: Color {
Color(
red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1)
)
}
}
================================================
FILE: Maccy/Extensions/Defaults.Keys+Names.swift
================================================
import AppKit
import Defaults
struct StorageType {
static let files = StorageType(types: [.fileURL])
static let images = StorageType(types: [.png, .tiff])
static let text = StorageType(types: [.html, .rtf, .string])
static let all = StorageType(types: files.types + images.types + text.types)
var types: [NSPasteboard.PasteboardType]
}
extension Defaults.Keys {
static let clearOnQuit = Key<Bool>("clearOnQuit", default: false)
static let clearSystemClipboard = Key<Bool>("clearSystemClipboard", default: false)
static let clipboardCheckInterval = Key<Double>("clipboardCheckInterval", default: 0.5)
static let enabledPasteboardTypes = Key<Set<NSPasteboard.PasteboardType>>(
"enabledPasteboardTypes", default: Set(StorageType.all.types)
)
static let highlightMatch = Key<HighlightMatch>("highlightMatch", default: .bold)
static let ignoreAllAppsExceptListed = Key<Bool>("ignoreAllAppsExceptListed", default: false)
static let ignoreEvents = Key<Bool>("ignoreEvents", default: false)
static let ignoreOnlyNextEvent = Key<Bool>("ignoreOnlyNextEvent", default: false)
static let ignoreRegexp = Key<[String]>("ignoreRegexp", default: [])
static let ignoredApps = Key<[String]>("ignoredApps", default: [])
static let ignoredPasteboardTypes = Key<Set<String>>(
"ignoredPasteboardTypes",
default: Set([
"Pasteboard generator type",
"com.agilebits.onepassword",
"com.typeit4me.clipping",
"de.petermaurer.TransientPasteboardType",
"net.antelle.keeweb"
])
)
static let imageMaxHeight = Key<Int>("imageMaxHeight", default: 40)
static let lastReviewRequestedAt = Key<Date>("lastReviewRequestedAt", default: Date.now)
static let menuIcon = Key<MenuIcon>("menuIcon", default: .maccy)
static let migrations = Key<[String: Bool]>("migrations", default: [:])
static let numberOfUsages = Key<Int>("numberOfUsages", default: 0)
static let pasteByDefault = Key<Bool>("pasteByDefault", default: false)
static let pinTo = Key<PinsPosition>("pinTo", default: .top)
static let popupPosition = Key<PopupPosition>("popupPosition", default: .cursor)
static let popupScreen = Key<Int>("popupScreen", default: 0)
static let previewDelay = Key<Int>("previewDelay", default: 1500)
static let removeFormattingByDefault = Key<Bool>("removeFormattingByDefault", default: false)
static let searchMode = Key<Search.Mode>("searchMode", default: .exact)
static let showFooter = Key<Bool>("showFooter", default: true)
static let showInStatusBar = Key<Bool>("showInStatusBar", default: true)
static let showRecentCopyInMenuBar = Key<Bool>("showRecentCopyInMenuBar", default: false)
static let showSearch = Key<Bool>("showSearch", default: true)
static let searchVisibility = Key<SearchVisibility>("searchVisibility", default: .always)
static let showSpecialSymbols = Key<Bool>("showSpecialSymbols", default: true)
static let showTitle = Key<Bool>("showTitle", default: true)
static let size = Key<Int>("historySize", default: 200)
static let sortBy = Key<Sorter.By>("sortBy", default: .lastCopiedAt)
static let suppressClearAlert = Key<Bool>("suppressClearAlert", default: false)
static let windowSize = Key<NSSize>("windowSize", default: NSSize(width: 450, height: 800))
static let windowPosition = Key<NSPoint>("windowPosition", default: NSPoint(x: 0.5, y: 0.8))
static let showApplicationIcons = Key<Bool>("showApplicationIcons", default: false)
static let previewWidth = Key<CGFloat>("previewWidth", default: 400)
}
================================================
FILE: Maccy/Extensions/Dictionary+RemoveItem.swift
================================================
extension Dictionary {
// Removes all key-value pairs where the value satisfies the given predicate.
mutating func removeValues(where shouldRemove: (Value) -> Bool) {
for (key, value) in self where shouldRemove(value) {
self.removeValue(forKey: key)
}
}
}
================================================
FILE: Maccy/Extensions/KeyEquivalent+Keys.swift
================================================
import SwiftUI
extension KeyEquivalent {
static let backspace = KeyEquivalent("\u{7F}")
}
================================================
FILE: Maccy/Extensions/KeyboardShortcuts.Name+Shortcuts.swift
================================================
import KeyboardShortcuts
extension KeyboardShortcuts.Name {
static let popup = Self("popup", default: Shortcut(.c, modifiers: [.command, .shift]))
static let pin = Self("pin", default: Shortcut(.p, modifiers: [.option]))
static let delete = Self("delete", default: Shortcut(.delete, modifiers: [.option]))
static let togglePreview = Self("togglePreview", default: Shortcut(.space, modifiers: [.control]))
}
================================================
FILE: Maccy/Extensions/ModifierFlags+Description.swift
================================================
import AppKit.NSEvent
import Carbon.HIToolbox
// https://github.com/sindresorhus/KeyboardShortcuts/blob/e6b60117ec266e1e5d059f7f34815144f9762b36/Sources/KeyboardShortcuts/Utilities.swift#L308-L342
extension NSEvent.ModifierFlags {
var description: String {
var description = ""
if contains(.control) {
description += "⌃"
}
if contains(.option) {
description += "⌥"
}
if contains(.shift) {
description += "⇧"
}
if contains(.command) {
description += "⌘"
}
if contains(.function) {
description += "🌐\u{FE0E}"
}
return description
}
}
================================================
FILE: Maccy/Extensions/NSApplication+Windows.swift
================================================
import AppKit
extension NSApplication {
var alertWindow: NSWindow? { windows.first { $0.className == "_NSAlertPanel" } }
var characterPickerWindow: NSWindow? { windows.first { $0.className == "NSPanelViewBridge" } }
}
================================================
FILE: Maccy/Extensions/NSImage+Names.swift
================================================
import Cocoa
extension NSImage {
static let gearshape = NSImage(systemSymbolName: "gearshape", accessibilityDescription: "gearshape")
static let externaldrive = NSImage(systemSymbolName: "externaldrive", accessibilityDescription: "externaldrive")
static let paintpalette = NSImage(systemSymbolName: "paintpalette", accessibilityDescription: "paintpalette")
static let pincircle = NSImage(systemSymbolName: "pin.circle", accessibilityDescription: "pin.cirlce")
static let nosign = NSImage(systemSymbolName: "nosign", accessibilityDescription: "nosign")
static let gearshape2 = NSImage(systemSymbolName: "gearshape.2", accessibilityDescription: "gearshape2")
}
extension NSImage.Name {
static let clipboard = NSImage.Name("clipboard.fill")
static let maccyStatusBar = NSImage.Name("StatusBarMenuImage")
static let scissors = NSImage.Name("scissors")
static let paperclip = NSImage.Name("paperclip")
}
================================================
FILE: Maccy/Extensions/NSImage+Resized.swift
================================================
import AppKit.NSImage
// Based on https://stackoverflow.com/questions/73062803/resizing-nsimage-keeping-aspect-ratio-reducing-the-image-size-while-trying-to-sc.
extension NSImage {
func resized(to newSize: NSSize) -> NSImage {
let ratioX = newSize.width / size.width
let ratioY = newSize.height / size.height
let ratio = ratioX < ratioY ? ratioX : ratioY
let newHeight = size.height * ratio
let newWidth = size.width * ratio
let newSize = NSSize(width: newWidth, height: newHeight)
// Don't attempt to size up.
if newSize.height >= size.height {
return self
}
return NSImage(size: newSize, flipped: false) { destRect in
if let context = NSGraphicsContext.current {
context.imageInterpolation = .high
self.draw(in: destRect, from: NSRect.zero, operation: .copy, fraction: 1)
}
return true
}
}
}
================================================
FILE: Maccy/Extensions/NSPasteboard.PasteboardType+Types.swift
================================================
import AppKit.NSPasteboard
import Defaults
extension NSPasteboard.PasteboardType: Defaults.Serializable {
static let heic = NSPasteboard.PasteboardType(rawValue: "public.heic")
static let jpeg = NSPasteboard.PasteboardType(rawValue: "public.jpeg")
static let universalClipboard = NSPasteboard.PasteboardType(rawValue: "com.apple.is-remote-clipboard")
// See http://nspasteboard.org for more details.
static let autoGenerated = NSPasteboard.PasteboardType(rawValue: "org.nspasteboard.AutoGeneratedType")
static let concealed = NSPasteboard.PasteboardType(rawValue: "org.nspasteboard.ConcealedType")
static let source = NSPasteboard.PasteboardType(rawValue: "org.nspasteboard.source")
static let transient = NSPasteboard.PasteboardType(rawValue: "org.nspasteboard.TransientType")
// https://github.com/p0deje/Maccy/issues/429#issuecomment-1182575226
static let modified = NSPasteboard.PasteboardType(rawValue: "x.nspasteboard.ModifiedType")
// Marks that copy was made from Maccy.
static let fromMaccy = NSPasteboard.PasteboardType(rawValue: "org.p0deje.Maccy")
// Types that indicate Microsoft Word bookmarks (links).
static let microsoftObjectLink = NSPasteboard.PasteboardType(rawValue: "com.microsoft.ObjectLink")
static let microsoftLinkSource = NSPasteboard.PasteboardType(rawValue: "com.microsoft.Link-Source")
// Safari preview and extra metadata that changes frequently.
static let linkPresentationMetadata = NSPasteboard.PasteboardType(rawValue: "com.apple.linkpresentation.metadata")
// swiftlint:disable:next line_length
static let customWebKitPasteboardData = NSPasteboard.PasteboardType(rawValue: "com.apple.WebKit.custom-pasteboard-data")
// Chromium (VSCode)
static let customChromiumWebData = NSPasteboard.PasteboardType(rawValue: "org.chromium.web-custom-data")
static let chromiumSourceUrl = NSPasteboard.PasteboardType(rawValue: "org.chromium.source-url")
static let chromiumSourceToken = NSPasteboard.PasteboardType(rawValue: "org.chromium.internal.source-rfh-token")
// Apple Notes
static let notesRichText = NSPasteboard.PasteboardType(rawValue: "com.apple.notes.richtext")
}
================================================
FILE: Maccy/Extensions/NSPoint+DefaultsSerializable.swift
================================================
import CoreGraphics
import Defaults
import Foundation
extension NSPoint: Defaults.Serializable {
}
================================================
FILE: Maccy/Extensions/NSRect+Centered.swift
================================================
import Foundation
extension NSRect {
static func centered(ofSize size: NSSize, in frame: NSRect) -> NSRect {
let bottomLeftX = (frame.width - size.width) / 2 + frame.minX
let bottomLeftY = (frame.height - size.height) / 2 + frame.minY
return NSRect(x: bottomLeftX + 1.0, y: bottomLeftY + 1.0, width: size.width, height: size.height)
}
}
================================================
FILE: Maccy/Extensions/NSRunningApplication+WindowFrame.swift
================================================
import AppKit.NSRunningApplication
import Carbon
extension NSRunningApplication {
var windowFrame: NSRect? {
let options = CGWindowListOption(arrayLiteral: [.excludeDesktopElements, .optionOnScreenOnly])
let windowListInfo = CGWindowListCopyWindowInfo(options, CGWindowID(0))
if let windowInfoList = windowListInfo as NSArray? as? [[String: AnyObject]] {
for info in windowInfoList {
if let windowPID = info["kCGWindowOwnerPID"] as? UInt32, windowPID == processIdentifier {
if let screen = NSScreen.screens.first,
let topLeftX = info["kCGWindowBounds"]?["X"] as? Double,
let topLeftY = info["kCGWindowBounds"]?["Y"] as? Double,
let width = info["kCGWindowBounds"]?["Width"] as? Double,
let height = info["kCGWindowBounds"]?["Height"] as? Double {
var rect = NSRect(x: topLeftX, y: topLeftY, width: width, height: height)
// Convert CGWindowBounds to NSScreen coordinates
// http://www.krizka.net/2010/04/20/converting-between-kcgwindowbounds-and-nswindowframe
rect.origin.y = screen.frame.size.height - rect.origin.y - rect.size.height
return rect
}
}
}
}
return nil
}
}
================================================
FILE: Maccy/Extensions/NSScreen+ForPopup.swift
================================================
import AppKit.NSScreen
import Defaults
extension NSScreen {
static var forPopup: NSScreen? {
let desiredScreen = Defaults[.popupScreen]
if desiredScreen == 0 || desiredScreen > NSScreen.screens.count {
return NSScreen.main
} else {
return NSScreen.screens[desiredScreen - 1]
}
}
}
================================================
FILE: Maccy/Extensions/NSSize+DefaultsSerializable.swift
================================================
import CoreGraphics
import Defaults
import Foundation
extension NSSize: Defaults.Serializable {
}
================================================
FILE: Maccy/Extensions/NSSound+Named.swift
================================================
import AppKit.NSSound
extension NSSound {
static let knock = NSSound(
contentsOf: Bundle.main.url(forResource: "Knock", withExtension: "caf")!, byReference: true)
static let write = NSSound(
contentsOf: Bundle.main.url(forResource: "Write", withExtension: "caf")!, byReference: true)
}
================================================
FILE: Maccy/Extensions/NSWorkspace+ApplicationName.swift
================================================
import AppKit.NSWorkspace
extension NSWorkspace {
func applicationName(url: URL) -> String {
if let bundle = Bundle(url: url) {
if let displayName = bundle.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String {
return displayName
} else if let name = bundle.object(forInfoDictionaryKey: "CFBundleName") as? String {
return name
}
}
return url.deletingLastPathComponent().lastPathComponent
}
}
================================================
FILE: Maccy/Extensions/Sauce+KeyboardShortcuts.swift
================================================
import KeyboardShortcuts
import Sauce
extension Sauce {
func key(shortcut: KeyboardShortcuts.Name) -> Key? {
if let shortcut = KeyboardShortcuts.Shortcut(name: shortcut) {
return Sauce.shared.key(for: shortcut.carbonKeyCode)
} else {
return nil
}
}
}
================================================
FILE: Maccy/Extensions/Settings.PaneIdentifier+Panes.swift
================================================
import Settings
extension Settings.PaneIdentifier {
static let advanced = Self("advanced")
static let appearance = Self("appearance")
static let general = Self("general")
static let ignore = Self("ignore")
static let pins = Self("pins")
static let storage = Self("storage")
}
================================================
FILE: Maccy/Extensions/String+Identifiable.swift
================================================
extension String: @retroactive Identifiable {
public var id: Self { self }
}
================================================
FILE: Maccy/Extensions/String+Shortened.swift
================================================
extension String {
func shortened(to maxLength: Int) -> String {
guard count > maxLength else {
return self
}
return String(self[...index(startIndex, offsetBy: maxLength)])
}
}
================================================
FILE: Maccy/FloatingPanel.swift
================================================
import Defaults
import SwiftUI
// An NSPanel subclass that implements floating panel traits.
// https://stackoverflow.com/questions/46023769/how-to-show-a-window-without-stealing-focus-on-macos
class FloatingPanel<Content: View>: NSPanel, NSWindowDelegate {
var isPresented: Bool = false
var statusBarButton: NSStatusBarButton?
let onClose: () -> Void
override var isMovable: Bool {
get { Defaults[.popupPosition] != .statusItem }
set {}
}
init(
contentRect: NSRect,
identifier: String = "",
statusBarButton: NSStatusBarButton? = nil,
onClose: @escaping () -> Void,
view: () -> Content
) {
self.onClose = onClose
super.init(
contentRect: contentRect,
styleMask: [.nonactivatingPanel, .resizable, .closable, .fullSizeContentView],
backing: .buffered,
defer: false
)
self.statusBarButton = statusBarButton
self.identifier = NSUserInterfaceItemIdentifier(identifier)
Defaults[.windowSize] = contentRect.size
delegate = self
animationBehavior = .none
isFloatingPanel = true
level = .statusBar
collectionBehavior = [.auxiliary, .stationary, .moveToActiveSpace, .fullScreenAuxiliary]
titleVisibility = .hidden
titlebarAppearsTransparent = true
isMovableByWindowBackground = true
hidesOnDeactivate = false
backgroundColor = .clear
titlebarSeparatorStyle = .none
// Hide all traffic light buttons
standardWindowButton(.closeButton)?.isHidden = true
standardWindowButton(.miniaturizeButton)?.isHidden = true
standardWindowButton(.zoomButton)?.isHidden = true
contentView = NSHostingView(
rootView: view()
// The safe area is ignored because the title bar still interferes with the geometry
.ignoresSafeArea()
.gesture(DragGesture()
.onEnded { _ in
self.saveWindowPosition()
})
)
contentView?.layer?.cornerRadius = Popup.cornerRadius + Popup.horizontalPadding
}
func toggle(height: CGFloat, at popupPosition: PopupPosition = Defaults[.popupPosition]) {
if isPresented {
close()
} else {
open(height: height, at: popupPosition)
}
}
func open(height: CGFloat, at popupPosition: PopupPosition = Defaults[.popupPosition]) {
let size = Defaults[.windowSize]
setContentSize(NSSize(width: min(frame.width, size.width), height: min(height, size.height)))
setFrameOrigin(popupPosition.origin(size: frame.size, statusBarButton: statusBarButton))
orderFrontRegardless()
makeKey()
isPresented = true
if popupPosition == .statusItem {
DispatchQueue.main.async {
self.statusBarButton?.isHighlighted = true
}
}
}
func verticallyResize(to newHeight: CGFloat) {
var newSize = frame.size
newSize.height = newHeight
var newOrigin = frame.origin
newOrigin.y += (frame.height - newSize.height)
NSAnimationContext.runAnimationGroup { (context) in
context.duration = 0.2
animator().setFrame(NSRect(origin: newOrigin, size: newSize), display: true)
}
}
func determinePreviewPlacement() {
let preview = AppState.shared.preview
guard !preview.state.isOpen else { return }
let newSize = preview.computeSizeWithPreview(frame.size, state: .open)
preview.placement = preview.computePlacement(window: self, for: newSize)
}
func saveWindowPosition() {
if let screenFrame = screen?.visibleFrame {
// Only store the size of the window without the preview
let width = AppState.shared.preview.contentWidth
let anchorX = frame.minX + width / 2 - screenFrame.minX
let anchorY = frame.maxY - screenFrame.minY
Defaults[.windowPosition] = NSPoint(x: anchorX / screenFrame.width, y: anchorY / screenFrame.height)
}
}
func saveWindowFrame(frame: NSRect) {
Defaults[.windowSize] = frame.size
saveWindowPosition()
}
func windowWillResize(_ sender: NSWindow, to frameSize: NSSize) -> NSSize {
let preview = AppState.shared.preview
if inLiveResize && preview.resizingMode == .none {
let screenPoint = NSEvent.mouseLocation
let windowPoint = convertPoint(fromScreen: screenPoint)
let location: SlideoutPlacement = windowPoint.x <= frame.width / 2 ? .left : .right
if (location == preview.placement) && preview.state == .open {
preview.startResize(mode: .slideout)
} else {
preview.startResize(mode: .content)
}
}
var finalFrameSize = frameSize
var minContent = preview.minimumContentWidth
var minPreview = 0.0
if inLiveResize && preview.resizingMode != .none {
if preview.resizingMode == .content && preview.state == .open {
minPreview = preview.slideoutWidth
}
if preview.resizingMode == .slideout {
minPreview = preview.minimumSlideoutWidth
minContent = preview.contentWidth
}
}
finalFrameSize.width = max(finalFrameSize.width, minContent + minPreview)
if !AppState.shared.preview.state.isAnimating {
var size = frame.size
// Only store the size of the window without the preview
size.width = AppState.shared.preview.contentWidth
saveWindowFrame(frame: NSRect(origin: frame.origin, size: size))
}
return finalFrameSize
}
func windowWillMove(_ notification: Notification) {
determinePreviewPlacement()
}
func windowDidMove(_ notification: Notification) {
determinePreviewPlacement()
}
func windowWillStartLiveResize(_ notification: Notification) {
AppState.shared.preview.cancelAutoOpen()
}
func windowDidEndLiveResize(_ notification: Notification) {
AppState.shared.preview.startAutoOpen()
AppState.shared.preview.endResize()
}
func windowDidBecomeKey(_ notification: Notification) {
AppState.shared.preview.enableAutoOpen()
if AppState.shared.navigator.leadHistoryItem != nil {
AppState.shared.preview.startAutoOpen()
}
}
func windowDidResignKey(_ notification: Notification) {
AppState.shared.preview.disableAutoOpen()
}
// Close automatically when out of focus, e.g. outside click.
override func resignKey() {
super.resignKey()
// Don't hide if confirmation is shown.
if NSApp.alertWindow == nil {
close()
}
}
override func close() {
super.close()
AppState.shared.preview.state = .closed
isPresented = false
statusBarButton?.isHighlighted = false
onClose()
}
// Allow text inputs inside the panel can receive focus
override var canBecomeKey: Bool {
return true
}
}
================================================
FILE: Maccy/GlobalHotKey.swift
================================================
import AppKit
import KeyboardShortcuts
import Sauce
import SwiftUI
class GlobalHotKey {
typealias Handler = () -> Void
static public var key: KeyEquivalent? { KeyboardShortcuts.Shortcut(name: .popup)?.toKeyEquivalent() }
static public var modifierFlags: EventModifiers? { KeyboardShortcuts.Shortcut(name: .popup)?.toEventModifiers() }
private var handler: Handler
init(_ handler: @escaping Handler) {
self.handler = handler
// KeyboardShortcuts.onKeyDown(for: .popup, action: handler)
}
}
================================================
FILE: Maccy/HighlightMatch.swift
================================================
import Foundation
import Defaults
enum HighlightMatch: String, CaseIterable, Identifiable, CustomStringConvertible, Defaults.Serializable {
case color
case bold
case italic
case underline
var id: Self { self }
var description: String {
switch self {
case .bold:
return NSLocalizedString("HighlightMatchBold", tableName: "AppearanceSettings", comment: "")
case .color:
return NSLocalizedString("HighlightMatchColor", tableName: "AppearanceSettings", comment: "")
case .italic:
return NSLocalizedString("HighlightMatchItalic", tableName: "AppearanceSettings", comment: "")
case .underline:
return NSLocalizedString("HighlightMatchUnderline", tableName: "AppearanceSettings", comment: "")
}
}
}
================================================
FILE: Maccy/History.xcdatamodeld/History.xcdatamodel/contents
================================================
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19E266" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<elements/>
</model>
================================================
FILE: Maccy/HistoryItemAction.swift
================================================
import AppKit.NSEvent
import Defaults
enum HistoryItemAction {
case unknown
case copy
case paste
case pasteWithoutFormatting
init(_ modifierFlags: NSEvent.ModifierFlags) { // swiftlint:disable:this cyclomatic_complexity
switch modifierFlags {
case .command where !Defaults[.pasteByDefault]:
self = .copy
case .command where Defaults[.pasteByDefault] && !Defaults[.removeFormattingByDefault]:
self = .paste
case .command where Defaults[.pasteByDefault] && Defaults[.removeFormattingByDefault]:
self = .pasteWithoutFormatting
case .option where !Defaults[.pasteByDefault] && !Defaults[.removeFormattingByDefault]:
self = .paste
case .option where !Defaults[.pasteByDefault] && Defaults[.removeFormattingByDefault]:
self = .pasteWithoutFormatting
case .option where Defaults[.pasteByDefault] && !Defaults[.removeFormattingByDefault]:
self = .copy
case .option where Defaults[.pasteByDefault] && Defaults[.removeFormattingByDefault]:
self = .copy
case [.option, .shift] where !Defaults[.pasteByDefault] && !Defaults[.removeFormattingByDefault]:
self = .pasteWithoutFormatting
case [.option, .shift] where !Defaults[.pasteByDefault] && Defaults[.removeFormattingByDefault]:
self = .paste
case [.command, .shift] where Defaults[.pasteByDefault] && !Defaults[.removeFormattingByDefault]:
self = .pasteWithoutFormatting
case [.command, .shift] where Defaults[.pasteByDefault] && Defaults[.removeFormattingByDefault]:
self = .paste
default:
self = .unknown
}
}
var modifierFlags: NSEvent.ModifierFlags {
switch self {
case .copy where !Defaults[.pasteByDefault]:
return .command
case .paste where Defaults[.pasteByDefault] && !Defaults[.removeFormattingByDefault]:
return .command
case .pasteWithoutFormatting where Defaults[.pasteByDefault] && Defaults[.removeFormattingByDefault]:
return .command
case .paste where !Defaults[.pasteByDefault] && !Defaults[.removeFormattingByDefault]:
return .option
case .pasteWithoutFormatting where !Defaults[.pasteByDefault] && Defaults[.removeFormattingByDefault]:
return .option
case .copy where Defaults[.pasteByDefault] && !Defaults[.removeFormattingByDefault]:
return .option
case .copy where Defaults[.pasteByDefault] && Defaults[.removeFormattingByDefault]:
return .option
case .pasteWithoutFormatting where !Defaults[.pasteByDefault] && !Defaults[.removeFormattingByDefault]:
return [.option, .shift]
case .paste where !Defaults[.pasteByDefault] && Defaults[.removeFormattingByDefault]:
return [.option, .shift]
case .pasteWithoutFormatting where Defaults[.pasteByDefault] && !Defaults[.removeFormattingByDefault]:
return [.command, .shift]
case .paste where Defaults[.pasteByDefault] && Defaults[.removeFormattingByDefault]:
return [.command, .shift]
default:
return []
}
}
}
================================================
FILE: Maccy/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © Alexey Rodionov</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>SUEnableDownloaderService</key>
<true/>
<key>SUEnableInstallerLauncherService</key>
<true/>
<key>SUFeedURL</key>
<string>https://raw.githubusercontent.com/p0deje/Maccy/master/appcast.xml</string>
</dict>
</plist>
================================================
FILE: Maccy/Intents/AppIntentError.swift
================================================
import Foundation
enum AppIntentError: Swift.Error, CustomLocalizedStringResourceConvertible {
case notFound
var localizedStringResource: LocalizedStringResource {
switch self {
case .notFound: return "Clipboard item not found"
}
}
}
================================================
FILE: Maccy/Intents/Clear.swift
================================================
import AppIntents
import Defaults
struct Clear: AppIntent, CustomIntentMigratedAppIntent {
static let intentClassName = "ClearIntent"
static var title: LocalizedStringResource = "Clear Clipboard History"
static var description = IntentDescription("Clears all Maccy clipboard history except for pinned items.")
static var parameterSummary: some ParameterSummary {
Summary("Clear Clipboard History")
}
func perform() async throws -> some IntentResult {
if !Defaults[.suppressClearAlert] {
try await requestConfirmation()
}
await AppState.shared.history.clear()
return .result()
}
}
================================================
FILE: Maccy/Intents/Delete.swift
================================================
import AppIntents
struct Delete: AppIntent, CustomIntentMigratedAppIntent {
static let intentClassName = "DeleteIntent"
static var title: LocalizedStringResource = "Delete Item from Clipboard History"
static var description = IntentDescription("Deletes an item from Maccy clipboard history.")
@Parameter(title: "Number", default: 1)
var number: Int
static var parameterSummary: some ParameterSummary {
Summary("Delete \(\.$number) Item from Clipboard History")
}
private let positionOffset = 1
func perform() async throws -> some IntentResult {
let items = AppState.shared.history.items
let index = number - positionOffset
guard items.count >= index else {
throw AppIntentError.notFound
}
await AppState.shared.history.delete(items[index])
return .result()
}
}
================================================
FILE: Maccy/Intents/Get.swift
================================================
import Foundation
import AppIntents
struct Get: AppIntent, CustomIntentMigratedAppIntent {
static let intentClassName = "GetIntent"
static var title: LocalizedStringResource = "Get Item from Clipboard History"
static var description = IntentDescription("""
Gets an item from Maccy clipboard history.
The returned item can be used to access its plain/rich/HTML text, image contents or file location.
""")
@Parameter(title: "Selected", default: true)
var selected: Bool
@Parameter(title: "Number", default: 1)
var number: Int
private let positionOffset = 1
static var parameterSummary: some ParameterSummary {
When(\.$selected, .equalTo, false) {
Summary {
\.$number
\.$selected
}
} otherwise: {
Summary {
\.$selected
}
}
}
func perform() async throws -> some IntentResult & ReturnsValue<HistoryItemAppEntity> {
var item: HistoryItem?
if selected {
item = AppState.shared.navigator.selection.first?.item
} else {
let index = number - positionOffset
if AppState.shared.history.items.count >= index {
item = AppState.shared.history.items[index].item
}
}
guard let item else {
throw AppIntentError.notFound
}
let intentItem = HistoryItemAppEntity()
intentItem.text = item.text
if let html = item.htmlData {
intentItem.html = String(data: html, encoding: .utf8)
}
if let fileURL = item.fileURLs.first {
intentItem.file = fileURL
}
if let imageData = item.imageData {
let file = URL.documentsDirectory.appending(path: "image.png")
try imageData.write(to: file, options: [.atomic, .completeFileProtection])
intentItem.image = file
}
if let rtf = item.rtfData {
intentItem.richText = String(data: rtf, encoding: .utf8)
}
return .result(value: intentItem)
}
}
================================================
FILE: Maccy/Intents/HistoryItemAppEntity.swift
================================================
import AppIntents
struct HistoryItemAppEntity: TransientAppEntity {
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Clipboard item")
@Property(title: "File")
var file: URL?
@Property(title: "HTML")
var html: String?
@Property(title: "Image")
var image: URL?
@Property(title: "Rich Text")
var richText: String?
@Property(title: "Text")
var text: String?
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "Clipboard item")
}
}
================================================
FILE: Maccy/Intents/Select.swift
================================================
import AppIntents
struct Select: AppIntent, CustomIntentMigratedAppIntent {
static let intentClassName = "SelectIntent"
static var title: LocalizedStringResource = "Select Item in Clipboard History"
static var description = IntentDescription("""
Selects an item in Maccy clipboard history.
Depending on Maccy settings, it might trigger pasting of the selected item.
""")
static var parameterSummary: some ParameterSummary {
Summary("Select \(\.$number) Item in Clipboard History")
}
@Parameter(title: "Number", default: 1, requestValueDialog: "What is the number of the item?")
var number: Int
private let positionOffset = 1
func perform() async throws -> some IntentResult & ReturnsValue<String> {
let items = AppState.shared.history.items
let index = number - positionOffset
guard items.count >= index else {
throw AppIntentError.notFound
}
let value = items[index].title
await AppState.shared.history.select(items[index])
return .result(value: value)
}
}
================================================
FILE: Maccy/ItemsProtocol.swift
================================================
protocol HasVisibility {
var isVisible: Bool { get }
}
protocol ItemsContainer {
associatedtype Item
var containerVisible: Bool { get }
var items: [Item] { get set }
}
extension ItemsContainer {
var containerVisible: Bool { true }
}
private extension ItemsContainer where Item: HasVisibility {}
extension ItemsContainer where Item: HasVisibility {
var visibleItems: [Item] {
guard containerVisible else { return [] }
return self.items.lazy.filter(\.isVisible)
}
var firstVisibleItem: Item? {
guard containerVisible else { return nil }
return self.items.first(where: \.isVisible)
}
func firstVisibleItem(where predicate: (Item) -> Bool) -> Item? {
guard containerVisible else { return nil }
return self.items.first { $0.isVisible && predicate($0) }
}
var lastVisibleItem: Item? {
guard containerVisible else { return nil }
return self.items.last(where: \.isVisible)
}
func lastVisibleItem(where predicate: (Item) -> Bool) -> Item? {
guard containerVisible else { return nil }
return self.items.last { $0.isVisible && predicate($0) }
}
}
extension ItemsContainer where Item: HasVisibility, Item: Equatable {
func visibleItem(before: Item) -> Item? {
return self.items.item(before: before, where: \.isVisible)
}
func visibleItem(after: Item) -> Item? {
return self.items.item(after: after, where: \.isVisible)
}
}
================================================
FILE: Maccy/KeyChord.swift
================================================
import AppKit.NSEvent
import KeyboardShortcuts
import Sauce
enum KeyChord: CaseIterable {
static var pasteKey: Key { pasteMenuItem?.key ?? Key.v }
static var pasteKeyModifiers: NSEvent.ModifierFlags { pasteMenuItem?.keyEquivalentModifierMask ?? .command }
private static var pasteMenuItem: NSMenuItem? {
NSApp.mainMenu?.items
.flatMap { $0.submenu?.items ?? [] }
.first { $0.action == #selector(NSText.paste) }
}
static var deleteKey: Key? { Sauce.shared.key(shortcut: .delete) }
static var deleteModifiers: NSEvent.ModifierFlags? { KeyboardShortcuts.Shortcut(name: .delete)?.modifiers }
static var pinKey: Key? { Sauce.shared.key(shortcut: .pin) }
static var pinModifiers: NSEvent.ModifierFlags? { KeyboardShortcuts.Shortcut(name: .pin)?.modifiers }
static var previewKey: Key? { Sauce.shared.key(shortcut: .togglePreview) }
static var previewModifiers: NSEvent.ModifierFlags? { KeyboardShortcuts.Shortcut(name: .togglePreview)?.modifiers }
case clearHistory
case clearHistoryAll
case clearSearch
case deleteCurrentItem
case deleteOneCharFromSearch
case deleteLastWordFromSearch
case ignored
case moveToNext
case moveToLast
case moveToPrevious
case moveToFirst
case extendToNext
case extendToLast
case extendToPrevious
case extendToFirst
case openPreferences
case pinOrUnpin
case selectCurrentItem
case close
case togglePreview
case unknown
init(_ event: NSEvent?) {
guard let event, event.type == .keyDown else {
self = .unknown
return
}
let modifierFlags = event.modifierFlags
.intersection(.deviceIndependentFlagsMask)
.subtracting([.capsLock, .numericPad, .function])
var key: Key?
if KeyboardLayout.current.commandSwitchesToQWERTY, modifierFlags.contains(.command) {
key = Key(QWERTYKeyCode: Int(event.keyCode))
} else {
key = Sauce.shared.key(for: Int(event.keyCode))
}
guard let key else {
self = .unknown
return
}
self.init(key, modifierFlags)
}
// swiftlint:disable:next cyclomatic_complexity function_body_length
init(_ key: Key, _ modifierFlags: NSEvent.ModifierFlags) {
switch (key, modifierFlags) {
case (.delete, [.command, .option]):
self = .clearHistory
case (.delete, [.command, .option, .shift]):
self = .clearHistoryAll
case (.u, [.control]):
self = .clearSearch
case (KeyChord.deleteKey, KeyChord.deleteModifiers):
self = .deleteCurrentItem
case (.h, [.control]):
self = .deleteOneCharFromSearch
case (.w, [.control]):
self = .deleteLastWordFromSearch
case (.downArrow, [.shift]),
(.n, [.control, .shift]):
self = AppState.shared.multiSelectionEnabled ? .extendToNext : .moveToNext
case (.downArrow, []),
(.n, [.control]),
(.j, [.control]):
self = .moveToNext
case (.downArrow, [.command, .shift]),
(.downArrow, [.option, .shift]),
(.n, [.control, .option, .shift]):
self = AppState.shared.multiSelectionEnabled ? .extendToLast : .moveToLast
case (.downArrow, _) where modifierFlags.contains(.command) || modifierFlags.contains(.option),
(.n, [.control, .option]),
(.pageDown, []):
self = .moveToLast
case (.upArrow, [.shift]),
(.p, [.control, .shift]):
self = AppState.shared.multiSelectionEnabled ? .extendToPrevious : .moveToPrevious
case (.upArrow, []),
(.p, [.control]),
(.k, [.control]):
self = .moveToPrevious
case (.upArrow, [.command, .shift]),
(.upArrow, [.option, .shift]),
(.p, [.control, .option, .shift]):
self = AppState.shared.multiSelectionEnabled ? .extendToFirst : .moveToFirst
case (.upArrow, _) where modifierFlags.contains(.command) || modifierFlags.contains(.option),
(.p, [.control, .option]),
(.pageUp, []):
self = .moveToFirst
case (KeyChord.pinKey, KeyChord.pinModifiers):
self = .pinOrUnpin
case (.comma, [.command]):
self = .openPreferences
case (.return, _),
(.keypadEnter, _):
self = .selectCurrentItem
case (.escape, _):
self = .close
case (KeyChord.previewKey, KeyChord.previewModifiers):
self = .togglePreview
case (_, _) where !modifierFlags.isDisjoint(with: [.command, .control, .option]):
self = .ignored
default:
self = .unknown
}
}
}
================================================
FILE: Maccy/KeyShortcut.swift
================================================
import AppKit.NSEvent
import Defaults
import Sauce
struct KeyShortcut: Identifiable {
static func create(character: String) -> [KeyShortcut] {
let key = Key(character: character, virtualKeyCode: nil)
return [
KeyShortcut(key: key),
KeyShortcut(key: key, modifierFlags: [.option]),
KeyShortcut(key: key, modifierFlags: [Defaults[.pasteByDefault] ? .command : .option, .shift])
]
}
let id = UUID()
var key: Key?
var modifierFlags: NSEvent.ModifierFlags = [.command]
var description: String {
guard let key, let character = Sauce.shared.currentASCIICapableCharacter(
for: Int(Sauce.shared.keyCode(for: key)),
cocoaModifiers: []
) else {
return ""
}
return "\(modifierFlags.description)\(character.capitalized)"
}
func isVisible(_ all: [KeyShortcut], _ pressedModifierFlags: NSEvent.ModifierFlags) -> Bool {
if all.count == 1 {
return true
}
if modifierFlags == [.command], pressedModifierFlags.isEmpty {
return true
}
if modifierFlags == [.command], !pressedModifierFlags.isEmpty,
!all.contains(where: { $0.id != id && $0.modifierFlags == pressedModifierFlags }) {
return true
}
return modifierFlags == pressedModifierFlags
}
}
================================================
FILE: Maccy/KeyboardLayout.swift
================================================
import Carbon
import Sauce
class KeyboardLayout {
static var current: KeyboardLayout { KeyboardLayout() }
// Dvorak - QWERTY ⌘ (https://github.com/p0deje/Maccy/issues/482)
// bépo 1.1 - Azerty ⌘ (https://github.com/p0deje/Maccy/issues/520)
var commandSwitchesToQWERTY: Bool { localizedName.hasSuffix("⌘") }
var localizedName: String {
if let value = TISGetInputSourceProperty(inputSource, kTISPropertyLocalizedName) {
return Unmanaged<CFString>.fromOpaque(value).takeUnretainedValue() as String
} else {
return ""
}
}
private var inputSource: TISInputSource!
init() {
inputSource = TISCopyCurrentKeyboardLayoutInputSource().takeUnretainedValue()
}
}
================================================
FILE: Maccy/Maccy.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spks</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spki</string>
</array>
</dict>
</plist>
================================================
FILE: Maccy/MaccyApp.swift
================================================
import SwiftUI
@main
struct MaccyApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
// It's impossible to create sceneless application,
// so we are hacking this around by creating a menubar
// scene that is always hidden.
@State private var hiddenMenu: Bool = false
var body: some Scene {
MenuBarExtra("", isInserted: $hiddenMenu) {
EmptyView()
}
}
}
================================================
FILE: Maccy/MenuIcon.swift
================================================
import AppKit
import Defaults
enum MenuIcon: String, CaseIterable, Identifiable, Defaults.Serializable {
case maccy
case clipboard
case scissors
case paperclip
var id: Self { self }
var image: NSImage {
switch self {
case .maccy:
return NSImage(named: .maccyStatusBar)!
case .clipboard:
return NSImage(named: .clipboard)!
case .scissors:
return NSImage(named: .scissors)!
case .paperclip:
return NSImage(named: .paperclip)!
}
}
}
================================================
FILE: Maccy/Models/HistoryItem.swift
================================================
import AppKit
import Defaults
import Sauce
import SwiftData
import Vision
@Model
class HistoryItem {
static var supportedPins: Set<String> {
// "a" reserved for select all
// "q" reserved for quit
// "v" reserved for paste
// "w" reserved for close window
// "z" reserved for undo/redo
var keys = Set([
"b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l",
"m", "n", "o", "p", "r", "s", "t", "u", "x", "y"
])
if let deleteKey = KeyChord.deleteKey,
let character = Sauce.shared.character(for: Int(deleteKey.QWERTYKeyCode), cocoaModifiers: []) {
keys.remove(character)
}
if let pinKey = KeyChord.pinKey,
let character = Sauce.shared.character(for: Int(pinKey.QWERTYKeyCode), cocoaModifiers: []) {
keys.remove(character)
}
if let previewKey = KeyChord.previewKey,
let character = Sauce.shared.character(for: Int(previewKey.QWERTYKeyCode), cocoaModifiers: []) {
keys.remove(character)
}
return keys
}
@MainActor
static var availablePins: [String] {
let descriptor = FetchDescriptor<HistoryItem>(
predicate: #Predicate { $0.pin != nil }
)
let pins = try? Storage.shared.context.fetch(descriptor).compactMap({ $0.pin })
let assignedPins = Set(pins ?? [])
return Array(supportedPins.subtracting(assignedPins))
}
@MainActor
static var randomAvailablePin: String { availablePins.randomElement() ?? "" }
private static let transientTypes: [String] = [
NSPasteboard.PasteboardType.modified.rawValue,
NSPasteboard.PasteboardType.fromMaccy.rawValue,
NSPasteboard.PasteboardType.linkPresentationMetadata.rawValue,
NSPasteboard.PasteboardType.customWebKitPasteboardData.rawValue,
NSPasteboard.PasteboardType.source.rawValue,
NSPasteboard.PasteboardType.customChromiumWebData.rawValue,
NSPasteboard.PasteboardType.chromiumSourceUrl.rawValue,
NSPasteboard.PasteboardType.chromiumSourceToken.rawValue,
NSPasteboard.PasteboardType.notesRichText.rawValue
]
var application: String?
var firstCopiedAt: Date = Date.now
var lastCopiedAt: Date = Date.now
var numberOfCopies: Int = 1
var pin: String?
var title = ""
@Relationship(deleteRule: .cascade, inverse: \HistoryItemContent.item)
var contents: [HistoryItemContent] = []
init(contents: [HistoryItemContent] = []) {
self.firstCopiedAt = firstCopiedAt
self.lastCopiedAt = lastCopiedAt
self.contents = contents
}
func supersedes(_ item: HistoryItem) -> Bool {
return item.contents
.filter { content in
!Self.transientTypes.contains(content.type)
}
.allSatisfy { content in
contents.contains(where: { $0.type == content.type && $0.value == content.value })
}
}
func generateTitle() -> String {
guard image == nil else {
Task {
self.performTextRecognition()
}
return ""
}
// 1k characters is trade-off for performance
var title = previewableText.shortened(to: 1_000)
if Defaults[.showSpecialSymbols] {
if let range = title.range(of: "^ +", options: .regularExpression) {
title = title.replacingOccurrences(of: " ", with: "·", range: range)
}
if let range = title.range(of: " +$", options: .regularExpression) {
title = title.replacingOccurrences(of: " ", with: "·", range: range)
}
title = title
.replacingOccurrences(of: "\n", with: "⏎")
.replacingOccurrences(of: "\t", with: "⇥")
} else {
title = title.trimmingCharacters(in: .whitespacesAndNewlines)
}
return title
}
var previewableText: String {
if !fileURLs.isEmpty {
fileURLs
.compactMap { $0.absoluteString.removingPercentEncoding }
.joined(separator: "\n")
} else if let text = text, !text.isEmpty {
text
} else if let rtf = rtf, !rtf.string.isEmpty {
rtf.string
} else if let html = html, !html.string.isEmpty {
html.string
} else {
title
}
}
var fileURLs: [URL] {
guard !universalClipboardText else {
return []
}
return allContentData([.fileURL])
.compactMap { URL(dataRepresentation: $0, relativeTo: nil, isAbsolute: true) }
}
var htmlData: Data? { contentData([.html]) }
var html: NSAttributedString? {
guard let data = htmlData else {
return nil
}
return NSAttributedString(html: data, documentAttributes: nil)
}
var imageData: Data? {
var data: Data?
data = contentData([.tiff, .png, .jpeg, .heic])
if data == nil, universalClipboardImage, let url = fileURLs.first {
data = try? Data(contentsOf: url)
}
return data
}
var image: NSImage? {
guard let data = imageData else {
return nil
}
return NSImage(data: data)
}
var rtfData: Data? { contentData([.rtf]) }
var rtf: NSAttributedString? {
guard let data = rtfData else {
return nil
}
return NSAttributedString(rtf: data, documentAttributes: nil)
}
var text: String? {
guard let data = contentData([.string]) else {
return nil
}
return String(data: data, encoding: .utf8)
}
var modified: Int? {
guard let data = contentData([.modified]),
let modified = String(data: data, encoding: .utf8) else {
return nil
}
return Int(modified)
}
var fromMaccy: Bool { contentData([.fromMaccy]) != nil }
var universalClipboard: Bool { contentData([.universalClipboard]) != nil }
private var universalClipboardImage: Bool { universalClipboard && fileURLs.first?.pathExtension == "jpeg" }
private var universalClipboardText: Bool {
universalClipboard && contentData([.html, .tiff, .png, .jpeg, .rtf, .string, .heic]) != nil
}
private func contentData(_ types: [NSPasteboard.PasteboardType]) -> Data? {
let content = contents.first(where: { content in
return types.contains(NSPasteboard.PasteboardType(content.type))
})
return content?.value
}
private func allContentData(_ types: [NSPasteboard.PasteboardType]) -> [Data] {
return contents
.filter { types.contains(NSPasteboard.PasteboardType($0.type)) }
.compactMap { $0.value }
}
private func performTextRecognition() {
guard let cgImage = image?.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
return
}
let requestHandler = VNImageRequestHandler(cgImage: cgImage)
let request = VNRecognizeTextRequest(completionHandler: recognizeTextHandler)
request.recognitionLevel = .fast
do {
try requestHandler.perform([request])
} catch {
print("Unable to perform the request: \(error).")
}
}
private func recognizeTextHandler(request: VNRequest, error: Error?) {
guard let observations = request.results as? [VNRecognizedTextObservation] else {
return
}
let recognizedStrings = observations.compactMap { observation in
return observation.topCandidates(1).first?.string
}
self.title = recognizedStrings.joined(separator: "\n")
}
}
================================================
FILE: Maccy/Models/HistoryItemContent.swift
================================================
import Foundation
import SwiftData
@Model
class HistoryItemContent {
var type: String = ""
var value: Data?
@Relationship
var item: HistoryItem?
init(type: String, value: Data? = nil) {
self.type = type
self.value = value
}
}
================================================
FILE: Maccy/Notifier.swift
================================================
import AppKit
import UserNotifications
class Notifier {
private static var center: UNUserNotificationCenter { UNUserNotificationCenter.current() }
static func authorize() {
center.requestAuthorization(options: [.alert, .sound]) { _, error in
if error != nil {
NSLog("Failed to authorize notifications: \(String(describing: error))")
}
}
}
static func notify(body: String?, sound: NSSound?) {
guard let body else { return }
authorize()
center.getNotificationSettings { settings in
guard (settings.authorizationStatus == .authorized) ||
(settings.authorizationStatus == .provisional) else { return }
let content = UNMutableNotificationContent()
if settings.alertSetting == .enabled {
content.body = body
}
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
center.add(request) { error in
if error != nil {
NSLog("Failed to deliver notification: \(String(describing: error))")
} else {
if settings.soundSetting == .enabled {
sound?.play()
}
}
}
}
}
}
================================================
FILE: Maccy/Observables/AppState.swift
================================================
import AppKit
import Defaults
import Foundation
import Settings
import SwiftUI
@Observable
class AppState: Sendable {
static let shared = AppState(history: History.shared, footer: Footer())
let multiSelectionEnabled = false
var appDelegate: AppDelegate?
var popup: Popup
var history: History
var footer: Footer
var navigator: NavigationManager
var preview: SlideoutController
var searchVisible: Bool {
if !Defaults[.showSearch] { return false }
switch Defaults[.searchVisibility] {
case .always: return true
case .duringSearch: return !history.searchQuery.isEmpty
}
}
var menuIconText: String {
var title = history.unpinnedItems.first?.text.shortened(to: 100)
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
title.unicodeScalars.removeAll(where: CharacterSet.newlines.contains)
return title.shortened(to: 20)
}
private let about = About()
private var settingsWindowController: SettingsWindowController?
init(history: History, footer: Footer) {
self.history = history
self.footer = footer
popup = Popup()
navigator = NavigationManager(history: history, footer: footer)
preview = SlideoutController(
onContentResize: { contentWidth in
Defaults[.windowSize].width = contentWidth
},
onSlideoutResize: { previewWidth in
Defaults[.previewWidth] = previewWidth
})
preview.contentWidth = Defaults[.windowSize].width
preview.slideoutWidth = Defaults[.previewWidth]
}
@MainActor
func select() {
if !navigator.selection.isEmpty {
if navigator.isMultiSelectInProgress {
navigator.isManualMultiSelect = false
history.startPasteStack(selection: &navigator.selection)
} else {
history.select(navigator.selection.first)
}
} else if let item = footer.selectedItem {
// TODO: Use item.suppressConfirmation, but it's not updated!
if item.confirmation != nil, Defaults[.suppressClearAlert] == false {
item.showConfirmation = true
} else {
item.action()
}
} else {
Clipboard.shared.copy(history.searchQuery)
history.searchQuery = ""
}
}
@MainActor
func togglePin() {
withTransaction(Transaction()) {
navigator.selection.forEach { _, item in
history.togglePin(item)
}
}
}
@MainActor
func removePasteStack() {
history.interruptPasteStack()
navigator.highlightFirst()
}
@MainActor
func deleteSelection() {
guard let leadItem = navigator.leadHistoryItem else { return }
let nextUnselectedItem = history.visibleItems.nearest(to: leadItem) { !$0.isSelected }
withTransaction(Transaction()) {
navigator.selection.forEach { _, item in
history.delete(item)
}
navigator.select(item: nextUnselectedItem)
}
}
func openAbout() {
about.openAbout(nil)
}
@MainActor
func openPreferences() { // swiftlint:disable:this function_body_length
if settingsWindowController == nil {
settingsWindowController = SettingsWindowController(
panes: [
Settings.Pane(
identifier: Settings.PaneIdentifier.general,
title: NSLocalizedString("Title", tableName: "GeneralSettings", comment: ""),
toolbarIcon: NSImage.gearshape!
) {
GeneralSettingsPane()
},
Settings.Pane(
identifier: Settings.PaneIdentifier.storage,
title: NSLocalizedString("Title", tableName: "StorageSettings", comment: ""),
toolbarIcon: NSImage.externaldrive!
) {
StorageSettingsPane()
},
Settings.Pane(
identifier: Settings.PaneIdentifier.appearance,
title: NSLocalizedString("Title", tableName: "AppearanceSettings", comment: ""),
toolbarIcon: NSImage.paintpalette!
) {
AppearanceSettingsPane()
},
Settings.Pane(
identifier: Settings.PaneIdentifier.pins,
title: NSLocalizedString("Title", tableName: "PinsSettings", comment: ""),
toolbarIcon: NSImage.pincircle!
) {
PinsSettingsPane()
.environment(self)
.modelContainer(Storage.shared.container)
},
Settings.Pane(
identifier: Settings.PaneIdentifier.ignore,
title: NSLocalizedString("Title", tableName: "IgnoreSettings", comment: ""),
toolbarIcon: NSImage.nosign!
) {
IgnoreSettingsPane()
},
Settings.Pane(
identifier: Settings.PaneIdentifier.advanced,
title: NSLocalizedString("Title", tableName: "AdvancedSettings", comment: ""),
toolbarIcon: NSImage.gearshape2!
) {
AdvancedSettingsPane()
}
]
)
}
settingsWindowController?.show()
settingsWindowController?.window?.orderFrontRegardless()
}
func quit() {
NSApp.terminate(self)
}
}
================================================
FILE: Maccy/Observables/Footer.swift
================================================
import Defaults
import SwiftUI
@Observable
class Footer: ItemsContainer {
var items: [FooterItem] = []
var selectedItem: FooterItem? {
willSet {
selectedItem?.isSelected = false
newValue?.isSelected = true
}
}
var suppressClearAlert = Binding<Bool>(
get: { Defaults[.suppressClearAlert] },
set: { Defaults[.suppressClearAlert] = $0 }
)
private var showFooter: Bool {
return Defaults[.showFooter]
}
var containerVisible: Bool {
return showFooter
}
init() { // swiftlint:disable:this function_body_length
items = [
FooterItem(
title: "clear",
shortcuts: [KeyShortcut(key: .delete, modifierFlags: [.command, .option])],
help: "clear_tooltip",
confirmation: .init(
message: "clear_alert_message",
comment: "clear_alert_comment",
confirm: "clear_alert_confirm",
cancel: "clear_alert_cancel"
),
suppressConfirmation: suppressClearAlert
) {
Task { @MainActor in
AppState.shared.history.clear()
}
},
FooterItem(
title: "clear_all",
shortcuts: [KeyShortcut(key: .delete, modifierFlags: [.command, .option, .shift])],
help: "clear_all_tooltip",
confirmation: .init(
message: "clear_alert_message",
comment: "clear_alert_comment",
confirm: "clear_alert_confirm",
cancel: "clear_alert_cancel"
),
suppressConfirmation: suppressClearAlert
) {
Task { @MainActor in
AppState.shared.history.clearAll()
}
},
FooterItem(
title: "preferences",
shortcuts: [KeyShortcut(key: .comma)]
) {
Task { @MainActor in
AppState.shared.openPreferences()
}
},
FooterItem(
title: "about",
help: "about_tooltip"
) {
AppState.shared.openAbout()
},
FooterItem(
title: "quit",
shortcuts: [KeyShortcut(key: .q)],
help: "quit_tooltip"
) {
AppState.shared.quit()
}
]
}
}
================================================
FILE: Maccy/Observables/FooterItem.swift
================================================
import SwiftUI
@Observable
class FooterItem: Equatable, Identifiable, HasVisibility {
struct Confirmation {
var message: LocalizedStringKey
var comment: LocalizedStringKey
var confirm: LocalizedStringKey
var cancel: LocalizedStringKey
}
static func == (lhs: FooterItem, rhs: FooterItem) -> Bool {
return lhs.id == rhs.id
}
let id = UUID()
var title: String
var shortcuts: [KeyShortcut] = []
var help: LocalizedStringKey?
var isSelected: Bool = false
var confirmation: Confirmation?
var showConfirmation: Bool = false
var suppressConfirmation: Binding<Bool>?
var isVisible: Bool = true
var action: () -> Void
init(
title: String,
shortcuts: [KeyShortcut] = [],
help: LocalizedStringKey? = nil,
confirmation: Confirmation? = nil,
suppressConfirmation: Binding<Bool>? = nil,
action: @escaping () -> Void
) {
self.title = title
self.shortcuts = shortcuts
self.help = help
self.confirmation = confirmation
self.suppressConfirmation = suppressConfirmation
self.action = action
}
}
================================================
FILE: Maccy/Observables/History.swift
================================================
// swiftlint:disable file_length
import AppKit.NSRunningApplication
import Defaults
import Foundation
import Logging
import Observation
import Sauce
import Settings
import SwiftData
@Observable
class History: ItemsContainer { // swiftlint:disable:this type_body_length
static let shared = History()
let logger = Logger(label: "org.p0deje.Maccy")
var items: [HistoryItemDecorator] = []
var pasteStack: PasteStack?
var pinnedItems: [HistoryItemDecorator] { items.filter(\.isPinned) }
var unpinnedItems: [HistoryItemDecorator] { items.filter(\.isUnpinned) }
var searchQuery: String = "" {
didSet {
throttler.throttle { [self] in
updateItems(search.search(string: searchQuery, within: all))
if searchQuery.isEmpty {
AppState.shared.navigator.select(item: unpinnedItems.first)
} else {
AppState.shared.navigator.highlightFirst()
}
AppState.shared.popup.needsResize = true
}
}
}
var pressedShortcutItem: HistoryItemDecorator? {
guard let event = NSApp.currentEvent else {
return nil
}
let modifierFlags = event.modifierFlags
.intersection(.deviceIndependentFlagsMask)
.subtracting(.capsLock)
guard HistoryItemAction(modifierFlags) != .unknown else {
return nil
}
let key = Sauce.shared.key(for: Int(event.keyCode))
return items.first { $0.shortcuts.contains(where: { $0.key == key }) }
}
private let search = Search()
private let sorter = Sorter()
private let throttler = Throttler(minimumDelay: 0.2)
@ObservationIgnored
private var sessionLog: [Int: HistoryItem] = [:]
// The distinction between `all` and `items` is the following:
// - `all` stores all history items, even the ones that are currently hidden by a search
// - `items` stores only visible history items, updated during a search
@ObservationIgnored
var all: [HistoryItemDecorator] = []
init() {
Task {
for await _ in Defaults.updates(.pasteByDefault, initial: false) {
updateShortcuts()
}
}
Task {
for await _ in Defaults.updates(.sortBy, initial: false) {
try? await load()
}
}
Task {
for await _ in Defaults.updates(.pinTo, initial: false) {
try? await load()
}
}
Task {
for await _ in Defaults.updates(.showSpecialSymbols, initial: false) {
for item in items {
await updateTitle(item: item, title: item.item.generateTitle())
}
}
}
Task {
for await _ in Defaults.updates(.imageMaxHeight, initial: false) {
for item in items {
await item.cleanupImages()
}
}
}
}
@MainActor
func load() async throws {
let descriptor = FetchDescriptor<HistoryItem>()
let results = try Storage.shared.context.fetch(descriptor)
all = sorter.sort(results).map { HistoryItemDecorator($0) }
items = all
limitHistorySize(to: Defaults[.size])
updateShortcuts()
// Ensure that panel size is proper *after* loading all items.
Task {
AppState.shared.popup.needsResize = true
}
}
@MainActor
private func limitHistorySize(to maxSize: Int) {
let unpinned = all.filter(\.isUnpinned)
if unpinned.count >= maxSize {
unpinned[maxSize...].forEach(delete)
}
}
@MainActor
func insertIntoStorage(_ item: HistoryItem) throws {
logger.info("Inserting item with id '\(item.title)'")
Storage.shared.context.insert(item)
Storage.shared.context.processPendingChanges()
try? Storage.shared.context.save()
}
@discardableResult
@MainActor
func add(_ item: HistoryItem) -> HistoryItemDecorator {
if #available(macOS 15.0, *) {
try? History.shared.insertIntoStorage(item)
} else {
// On macOS 14 the history item needs to be inserted into storage directly after creating it.
// It was already inserted after creation in Clipboard.swift
}
var removedItemIndex: Int?
if let existingHistoryItem = findSimilarItem(item) {
if isModified(item) == nil {
item.contents = existingHistoryItem.contents
}
item.firstCopiedAt = existingHistoryItem.firstCopiedAt
item.numberOfCopies += existingHistoryItem.numberOfCopies
item.pin = existingHistoryItem.pin
item.title = existingHistoryItem.title
if !item.fromMaccy {
item.application = existingHistoryItem.application
}
logger.info("Removing duplicate item '\(item.title)'")
Storage.shared.context.delete(existingHistoryItem)
removedItemIndex = all.firstIndex(where: { $0.item == existingHistoryItem })
if let removedItemIndex {
all.remove(at: removedItemIndex)
}
} else {
Task {
Notifier.notify(body: item.title, sound: .write)
}
}
// Remove exceeding items. Do this after the item is added to avoid removing something
// if a duplicate was found as then the size already stayed the same.
limitHistorySize(to: Defaults[.size] - 1)
sessionLog[Clipboard.shared.changeCount] = item
var itemDecorator: HistoryItemDecorator
if let pin = item.pin {
itemDecorator = HistoryItemDecorator(item, shortcuts: KeyShortcut.create(character: pin))
// Keep pins in the same place.
if let removedItemIndex {
all.insert(itemDecorator, at: removedItemIndex)
}
} else {
itemDecorator = HistoryItemDecorator(item)
let sortedItems = sorter.sort(all.map(\.item) + [item])
if let index = sortedItems.firstIndex(of: item) {
all.insert(itemDecorator, at: index)
}
items = all
updateUnpinnedShortcuts()
AppState.shared.popup.needsResize = true
}
return itemDecorator
}
@MainActor
private func withLogging(_ msg: String, _ block: () throws -> Void) rethrows {
func dataCounts() -> String {
let historyItemCount = try? Storage.shared.context.fetchCount(FetchDescriptor<HistoryItem>())
let historyContentCount = try? Storage.shared.context.fetchCount(FetchDescriptor<HistoryItemContent>())
return "HistoryItem=\(historyItemCount ?? 0) HistoryItemContent=\(historyContentCount ?? 0)"
}
logger.info("\(msg) Before: \(dataCounts())")
try? block()
logger.info("\(msg) After: \(dataCounts())")
}
@MainActor
func clear() {
withLogging("Clearing history") {
all.forEach { item in
if item.isUnpinned {
cleanup(item)
}
}
all.removeAll(where: \.isUnpinned)
sessionLog.removeValues { $0.pin == nil }
items = all
try? Storage.shared.context.transaction {
try? Storage.shared.context.delete(
model: HistoryItem.self,
where: #Predicate { $0.pin == nil }
)
try? Storage.shared.context.delete(
model: HistoryItemContent.self,
where: #Predicate { $0.item?.pin == nil }
)
}
Storage.shared.context.processPendingChanges()
try? Storage.shared.context.save()
}
Clipboard.shared.clear()
AppState.shared.popup.close()
Task {
AppState.shared.popup.needsResize = true
}
}
@MainActor
func clearAll() {
withLogging("Clearing all history") {
all.forEach { item in
cleanup(item)
}
all.removeAll()
sessionLog.removeAll()
items = all
try? Storage.shared.context.delete(model: HistoryItem.self)
Storage.shared.context.processPendingChanges()
try? Storage.shared.context.save()
}
Clipboard.shared.clear()
AppState.shared.popup.close()
Task {
AppState.shared.popup.needsResize = true
}
}
@MainActor
func delete(_ item: HistoryItemDecorator?) {
guard let item else { return }
cleanup(item)
withLogging("Removing history item") {
Storage.shared.context.delete(item.item)
Storage.shared.context.processPendingChanges()
try? Storage.shared.context.save()
}
all.removeAll { $0 == item }
items.removeAll { $0 == item }
sessionLog.removeValues { $0 == item.item }
updateUnpinnedShortcuts()
Task {
AppState.shared.popup.needsResize = true
}
}
@MainActor
private func cleanup(_ item: HistoryItemDecorator) {
item.cleanupImages()
}
private func currentModifierFlags() -> NSEvent.ModifierFlags {
return NSApp.currentEvent?.modifierFlags
.intersection(.deviceIndependentFlagsMask)
.subtracting([.capsLock, .numericPad, .function]) ?? []
}
@MainActor
func select(_ item: HistoryItemDecorator?) {
guard let item else {
return
}
let modifierFlags = currentModifierFlags()
if modifierFlags.isEmpty {
AppState.shared.popup.close()
Clipboard.shared.copy(item.item, removeFormatting: Defaults[.removeFormattingByDefault])
if Defaults[.pasteByDefault] {
Clipboard.shared.paste()
}
} else {
switch HistoryItemAction(modifierFlags) {
case .copy:
AppState.shared.popup.close()
Clipboard.shared.copy(item.item)
case .paste:
AppState.shared.popup.close()
Clipboard.shared.copy(item.item)
Clipboard.shared.paste()
case .pasteWithoutFormatting:
AppState.shared.popup.close()
Clipboard.shared.copy(item.item, removeFormatting: true)
Clipboard.shared.paste()
case .unknown:
return
}
}
Task {
searchQuery = ""
}
}
@MainActor
func startPasteStack(selection: inout Selection<HistoryItemDecorator>) {
guard AppState.shared.multiSelectionEnabled else { return }
guard let item = selection.first else { return }
PasteStack.initializeIfNeeded()
let modifierFlags = currentModifierFlags()
let stack = PasteStack(items: selection.items, modifierFlags: modifierFlags)
pasteStack = stack
logger.info("Initialising PasteStack with \(stack.items.count) items")
logger.info("Copying \(item.item.title) from PasteStack")
if modifierFlags.isEmpty {
AppState.shared.popup.close()
Clipboard.shared.copy(item.item, removeFormatting: Defaults[.removeFormattingByDefault])
} else {
switch HistoryItemAction(modifierFlags) {
case .copy:
AppState.shared.popup.close()
Clipboard.shared.copy(item.item)
case .paste:
AppState.shared.popup.close()
Clipboard.shared.copy(item.item)
case .pasteWithoutFormatting:
AppState.shared.popup.close()
Clipboard.shared.copy(item.item, removeFormatting: true)
Clipboard.shared.paste()
case .unknown:
return
}
}
Task {
searchQuery = ""
}
}
func handlePasteStack() {
guard let stack = pasteStack else {
return
}
guard let pasted = stack.items.first else {
pasteStack = nil
logger.info("PasteStack is empty")
return
}
logger.info("PasteStack pasted \(pasted.item.title)")
stack.items.removeFirst()
guard let item = stack.items.first else {
pasteStack = nil
logger.info("PasteStack is empty")
return
}
logger.info("Copying \(item.item.title) from PasteStack. \(stack.items.count) items remaining in stack.")
Task {
if stack.modifierFlags.isEmpty {
await Clipboard.shared.copy(item.item, removeFormatting: Defaults[.removeFormattingByDefault])
} else {
switch HistoryItemAction(stack.modifierFlags) {
case .copy:
await Clipboard.shared.copy(item.item)
case .paste:
await Clipboard.shared.copy(item.item)
case .pasteWithoutFormatting:
await Clipboard.shared.copy(item.item, removeFormatting: true)
case .unknown:
return
}
}
}
}
func interruptPasteStack() {
guard pasteStack != nil else {
return
}
logger.info("Interrupting PasteStack")
pasteStack = nil
}
@MainActor
func togglePin(_ item: HistoryItemDecorator?) {
guard let item else { return }
item.togglePin()
let sortedItems = sorter.sort(all.map(\.item))
if let currentIndex = all.firstIndex(of: item),
let newIndex = sortedItems.firstIndex(of: item.item) {
all.remove(at: currentIndex)
all.insert(item, at: newIndex)
}
items = all
searchQuery = ""
updateUnpinnedShortcuts()
if item.isUnpinned {
AppState.shared.navigator.scrollTarget = item.id
}
}
@MainActor
private func findSimilarItem(_ item: HistoryItem) -> HistoryItem? {
let descriptor = FetchDescriptor<HistoryItem>()
if let all = try? Storage.shared.context.fetch(descriptor) {
let duplicates = all.filter({ $0 == item || $0.supersedes(item) })
if duplicates.count > 1 {
return duplicates.first(where: { $0 != item })
} else {
return isModified(item)
}
}
return item
}
private func isModified(_ item: HistoryItem) -> HistoryItem? {
if let modified = item.modified, sessionLog.keys.contains(modified) {
return sessionLog[modified]
}
return nil
}
private func updateItems(_ newItems: [Search.SearchResult]) {
items = newItems.map { result in
let item = result.object
item.highlight(searchQuery, result.ranges)
return item
}
updateUnpinnedShortcuts()
}
private func updateShortcuts() {
for item in pinnedItems {
if let pin = item.item.pin {
item.shortcuts = KeyShortcut.create(character: pin)
}
}
updateUnpinnedShortcuts()
}
@MainActor
private func updateTitle(item: HistoryItemDecorator, title: String) {
item.title = title
item.item.title = title
}
private func updateUnpinnedShortcuts() {
let visibleUnpinnedItems = unpinnedItems.filter(\.isVisible)
for item in visibleUnpinnedItems {
item.shortcuts = []
}
var index = 1
for item in visibleUnpinnedItems.prefix(9) {
item.shortcuts = KeyShortcut.create(character: String(index))
index += 1
}
}
}
================================================
FILE: Maccy/Observables/HistoryItemDecorator.swift
================================================
import AppKit.NSWorkspace
import Defaults
import Foundation
import Observation
import Sauce
@Observable
class HistoryItemDecorator: Identifiable, Hashable, HasVisibility {
static func == (lhs: HistoryItemDecorator, rhs: HistoryItemDecorator) -> Bool {
return lhs.id == rhs.id
}
static var previewImageSize: NSSize { NSScreen.forPopup?.visibleFrame.size ?? NSSize(width: 2048, height: 1536) }
static var thumbnailImageSize: NSSize { NSSize(width: 340, height: Defaults[.imageMaxHeight]) }
let id = UUID()
var title: String = ""
var attributedTitle: AttributedString?
var isVisible: Bool = true
var selectionIndex: Int = -1
var isSelected: Bool {
return selectionIndex != -1
}
var shortcuts: [KeyShortcut] = []
var application: String? {
if item.universalClipboard {
return "iCloud"
}
guard let bundle = item.application,
let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundle)
else {
return nil
}
return url.deletingPathExtension().lastPathComponent
}
var hasImage: Bool { item.image != nil }
var previewImageGenerationTask: Task<(), Error>?
var thumbnailImageGenerationTask: Task<(), Error>?
var previewImage: NSImage?
var thumbnailImage: NSImage?
var applicationImage: ApplicationImage
// 10k characters seems to be more than enough on large displays
var text: String { item.previewableText.shortened(to: 10_000) }
var isPinned: Bool { item.pin != nil }
var isUnpinned: Bool { item.pin == nil }
func hash(into hasher: inout Hasher) {
// We need to hash title and attributedTitle, so SwiftUI knows it needs to update the view if they chage
hasher.combine(id)
hasher.combine(title)
hasher.combine(attributedTitle)
}
private(set) var item: HistoryItem
init(_ item: HistoryItem, shortcuts: [KeyShortcut] = []) {
self.item = item
self.shortcuts = shortcuts
self.title = item.title
self.applicationImage = ApplicationImageCache.shared.getImage(item: item)
synchronizeItemPin()
synchronizeItemTitle()
}
@MainActor
func ensureThumbnailImage() {
guard item.image != nil else {
return
}
guard thumbnailImage == nil else {
return
}
guard thumbnailImageGenerationTask == nil else {
return
}
thumbnailImageGenerationTask = Task { [weak self] in
self?.generateThumbnailImage()
}
}
@MainActor
func ensurePreviewImage() {
guard item.image != nil else {
return
}
guard previewImage == nil else {
return
}
guard previewImageGenerationTask == nil else {
return
}
previewImageGenerationTask = Task { [weak self] in
self?.generatePreviewImage()
}
}
@MainActor
func asyncGetPreviewImage() async -> NSImage? {
if let image = previewImage {
return image
}
ensurePreviewImage()
_ = await previewImageGenerationTask?.result
return previewImage
}
@MainActor
func cleanupImages() {
thumbnailImageGenerationTask?.cancel()
previewImageGenerationTask?.cancel()
thumbnailImage?.recache()
previewImage?.recache()
thumbnailImage = nil
previewImage = nil
}
@MainActor
private func generateThumbnailImage() {
guard let image = item.image else {
return
}
thumbnailImage = image.resized(to: HistoryItemDecorator.thumbnailImageSize)
}
@MainActor
private func generatePreviewImage() {
guard let image = item.image else {
return
}
previewImage = image.resized(to: HistoryItemDecorator.previewImageSize)
}
@MainActor
func sizeImages() {
generatePreviewImage()
generateThumbnailImage()
}
func highlight(_ query: String, _ ranges: [Range<String.Index>]) {
guard !query.isEmpty, !title.isEmpty else {
attributedTitle = nil
return
}
var attributedString = AttributedString(title.shortened(to: 500))
for range in ranges {
if let lowerBound = AttributedString.Index(range.lowerBound, within: attributedString),
let upperBound = AttributedString.Index(range.upperBound, within: attributedString) {
switch Defaults[.highlightMatch] {
case .bold:
attributedString[lowerBound..<upperBound].font = .bold(.body)()
case .italic:
attributedString[lowerBound..<upperBound].font = .italic(.body)()
case .underline:
attributedString[lowerBound..<upperBound].underlineStyle = .single
default:
attributedString[lowerBound..<upperBound].backgroundColor = .findHighlightColor
attributedString[lowerBound..<upperBound].foregroundColor = .black
}
}
}
attributedTitle = attributedString
}
@MainActor
func togglePin() {
if item.pin != nil {
item.pin = nil
} else {
let pin = HistoryItem.randomAvailablePin
item.pin = pin
}
}
private func synchronizeItemPin() {
_ = withObservationTracking {
item.pin
} onChange: {
DispatchQueue.main.async {
if let pin = self.item.pin {
self.shortcuts = KeyShortcut.create(character: pin)
}
self.synchronizeItemPin()
}
}
}
private func synchronizeItemTitle() {
_ = withObservationTracking {
item.title
} onChange: {
DispatchQueue.main.async {
self.title = self.item.title
self.synchronizeItemTitle()
}
}
}
}
================================================
FILE: Maccy/Observables/ModifierFlags.swift
================================================
import AppKit.NSEvent
import Defaults
@Observable
class ModifierFlags {
var flags: NSEvent.ModifierFlags = []
init() {
NSEvent.addLocalMonitorForEvents(matching: .flagsChanged) { event in
self.flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
return event
}
}
}
================================================
FILE: Maccy/Observables/NavigationManager.swift
================================================
import Foundation
import SwiftUI
@Observable
class NavigationManager { // swiftlint:disable:this type_body_length
private var history: History
private var footer: Footer
init(history: History, footer: Footer) {
self.history = history
self.footer = footer
}
var selection: Selection<HistoryItemDecorator> = Selection() {
willSet {
selection.forEach { _, item in item.selectionIndex = -1 }
newValue.forEach { index, item in item.selectionIndex = index }
}
}
var scrollTarget: UUID?
var leadSelection: UUID? {
if let item = leadHistoryItem {
return item.id
}
if let footerItem = footer.selectedItem {
return footerItem.id
}
return history.pasteStack?.id
}
private(set) var leadHistoryItem: HistoryItemDecorator? {
didSet {
guard oldValue?.id != leadHistoryItem?.id else { return }
let preview = AppState.shared.preview
if leadHistoryItem != nil {
preview.resetAutoOpenSuppression()
preview.startAutoOpen()
} else {
preview.cancelAutoOpen()
}
}
}
var pasteStackSelected: Bool {
return leadSelection != nil && leadSelection == history.pasteStack?.id
}
var isManualMultiSelect: Bool = false
var isMultiSelectInProgress: Bool {
return isManualMultiSelect || selection.count > 1
}
var hoverSelectionWhileKeyboardNavigating: UUID?
var isKeyboardNavigating: Bool = true {
didSet {
if !isKeyboardNavigating && !isMultiSelectInProgress,
let hoverSelection = hoverSelectionWhileKeyboardNavigating {
hoverSelectionWhileKeyboardNavigating = nil
select(id: hoverSelection)
}
}
}
private func scroll(to id: UUID?, item: HistoryItemDecorator? = nil) {
scrollTarget = id
}
func select(id: UUID) {
if let item = history.items.first(where: { $0.id == id }) {
select(item: item, footerItem: nil)
} else if let item = footer.items.first(where: { $0.id == id }) {
select(item: nil, footerItem: item)
} else {
select(item: nil, footerItem: nil)
}
}
func select(item: HistoryItemDecorator? = nil, footerItem: FooterItem? = nil) {
withTransaction(Transaction()) {
selectWithoutScrolling(item: item, footerItem: footerItem)
scroll(to: item?.id, item: item)
}
}
func addToSelection(item: HistoryItemDecorator) {
var newSelectionState = selection
if item.isSelected {
if newSelectionState.count <= 1 {
isManualMultiSelect = !isManualMultiSelect
} else {
newSelectionState.remove(item)
}
} else {
newSelectionState.add(item)
}
withTransaction(Transaction()) {
selection = newSelectionState
leadHistoryItem = item
scrollTarget = leadSelection
}
}
func extendSelection(
from fromItem: HistoryItemDecorator,
to toItem: HistoryItemDecorator,
isRange: Bool
) {
var newSelectionState = selection
if isRange {
if let itemRange = history.visibleItems.between(
from: fromItem,
to: toItem,
inOrder: false
) {
newSelectionState = Selection(items: itemRange)
}
} else {
if toItem.isSelected {
newSelectionState.remove(fromItem)
} else {
newSelectionState.add(toItem)
}
}
withTransaction(Transaction()) {
selection = newSelectionState
leadHistoryItem = toItem
scrollTarget = leadSelection
}
}
func selectWithoutScrolling(id: UUID) {
if let stack = history.pasteStack,
stack.id == id {
selectWithoutScrolling(item: nil, footerItem: nil)
} else if let item = history.items.first(where: { $0.id == id }) {
if !isMultiSelectInProgress {
selectWithoutScrolling(item: item, footerItem: nil)
}
} else if let item = footer.items.first(where: { $0.id == id }) {
selectWithoutScrolling(item: nil, footerItem: item)
} else {
selectWithoutScrolling(item: nil, footerItem: nil)
}
}
func selectWithoutScrolling(
item: HistoryItemDecorator? = nil,
footerItem: FooterItem? = nil
) {
if let item = item {
selectInHistory(item)
} else if let footerItem = footerItem {
selectInFooter(footerItem)
} else {
leadHistoryItem = nil
selection = .init()
footer.selectedItem = nil
}
}
private func selectInHistory(_ item: HistoryItemDecorator) {
leadHistoryItem = item
selection = .init(items: [item])
footer.selectedItem = nil
}
private func selectInFooter(_ item: FooterItem) {
leadHistoryItem = nil
if !isMultiSelectInProgress {
selection = .init()
}
footer.selectedItem = item
}
private func selectFromKeyboardNavigation(
item: HistoryItemDecorator? = nil,
footerItem: FooterItem? = nil
) {
isKeyboardNavigating = true
isManualMultiSelect = false
select(item: item, footerItem: footerItem)
}
private func extendHistorySelectionFromKeyboardNavigation(
from fromItem: HistoryItemDecorator,
to toItem: HistoryItemDecorator,
isRange: Bool
) {
isKeyboardNavigating = true
extendSelection(from: fromItem, to: toItem, isRange: isRange)
}
func highlightFirst() {
if let item = history.firstVisibleItem {
selectFromKeyboardNavigation(item: item)
} else {
selectFromKeyboardNavigation(item: nil)
}
}
func highlightPrevious() {
guard let lead = leadSelection else { return }
if let historyItem = history.firstVisibleItem(where: { $0.id == lead }) {
if let nextItem = history.visibleItem(before: historyItem) {
selectFromKeyboardNavigation(item: nextItem)
} else if history.pasteStack != nil {
selectWithoutScrolling(item: nil)
} else {
highlightFirst()
}
} else if let footerItem = footer.firstVisibleItem(where: { $0.id == lead }) {
if let nextItem = footer.visibleItem(before: footerItem) {
selectFromKeyboardNavigation(footerItem: nextItem)
} else if let nextItem = history.lastVisibleItem {
selectFromKeyboardNavigation(item: nextItem)
}
}
}
func highlightNext(allowCycle: Bool = false) {
guard let lead = leadSelection else { return }
if leadSelection == history.pasteStack?.id {
highlightFirst()
return
}
if let historyItem = history.firstVisibleItem(where: { $0.id == lead }) {
if let nextItem = history.visibleItem(after: historyItem) {
selectFromKeyboardNavigation(item: nextItem)
} else if let nextItem = footer.firstVisibleItem {
selectFromKeyboardNavigation(footerItem: nextItem)
} else if allowCycle {
highlightFirst()
}
} else if let footerItem = footer.firstVisibleItem(where: { $0.id == lead }) {
if let nextItem = footer.visibleItem(after: footerItem) {
selectFromKeyboardNavigation(footerItem: nextItem)
} else if let nextItem = footer.firstVisibleItem {
selectFromKeyboardNavigation(footerItem: nextItem)
} else if allowCycle {
// End of footer; cycle to the beginning
highlightFirst()
}
}
}
func highlightLast() {
guard let lead = leadSelection else { return }
if let historyItem = history.firstVisibleItem(where: { $0.id == lead }) {
if historyItem == history.lastVisibleItem,
let nextItem = footer.firstVisibleItem {
selectFromKeyboardNavigation(footerItem: nextItem)
} else {
selectFromKeyboardNavigation(item: history.lastVisibleItem)
}
} else if footer.selectedItem != nil {
selectFromKeyboardNavigation(footerItem: footer.lastVisibleItem)
} else {
selectFromKeyboardNavigation(footerItem: footer.firstVisibleItem)
}
}
func extendHighlightToNext() {
if let leadSelection,
let leadItem = history.firstVisibleItem(where: {$0.id == leadSelection}) {
guard let nextItem = history.visibleItem(after: leadItem) else { return }
extendHistorySelectionFromKeyboardNavigation(from: leadItem, to: nextItem, isRange: false)
} else {
highlightNext()
}
}
func extendHighlightToPrevious() {
if let leadSelection,
let leadItem = history.firstVisibleItem(where: {$0.id == leadSelection}) {
guard let nextItem = history.visibleItem(before: leadItem) else { return }
extendHistorySelectionFromKeyboardNavigation(from: leadItem, to: nextItem, isRange: false)
} else {
highlightPrevious()
}
}
func extendHighlightToFirst() {
if let leadSelection,
let leadItem = history.firstVisibleItem(where: {$0.id == leadSelection}) {
guard let nextItem = history.firstVisibleItem else { return }
extendHistorySelectionFromKeyboardNavigation(from: leadItem, to: nextItem, isRange: true)
} else {
highlightFirst()
}
}
func extendHighlightToLast() {
if let leadSelection,
let leadItem = history.firstVisibleItem(where: {$0.id == leadSelection}) {
guard let nextItem = history.lastVisibleItem else { return }
extendHistorySelectionFromKeyboardNavigation(from: leadItem, to: nextItem, isRange: true)
} else {
highlightFirst()
}
}
}
================================================
FILE: Maccy/Observables/Popup.swift
================================================
import AppKit.NSRunningApplication
import Defaults
import KeyboardShortcuts
import Observation
enum PopupState {
// Default; shortcut will toggle the popup
case toggle
// In this mode, every additional press of the main key
// will cycle to the next item in the paste history list.
// Releasing the modifier keys will accept selection and close the popup
case cycle
// Transition state when the shortcut is first pressed and
// we don't know whether we are in "toggle" or "cycle" mode.
case opening
}
@Observable
class Popup {
static let verticalSeparatorPadding = 6.0
static let horizontalSeparatorPadding = 6.0
static let verticalPadding: CGFloat = 5
static let horizontalPadding: CGFloat = 5
static let minimumPreviewHeight: CGFloat = 150
// Radius used for items inset by the padding. Ensures they visually have the same curvature
// as the menu.
static let cornerRadius: CGFloat = if #available(macOS 26.0, *) {
7
} else {
4
}
static let itemHeight: CGFloat = if #available(macOS 26.0, *) {
24
} else {
22
}
var needsResize = false
var height: CGFloat = 0
var headerHeight: CGFloat = 0
var extraTopHeight: CGFloat = 0
var extraBottomHeight: CGFloat = 0
var footerHeight: CGFloat = 0
private var eventsMonitor: Any?
private var state: PopupState = .toggle
init() {
KeyboardShortcuts.onKeyDown(for: .popup, action: handleFirstKeyDown)
initEventsMonitor()
}
deinit {
deinitEventsMonitor()
}
func initEventsMonitor() {
guard eventsMonitor == nil else { return }
self.eventsMonitor = NSEvent.addLocalMonitorForEvents(
matching: [.flagsChanged, .keyDown],
handler: handleEvent
)
}
func deinitEventsMonitor() {
guard let eventsMonitor else { return }
NSEvent.removeMonitor(eventsMonitor)
}
func open(height: CGFloat, at popupPosition: PopupPosition = Defaults[.popupPosition]) {
AppState.shared.appDelegate?.panel.open(height: height, at: popupPosition)
}
func reset() {
state = .toggle
KeyboardShortcuts.enable(.popup)
}
func close() {
AppState.shared.appDelegate?.panel.close() // close() calls reset
}
func isClosed() -> Bool {
AppState.shared.appDelegate?.panel.isPresented != true
}
func preferredHeight(for newHeight: CGFloat) -> CGFloat {
var height = newHeight
var minimumHeight = 0.0
// If the preview is non-empty make sure the window accomodates for it to be visible.
if AppState.shared.preview.state.isOpen && AppState.shared.navigator.leadSelection != nil {
minimumHeight += Self.minimumPreviewHeight
}
minimumHeight = max(headerHeight + Self.verticalPadding, minimumHeight)
height = max(height, minimumHeight)
height = min(height, Defaults[.windowSize].height)
return height
}
func resize(height: CGFloat) {
self.height = height + headerHeight + extraTopHeight + extraBottomHeight + footerHeight
AppState.shared.appDelegate?.panel.verticallyResize(to: preferredHeight(for: self.height))
needsResize = false
}
private func handleFirstKeyDown() {
if isClosed() {
open(height: height)
state = .opening
KeyboardShortcuts.disable(.popup) // Handle events via eventsMonitor. Re-enable on popup close
return
}
// Maccy was not opened via shortcut. We assume toggle mode and close it
close()
}
private func handleEvent(_ event: NSEvent) -> NSEvent? {
switch event.type {
case .keyDown:
return handleKeyDown(event)
case .flagsChanged:
return handleFlagsChanged(event)
default:
return event
}
}
private func handleKeyDown(_ event: NSEvent) -> NSEvent? {
if isHotKeyCode(Int(event.keyCode)) {
if let item = History.shared.pressedShortcutItem {
AppState.shared.navigator.select(item: item)
Task { @MainActor in
AppState.shared.history.select(item)
}
return nil
}
if state == .opening {
state = .cycle
// Next 'if' will highlight next item and then return nil
}
if state == .cycle {
AppState.shared.navigator.highlightNext(allowCycle: true)
return nil
}
if state == .toggle && isHotKeyModifiers(event.modifierFlags) {
close()
return nil
}
}
return event
}
private func handleFlagsChanged(_ event: NSEvent) -> NSEvent? {
// If we are in cycle mode, releasing modifiers triggers a selection
if state == .cycle && allModifiersReleased(event) {
DispatchQueue.main.async {
AppState.shared.select()
}
return nil
}
// Otherwise if in opening mode, enter toggle mode
if state == .opening && allModifiersReleased(event) {
state = .toggle
return event
}
return event
}
private func isHotKeyCode(_ keyCode: Int) -> Bool {
guard let shortcut = KeyboardShortcuts.Name.popup.shortcut else {
return false
}
return shortcut.key?.rawValue == keyCode
}
private func isHotKeyModifiers(_ modifiers: NSEvent.ModifierFlags) -> Bool {
guard let shortcut = KeyboardShortcuts.Name.popup.shortcut else {
return false
}
return modifiers.intersection(.deviceIndependentFlagsMask) ==
shortcut.modifiers.intersection(.deviceIndependentFlagsMask)
}
private func allModifiersReleased(_ event: NSEvent) -> Bool {
return event.modifierFlags.isDisjoint(with: .deviceIndependentFlagsMask)
}
}
================================================
FILE: Maccy/Observables/SlideoutController.swift
================================================
import Defaults
import Logging
import Observation
import SwiftUI
enum SlideoutState {
case opening
case closing
case open
case closed
var isAnimating: Bool {
switch self {
case .closed, .open:
return false
case .opening, .closing:
return true
}
}
var isOpen: Bool {
switch self {
case .open, .opening:
return true
case .closed, .closing:
return false
}
}
fileprivate func toggleWithAnimation() -> SlideoutState {
switch self {
case .open, .opening:
return .closing
case .closed, .closing:
return .opening
}
}
func animationDone() -> SlideoutState {
switch self {
case .open, .opening:
return .open
case .closed, .closing:
return .closed
}
}
}
enum SlideoutPlacement {
case left
case right
}
enum SlideoutToggleTrigger {
case autoOpen
case manual
}
enum ResizingMode {
case none
case content
case slideout
}
@Observable
class SlideoutController {
let logger = Logger(label: "org.p0deje.Maccy")
private static let animationDuration = 0.25
let onContentResize: (CGFloat) -> Void
let onSlideoutResize: (CGFloat) -> Void
let minimumContentWidth: CGFloat = 200
var contentResizeWidth: CGFloat = 0
var contentAnimationWidth: CGFloat?
let minimumSlideoutWidth: CGFloat = 200
var slideoutResizeWidth: CGFloat = 0
private var _contentWidth: CGFloat = 0
var contentWidth: CGFloat {
get { return _contentWidth }
set {
_contentWidth = max(minimumContentWidth, newValue).rounded()
onContentResize(_contentWidth)
}
}
private var _slideoutWidth: CGFloat = 400
var slideoutWidth: CGFloat {
get { return _slideoutWidth }
set {
_slideoutWidth = max(minimumSlideoutWidth, newValue).rounded()
onSlideoutResize(_slideoutWidth)
}
}
var placement: SlideoutPlacement = .right
var state: SlideoutState = .closed
var resizingMode: ResizingMode = .none
var nswindow: NSWindow? {
return AppState.shared.appDelegate?.panel
}
private var windowAnimationOrigin: CGPoint?
private var windowAnimationOriginBaseState: SlideoutState = .closed
private var autoOpenTask: Task<Void, Never>?
private var autoOpenSuppressed = false
private var autoOpenEnabled = true
init(onContentResize: @escaping (CGFloat) -> Void, onSlideoutResize: @escaping (CGFloat) -> Void) {
self.onContentResize = onContentResize
self.onSlideoutResize = onSlideoutResize
}
private func togglePreviewStateWithAnimation(windowFrame: NSRect) {
let newValue = state.toggleWithAnimation()
if !state.isAnimating && newValue.isAnimating {
contentAnimationWidth = contentWidth
windowAnimationOrigin = windowFrame.origin
windowAnimationOriginBaseState = state
}
state = newValue
}
func computePlacement(window: NSWindow, for size: NSSize) -> SlideoutPlacement {
guard let screen = window.screen?.frame else { return placement }
let windowFrame = window.frame
if windowFrame.minX + size.width > screen.maxX {
return .left
} else {
return .right
}
}
func computeSizeWithPreview(_ size: NSSize, state newState: SlideoutState) -> NSSize {
var newSize = size
if newState.isOpen {
newSize.width += slideoutWidth
}
let popup = AppState.shared.popup
newSize.height = popup.preferredHeight(for: popup.height)
return newSize
}
func togglePreview(trigger: SlideoutToggleTrigger = .manual) {
if !state.isOpen {
let navigator = AppState.shared.navigator
guard navigator.leadHistoryItem != nil || navigator.pasteStackSelected else { return }
}
if trigger == .manual {
if state.isOpen {
autoOpenSuppressed = true
} else {
autoOpenSuppressed = false
}
}
cancelAutoOpen()
withAnimation(.easeInOut(duration: Self.animationDuration), completionCriteria: .removed) {
if let window = nswindow {
togglePreviewStateWithAnimation(windowFrame: window.frame)
var newSize = window.frame.size
newSize.width = contentWidth
newSize = computeSizeWithPreview(newSize, state: self.state)
if state.isOpen {
placement = computePlacement(window: window, for: newSize)
}
let expectedAnimationState = state
NSAnimationContext.runAnimationGroup { (context) in
var newOrigin = windowAnimationOrigin ?? window.frame.origin
newOrigin.y += (window.frame.height - newSize.height)
if placement == .left {
if windowAnimationOriginBaseState == .closed && state.isOpen {
newOrigin.x -= slideoutWidth
} else if windowAnimationOriginBaseState == .open
&& !state.isOpen {
newOrigin.x += slideoutWidth
}
// Otherwise the base is the desired position
}
context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
context.completionHandler = {
if self.state == expectedAnimationState {
self.state = expectedAnimationState.animationDone()
}
}
context.duration = Self.animationDuration
window.animator().setFrame(
NSRect(origin: newOrigin, size: newSize),
display: true
)
}
}
} completion: {
}
}
func startResize(mode: ResizingMode) {
logger.info("Starting resize with mode \(mode)")
resizingMode = mode
contentWidth = contentResizeWidth
slideoutWidth = slideoutResizeWidth
}
func endResize() {
logger.info("Ended resize. Mode was \(resizingMode)")
switch resizingMode {
case .none:
return
case .content:
contentWidth = contentResizeWidth
case .slideout:
slideoutWidth = slideoutResizeWidth
}
resizingMode = .none
}
func startAutoOpen() {
cancelAutoOpen()
guard autoOpenEnabled else { return }
guard !autoOpenSuppressed else { return }
guard !state.isOpen else { return }
autoOpenTask = Task { @MainActor in
try? await Task.sleep(for: .milliseconds(Defaults[.previewDelay]))
guard !Task.isCancelled else { return }
if !state.isOpen {
togglePreview(trigger: .autoOpen)
}
}
}
func cancelAutoOpen() {
autoOpenTask?.cancel()
autoOpenTask = nil
}
func enableAutoOpen() {
autoOpenEnabled = true
}
func disableAutoOpen() {
autoOpenEnabled = false
cancelAutoOpen()
}
func resetAutoOpenSuppression() {
autoOpenSuppressed = false
}
}
================================================
FILE: Maccy/PasteStack.swift
================================================
import Foundation
import AppKit
@Observable
class PasteStack: Identifiable, Hashable {
private static var listener: Any?
static func initializeIfNeeded() {
guard listener == nil else { return }
Accessibility.check()
var pasteDown: Bool = false
listener = NSEvent.addGlobalMonitorForEvents(matching: [.keyUp, .keyDown]) { event in
switch event.type {
case .keyDown:
if event.keyCode == KeyChord.pasteKey.QWERTYKeyCode
&& event.modifierFlags.intersection(.deviceIndependentFlagsMask) == [.command] {
pasteDown = true
}
case .keyUp:
if pasteDown && event.keyCode == KeyChord.pasteKey.QWERTYKeyCode {
pasteDown = false
AppState.shared.history.handlePasteStack()
}
default:
break
}
}
}
var id: UUID = UUID()
var items: [HistoryItemDecorator] = []
var modifierFlags: NSEvent.ModifierFlags
init(items: [HistoryItemDecorator], modifierFlags: NSEvent.ModifierFlags) {
self.items = items
self.modifierFlags = modifierFlags
}
static func == (lhs: PasteStack, rhs: PasteStack) -> Bool {
return lhs.id == rhs.id
&& lhs.items == rhs.items
&& lhs.modifierFlags.rawValue == rhs.modifierFlags.rawValue
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(items)
hasher.combine(modifierFlags.rawValue)
}
}
================================================
FILE: Maccy/PinsPosition.swift
================================================
import Foundation
import Defaults
enum PinsPosition: String, CaseIterable, Identifiable, CustomStringConvertible, Defaults.Serializable {
case top
case bottom
var id: Self { self }
var description: String {
switch self {
case .top:
return NSLocalizedString("PinToTop", tableName: "AppearanceSettings", comment: "")
case .bottom:
return NSLocalizedString("PinToBottom", tableName: "AppearanceSettings", comment: "")
}
}
}
================================================
FILE: Maccy/PopupPosition.swift
================================================
import AppKit.NSEvent
import Defaults
import Foundation
enum PopupPosition: String, CaseIterable, Identifiable, CustomStringConvertible, Defaults.Serializable {
case cursor
case statusItem
case window
case center
case lastPosition
var id: Self { self }
var description: String {
switch self {
case .cursor:
return NSLocalizedString("PopupAtCursor", tableName: "AppearanceSettings", comment: "")
case .statusItem:
return NSLocalizedString("PopupAtMenuBarIcon", tableName: "AppearanceSettings", comment: "")
case .window:
return NSLocalizedString("PopupAtWindowCenter", tableName: "AppearanceSettings", comment: "")
case .center:
return NSLocalizedString("PopupAtScreenCenter", tableName: "AppearanceSettings", comment: "")
case .lastPosition:
return NSLocalizedString("PopupAtLastPosition", tableName: "AppearanceSettings", comment: "")
}
}
// swiftlint:disable:next cyclomatic_complexity
func origin(size: NSSize, statusBarButton: NSStatusBarButton?) -> NSPoint {
switch self {
case .center:
if let frame = NSScreen.forPopup?.visibleFrame {
return NSRect.centered(ofSize: size, in: frame).origin
}
case .window:
if let frame = NSWorkspace.shared.frontmostApplication?.windowFrame {
return NSRect.centered(ofSize: size, in: frame).origin
}
case .statusItem:
if let statusBarButton, let screen = NSScreen.main {
let rectInWindow = statusBarButton.convert(statusBarButton.bounds, to: nil)
if let screenRect = statusBarButton.window?.convertToScreen(rectInWindow) {
var topLeftPoint = NSPoint(x: screenRect.minX, y: screenRect.minY - size.height)
// Ensure that window doesn't spill over to the right screen.
if (topLeftPoint.x + size.width) > screen.frame.maxX {
topLeftPoint.x = screen.frame.maxX - size.width
}
return topLeftPoint
}
}
case .lastPosition:
if let frame = NSScreen.forPopup?.visibleFrame {
let relativePos = Defaults[.windowPosition]
let anchorX = frame.minX + frame.width * relativePos.x
let anchorY = frame.minY + frame.height * relativePos.y
// Anchor is top middle of frame
return NSPoint(x: anchorX - size.width / 2, y: anchorY - size.height)
}
default:
break
}
var point = NSEvent.mouseLocation
point.y -= size.height
return point
}
}
================================================
FILE: Maccy/Search.swift
================================================
import AppKit
import Defaults
import Fuse
class Search {
enum Mode: String, CaseIterable, Identifiable, CustomStringConvertible, Defaults.Serializable {
case exact
case fuzzy
case regexp
case mixed
var id: Self { self }
var description: String {
switch self {
case .exact:
return NSLocalizedString("Exact", tableName: "GeneralSettings", comment: "")
case .fuzzy:
return NSLocalizedString("Fuzzy", tableName: "GeneralSettings", comment: "")
case .regexp:
return NSLocalizedString("Regex", tableName: "GeneralSettings", comment: "")
case .mixed:
return NSLocalizedString("Mixed", tableName: "GeneralSettings", comment: "")
}
}
}
struct SearchResult: Equatable {
var score: Double?
var object: Searchable
var ranges: [Range<String.Index>] = []
}
typealias Searchable = HistoryItemDecorator
private let fuse = Fuse(threshold: 0.7) // threshold found by trial-and-error
private let fuzzySearchLimit = 5_000
func search(string: String, within: [Searchable]) -> [SearchResult] {
guard !string.isEmpty else {
return within.map { SearchResult(object: $0) }
}
switch Defaults[.searchMode] {
case .mixed:
return mixedSearch(string: string, within: within)
case .regexp:
return simpleSearch(string: string, within: within, options: .regularExpression)
case .fuzzy:
return fuzzySearch(string: string, within: within)
default:
return simpleSearch(string: string, within: within, options: .caseInsensitive)
}
}
private func fuzzySearch(string: String, within: [Searchable]) -> [SearchResult] {
let pattern = fuse.createPattern(from: string)
let searchResults: [SearchResult] = within.compactMap { item in
fuzzySearch(for: pattern, in: item.title, of: item)
}
let sortedResults = searchResults.sorted(by: { ($0.score ?? 0) < ($1.score ?? 0) })
return sortedResults
}
private func fuzzySearch(
for pattern: Fuse.Pattern?,
in searchString: String,
of item: Searchable
) -> SearchResult? {
var searchString = searchString
if searchString.count > fuzzySearchLimit {
// shortcut to avoid slow search
let stopIndex = searchString.index(searchString.startIndex, offsetBy: fuzzySearchLimit)
searchString = "\(searchString[...stopIndex])"
}
if let fuzzyResult = fuse.search(pattern, in: searchString) {
return SearchResult(
score: fuzzyResult.score,
object: item,
ranges: fuzzyResult.ranges.map {
let startIndex = searchString.startIndex
let lowerBound = searchString.index(startIndex, offsetBy: $0.lowerBound)
let upperBound = searchString.index(startIndex, offsetBy: $0.upperBound + 1)
return lowerBound..<upperBound
}
)
} else {
return nil
}
}
private func simpleSearch(
string: String,
within: [Searchable],
options: NSString.CompareOptions
) -> [SearchResult] {
return within.compactMap { simpleSearch(for: string, in: $0.title, of: $0, options: options) }
}
private func simpleSearch(
for string: String,
in searchString: String,
of item: Searchable,
options: NSString.CompareOptions
) -> SearchResult? {
if let range = searchString.range(of: string, options: options, range: nil, locale: nil) {
return SearchResult(object: item, ranges: [range])
} else {
return nil
}
}
private func mixedSearch(string: String, within: [Searchable]) -> [SearchResult] {
var results = simpleSearch(string: string, within: within, options: .caseInsensitive)
guard results.isEmpty else {
return results
}
results = simpleSearch(string: string, within: within, options: .regularExpression)
guard results.isEmpty else {
return results
}
results = fuzzySearch(string: string, within: within)
guard results.isEmpty else {
return results
}
return []
}
}
================================================
FILE: Maccy/SearchVisibility.swift
================================================
import Defaults
import Foundation
enum SearchVisibility: String, CaseIterable, Identifiable, CustomStringConvertible, Defaults.Serializable {
case always
case duringSearch
var id: Self { self }
var description: String {
switch self {
case .always:
return NSLocalizedString("SearchVisibilityAlways", tableName: "AppearanceSettings", comment: "")
case .duringSearch:
return NSLocalizedString("SearchVisibilityDuringSearch", tableName: "AppearanceSettings", comment: "")
}
}
}
================================================
FILE: Maccy/Selection.swift
================================================
import AppKit
struct Selection<Item: Equatable> {
var items: [Item]
init(items: [Item] = []) {
self.items = items
}
var isEmpty: Bool {
return items.isEmpty
}
var count: Int {
return items.count
}
var first: Item? {
return items.first
}
func first(where condition: (Item) -> Bool) -> Item? {
return items.first(where: condition)
}
func forEach(_ body: (Int, Item) throws -> Void) rethrows {
try items.enumerated().forEach(body)
}
mutating func remove(_ item: Item) {
items.removeAll { $0 == item }
}
mutating func add(_ item: Item) {
items.append(item)
}
}
================================================
FILE: Maccy/Settings/AdvancedSettingsPane.swift
================================================
import SwiftUI
import Defaults
struct AdvancedSettingsPane: View {
var body: some View {
VStack(alignment: .leading) {
Defaults.Toggle(key: .ignoreEvents) {
Text("TurnOff", tableName: "AdvancedSettings")
}
Text("TurnOffDescription", tableName: "AdvancedSettings")
.fixedSize(horizontal: false, vertical: true)
.foregroundStyle(.gray)
.controlSize(.small)
Text("TurnOffShellScript", tableName: "AdvancedSettings")
.fixedSize(horizontal: false, vertical: true)
.foregroundStyle(.gray)
.font(.system(size: 11, design: .monospaced))
.controlSize(.small)
.padding(.vertical, 2)
Text("TurnOffViaMenuIconDescription", tableName: "AdvancedSettings")
.fixedSize(horizontal: false, vertical: true)
.foregroundStyle(.gray)
.controlSize(.small)
Text("TurnOffNextShellScript", tableName: "AdvancedSettings")
.fixedSize(horizontal: false, vertical: true)
.foregroundStyle(.gray)
.font(.system(size: 11, design: .monospaced))
.controlSize(.small)
.padding(.vertical, 2)
Divider()
Defaults.Toggle(key: .clearOnQuit) {
Text("ClearHistoryOnQuit", tableName: "AdvancedSettings")
}.help(Text("ClearHistoryOnQuitTooltip", tableName: "AdvancedSettings"))
Defaults.Toggle(key: .clearSystemClipboard) {
Text("ClearSystemClipboard", tableName: "AdvancedSettings")
}.help(Text("ClearSystemClipboardTooltip", tableName: "AdvancedSettings"))
}
.frame(minWidth: 350, maxWidth: 450)
.padding()
}
}
#Preview {
AdvancedSettingsPane()
.environment(\.locale, .init(identifier: "en"))
}
================================================
FILE: Maccy/Settings/AppearanceSettingsPane.swift
================================================
import AppKit
import SwiftUI
import Defaults
import Settings
struct AppearanceSettingsPane: View {
@Default(.popupPosition) private var popupAt
@Default(.popupScreen) private var popupScreen
@Default(.pinTo) private var pinTo
@Default(.imageMaxHeight) private var imageHeight
@Default(.previewDelay) private var previewDelay
@Default(.highlightMatch) private var highlightMatch
@Default(.menuIcon) private var menuIcon
@Default(.showInStatusBar) private var showInStatusBar
@Default(.showSearch) private var showSearch
@Default(.searchVisibility) private var searchVisibility
@Default(.showFooter) private var showFooter
@Default(.windowPosition) private var windowPosition
@Default(.showApplicationIcons) private var showApplicationIcons
@State private var screens = NSScreen.screens
private let imageHeightFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.minimum = 1
formatter.maximum = 200
return formatter
}()
private let numberOfItemsFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.minimum = 0
formatter.maximum = 100
return formatter
}()
private let titleLengthFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.minimum = 30
formatter.maximum = 200
return formatter
}()
private let previewDelayFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.minimum = 200
formatter.maximum = 100_000
return formatter
}()
var body: some View {
Settings.Container(contentWidth: 650) {
Settings.Section(label: { Text("PopupAt", tableName: "AppearanceSettings") }) {
HStack {
Picker("", selection: $popupAt) {
ForEach(PopupPosition.allCases) { position in
if position == .center || position == .lastPosition, screens.count > 1 {
screenPicker(for: position)
} else {
Text(position.description)
}
}
}
.labelsHidden()
.frame(width: 141, alignment: .leading)
.help(Text("PopupAtTooltip", tableName: "AppearanceSettings"))
if popupAt == .lastPosition {
Button {
_windowPosition.reset()
} label: {
Image(systemName: "arrow.uturn.backward.circle.fill")
.imageScale(.large)
}
.buttonStyle(.borderless)
.help(Text("PopupAtLastLocationReset", tableName: "AppearanceSettings"))
.disabled(windowPosition == _windowPosition.defaultValue)
}
}
}
Settings.Section(label: { Text("PinTo", tableName: "AppearanceSettings") }) {
Picker("", selection: $pinTo) {
ForEach(PinsPosition.allCases) { position in
Text(position.description)
}
}
.labelsHidden()
.frame(width: 141, alignment: .leading)
.help(Text("PinToTooltip", tableName: "AppearanceSettings"))
}
Settings.Section(label: { Text("ImageHeight", tableName: "AppearanceSettings") }) {
HStack {
TextField("", value: $imageHeight, formatter: imageHeightFormatter)
.frame(width: 120)
.help(Text("ImageHeightTooltip", tableName: "AppearanceSettings"))
Stepper("", value: $imageHeight, in: 1...200)
.labelsHidden()
}
}
Settings.Section(label: { Text("PreviewDelay", tableName: "AppearanceSettings") }) {
HStack {
TextField("", value: $previewDelay, formatter: previewDelayFormatter)
.frame(width: 120)
.help(Text("PreviewDelayTooltip", tableName: "AppearanceSettings"))
Stepper("", value: $previewDelay, in: 200...100_000)
.labelsHidden()
}
}
Settings.Section(
bottomDivider: true,
label: { Text("HighlightMatches", tableName: "AppearanceSettings") }
) {
Picker("", selection: $highlightMatch) {
ForEach(HighlightMatch.allCases) { match in
Text(match.description)
}
}
.labelsHidden()
.frame(width: 141, alignment: .leading)
.help(Text("HighlightMatchesTooltip", tableName: "AppearanceSettings"))
}
Settings.Section(title: "") {
Defaults.Toggle(key: .showSpecialSymbols) {
Text("ShowSpecialSymbols", tableName: "AppearanceSettings")
}
.help(Text("ShowSpecialSymbolsTooltip", tableName: "AppearanceSettings"))
HStack {
Defaults.Toggle(key: .showInStatusBar) {
Text("ShowMenuIcon", tableName: "AppearanceSettings")
}
Picker("", selection: $menuIcon) {
ForEach(MenuIcon.allCases) { icon in
Image(nsImage: icon.image)
}
}
.labelsHidden()
.scaledToFit()
.disabled(!showInStatusBar)
.controlSize(.small)
}
Defaults.Toggle(key: .showRecentCopyInMenuBar) {
Text("ShowRecentCopyInMenuBar", tableName: "AppearanceSettings")
}
HStack {
Defaults.Toggle(key: .showSearch) {
Text("ShowSearchField", tableName: "AppearanceSettings")
}
Picker("", selection: $searchVisibility) {
ForEach(SearchVisibility.allCases) { type in
Text(type.description)
}
}
.labelsHidden()
.scaledToFit()
.disabled(!showSearch)
.controlSize(.small)
}
Defaults.Toggle(key: .showTitle) {
Text("ShowTitleBeforeSearchField", tableName: "AppearanceSettings")
}
Defaults.Toggle(key: .showApplicationIcons) {
Text("ShowApplicationIcons", tableName: "AppearanceSettings")
}
Defaults.Toggle(key: .showFooter) {
Text("ShowFooter", tableName: "AppearanceSettings")
}
Text("OpenPreferencesWarning", tableName: "AppearanceSettings")
.opacity(showFooter ? 0 : 1)
.controlSize(.small)
.foregroundStyle(.gray)
}
}
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didChangeScreenParametersNotification)) { _ in
screens = NSScreen.screens
}
}
@ViewBuilder
private func screenPicker(for position: PopupPosition) -> some View {
let screenBinding: Binding<Int> = Binding {
return popupScreen
} set: {
popupScreen = $0
popupAt = position
}
Picker(selection: screenBinding) {
Text(labelForScreen(index: 0))
.tag(0)
ForEach(screens.indices, id: \.self) { index in
Text(labelForScreen(index: index + 1))
.tag(index + 1)
}
} label: {
if popupAt == position {
Text("\(position.description) (\(labelForScreen(index: popupScreen)))")
} else {
Text(position.description)
}
}
}
private func labelForScreen(index screenIndex: Int) -> String {
switch screenIndex {
case 0:
return String(localized: "ActiveScreen", table: "AppearanceSettings")
case _:
return screens[screenIndex - 1].localizedName
}
}
}
#Preview {
AppearanceSettingsPane()
.environment(\.locale, .init(identifier: "en"))
}
================================================
FILE: Maccy/Settings/GeneralSettingsPane.swift
================================================
import SwiftUI
import Defaults
import KeyboardShortcuts
import LaunchAtLogin
import Settings
struct GeneralSettingsPane: View {
private let notificationsURL = URL(
string: "x-apple.systempreferences:com.apple.preference.notifications?id=\(Bundle.main.bundleIdentifier ?? "")"
)
@Default(.searchMode) private var searchMode
@State private var copyModifier = HistoryItemAction.copy.modifierFlags.description
@State private var pasteModifier = HistoryItemAction.paste.modifierFlags.description
@State private var pasteWithoutFormatting = HistoryItemAction.pasteWithoutFormatting.modifierFlags.description
@State private var updater = SoftwareUpdater()
var body: some View {
Settings.Container(contentWidth: 450) {
Settings.Section(title: "", bottomDivider: true) {
LaunchAtLogin.Toggle {
Text("LaunchAtLogin", tableName: "GeneralSettings")
}
Toggle(isOn: $updater.automaticallyChecksForUpdates) {
Text("CheckForUpdates", tableName: "GeneralSettings")
}
Button(
action: { updater.checkForUpdates() },
label: { Text("CheckNow", tableName: "GeneralSettings") }
)
}
Settings.Section(label: { Text("Open", tableName: "GeneralSettings") }) {
KeyboardShortcuts.Recorder(for: .popup, onChange: { newShortcut in
if newShortcut == nil {
// No shortcut is recorded. Remove keys monitor
AppState.shared.popup.deinitEventsMonitor()
} else {
// User is using shortcut. Ensure keys monitor is initialized
AppState.shared.popup.initEventsMonitor()
}
})
.help(Text("OpenTooltip", tableName: "GeneralSettings"))
}
Settings.Section(label: { Text("Pin", tableName: "GeneralSettings") }) {
KeyboardShortcuts.Recorder(for: .pin)
.help(Text("PinTooltip", tableName: "GeneralSettings"))
}
Settings.Section(label: { Text("Delete", tableName: "GeneralSettings") }
) {
KeyboardShortcuts.Recorder(for: .delete)
.help(Text("DeleteTooltip", tableName: "GeneralSettings"))
}
Settings.Section(
bottomDivider: true,
label: { Text("ShowPreview", tableName: "GeneralSettings") }
) {
KeyboardShortcuts.Recorder(for: .togglePreview)
.help(Text("ShowPreviewTooltip", tableName: "GeneralSettings"))
}
Settings.Section(
bottomDivider: true,
label: { Text("Search", tableName: "GeneralSettings") }
) {
Picker("", selection: $searchMode) {
ForEach(Search.Mode.allCases) { mode in
Text(mode.description)
}
}
.labelsHidden()
.frame(width: 180, alignment: .leading)
}
Settings.Section(
bottomDivider: true,
label: { Text("Behavior", tableName: "GeneralSettings") }
) {
Defaults.Toggle(key: .pasteByDefault) {
Text("PasteAutomatically", tableName: "GeneralSettings")
}
.onChange(refreshModifiers)
.fixedSize()
Defaults.Toggle(key: .removeFormattingByDefault) {
Text("PasteWithoutFormatting", tableName: "GeneralSettings")
}
.onChange(refreshModifiers)
.fixedSize()
Text(String(
format: NSLocalizedString("Modifiers", tableName: "GeneralSettings", comment: ""),
copyModifier, pasteModifier, pasteWithoutFormatting
))
.fixedSize(horizontal: false, vertical: true)
.foregroundStyle(.gray)
.controlSize(.small)
}
Settings.Section(title: "") {
if let notificationsURL = notificationsURL {
Link(destination: notificationsURL, label: {
Text("NotificationsAndSounds", tableName: "GeneralSettings")
})
}
}
}
}
private func refreshModifiers(_ sender: Sendable) {
copyModifier = HistoryItemAction.copy.modifierFlags.description
pasteModifier = HistoryItemAction.paste.modifierFlags.description
pasteWithoutFormatting = HistoryItemAction.pasteWithoutFormatting.modifierFlags.description
}
}
#Preview {
GeneralSettingsPane()
.environment(\.locale, .init(identifier: "en"))
}
================================================
FILE: Maccy/Settings/IgnoreSettingsPane/IgnoreApplicationsSettingsView.swift
================================================
import SwiftUI
import Defaults
struct IgnoreApplicationsSettingsView: View {
@Default(.ignoredApps) private var ignoredApps
@State private var isAdding = false
@State private var selection = ""
var body: some View {
VStack(alignment: .leading) {
List(selection: $selection) {
ForEach($ignoredApps) { $app in
if let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: app) {
Label(
title: {
Text(NSWorkspace.shared.applicationName(url: url))
.padding(.horizontal, 5)
},
icon: {
Image(nsImage: NSWorkspace.shared.icon(forFile: url.path))
}
).frame(height: 32).padding(.horizontal, 5)
} else {
Label(
title: { Text(app).padding(.horizontal, 5) },
icon: { Image(systemName: "questionmark.circle").imageScale(.large) }
).frame(height: 32).padding(.horizontal, 5)
}
}
}.onDeleteCommand {
remove(selection)
}
HStack {
ControlGroup {
Button("", systemImage: "plus") {
isAdding = true
}
Button("", systemImage: "minus") {
remove(selection)
}
}
.frame(width: 50)
.fileDialogDefaultDirectory(URL(string: "/Applications"))
.fileImporter(
isPresented: $isAdding,
allowedContentTypes: [.application]
) { result in
switch result {
case .success(let appUrl):
if let bundle = Bundle(path: appUrl.path),
let bundleIdentifier = bundle.bundleIdentifier,
!ignoredApps.contains(bundleIdentifier) {
ignoredApps.append(bundleIdentifier)
}
case .failure(let error):
print("Failed to select application: \(error)")
}
}
Defaults.Toggle(key: .ignoreAllAppsExceptListed) {
Text("IgnoredAllAppsExceptListed", tableName: "IgnoreSettings")
}
}
Text("IgnoredAppsDescription", tableName: "IgnoreSettings")
.fixedSize(horizontal: false, vertical: true)
.foregroundStyle(.gray)
.controlSize(.small)
}.padding()
}
private func remove(_ app: String?) {
guard let app else { return }
ignoredApps.removeAll(where: { $0 == app })
}
}
#Preview {
IgnoreApplicationsSettingsView()
.environment(\.locale, .init(identifier: "en"))
}
================================================
FILE: Maccy/Settings/IgnoreSettingsPane/IgnorePasteboardTypesSettingsView.swift
================================================
import Defaults
import SwiftUI
struct IgnorePasteboardTypesSettingsView: View {
@Default(.ignoredPasteboardTypes) private var ignoredPasteboardTypes
@FocusState private var focus: String.ID?
@State private var edit = ""
@State private var selection = ""
var body: some View {
VStack(alignment: .leading) {
List(selection: $selection) {
ForEach(ignoredPasteboardTypes.sorted()) { type in
TextField("", text: Binding(
get: { type },
set: {
guard !$0.isEmpty, type != $0 else { return }
edit = $0
})
)
.onSubmit {
remove(type)
ignoredPasteboardTypes.insert(edit)
}
.focused($focus, equals: type)
}
}
.onDeleteCommand {
remove(selection)
}
HStack {
ControlGroup {
Button("", systemImage: "plus") {
ignoredPasteboardTypes.insert("xxx.yyy.zzz")
focus = "xxx.yyy.zzz"
}
Button("", systemImage: "minus") {
remove(selection)
}
}
.frame(width: 50)
Spacer()
Button {
Defaults.reset(.ignoredPasteboardTypes)
} label: {
Text("IgnoredPasteboardTypesReset", tableName: "IgnoreSettings")
}
}
Text("IgnoredPasteboardTypesDescription", tableName: "IgnoreSettings")
.fixedSize(horizontal: false, vertical: true)
.foregroundStyle(.gray)
.controlSize(.small)
}
.padding()
}
private func remove(_ type: String?) {
guard let type else { return }
ignoredPasteboardTypes.remove(type)
}
}
#Preview {
IgnorePasteboardTypesSettingsView()
.environment(\.locale, .init(identifier: "en"))
}
================================================
FILE: Maccy/Settings/IgnoreSettingsPane/IgnoreRegexpsSettingsView.swift
================================================
import SwiftUI
import Defaults
struct IgnoreRegexpsSettingsView: View {
@Default(.ignoreRegexp) private var ignoredRegexps
@FocusState private var focus: String.ID?
@State private var edit = ""
@State private var selection = ""
var body: some View {
VStack(alignment: .leading) {
List(selection: $selection) {
ForEach(ignoredRegexps) { regexp in
TextField("", text: Binding(
get: { regexp },
set: {
guard !$0.isEmpty, regexp != $0 else { return }
edit = $0
})
).onSubmit {
remove(regexp)
ignoredRegexps.append(edit)
}.focused($focus, equals: regexp)
}
}.onDeleteCommand {
remove(selection)
}
ControlGroup {
Button("", systemImage: "plus") {
ignoredRegexps.append("^[a-zA-Z0-9]{50}$")
focus = "^[a-zA-Z0-9]{50}$"
}
Button("", systemImage: "minus") {
remove(selection)
}
}.frame(width: 50)
Text("IgnoredRegexpsDescription", tableName: "IgnoreSettings")
.fixedSize(horizontal: false, vertical: true)
.foregroundStyle(.gray)
.controlSize(.small)
}.padding()
}
private func remove(_ regexp: String?) {
guard let regexp else { return }
ignoredRegexps.removeAll(where: { $0 == regexp })
}
}
#Preview {
IgnoreRegexpsSettingsView()
.environment(\.locale, .init(identifier: "en"))
}
================================================
FILE: Maccy/Settings/IgnoreSettingsPane.swift
================================================
import SwiftUI
struct IgnoreSettingsPane: View {
var body: some View {
TabView {
IgnoreApplicationsSettingsView()
.tabItem {
Text("ApplicationsTab", tableName: "IgnoreSettings")
}
IgnorePasteboardTypesSettingsView()
.tabItem {
Text("PasteboardTypesTab", tableName: "IgnoreSettings")
}
IgnoreRegexpsSettingsView()
.tabItem {
Text("RegexpTab", tableName: "IgnoreSettings")
}
}
.frame(maxWidth: 500, minHeight: 400)
.padding()
}
}
#Preview {
IgnoreSettingsPane()
.environment(\.locale, .init(identifier: "en"))
}
================================================
FILE: Maccy/Settings/PinsSettingsPane.swift
================================================
import SwiftData
import SwiftUI
struct PinPickerView: View {
@Bindable var item: HistoryItem
var availablePins: [String]
var body: some View {
if let pin = item.pin {
// Ensure unique pins for ForEach
let uniquePins = Array(Set(availablePins + [pin])).sorted()
Picker("", selection: $item.pin) {
ForEach(uniquePins, id: \.self) { pin in
Text(pin)
.tag(pin as String?)
}
}
.controlSize(.small)
.labelsHidden()
}
}
}
struct PinTitleView: View {
@Bindable var item: HistoryItem
var body: some View {
TextField("", text: $item.title)
}
}
struct PinValueView: View {
@Bindable var item: HistoryItem
@State private var editableValue: String
@State private var isTextContent: Bool
@State private var isRichText: Bool
@FocusState private var isEditing: Bool
@State private var showWarningPopover: Bool = false
init(item: HistoryItem) {
self.item = item
self._editableValue = State(initialValue: item.previewableText)
// Check if this item has editable text content
let hasPlainText = item.text != nil
let hasImage = item.image != nil
let hasFileURLs = !item.fileURLs.isEmpty
let hasRichText = item.rtf != nil || item.html != nil
// Consider it text content only if it has plain text and doesn't have images or file URLs
self._isTextContent = State(initialValue: hasPlainText && !hasImage && !hasFileURLs)
self._isRichText = State(initialValue: hasRichText && !hasImage && !hasFileURLs)
}
var body: some View {
Group {
if isTextContent || isRichText {
ZStack(alignment: .trailing) {
TextField("", text: $editableValue)
.focused($isEditing)
.onSubmit {
updateItemContent()
}
.onChange(of: editableValue) { _, _ in
updateItemContent()
}
.padding(.trailing, isRichText ? 40 : 0) // increased space for icon
if isRichText && isEditing {
HStack(spacing: 0) {
Spacer(minLength: 0)
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.orange)
.help(Text("RichTextEditWarning", tableName: "PinsSettings"))
Spacer().frame(width: 4)
}
.frame(maxHeight: .infinity, alignment: .center)
.padding(.trailing, 4)
}
}
} else {
// Non-editable display for non-text content
Text("ContentIsNotText", tableName: "PinsSettings")
.foregroundStyle(.secondary)
.italic()
}
}
}
private func updateItemContent() {
// Only update if we're dealing with text or rich text content
guard isTextContent || isRichText else { return }
// Remove all non-plain-text content
let stringType = NSPasteboard.PasteboardType.string.rawValue
item.contents.removeAll { $0.type != stringType }
// Update or add the plain text content
if let index = item.contents.firstIndex(where: { $0.type == stringType }) {
if let data = editableValue.data(using: .utf8) {
item.contents[index].value = data
}
} else {
if let data = editableValue.data(using: .utf8) {
let newContent = HistoryItemContent(type: stringType, value: data)
item.contents.append(newContent)
}
}
// We don't automatically update title here since we want to preserve
// OCR-extracted titles for images and other non-text content
}
}
struct PinsSettingsPane: View {
@Environment(AppState.self) private var appState
@Environment(\.modelContext) private var modelContext
@Query(filter: #Predicate<HistoryItem> { $0.pin != nil }, sort: \.firstCopiedAt)
private var items: [HistoryItem]
@State private var availablePins: [String] = []
@State private var selection: PersistentIdentifier?
var body: some View {
VStack(alignment: .leading) {
Table(items, selection: $selection) {
TableColumn(Text("Key", tableName: "PinsSettings")) { item in
PinPickerView(item: item, availablePins: availablePins)
.onChange(of: item.pin) {
availablePins = HistoryItem.availablePins
}
}
.width(60)
TableColumn(Text("Alias", tableName: "PinsSettings")) { item in
PinTitleView(item: item)
}
TableColumn(Text("Content", tableName: "PinsSettings")) { item in
PinValueView(item: item)
}
}
.onAppear {
availablePins = HistoryItem.availablePins
}
.onDeleteCommand {
guard let selection,
let item = appState.history.items.first(where: { $0.item.id == selection }) else {
return
}
appState.history.delete(item)
}
Text("PinCustomizationDescription", tableName: "PinsSettings")
.foregroundStyle(.gray)
.controlSize(.small)
}
.frame(minWidth: 500, minHeight: 400)
.padding()
}
}
#Preview {
return PinsSettingsPane()
.environment(\.locale, .init(identifier: "en"))
.modelContainer(Storage.shared.container)
}
================================================
FILE: Maccy/Settings/StorageSettingsPane.swift
================================================
import SwiftUI
import Defaults
import Settings
struct StorageSettingsPane: View {
@Observable
class ViewModel {
var saveFiles = false {
didSet {
Defaults.withoutPropagation {
if saveFiles {
Defaults[.enabledPasteboardTypes].formUnion(StorageType.files.types)
} else {
Defaults[.enabledPasteboardTypes].subtract(StorageType.files.types)
}
}
}
}
var saveImages = false {
didSet {
Defaults.withoutPropagation {
if saveImages {
Defaults[.enabledPasteboardTypes].formUnion(StorageType.images.types)
} else {
Defaults[.enabledPasteboardTypes].subtract(StorageType.images.types)
}
}
}
}
var saveText = false {
didSet {
Defaults.withoutPropagation {
if saveText {
Defaults[.enabledPasteboardTypes].formUnion(StorageType.text.types)
} else {
Defaults[.enabledPasteboardTypes].subtract(StorageType.text.types)
}
}
}
}
private var observer: Defaults.Observation?
init() {
observer = Defaults.observe(.enabledPasteboardTypes) { change in
self.saveFiles = change.newValue.isSuperset(of: StorageType.files.types)
self.saveImages = change.newValue.isSuperset(of: StorageType.images.types)
self.saveText = change.newValue.isSuperset(of: StorageType.text.types)
}
}
deinit {
observer?.invalidate()
}
}
@Default(.size) private var size
@Default(.sortBy) private var sortBy
@State private var viewModel = ViewModel()
@State private var storageSize = Storage.shared.size
private let sizeFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.minimum = 1
formatter.maximum = 999
return formatter
}()
var body: some View {
Settings.Container(contentWidth: 450) {
Settings.Section(
bottomDivider: true,
label: { Text("Save", tableName: "StorageSettings") }
) {
Toggle(
isOn: $viewModel.saveFiles,
label: { Text("Files", tableName: "StorageSettings") }
)
Toggle(
isOn: $viewModel.saveImages,
label: { Text("Images", tableName: "StorageSettings") }
)
Toggle(
isOn: $viewModel.saveText,
label: { Text("Text", tableName: "StorageSettings") }
)
Text("SaveDescription", tableName: "StorageSettings")
.controlSize(.small)
.foregroundStyle(.gray)
}
Settings.Section(label: { Text("Size", tableName: "StorageSettings") }) {
HStack {
TextField("", value: $size, formatter: sizeFormatter)
.frame(width: 80)
.help(Text("SizeTooltip", tableName: "StorageSettings"))
Stepper("", value: $size, in: 1...999)
.labelsHidden()
Text(storageSize)
.controlSize(.small)
.foregroundStyle(.gray)
.help(Text("CurrentSizeTooltip", tableName: "StorageSettings"))
.onAppear {
storageSize = Storage.shared.size
}
}
}
Settings.Section(label: { Text("SortBy", tableName: "StorageSettings") }) {
Picker("", selection: $sortBy) {
ForEach(Sorter.By.allCases) { mode in
Text(mode.description)
}
}
.labelsHidden()
.frame(width: 160, alignment: .leading)
.help(Text("SortByTooltip", tableName: "StorageSettings"))
}
}
}
}
#Preview {
StorageSettingsPane()
.environment(\.locale, .init(identifier: "en"))
}
================================================
FILE: Maccy/Settings/ar.lproj/AdvancedSettings.strings
================================================
"Title" = "متقدم";
"TurnOff" = "إيقاف";
"TurnOffDescription" = "تجاهل جميع النسخ الجديدة مؤقتًا.\nمن المرجح أن تستخدمه برمجيًا وتعطل التطبيق أثناء نسخ البيانات الحساسة.";
"TurnOffShellScript" = "defaults write org.p0deje.Maccy ignoreEvents true\nنسخ البيانات #\ndefaults write org.p0deje.Maccy ignoreEvents false";
"TurnOffViaMenuIconDescription" = "يمكنك أيضًا النقر على أيقونة القائمة مع ⌥ مضغوط.\nلتجاهل النسخة التالية فقط، انقر بـ ⌥⇧.";
"TurnOffNextShellScript" = "defaults write org.p0deje.Maccy ignoreOnlyNextEvent true\nنسخ البيانات #";
"ClearHistoryOnQuit" = "مسح السجل عند الخروج";
"ClearHistoryOnQuitTooltip" = "إزالة جميع العناصر غير المثبتة تلقائيًا قبل إغلاق التطبيق.";
"ClearSystemClipboard" = "مسح حافظة النظام أيضًا";
"ClearSystemClipboardTooltip" = "عند التمكين، سيؤدي مسح السجل أيضًا إلى مسح حافظة النظام الحالية.";
================================================
FILE: Maccy/Settings/ar.lproj/AppearanceSettings.strings
================================================
"Title" = "المظهر";
"PopupAt" = "ظهور الإنبثاق عند:";
"PopupAtCursor" = "المؤشر";
"PopupAtMenuBarIcon" = "أيقونة القائمة";
"PopupAtWindowCenter" = "مركز النافذة";
"PopupAtScreenCenter" = "مركز الشاشة";
"PopupAtLastPosition" = "الموضع الأخير";
"PopupAtLastLocationReset" = "إعادة تعيين الموضع";
"PopupAtTooltip" = "تغيير موقع ظهور الإنبثاق.\nالافتراضي: المؤشر.";
"SearchVisibilityAlways" = "دائمًا";
"SearchVisibilityDuringSearch" = "أثناء البحث";
"ActiveScreen" = "شاشة نشطة";
"PinTo" = "تثبيت إلى:";
"PinToTop" = "الأعلى";
"PinToBottom" = "الأسفل";
"PinToTooltip" = "تغيير موقع العناصر المثبتة.\nالافتراضي: الأعلى.";
"ImageHeight" = "ارتفاع الصورة:";
"ImageHeightTooltip" = "أقصى ارتفاع معاينة الصورة.\nالافتراضي: 40.\nتلميح: قم بتعيينه إلى 16 ليبدو مثل عناصر النص.";
"PreviewDelay" = "تأخير المعاينة:";
"PreviewDelayTooltip" = "التأخير بالميللي ثانية حتى يتم عرض معاينة النافذة المنبثقة.\nالافتراضي: 1500.";
"HighlightMatches" = "تسليط الضوء على التطابقات:";
"HighlightMatchColor" = "اللون";
"HighlightMatchBold" = "عريض";
"HighlightMatchItalic" = "مائل";
"HighlightMatchUnderline" = "تسطير";
"HighlightMatchesTooltip" = "تغيير نمط التميز للبحث عن المطابقات.\nالافتراضي: عريض.";
"ShowSpecialSymbols" = "إظهار الرموز الخاصة";
"ShowSpecialSymbolsTooltip" = "إظهار الأسطر الجديدة، علامات التبويب، المسافات الأمامية والخلفية عبر الرموز الخاصة.";
"ShowMenuIcon" = "إظهار أيقونة القائمة";
"ShowRecentCopyInMenuBar" = "إظهار النسخة الأخيرة بجوار أيقونة القائمة";
"ShowSearchField" = "إظهار حقل البحث";
"ShowTitleBeforeSearchField" = "إظهار العنوان قبل حقل البحث";
"ShowFooter" = "إظهار التذييل";
"ShowApplicationIcons" = "إظهار أيقونات التطبيقات";
"OpenPreferencesWarning" = "⚠️ اضغط على ⌘، (command+comma) لفتح التفضيلات عندما يكون التذييل مخفيًا.";
================================================
FILE: Maccy/Settings/ar.lproj/GeneralSettings.strings
================================================
"Title" = "عام";
"LaunchAtLogin" = "التشغيل عند تسجيل الدخول";
"CheckForUpdates" = "التحقق من التحديثات تلقائيًا";
"CheckNow" = "التحقق الآن";
"Open" = "فتح:";
"OpenTooltip" = "مفتاح الاختصار العام لفتح التطبيق.\nسيؤدي الضغط المتكرر على المفتاح الرئيسي أثناء الضغط باستمرار على مفاتيح التعديل إلى تحديد العنصر التالي في القائمة. في هذا الوضع، سيؤدي تحرير مفاتيح التعديل إلى تأكيد التحديد وإغلاق النافذة المنبثقة.\nالوضع الافتراضي: ⇧⌘C.";
"Pin" = "تثبيت:";
"PinTooltip" = "مفتاح الاختصار لتثبيت عنصر السجل.\nالافتراضي: %@P.";
"Delete" = "حذف:";
"DeleteTooltip" = "مفتاح الاختصار لحذف عنصر السجل.\nالافتراضي: %@⌫.";
"ShowPreview" = "معاينة:";
"ShowPreviewTooltip" = "إظهار المعاينة مع معلومات إضافية.\nالافتراضي: ⌃Space.";
"Behavior" = "الإجراء:";
"PasteAutomatically" = "لصق تلقائيًا";
"PasteWithoutFormatting" = "لصق بدون تنسيق";
"Modifiers" = "تخصيص الإجراء عند تحديد العنصر:\n• حدد مع %@ ضغط لنسخ العنصر.\n• حدد مع %@ ضغط لنسخ ولصق العنصر.\n• حدد مع %@ ضغط لنسخ، مسح التنسيق ولصق العنصر.";
"Search" = "البحث:";
"Exact" = "دقيق";
"Fuzzy" = "غامض";
"Regex" = "التعبيرات العادية";
"Mixed" = "مختلط";
"NotificationsAndSounds" = "الإشعارات والأصوات ";
================================================
FILE: Maccy/Settings/ar.lproj/IgnoreSettings.strings
================================================
"Title" = "تجاهل";
"ApplicationsTab" = "التطبيقات";
"IgnoredAppsDescription" = "من الممكن تجاهل النسخ القادمة من بعض التطبيقات.\nيرجى ملاحظة أن الطريقة التي تعمل بها ليست مضمونة بنسبة 100٪، لذا من الأفضل استخدام أنواع الحافظة عند الإمكان.";
"IgnoredAllAppsExceptListed" = "تجاهل جميع التطبيقات ما عدا المُدرجة";
"PasteboardTypesTab" = "أنواع الحافظة";
"IgnoredPasteboardTypesDescription" = "من الممكن تجاهل بعض أنواع عناصر الحافظة من التذكر.\nبشكل افتراضي، تم تحديد بعض الأنواع المعروفة المحددة للتطبيق. يمكنك إزالتها وإضافة أي أنواع مخصصة ترغب فيها.";
"IgnoredPasteboardTypesReset" = "إعادة تعيين";
"RegexpTab" = "التعبيرات العادية";
"IgnoredRegexpsDescription" = "من الممكن تجاهل بعض النسخ من التذكر استنادًا إلى التعبيرات العادية المحددة.";
================================================
FILE: Maccy/Settings/ar.lproj/PinsSettings.strings
================================================
"Title" = "مثبتة";
"Key" = "المفتاح";
"Alias" = "العنوان";
"Content" = "المحتوى";
"ContentIsNotText" = "محتوى غير قابل للتحرير (صورة أو ملف)";
"RichTextEditWarning" = "سيؤدي التحرير إلى تجاهل جميع التنسيقات.";
"PinCustomizationDescription" = "يمكنك تخصيص مفتاح الاختصار، والعنوان، ومحتوى أي عنصر مثبت. للتعديل، انقر نقرًا مزدوجًا فوق العنصر وأدخل قيمة جديدة.\nيرجى ملاحظة أنه يمكن تغيير النص العادي فقط.";
================================================
FILE: Maccy/Settings/ar.lproj/StorageSettings.strings
================================================
"Title" = "التخزين";
"Save" = "حفظ:";
"Files" = "الملفات";
"Images" = "الصور";
"Text" = "النصوص";
"SaveDescription" = "تغيير أنواع المحتوى المنسوخة التي يجب حفظها.";
"Size" = "الحجم:";
"SizeTooltip" = "عدد عناصر التاريخ للحفاظ عليها.\nالافتراضي: 200.";
"CurrentSizeTooltip" = "الحجم الحالي على القرص.";
"SortBy" = "الترتيب حسب:";
"LastCopiedAt" = "وقت النسخة الأخيرة";
"FirstCopiedAt" = "وقت النسخة الأولى";
"NumberOfCopies" = "عدد النسخ";
"SortByTooltip" = "الافتراضي: وقت النسخة الأخيرة.";
================================================
FILE: Maccy/Settings/be.lproj/AdvancedSettings.strings
================================================
"Title" = "Прасунутыя";
"TurnOff" = "Выключыць";
"TurnOffShellScript" = "defaults write org.p0deje.Maccy ignoreEvents true\n# капіяваць дадзеныя\ndefaults write org.p0deje.Maccy ignoreEvents false";
"TurnOffNextShellScript" = "defaults write org.p0deje.Maccy ignoreOnlyNextEvent true\n# капіяваць дадзеныя";
"ClearHistoryOnQuit" = "Ачысціць гісторыю пры зачыненні";
"ClearSystemClipboard" = "Таксама ачысціць сістэмны буфер абмену";
"ClearSystemClipboardTooltip" = "Калі гэты параметр улучаны, ачыстка гісторыі таксама ачысціць бягучы сістэмны буфер абмену.";
"TurnOffDescription" = "Часова ігнараваць усе новыя капіяванні.\nВы можаце скарыстаць гэту наладу праграмна і адключыць дадатак на час капіявання прыватных дадзеных.";
"TurnOffViaMenuIconDescription" = "Вы таксама можаце клікнуць значок меню з націснутым ⌥.\nКаб ігнараваць толькі наступную копію, клікніце з ⌥⇧.";
"ClearHistoryOnQuitTooltip" = "Аўтаматычна падаляць усе незамацаваныя запісы перад зачыненнем дадатку.";
================================================
FILE: Maccy/Settings/be.lproj/AppearanceSettings.strings
================================================
"Title" = "Выгляд";
"PopupAtMenuBarIcon" = "Значка меню";
"PopupAtWindowCenter" = "Цэнтра акна";
"PopupAtScreenCenter" = "Цэнтра экрану";
"PopupAtLastLocationReset" = "Скінуць пазіцыю";
"PopupAtLastPosition" = "Апошняй пазіцыіі";
"SearchVisibilityDuringSearch" = "Падчас пошуку";
"ActiveScreen" = "Актыўны экран";
"PinToTop" = "Верху";
"PinToBottom" = "Нізу";
"ImageHeight" = "Вышыня выяў:";
"PreviewDelay" = "Затрымка прадпрагляду:";
"HighlightMatches" = "Вылучыць супадзенні:";
"HighlightMatchBold" = "Тоўсты";
"HighlightMatchItalic" = "Курсіў";
"HighlightMatchUnderline" = "Падкрэслены";
"ShowSpecialSymbols" = "Паказваць адмысловыя знакі";
"ShowMenuIcon" = "Паказваць значок меню";
"ShowRecentCopyInMenuBar" = "Паказваць апошні элемент поруч з значком меню";
"ShowFooter" = "Паказваць ніжні калонтытул";
"PopupAt" = "Усплываць каля:";
"PopupAtCursor" = "Курсору";
"SearchVisibilityAlways" = "Заўсёды";
"PinTo" = "Замацаваць да:";
"PinToTooltip" = "Змяніць месца замацаваных элементаў.\nПадставова: Верх.";
"HighlightMatchColor" = "Колер";
"OpenPreferencesWarning" = "⚠️ Націсніце ⌘, (command+comma) каб адкрыць налады калі ніжні калонтытул утоены.";
"ShowSpecialSymbolsTooltip" = "Паказваць знакі новага радка, табуляцыі, пачатковыя і канчатковыя прабелы з дапамогай адмысловых знакаў.";
"ShowSearchField" = "Паказваць поле пошуку";
"ShowTitleBeforeSearchField" = "Паказваць назву перад пошукам";
"ImageHeightTooltip" = "Максімальная вышыня прадпрагляду выяў.\nПадставова: 40.\nПадказка: Пастаўце 16 для падабенства з тэкставымі запісамі.";
"HighlightMatchesTooltip" = "Змяніце стыль вылучэння для супадзенняў пры пошуку.\nПадставова: Тоўсты.";
"PreviewDelayTooltip" = "Затрымка ў мілісекундах да з'яўлення ўсплыўнога акна прадпрагляду.\nПадставова: 1500.";
"PopupAtTooltip" = "Змяніць месца для з'яўлення ўсплыўнога акна.\nПадставова: Курсор.";
"ShowApplicationIcons" = "Паказаць значкі прыкладанняў";
================================================
FILE: Maccy/Settings/be.lproj/GeneralSettings.strings
================================================
"LaunchAtLogin" = "Запускаць пры логіне";
"CheckNow" = "Праверыць зараз";
"Open" = "Адчыніць:";
"PinTooltip" = "Гарачая клавіша для замацавання элементу.\nПадставова: %@P.";
"Delete" = "Выдаліць:";
"DeleteTooltip" = "Гарачая клавіша для выдалення элементу.\nПадставова: ⌥⌫.";
"ShowPreview" = "Перадпрагляд:";
"ShowPreviewTooltip" = "Паказаць перадпрагляд з дадатковай інфармацыяй.\nПадставова: ⌃Space.";
"Behavior" = "Паводзіны:";
"PasteAutomatically" = "Аўтаматычна ўстаўляць";
"PasteWithoutFormatting" = "Устаўляць без фарматавання";
"Modifiers" = "Наладзіць паводзіны пры выбары элемента:\n• Выберыце з %@, каб скапіяваць элемент.\n• Выберыце з %@, каб скапіяваць і ўставіць элемент.\n• Выберыце з %@, каб скапіяваць, ачысціць фарматаванне і ўставіць элемент.";
"Exact" = "Дакладны";
"Fuzzy" = "Недакладны";
"Regex" = "Рэгулярныя выразы";
"Mixed" = "Змяшаны";
"NotificationsAndSounds" = "Паведамленні і гукі ";
"Title" = "Асноўныя";
"CheckForUpdates" = "Аўтаматычна правяраць абнаўленні";
"OpenTooltip" = "Глабальная камбінацыя клавіш для адкрыцця праграмы.\nПаўторнае націсканне асноўнай клавішы з утрыманнем мадыфікатараў выбера наступны элемент у спісе. У гэтым рэжыме адпусканне клавіш-мадыфікатараў пацвердзіць выбар і закрые ўсплывальнае акно.\nПа змаўчанні: ⇧⌘C.";
"Search" = "Пошук:";
"Pin" = "Замацаваць:";
================================================
FILE: Maccy/Settings/be.lproj/IgnoreSettings.strings
================================================
"IgnoredAppsDescription" = "Капіяванні з пэўных дадаткаў можна ігнараваць.\nКалі ласка ўлічвайце, што гэтае вырашэнне не гарантуе стаадсадковага выніку, таму, па-магчымасці, рэкамендавана карыстацца з ігнараваных тыпаў.";
"ApplicationsTab" = "Дадаткі";
"Title" = "Ігнараванне";
"IgnoredAllAppsExceptListed" = "Ігнараваць усе дадаткі, апроч пералічаных";
"PasteboardTypesTab" = "Тыпы капіяваных дадзеных";
"IgnoredPasteboardTypesDescription" = "Капіяванні пэўных тыпаў капіяваных дадзеных можна ігнараваць.\nПа змаўчанні паказаны некаторыя вядомыя тыпы, спецыфічныя для некаторых дадаткаў. Вы можаце іх выдаліць і дадаць свае тыпы.";
"IgnoredPasteboardTypesReset" = "Скід";
"RegexpTab" = "Рэгулярныя выразы";
"IgnoredRegexpsDescription" = "Капіяванні, якія адпавядаюць некаторым рэгулярным выразам, можна ігнараваць.";
================================================
FILE: Maccy/Settings/be.lproj/PinsSettings.strings
================================================
"Title" = "Замацаваныя";
"Key" = "Клавіша";
"Alias" = "Назва";
"Content" = "Кантэнт";
"ContentIsNotText" = "Нерэдагавальны кантэнт (малюнак або файл)";
"RichTextEditWarning" = "Рэдагаванне адменіць усё фарматаванне.";
"PinCustomizationDescription" =
gitextract_l43svsy0/
├── .bartycrouch.toml
├── .github/
│ ├── FUNDING.yml
│ └── ISSUE_TEMPLATE/
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
├── .gitignore
├── .periphery.yml
├── .swiftlint.yml
├── Designs/
│ ├── App-Store/
│ │ ├── 01-Main.pxd
│ │ ├── 02-Types.pxd
│ │ ├── 03-Search.pxd
│ │ ├── 04-Pin.pxd
│ │ ├── 05-Shortcuts.pxd
│ │ ├── 06-Languages.pxd
│ │ └── Promo/
│ │ └── Maccy_1527619437_20240124_MacAppStore_SupportingImagery.psd
│ ├── Copies.txt
│ ├── Icons.sketch
│ ├── Instructions.pxd/
│ │ ├── QuickLook/
│ │ │ ├── Icon.tiff
│ │ │ └── Thumbnail.tiff
│ │ ├── data/
│ │ │ ├── CAE20F62-9DFC-4BD2-AE79-BE620A2ADE55
│ │ │ ├── DF48A8C2-D105-49EF-BF24-87B87BAECAC7-OriginalContentSource
│ │ │ └── selectionForContentTransform/
│ │ │ ├── meta
│ │ │ └── shapeSelection/
│ │ │ ├── meta
│ │ │ └── path
│ │ └── metadata.info
│ └── Storage-Types.pxd/
│ ├── QuickLook/
│ │ ├── Icon.tiff
│ │ └── Thumbnail.tiff
│ ├── data/
│ │ ├── 460814B1-E0F9-4E2C-B4CA-32C0CD866260-OriginalContentSource
│ │ ├── A2CD8EA6-822D-4757-B972-8653B2AABD6B
│ │ ├── selection/
│ │ │ ├── meta
│ │ │ └── shapeSelection/
│ │ │ ├── meta
│ │ │ └── path
│ │ └── selectionForContentTransform/
│ │ ├── meta
│ │ └── shapeSelection/
│ │ ├── meta
│ │ └── path
│ └── metadata.info
├── LICENSE
├── Maccy/
│ ├── About.swift
│ ├── Accessibility.swift
│ ├── AppDelegate.swift
│ ├── AppStoreReview.swift
│ ├── ApplicationImage.swift
│ ├── ApplicationImageCache.swift
│ ├── Assets.xcassets/
│ │ ├── AppIcon.appiconset/
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── StatusBarMenuImage.imageset/
│ │ │ └── Contents.json
│ │ ├── clipboard.fill.imageset/
│ │ │ └── Contents.json
│ │ ├── paperclip.imageset/
│ │ │ └── Contents.json
│ │ └── scissors.imageset/
│ │ └── Contents.json
│ ├── Clipboard.swift
│ ├── ColorImage.swift
│ ├── Extensions/
│ │ ├── Collection+Surrounding.swift
│ │ ├── Color+Random.swift
│ │ ├── Defaults.Keys+Names.swift
│ │ ├── Dictionary+RemoveItem.swift
│ │ ├── KeyEquivalent+Keys.swift
│ │ ├── KeyboardShortcuts.Name+Shortcuts.swift
│ │ ├── ModifierFlags+Description.swift
│ │ ├── NSApplication+Windows.swift
│ │ ├── NSImage+Names.swift
│ │ ├── NSImage+Resized.swift
│ │ ├── NSPasteboard.PasteboardType+Types.swift
│ │ ├── NSPoint+DefaultsSerializable.swift
│ │ ├── NSRect+Centered.swift
│ │ ├── NSRunningApplication+WindowFrame.swift
│ │ ├── NSScreen+ForPopup.swift
│ │ ├── NSSize+DefaultsSerializable.swift
│ │ ├── NSSound+Named.swift
│ │ ├── NSWorkspace+ApplicationName.swift
│ │ ├── Sauce+KeyboardShortcuts.swift
│ │ ├── Settings.PaneIdentifier+Panes.swift
│ │ ├── String+Identifiable.swift
│ │ └── String+Shortened.swift
│ ├── FloatingPanel.swift
│ ├── GlobalHotKey.swift
│ ├── HighlightMatch.swift
│ ├── History.xcdatamodeld/
│ │ └── History.xcdatamodel/
│ │ └── contents
│ ├── HistoryItemAction.swift
│ ├── Info.plist
│ ├── Intents/
│ │ ├── AppIntentError.swift
│ │ ├── Clear.swift
│ │ ├── Delete.swift
│ │ ├── Get.swift
│ │ ├── HistoryItemAppEntity.swift
│ │ └── Select.swift
│ ├── ItemsProtocol.swift
│ ├── KeyChord.swift
│ ├── KeyShortcut.swift
│ ├── KeyboardLayout.swift
│ ├── Maccy.entitlements
│ ├── MaccyApp.swift
│ ├── MenuIcon.swift
│ ├── Models/
│ │ ├── HistoryItem.swift
│ │ └── HistoryItemContent.swift
│ ├── Notifier.swift
│ ├── Observables/
│ │ ├── AppState.swift
│ │ ├── Footer.swift
│ │ ├── FooterItem.swift
│ │ ├── History.swift
│ │ ├── HistoryItemDecorator.swift
│ │ ├── ModifierFlags.swift
│ │ ├── NavigationManager.swift
│ │ ├── Popup.swift
│ │ └── SlideoutController.swift
│ ├── PasteStack.swift
│ ├── PinsPosition.swift
│ ├── PopupPosition.swift
│ ├── Search.swift
│ ├── SearchVisibility.swift
│ ├── Selection.swift
│ ├── Settings/
│ │ ├── AdvancedSettingsPane.swift
│ │ ├── AppearanceSettingsPane.swift
│ │ ├── GeneralSettingsPane.swift
│ │ ├── IgnoreSettingsPane/
│ │ │ ├── IgnoreApplicationsSettingsView.swift
│ │ │ ├── IgnorePasteboardTypesSettingsView.swift
│ │ │ └── IgnoreRegexpsSettingsView.swift
│ │ ├── IgnoreSettingsPane.swift
│ │ ├── PinsSettingsPane.swift
│ │ ├── StorageSettingsPane.swift
│ │ ├── ar.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── be.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── bn.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── bs.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ca.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ckb.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── cs.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── de.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── el.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── en.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── eo.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── es.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── fa.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── fr.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── he.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── hi.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── hr.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── hu.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── id.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── it.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ja.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ko.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── lt.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── lv.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── nb.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── nl.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── pl.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── pt-BR.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── pt.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ro.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ru.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── sl.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── sv.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── ta.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── th.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── tr.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── uk.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── uz.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── vi.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ ├── zh-Hans.lproj/
│ │ │ ├── AdvancedSettings.strings
│ │ │ ├── AppearanceSettings.strings
│ │ │ ├── GeneralSettings.strings
│ │ │ ├── IgnoreSettings.strings
│ │ │ ├── PinsSettings.strings
│ │ │ └── StorageSettings.strings
│ │ └── zh-Hant.lproj/
│ │ ├── AdvancedSettings.strings
│ │ ├── AppearanceSettings.strings
│ │ ├── GeneralSettings.strings
│ │ ├── IgnoreSettings.strings
│ │ ├── PinsSettings.strings
│ │ └── StorageSettings.strings
│ ├── SoftwareUpdater.swift
│ ├── Sorter.swift
│ ├── Sounds/
│ │ ├── Knock.caf
│ │ └── Write.caf
│ ├── Storage.swift
│ ├── Storage.xcdatamodeld/
│ │ └── Storage.xcdatamodel/
│ │ └── contents
│ ├── Throttler.swift
│ ├── Views/
│ │ ├── AppImageView.swift
│ │ ├── AsyncView.swift
│ │ ├── ConfirmationView.swift
│ │ ├── ContentView.swift
│ │ ├── FooterItemView.swift
│ │ ├── FooterView.swift
│ │ ├── HeaderView.swift
│ │ ├── HeightReaderModifier.swift
│ │ ├── HistoryItemView.swift
│ │ ├── HistoryListView.swift
│ │ ├── HoverSelectionModifier.swift
│ │ ├── KeyHandlingView.swift
│ │ ├── KeyboardShortcutView.swift
│ │ ├── ListHeaderView.swift
│ │ ├── ListItemTitleView.swift
│ │ ├── ListItemView.swift
│ │ ├── MouseMovedViewModifer.swift
│ │ ├── MultipleSelectionListView.swift
│ │ ├── PasteStackItemView.swift
│ │ ├── PasteStackPreviewView.swift
│ │ ├── PasteStackView.swift
│ │ ├── PinsView.swift
│ │ ├── PreviewItemView.swift
│ │ ├── SearchFieldView.swift
│ │ ├── SlideoutContentView.swift
│ │ ├── SlideoutView.swift
│ │ ├── ToolbarView.swift
│ │ ├── VisualEffectView.swift
│ │ ├── WrappingTextView.swift
│ │ ├── ar.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── be.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── bn.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── bs.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ca.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ckb.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── cs.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── de.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── el.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── en.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── eo.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── es.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── fa.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── fr.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── he.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── hi.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── hr.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── hu.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── id.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── it.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ja.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ko.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── lt.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── lv.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── nb.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── nl.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── pl.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── pt-BR.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── pt.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ro.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ru.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── sl.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── sv.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── ta.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── th.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── tr.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── uk.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── uz.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── vi.lproj/
│ │ │ └── PreviewItemView.strings
│ │ ├── zh-Hans.lproj/
│ │ │ └── PreviewItemView.strings
│ │ └── zh-Hant.lproj/
│ │ └── PreviewItemView.strings
│ ├── ar.lproj/
│ │ └── Localizable.strings
│ ├── be.lproj/
│ │ └── Localizable.strings
│ ├── bn.lproj/
│ │ └── Localizable.strings
│ ├── bs.lproj/
│ │ └── Localizable.strings
│ ├── ca.lproj/
│ │ └── Localizable.strings
│ ├── ckb.lproj/
│ │ └── Localizable.strings
│ ├── cs.lproj/
│ │ ├── Localizable.strings
│ │ └── Preview.strings
│ ├── de.lproj/
│ │ └── Localizable.strings
│ ├── el.lproj/
│ │ └── Localizable.strings
│ ├── en.lproj/
│ │ └── Localizable.strings
│ ├── eo.lproj/
│ │ └── Localizable.strings
│ ├── es.lproj/
│ │ └── Localizable.strings
│ ├── fa.lproj/
│ │ └── Localizable.strings
│ ├── fr.lproj/
│ │ └── Localizable.strings
│ ├── he.lproj/
│ │ └── Localizable.strings
│ ├── hi.lproj/
│ │ └── Localizable.strings
│ ├── hr.lproj/
│ │ └── Localizable.strings
│ ├── hu.lproj/
│ │ └── Localizable.strings
│ ├── id.lproj/
│ │ └── Localizable.strings
│ ├── it.lproj/
│ │ └── Localizable.strings
│ ├── ja.lproj/
│ │ └── Localizable.strings
│ ├── ko.lproj/
│ │ └── Localizable.strings
│ ├── lt.lproj/
│ │ └── Localizable.strings
│ ├── lv.lproj/
│ │ └── Localizable.strings
│ ├── nb.lproj/
│ │ └── Localizable.strings
│ ├── nl.lproj/
│ │ └── Localizable.strings
│ ├── pl.lproj/
│ │ └── Localizable.strings
│ ├── pt-BR.lproj/
│ │ ├── Localizable.strings
│ │ └── Preview.strings
│ ├── pt.lproj/
│ │ └── Localizable.strings
│ ├── ro.lproj/
│ │ └── Localizable.strings
│ ├── ru.lproj/
│ │ └── Localizable.strings
│ ├── sl.lproj/
│ │ └── Localizable.strings
│ ├── sv.lproj/
│ │ └── Localizable.strings
│ ├── ta.lproj/
│ │ └── Localizable.strings
│ ├── th.lproj/
│ │ └── Localizable.strings
│ ├── tr.lproj/
│ │ └── Localizable.strings
│ ├── uk.lproj/
│ │ └── Localizable.strings
│ ├── uz.lproj/
│ │ └── Localizable.strings
│ ├── vi.lproj/
│ │ └── Localizable.strings
│ ├── zh-Hans.lproj/
│ │ └── Localizable.strings
│ └── zh-Hant.lproj/
│ └── Localizable.strings
├── Maccy.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ └── contents.xcworkspacedata
│ └── xcshareddata/
│ └── xcschemes/
│ └── Maccy.xcscheme
├── Maccy.xctestplan
├── MaccyTests/
│ ├── ClipboardTests.swift
│ ├── ColorImageTests.swift
│ ├── HistoryDecoratorTests.swift
│ ├── HistoryItemTests.swift
│ ├── HistoryTests.swift
│ ├── Info.plist
│ ├── SearchTests.swift
│ └── SorterTests.swift
├── MaccyUITests/
│ ├── Info.plist
│ └── MaccyUITests.swift
├── README.md
├── appcast.xml
└── docs/
└── keyboard-shortcut-password-fields.md
Condensed preview — 502 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (840K chars).
[
{
"path": ".bartycrouch.toml",
"chars": 807,
"preview": "[update]\ntasks = [\"interfaces\", \"translate\", \"normalize\"]\n\n[update.interfaces]\npaths = [\"Maccy\"]\nsubpathsToIgnore = [\".g"
},
{
"path": ".github/FUNDING.yml",
"chars": 24,
"preview": "buy_me_a_coffee: p0deje\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 1759,
"preview": "---\nname: 🐛 Bug Report\ndescription: Submit a Bug Report\nlabels: [bug]\nbody:\n - type: markdown\n attributes:\n val"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 27,
"preview": "blank_issues_enabled: true\n"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yml",
"chars": 1007,
"preview": "---\nname: 💪 Feature request\ndescription: Suggest an idea for this project\nlabels: [enhancement]\nbody:\n - type: checkbox"
},
{
"path": ".gitignore",
"chars": 93,
"preview": "xcuserdata/\nMaccy.xcodeproj/project.xcworkspace/xcshareddata\n.idea\n.DS_Store\nMaccy/.DS_Store\n"
},
{
"path": ".periphery.yml",
"chars": 88,
"preview": "project: Maccy.xcodeproj\nretain_objc_accessible: true\nschemes:\n- Maccy\ntargets:\n- Maccy\n"
},
{
"path": ".swiftlint.yml",
"chars": 147,
"preview": "disabled_rules:\n - multiple_closures_with_trailing_closure\n - non_optional_string_data_conversion\n - todo\nline_length"
},
{
"path": "Designs/Copies.txt",
"chars": 103,
"preview": "Hello! 👋\nThis is Maccy.\nClipboard manager for Mac. 💻\nSearch as you type. 🔍\nOpen source. ⚙️\nNo fluff! 😛\n"
},
{
"path": "Designs/Instructions.pxd/data/selectionForContentTransform/meta",
"chars": 802,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Designs/Instructions.pxd/data/selectionForContentTransform/shapeSelection/meta",
"chars": 319,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Designs/Storage-Types.pxd/data/selection/meta",
"chars": 737,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Designs/Storage-Types.pxd/data/selection/shapeSelection/meta",
"chars": 319,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Designs/Storage-Types.pxd/data/selectionForContentTransform/meta",
"chars": 737,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Designs/Storage-Types.pxd/data/selectionForContentTransform/shapeSelection/meta",
"chars": 319,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "LICENSE",
"chars": 1080,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2025 Alex Rodionov\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "Maccy/About.swift",
"chars": 1927,
"preview": "import Cocoa\n\nclass About {\n private let familyCredits = NSAttributedString(\n string: \"Special thank you to Tonia, A"
},
{
"path": "Maccy/Accessibility.swift",
"chars": 188,
"preview": "import AppKit\n\nstruct Accessibility {\n private static var allowed: Bool { AXIsProcessTrustedWithOptions(nil) }\n\n stati"
},
{
"path": "Maccy/AppDelegate.swift",
"chars": 5684,
"preview": "import Defaults\nimport KeyboardShortcuts\nimport Sparkle\nimport SwiftUI\n\nclass AppDelegate: NSObject, NSApplicationDelega"
},
{
"path": "Maccy/AppStoreReview.swift",
"chars": 597,
"preview": "import StoreKit\nimport Defaults\n\nclass AppStoreReview {\n class func ask() {\n Defaults[.numberOfUsages] += 1\n guar"
},
{
"path": "Maccy/ApplicationImage.swift",
"chars": 2371,
"preview": "import Defaults\nimport SwiftUI\n\nclass ApplicationImage {\n fileprivate static let fallbackImage = NSImage(\n systemSym"
},
{
"path": "Maccy/ApplicationImageCache.swift",
"chars": 898,
"preview": "class ApplicationImageCache {\n static let shared = ApplicationImageCache()\n\n private let universalClipboardIdentifier:"
},
{
"path": "Maccy/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1397,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"AppIcon (Big Sur)-16w.png\",\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\",\n "
},
{
"path": "Maccy/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Maccy/Assets.xcassets/StatusBarMenuImage.imageset/Contents.json",
"chars": 786,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"mac\",\n \"filename\" : \"DarkMenuBar-16w.png\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Maccy/Assets.xcassets/clipboard.fill.imageset/Contents.json",
"chars": 815,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"clipboard.fill.light_16.png\",\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\"\n "
},
{
"path": "Maccy/Assets.xcassets/paperclip.imageset/Contents.json",
"chars": 730,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"clip.fill.light_16.png\",\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Maccy/Assets.xcassets/scissors.imageset/Contents.json",
"chars": 791,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"scissors_light_16.png\",\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "Maccy/Clipboard.swift",
"chars": 10297,
"preview": "import AppKit\nimport Defaults\nimport Sauce\n\nclass Clipboard {\n static let shared = Clipboard()\n\n typealias OnNewCopyHo"
},
{
"path": "Maccy/ColorImage.swift",
"chars": 390,
"preview": "import AppKit\nimport SwiftHEXColors\n\nclass ColorImage {\n static func from(_ colorHex: String) -> NSImage? {\n guard l"
},
{
"path": "Maccy/Extensions/Collection+Surrounding.swift",
"chars": 2230,
"preview": "extension Collection where Element: Equatable {\n func item(after: Element, where predicate: (Element) -> Bool) -> Eleme"
},
{
"path": "Maccy/Extensions/Color+Random.swift",
"chars": 269,
"preview": "import SwiftUI\n\n// Useful to debug SwiftUI view redraws: .background(.random).\nextension ShapeStyle where Self == Color "
},
{
"path": "Maccy/Extensions/Defaults.Keys+Names.swift",
"chars": 3517,
"preview": "import AppKit\nimport Defaults\n\nstruct StorageType {\n static let files = StorageType(types: [.fileURL])\n static let ima"
},
{
"path": "Maccy/Extensions/Dictionary+RemoveItem.swift",
"chars": 276,
"preview": "extension Dictionary {\n // Removes all key-value pairs where the value satisfies the given predicate.\n mutating func r"
},
{
"path": "Maccy/Extensions/KeyEquivalent+Keys.swift",
"chars": 93,
"preview": "import SwiftUI\n\nextension KeyEquivalent {\n static let backspace = KeyEquivalent(\"\\u{7F}\")\n}\n"
},
{
"path": "Maccy/Extensions/KeyboardShortcuts.Name+Shortcuts.swift",
"chars": 416,
"preview": "import KeyboardShortcuts\n\nextension KeyboardShortcuts.Name {\n static let popup = Self(\"popup\", default: Shortcut(.c, mo"
},
{
"path": "Maccy/Extensions/ModifierFlags+Description.swift",
"chars": 621,
"preview": "import AppKit.NSEvent\nimport Carbon.HIToolbox\n\n// https://github.com/sindresorhus/KeyboardShortcuts/blob/e6b60117ec266e1"
},
{
"path": "Maccy/Extensions/NSApplication+Windows.swift",
"chars": 223,
"preview": "import AppKit\n\nextension NSApplication {\n var alertWindow: NSWindow? { windows.first { $0.className == \"_NSAlertPanel\" "
},
{
"path": "Maccy/Extensions/NSImage+Names.swift",
"chars": 921,
"preview": "import Cocoa\n\nextension NSImage {\n static let gearshape = NSImage(systemSymbolName: \"gearshape\", accessibilityDescripti"
},
{
"path": "Maccy/Extensions/NSImage+Resized.swift",
"chars": 886,
"preview": "import AppKit.NSImage\n\n// Based on https://stackoverflow.com/questions/73062803/resizing-nsimage-keeping-aspect-ratio-re"
},
{
"path": "Maccy/Extensions/NSPasteboard.PasteboardType+Types.swift",
"chars": 2155,
"preview": "import AppKit.NSPasteboard\nimport Defaults\n\nextension NSPasteboard.PasteboardType: Defaults.Serializable {\n static let "
},
{
"path": "Maccy/Extensions/NSPoint+DefaultsSerializable.swift",
"chars": 100,
"preview": "import CoreGraphics\nimport Defaults\nimport Foundation\n\nextension NSPoint: Defaults.Serializable {\n}\n"
},
{
"path": "Maccy/Extensions/NSRect+Centered.swift",
"chars": 355,
"preview": "import Foundation\n\nextension NSRect {\n static func centered(ofSize size: NSSize, in frame: NSRect) -> NSRect {\n let "
},
{
"path": "Maccy/Extensions/NSRunningApplication+WindowFrame.swift",
"chars": 1257,
"preview": "import AppKit.NSRunningApplication\nimport Carbon\n\nextension NSRunningApplication {\n var windowFrame: NSRect? {\n let "
},
{
"path": "Maccy/Extensions/NSScreen+ForPopup.swift",
"chars": 314,
"preview": "import AppKit.NSScreen\nimport Defaults\n\nextension NSScreen {\n static var forPopup: NSScreen? {\n let desiredScreen = "
},
{
"path": "Maccy/Extensions/NSSize+DefaultsSerializable.swift",
"chars": 99,
"preview": "import CoreGraphics\nimport Defaults\nimport Foundation\n\nextension NSSize: Defaults.Serializable {\n}\n"
},
{
"path": "Maccy/Extensions/NSSound+Named.swift",
"chars": 299,
"preview": "import AppKit.NSSound\n\nextension NSSound {\n static let knock = NSSound(\n contentsOf: Bundle.main.url(forResource: \"K"
},
{
"path": "Maccy/Extensions/NSWorkspace+ApplicationName.swift",
"chars": 455,
"preview": "import AppKit.NSWorkspace\n\nextension NSWorkspace {\n func applicationName(url: URL) -> String {\n if let bundle = Bund"
},
{
"path": "Maccy/Extensions/Sauce+KeyboardShortcuts.swift",
"chars": 280,
"preview": "import KeyboardShortcuts\nimport Sauce\n\nextension Sauce {\n func key(shortcut: KeyboardShortcuts.Name) -> Key? {\n if l"
},
{
"path": "Maccy/Extensions/Settings.PaneIdentifier+Panes.swift",
"chars": 289,
"preview": "import Settings\n\nextension Settings.PaneIdentifier {\n static let advanced = Self(\"advanced\")\n static let appearance = "
},
{
"path": "Maccy/Extensions/String+Identifiable.swift",
"chars": 79,
"preview": "extension String: @retroactive Identifiable {\n public var id: Self { self }\n}\n"
},
{
"path": "Maccy/Extensions/String+Shortened.swift",
"chars": 200,
"preview": "extension String {\n func shortened(to maxLength: Int) -> String {\n guard count > maxLength else {\n return self\n"
},
{
"path": "Maccy/FloatingPanel.swift",
"chars": 6580,
"preview": "import Defaults\nimport SwiftUI\n\n// An NSPanel subclass that implements floating panel traits.\n// https://stackoverflow.c"
},
{
"path": "Maccy/GlobalHotKey.swift",
"chars": 512,
"preview": "import AppKit\nimport KeyboardShortcuts\nimport Sauce\nimport SwiftUI\n\nclass GlobalHotKey {\n typealias Handler = () -> Voi"
},
{
"path": "Maccy/HighlightMatch.swift",
"chars": 757,
"preview": "import Foundation\nimport Defaults\n\nenum HighlightMatch: String, CaseIterable, Identifiable, CustomStringConvertible, Def"
},
{
"path": "Maccy/History.xcdatamodeld/History.xcdatamodel/contents",
"chars": 301,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVer"
},
{
"path": "Maccy/HistoryItemAction.swift",
"chars": 2990,
"preview": "import AppKit.NSEvent\nimport Defaults\n\nenum HistoryItemAction {\n case unknown\n case copy\n case paste\n case pasteWith"
},
{
"path": "Maccy/Info.plist",
"chars": 1276,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Maccy/Intents/AppIntentError.swift",
"chars": 254,
"preview": "import Foundation\n\nenum AppIntentError: Swift.Error, CustomLocalizedStringResourceConvertible {\n case notFound\n\n var l"
},
{
"path": "Maccy/Intents/Clear.swift",
"chars": 625,
"preview": "import AppIntents\nimport Defaults\n\nstruct Clear: AppIntent, CustomIntentMigratedAppIntent {\n static let intentClassName"
},
{
"path": "Maccy/Intents/Delete.swift",
"chars": 823,
"preview": "import AppIntents\n\nstruct Delete: AppIntent, CustomIntentMigratedAppIntent {\n static let intentClassName = \"DeleteInten"
},
{
"path": "Maccy/Intents/Get.swift",
"chars": 1896,
"preview": "import Foundation\nimport AppIntents\n\nstruct Get: AppIntent, CustomIntentMigratedAppIntent {\n static let intentClassName"
},
{
"path": "Maccy/Intents/HistoryItemAppEntity.swift",
"chars": 516,
"preview": "import AppIntents\n\nstruct HistoryItemAppEntity: TransientAppEntity {\n static var typeDisplayRepresentation = TypeDispla"
},
{
"path": "Maccy/Intents/Select.swift",
"chars": 1030,
"preview": "import AppIntents\n\nstruct Select: AppIntent, CustomIntentMigratedAppIntent {\n static let intentClassName = \"SelectInten"
},
{
"path": "Maccy/ItemsProtocol.swift",
"chars": 1406,
"preview": "protocol HasVisibility {\n var isVisible: Bool { get }\n}\n\nprotocol ItemsContainer {\n associatedtype Item\n var containe"
},
{
"path": "Maccy/KeyChord.swift",
"chars": 4434,
"preview": "import AppKit.NSEvent\nimport KeyboardShortcuts\nimport Sauce\n\nenum KeyChord: CaseIterable {\n static var pasteKey: Key { "
},
{
"path": "Maccy/KeyShortcut.swift",
"chars": 1269,
"preview": "import AppKit.NSEvent\nimport Defaults\nimport Sauce\n\nstruct KeyShortcut: Identifiable {\n static func create(character: S"
},
{
"path": "Maccy/KeyboardLayout.swift",
"chars": 701,
"preview": "import Carbon\nimport Sauce\n\nclass KeyboardLayout {\n static var current: KeyboardLayout { KeyboardLayout() }\n\n // Dvora"
},
{
"path": "Maccy/Maccy.entitlements",
"chars": 516,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Maccy/MaccyApp.swift",
"chars": 408,
"preview": "import SwiftUI\n\n@main\nstruct MaccyApp: App {\n @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate\n\n // It'"
},
{
"path": "Maccy/MenuIcon.swift",
"chars": 495,
"preview": "import AppKit\nimport Defaults\n\nenum MenuIcon: String, CaseIterable, Identifiable, Defaults.Serializable {\n case maccy\n "
},
{
"path": "Maccy/Models/HistoryItem.swift",
"chars": 7050,
"preview": "import AppKit\nimport Defaults\nimport Sauce\nimport SwiftData\nimport Vision\n\n@Model\nclass HistoryItem {\n static var suppo"
},
{
"path": "Maccy/Models/HistoryItemContent.swift",
"chars": 249,
"preview": "import Foundation\nimport SwiftData\n\n@Model\nclass HistoryItemContent {\n var type: String = \"\"\n var value: Data?\n\n @Rel"
},
{
"path": "Maccy/Notifier.swift",
"chars": 1185,
"preview": "import AppKit\nimport UserNotifications\n\nclass Notifier {\n private static var center: UNUserNotificationCenter { UNUserN"
},
{
"path": "Maccy/Observables/AppState.swift",
"chars": 5031,
"preview": "import AppKit\nimport Defaults\nimport Foundation\nimport Settings\nimport SwiftUI\n\n@Observable\nclass AppState: Sendable {\n "
},
{
"path": "Maccy/Observables/Footer.swift",
"chars": 2116,
"preview": "import Defaults\nimport SwiftUI\n\n@Observable\nclass Footer: ItemsContainer {\n var items: [FooterItem] = []\n\n var selecte"
},
{
"path": "Maccy/Observables/FooterItem.swift",
"chars": 1082,
"preview": "import SwiftUI\n\n@Observable\nclass FooterItem: Equatable, Identifiable, HasVisibility {\n struct Confirmation {\n var m"
},
{
"path": "Maccy/Observables/History.swift",
"chars": 14040,
"preview": "// swiftlint:disable file_length\nimport AppKit.NSRunningApplication\nimport Defaults\nimport Foundation\nimport Logging\nimp"
},
{
"path": "Maccy/Observables/HistoryItemDecorator.swift",
"chars": 5434,
"preview": "import AppKit.NSWorkspace\nimport Defaults\nimport Foundation\nimport Observation\nimport Sauce\n\n@Observable\nclass HistoryIt"
},
{
"path": "Maccy/Observables/ModifierFlags.swift",
"chars": 310,
"preview": "import AppKit.NSEvent\nimport Defaults\n\n@Observable\nclass ModifierFlags {\n var flags: NSEvent.ModifierFlags = []\n\n init"
},
{
"path": "Maccy/Observables/NavigationManager.swift",
"chars": 9230,
"preview": "import Foundation\nimport SwiftUI\n\n@Observable\nclass NavigationManager { // swiftlint:disable:this type_body_length\n pri"
},
{
"path": "Maccy/Observables/Popup.swift",
"chars": 5497,
"preview": "import AppKit.NSRunningApplication\nimport Defaults\nimport KeyboardShortcuts\nimport Observation\n\nenum PopupState {\n // D"
},
{
"path": "Maccy/Observables/SlideoutController.swift",
"chars": 6609,
"preview": "import Defaults\nimport Logging\nimport Observation\nimport SwiftUI\n\nenum SlideoutState {\n case opening\n case closing\n c"
},
{
"path": "Maccy/PasteStack.swift",
"chars": 1409,
"preview": "import Foundation\nimport AppKit\n\n@Observable\nclass PasteStack: Identifiable, Hashable {\n private static var listener: A"
},
{
"path": "Maccy/PinsPosition.swift",
"chars": 462,
"preview": "import Foundation\nimport Defaults\n\nenum PinsPosition: String, CaseIterable, Identifiable, CustomStringConvertible, Defau"
},
{
"path": "Maccy/PopupPosition.swift",
"chars": 2476,
"preview": "import AppKit.NSEvent\nimport Defaults\nimport Foundation\n\nenum PopupPosition: String, CaseIterable, Identifiable, CustomS"
},
{
"path": "Maccy/Search.swift",
"chars": 4027,
"preview": "import AppKit\nimport Defaults\nimport Fuse\n\nclass Search {\n enum Mode: String, CaseIterable, Identifiable, CustomStringC"
},
{
"path": "Maccy/SearchVisibility.swift",
"chars": 515,
"preview": "import Defaults\nimport Foundation\n\nenum SearchVisibility: String, CaseIterable, Identifiable, CustomStringConvertible, D"
},
{
"path": "Maccy/Selection.swift",
"chars": 632,
"preview": "import AppKit\n\nstruct Selection<Item: Equatable> {\n var items: [Item]\n\n init(items: [Item] = []) {\n self.items = it"
},
{
"path": "Maccy/Settings/AdvancedSettingsPane.swift",
"chars": 1701,
"preview": "import SwiftUI\nimport Defaults\n\nstruct AdvancedSettingsPane: View {\n var body: some View {\n VStack(alignment: .leadi"
},
{
"path": "Maccy/Settings/AppearanceSettingsPane.swift",
"chars": 7325,
"preview": "import AppKit\nimport SwiftUI\nimport Defaults\nimport Settings\n\nstruct AppearanceSettingsPane: View {\n @Default(.popupPos"
},
{
"path": "Maccy/Settings/GeneralSettingsPane.swift",
"chars": 4249,
"preview": "import SwiftUI\nimport Defaults\nimport KeyboardShortcuts\nimport LaunchAtLogin\nimport Settings\n\nstruct GeneralSettingsPane"
},
{
"path": "Maccy/Settings/IgnoreSettingsPane/IgnoreApplicationsSettingsView.swift",
"chars": 2528,
"preview": "import SwiftUI\nimport Defaults\n\nstruct IgnoreApplicationsSettingsView: View {\n @Default(.ignoredApps) private var ignor"
},
{
"path": "Maccy/Settings/IgnoreSettingsPane/IgnorePasteboardTypesSettingsView.swift",
"chars": 1792,
"preview": "import Defaults\nimport SwiftUI\n\nstruct IgnorePasteboardTypesSettingsView: View {\n @Default(.ignoredPasteboardTypes) pri"
},
{
"path": "Maccy/Settings/IgnoreSettingsPane/IgnoreRegexpsSettingsView.swift",
"chars": 1483,
"preview": "import SwiftUI\nimport Defaults\n\nstruct IgnoreRegexpsSettingsView: View {\n @Default(.ignoreRegexp) private var ignoredRe"
},
{
"path": "Maccy/Settings/IgnoreSettingsPane.swift",
"chars": 634,
"preview": "import SwiftUI\n\nstruct IgnoreSettingsPane: View {\n var body: some View {\n TabView {\n IgnoreApplicationsSettings"
},
{
"path": "Maccy/Settings/PinsSettingsPane.swift",
"chars": 5180,
"preview": "import SwiftData\nimport SwiftUI\n\nstruct PinPickerView: View {\n @Bindable var item: HistoryItem\n var availablePins: [St"
},
{
"path": "Maccy/Settings/StorageSettingsPane.swift",
"chars": 3683,
"preview": "import SwiftUI\nimport Defaults\nimport Settings\n\nstruct StorageSettingsPane: View {\n @Observable\n class ViewModel {\n "
},
{
"path": "Maccy/Settings/ar.lproj/AdvancedSettings.strings",
"chars": 834,
"preview": "\"Title\" = \"متقدم\";\n\"TurnOff\" = \"إيقاف\";\n\"TurnOffDescription\" = \"تجاهل جميع النسخ الجديدة مؤقتًا.\\nمن المرجح أن تستخدمه ب"
},
{
"path": "Maccy/Settings/ar.lproj/AppearanceSettings.strings",
"chars": 1746,
"preview": "\"Title\" = \"المظهر\";\n\"PopupAt\" = \"ظهور الإنبثاق عند:\";\n\"PopupAtCursor\" = \"المؤشر\";\n\"PopupAtMenuBarIcon\" = \"أيقونة القائمة"
},
{
"path": "Maccy/Settings/ar.lproj/GeneralSettings.strings",
"chars": 1150,
"preview": "\"Title\" = \"عام\";\n\"LaunchAtLogin\" = \"التشغيل عند تسجيل الدخول\";\n\"CheckForUpdates\" = \"التحقق من التحديثات تلقائيًا\";\n\"Chec"
},
{
"path": "Maccy/Settings/ar.lproj/IgnoreSettings.strings",
"chars": 744,
"preview": "\"Title\" = \"تجاهل\";\n\"ApplicationsTab\" = \"التطبيقات\";\n\"IgnoredAppsDescription\" = \"من الممكن تجاهل النسخ القادمة من بعض الت"
},
{
"path": "Maccy/Settings/ar.lproj/PinsSettings.strings",
"chars": 406,
"preview": "\"Title\" = \"مثبتة\";\n\"Key\" = \"المفتاح\";\n\"Alias\" = \"العنوان\";\n\"Content\" = \"المحتوى\";\n\"ContentIsNotText\" = \"محتوى غير قابل ل"
},
{
"path": "Maccy/Settings/ar.lproj/StorageSettings.strings",
"chars": 492,
"preview": "\"Title\" = \"التخزين\";\n\"Save\" = \"حفظ:\";\n\"Files\" = \"الملفات\";\n\"Images\" = \"الصور\";\n\"Text\" = \"النصوص\";\n\"SaveDescription\" = \"ت"
},
{
"path": "Maccy/Settings/be.lproj/AdvancedSettings.strings",
"chars": 980,
"preview": "\"Title\" = \"Прасунутыя\";\n\"TurnOff\" = \"Выключыць\";\n\"TurnOffShellScript\" = \"defaults write org.p0deje.Maccy ignoreEvents tr"
},
{
"path": "Maccy/Settings/be.lproj/AppearanceSettings.strings",
"chars": 1907,
"preview": "\"Title\" = \"Выгляд\";\n\"PopupAtMenuBarIcon\" = \"Значка меню\";\n\"PopupAtWindowCenter\" = \"Цэнтра акна\";\n\"PopupAtScreenCenter\" ="
},
{
"path": "Maccy/Settings/be.lproj/GeneralSettings.strings",
"chars": 1320,
"preview": "\"LaunchAtLogin\" = \"Запускаць пры логіне\";\n\"CheckNow\" = \"Праверыць зараз\";\n\"Open\" = \"Адчыніць:\";\n\"PinTooltip\" = \"Гарачая "
},
{
"path": "Maccy/Settings/be.lproj/IgnoreSettings.strings",
"chars": 818,
"preview": "\"IgnoredAppsDescription\" = \"Капіяванні з пэўных дадаткаў можна ігнараваць.\\nКалі ласка ўлічвайце, што гэтае вырашэнне не"
},
{
"path": "Maccy/Settings/be.lproj/PinsSettings.strings",
"chars": 465,
"preview": "\"Title\" = \"Замацаваныя\";\n\"Key\" = \"Клавіша\";\n\"Alias\" = \"Назва\";\n\"Content\" = \"Кантэнт\";\n\"ContentIsNotText\" = \"Нерэдагаваль"
},
{
"path": "Maccy/Settings/be.lproj/StorageSettings.strings",
"chars": 538,
"preview": "\"Save\" = \"Захоўваць:\";\n\"Files\" = \"Файлы\";\n\"Images\" = \"Выявы\";\n\"Text\" = \"Тэкст\";\n\"SaveDescription\" = \"Змяніць тыпы элемен"
},
{
"path": "Maccy/Settings/bn.lproj/AdvancedSettings.strings",
"chars": 974,
"preview": "\"ClearHistoryOnQuit\" = \"বন্ধ করে ছেড়ে যাওয়ার সময় ইতিহাস সাফ করুন\";\n\"ClearHistoryOnQuitTooltip\" = \"অ্যাপ্লিকেশনটি ছাড়ার "
},
{
"path": "Maccy/Settings/bn.lproj/AppearanceSettings.strings",
"chars": 901,
"preview": "\"Title\" = \"\";\n\"PopupAt\" = \"\";\n\"PopupAtCursor\" = \"\";\n\"PopupAtMenuBarIcon\" = \"\";\n\"PopupAtWindowCenter\" = \"\";\n\"PopupAtScree"
},
{
"path": "Maccy/Settings/bn.lproj/GeneralSettings.strings",
"chars": 1117,
"preview": "\"CheckForUpdates\" = \"স্বয়ংক্রিয়ভাবে আপডেটের জন্য সন্ধান করুন\";\n\"Title\" = \"সাধারণ\";\n\"LaunchAtLogin\" = \"লগইন করার সময় চ"
},
{
"path": "Maccy/Settings/bn.lproj/IgnoreSettings.strings",
"chars": 843,
"preview": "\"Title\" = \"উপেক্ষা করুন\";\n\"ApplicationsTab\" = \"অ্যাপ্লিকেশন\";\n\"IgnoredAppsDescription\" = \"নির্দিষ্ট অ্যাপ্লিকেশন থেকে আস"
},
{
"path": "Maccy/Settings/bn.lproj/PinsSettings.strings",
"chars": 296,
"preview": "\"Key\" = \"মূল শব্দ\";\n\"Alias\" = \"শিরোনাম\";\n\"PinCustomizationDescription\" = \"আপনি প্রতিটি আলপিন করা আইটেমের শিরোনাম এবং হটক"
},
{
"path": "Maccy/Settings/bn.lproj/StorageSettings.strings",
"chars": 256,
"preview": "\"Title\" = \"\";\n\"Save\" = \"\";\n\"Files\" = \"\";\n\"Images\" = \"\";\n\"Text\" = \"\";\n\"SaveDescription\" = \"\";\n\"Size\" = \"\";\n\"SizeTooltip\" "
},
{
"path": "Maccy/Settings/bs.lproj/AdvancedSettings.strings",
"chars": 942,
"preview": "\"Title\" = \"Napredno\";\n\"TurnOff\" = \"Ugasi\";\n\"TurnOffDescription\" = \"Privremeno zanemari sve nove kopije.\\nKoristi program"
},
{
"path": "Maccy/Settings/bs.lproj/AppearanceSettings.strings",
"chars": 1862,
"preview": "\"Title\" = \"Izgled\";\n\"PopupAt\" = \"Popup na:\";\n\"PopupAtCursor\" = \"Kursor\";\n\"PopupAtMenuBarIcon\" = \"Meni ikona\";\n\"PopupAtWi"
},
{
"path": "Maccy/Settings/bs.lproj/GeneralSettings.strings",
"chars": 1270,
"preview": "\"Title\" = \"Opšte\";\n\"LaunchAtLogin\" = \"Započni aplikacjiu pri prijavi\";\n\"CheckForUpdates\" = \"Provjeri dostupna ažuriranja"
},
{
"path": "Maccy/Settings/bs.lproj/IgnoreSettings.strings",
"chars": 859,
"preview": "\"Title\" = \"Ignoriši\";\n\"ApplicationsTab\" = \"Aplikacije\";\n\"IgnoredAppsDescription\" = \"Moguće je zanemariti kopije koje dol"
},
{
"path": "Maccy/Settings/bs.lproj/PinsSettings.strings",
"chars": 463,
"preview": "\"Title\" = \"Zakačke\";\n\"Key\" = \"Dugme\";\n\"Alias\" = \"Naziv\";\n\"Content\" = \"Sadržaj\";\n\"ContentIsNotText\" = \"Sadržaj koji se ne"
},
{
"path": "Maccy/Settings/bs.lproj/StorageSettings.strings",
"chars": 526,
"preview": "\"Title\" = \"Pohrana\";\n\"Save\" = \"Snimi:\";\n\"Files\" = \"Datoteke\";\n\"Images\" = \"Slike\";\n\"Text\" = \"Tekst\";\n\"SaveDescription\" = "
},
{
"path": "Maccy/Settings/ca.lproj/AdvancedSettings.strings",
"chars": 279,
"preview": "\"Title\" = \"\";\n\"TurnOff\" = \"\";\n\"TurnOffDescription\" = \"\";\n\"TurnOffShellScript\" = \"\";\n\"TurnOffViaMenuIconDescription\" = \"\""
},
{
"path": "Maccy/Settings/ca.lproj/AppearanceSettings.strings",
"chars": 901,
"preview": "\"Title\" = \"\";\n\"PopupAt\" = \"\";\n\"PopupAtCursor\" = \"\";\n\"PopupAtMenuBarIcon\" = \"\";\n\"PopupAtWindowCenter\" = \"\";\n\"PopupAtScree"
},
{
"path": "Maccy/Settings/ca.lproj/GeneralSettings.strings",
"chars": 420,
"preview": "\"Title\" = \"\";\n\"LaunchAtLogin\" = \"\";\n\"CheckForUpdates\" = \"\";\n\"CheckNow\" = \"\";\n\"Open\" = \"\";\n\"OpenTooltip\" = \"\";\n\"Pin\" = \"\""
},
{
"path": "Maccy/Settings/ca.lproj/IgnoreSettings.strings",
"chars": 261,
"preview": "\"Title\" = \"\";\n\"ApplicationsTab\" = \"\";\n\"IgnoredAppsDescription\" = \"\";\n\"IgnoredAllAppsExceptListed\" = \"\";\n\"PasteboardTypes"
},
{
"path": "Maccy/Settings/ca.lproj/PinsSettings.strings",
"chars": 145,
"preview": "\"Title\" = \"\";\n\"Key\" = \"\";\n\"Alias\" = \"\";\n\"Content\" = \"\";\n\"ContentIsNotText\" = \"\";\n\"RichTextEditWarning\" = \"\";\n\"PinCustomi"
},
{
"path": "Maccy/Settings/ca.lproj/StorageSettings.strings",
"chars": 526,
"preview": "\"Title\" = \"Emmagatzematge\";\n\"Save\" = \"Desa:\";\n\"Files\" = \"Arxius\";\n\"Images\" = \"Imatges\";\n\"Text\" = \"Text\";\n\"FirstCopiedAt\""
},
{
"path": "Maccy/Settings/ckb.lproj/AdvancedSettings.strings",
"chars": 971,
"preview": "\"Title\" = \"پێشکەوتوو\";\n\"TurnOff\" = \"ناچالاککردن\";\n\"TurnOffDescription\" = \"پشتگوێخستنی کاتیی هەموو کۆپییە نوێیەکان.\\nئەگە"
},
{
"path": "Maccy/Settings/ckb.lproj/AppearanceSettings.strings",
"chars": 1818,
"preview": "\"Title\" = \"ڕووکار\";\n\"PopupAt\" = \"شوێنی دەرکەوتنی پۆپئەپ:\";\n\"PopupAtCursor\" = \"نیشاندەر\";\n\"PopupAtMenuBarIcon\" = \"ئایکۆنی"
},
{
"path": "Maccy/Settings/ckb.lproj/GeneralSettings.strings",
"chars": 1291,
"preview": "\"Title\" = \"گشتی\";\n\"LaunchAtLogin\" = \"کارپێکردن لەکاتی چوونەژوورەوەدا\";\n\"CheckForUpdates\" = \"پشکنینی خۆکارانە بۆ نوێکاریی"
},
{
"path": "Maccy/Settings/ckb.lproj/IgnoreSettings.strings",
"chars": 868,
"preview": "\"Title\" = \"پشتگوێخستن\";\n\"ApplicationsTab\" = \"بەرنامەکان\";\n\"IgnoredAppsDescription\" = \"دەکرێت ئەو کۆپیانەی لە هەندێک بەرن"
},
{
"path": "Maccy/Settings/ckb.lproj/PinsSettings.strings",
"chars": 465,
"preview": "\"Title\" = \"چەسپێنراوەکان\";\n\"Key\" = \"دوگمە\";\n\"Alias\" = \"ناونیشان\";\n\"Content\" = \"ناوەڕۆک\";\n\"ContentIsNotText\" = \"ناوەڕۆکی "
},
{
"path": "Maccy/Settings/ckb.lproj/StorageSettings.strings",
"chars": 520,
"preview": "\"Title\" = \"کۆگا\";\n\"Save\" = \"پاشەکەوتکردن:\";\n\"Files\" = \"فایلەکان\";\n\"Images\" = \"وێنەکان\";\n\"Text\" = \"دەق\";\n\"SaveDescription"
},
{
"path": "Maccy/Settings/cs.lproj/AdvancedSettings.strings",
"chars": 916,
"preview": "\"Title\" = \"Pokročilé\";\n\"TurnOff\" = \"Vypnout\";\n\"TurnOffDescription\" = \"Dočasně ignorovat všechny nové kopie.\\nPravděpodob"
},
{
"path": "Maccy/Settings/cs.lproj/AppearanceSettings.strings",
"chars": 1782,
"preview": "\"Title\" = \"Vzhled\";\n\"PopupAt\" = \"Zobrazovat popup vedle:\";\n\"PopupAtCursor\" = \"Kurzoru\";\n\"PopupAtMenuBarIcon\" = \"Menu iko"
},
{
"path": "Maccy/Settings/cs.lproj/GeneralSettings.strings",
"chars": 1256,
"preview": "\"Title\" = \"Obecné\";\n\"LaunchAtLogin\" = \"Spustit při přihlášení\";\n\"CheckForUpdates\" = \"Automaticky kontrolovat aktualizace"
},
{
"path": "Maccy/Settings/cs.lproj/IgnoreSettings.strings",
"chars": 785,
"preview": "\"Title\" = \"Ignorovat\";\n\"ApplicationsTab\" = \"Aplikace\";\n\"IgnoredAppsDescription\" = \"Je možné ignorovat kopie z určitých a"
},
{
"path": "Maccy/Settings/cs.lproj/PinsSettings.strings",
"chars": 462,
"preview": "\"Title\" = \"Připnuté\";\n\"Key\" = \"Key\";\n\"Alias\" = \"Title\";\n\"Content\" = \"Obsah\";\n\"ContentIsNotText\" = \"Neupravitelný obsah ("
},
{
"path": "Maccy/Settings/cs.lproj/StorageSettings.strings",
"chars": 543,
"preview": "\"Title\" = \"Úložistě\";\n\"Save\" = \"Ukládat:\";\n\"Files\" = \"Soubory\";\n\"Images\" = \"Obrázky\";\n\"Text\" = \"Text\";\n\"SaveDescription\""
},
{
"path": "Maccy/Settings/de.lproj/AdvancedSettings.strings",
"chars": 1120,
"preview": "\"Title\" = \"Erweitert\";\n\"TurnOff\" = \"Deaktivieren\";\n\"TurnOffDescription\" = \"Vorrübergehend alle neue Einträge ignorieren."
},
{
"path": "Maccy/Settings/de.lproj/AppearanceSettings.strings",
"chars": 1964,
"preview": "\"Title\" = \"Erscheinungsbild\";\n\"PopupAt\" = \"Position des Popup-Fensters:\";\n\"PopupAtCursor\" = \"Mauszeiger\";\n\"PopupAtMenuBa"
},
{
"path": "Maccy/Settings/de.lproj/GeneralSettings.strings",
"chars": 1409,
"preview": "\"Title\" = \"Allgemein\";\n\"LaunchAtLogin\" = \"Beim Anmelden starten\";\n\"CheckForUpdates\" = \"Automatisch nach Updates suchen\";"
},
{
"path": "Maccy/Settings/de.lproj/IgnoreSettings.strings",
"chars": 930,
"preview": "\"Title\" = \"Ignorieren\";\n\"ApplicationsTab\" = \"Anwendungen\";\n\"IgnoredAppsDescription\" = \"Es ist möglich, kopierte Elemente"
},
{
"path": "Maccy/Settings/de.lproj/PinsSettings.strings",
"chars": 499,
"preview": "\"Title\" = \"Pins\";\n\"Key\" = \"Hotkey\";\n\"Alias\" = \"Titel\";\n\"Content\" = \"Inhalt\";\n\"ContentIsNotText\" = \"Nicht editierbarer In"
},
{
"path": "Maccy/Settings/de.lproj/StorageSettings.strings",
"chars": 578,
"preview": "\"Title\" = \"Speicher\";\n\"Save\" = \"Sichern:\";\n\"Files\" = \"Dateien\";\n\"Images\" = \"Bilder\";\n\"Text\" = \"Text\";\n\"SaveDescription\" "
},
{
"path": "Maccy/Settings/el.lproj/AdvancedSettings.strings",
"chars": 279,
"preview": "\"Title\" = \"\";\n\"TurnOff\" = \"\";\n\"TurnOffDescription\" = \"\";\n\"TurnOffShellScript\" = \"\";\n\"TurnOffViaMenuIconDescription\" = \"\""
},
{
"path": "Maccy/Settings/el.lproj/AppearanceSettings.strings",
"chars": 901,
"preview": "\"Title\" = \"\";\n\"PopupAt\" = \"\";\n\"PopupAtCursor\" = \"\";\n\"PopupAtMenuBarIcon\" = \"\";\n\"PopupAtWindowCenter\" = \"\";\n\"PopupAtScree"
},
{
"path": "Maccy/Settings/el.lproj/GeneralSettings.strings",
"chars": 420,
"preview": "\"Title\" = \"\";\n\"LaunchAtLogin\" = \"\";\n\"CheckForUpdates\" = \"\";\n\"CheckNow\" = \"\";\n\"Open\" = \"\";\n\"OpenTooltip\" = \"\";\n\"Pin\" = \"\""
},
{
"path": "Maccy/Settings/el.lproj/IgnoreSettings.strings",
"chars": 803,
"preview": "\"ApplicationsTab\" = \"Εφαρμογές\";\n\"IgnoredAppsDescription\" = \"Υπάρχει δυνατότητα να αγνοήσεις αντιγραφές που προέρχονται "
},
{
"path": "Maccy/Settings/el.lproj/PinsSettings.strings",
"chars": 145,
"preview": "\"Title\" = \"\";\n\"Key\" = \"\";\n\"Alias\" = \"\";\n\"Content\" = \"\";\n\"ContentIsNotText\" = \"\";\n\"RichTextEditWarning\" = \"\";\n\"PinCustomi"
},
{
"path": "Maccy/Settings/el.lproj/StorageSettings.strings",
"chars": 256,
"preview": "\"Title\" = \"\";\n\"Save\" = \"\";\n\"Files\" = \"\";\n\"Images\" = \"\";\n\"Text\" = \"\";\n\"SaveDescription\" = \"\";\n\"Size\" = \"\";\n\"SizeTooltip\" "
},
{
"path": "Maccy/Settings/en.lproj/AdvancedSettings.strings",
"chars": 907,
"preview": "\"Title\" = \"Advanced\";\n\"TurnOff\" = \"Turn off\";\n\"TurnOffDescription\" = \"Temporarily ignore all new copies.\\nYou are likely"
},
{
"path": "Maccy/Settings/en.lproj/AppearanceSettings.strings",
"chars": 1720,
"preview": "\"Title\" = \"Appearance\";\n\"PopupAt\" = \"Popup at:\";\n\"PopupAtCursor\" = \"Cursor\";\n\"PopupAtMenuBarIcon\" = \"Menu icon\";\n\"PopupA"
},
{
"path": "Maccy/Settings/en.lproj/GeneralSettings.strings",
"chars": 1217,
"preview": "\"Title\" = \"General\";\n\"LaunchAtLogin\" = \"Launch at login\";\n\"CheckForUpdates\" = \"Check for updates automatically\";\n\"CheckN"
},
{
"path": "Maccy/Settings/en.lproj/IgnoreSettings.strings",
"chars": 813,
"preview": "\"Title\" = \"Ignore\";\n\"ApplicationsTab\" = \"Applications\";\n\"IgnoredAppsDescription\" = \"It's possible to ignore copies comin"
},
{
"path": "Maccy/Settings/en.lproj/PinsSettings.strings",
"chars": 408,
"preview": "\"Title\" = \"Pins\";\n\"Key\" = \"Key\";\n\"Alias\" = \"Title\";\n\"Content\" = \"Content\";\n\"ContentIsNotText\" = \"Non-editable content (i"
},
{
"path": "Maccy/Settings/en.lproj/StorageSettings.strings",
"chars": 495,
"preview": "\"Title\" = \"Storage\";\n\"Save\" = \"Save:\";\n\"Files\" = \"Files\";\n\"Images\" = \"Images\";\n\"Text\" = \"Text\";\n\"SaveDescription\" = \"Cha"
},
{
"path": "Maccy/Settings/eo.lproj/AdvancedSettings.strings",
"chars": 840,
"preview": "\"Title\" = \"Altnivelaj agordoj\";\n\"TurnOff\" = \"Malŝalti\";\n\"TurnOffDescription\" = \"Provizore ignori ĉiujn novajn kopiojn.\\n"
},
{
"path": "Maccy/Settings/eo.lproj/AppearanceSettings.strings",
"chars": 1544,
"preview": "\"Title\" = \"Aspekto\";\n\"PopupAtLastPosition\" = \"Lasta pozicio\";\n\"SearchVisibilityAlways\" = \"Ĉiam\";\n\"SearchVisibilityDuring"
},
{
"path": "Maccy/Settings/eo.lproj/GeneralSettings.strings",
"chars": 1077,
"preview": "\"CheckNow\" = \"Kontroli nun\";\n\"Open\" = \"Malfermi:\";\n\"Pin\" = \"Fiksi:\";\n\"Delete\" = \"Forigi:\";\n\"Behavior\" = \"Konduto:\";\n\"Pas"
},
{
"path": "Maccy/Settings/eo.lproj/IgnoreSettings.strings",
"chars": 421,
"preview": "\"Title\" = \"Ignori\";\n\"ApplicationsTab\" = \"Aplikaĵoj\";\n\"IgnoredAppsDescription\" = \"\";\n\"IgnoredAllAppsExceptListed\" = \"Igno"
},
{
"path": "Maccy/Settings/eo.lproj/PinsSettings.strings",
"chars": 250,
"preview": "\"Title\" = \"Fiksitaj eroj\";\n\"Key\" = \"Klavo\";\n\"Alias\" = \"Titolo\";\n\"Content\" = \"Enhavo\";\n\"ContentIsNotText\" = \"Ne-redaktebl"
},
{
"path": "Maccy/Settings/eo.lproj/StorageSettings.strings",
"chars": 538,
"preview": "\"Text\" = \"Teksto\";\n\"Images\" = \"Bildoj\";\n\"Files\" = \"Dosieroj\";\n\"Save\" = \"Konservi:\";\n\"SortBy\" = \"Ordigi laŭ:\";\n\"Title\" = "
},
{
"path": "Maccy/Settings/es.lproj/AdvancedSettings.strings",
"chars": 1018,
"preview": "\"Title\" = \"Avanzado\";\n\"TurnOff\" = \"Desactivar\";\n\"TurnOffDescription\" = \"Ignorar temporalmente los nuevos elementos copia"
},
{
"path": "Maccy/Settings/es.lproj/AppearanceSettings.strings",
"chars": 2037,
"preview": "\"Title\" = \"Apariencia\";\n\"PopupAt\" = \"Ventana emergente en:\";\n\"PopupAtCursor\" = \"Cursor\";\n\"PopupAtMenuBarIcon\" = \"Icono d"
},
{
"path": "Maccy/Settings/es.lproj/GeneralSettings.strings",
"chars": 1476,
"preview": "\"Title\" = \"General\";\n\"LaunchAtLogin\" = \"Abrir al iniciar sesión\";\n\"CheckForUpdates\" = \"Buscar actualizaciones automática"
},
{
"path": "Maccy/Settings/es.lproj/IgnoreSettings.strings",
"chars": 835,
"preview": "\"Title\" = \"Exclusiones\";\n\"ApplicationsTab\" = \"Aplicaciones\";\n\"IgnoredAppsDescription\" = \"Es posible excluir la copia des"
},
{
"path": "Maccy/Settings/es.lproj/PinsSettings.strings",
"chars": 427,
"preview": "\"Title\" = \"Anclas\";\n\"Key\" = \"Clave\";\n\"Alias\" = \"Título\";\n\"Content\" = \"Contenido\";\n\"ContentIsNotText\" = \"Contenido no edi"
},
{
"path": "Maccy/Settings/es.lproj/StorageSettings.strings",
"chars": 560,
"preview": "\"Title\" = \"Almacenamiento\";\n\"Save\" = \"Almacenar:\";\n\"Files\" = \"Archivos\";\n\"Images\" = \"Imágenes\";\n\"Text\" = \"Texto\";\n\"SaveD"
},
{
"path": "Maccy/Settings/fa.lproj/AdvancedSettings.strings",
"chars": 783,
"preview": "\"Title\" = \"پیشرفته\";\n\"TurnOff\" = \"خاموش کردن\";\n\"TurnOffDescription\" = \"برای مدتی همهٔ کپیهای جدید را نادیده بگیرید.\\nاح"
},
{
"path": "Maccy/Settings/fa.lproj/AppearanceSettings.strings",
"chars": 975,
"preview": "\"Title\" = \"ظاهر\";\n\"PopupAt\" = \"مکان باز شدن پنجره:\";\n\"PopupAtCursor\" = \"مکاننما (Cursor)\";\n\"PopupAtMenuBarIcon\" = \"آیکو"
},
{
"path": "Maccy/Settings/fa.lproj/GeneralSettings.strings",
"chars": 1000,
"preview": "\"Title\" = \"عمومی\";\n\"CheckForUpdates\" = \"بررسی خودکار به روز رسانی\";\n\"CheckNow\" = \"بررسی کن\";\n\"Open\" = \"باز:\";\n\"LaunchAtL"
},
{
"path": "Maccy/Settings/fa.lproj/IgnoreSettings.strings",
"chars": 835,
"preview": "\"Title\" = \"نادیده گرفتن\";\n\"ApplicationsTab\" = \"اپلیکیشن\";\n\"IgnoredAppsDescription\" = \"میتوانید کپیهایی را که از بعضی ب"
},
{
"path": "Maccy/Settings/fa.lproj/PinsSettings.strings",
"chars": 144,
"preview": "\"Title\" = \"\";\n\"Key\" = \"\";\n\"Alias\" = \"\";\n\"Content\" = \"\";\n\"ContentIsNotText\" = \"\";\n\"RichTextEditWarning\" = \"\";\n\"PinCustomi"
},
{
"path": "Maccy/Settings/fa.lproj/StorageSettings.strings",
"chars": 336,
"preview": "\"Title\" = \"حافظه\";\n\"Save\" = \"ذخیره:\";\n\"Files\" = \"فایلها\";\n\"Images\" = \"تصاویر\";\n\"Text\" = \"متن\";\n\"SaveDescription\" = \"تعی"
},
{
"path": "Maccy/Settings/fr.lproj/AdvancedSettings.strings",
"chars": 1045,
"preview": "\"Title\" = \"Avancé\";\n\"TurnOff\" = \"Désactiver\";\n\"TurnOffDescription\" = \"Ignorer temporairement toute nouvelle copie.\\nVous"
},
{
"path": "Maccy/Settings/fr.lproj/AppearanceSettings.strings",
"chars": 2074,
"preview": "\"Title\" = \"Apparence\";\n\"PopupAt\" = \"Popup sur :\";\n\"PopupAtCursor\" = \"Curseur\";\n\"PopupAtMenuBarIcon\" = \"Icône du menu\";\n\""
},
{
"path": "Maccy/Settings/fr.lproj/GeneralSettings.strings",
"chars": 1520,
"preview": "\"Title\" = \"Général\";\n\"LaunchAtLogin\" = \"Lancer au démarrage\";\n\"CheckForUpdates\" = \"Vérifier les mises à jour automatique"
},
{
"path": "Maccy/Settings/fr.lproj/IgnoreSettings.strings",
"chars": 926,
"preview": "\"Title\" = \"Ignorer\";\n\"ApplicationsTab\" = \"Applications\";\n\"IgnoredAppsDescription\" = \"Il est possible d’ignorer les copie"
},
{
"path": "Maccy/Settings/fr.lproj/PinsSettings.strings",
"chars": 495,
"preview": "\"Title\" = \"Épingles\";\n\"Key\" = \"Clé\";\n\"Alias\" = \"Titre\";\n\"Content\" = \"Contenu\";\n\"ContentIsNotText\" = \"Contenu non modifia"
},
{
"path": "Maccy/Settings/fr.lproj/StorageSettings.strings",
"chars": 547,
"preview": "\"Title\" = \"Stockage\";\n\"Save\" = \"Sauver :\";\n\"Files\" = \"Fichiers\";\n\"Images\" = \"Images\";\n\"Text\" = \"Textes\";\n\"SaveDescriptio"
},
{
"path": "Maccy/Settings/he.lproj/AdvancedSettings.strings",
"chars": 870,
"preview": "\"Title\" = \"מתקדם\";\n\"TurnOff\" = \"כיבוי\";\n\"TurnOffDescription\" = \"להתעלם זמנית מכל העותקים החדשים.\\nכנראה שזה ישמש אותך במ"
},
{
"path": "Maccy/Settings/he.lproj/AppearanceSettings.strings",
"chars": 1681,
"preview": "\"Title\" = \"מראה\";\n\"PopupAt\" = \"הקפצת חלונית ב־:\";\n\"PopupAtCursor\" = \"סמן\";\n\"PopupAtMenuBarIcon\" = \"סמל תפריט\";\n\"PopupAtW"
},
{
"path": "Maccy/Settings/he.lproj/GeneralSettings.strings",
"chars": 1139,
"preview": "\"Title\" = \"כללי\";\n\"LaunchAtLogin\" = \"להפעיל עם הכניסה למערכת\";\n\"CheckForUpdates\" = \"לבדוק אוטומטית אם יש עדכונים\";\n\"Chec"
},
{
"path": "Maccy/Settings/he.lproj/IgnoreSettings.strings",
"chars": 692,
"preview": "\"Title\" = \"התעלמות\";\n\"ApplicationsTab\" = \"יישומים\";\n\"IgnoredAppsDescription\" = \"אפשר להתעלם מעותקים שמגיעים מיישומים מסו"
},
{
"path": "Maccy/Settings/he.lproj/PinsSettings.strings",
"chars": 376,
"preview": "\"Title\" = \"נעוצים\";\n\"Key\" = \"מקש קישור\";\n\"Alias\" = \"כותרת\";\n\"Content\" = \"תוכן\";\n\"ContentIsNotText\" = \"תוכן שאינו ניתן לע"
},
{
"path": "Maccy/Settings/he.lproj/StorageSettings.strings",
"chars": 474,
"preview": "\"Title\" = \"אחסון\";\n\"Save\" = \"שמירה:\";\n\"Files\" = \"קבצים\";\n\"Images\" = \"תמונות\";\n\"Text\" = \"טקסט\";\n\"SaveDescription\" = \"לשנו"
},
{
"path": "Maccy/Settings/hi.lproj/AdvancedSettings.strings",
"chars": 279,
"preview": "\"Title\" = \"\";\n\"TurnOff\" = \"\";\n\"TurnOffDescription\" = \"\";\n\"TurnOffShellScript\" = \"\";\n\"TurnOffViaMenuIconDescription\" = \"\""
},
{
"path": "Maccy/Settings/hi.lproj/AppearanceSettings.strings",
"chars": 901,
"preview": "\"Title\" = \"\";\n\"PopupAt\" = \"\";\n\"PopupAtCursor\" = \"\";\n\"PopupAtMenuBarIcon\" = \"\";\n\"PopupAtWindowCenter\" = \"\";\n\"PopupAtScree"
},
{
"path": "Maccy/Settings/hi.lproj/GeneralSettings.strings",
"chars": 427,
"preview": "\"Title\" = \"सामान्य\";\n\"LaunchAtLogin\" = \"\";\n\"CheckForUpdates\" = \"\";\n\"CheckNow\" = \"\";\n\"Open\" = \"\";\n\"OpenTooltip\" = \"\";\n\"Pi"
},
{
"path": "Maccy/Settings/hi.lproj/IgnoreSettings.strings",
"chars": 261,
"preview": "\"Title\" = \"\";\n\"ApplicationsTab\" = \"\";\n\"IgnoredAppsDescription\" = \"\";\n\"IgnoredAllAppsExceptListed\" = \"\";\n\"PasteboardTypes"
},
{
"path": "Maccy/Settings/hi.lproj/PinsSettings.strings",
"chars": 145,
"preview": "\"Title\" = \"\";\n\"Key\" = \"\";\n\"Alias\" = \"\";\n\"Content\" = \"\";\n\"ContentIsNotText\" = \"\";\n\"RichTextEditWarning\" = \"\";\n\"PinCustomi"
},
{
"path": "Maccy/Settings/hi.lproj/StorageSettings.strings",
"chars": 256,
"preview": "\"Title\" = \"\";\n\"Save\" = \"\";\n\"Files\" = \"\";\n\"Images\" = \"\";\n\"Text\" = \"\";\n\"SaveDescription\" = \"\";\n\"Size\" = \"\";\n\"SizeTooltip\" "
},
{
"path": "Maccy/Settings/hr.lproj/AdvancedSettings.strings",
"chars": 974,
"preview": "\"Title\" = \"Napredno\";\n\"TurnOff\" = \"Isključi\";\n\"TurnOffDescription\" = \"Privremeno zanemari sve nove kopije.\\nVjerojatno ć"
},
{
"path": "Maccy/Settings/hr.lproj/AppearanceSettings.strings",
"chars": 1885,
"preview": "\"Title\" = \"Izgled\";\n\"PopupAt\" = \"Skočni prozor:\";\n\"PopupAtCursor\" = \"Kursor\";\n\"PopupAtMenuBarIcon\" = \"Ikona u izborniku\""
},
{
"path": "Maccy/Settings/hr.lproj/GeneralSettings.strings",
"chars": 1379,
"preview": "\"Title\" = \"Opće\";\n\"LaunchAtLogin\" = \"Pokreni nakon prijave\";\n\"CheckForUpdates\" = \"Automatski provjeri aktualnost\";\n\"Chec"
},
{
"path": "Maccy/Settings/hr.lproj/IgnoreSettings.strings",
"chars": 823,
"preview": "\"Title\" = \"Zanemari\";\n\"ApplicationsTab\" = \"Programi\";\n\"IgnoredAppsDescription\" = \"Moguće je zanemariti kopije koje dolaz"
},
{
"path": "Maccy/Settings/hr.lproj/PinsSettings.strings",
"chars": 463,
"preview": "\"Title\" = \"Pribadače\";\n\"Key\" = \"Tipka\";\n\"Alias\" = \"Naslov\";\n\"Content\" = \"Sadržaj\";\n\"ContentIsNotText\" = \"Sadržaj koji se"
},
{
"path": "Maccy/Settings/hr.lproj/StorageSettings.strings",
"chars": 540,
"preview": "\"Title\" = \"Skladištenje\";\n\"Save\" = \"Spremi:\";\n\"Files\" = \"Datoteke\";\n\"Images\" = \"Slike\";\n\"Text\" = \"Tekst\";\n\"SaveDescripti"
}
]
// ... and 302 more files (download for full content)
About this extraction
This page contains the full source code of the p0deje/Maccy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 502 files (31.3 MB), approximately 243.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.