Skip to content
8 changes: 5 additions & 3 deletions CLI/cmuxterm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,12 @@ struct CMUXCLI {
print(response)

case "new-split":
guard let direction = commandArgs.first else {
let (panelArg, remaining) = parseOption(commandArgs, name: "--panel")
guard let direction = remaining.first else {
throw CLIError(message: "new-split requires a direction")
}
let response = try client.send(command: "new_split \(direction)")
let cmd = panelArg != nil ? "new_split \(direction) \(panelArg!)" : "new_split \(direction)"
let response = try client.send(command: cmd)
print(response)

case "list-panels":
Expand Down Expand Up @@ -509,7 +511,7 @@ struct CMUXCLI {
ping
list-tabs
new-tab
new-split <left|right|up|down>
new-split <left|right|up|down> [--panel <id|index>]
list-panels [--tab <id|index>]
focus-panel --panel <id|index>
close-tab --tab <id|index>
Expand Down
1,013 changes: 932 additions & 81 deletions Sources/ContentView.swift

Large diffs are not rendered by default.

122 changes: 112 additions & 10 deletions Sources/GhosttyTerminalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,61 @@ class GhosttyApp {
}
}

func reloadConfiguration(soft: Bool = false) {
guard let app else { return }
if soft, let config {
ghostty_app_update_config(app, config)
NotificationCenter.default.post(name: .ghosttyConfigDidReload, object: nil)
return
}

guard let newConfig = ghostty_config_new() else { return }
ghostty_config_load_default_files(newConfig)
ghostty_config_finalize(newConfig)
ghostty_app_update_config(app, newConfig)
updateDefaultBackground(from: newConfig)
DispatchQueue.main.async {
self.applyBackgroundToKeyWindow()
}
if let oldConfig = config {
ghostty_config_free(oldConfig)
}
config = newConfig
NotificationCenter.default.post(name: .ghosttyConfigDidReload, object: nil)
}

func reloadConfiguration(for surface: ghostty_surface_t, soft: Bool = false) {
if soft, let config {
ghostty_surface_update_config(surface, config)
return
}

guard let newConfig = ghostty_config_new() else { return }
ghostty_config_load_default_files(newConfig)
ghostty_config_finalize(newConfig)
ghostty_surface_update_config(surface, newConfig)
ghostty_config_free(newConfig)
}

func openConfigurationInTextEdit() {
#if os(macOS)
let path = ghosttyStringValue(ghostty_config_open_path())
guard !path.isEmpty else { return }
let fileURL = URL(fileURLWithPath: path)
let editorURL = URL(fileURLWithPath: "/System/Applications/TextEdit.app")
let configuration = NSWorkspace.OpenConfiguration()
NSWorkspace.shared.open([fileURL], withApplicationAt: editorURL, configuration: configuration)
#endif
}

private func ghosttyStringValue(_ value: ghostty_string_s) -> String {
defer { ghostty_string_free(value) }
guard let ptr = value.ptr, value.len > 0 else { return "" }
let rawPtr = UnsafeRawPointer(ptr).assumingMemoryBound(to: UInt8.self)
let buffer = UnsafeBufferPointer(start: rawPtr, count: Int(value.len))
return String(decoding: buffer, as: UTF8.self)
}

private func updateDefaultBackground(from config: ghostty_config_t?) {
guard let config else { return }

Expand Down Expand Up @@ -389,6 +444,14 @@ class GhosttyApp {
return true
}

if action.tag == GHOSTTY_ACTION_RELOAD_CONFIG {
let soft = action.action.reload_config.soft
performOnMain {
GhosttyApp.shared.reloadConfiguration(soft: soft)
}
return true
}

if action.tag == GHOSTTY_ACTION_COLOR_CHANGE,
action.action.color_change.kind == GHOSTTY_ACTION_COLOR_KIND_BACKGROUND {
let change = action.action.color_change
Expand Down Expand Up @@ -429,7 +492,7 @@ class GhosttyApp {
return false
}
return performOnMain {
tabManager.newSplit(tabId: tabId, surfaceId: surfaceId, direction: direction)
tabManager.newSplit(tabId: tabId, surfaceId: surfaceId, direction: direction) != nil
}
case GHOSTTY_ACTION_GOTO_SPLIT:
guard let tabId = surfaceView.tabId,
Expand Down Expand Up @@ -534,13 +597,15 @@ class GhosttyApp {
case GHOSTTY_ACTION_SET_TITLE:
let title = action.action.set_title.title
.flatMap { String(cString: $0) } ?? ""
if let tabId = surfaceView.tabId {
if let tabId = surfaceView.tabId,
let surfaceId = surfaceView.terminalSurface?.id {
DispatchQueue.main.async {
NotificationCenter.default.post(
name: .ghosttyDidSetTitle,
object: surfaceView,
userInfo: [
GhosttyNotificationKey.tabId: tabId,
GhosttyNotificationKey.surfaceId: surfaceId,
GhosttyNotificationKey.title: title,
]
)
Expand Down Expand Up @@ -604,6 +669,16 @@ class GhosttyApp {
surfaceView.applyWindowBackgroundIfActive()
}
return true
case GHOSTTY_ACTION_RELOAD_CONFIG:
let soft = action.action.reload_config.soft
return performOnMain {
if let surface = surfaceView.terminalSurface?.surface {
GhosttyApp.shared.reloadConfiguration(for: surface, soft: soft)
} else {
GhosttyApp.shared.reloadConfiguration(soft: soft)
}
return true
}
case GHOSTTY_ACTION_KEY_SEQUENCE:
return performOnMain {
surfaceView.updateKeySequence(action.action.key_sequence)
Expand All @@ -620,15 +695,33 @@ class GhosttyApp {
}

private func applyBackgroundToKeyWindow() {
guard let window = NSApp.keyWindow ?? NSApp.windows.first else { return }
let color = defaultBackgroundColor.withAlphaComponent(defaultBackgroundOpacity)
window.backgroundColor = color
window.isOpaque = color.alphaComponent >= 1.0
if backgroundLogEnabled {
logBackground("applied default window background color=\(color) opacity=\(String(format: "%.3f", color.alphaComponent))")
guard let window = activeMainWindow() else { return }
// Check if sidebar uses behindWindow blur - if so, keep window non-opaque
let sidebarBlendMode = UserDefaults.standard.string(forKey: "sidebarBlendMode") ?? "withinWindow"
if sidebarBlendMode == "behindWindow" {
window.backgroundColor = .clear
window.isOpaque = false
if backgroundLogEnabled {
logBackground("applied transparent window for behindWindow blur")
}
} else {
let color = defaultBackgroundColor.withAlphaComponent(defaultBackgroundOpacity)
window.backgroundColor = color
window.isOpaque = color.alphaComponent >= 1.0
if backgroundLogEnabled {
logBackground("applied default window background color=\(color) opacity=\(String(format: "%.3f", color.alphaComponent))")
}
}
}

private func activeMainWindow() -> NSWindow? {
let keyWindow = NSApp.keyWindow
if keyWindow?.identifier?.rawValue == "cmux.main" {
return keyWindow
}
return NSApp.windows.first(where: { $0.identifier?.rawValue == "cmux.main" })
}

func logBackground(_ message: String) {
let line = "cmux bg: \(message)\n"
if let data = line.data(using: .utf8) {
Expand Down Expand Up @@ -991,8 +1084,15 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
}
applySurfaceBackground()
let color = effectiveBackgroundColor()
window.backgroundColor = color
window.isOpaque = color.alphaComponent >= 1.0
// Check if sidebar uses behindWindow blur - if so, keep window non-opaque
let sidebarBlendMode = UserDefaults.standard.string(forKey: "sidebarBlendMode") ?? "withinWindow"
if sidebarBlendMode == "behindWindow" {
window.backgroundColor = .clear
window.isOpaque = false
} else {
window.backgroundColor = color
window.isOpaque = color.alphaComponent >= 1.0
}
if GhosttyApp.shared.backgroundLogEnabled {
GhosttyApp.shared.logBackground("applied window background tab=\(tabId?.uuidString ?? "unknown") color=\(color) opacity=\(String(format: "%.3f", color.alphaComponent))")
}
Expand Down Expand Up @@ -1758,13 +1858,15 @@ enum GhosttyNotificationKey {
static let scrollbar = "ghostty.scrollbar"
static let cellSize = "ghostty.cellSize"
static let tabId = "ghostty.tabId"
static let surfaceId = "ghostty.surfaceId"
static let title = "ghostty.title"
}

extension Notification.Name {
static let ghosttyDidUpdateScrollbar = Notification.Name("ghosttyDidUpdateScrollbar")
static let ghosttyDidUpdateCellSize = Notification.Name("ghosttyDidUpdateCellSize")
static let ghosttySearchFocus = Notification.Name("ghosttySearchFocus")
static let ghosttyConfigDidReload = Notification.Name("ghosttyConfigDidReload")
}

// MARK: - Scroll View Wrapper (Ghostty-style scrollbar)
Expand Down
4 changes: 4 additions & 0 deletions Sources/Splits/TerminalSplitTreeView.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SwiftUI
import Foundation

struct TerminalSplitTreeView: View {
@ObservedObject var tab: Tab
Expand Down Expand Up @@ -37,6 +38,9 @@ struct TerminalSplitTreeView: View {
.onAppear { tab.updateSplitViewSize(proxy.size) }
.onChange(of: proxy.size) { tab.updateSplitViewSize($0) }
})
.onReceive(NotificationCenter.default.publisher(for: .ghosttyConfigDidReload)) { _ in
config = GhosttyConfig.load()
}
}
}

Expand Down
Loading