Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
510 changes: 510 additions & 0 deletions Resources/Localizable.xcstrings

Large diffs are not rendered by default.

63 changes: 56 additions & 7 deletions Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8560,12 +8560,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
// On the VM, launching/initializing multiple windows can occasionally take longer than a
// few seconds; keep the deadline generous so the test doesn't flake.
let deadline = Date().addingTimeInterval(20.0)
func hasMainTerminalWindow() -> Bool {
NSApp.windows.contains { window in
func mainTerminalWindow() -> NSWindow? {
NSApp.windows.first { window in
guard let raw = window.identifier?.rawValue else { return false }
return raw == "cmux.main" || raw.hasPrefix("cmux.main.")
}
}
func hasMainTerminalWindow() -> Bool {
mainTerminalWindow() != nil
}

func runSetupWhenWindowReady() {
guard Date() < deadline else {
Expand All @@ -8580,6 +8583,34 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
}
guard let tabManager = self.tabManager else { return }

// Keep the app front-most for deterministic XCUI key routing on CI runners.
NSApp.unhide(nil)
NSApp.activate(ignoringOtherApps: true)
if let window = mainTerminalWindow() {
window.makeKeyAndOrderFront(nil)
window.orderFrontRegardless()
}

// Re-apply these at setup time. Startup config loading can overwrite earlier values.
if !useGhosttyConfig {
KeyboardShortcutSettings.setShortcut(
StoredShortcut(key: "h", command: true, shift: false, option: false, control: true),
for: .focusLeft
)
KeyboardShortcutSettings.setShortcut(
StoredShortcut(key: "l", command: true, shift: false, option: false, control: true),
for: .focusRight
)
KeyboardShortcutSettings.setShortcut(
StoredShortcut(key: "k", command: true, shift: false, option: false, control: true),
for: .focusUp
)
KeyboardShortcutSettings.setShortcut(
StoredShortcut(key: "j", command: true, shift: false, option: false, control: true),
for: .focusDown
)
}

let tab = tabManager.addTab()
guard let initialPanelId = tab.focusedPanelId else {
self.writeGotoSplitTestData(["setupError": "Missing initial panel id"])
Expand All @@ -8588,14 +8619,31 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent

let requestedBrowserURL = env["CMUX_UI_TEST_GOTO_SPLIT_BROWSER_URL"]?
.trimmingCharacters(in: .whitespacesAndNewlines)
let defaultBrowserURL: URL = {
let html = """
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>cmux-goto-split</title>
</head>
<body tabindex="-1">
<main>cmux goto split ui test</main>
<script>
window.addEventListener('load', () => {
try { document.body.focus(); } catch (_) {}
});
</script>
</body>
</html>
"""
let encoded = Data(html.utf8).base64EncodedString()
return URL(string: "data:text/html;base64,\(encoded)") ?? URL(string: "about:blank")!
}()
let url = requestedBrowserURL.flatMap { rawURL in
guard !rawURL.isEmpty else { return nil }
return URL(string: rawURL)
} ?? URL(string: "https://example.com")
guard let url else {
self.writeGotoSplitTestData(["setupError": "Invalid browser URL"])
return
}
} ?? defaultBrowserURL
guard let browserPanelId = tabManager.newBrowserSplit(
tabId: tab.id,
fromPanelId: initialPanelId,
Expand Down Expand Up @@ -8877,6 +8925,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
}

tab.focusPanel(browserPanelId)
_ = panel.requestExplicitWebViewFocus()

guard isWebViewFocused(panel),
let (browserPaneId, terminalPaneId) = paneIdsForGotoSplitUITest(
Expand Down
2 changes: 2 additions & 0 deletions Sources/CmuxConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -248,12 +248,14 @@ struct CmuxSurfaceDefinition: Codable, Sendable {
var cwd: String?
var env: [String: String]?
var url: String?
var vnc: String?
var focus: Bool?
}

enum CmuxSurfaceType: String, Codable, Sendable {
case terminal
case browser
case vnc
}

@MainActor
Expand Down
19 changes: 19 additions & 0 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2050,6 +2050,8 @@ struct ContentView: View {
targetView = terminal.hostedView
case let browser as BrowserPanel:
targetView = browser.webView
case let vnc as VncPanel:
targetView = vnc.nativeSessionHostView ?? vnc.webView
default:
targetView = nil
}
Expand Down Expand Up @@ -6225,6 +6227,8 @@ struct ContentView: View {
return String(localized: "commandPalette.kind.terminal", defaultValue: "Terminal")
case .browser:
return String(localized: "commandPalette.kind.browser", defaultValue: "Browser")
case .vnc:
return String(localized: "commandPalette.kind.vnc", defaultValue: "VNC")
case .markdown:
return String(localized: "commandPalette.kind.markdown", defaultValue: "Markdown")
}
Expand All @@ -6236,6 +6240,8 @@ struct ContentView: View {
return ["terminal", "shell", "console"]
case .browser:
return ["browser", "web", "page"]
case .vnc:
return ["vnc", "remote", "screen", "desktop"]
case .markdown:
return ["markdown", "note", "preview"]
}
Expand Down Expand Up @@ -6607,6 +6613,14 @@ struct ContentView: View {
keywords: ["new", "browser", "tab", "web"]
)
)
contributions.append(
CommandPaletteCommandContribution(
commandId: "palette.newVncTab",
title: constant(String(localized: "command.newVncTab.title", defaultValue: "New Tab (VNC)")),
subtitle: constant(String(localized: "command.newVncTab.subtitle", defaultValue: "Tab")),
keywords: ["new", "vnc", "remote", "desktop", "screen"]
)
)
contributions.append(
CommandPaletteCommandContribution(
commandId: "palette.closeTab",
Expand Down Expand Up @@ -7329,6 +7343,11 @@ struct ContentView: View {
_ = AppDelegate.shared?.openBrowserAndFocusAddressBar()
}
}
registry.register(commandId: "palette.newVncTab") {
DispatchQueue.main.async {
_ = tabManager.openVnc()
}
}
registry.register(commandId: "palette.closeTab") {
tabManager.closeCurrentPanelWithConfirmation()
}
Expand Down
Loading
Loading