Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ The build is signed with a Developer ID and notarized by Apple, so it opens with
| Key | Action |
| --- | --- |
| `⌘⇧V` | Show / hide the strip (configurable) |
| `⌘⇧S` (with strip open) | Open Settings |
| `⌘⇧P` (with strip open) | Pause / resume clipboard monitoring |
| `←` `→` `↑` `↓` | Move selection |
| `return` | Paste selected clip |
| `⌘1`–`⌘9` | Quick-paste the Nth clip |
Expand Down
46 changes: 42 additions & 4 deletions Sources/Pesty/AppController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ final class AppController: NSObject, NSApplicationDelegate {

private var barController: BarWindowController?
private var statusItem: NSStatusItem?
private var pauseMenuItem: NSMenuItem?
private var settingsWindow: NSWindow?
private var keyMonitor: Any?

Expand Down Expand Up @@ -64,15 +65,15 @@ final class AppController: NSObject, NSApplicationDelegate {

private func setupStatusItem() {
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = item.button {
button.image = NSImage(systemSymbolName: "doc.on.clipboard", accessibilityDescription: "Pesty")
button.image?.isTemplate = true
}
updateStatusItemIcon(item)
let menu = NSMenu()
menu.addItem(withTitle: "Open Pesty \(Settings.shared.hotkeyDisplay)",
action: #selector(menuOpen), keyEquivalent: "").target = self
menu.addItem(.separator())
menu.addItem(withTitle: "Settings…", action: #selector(menuSettings), keyEquivalent: ",").target = self
let pause = menu.addItem(withTitle: "Pause Pesty", action: #selector(menuTogglePause), keyEquivalent: "")
pause.target = self
pauseMenuItem = pause
menu.addItem(withTitle: "Clear History", action: #selector(menuClear), keyEquivalent: "").target = self
menu.addItem(.separator())
let about = menu.addItem(withTitle: "About Pesty", action: #selector(menuAbout), keyEquivalent: "")
Expand All @@ -85,9 +86,23 @@ final class AppController: NSObject, NSApplicationDelegate {
@objc private func menuOpen() { showBar() }
@objc private func menuSettings() { showSettings() }
@objc private func menuClear() { store.clearHistory() }
@objc private func menuTogglePause() { togglePestyPause() }
@objc private func menuQuit() { NSApp.terminate(nil) }
@objc private func menuAbout() { showAbout() }

func togglePestyPause() {
monitor.togglePause()
pauseMenuItem?.title = monitor.isPaused ? "Resume Pesty" : "Pause Pesty"
if let item = statusItem { updateStatusItemIcon(item) }
}

private func updateStatusItemIcon(_ item: NSStatusItem) {
item.button?.image = NSImage(
systemSymbolName: monitor.isPaused ? "pause.circle" : "doc.on.clipboard",
accessibilityDescription: "Pesty")
item.button?.image?.isTemplate = true
}

func showAbout() {
NSApp.activate(ignoringOtherApps: true)
NSApp.orderFrontStandardAboutPanel(options: [
Expand Down Expand Up @@ -185,6 +200,27 @@ final class AppController: NSObject, NSApplicationDelegate {
win.makeKeyAndOrderFront(nil)
}

/// Handles commands that only apply while the Paste Bar owns keyboard focus.
/// The panel's key-equivalent path calls this before SwiftUI receives command keys.
func handleBarCommandShortcut(_ event: NSEvent) -> Bool {
guard barController?.window?.isKeyWindow == true else { return false }

let flags = event.modifierFlags
guard flags.contains(.command), flags.contains(.shift),
!flags.contains(.control), !flags.contains(.option) else { return false }

switch Int(event.keyCode) {
case kVK_ANSI_S:
showSettings()
return true
case kVK_ANSI_P:
togglePestyPause()
return true
default:
return false
}
}

private func startKeyMonitor() {
stopKeyMonitor()
keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
Expand All @@ -198,6 +234,8 @@ final class AppController: NSObject, NSApplicationDelegate {
}

private func handleKey(_ event: NSEvent) -> NSEvent? {
if handleBarCommandShortcut(event) { return nil }

let code = Int(event.keyCode)
let flags = event.modifierFlags
let cmd = flags.contains(.command)
Expand Down
4 changes: 4 additions & 0 deletions Sources/Pesty/Monitor/ClipboardMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ final class ClipboardMonitor {
private var timer: Timer?

var suppressUntilChangeCount: Int = -1
private(set) var isPaused = false

init() {
lastChangeCount = pasteboard.changeCount
Expand All @@ -24,10 +25,13 @@ final class ClipboardMonitor {

func stop() { timer?.invalidate(); timer = nil }

func togglePause() { isPaused.toggle() }

private func poll() {
let current = pasteboard.changeCount
guard current != lastChangeCount else { return }
lastChangeCount = current
guard !isPaused else { return }
if current == suppressUntilChangeCount { return }
guard let item = makeItem() else { return }
ClipboardStore.shared.addCaptured(item)
Expand Down
7 changes: 7 additions & 0 deletions Sources/Pesty/UI/BarWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import SwiftUI
final class BarPanel: NSPanel {
override var canBecomeKey: Bool { true }
override var canBecomeMain: Bool { false }

override func performKeyEquivalent(with event: NSEvent) -> Bool {
if AppController.shared.handleBarCommandShortcut(event) {
return true
}
return super.performKeyEquivalent(with: event)
}
}

@MainActor
Expand Down