import SwiftUI import AppKit extension Notification.Name { static let promptPanelDidShow = Notification.Name("PromptPanelDidShowNotification") } private final class KeyablePanel: NSPanel { override var canBecomeKey: Bool { true } override var canBecomeMain: Bool { false } } @MainActor final class PanelController: NSObject { static let shared = PanelController() private var panel: KeyablePanel? private var pasteMonitor: Any? var isVisible: Bool { panel?.isVisible ?? false } func toggle() { isVisible ? hide() : show() } func show() { let panel = panel ?? makePanel() if UserDefaults.standard.string(forKey: "NSWindow Frame PromptPanel") == nil { positionAtRightEdge(panel) } NSApp.activate(ignoringOtherApps: true) panel.makeKeyAndOrderFront(nil) NSLog("Prompt: panel shown") NotificationCenter.default.post(name: .promptPanelDidShow, object: nil) } func hide() { panel?.orderOut(nil) NSApp.deactivate() NSLog("Prompt: panel hidden (app keeps running for the Shift-Shift hotkeys)") } private func makePanel() -> KeyablePanel { let panel = KeyablePanel( contentRect: NSRect(x: 0, y: 0, width: 360, height: 640), styleMask: [.titled, .closable, .resizable, .nonactivatingPanel, .fullSizeContentView], backing: .buffered, defer: false ) panel.titleVisibility = .hidden panel.titlebarAppearsTransparent = true panel.standardWindowButton(.miniaturizeButton)?.isHidden = true panel.standardWindowButton(.zoomButton)?.isHidden = true panel.level = .floating panel.isMovableByWindowBackground = true panel.hidesOnDeactivate = false panel.isReleasedWhenClosed = false panel.isOpaque = true // Match the SwiftUI content so the titlebar strip and any resize flash // do not show the system grey through. panel.backgroundColor = Theme.background panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] panel.contentView = NSHostingView(rootView: PanelView(store: Store.shared)) panel.setFrameAutosaveName("PromptPanel") self.panel = panel installPasteMonitor() return panel } private func positionAtRightEdge(_ panel: NSPanel) { guard let screen = NSScreen.main?.visibleFrame else { return } let size = NSSize(width: 360, height: screen.height - 24) panel.setContentSize(size) panel.setFrameOrigin(NSPoint(x: screen.maxX - size.width - 12, y: screen.minY + 12)) } // Cmd+V is handled here rather than left to a menu key equivalent. The // panel is a nonactivating panel, so it can be key while the app is not // active, and menu key equivalents are not reliably delivered in that // state — which is what silently broke pasting. Dispatching paste: to the // responder chain ourselves removes that dependency. private func installPasteMonitor() { guard pasteMonitor == nil else { return } pasteMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in guard let self, let panel = self.panel, event.window == panel else { return event } let modifiers = event.modifierFlags.intersection(.deviceIndependentFlagsMask) guard modifiers == [.command], event.charactersIgnoringModifiers == "v" else { return event } let pasteboard = NSPasteboard.general if pasteboard.string(forType: .string) != nil { NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: nil) return nil } if let images = pasteboard.readObjects(forClasses: [NSImage.self], options: nil) as? [NSImage], let image = images.first { Task { @MainActor in Store.shared.addImage(image) } return nil } return event } } }