forked from manaflow-ai/cmux
-
Notifications
You must be signed in to change notification settings - Fork 0
linux: implement runtime callbacks (close surface, set title, pwd) #237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+107
−5
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,13 +10,77 @@ const window = @import("window.zig"); | |||||||||||||
| /// Returns true if handled, false to let libghostty handle it. | ||||||||||||||
| pub fn onAction( | ||||||||||||||
| _: c.ghostty_app_t, | ||||||||||||||
| _: c.ghostty.ghostty_target_s, | ||||||||||||||
| _: c.ghostty.ghostty_action_s, | ||||||||||||||
| target: c.ghostty.ghostty_target_s, | ||||||||||||||
| action: c.ghostty.ghostty_action_s, | ||||||||||||||
| ) callconv(.c) bool { | ||||||||||||||
| // TODO: dispatch actions (new_tab, close_tab, set_title, etc.) | ||||||||||||||
| return switch (action.tag) { | ||||||||||||||
| c.ghostty.GHOSTTY_ACTION_SET_TITLE => handleSetTitle(target, action.action.set_title), | ||||||||||||||
| c.ghostty.GHOSTTY_ACTION_PWD => handlePwd(target, action.action.pwd), | ||||||||||||||
| else => false, | ||||||||||||||
| }; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /// Update the panel/workspace title from terminal escape sequences. | ||||||||||||||
| fn handleSetTitle(target: c.ghostty.ghostty_target_s, title: c.ghostty.ghostty_action_set_title_s) bool { | ||||||||||||||
| const tm = window.getTabManager() orelse return false; | ||||||||||||||
| const title_str = if (title.title) |t| std.mem.span(t) else return false; | ||||||||||||||
|
|
||||||||||||||
| // Find the surface from target and update its workspace title. | ||||||||||||||
| const surface_ud = getSurfaceUserdata(target) orelse return false; | ||||||||||||||
| const widget: *c.GtkWidget = @ptrCast(@alignCast(surface_ud)); | ||||||||||||||
|
|
||||||||||||||
| for (tm.workspaces.items) |ws| { | ||||||||||||||
| var it = ws.panels.valueIterator(); | ||||||||||||||
| while (it.next()) |panel_ptr| { | ||||||||||||||
| const panel = panel_ptr.*; | ||||||||||||||
| if (panel.widget) |pw| { | ||||||||||||||
| if (pw == widget) { | ||||||||||||||
| // Set panel title | ||||||||||||||
| panel.title = ws.alloc.dupe(u8, title_str) catch null; | ||||||||||||||
| // If no custom workspace title, propagate to workspace | ||||||||||||||
| if (ws.custom_title == null) { | ||||||||||||||
| ws.setTitle(title_str); | ||||||||||||||
| tm.updateTabTitle(ws); | ||||||||||||||
| } | ||||||||||||||
| return true; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| return false; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /// Update the workspace's current directory from shell integration. | ||||||||||||||
| fn handlePwd(target: c.ghostty.ghostty_target_s, pwd: c.ghostty.ghostty_action_pwd_s) bool { | ||||||||||||||
| const tm = window.getTabManager() orelse return false; | ||||||||||||||
| const pwd_str = if (pwd.pwd) |p| std.mem.span(p) else return false; | ||||||||||||||
|
|
||||||||||||||
| const surface_ud = getSurfaceUserdata(target) orelse return false; | ||||||||||||||
| const widget: *c.GtkWidget = @ptrCast(@alignCast(surface_ud)); | ||||||||||||||
|
|
||||||||||||||
| for (tm.workspaces.items) |ws| { | ||||||||||||||
| var it = ws.panels.valueIterator(); | ||||||||||||||
| while (it.next()) |panel_ptr| { | ||||||||||||||
| const panel = panel_ptr.*; | ||||||||||||||
| if (panel.widget) |pw| { | ||||||||||||||
| if (pw == widget) { | ||||||||||||||
| panel.directory = ws.alloc.dupe(u8, pwd_str) catch null; | ||||||||||||||
| ws.current_directory = ws.alloc.dupe(u8, pwd_str) catch null; | ||||||||||||||
|
Comment on lines
+67
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Both slices are overwritten without freeing the previous allocation, leaking memory on every shell
Suggested change
|
||||||||||||||
| return true; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| return false; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /// Extract the surface userdata pointer from a ghostty target. | ||||||||||||||
| fn getSurfaceUserdata(target: c.ghostty.ghostty_target_s) ?*anyopaque { | ||||||||||||||
| if (target.tag != c.ghostty.GHOSTTY_TARGET_SURFACE) return null; | ||||||||||||||
| const surface = target.target.surface orelse return null; | ||||||||||||||
| return c.ghostty.ghostty_surface_userdata(surface); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /// Read clipboard callback: libghostty wants clipboard contents. | ||||||||||||||
| /// Returns false when clipboard data is not available. | ||||||||||||||
| pub fn onReadClipboard( | ||||||||||||||
|
|
@@ -45,7 +109,41 @@ pub fn onWriteClipboard( | |||||||||||||
| ) callconv(.c) void {} | ||||||||||||||
|
|
||||||||||||||
| /// Close surface callback: terminal process exited or user requested close. | ||||||||||||||
| /// The userdata is the GtkWidget pointer set during surface creation. | ||||||||||||||
| /// Walk the tab manager to find the owning panel and remove it. | ||||||||||||||
| /// If a workspace becomes empty after removal, close it. | ||||||||||||||
| pub fn onCloseSurface( | ||||||||||||||
| _: ?*anyopaque, | ||||||||||||||
| userdata: ?*anyopaque, | ||||||||||||||
| _: bool, | ||||||||||||||
| ) callconv(.c) void {} | ||||||||||||||
| ) callconv(.c) void { | ||||||||||||||
| const widget: *c.GtkWidget = @ptrCast(@alignCast(userdata orelse return)); | ||||||||||||||
| const tm = window.getTabManager() orelse return; | ||||||||||||||
|
|
||||||||||||||
| // Find which workspace/panel owns this widget. | ||||||||||||||
| // Capture the panel ID first, then remove outside the iterator | ||||||||||||||
| // to avoid iterator invalidation. | ||||||||||||||
| var found_panel_id: ?u128 = null; | ||||||||||||||
| var found_ws_idx: usize = 0; | ||||||||||||||
| outer: for (tm.workspaces.items, 0..) |ws, ws_idx| { | ||||||||||||||
| var it = ws.panels.valueIterator(); | ||||||||||||||
| while (it.next()) |panel_ptr| { | ||||||||||||||
| const panel = panel_ptr.*; | ||||||||||||||
| if (panel.widget) |pw| { | ||||||||||||||
| if (pw == widget) { | ||||||||||||||
| found_panel_id = panel.id; | ||||||||||||||
| found_ws_idx = ws_idx; | ||||||||||||||
| break :outer; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const panel_id = found_panel_id orelse return; | ||||||||||||||
| const ws = tm.workspaces.items[found_ws_idx]; | ||||||||||||||
| ws.removePanel(panel_id); | ||||||||||||||
|
|
||||||||||||||
| // If the workspace is now empty, close it (unless it's the last one). | ||||||||||||||
| if (ws.panelCount() == 0 and tm.workspaces.items.len > 1) { | ||||||||||||||
| tm.closeWorkspace(found_ws_idx); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
panel.titlenot freed before replacementpanel.titleis overwritten on everyset_titleescape sequence (which fires on each shell prompt change) without freeing the previous allocation. Over a long session this grows unboundedly. Compare withws.setTitle()which correctly frees first.