Is your feature request related to a problem? Please describe.
There are two per-keystroke/per-tick performance bottlenecks in GhosttySurfaceView:
- O(N) global event monitors: Every initialized
GhosttySurfaceView registers its own NSEvent.addLocalMonitorForEvents. If a user restores a workspace with 30 terminal tabs, hitting a modifier key (or clicking) forces AppKit to iterate and execute 30 separate closures.
- Task allocation on cache miss:
CachedValue (used for cachedScreenContents) spawns an async Task { try await sleep() } on every fetch miss just to clear the cache. This forces the Swift concurrency runtime to allocate and schedule a new Task continuously during high-frequency screen updates.
Describe the solution you'd like
- O(1) Shared Event Monitor: Register a single, static
NSEvent.addLocalMonitorForEvents that maintains a weak registry (NSHashTable.weakObjects) of live surfaces. When an event fires, the single monitor checks the registry for the focused view and routes the event accordingly.
- Synchronous Cache Expiry: Refactor
CachedValue to store a ContinuousClock.Instant. On access, synchronously compare now - fetchedAt < duration instead of allocating a Task to clear it.
Describe alternatives you've considered
No response
Are you planning to implement this yourself?
Before submitting
Is your feature request related to a problem? Please describe.
There are two per-keystroke/per-tick performance bottlenecks in
GhosttySurfaceView:GhosttySurfaceViewregisters its ownNSEvent.addLocalMonitorForEvents. If a user restores a workspace with 30 terminal tabs, hitting a modifier key (or clicking) forces AppKit to iterate and execute 30 separate closures.CachedValue(used forcachedScreenContents) spawns an asyncTask { try await sleep() }on every fetch miss just to clear the cache. This forces the Swift concurrency runtime to allocate and schedule a new Task continuously during high-frequency screen updates.Describe the solution you'd like
NSEvent.addLocalMonitorForEventsthat maintains a weak registry (NSHashTable.weakObjects) of live surfaces. When an event fires, the single monitor checks the registry for thefocusedview and routes the event accordingly.CachedValueto store aContinuousClock.Instant. On access, synchronously comparenow - fetchedAt < durationinstead of allocating aTaskto clear it.Describe alternatives you've considered
No response
Are you planning to implement this yourself?
ready.Before submitting
ready.