diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 5c0a2fd2..9de1ca18 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -1864,10 +1864,13 @@ struct CMUXCLI { case "trigger-flash": let tfWsFlag = optionValue(commandArgs, name: "--workspace") let explicitWorkspaceArg = tfWsFlag - let callerWorkspaceArg = windowId == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil + let preferTTYFallback = windowId == nil && ProcessInfo.processInfo.environment["TMUX"] != nil + let callerWorkspaceArg = preferTTYFallback + ? nil + : (windowId == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil) let workspaceArg = explicitWorkspaceArg ?? callerWorkspaceArg let explicitSurfaceArg = optionValue(commandArgs, name: "--surface") ?? optionValue(commandArgs, name: "--panel") - let callerSurfaceArg = explicitWorkspaceArg == nil && windowId == nil + let callerSurfaceArg = explicitSurfaceArg == nil && preferTTYFallback == false && windowId == nil ? ProcessInfo.processInfo.environment["CMUX_SURFACE_ID"] : nil let surfaceArg = explicitSurfaceArg ?? callerSurfaceArg @@ -2084,10 +2087,13 @@ struct CMUXCLI { let body = optionValue(commandArgs, name: "--body") ?? "" let explicitWorkspaceArg = optionValue(commandArgs, name: "--workspace") - let callerWorkspaceArg = windowId == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil + let preferTTYFallback = windowId == nil && ProcessInfo.processInfo.environment["TMUX"] != nil + let callerWorkspaceArg = preferTTYFallback + ? nil + : (windowId == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil) let workspaceArg = explicitWorkspaceArg ?? callerWorkspaceArg let explicitSurfaceArg = optionValue(commandArgs, name: "--surface") - let callerSurfaceArg = explicitWorkspaceArg == nil && windowId == nil + let callerSurfaceArg = explicitSurfaceArg == nil && preferTTYFallback == false && windowId == nil ? ProcessInfo.processInfo.environment["CMUX_SURFACE_ID"] : nil let surfaceArg = explicitSurfaceArg ?? callerSurfaceArg diff --git a/Resources/shell-integration/cmux-bash-integration.bash b/Resources/shell-integration/cmux-bash-integration.bash index 0e6c8b68..db9ae9f3 100644 --- a/Resources/shell-integration/cmux-bash-integration.bash +++ b/Resources/shell-integration/cmux-bash-integration.bash @@ -76,6 +76,10 @@ _CMUX_TMUX_SYNC_KEYS=( CMUX_TAG CMUX_WORKSPACE_ID ) +_CMUX_TMUX_SURFACE_SCOPED_KEYS=( + CMUX_PANEL_ID + CMUX_SURFACE_ID +) _cmux_tmux_sync_key_is_managed() { local candidate="$1" @@ -116,6 +120,10 @@ _cmux_tmux_publish_cmux_environment() { tmux set-environment -g "$key" "$value" >/dev/null 2>&1 || return 0 done + for key in "${_CMUX_TMUX_SURFACE_SCOPED_KEYS[@]}"; do + tmux set-environment -gu "$key" >/dev/null 2>&1 || return 0 + done + _CMUX_TMUX_PUSH_SIGNATURE="$signature" } @@ -205,17 +213,32 @@ _cmux_git_head_signature() { printf '%s\n' "$line" } +_cmux_report_tty_payload() { + [[ -n "$CMUX_TAB_ID" ]] || return 0 + [[ -n "$_CMUX_TTY_NAME" ]] || return 0 + + local payload="report_tty $_CMUX_TTY_NAME --tab=$CMUX_TAB_ID" + if [[ -z "$TMUX" ]]; then + [[ -n "$CMUX_PANEL_ID" ]] || return 0 + payload+=" --panel=$CMUX_PANEL_ID" + fi + + printf '%s\n' "$payload" +} + _cmux_report_tty_once() { # Send the TTY name to the app once per session so the batched port scanner # knows which TTY belongs to this panel. (( _CMUX_TTY_REPORTED )) && return 0 [[ -S "$CMUX_SOCKET_PATH" ]] || return 0 - [[ -n "$CMUX_TAB_ID" ]] || return 0 - [[ -n "$CMUX_PANEL_ID" ]] || return 0 - [[ -n "$_CMUX_TTY_NAME" ]] || return 0 + + local payload="" + payload="$(_cmux_report_tty_payload)" + [[ -n "$payload" ]] || return 0 + _CMUX_TTY_REPORTED=1 { - _cmux_send "report_tty $_CMUX_TTY_NAME --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" + _cmux_send "$payload" } >/dev/null 2>&1 & disown } diff --git a/Resources/shell-integration/cmux-zsh-integration.zsh b/Resources/shell-integration/cmux-zsh-integration.zsh index 0a0823bb..1364f4a6 100644 --- a/Resources/shell-integration/cmux-zsh-integration.zsh +++ b/Resources/shell-integration/cmux-zsh-integration.zsh @@ -82,6 +82,10 @@ typeset -ga _CMUX_TMUX_SYNC_KEYS=( CMUX_TAG CMUX_WORKSPACE_ID ) +typeset -ga _CMUX_TMUX_SURFACE_SCOPED_KEYS=( + CMUX_PANEL_ID + CMUX_SURFACE_ID +) _cmux_tmux_sync_key_is_managed() { local candidate="$1" @@ -119,6 +123,10 @@ _cmux_tmux_publish_cmux_environment() { tmux set-environment -g "$key" "$value" >/dev/null 2>&1 || return 0 done + for key in "${_CMUX_TMUX_SURFACE_SCOPED_KEYS[@]}"; do + tmux set-environment -gu "$key" >/dev/null 2>&1 || return 0 + done + _CMUX_TMUX_PUSH_SIGNATURE="$signature" } @@ -305,17 +313,32 @@ _cmux_git_head_signature() { return 1 } +_cmux_report_tty_payload() { + [[ -n "$CMUX_TAB_ID" ]] || return 0 + [[ -n "$_CMUX_TTY_NAME" ]] || return 0 + + local payload="report_tty $_CMUX_TTY_NAME --tab=$CMUX_TAB_ID" + if [[ -z "$TMUX" ]]; then + [[ -n "$CMUX_PANEL_ID" ]] || return 0 + payload+=" --panel=$CMUX_PANEL_ID" + fi + + print -r -- "$payload" +} + _cmux_report_tty_once() { # Send the TTY name to the app once per session so the batched port scanner # knows which TTY belongs to this panel. (( _CMUX_TTY_REPORTED )) && return 0 [[ -S "$CMUX_SOCKET_PATH" ]] || return 0 - [[ -n "$CMUX_TAB_ID" ]] || return 0 - [[ -n "$CMUX_PANEL_ID" ]] || return 0 - [[ -n "$_CMUX_TTY_NAME" ]] || return 0 + + local payload="" + payload="$(_cmux_report_tty_payload)" + [[ -n "$payload" ]] || return 0 + _CMUX_TTY_REPORTED=1 { - _cmux_send "report_tty $_CMUX_TTY_NAME --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" + _cmux_send "$payload" } >/dev/null 2>&1 &! } diff --git a/cmuxTests/GhosttyConfigTests.swift b/cmuxTests/GhosttyConfigTests.swift index bb16f7dd..bffec957 100644 --- a/cmuxTests/GhosttyConfigTests.swift +++ b/cmuxTests/GhosttyConfigTests.swift @@ -2526,51 +2526,21 @@ final class ZshShellIntegrationHandoffTests: XCTestCase { } func testShellIntegrationReportsTTYFromTmuxWithoutUsingPanelScope() throws { - let fileManager = FileManager.default - let root = fileManager.temporaryDirectory - .appendingPathComponent("cmux-zsh-tmux-report-tty-\(UUID().uuidString)") - let binDir = root.appendingPathComponent("bin", isDirectory: true) - let socketPath = root.appendingPathComponent("cmux-test.sock", isDirectory: false) - let logPath = root.appendingPathComponent("tty.log", isDirectory: false) - - try fileManager.createDirectory(at: root, withIntermediateDirectories: true) - try fileManager.createDirectory(at: binDir, withIntermediateDirectories: true) - let listenerFD = try bindUnixSocket(at: socketPath.path) - defer { - Darwin.close(listenerFD) - unlink(socketPath.path) - try? fileManager.removeItem(at: root) - } - - try writeExecutableScript( - at: binDir.appendingPathComponent("ncat", isDirectory: false), - contents: """ - #!/bin/sh - cat > "\(logPath.path)" - exit 0 - """ - ) - - _ = try runInteractiveZsh( + let output = try runInteractiveZsh( cmuxLoadGhosttyIntegration: false, cmuxLoadShellIntegration: true, command: """ _CMUX_TTY_NAME=ttys999 - _cmux_report_tty_once - sleep 0.05 - print -r -- READY + print -r -- "$(_cmux_report_tty_payload)" """, extraEnvironment: [ - "PATH": "\(binDir.path):/usr/bin:/bin:/usr/sbin:/sbin", "TMUX": "/tmp/tmux-current,123,0", - "CMUX_SOCKET_PATH": socketPath.path, "CMUX_TAB_ID": "11111111-1111-1111-1111-111111111111", "CMUX_PANEL_ID": "99999999-9999-9999-9999-999999999999", ] ) - let log = (try? String(contentsOf: logPath, encoding: .utf8)) ?? "" - XCTAssertEqual(log, "report_tty ttys999 --tab=11111111-1111-1111-1111-111111111111\n") + XCTAssertEqual(output, "report_tty ttys999 --tab=11111111-1111-1111-1111-111111111111") } private func runInteractiveZsh(cmuxLoadGhosttyIntegration: Bool) throws -> String {