Fix hidden titlebar underlap and settings focus

This commit is contained in:
Lawrence Chen 2026-03-15 18:03:38 -07:00
parent f592d97126
commit d67237b891
No known key found for this signature in database
3 changed files with 105 additions and 8 deletions

View file

@ -9,6 +9,30 @@ import Combine
import ObjectiveC.runtime
import Darwin
final class MainWindowHostingView<Content: View>: NSHostingView<Content> {
private let zeroSafeAreaLayoutGuide = NSLayoutGuide()
override var safeAreaInsets: NSEdgeInsets { NSEdgeInsetsZero }
override var safeAreaRect: NSRect { bounds }
override var safeAreaLayoutGuide: NSLayoutGuide { zeroSafeAreaLayoutGuide }
required init(rootView: Content) {
super.init(rootView: rootView)
addLayoutGuide(zeroSafeAreaLayoutGuide)
NSLayoutConstraint.activate([
zeroSafeAreaLayoutGuide.leadingAnchor.constraint(equalTo: leadingAnchor),
zeroSafeAreaLayoutGuide.trailingAnchor.constraint(equalTo: trailingAnchor),
zeroSafeAreaLayoutGuide.topAnchor.constraint(equalTo: topAnchor),
zeroSafeAreaLayoutGuide.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private enum CmuxThemeNotifications {
static let reloadConfig = Notification.Name("com.cmuxterm.themes.reload-config")
}
@ -5457,7 +5481,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
} else {
window.center()
}
window.contentView = NSHostingView(rootView: root)
window.contentView = MainWindowHostingView(rootView: root)
// Apply shared window styling.
attachUpdateAccessory(to: window)

View file

@ -1173,6 +1173,7 @@ private struct NotificationPopoverRow: View {
}
}
@MainActor
final class UpdateTitlebarAccessoryController {
private weak var updateViewModel: UpdateViewModel?
private var didStart = false
@ -1213,7 +1214,9 @@ final class UpdateTitlebarAccessoryController {
queue: .main
) { [weak self] notification in
guard let window = notification.object as? NSWindow else { return }
self?.attachIfNeeded(to: window)
Task { @MainActor [weak self] in
self?.attachIfNeeded(to: window)
}
})
observers.append(center.addObserver(
@ -1222,7 +1225,9 @@ final class UpdateTitlebarAccessoryController {
queue: .main
) { [weak self] notification in
guard let window = notification.object as? NSWindow else { return }
self?.attachIfNeeded(to: window)
Task { @MainActor [weak self] in
self?.attachIfNeeded(to: window)
}
})
// We intentionally do not rely on "window became visible" notifications here:
@ -1242,7 +1247,9 @@ final class UpdateTitlebarAccessoryController {
let delays: [TimeInterval] = [0.05, 0.15, 0.3, 0.6, 1.0, 2.0, 3.0]
for delay in delays {
let item = DispatchWorkItem { [weak self] in
self?.attachToExistingWindows()
Task { @MainActor [weak self] in
self?.attachToExistingWindows()
}
#if DEBUG
let env = ProcessInfo.processInfo.environment
if env["CMUX_UI_TEST_MODE"] == "1" {
@ -1258,7 +1265,6 @@ final class UpdateTitlebarAccessoryController {
}
private func attachIfNeeded(to window: NSWindow) {
guard !attachedWindows.contains(window) else { return }
guard !isSettingsWindow(window) else { return }
// Window identifiers are assigned by SwiftUI via WindowAccessor, which can run
@ -1270,8 +1276,10 @@ final class UpdateTitlebarAccessoryController {
if attempts < 40 {
pendingAttachRetries[key] = attempts + 1
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self, weak window] in
guard let self, let window else { return }
self.attachIfNeeded(to: window)
Task { @MainActor [weak self, weak window] in
guard let self, let window else { return }
self.attachIfNeeded(to: window)
}
}
} else {
pendingAttachRetries.removeValue(forKey: key)
@ -1281,6 +1289,13 @@ final class UpdateTitlebarAccessoryController {
pendingAttachRetries.removeValue(forKey: ObjectIdentifier(window))
guard WorkspaceTitlebarSettings.isVisible() else {
removeAccessoryIfPresent(from: window)
return
}
guard !attachedWindows.contains(window) else { return }
if !window.titlebarAccessoryViewControllers.contains(where: { $0.view.identifier == controlsIdentifier }) {
let controls = TitlebarControlsAccessoryViewController(
notificationStore: TerminalNotificationStore.shared
@ -1302,6 +1317,40 @@ final class UpdateTitlebarAccessoryController {
#endif
}
private func removeAccessoryIfPresent(from window: NSWindow) {
let matchingIndices = window.titlebarAccessoryViewControllers.indices.reversed().filter { index in
window.titlebarAccessoryViewControllers[index].view.identifier == controlsIdentifier
}
guard !matchingIndices.isEmpty || attachedWindows.contains(window) else { return }
for index in matchingIndices {
let accessory = window.titlebarAccessoryViewControllers[index]
if let controls = accessory as? TitlebarControlsAccessoryViewController {
controls.dismissNotificationsPopover()
}
window.removeTitlebarAccessoryViewController(at: index)
}
attachedWindows.remove(window)
pendingAttachRetries.removeValue(forKey: ObjectIdentifier(window))
DispatchQueue.main.async { [weak window] in
guard let window else { return }
window.contentView?.needsLayout = true
window.contentView?.superview?.needsLayout = true
window.contentView?.layoutSubtreeIfNeeded()
window.contentView?.superview?.layoutSubtreeIfNeeded()
window.invalidateShadow()
}
#if DEBUG
let env = ProcessInfo.processInfo.environment
if env["CMUX_UI_TEST_MODE"] == "1" {
let ident = window.identifier?.rawValue ?? "<nil>"
UpdateLogStore.shared.append("removed titlebar accessories from window id=\(ident)")
}
#endif
}
private func isSettingsWindow(_ window: NSWindow) -> Bool {
if window.identifier?.rawValue == "cmux.settings" {
return true

View file

@ -3241,6 +3241,16 @@ struct SettingsView: View {
)
}
private var showWorkspaceTitlebarBinding: Binding<Bool> {
Binding(
get: { showWorkspaceTitlebar },
set: { newValue in
showWorkspaceTitlebar = newValue
reassertSettingsWindowFocusIfNeeded()
}
)
}
private var settingsSidebarTintLightBinding: Binding<Color> {
Binding(
get: {
@ -3590,9 +3600,10 @@ struct SettingsView: View {
String(localized: "settings.app.showWorkspaceTitlebar", defaultValue: "Show Workspace Title Bar"),
subtitle: workspaceTitlebarSubtitle
) {
Toggle("", isOn: $showWorkspaceTitlebar)
Toggle("", isOn: showWorkspaceTitlebarBinding)
.labelsHidden()
.controlSize(.small)
.accessibilityIdentifier("SettingsShowWorkspaceTitlebarToggle")
.accessibilityLabel(
String(localized: "settings.app.showWorkspaceTitlebar", defaultValue: "Show Workspace Title Bar")
)
@ -4625,6 +4636,19 @@ struct SettingsView: View {
NSApplication.shared.terminate(nil)
}
private func reassertSettingsWindowFocusIfNeeded() {
DispatchQueue.main.async {
guard let window = SettingsWindowController.shared.window, window.isVisible else { return }
window.orderFrontRegardless()
window.makeKeyAndOrderFront(nil)
DispatchQueue.main.async {
guard window.isVisible else { return }
window.orderFrontRegardless()
window.makeKeyAndOrderFront(nil)
}
}
}
private func resetAllSettings() {
isResettingSettings = true
appLanguage = LanguageSettings.defaultLanguage.rawValue