Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ public enum ControlCommandExecutionPolicy: Sendable, Equatable {
/// - Parameter method: The trimmed method name.
public init(forMethod method: String) {
#if DEBUG
if method == "remote.tmux.test_exec" || method == "remote.tmux.test_set_frame" { self = .socketWorker(mainThreadCallable: false); return }
if method == "remote.tmux.test_exec" || method == "remote.tmux.test_set_frame"
|| method == "remote.tmux.sizing_settled" {
self = .socketWorker(mainThreadCallable: false)
return
}
#endif
if method.hasPrefix("vm.") || method.hasPrefix("remotes.") || method.hasPrefix("aiAccounts.")
|| Self.socketWorkerMethods.contains(method) {
Expand Down
19 changes: 19 additions & 0 deletions Sources/App/CmuxMainWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,25 @@ func configureCmuxMainWindowDragBehavior(_ window: NSWindow) {
@MainActor
final class CmuxMainWindow: NSWindow {

/// No content may resize this window past its display. The content view
/// hosts AppKit subtrees whose subviews carry REQUIRED autoresizing-mask
/// constraints, and if any of them is ever laid out oversized, AppKit
/// satisfies those constraints by growing the WINDOW — and since this
/// window is non-movable, nothing ever constrains it back. A layout bug
/// then compounds through everything derived from window geometry
/// (observed live: the window at 29,000 points wide, growing every
/// pass). The user sizes this window; layout does not.
override func setFrame(_ frameRect: NSRect, display flag: Bool) {
var frame = frameRect
if !styleMask.contains(.fullScreen),
let limit = (screen ?? NSScreen.main)?.frame,
limit.width > 1, limit.height > 1 {
frame.size.width = min(frame.size.width, limit.width)
frame.size.height = min(frame.size.height, limit.height)
}
super.setFrame(frame, display: flag)
}

static var minimumContentSize: NSSize {
NSSize(
width: CGFloat(SessionPersistencePolicy.minimumWindowWidth),
Expand Down
19 changes: 18 additions & 1 deletion Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3546,7 +3546,24 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
displaySnapshot: displaySnapshot,
targetDisplay: targetDisplay
) {
return frame
// Same display, same geometry: keep the exact frame — unless
// its SIZE exceeds the display itself. No user interaction
// produces a window larger than its own screen (interactive
// resizing and zoom are both display-bounded), so such a
// frame can only be programmatic growth — a content-sizing
// feedback loop once persisted a 13,000-point-wide window
// here — and restoring it verbatim resurrects the pathology
// on every launch.
if frame.width <= targetDisplay.visibleFrame.width + 1,
frame.height <= targetDisplay.visibleFrame.height + 1 {
return frame
}
return clampFrame(
frame,
within: targetDisplay.visibleFrame,
minWidth: minWidth,
minHeight: minHeight
)
}
return resolvedWindowFrame(
frame: frame,
Expand Down
19 changes: 19 additions & 0 deletions Sources/GhosttyTerminalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12176,6 +12176,19 @@ struct GhosttyTerminalView: NSViewRepresentable {
TerminalWindowPortalRegistry.synchronizeForAnchor(host, syncLayout: false)
}

// A terminal is space-filling: it renders in whatever space its
// container gives it and must never answer a size proposal with a
// content-derived ideal. Without this, SwiftUI's default representable
// sizing falls through to AppKit's fittingSize, and the portal-hosted
// terminal view's autoresizing constraints make that the HOSTED view's
// current frame — so any ancestor that honors ideals adopts the
// terminal's own layout as its size and hands it back grown (observed
// live as a mirror container inflating 19 points per layout pass inside
// a normal-sized window).
func sizeThatFits(_ proposal: ProposedViewSize, nsView: NSView, context: Context) -> CGSize? {
CGSize(width: proposal.width ?? 0, height: proposal.height ?? 0)
}

func makeNSView(context: Context) -> NSView {
let container = HostContainerView(frame: .zero)
container.wantsLayer = false
Expand Down Expand Up @@ -12317,6 +12330,12 @@ struct GhosttyTerminalView: NSViewRepresentable {
hostedView.setVisibleInUI(coordinator.desiredIsVisibleInUI)
hostedView.setActive(coordinator.desiredIsActive)
hostedView.setNotificationRing(visible: coordinator.desiredShowsUnreadNotificationRing)
// A grid resize that applied while this view was between
// windows (portal churn during a tab switch) owed its size
// report; entering a window is the moment it becomes
// deliverable. Without this, the listener's rendered-grid
// bookkeeping stays frozen at the pre-detach grid forever.
terminalSurface.flushPendingManualSizeReportIfAttached()
}
host.onGeometryChanged = { [weak host, weak hostedView, weak coordinator] in
guard let host, let hostedView, let coordinator else { return }
Expand Down
17 changes: 17 additions & 0 deletions Sources/RemoteTmuxLayoutNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,20 @@ struct RemoteTmuxLayoutNode: Sendable, Equatable, Codable {
}
}
}


extension RemoteTmuxLayoutNode {
/// The leaf carrying `paneId`, or nil — a lookup for probes that compare
/// a pane's rendered grid against its assigned span.
func firstLeaf(withPaneId paneId: Int) -> RemoteTmuxLayoutNode? {
switch content {
case .pane(let id):
return id == paneId ? self : nil
case .horizontal(let children), .vertical(let children):
for child in children {
if let found = child.firstLeaf(withPaneId: paneId) { return found }
}
return nil
}
}
}
16 changes: 0 additions & 16 deletions Sources/RemoteTmuxMirrorFrames.swift

This file was deleted.

Loading