11import AppKit
22import Foundation
33
4+ final class TaskersGhosttySurfaceContext {
5+ private weak var host : TaskersGhosttyHost ?
6+ private( set) var isClosing = false
7+
8+ let workspaceID : String
9+ let paneID : String
10+ let surfaceID : String
11+
12+ init ( host: TaskersGhosttyHost , workspaceID: String , paneID: String , surfaceID: String ) {
13+ self . host = host
14+ self . workspaceID = workspaceID
15+ self . paneID = paneID
16+ self . surfaceID = surfaceID
17+ }
18+
19+ func retainForUserdata( ) -> UnsafeMutableRawPointer {
20+ Unmanaged . passRetained ( self ) . toOpaque ( )
21+ }
22+
23+ func beginTeardown( ) {
24+ isClosing = true
25+ }
26+
27+ func handleChildExited( exitCode: UInt32 ) {
28+ _ = exitCode
29+ closeSurfaceIfNeeded ( )
30+ }
31+
32+ func handleSurfaceClosed( ) {
33+ closeSurfaceIfNeeded ( )
34+ }
35+
36+ private func closeSurfaceIfNeeded( ) {
37+ guard !isClosing else {
38+ return
39+ }
40+
41+ isClosing = true
42+ host? . surfaceDidClose ( workspaceID: workspaceID, paneID: paneID, surfaceID: surfaceID)
43+ }
44+
45+ static func from( surface: ghostty_surface_t ) -> TaskersGhosttySurfaceContext ? {
46+ from ( userdata: ghostty_surface_userdata ( surface) )
47+ }
48+
49+ static func from( userdata: UnsafeMutableRawPointer ? ) -> TaskersGhosttySurfaceContext ? {
50+ guard let userdata else {
51+ return nil
52+ }
53+
54+ return Unmanaged < TaskersGhosttySurfaceContext > . fromOpaque ( userdata) . takeUnretainedValue ( )
55+ }
56+
57+ static func releaseUserdata( _ userdata: UnsafeMutableRawPointer ) {
58+ Unmanaged < TaskersGhosttySurfaceContext > . fromOpaque ( userdata) . release ( )
59+ }
60+ }
61+
462final class TaskersTerminalView : NSView {
563 let workspaceID : String
664 let paneID : String
765 let surfaceID : String
866
967 private weak var host : TaskersGhosttyHost ?
68+ private let callbackContext : TaskersGhosttySurfaceContext
69+ private var callbackContextHandle : UnsafeMutableRawPointer ?
70+ private var isDisposed = false
1071 private var surface : ghostty_surface_t ?
1172 private var commandString : String
1273
@@ -26,6 +87,13 @@ final class TaskersTerminalView: NSView {
2687 self . workspaceID = workspaceID
2788 self . paneID = paneID
2889 self . surfaceID = surfaceID
90+ self . callbackContext = TaskersGhosttySurfaceContext (
91+ host: host,
92+ workspaceID: workspaceID,
93+ paneID: paneID,
94+ surfaceID: surfaceID
95+ )
96+ self . callbackContextHandle = callbackContext. retainForUserdata ( )
2997 self . commandString = Self . commandString ( for: descriptor. commandArgv)
3098
3199 super. init ( frame: NSRect ( x: 0 , y: 0 , width: 640 , height: 420 ) )
@@ -36,7 +104,8 @@ final class TaskersTerminalView: NSView {
36104 view: self ,
37105 app: app,
38106 descriptor: descriptor,
39- commandString: commandString
107+ commandString: commandString,
108+ userdata: self . callbackContextHandle!
40109 )
41110 updateSurfaceMetrics ( )
42111 }
@@ -46,10 +115,7 @@ final class TaskersTerminalView: NSView {
46115 }
47116
48117 deinit {
49- if let surface {
50- ghostty_surface_free ( surface)
51- }
52- host? . unregisterSurface ( self )
118+ dispose ( )
53119 }
54120
55121 override func becomeFirstResponder( ) -> Bool {
@@ -135,13 +201,38 @@ final class TaskersTerminalView: NSView {
135201 needsDisplay = true
136202 }
137203
138- func handleChildExited( exitCode: UInt32 ) {
139- _ = exitCode
140- host? . surfaceDidClose ( workspaceID: workspaceID, paneID: paneID, surfaceID: surfaceID)
204+ func beginTeardown( ) {
205+ callbackContext. beginTeardown ( )
141206 }
142207
143- func handleSurfaceClosed( ) {
144- host? . surfaceDidClose ( workspaceID: workspaceID, paneID: paneID, surfaceID: surfaceID)
208+ func dispose( ) {
209+ guard !isDisposed else {
210+ return
211+ }
212+
213+ isDisposed = true
214+ callbackContext. beginTeardown ( )
215+ host? . unregisterSurface ( self )
216+
217+ let surface = self . surface
218+ self . surface = nil
219+
220+ guard let callbackContextHandle else {
221+ return
222+ }
223+ self . callbackContextHandle = nil
224+
225+ let cleanup = {
226+ if let surface {
227+ ghostty_surface_free ( surface)
228+ }
229+ TaskersGhosttySurfaceContext . releaseUserdata ( callbackContextHandle)
230+ }
231+ if Thread . isMainThread {
232+ cleanup ( )
233+ } else {
234+ DispatchQueue . main. async ( execute: cleanup)
235+ }
145236 }
146237
147238 private func setFocused( _ focused: Bool ) {
@@ -254,15 +345,16 @@ final class TaskersTerminalView: NSView {
254345 view: TaskersTerminalView ,
255346 app: ghostty_app_t ,
256347 descriptor: TaskersSurfaceDescriptor ,
257- commandString: String
348+ commandString: String ,
349+ userdata: UnsafeMutableRawPointer
258350 ) throws -> ghostty_surface_t {
259351 let scale = NSScreen . main? . backingScaleFactor ?? 2.0
260352 var config = ghostty_surface_config_new ( )
261353 config. platform_tag = GHOSTTY_PLATFORM_MACOS
262354 config. platform = ghostty_platform_u (
263355 macos: ghostty_platform_macos_s ( nsview: Unmanaged . passUnretained ( view) . toOpaque ( ) )
264356 )
265- config. userdata = Unmanaged . passUnretained ( view ) . toOpaque ( )
357+ config. userdata = userdata
266358 config. scale_factor = scale
267359 config. context = GHOSTTY_SURFACE_CONTEXT_SPLIT
268360
@@ -394,17 +486,6 @@ final class TaskersTerminalView: NSView {
394486 return flags
395487 }
396488
397- static func from( surface: ghostty_surface_t ) -> TaskersTerminalView ? {
398- from ( userdata: ghostty_surface_userdata ( surface) )
399- }
400-
401- static func from( userdata: UnsafeMutableRawPointer ? ) -> TaskersTerminalView ? {
402- guard let userdata else {
403- return nil
404- }
405-
406- return Unmanaged < TaskersTerminalView > . fromOpaque ( userdata) . takeUnretainedValue ( )
407- }
408489}
409490
410491private extension NSEvent {
0 commit comments