Fix macOS Swift warnings

pull/12764/head
1pitaph 2026-05-22 07:55:33 +08:00
parent 46d54ed673
commit 7b17c588c6
12 changed files with 68 additions and 33 deletions

View File

@ -136,7 +136,7 @@ struct CommandPaletteView: View {
break
}
}
.onChange(of: query) { newValue in
.backport.onChange(of: query) { newValue in
// If the user types a query then we want to make sure the first
// value is selected. If the user clears the query and we were selecting
// the first, we unset any selection.
@ -181,7 +181,7 @@ struct CommandPaletteView: View {
.shadow(radius: 32, x: 0, y: 12)
.padding()
.environment(\.colorScheme, scheme)
.onChange(of: isPresented) { newValue in
.backport.onChange(of: isPresented) { newValue in
if !newValue {
// This is optional, since most of the time
// there will be a delay before the next use.
@ -261,7 +261,7 @@ private struct CommandPaletteQuery: View {
.frame(height: 48)
.textFieldStyle(.plain)
.focused($isTextFieldFocused)
.onChange(of: isTextFieldFocused) { focused in
.backport.onChange(of: isTextFieldFocused) { focused in
if !focused {
onEvent?(.exit)
}
@ -322,7 +322,7 @@ private struct CommandTable: View {
.padding(10)
}
.frame(maxHeight: 200)
.onChange(of: selectedIndex) { _ in
.backport.onChange(of: selectedIndex) {
guard let selectedIndex,
selectedIndex < options.count else { return }
proxy.scrollTo(

View File

@ -41,7 +41,7 @@ struct TerminalCommandPaletteView: View {
}
}
}
.onChange(of: isPresented) { newValue in
.backport.onChange(of: isPresented) { newValue in
// When the command palette disappears we need to send focus back to the
// surface view we were overlaid on top of. There's probably a better way
// to handle the first responder state here but I don't know it.

View File

@ -86,7 +86,7 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
.ghosttyLastFocusedSurface(lastFocusedSurface)
.focused($focused)
.onAppear { self.focused = true }
.onChange(of: focusedSurface) { newValue in
.backport.onChange(of: focusedSurface) { newValue in
// We want to keep track of our last focused surface so even if
// we lose focus we keep this set to the last non-nil value.
if newValue != nil {
@ -94,10 +94,10 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
self.delegate?.focusedSurfaceDidChange(to: newValue)
}
}
.onChange(of: pwdURL) { newValue in
.backport.onChange(of: pwdURL) { newValue in
self.delegate?.pwdDidChange(to: newValue)
}
.onChange(of: cellSize) { newValue in
.backport.onChange(of: cellSize) { newValue in
guard let size = newValue else { return }
self.delegate?.cellSizeDidChange(to: size)
}

View File

@ -21,7 +21,7 @@ struct UpdatePill: View {
UpdatePopoverView(model: model)
}
.transition(.opacity.combined(with: .scale(scale: 0.95)))
.onChange(of: model.state) { newState in
.backport.onChange(of: model.state) { newState in
resetTask?.cancel()
if case .notFound(let notFound) = newState {
resetTask = Task { [weak model] in

View File

@ -5,7 +5,7 @@ extension Ghostty {
/// Represents the inspector for a surface within Ghostty.
///
/// Wraps a `ghostty_inspector_t`
final class Inspector: Sendable {
final class Inspector: @unchecked Sendable {
private let inspector: ghostty_inspector_t
/// Read the underlying C value for this inspector. This is unsafe because the value will be

View File

@ -9,7 +9,7 @@ extension Ghostty {
/// all over.
///
/// Wraps a `ghostty_surface_t`
final class Surface: Sendable {
final class Surface: @unchecked Sendable {
private let surface: ghostty_surface_t
/// Read the underlying C value for this surface. This is unsafe because the value will be

View File

@ -7,10 +7,6 @@ import GhosttyKit
/// A command is fully self-contained so it is Sendable.
extension ghostty_command_s: @unchecked @retroactive Sendable {}
/// A surface is sendable because it is just a reference type. Using the surface in parameters
/// may be unsafe but the value itself is safe to send across threads.
extension ghostty_surface_t: @unchecked @retroactive Sendable {}
extension Ghostty {
// The user notification category identifier
static let userNotificationCategory = "com.mitchellh.ghostty.userNotification"

View File

@ -39,7 +39,7 @@ extension Ghostty {
}
}
.onReceive(pubInspector) { onControlInspector($0) }
.onChange(of: surfaceView.inspectorVisible) { inspectorVisible in
.backport.onChange(of: surfaceView.inspectorVisible) { inspectorVisible in
// When we show the inspector, we want to focus on the inspector.
// When we hide the inspector, we want to move focus back to the surface.
if inspectorVisible {

View File

@ -937,7 +937,7 @@ extension Ghostty {
.opacity(dotOpacity(for: index))
}
}
.onChange(of: context.date.timeIntervalSinceReferenceDate) { newValue in
.backport.onChange(of: context.date.timeIntervalSinceReferenceDate) { newValue in
animationPhase = newValue
}
}
@ -1011,7 +1011,7 @@ extension Ghostty {
.allowsHitTesting(false)
.opacity(highlighted ? 1.0 : 0.0)
.animation(.easeOut(duration: 0.4), value: highlighted)
.onChange(of: highlighted) { newValue in
.backport.onChange(of: highlighted) { newValue in
if newValue {
withAnimation(.easeInOut(duration: 0.4).repeatForever(autoreverses: true)) {
borderPulse = true

View File

@ -1726,27 +1726,31 @@ extension Ghostty {
trigger: nil
)
// Note the callback may be executed on a background thread as documented
// so we need @MainActor since we're reading/writing view state.
UNUserNotificationCenter.current().add(request) { @MainActor error in
// Note the callback may be executed on a background thread as documented,
// so view state is updated from an explicit main-actor task below.
UNUserNotificationCenter.current().add(request) { [weak self, uuid] error in
if let error = error {
AppDelegate.logger.error("Error scheduling user notification: \(error)")
return
}
// We need to keep track of this notification so we can remove it
// under certain circumstances
self.notificationIdentifiers.insert(uuid)
Task { @MainActor [weak self, uuid] in
guard let self else { return }
// If we're focused then we schedule to remove the notification
// after a few seconds. If we gain focus we automatically remove it
// in focusDidChange.
if self.focused {
Task { @MainActor [weak self] in
try await Task.sleep(for: .seconds(3))
self?.notificationIdentifiers.remove(uuid)
UNUserNotificationCenter.current()
.removeDeliveredNotifications(withIdentifiers: [uuid])
// We need to keep track of this notification so we can remove it
// under certain circumstances.
self.notificationIdentifiers.insert(uuid)
// If we're focused then we schedule to remove the notification
// after a few seconds. If we gain focus we automatically remove it
// in focusDidChange.
if self.focused {
Task { @MainActor [weak self, uuid] in
try await Task.sleep(for: .seconds(3))
self?.notificationIdentifiers.remove(uuid)
UNUserNotificationCenter.current()
.removeDeliveredNotifications(withIdentifiers: [uuid])
}
}
}
}

View File

@ -25,6 +25,39 @@ enum BackportKeyPressResult {
}
extension Backport where Content: View {
/// Backported onChange that uses the modern two-parameter closure on
/// platforms that provide it while preserving macOS 13 support.
func onChange<Value: Equatable>(
of value: Value,
_ action: @escaping (Value) -> Void
) -> some View {
if #available(iOS 17, macOS 14, *) {
return content.onChange(of: value) { _, newValue in
action(newValue)
}
} else {
return content.onChange(of: value) { newValue in
action(newValue)
}
}
}
/// Backported onChange variant for handlers that do not need the new value.
func onChange<Value: Equatable>(
of value: Value,
_ action: @escaping () -> Void
) -> some View {
if #available(iOS 17, macOS 14, *) {
return content.onChange(of: value) { _, _ in
action()
}
} else {
return content.onChange(of: value) { _ in
action()
}
}
}
func pointerVisibility(_ v: BackportVisibility) -> some View {
#if canImport(AppKit)
if #available(macOS 15, *) {

View File

@ -47,6 +47,8 @@ extension KeyboardShortcut: @retroactive CustomStringConvertible {
}
// This is available in macOS 14 so this only applies to early macOS versions.
@available(macOS, introduced: 10.15, obsoleted: 14.0)
@available(iOS, introduced: 13.0, obsoleted: 17.0)
extension KeyEquivalent: @retroactive Equatable {
public static func == (lhs: KeyEquivalent, rhs: KeyEquivalent) -> Bool {
lhs.character == rhs.character