Repository navigation
Native tmux control mode (tmux -CC) support #2474
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
Changes from all commits
48cdfab
5e7ecfc
df05fc7
f9376ac
53c1b55
7f2d747
6eae187
f6241a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2860,6 +2860,44 @@ class GhosttyApp { | |
| } | ||
| } | ||
| } | ||
| case GHOSTTY_ACTION_TMUX_STATE: | ||
| guard let terminalSurface = surfaceView.terminalSurface else { return true } | ||
| let tmuxState = action.action.tmux_state | ||
| DispatchQueue.main.async { | ||
| switch tmuxState { | ||
| case GHOSTTY_TMUX_STATE_ENTERED: | ||
| terminalSurface.tmuxActive = true | ||
| #if DEBUG | ||
| dlog("tmux.entered tab=\(surfaceView.tabId?.uuidString.prefix(5) ?? "nil") surface=\(terminalSurface.id.uuidString.prefix(5))") | ||
| #endif | ||
| case GHOSTTY_TMUX_STATE_EXITED: | ||
| terminalSurface.tmuxActive = false | ||
| #if DEBUG | ||
| dlog("tmux.exited tab=\(surfaceView.tabId?.uuidString.prefix(5) ?? "nil") surface=\(terminalSurface.id.uuidString.prefix(5))") | ||
| #endif | ||
| case GHOSTTY_TMUX_STATE_WINDOWS_CHANGED: | ||
| // The tmux window/pane layout changed. Output is written | ||
| // directly to the surface terminal by the stream handler's | ||
| // octal decoder, so no renderer pointer swap is needed. | ||
| if let surface = terminalSurface.surface { | ||
| let paneCount = ghostty_surface_tmux_pane_count(surface) | ||
| let windowCount = ghostty_surface_tmux_window_count(surface) | ||
| #if DEBUG | ||
| dlog("tmux.windowsChanged windows=\(windowCount) panes=\(paneCount)") | ||
| #endif | ||
| _ = paneCount | ||
| _ = windowCount | ||
| } | ||
| default: | ||
| break | ||
| } | ||
| NotificationCenter.default.post( | ||
| name: .ghosttyTmuxStateChanged, | ||
| object: terminalSurface, | ||
| userInfo: ["state": tmuxState] | ||
| ) | ||
| } | ||
|
Comment on lines
+2863
to
+2899
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. 🧩 Analysis chain🏁 Script executed: # Find the definition of liveSurfaceForGhosttyAccess and understand how it validates surfaces
rg -A 10 "func liveSurfaceForGhosttyAccess" Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 660 🏁 Script executed: # Check how TerminalSurface.surface can be replaced/rebound
rg -B 2 -A 3 "terminalSurface.surface\s*=" Sources/GhosttyTerminalView.swift | head -50Repository: manaflow-ai/cmux Length of output: 335 🏁 Script executed: # Look for similar async dispatch patterns that guard against stale surfaces
rg -B 3 -A 8 "DispatchQueue.main.async.*target.target.surface" Sources/GhosttyTerminalView.swift | head -80Repository: manaflow-ai/cmux Length of output: 42 🏁 Script executed: # Check if other tmux-related operations guard the surface
rg -B 5 -A 5 "ghostty_surface_tmux_" Sources/GhosttyTerminalView.swift | head -100Repository: manaflow-ai/cmux Length of output: 1113 🏁 Script executed: # Verify how surface lifecycle/rebinding works
rg -B 2 -A 5 "\.surface\s*=" Sources/GhosttyTerminalView.swift | grep -A 5 "terminalSurface\|TerminalSurface" | head -60Repository: manaflow-ai/cmux Length of output: 449 🏁 Script executed: # Find the handleAction method signature to understand the target parameter
rg -B 5 "func handleAction.*target.*action" Sources/GhosttyTerminalView.swift | head -40Repository: manaflow-ai/cmux Length of output: 421 🏁 Script executed: # Check what target.target.surface represents at the call site
rg -B 10 "case GHOSTTY_ACTION_TMUX_STATE:" Sources/GhosttyTerminalView.swift | head -30Repository: manaflow-ai/cmux Length of output: 612 🏁 Script executed: # Search for other action handlers to see if they use similar guards
rg -B 2 -A 8 "case GHOSTTY_ACTION_" Sources/GhosttyTerminalView.swift | grep -A 8 "DispatchQueue.main.async" | head -60Repository: manaflow-ai/cmux Length of output: 2485 🏁 Script executed: # Verify if surfaces can truly be unregistered mid-flight during async operations
rg -B 3 -A 5 "unregisterRuntimeSurface\|registry.unregister" Sources/GhosttyTerminalView.swift | head -80Repository: manaflow-ai/cmux Length of output: 42 🏁 Script executed: # Check the exact line where target is obtained in handleAction
sed -n '2800,2920p' Sources/GhosttyTerminalView.swift | grep -B 30 "GHOSTTY_ACTION_TMUX_STATE"Repository: manaflow-ai/cmux Length of output: 1619 🏁 Script executed: # Find the callback registration and invocation to understand what target contains
rg -B 5 -A 10 "surfaceCallbackContext" Sources/GhosttyTerminalView.swift | grep -A 10 "Unmanaged\|handleAction" | head -60Repository: manaflow-ai/cmux Length of output: 1444 🏁 Script executed: # Look for where ghostty_surface callbacks are registered and how target is populated
rg "ghostty_surface_on_action" Sources/GhosttyTerminalView.swift -B 3 -A 3Repository: manaflow-ai/cmux Length of output: 42 🏁 Script executed: # Check the actual closure at handleAction call site to see when it's invoked
rg -B 15 "handleAction.*target.*action" Sources/GhosttyTerminalView.swift | head -50Repository: manaflow-ai/cmux Length of output: 1635 🏁 Script executed: # Search for similar patterns where ghostty_surface_t is captured before async dispatch
rg -B 5 -A 10 "let.*surface.*=.*target.target.surface" Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 42 🏁 Script executed: # Check other critical surface operations that happen in async closures
rg -B 8 -A 8 "ghostty_surface_.*\(" Sources/GhosttyTerminalView.swift | grep -B 8 -A 8 "DispatchQueue.main.async" | head -80Repository: manaflow-ai/cmux Length of output: 2877 🏁 Script executed: # Check the Ghostty C API to understand what target.target.surface is
rg "ghostty_target" Sources/GhosttyTerminalView.swift -B 2 -A 2 | head -40Repository: manaflow-ai/cmux Length of output: 528 🏁 Script executed: # Find documentation or comments about what the target parameter contains
grep -r "target.target.surface" Sources/ --include="*.swift"Repository: manaflow-ai/cmux Length of output: 199 🏁 Script executed: # Look at how other handlers use the target parameter
sed -n '2600,2950p' Sources/GhosttyTerminalView.swift | grep -B 5 -A 10 "target.target"Repository: manaflow-ai/cmux Length of output: 42 🏁 Script executed: # Check if there are any other uses of target in handleAction
rg "private func handleAction" -A 200 Sources/GhosttyTerminalView.swift | grep "target\." | head -20Repository: manaflow-ai/cmux Length of output: 215 🏁 Script executed: # Verify the exact pattern used in clipboard callback - does it capture the source surface?
sed -n '2380,2450p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 3253 🏁 Script executed: # Get the full clipboard callback pattern to see how it captures and validates
sed -n '2380,2430p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 2510 🏁 Script executed: # Check the deinit to understand surface lifecycle risks
sed -n '1050,1130p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 3135 🏁 Script executed: # See if surfaces can actually be replaced during operation
rg "surface = nil" Sources/GhosttyTerminalView.swift -B 3 -A 3Repository: manaflow-ai/cmux Length of output: 1066 🏁 Script executed: # Verify if liveSurfaceForGhosttyAccess is the proper validation function
sed -n '970,1010p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 1998 🏁 Script executed: # Get the full read_clipboard_cb to see the capture + validate pattern
sed -n '2345,2430p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 4113 🏁 Script executed: # Verify the surfaces ARE actually changed/replaced during normal operation
rg "self.surface = " Sources/GhosttyTerminalView.swift | head -20Repository: manaflow-ai/cmux Length of output: 238 🏁 Script executed: # Check if terminalSurface reference is stable even if its .surface property changes
rg "class TerminalSurface" -A 30 Sources/GhosttyTerminalView.swift | head -50Repository: manaflow-ai/cmux Length of output: 1624 🏁 Script executed: # Find the complete clipboard callback including the validation pattern
sed -n '2545,2620p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 3622 🏁 Script executed: # Verify the exact pattern for capturing and validating in async closures
rg -B 8 -A 12 "runtimeSurface == requestSurface" Sources/GhosttyTerminalView.swift | head -50Repository: manaflow-ai/cmux Length of output: 1073 🏁 Script executed: # Check what happens if we try to access target in the async closure - is it captured?
sed -n '2863,2910p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 2402 🏁 Script executed: # Look for other places where target.target.surface is captured before async dispatch
rg -B 5 -A 15 "let.*=.*target.target.surface" Sources/GhosttyTerminalView.swift | head -80Repository: manaflow-ai/cmux Length of output: 944 🏁 Script executed: # Verify if the `target` parameter can be captured in the closure or goes out of scope
sed -n '2820,2870p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 2575 🏁 Script executed: # Check lifecycle: can a TerminalSurface's ghostty_surface_t pointer change between callback and async closure?
rg -B 3 -A 5 "hasLiveSurface\|cmuxSurfacePointerAppearsLive" Sources/GhosttyTerminalView.swift | head -60Repository: manaflow-ai/cmux Length of output: 42 🏁 Script executed: # Check if target is available at the time of DispatchQueue.main.async or if it goes out of scope
sed -n '2840,2880p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 2334 🏁 Script executed: # Look at the signature and see if target is passed by value (can be captured) or reference
rg "func handleAction" -A 5 Sources/GhosttyTerminalView.swift | head -15Repository: manaflow-ai/cmux Length of output: 462 🏁 Script executed: # Verify what ghostty_target_s is and if it can be safely captured
rg -B 2 -A 2 "ghostty_target_s" Sources/GhosttyTerminalView.swift | head -30Repository: manaflow-ai/cmux Length of output: 528 🏁 Script executed: # Check if surfaces are actually torn down/reparented in normal operation
rg "forceRefreshSurface\|teardown\|reparent" Sources/GhosttyTerminalView.swift | head -20Repository: manaflow-ai/cmux Length of output: 42 🏁 Script executed: # Verify the learning about surface re-reading after async operations
sed -n '1,100p' Sources/GhosttyTerminalView.swift | grep -A 10 "Learning:"Repository: manaflow-ai/cmux Length of output: 42 🏁 Script executed: # Check how liveSurfaceForGhosttyAccess handles stale surfaces
sed -n '970,1000p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 1629 🏁 Script executed: # Look at the full liveSurfaceForGhosttyAccess implementation to understand the validation
sed -n '900,950p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 1816 🏁 Script executed: # Check if target can actually be captured in a closure (it's a struct)
rg "ghostty_target" Sources/GhosttyTerminalView.swift -B 2 -A 2 | grep -A 5 "struct\|class"Repository: manaflow-ai/cmux Length of output: 42 🏁 Script executed: # Look for examples where target parameters ARE captured before async
rg -B 8 -A 8 "let.*=.*target\." Sources/GhosttyTerminalView.swift | head -40Repository: manaflow-ai/cmux Length of output: 1558 🏁 Script executed: # Verify the learning about surface reparenting to understand if this is a real scenario
rg "forceRefreshSurface\|teardown\|initial-present recovery" Sources/GhosttyTerminalView.swift -B 2 -A 5 | head -40Repository: manaflow-ai/cmux Length of output: 42 🏁 Script executed: # Check if target can be captured and if target.target.surface is a valid pointer at closure time
sed -n '2415,2445p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 1355 🏁 Script executed: # Check if there are actual examples of surface rebinding during operation
rg "surface = ghostty_surface_new\|surface = nil" Sources/GhosttyTerminalView.swift -B 3 -A 1Repository: manaflow-ai/cmux Length of output: 42 🏁 Script executed: # Verify if requestBackgroundSurfaceStartIfNeeded or similar can change surfaces
sed -n '1250,1320p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 3139 🏁 Script executed: # Verify that target (a struct parameter) can actually be captured in the closure
python3 << 'EOF'
# Swift struct parameters can be captured in closures - this is standard Swift
# The ghostty_target_s is a struct, so target.target.surface is a value that can be captured
print("Swift closure capture behavior:")
print("✓ Function parameters (value types like structs) CAN be captured")
print("✓ target.target.surface is a raw pointer value that can be captured")
print("✓ The pointer value remains valid across the async dispatch")
EOFRepository: manaflow-ai/cmux Length of output: 284 🏁 Script executed: # Verify the lifecycle: do surfaces actually get rebound to new pointers?
rg "requestBackgroundSurfaceStartIfNeeded\|surface.*ghostty_surface_new" Sources/GhosttyTerminalView.swift -B 2 -A 5 | head -40Repository: manaflow-ai/cmux Length of output: 42 🏁 Script executed: # Final check: look at a complete timeline - does terminalSurface.surface actually get replaced?
sed -n '960,1020p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 3063 🏁 Script executed: # Verify the complete flow: callback receives target at callback time, then async dispatch happens
sed -n '2655,2710p' Sources/GhosttyTerminalView.swiftRepository: manaflow-ai/cmux Length of output: 3047 Guard tmux callbacks against stale/replaced runtime surfaces. Line 2866 dispatches to the main queue, but line 2880 then re-reads Suggested guard case GHOSTTY_ACTION_TMUX_STATE:
guard let terminalSurface = surfaceView.terminalSurface else { return true }
let tmuxState = action.action.tmux_state
+ let sourceSurface = target.target.surface
DispatchQueue.main.async {
+ let liveSurface = MainActor.assumeIsolated {
+ terminalSurface.liveSurfaceForGhosttyAccess(reason: "tmux.state")
+ }
+ guard let liveSurface, liveSurface == sourceSurface else { return }
switch tmuxState {
case GHOSTTY_TMUX_STATE_ENTERED:
terminalSurface.tmuxActive = true
`#if` DEBUG
dlog("tmux.entered tab=\(surfaceView.tabId?.uuidString.prefix(5) ?? "nil") surface=\(terminalSurface.id.uuidString.prefix(5))")
`#endif`
case GHOSTTY_TMUX_STATE_EXITED:
terminalSurface.tmuxActive = false
`#if` DEBUG
dlog("tmux.exited tab=\(surfaceView.tabId?.uuidString.prefix(5) ?? "nil") surface=\(terminalSurface.id.uuidString.prefix(5))")
`#endif`
case GHOSTTY_TMUX_STATE_WINDOWS_CHANGED:
// Auto-set the renderer to the first pane so we can see tmux output
- if let surface = terminalSurface.surface {
- let paneCount = ghostty_surface_tmux_pane_count(surface)
- if paneCount > 0 {
- var paneId: UInt = 0
- let idCount = ghostty_surface_tmux_pane_ids(surface, &paneId, 1)
- if idCount > 0 {
- let success = ghostty_surface_tmux_set_active_pane(surface, paneId)
- `#if` DEBUG
- dlog("tmux.windowsChanged panes=\(paneCount) activePaneId=\(paneId) setActive=\(success)")
- `#endif`
- }
- }
+ let paneCount = ghostty_surface_tmux_pane_count(liveSurface)
+ if paneCount > 0 {
+ var paneId: UInt = 0
+ let idCount = ghostty_surface_tmux_pane_ids(liveSurface, &paneId, 1)
+ if idCount > 0 {
+ let success = ghostty_surface_tmux_set_active_pane(liveSurface, paneId)
+ `#if` DEBUG
+ dlog("tmux.windowsChanged panes=\(paneCount) activePaneId=\(paneId) setActive=\(success)")
+ `#endif`
+ }
}
default:
break
}
NotificationCenter.default.post(
name: .ghosttyTmuxStateChanged,
object: terminalSurface,
userInfo: ["state": tmuxState]
)
}🤖 Prompt for AI Agents |
||
| return true | ||
| default: | ||
| return false | ||
| } | ||
|
|
@@ -3130,6 +3168,8 @@ final class TerminalSurface: Identifiable, ObservableObject { | |
| } | ||
| } | ||
| @Published private(set) var keyboardCopyModeActive: Bool = false | ||
| /// Whether this surface is currently in tmux control mode (DCS 1000p). | ||
|
Comment on lines
3170
to
+3171
Contributor
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.
Neighbouring state flags use @Published private(set) var keyboardCopyModeActive: Bool = false
@Published var tmuxActive: Bool = falseBecause the setter is called from inside |
||
| @Published var tmuxActive: Bool = false | ||
| private var searchNeedleCancellable: AnyCancellable? | ||
| var currentKeyStateIndicatorText: String? { surfaceView.currentKeyStateIndicatorText } | ||
|
|
||
|
|
@@ -7185,6 +7225,7 @@ extension Notification.Name { | |
| static let ghosttySearchFocus = Notification.Name("ghosttySearchFocus") | ||
| static let ghosttyConfigDidReload = Notification.Name("ghosttyConfigDidReload") | ||
| static let ghosttyDefaultBackgroundDidChange = Notification.Name("ghosttyDefaultBackgroundDidChange") | ||
| static let ghosttyTmuxStateChanged = Notification.Name("ghosttyTmuxStateChanged") | ||
| static let browserSearchFocus = Notification.Name("browserSearchFocus") | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5539,6 +5539,9 @@ final class Workspace: Identifiable, ObservableObject { | |
| @Published private(set) var tmuxWorkspaceFlashPanelId: UUID? | ||
| @Published private(set) var tmuxWorkspaceFlashReason: WorkspaceAttentionFlashReason? | ||
| @Published private(set) var tmuxWorkspaceFlashToken: UInt64 = 0 | ||
| /// Whether any terminal panel in this workspace is in tmux control mode. | ||
| @Published private(set) var tmuxControlModeActive: Bool = false | ||
| private var tmuxControlModeSubscription: AnyCancellable? | ||
|
Comment on lines
+5542
to
+5544
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. 🧩 Analysis chain🏁 Script executed: # First, let's find and inspect the relevant lines in Workspace.swift
wc -l Sources/Workspace.swiftRepository: manaflow-ai/cmux Length of output: 89 🏁 Script executed: # Read the section around lines 5542-5544 to see the property definition
sed -n '5535,5550p' Sources/Workspace.swiftRepository: manaflow-ai/cmux Length of output: 1191 🏁 Script executed: # Read the section around lines 6050-6058 to see the subscription setup
sed -n '6040,6070p' Sources/Workspace.swiftRepository: manaflow-ai/cmux Length of output: 1351 🏁 Script executed: # Search for the entire configureTerminalPanel method to understand the subscription logic
rg -A 30 'func configureTerminalPanel' Sources/Workspace.swiftRepository: manaflow-ai/cmux Length of output: 1406 🏁 Script executed: # Look for panel removal/teardown paths to see if subscriptions are cleaned up
rg -B 3 -A 5 'panelSubscriptions.*removeValue|panel.*deinit|removeTerminalPanel|panelWasRemoved' Sources/Workspace.swift | head -100Repository: manaflow-ai/cmux Length of output: 2044 🏁 Script executed: # Check if Workspace supports multiple terminals by looking at panel management
rg -A 3 'var panels|var panelSubscriptions' Sources/Workspace.swift | head -30Repository: manaflow-ai/cmux Length of output: 410 🏁 Script executed: # Search for calls to configureTerminalPanel to verify it's invoked per terminal
rg 'configureTerminalPanel\(' Sources/Workspace.swiftRepository: manaflow-ai/cmux Length of output: 404 🏁 Script executed: # Check methods that add new terminals to the workspace
rg -B 5 -A 10 'func newTerminalSplit|func newTerminalSurface|func createReplacement' Sources/Workspace.swift | head -80Repository: manaflow-ai/cmux Length of output: 2540 🏁 Script executed: # Verify the exact flow: when a terminal is added, is configureTerminalPanel called?
rg -B 3 -A 3 'configureTerminalPanel.*terminalPanel' Sources/Workspace.swiftRepository: manaflow-ai/cmux Length of output: 842 🏁 Script executed: # Check if there are any other tmux-related subscriptions or if this is the only place
rg 'tmuxControlMode|tmuxActive' Sources/Workspace.swiftRepository: manaflow-ai/cmux Length of output: 345 Aggregate tmux state across all terminal panels, not just the last one.
🐛 Suggested direction- private var tmuxControlModeSubscription: AnyCancellable?
+ private var tmuxControlModeSubscriptions: [UUID: AnyCancellable] = [:]
+
+ private func recomputeTmuxControlModeActive() {
+ tmuxControlModeActive = panels.values.contains { ($0 as? TerminalPanel)?.tmuxActive == true }
+ }- let subscription = terminalPanel.$tmuxActive
+ tmuxControlModeSubscriptions[terminalPanel.id] = terminalPanel.$tmuxActive
.removeDuplicates()
.receive(on: DispatchQueue.main)
- .sink { [weak self] active in
- self?.tmuxControlModeActive = active
+ .sink { [weak self] _ in
+ self?.recomputeTmuxControlModeActive()
}
- // Store the subscription (replacing any prior one)
- tmuxControlModeSubscription = subscription
+ recomputeTmuxControlModeActive()Also remove the terminal's cancellable and call 🤖 Prompt for AI Agents |
||
| private var manualUnreadMarkedAt: [UUID: Date] = [:] | ||
| nonisolated private static let manualUnreadFocusGraceInterval: TimeInterval = 0.2 | ||
| nonisolated private static let manualUnreadClearDelayAfterFocusFlash: TimeInterval = 0.2 | ||
|
|
@@ -6043,6 +6046,16 @@ final class Workspace: Identifiable, ObservableObject { | |
| guard let self, let terminalPanel else { return } | ||
| self.triggerWorkspacePaneFlash(panelId: terminalPanel.id, reason: reason) | ||
| } | ||
|
|
||
| // Subscribe to tmux control mode state changes | ||
| let subscription = terminalPanel.$tmuxActive | ||
| .removeDuplicates() | ||
| .receive(on: DispatchQueue.main) | ||
| .sink { [weak self] active in | ||
| self?.tmuxControlModeActive = active | ||
|
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. P2: tmuxControlModeActive only reflects the last configured TerminalPanel. It can flip to false while another panel is still in control mode and won’t update when earlier panels change. Track tmuxActive per panel and derive Prompt for AI agents |
||
| } | ||
| // Store the subscription (replacing any prior one) | ||
| tmuxControlModeSubscription = subscription | ||
|
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.
Useful? React with 👍 / 👎.
Comment on lines
+6050
to
+6058
Contributor
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.
Concrete failure scenario with panels A and B in the same workspace:
The property's doc-comment says "Whether any terminal panel in this workspace is in tmux control mode", but the implementation only tracks the last configured panel. Fix: track subscriptions per-panel in the existing
Comment on lines
+6057
to
+6058
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.
Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| private func triggerWorkspacePaneFlash(panelId: UUID, reason: WorkspaceAttentionFlashReason) { | ||
|
|
||
| +35 −0 | include/ghostty.h | |
| +70 −0 | src/Surface.zig | |
| +22 −0 | src/apprt/action.zig | |
| +63 −0 | src/apprt/embedded.zig | |
| +3 −0 | src/apprt/surface.zig | |
| +68 −4 | src/terminal/tmux/viewer.zig | |
| +71 −5 | src/termio/stream_handler.zig |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,3 +10,4 @@ c47010b80cd9ae6d1ab744c120f011a465521ea3 d6904870a3c920b2787b1c4b950cfdef232606b | |
| bc9be90a21997a4e5f06bf15ae2ec0f937c2dc42 6b83b66768e8bba871a3753ae8ffbaabd03370b306c429cd86c9cdcc8db82589 | ||
| 41e796064e89eacabdf3a6729475e250a5518e7a 135302bbdf3e83b200f0165ff2a32cdf13017219e6c8ffca17b672edfbfae395 | ||
| f9030b5c5232db69ba8625bb53d51ce735b80d51 6c439d731d97bd35a3289f54478af3ac01e30ba74ec2672490e0c98f95262b55 | ||
| 4c170d120d5bc87d039fab75c1f2cfeed1951108 56e1644d1e9020edd2c41e326df03d00387538371fc5498a4febcf9d17b4d7de | ||
|
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.
Useful? React with 👍 / 👎. |
||
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.
windows_changedalways snaps the renderer to pane index 0Every
GHOSTTY_TMUX_STATE_WINDOWS_CHANGEDevent callsghostty_surface_tmux_pane_ids(surface, &paneId, 1)to grab one ID and immediately makes it the active renderer. "First pane" here means whatever ordering the C API returns, with no memory of which pane was previously active.In practice this fires on any tmux layout change (new window, pane split, pane close). Each such event will silently jump the user's view to pane 0, even if they were looking at a different pane a moment before. Consider tracking the last-set active pane and only auto-switching when the previously active pane is no longer present (i.e. it was deleted).