From 815eb961b68e1dbdd161c6c8ca7f294ce9a3c7b2 Mon Sep 17 00:00:00 2001 From: Alvie Stoddard Date: Fri, 24 Jul 2026 14:30:15 -0700 Subject: [PATCH] Add Paste Bar settings and pause shortcuts --- README.md | 2 + Sources/Pesty/AppController.swift | 46 ++++++++++++++++++-- Sources/Pesty/Monitor/ClipboardMonitor.swift | 4 ++ Sources/Pesty/UI/BarWindowController.swift | 7 +++ 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4ecb2e3..14a9866 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/Sources/Pesty/AppController.swift b/Sources/Pesty/AppController.swift index ee649f5..f2c649b 100644 --- a/Sources/Pesty/AppController.swift +++ b/Sources/Pesty/AppController.swift @@ -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? @@ -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: "") @@ -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: [ @@ -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 @@ -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) diff --git a/Sources/Pesty/Monitor/ClipboardMonitor.swift b/Sources/Pesty/Monitor/ClipboardMonitor.swift index 37e1d99..97bb7b2 100644 --- a/Sources/Pesty/Monitor/ClipboardMonitor.swift +++ b/Sources/Pesty/Monitor/ClipboardMonitor.swift @@ -8,6 +8,7 @@ final class ClipboardMonitor { private var timer: Timer? var suppressUntilChangeCount: Int = -1 + private(set) var isPaused = false init() { lastChangeCount = pasteboard.changeCount @@ -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) diff --git a/Sources/Pesty/UI/BarWindowController.swift b/Sources/Pesty/UI/BarWindowController.swift index cbd1c2a..7fb655a 100644 --- a/Sources/Pesty/UI/BarWindowController.swift +++ b/Sources/Pesty/UI/BarWindowController.swift @@ -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