native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #172 from supabitapp/fix-clipboard-copy-as-screenshot

Terminal improvenents - Secure input, Force Touch, Pasting Images

authored by

khoi and committed by
GitHub
cec70459 92e76128

+275 -9
+12 -9
supacode/Infrastructure/Ghostty/GhosttyRuntime.swift
··· 280 280 confirm: Bool 281 281 ) { 282 282 guard let content, len > 0 else { return } 283 - var items: [(mime: String, data: String)] = [] 284 - items.reserveCapacity(len) 285 - for index in 0..<len { 283 + let items: [(mime: String, data: String)] = (0..<len).compactMap { index in 286 284 let item = content.advanced(by: index).pointee 287 - guard let mimePtr = item.mime, let dataPtr = item.data else { continue } 288 - items.append((mime: String(cString: mimePtr), data: String(cString: dataPtr))) 285 + guard let mimePtr = item.mime, let dataPtr = item.data else { return nil } 286 + return (mime: String(cString: mimePtr), data: String(cString: dataPtr)) 289 287 } 290 288 guard !items.isEmpty else { return } 291 - Task { @MainActor in 289 + 290 + let write = { 292 291 guard let pasteboard = NSPasteboard.ghostty(location) else { return } 293 292 let types = items.compactMap { NSPasteboard.PasteboardType(mimeType: $0.mime) } 294 - if !types.isEmpty { 295 - pasteboard.declareTypes(types, owner: nil) 296 - } 293 + pasteboard.declareTypes(types, owner: nil) 297 294 for item in items { 298 295 guard let type = NSPasteboard.PasteboardType(mimeType: item.mime) else { continue } 299 296 pasteboard.setString(item.data, forType: type) 300 297 } 298 + } 299 + 300 + if Thread.isMainThread { 301 + write() 302 + } else { 303 + DispatchQueue.main.async { write() } 301 304 } 302 305 } 303 306
+24
supacode/Infrastructure/Ghostty/GhosttySurfaceBridge.swift
··· 1 + import AppKit 1 2 import Foundation 2 3 import GhosttyKit 3 4 ··· 12 13 var onNewTab: (() -> Bool)? 13 14 var onCloseTab: ((ghostty_action_close_tab_mode_e) -> Bool)? 14 15 var onGotoTab: ((ghostty_action_goto_tab_e) -> Bool)? 16 + var onMoveTab: ((ghostty_action_move_tab_s) -> Bool)? 15 17 var onProgressReport: ((ghostty_action_progress_report_state_e) -> Void)? 16 18 var onDesktopNotification: ((String, String) -> Void)? 17 19 private var progressResetTask: Task<Void, Never>? ··· 56 58 return onCloseTab?(action.action.close_tab_mode) ?? false 57 59 case GHOSTTY_ACTION_GOTO_TAB: 58 60 return onGotoTab?(action.action.goto_tab) ?? false 61 + case GHOSTTY_ACTION_MOVE_TAB: 62 + return onMoveTab?(action.action.move_tab) ?? false 63 + case GHOSTTY_ACTION_GOTO_WINDOW, 64 + GHOSTTY_ACTION_TOGGLE_QUICK_TERMINAL, 65 + GHOSTTY_ACTION_CLOSE_ALL_WINDOWS: 66 + return false 67 + case GHOSTTY_ACTION_UNDO: 68 + NSApp.sendAction(#selector(UndoManager.undo), to: nil, from: nil) 69 + return true 70 + case GHOSTTY_ACTION_REDO: 71 + NSApp.sendAction(#selector(UndoManager.redo), to: nil, from: nil) 72 + return true 59 73 default: 60 74 return nil 61 75 } ··· 368 382 switch action.tag { 369 383 case GHOSTTY_ACTION_SECURE_INPUT: 370 384 state.secureInput = action.action.secure_input 385 + switch action.action.secure_input { 386 + case GHOSTTY_SECURE_INPUT_ON: 387 + surfaceView?.passwordInput = true 388 + case GHOSTTY_SECURE_INPUT_OFF: 389 + surfaceView?.passwordInput = false 390 + case GHOSTTY_SECURE_INPUT_TOGGLE: 391 + surfaceView?.passwordInput.toggle() 392 + default: 393 + break 394 + } 371 395 return true 372 396 373 397 case GHOSTTY_ACTION_FLOAT_WINDOW:
+139
supacode/Infrastructure/Ghostty/GhosttySurfaceView.swift
··· 2 2 import Carbon 3 3 import CoreText 4 4 import GhosttyKit 5 + import QuartzCore 5 6 6 7 final class GhosttySurfaceView: NSView, Identifiable { 7 8 private struct ScrollbarState { ··· 27 28 private var keyTextAccumulator: [String]? 28 29 private var cellSize: CGSize = .zero 29 30 private var lastScrollbar: ScrollbarState? 31 + private var eventMonitor: Any? 32 + private var prevPressureStage: Int = 0 33 + var passwordInput: Bool = false { 34 + didSet { 35 + let input = SecureInput.shared 36 + let id = ObjectIdentifier(self) 37 + if passwordInput { 38 + input.setScoped(id, focused: focused) 39 + } else { 40 + input.removeScoped(id) 41 + } 42 + } 43 + } 30 44 weak var scrollWrapper: GhosttySurfaceScrollView? { 31 45 didSet { 32 46 if let lastScrollbar { ··· 95 109 surfaceRef = runtime.registerSurface(surface) 96 110 } 97 111 registerForDraggedTypes(Array(Self.dropTypes)) 112 + 113 + eventMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyUp, .leftMouseDown]) { 114 + [weak self] event in 115 + self?.localEventHandler(event) 116 + } 98 117 } 99 118 100 119 required init?(coder: NSCoder) { ··· 102 121 } 103 122 104 123 deinit { 124 + if let eventMonitor { 125 + NSEvent.removeMonitor(eventMonitor) 126 + } 127 + let id = ObjectIdentifier(self) 128 + MainActor.assumeIsolated { 129 + SecureInput.shared.removeScoped(id) 130 + } 105 131 closeSurface() 106 132 if let workingDirectoryCString { 107 133 free(workingDirectoryCString) ··· 135 161 136 162 override func viewDidChangeBackingProperties() { 137 163 super.viewDidChangeBackingProperties() 164 + if let window { 165 + CATransaction.begin() 166 + CATransaction.setDisableActions(true) 167 + layer?.contentsScale = window.backingScaleFactor 168 + CATransaction.commit() 169 + } 138 170 updateContentScale() 139 171 updateSurfaceSize() 140 172 } ··· 168 200 focused = true 169 201 setSurfaceFocus(true) 170 202 onFocusChange?(true) 203 + if passwordInput { 204 + SecureInput.shared.setScoped(ObjectIdentifier(self), focused: true) 205 + } 171 206 } 172 207 return result 173 208 } ··· 178 213 focused = false 179 214 setSurfaceFocus(false) 180 215 onFocusChange?(false) 216 + if passwordInput { 217 + SecureInput.shared.setScoped(ObjectIdentifier(self), focused: false) 218 + } 181 219 } 182 220 return result 183 221 } ··· 283 321 } 284 322 285 323 override func mouseUp(with event: NSEvent) { 324 + prevPressureStage = 0 286 325 sendMouseButton(event, state: GHOSTTY_MOUSE_RELEASE, button: GHOSTTY_MOUSE_LEFT) 287 326 } 288 327 ··· 339 378 scrollY *= 2 340 379 } 341 380 ghostty_surface_mouse_scroll(surface, scrollX, scrollY, scrollMods(for: event)) 381 + } 382 + 383 + override func pressureChange(with event: NSEvent) { 384 + guard let surface else { return } 385 + ghostty_surface_mouse_pressure(surface, UInt32(event.stage), Double(event.pressure)) 386 + guard prevPressureStage < 2 else { return } 387 + prevPressureStage = event.stage 388 + guard event.stage == 2 else { return } 389 + guard UserDefaults.standard.bool(forKey: "com.apple.trackpad.forceClick") else { return } 390 + quickLook(with: event) 391 + } 392 + 393 + override func quickLook(with event: NSEvent) { 394 + guard let surface else { return super.quickLook(with: event) } 395 + var text = ghostty_text_s() 396 + guard ghostty_surface_quicklook_word(surface, &text) else { return super.quickLook(with: event) } 397 + defer { ghostty_surface_free_text(surface, &text) } 398 + guard text.text_len > 0 else { return super.quickLook(with: event) } 399 + 400 + var attributes: [NSAttributedString.Key: Any] = [:] 401 + if let fontRaw = ghostty_surface_quicklook_font(surface) { 402 + let font = Unmanaged<CTFont>.fromOpaque(fontRaw) 403 + attributes[.font] = font.takeUnretainedValue() 404 + font.release() 405 + } 406 + 407 + let pt = NSPoint(x: text.tl_px_x, y: frame.size.height - text.tl_px_y) 408 + let str = NSAttributedString(string: String(cString: text.text), attributes: attributes) 409 + showDefinition(for: str, at: pt) 410 + } 411 + 412 + private func localEventHandler(_ event: NSEvent) -> NSEvent? { 413 + switch event.type { 414 + case .keyUp: 415 + localEventKeyUp(event) 416 + case .leftMouseDown: 417 + localEventLeftMouseDown(event) 418 + default: 419 + event 420 + } 421 + } 422 + 423 + private func localEventKeyUp(_ event: NSEvent) -> NSEvent? { 424 + if !event.modifierFlags.contains(.command) { return event } 425 + guard focused else { return event } 426 + keyUp(with: event) 427 + return nil 428 + } 429 + 430 + private func localEventLeftMouseDown(_ event: NSEvent) -> NSEvent? { 431 + guard let window, event.window != nil, window == event.window else { return event } 432 + let location = convert(event.locationInWindow, from: nil) 433 + guard hitTest(location) == self else { return event } 434 + guard !NSApp.isActive || !window.isKeyWindow else { return event } 435 + guard !focused else { return event } 436 + window.makeFirstResponder(self) 437 + return event 342 438 } 343 439 344 440 func updateSurfaceSize() { ··· 1043 1139 chars.withCString { ptr in 1044 1140 ghostty_surface_text(surface, ptr, UInt(len - 1)) 1045 1141 } 1142 + } 1143 + } 1144 + 1145 + extension GhosttySurfaceView: NSServicesMenuRequestor { 1146 + override func validRequestor( 1147 + forSendType sendType: NSPasteboard.PasteboardType?, 1148 + returnType: NSPasteboard.PasteboardType? 1149 + ) -> Any? { 1150 + let receivable: [NSPasteboard.PasteboardType] = [.string, .init("public.utf8-plain-text")] 1151 + let sendable = receivable 1152 + let sendableRequiresSelection = sendable 1153 + 1154 + if (returnType == nil || receivable.contains(returnType!)) 1155 + && (sendType == nil || sendable.contains(sendType!)) 1156 + { 1157 + if let sendType, sendableRequiresSelection.contains(sendType) { 1158 + if surface == nil || !ghostty_surface_has_selection(surface) { 1159 + return super.validRequestor(forSendType: sendType, returnType: returnType) 1160 + } 1161 + } 1162 + return self 1163 + } 1164 + return super.validRequestor(forSendType: sendType, returnType: returnType) 1165 + } 1166 + 1167 + func writeSelection(to pboard: NSPasteboard, types: [NSPasteboard.PasteboardType]) -> Bool { 1168 + guard let surface else { return false } 1169 + var text = ghostty_text_s() 1170 + guard ghostty_surface_read_selection(surface, &text) else { return false } 1171 + defer { ghostty_surface_free_text(surface, &text) } 1172 + pboard.declareTypes([.string], owner: nil) 1173 + pboard.setString(String(cString: text.text), forType: .string) 1174 + return true 1175 + } 1176 + 1177 + func readSelection(from pboard: NSPasteboard) -> Bool { 1178 + guard let str = pboard.getOpinionatedStringContents() else { return false } 1179 + let len = str.utf8CString.count 1180 + if len == 0 { return true } 1181 + str.withCString { ptr in 1182 + ghostty_surface_text(surface, ptr, UInt(len - 1)) 1183 + } 1184 + return true 1046 1185 } 1047 1186 } 1048 1187
+100
supacode/Infrastructure/Ghostty/SecureInput.swift
··· 1 + import Carbon 2 + import Cocoa 3 + import OSLog 4 + 5 + @MainActor 6 + final class SecureInput: Observable { 7 + static let shared = SecureInput() 8 + 9 + private static let logger = Logger( 10 + subsystem: Bundle.main.bundleIdentifier!, 11 + category: String(describing: SecureInput.self) 12 + ) 13 + 14 + var global: Bool = false { 15 + didSet { apply() } 16 + } 17 + 18 + private var scoped: [ObjectIdentifier: Bool] = [:] 19 + private(set) var enabled: Bool = false 20 + 21 + private var desired: Bool { 22 + global || scoped.contains(where: { $0.value }) 23 + } 24 + 25 + private init() { 26 + let center = NotificationCenter.default 27 + center.addObserver( 28 + self, 29 + selector: #selector(onDidResignActive(notification:)), 30 + name: NSApplication.didResignActiveNotification, 31 + object: nil 32 + ) 33 + center.addObserver( 34 + self, 35 + selector: #selector(onDidBecomeActive(notification:)), 36 + name: NSApplication.didBecomeActiveNotification, 37 + object: nil 38 + ) 39 + } 40 + 41 + deinit { 42 + NotificationCenter.default.removeObserver(self) 43 + scoped.removeAll() 44 + global = false 45 + apply() 46 + } 47 + 48 + func setScoped(_ object: ObjectIdentifier, focused: Bool) { 49 + scoped[object] = focused 50 + apply() 51 + } 52 + 53 + func removeScoped(_ object: ObjectIdentifier) { 54 + scoped[object] = nil 55 + apply() 56 + } 57 + 58 + private nonisolated func apply() { 59 + MainActor.assumeIsolated { 60 + guard NSApp.isActive else { return } 61 + guard enabled != desired else { return } 62 + 63 + let err: OSStatus 64 + if enabled { 65 + err = DisableSecureEventInput() 66 + } else { 67 + err = EnableSecureEventInput() 68 + } 69 + if err == noErr { 70 + enabled = desired 71 + Self.logger.debug("secure input state=\(self.enabled)") 72 + return 73 + } 74 + 75 + Self.logger.warning("secure input apply failed err=\(err)") 76 + } 77 + } 78 + 79 + @objc private func onDidBecomeActive(notification: NSNotification) { 80 + guard !enabled && desired else { return } 81 + let err = EnableSecureEventInput() 82 + if err == noErr { 83 + enabled = true 84 + Self.logger.debug("secure input enabled on activation") 85 + return 86 + } 87 + Self.logger.warning("secure input apply failed err=\(err)") 88 + } 89 + 90 + @objc private func onDidResignActive(notification: NSNotification) { 91 + guard enabled else { return } 92 + let err = DisableSecureEventInput() 93 + if err == noErr { 94 + enabled = false 95 + Self.logger.debug("secure input disabled on deactivation") 96 + return 97 + } 98 + Self.logger.warning("secure input apply failed err=\(err)") 99 + } 100 + }