linux: implement runtime callbacks (close surface, set title, pwd)#237
linux: implement runtime callbacks (close surface, set title, pwd)#237Jesssullivan merged 2 commits intomainfrom
Conversation
When a terminal process exits, libghostty calls close_surface_cb. Previously this was a no-op, leaving dead shells visible. Now: - Set surface_config.userdata to the GtkWidget pointer during surface creation so the callback can identify the owning panel. - Walk the tab manager to find the panel matching the widget, remove it, and close the workspace if it becomes empty (unless it's the last one). - Capture panel ID before removal to avoid iterator invalidation on the panel hash map.
Wire the onAction callback to dispatch SET_TITLE and PWD actions: - SET_TITLE: updates the panel title and propagates to workspace tab title (unless a custom title is set). Enables terminal escape sequences (like shell prompt integration) to update tab names. - PWD: updates the panel and workspace current_directory, enabling shell-integration working directory tracking. Both use getSurfaceUserdata() to resolve the target surface's widget pointer and walk the tab manager to find the owning panel.
Greptile SummaryThis PR implements
Confidence Score: 4/5Safe to merge after fixing two memory leaks in the new action handlers; the core close-surface logic is correct. Two P1 memory leaks are introduced by the new handleSetTitle and handlePwd functions — they accumulate unboundedly during normal terminal use (every shell prompt or directory change). The onCloseSurface implementation itself is correct. Score is 4 pending the leak fixes. cmux-linux/src/app.zig — handleSetTitle (line 39) and handlePwd (lines 67–68) Important Files Changed
Sequence DiagramsequenceDiagram
participant GH as libghostty
participant CB as app.zig callbacks
participant WS as Workspace
participant TM as TabManager
GH->>CB: onCloseSurface(userdata=GtkWidget*)
CB->>TM: getTabManager()
CB->>WS: iterate panels to find widget
CB->>WS: removePanel(panel_id)
alt workspace empty && not last
CB->>TM: closeWorkspace(ws_idx)
TM->>WS: deinit + destroy
TM-->>TM: remove AdwTabView page
end
GH->>CB: onAction(SET_TITLE)
CB->>CB: getSurfaceUserdata(target)
CB->>WS: iterate panels to find widget
CB->>WS: panel.title = dupe(title_str)
Note over CB,WS: old panel.title not freed
CB->>WS: ws.setTitle(title_str)
CB->>TM: updateTabTitle(ws)
GH->>CB: onAction(PWD)
CB->>CB: getSurfaceUserdata(target)
CB->>WS: iterate panels to find widget
CB->>WS: panel.directory = dupe(pwd_str)
Note over CB,WS: old panel.directory not freed
CB->>WS: ws.current_directory = dupe(pwd_str)
Note over CB,WS: old ws.current_directory not freed
Reviews (1): Last reviewed commit: "linux: handle SET_TITLE and PWD actions ..." | Re-trigger Greptile |
| if (panel.widget) |pw| { | ||
| if (pw == widget) { | ||
| // Set panel title | ||
| panel.title = ws.alloc.dupe(u8, title_str) catch null; |
There was a problem hiding this comment.
Memory leak: old
panel.title not freed before replacement
panel.title is overwritten on every set_title escape sequence (which fires on each shell prompt change) without freeing the previous allocation. Over a long session this grows unboundedly. Compare with ws.setTitle() which correctly frees first.
| panel.title = ws.alloc.dupe(u8, title_str) catch null; | |
| if (panel.title) |old| ws.alloc.free(old); | |
| panel.title = ws.alloc.dupe(u8, title_str) catch null; |
| panel.directory = ws.alloc.dupe(u8, pwd_str) catch null; | ||
| ws.current_directory = ws.alloc.dupe(u8, pwd_str) catch null; |
There was a problem hiding this comment.
Memory leak: old
panel.directory / ws.current_directory not freed
Both slices are overwritten without freeing the previous allocation, leaking memory on every shell PWD integration update. Same fix pattern as ws.setTitle():
| panel.directory = ws.alloc.dupe(u8, pwd_str) catch null; | |
| ws.current_directory = ws.alloc.dupe(u8, pwd_str) catch null; | |
| if (panel.directory) |old| ws.alloc.free(old); | |
| panel.directory = ws.alloc.dupe(u8, pwd_str) catch null; | |
| if (ws.current_directory) |old| ws.alloc.free(old); | |
| ws.current_directory = ws.alloc.dupe(u8, pwd_str) catch null; |
Summary
surface_config.userdatato the GtkWidget pointer during surface creationonCloseSurfacecallback: walks tab manager to find the owning panel, removes it, closes empty workspacesPreviously
onCloseSurfacewas a no-op stub (identified as a remaining gap from #207). Now libghostty's close notification properly triggers panel cleanup.Design notes
removePanelto avoid hash map iterator invalidationpanel.surfaceis always null (ghostty surface lives on theSurfacewrapper, not the panel struct), soremovePanel'sghostty_surface_freeguard is a no-op — the surface is already freed by libghostty before calling the callbackTest plan
exit— panel should be removed🤖 Generated with Claude Code