Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
622e8f6
Resize visible bar when height changes
alvst Jul 24, 2026
ae6b9c2
Hide sync control when sync is disabled
alvst Jul 24, 2026
043e1ad
Use sidebar navigation in Settings
alvst Jul 24, 2026
c844554
Add Quick Look previews for clips
alvst Jul 24, 2026
17929d7
Add optional Pesty bar resize handle
alvst Jul 24, 2026
30cfff4
Render text and links richly in Quick Look
alvst Jul 24, 2026
ac624e1
Add configurable clip previews
alvst Jul 24, 2026
167378f
Configure quick paste modifiers
alvst Jul 24, 2026
545a241
Add count and time history retention
alvst Jul 24, 2026
3a2028a
Add click-outside hide preference
alvst Jul 24, 2026
f60114f
Add menu bar visibility preference
alvst Jul 24, 2026
9efdab2
Add a Quit action to Settings
alvst Jul 24, 2026
b26d657
Use native switches in Settings
alvst Jul 24, 2026
59e94d4
Exclude selected apps from clipboard history
alvst Jul 24, 2026
0bdcc4a
Merge branch 'focused/pr-01-live-bar-resize' into focused/pr-31-setti…
alvst Jul 24, 2026
04c19b4
Merge branch 'focused/pr-02-settings-sidebar' into focused/pr-31-sett…
alvst Jul 24, 2026
ae35513
Merge branch 'focused/pr-03-hide-disabled-sync' into focused/pr-31-se…
alvst Jul 24, 2026
1f3216f
Merge branch 'focused/pr-06-quick-look-preview' into focused/pr-31-se…
alvst Jul 24, 2026
7df6631
Merge branch 'focused/pr-18-history-retention' into focused/pr-31-set…
alvst Jul 24, 2026
377ea01
Merge branch 'focused/pr-19-quick-paste-modifiers' into focused/pr-31…
alvst Jul 24, 2026
6777131
Merge branch 'focused/pr-22-click-outside' into focused/pr-31-setting…
alvst Jul 24, 2026
ebc35b8
Merge branch 'focused/pr-24-menu-bar-visibility' into focused/pr-31-s…
alvst Jul 24, 2026
2b5e0df
Merge branch 'focused/pr-25-settings-quit' into focused/pr-31-setting…
alvst Jul 24, 2026
7610e86
Merge branch 'focused/pr-26-settings-switches' into focused/pr-31-set…
alvst Jul 24, 2026
f53d1fa
Merge branch 'focused/pr-27-ignored-source-apps' into focused/pr-31-s…
alvst Jul 24, 2026
f882692
Polish Settings layout and organization
alvst Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 53 additions & 10 deletions Sources/Pesty/AppController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ final class AppController: NSObject, NSApplicationDelegate {
HotKeyCenter.shared.onTrigger = { [weak self] in self?.toggleBar() }
HotKeyCenter.shared.start()

setupStatusItem()
setMenuBarIconVisible(Settings.shared.showMenuBarIcon)

if Settings.shared.launchAtLogin { LaunchAtLogin.set(enabled: true) }

Expand Down Expand Up @@ -63,6 +63,7 @@ final class AppController: NSObject, NSApplicationDelegate {
}

private func setupStatusItem() {
guard statusItem == nil else { return }
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = item.button {
button.image = NSImage(systemSymbolName: "doc.on.clipboard", accessibilityDescription: "Pesty")
Expand All @@ -82,6 +83,15 @@ final class AppController: NSObject, NSApplicationDelegate {
statusItem = item
}

func setMenuBarIconVisible(_ visible: Bool) {
if visible {
setupStatusItem()
} else if let item = statusItem {
NSStatusBar.system.removeStatusItem(item)
statusItem = nil
}
}

@objc private func menuOpen() { showBar() }
@objc private func menuSettings() { showSettings() }
@objc private func menuClear() { store.clearHistory() }
Expand Down Expand Up @@ -136,7 +146,9 @@ final class AppController: NSObject, NSApplicationDelegate {
}
store.searchText = ""
store.source = .history
store.applyHistoryPolicy()
store.selectFirst()
store.inlinePreviewVisible = false

if barController == nil {
barController = BarWindowController()
Expand All @@ -147,18 +159,23 @@ final class AppController: NSObject, NSApplicationDelegate {

func hideBar() {
stopKeyMonitor()
store.inlinePreviewVisible = false
barController?.hide()
}

func pasteSelected() {
func resizeVisibleBar(to height: Double) {
barController?.resize(to: CGFloat(height))
}

func pasteSelected(asPlainText: Bool = false) {
guard let item = store.selectedItem else { return }
hideBar()
PasteService.paste(item, into: previousApp, monitor: monitor)
PasteService.paste(item, into: previousApp, monitor: monitor, asPlainText: asPlainText)
}

func pasteItem(_ item: ClipItem) {
func pasteItem(_ item: ClipItem, asPlainText: Bool = false) {
hideBar()
PasteService.paste(item, into: previousApp, monitor: monitor)
PasteService.paste(item, into: previousApp, monitor: monitor, asPlainText: asPlainText)
}

func copyItem(_ item: ClipItem) {
Expand All @@ -178,7 +195,7 @@ final class AppController: NSObject, NSApplicationDelegate {
let win = NSWindow(contentViewController: host)
win.title = "Pesty Settings"
win.styleMask = [.titled, .closable, .miniaturizable]
win.setContentSize(NSSize(width: 520, height: 560))
win.setContentSize(NSSize(width: 760, height: 680))
win.center()
win.isReleasedWhenClosed = false
settingsWindow = win
Expand All @@ -204,23 +221,34 @@ final class AppController: NSObject, NSApplicationDelegate {
let ctrl = flags.contains(.control)
let opt = flags.contains(.option)

if cmd, let chars = event.charactersIgnoringModifiers, let n = Int(chars), (1...9).contains(n) {
if includes(Settings.shared.quickPasteModifier, in: flags),
let chars = event.charactersIgnoringModifiers,
let n = Int(chars), (1...9).contains(n) {
let items = store.visibleItems
if n <= items.count { pasteItem(items[n - 1]) }
if n <= items.count {
pasteItem(items[n - 1], asPlainText: includes(Settings.shared.plainTextModifier, in: flags))
}
return nil
}

switch code {
case kVK_Space where store.searchText.isEmpty:
if Settings.shared.clipPreviewStyle == .nativeQuickLook {
QuickLookService.shared.toggle(items: store.visibleItems, selectedID: store.selectedID)
} else if store.selectedItem != nil {
store.inlinePreviewVisible.toggle()
}
return nil
case kVK_Escape:
if !store.searchText.isEmpty { store.searchText = ""; store.selectFirst() }
else { hideBar() }
return nil
case kVK_Return, kVK_ANSI_KeypadEnter:
pasteSelected(); return nil
case kVK_LeftArrow, kVK_UpArrow:
store.moveSelection(by: -1); return nil
moveBarSelection(by: -1); return nil
case kVK_RightArrow, kVK_DownArrow:
store.moveSelection(by: 1); return nil
moveBarSelection(by: 1); return nil
case kVK_Delete:
if cmd, let sel = store.selectedItem { store.delete(sel); return nil }
if !store.searchText.isEmpty {
Expand All @@ -244,6 +272,21 @@ final class AppController: NSObject, NSApplicationDelegate {
}
return event
}

private func moveBarSelection(by delta: Int) {
store.moveSelection(by: delta)
QuickLookService.shared.updateSelection(selectedID: store.selectedID)
}

private func includes(_ carbonModifier: Int, in flags: NSEvent.ModifierFlags) -> Bool {
switch carbonModifier {
case cmdKey: return flags.contains(.command)
case optionKey: return flags.contains(.option)
case controlKey: return flags.contains(.control)
case shiftKey: return flags.contains(.shift)
default: return false
}
}
}

extension Bundle {
Expand Down
1 change: 1 addition & 0 deletions Sources/Pesty/Monitor/ClipboardMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ final class ClipboardMonitor {
}
let bundleID = src?.bundleIdentifier
let appName = src?.localizedName
guard !Settings.shared.isIgnoringSourceApp(bundleID) else { return nil }

func decorate(_ item: inout ClipItem) {
item.sourceBundleID = bundleID
Expand Down
14 changes: 11 additions & 3 deletions Sources/Pesty/Monitor/PasteService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import Carbon.HIToolbox
enum PasteService {

@discardableResult
static func copy(_ item: ClipItem, to pasteboard: NSPasteboard = .general) -> Int {
static func copy(_ item: ClipItem,
to pasteboard: NSPasteboard = .general,
asPlainText: Bool = false) -> Int {
if asPlainText, let text = item.text ?? item.colorHex {
pasteboard.clearContents()
pasteboard.setString(text, forType: .string)
return pasteboard.changeCount
}
if item.type == .image {
guard let img = ClipboardStore.shared.loadImage(for: item) else {
return pasteboard.changeCount
Expand Down Expand Up @@ -38,8 +45,9 @@ enum PasteService {

static func paste(_ item: ClipItem,
into targetApp: NSRunningApplication?,
monitor: ClipboardMonitor) {
let change = copy(item)
monitor: ClipboardMonitor,
asPlainText: Bool = false) {
let change = copy(item, asPlainText: asPlainText)
monitor.suppressUntilChangeCount = change
if Settings.shared.playSound { NSSound(named: "Pop")?.play() }

Expand Down
Loading