From 625f2b64036e9293cf38f348cadc7b9643c8355b Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Wed, 11 Feb 2026 00:08:51 -0800 Subject: [PATCH 01/13] no more wacko scrollbar, and supports clear backgrounds --- Sources/SwiftTerm/Mac/MacTerminalView.swift | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftTerm/Mac/MacTerminalView.swift b/Sources/SwiftTerm/Mac/MacTerminalView.swift index ed4aac911..44ac2bb7d 100644 --- a/Sources/SwiftTerm/Mac/MacTerminalView.swift +++ b/Sources/SwiftTerm/Mac/MacTerminalView.swift @@ -307,10 +307,18 @@ open class TerminalView: NSView, NSTextInputClient, NSUserInterfaceValidations, if settingBg { return } settingBg = true _nativeBg = newValue + layer?.backgroundColor = newValue.cgColor terminal.backgroundColor = nativeBackgroundColor.getTerminalColor () settingBg = false } } + + override public var isOpaque: Bool { + if let bg = _nativeBg { + return bg.alphaComponent >= 1.0 + } + return true + } /// Controls weather to use high ansi colors, if false terminal will use bold text instead of high ansi colors public var useBrightColors: Bool = true @@ -393,13 +401,18 @@ open class TerminalView: NSView, NSTextInputClient, NSUserInterfaceValidations, func setupScroller() { - let scrollerFrame = getScrollerFrame() + let scrollerWidth = NSScroller.scrollerWidth(for: .regular, scrollerStyle: scrollerStyle) + // Use a tall-enough frame so NSScroller initializes as vertical (orientation is determined by frame aspect) + let initialFrame = bounds.height > scrollerWidth + ? getScrollerFrame() + : NSRect(x: 0, y: 0, width: scrollerWidth, height: scrollerWidth + 1) + if scroller == nil { - scroller = NSScroller(frame: scrollerFrame) + scroller = NSScroller(frame: initialFrame) } else { - scroller?.frame = scrollerFrame + scroller?.frame = initialFrame } - scroller.autoresizingMask = [.minXMargin, .height] + scroller.autoresizingMask = [] scroller.scrollerStyle = scrollerStyle scroller.knobProportion = 0.1 scroller.isEnabled = false From 3168dea99c127e1b9064d1fd81358e91ffc0ceb8 Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Wed, 11 Feb 2026 00:29:44 -0800 Subject: [PATCH 02/13] fixed colors, not public --- Sources/SwiftTerm/Colors.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftTerm/Colors.swift b/Sources/SwiftTerm/Colors.swift index 7411bb524..d68f87176 100644 --- a/Sources/SwiftTerm/Colors.swift +++ b/Sources/SwiftTerm/Colors.swift @@ -33,7 +33,7 @@ public class Color: Hashable { hasher.combine(blue) } - static let paleColors: [Color] = [ + public static let paleColors: [Color] = [ // dark colors Color (red8: 0x2e, green8: 0x34, blue8: 0x36), Color (red8: 0xcc, green8: 0x00, blue8: 0x00), @@ -55,7 +55,7 @@ public class Color: Hashable { Color (red8: 0xee, green8: 0xee, blue8: 0xec) ] - static let vgaColors: [Color] = [ + public static let vgaColors: [Color] = [ // dark colors Color (red8: 0, green8: 0, blue8: 0), Color (red8: 170, green8: 0, blue8: 0), @@ -75,7 +75,7 @@ public class Color: Hashable { Color (red8: 255, green8: 255, blue8: 255), ] - static let terminalAppColors: [Color] = [ + public static let terminalAppColors: [Color] = [ Color (red8: 0, green8: 0, blue8: 0), Color (red8: 194, green8: 54, blue8: 33), Color (red8: 37, green8: 188, blue8: 36), @@ -94,7 +94,7 @@ public class Color: Hashable { Color (red8: 233, green8: 235, blue8: 235), ] - static let xtermColors: [Color] = [ + public static let xtermColors: [Color] = [ Color (red8: 0, green8: 0, blue8: 0), Color (red8: 205, green8: 0, blue8: 0), Color (red8: 0, green8: 205, blue8: 0), @@ -113,7 +113,7 @@ public class Color: Hashable { Color (red8: 255, green8: 255, blue8: 255), ] - static let defaultInstalledColors: [Color] = [ + public static let defaultInstalledColors: [Color] = [ Color (red8: 0, green8: 0, blue8: 0), Color (red8: 153, green8: 0, blue8: 1), Color (red8: 0, green8: 166, blue8: 3), From 46546105c968f0334628ad1dce4cee9d39cd1fbd Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Wed, 11 Feb 2026 01:10:16 -0800 Subject: [PATCH 03/13] added better colors --- .gitignore | 1 + Sources/SwiftTerm/Colors.swift | 129 +++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/.gitignore b/.gitignore index 7eb1730ae..66494288c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ DerivedData .DS_Store *~ esctest/ +Package.resolved diff --git a/Sources/SwiftTerm/Colors.swift b/Sources/SwiftTerm/Colors.swift index d68f87176..9b256702c 100644 --- a/Sources/SwiftTerm/Colors.swift +++ b/Sources/SwiftTerm/Colors.swift @@ -131,6 +131,135 @@ public class Color: Hashable { Color (red8: 0, green8: 229, blue8: 229), Color (red8: 229, green8: 229, blue8: 229), ] + + /// Dracula - popular vibrant dark theme + public static let draculaColors: [Color] = [ + // normal colors + Color (red8: 0x21, green8: 0x22, blue8: 0x2c), + Color (red8: 0xff, green8: 0x55, blue8: 0x55), + Color (red8: 0x50, green8: 0xfa, blue8: 0x7b), + Color (red8: 0xf1, green8: 0xfa, blue8: 0x8c), + Color (red8: 0xbd, green8: 0x93, blue8: 0xf9), + Color (red8: 0xff, green8: 0x79, blue8: 0xc6), + Color (red8: 0x8b, green8: 0xe9, blue8: 0xfd), + Color (red8: 0xf8, green8: 0xf8, blue8: 0xf2), + // bright colors + Color (red8: 0x62, green8: 0x72, blue8: 0xa4), + Color (red8: 0xff, green8: 0x6e, blue8: 0x6e), + Color (red8: 0x69, green8: 0xff, blue8: 0x94), + Color (red8: 0xff, green8: 0xff, blue8: 0xa5), + Color (red8: 0xd6, green8: 0xac, blue8: 0xff), + Color (red8: 0xff, green8: 0x92, blue8: 0xdf), + Color (red8: 0xa4, green8: 0xff, blue8: 0xff), + Color (red8: 0xff, green8: 0xff, blue8: 0xff), + ] + + /// Catppuccin Mocha - soothing pastel dark theme + public static let catppuccinMochaColors: [Color] = [ + // normal colors + Color (red8: 0x45, green8: 0x47, blue8: 0x5a), + Color (red8: 0xf3, green8: 0x8b, blue8: 0xa8), + Color (red8: 0xa6, green8: 0xe3, blue8: 0xa1), + Color (red8: 0xf9, green8: 0xe2, blue8: 0xaf), + Color (red8: 0x89, green8: 0xb4, blue8: 0xfa), + Color (red8: 0xf5, green8: 0xc2, blue8: 0xe7), + Color (red8: 0x94, green8: 0xe2, blue8: 0xd5), + Color (red8: 0xba, green8: 0xc2, blue8: 0xde), + // bright colors + Color (red8: 0x58, green8: 0x5b, blue8: 0x70), + Color (red8: 0xf3, green8: 0x8b, blue8: 0xa8), + Color (red8: 0xa6, green8: 0xe3, blue8: 0xa1), + Color (red8: 0xf9, green8: 0xe2, blue8: 0xaf), + Color (red8: 0x89, green8: 0xb4, blue8: 0xfa), + Color (red8: 0xf5, green8: 0xc2, blue8: 0xe7), + Color (red8: 0x94, green8: 0xe2, blue8: 0xd5), + Color (red8: 0xa6, green8: 0xad, blue8: 0xc8), + ] + + /// Gruvbox Dark - retro, warm, earthy tones + public static let gruvboxDarkColors: [Color] = [ + // normal colors + Color (red8: 0x28, green8: 0x28, blue8: 0x28), + Color (red8: 0xcc, green8: 0x24, blue8: 0x1d), + Color (red8: 0x98, green8: 0x97, blue8: 0x1a), + Color (red8: 0xd7, green8: 0x99, blue8: 0x21), + Color (red8: 0x45, green8: 0x85, blue8: 0x88), + Color (red8: 0xb1, green8: 0x62, blue8: 0x86), + Color (red8: 0x68, green8: 0x9d, blue8: 0x6a), + Color (red8: 0xa8, green8: 0x99, blue8: 0x84), + // bright colors + Color (red8: 0x92, green8: 0x83, blue8: 0x74), + Color (red8: 0xfb, green8: 0x49, blue8: 0x34), + Color (red8: 0xb8, green8: 0xbb, blue8: 0x26), + Color (red8: 0xfa, green8: 0xbd, blue8: 0x2f), + Color (red8: 0x83, green8: 0xa5, blue8: 0x98), + Color (red8: 0xd3, green8: 0x86, blue8: 0x9b), + Color (red8: 0x8e, green8: 0xc0, blue8: 0x7c), + Color (red8: 0xeb, green8: 0xdb, blue8: 0xb2), + ] + + /// Solarized Dark - Ethan Schoonover's CIELAB-designed palette + public static let solarizedDarkColors: [Color] = [ + // normal colors + Color (red8: 0x00, green8: 0x2b, blue8: 0x36), + Color (red8: 0xdc, green8: 0x32, blue8: 0x2f), + Color (red8: 0x85, green8: 0x99, blue8: 0x00), + Color (red8: 0xb5, green8: 0x89, blue8: 0x00), + Color (red8: 0x26, green8: 0x8b, blue8: 0xd2), + Color (red8: 0xd3, green8: 0x36, blue8: 0x82), + Color (red8: 0x2a, green8: 0xa1, blue8: 0x98), + Color (red8: 0xee, green8: 0xe8, blue8: 0xd5), + // bright colors + Color (red8: 0x07, green8: 0x36, blue8: 0x42), + Color (red8: 0xcb, green8: 0x4b, blue8: 0x16), + Color (red8: 0x58, green8: 0x6e, blue8: 0x75), + Color (red8: 0x65, green8: 0x7b, blue8: 0x83), + Color (red8: 0x83, green8: 0x94, blue8: 0x96), + Color (red8: 0x6c, green8: 0x71, blue8: 0xc4), + Color (red8: 0x93, green8: 0xa1, blue8: 0xa1), + Color (red8: 0xfd, green8: 0xf6, blue8: 0xe3), + ] + + /// All available color scheme names, suitable for populating a menu + public static let colorSchemeNames: [String] = [ + "Dracula", + "Catppuccin Mocha", + "Gruvbox Dark", + "Solarized Dark", + "Tango", + "VGA", + "Xterm", + "macOS Terminal", + "Default", + ] + + /// Returns the 16 ANSI base colors for the given color scheme name. + /// The name must be one of the values from ``colorSchemeNames``. + /// Returns `nil` if the name is not recognized. + public static func colorScheme(forName name: String) -> [Color]? { + switch name { + case "Dracula": + return draculaColors + case "Catppuccin Mocha": + return catppuccinMochaColors + case "Gruvbox Dark": + return gruvboxDarkColors + case "Solarized Dark": + return solarizedDarkColors + case "Tango": + return paleColors + case "VGA": + return vgaColors + case "Xterm": + return xtermColors + case "macOS Terminal": + return terminalAppColors + case "Default": + return defaultInstalledColors + default: + return nil + } + } static func setupDefaultAnsiColors (initialColors: [Color]) -> [Color] { From 48c456ce25c91ff0c97b09e49dfe2b4082af8c24 Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Wed, 11 Feb 2026 01:13:10 -0800 Subject: [PATCH 04/13] fix --- Sources/SwiftTerm/Colors.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftTerm/Colors.swift b/Sources/SwiftTerm/Colors.swift index 9b256702c..cc84cd547 100644 --- a/Sources/SwiftTerm/Colors.swift +++ b/Sources/SwiftTerm/Colors.swift @@ -236,7 +236,7 @@ public class Color: Hashable { /// Returns the 16 ANSI base colors for the given color scheme name. /// The name must be one of the values from ``colorSchemeNames``. /// Returns `nil` if the name is not recognized. - public static func colorScheme(forName name: String) -> [Color]? { + public static func colorScheme(forName name: String) -> [Color] { switch name { case "Dracula": return draculaColors @@ -257,7 +257,7 @@ public class Color: Hashable { case "Default": return defaultInstalledColors default: - return nil + return defaultInstalledColors } } From e5d62fd19c899e73fafb9c52b65f94bbe9bf0a69 Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Wed, 11 Feb 2026 16:19:00 -0800 Subject: [PATCH 05/13] Miguel banned me from the github for fixing and reporting bugs, use my fork. This fixes pasting text with the correct color, another obvious bug ignored for 5 years. --- Sources/SwiftTerm/Mac/MacTerminalView.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftTerm/Mac/MacTerminalView.swift b/Sources/SwiftTerm/Mac/MacTerminalView.swift index 44ac2bb7d..0d1ba8f76 100644 --- a/Sources/SwiftTerm/Mac/MacTerminalView.swift +++ b/Sources/SwiftTerm/Mac/MacTerminalView.swift @@ -1130,10 +1130,15 @@ open class TerminalView: NSView, NSTextInputClient, NSUserInterfaceValidations, { // find the selected range of text in the buffer and put in the clipboard let str = selection.getSelectedText() - + let clipboard = NSPasteboard.general clipboard.clearContents() - clipboard.setString(str, forType: .string) + + let attributed = NSAttributedString(string: str, attributes: [ + .font: fontSet.normal, + .foregroundColor: nativeForegroundColor + ]) + clipboard.writeObjects([attributed]) } public override func selectAll(_ sender: Any?) From 19eb950e59e9463240fb8f1ef47ca344346681a5 Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Wed, 11 Feb 2026 16:23:39 -0800 Subject: [PATCH 06/13] pasting correct color fix part 2 --- Sources/SwiftTerm/Mac/MacTerminalView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftTerm/Mac/MacTerminalView.swift b/Sources/SwiftTerm/Mac/MacTerminalView.swift index 0d1ba8f76..317cd133c 100644 --- a/Sources/SwiftTerm/Mac/MacTerminalView.swift +++ b/Sources/SwiftTerm/Mac/MacTerminalView.swift @@ -1134,9 +1134,11 @@ open class TerminalView: NSView, NSTextInputClient, NSUserInterfaceValidations, let clipboard = NSPasteboard.general clipboard.clearContents() + // Resolve the dynamic color to a concrete sRGB color for RTF serialization + let fg = nativeForegroundColor.usingColorSpace(.sRGB) ?? nativeForegroundColor let attributed = NSAttributedString(string: str, attributes: [ .font: fontSet.normal, - .foregroundColor: nativeForegroundColor + .foregroundColor: fg ]) clipboard.writeObjects([attributed]) } From 49fd732f6d5cf4ead0c6717d791a3f443a7538d1 Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Wed, 11 Feb 2026 16:36:15 -0800 Subject: [PATCH 07/13] paste color, attempt 3 --- Sources/SwiftTerm/Apple/AppleTerminalView.swift | 13 ++++--------- Sources/SwiftTerm/Mac/MacTerminalView.swift | 9 +-------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/Sources/SwiftTerm/Apple/AppleTerminalView.swift b/Sources/SwiftTerm/Apple/AppleTerminalView.swift index f88ed92e3..8d0cef069 100644 --- a/Sources/SwiftTerm/Apple/AppleTerminalView.swift +++ b/Sources/SwiftTerm/Apple/AppleTerminalView.swift @@ -1133,15 +1133,10 @@ extension TerminalView { y: lineOrigin.y + yOffset + ctPosition.y) } - nativeForegroundColor.set() - - if runAttributes.keys.contains(.foregroundColor) { - let color = runAttributes[.foregroundColor] as! TTColor - let cgColor = color.cgColor - if let colorSpace = cgColor.colorSpace { - context.setFillColorSpace(colorSpace) - } - context.setFillColor(cgColor) + if let color = runAttributes[.foregroundColor] as? TTColor { + color.setFill() + } else { + nativeForegroundColor.setFill() } CTFontDrawGlyphs(runFont, runGlyphs, &positions, positions.count, context) diff --git a/Sources/SwiftTerm/Mac/MacTerminalView.swift b/Sources/SwiftTerm/Mac/MacTerminalView.swift index 317cd133c..c94cee973 100644 --- a/Sources/SwiftTerm/Mac/MacTerminalView.swift +++ b/Sources/SwiftTerm/Mac/MacTerminalView.swift @@ -1133,14 +1133,7 @@ open class TerminalView: NSView, NSTextInputClient, NSUserInterfaceValidations, let clipboard = NSPasteboard.general clipboard.clearContents() - - // Resolve the dynamic color to a concrete sRGB color for RTF serialization - let fg = nativeForegroundColor.usingColorSpace(.sRGB) ?? nativeForegroundColor - let attributed = NSAttributedString(string: str, attributes: [ - .font: fontSet.normal, - .foregroundColor: fg - ]) - clipboard.writeObjects([attributed]) + clipboard.setString(str, forType: .string) } public override func selectAll(_ sender: Any?) From 7639efcd2ec22276d417d24ca388bce4da1c4892 Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Wed, 11 Feb 2026 16:44:19 -0800 Subject: [PATCH 08/13] paste color attempt 4 --- Sources/SwiftTerm/Mac/MacExtensions.swift | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftTerm/Mac/MacExtensions.swift b/Sources/SwiftTerm/Mac/MacExtensions.swift index 90402c064..24b0b3a5e 100644 --- a/Sources/SwiftTerm/Mac/MacExtensions.swift +++ b/Sources/SwiftTerm/Mac/MacExtensions.swift @@ -11,13 +11,23 @@ import AppKit extension NSColor { func getTerminalColor () -> Color { - guard let color = self.usingColorSpace(.deviceRGB) else { - return Color.defaultForeground + var result = Color.defaultForeground + let resolve = { + guard let color = self.usingColorSpace(.deviceRGB) else { + return + } + var red: CGFloat = 0.0, green: CGFloat = 0.0, blue: CGFloat = 0.0, alpha: CGFloat = 1.0 + color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + result = Color(red: UInt16(red*65535), green: UInt16(green*65535), blue: UInt16(blue*65535)) } - - var red: CGFloat = 0.0, green: CGFloat = 0.0, blue: CGFloat = 0.0, alpha: CGFloat = 1.0 - color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) - return Color(red: UInt16(red*65535), green: UInt16(green*65535), blue: UInt16(blue*65535)) + // Resolve dynamic colors (e.g. labelColor) in the app's effective appearance + // so they produce the correct value in dark mode + if let appearance = NSApp?.effectiveAppearance { + appearance.performAsCurrentDrawingAppearance(resolve) + } else { + resolve() + } + return result } func inverseColor() -> NSColor { guard let color = self.usingColorSpace(.deviceRGB) else { From d4f377656d27c84c62db0a2c44f124f7ed358f8b Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Wed, 11 Feb 2026 16:58:05 -0800 Subject: [PATCH 09/13] revert fix, trying again --- Sources/SwiftTerm/Mac/MacExtensions.swift | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/Sources/SwiftTerm/Mac/MacExtensions.swift b/Sources/SwiftTerm/Mac/MacExtensions.swift index 24b0b3a5e..90402c064 100644 --- a/Sources/SwiftTerm/Mac/MacExtensions.swift +++ b/Sources/SwiftTerm/Mac/MacExtensions.swift @@ -11,23 +11,13 @@ import AppKit extension NSColor { func getTerminalColor () -> Color { - var result = Color.defaultForeground - let resolve = { - guard let color = self.usingColorSpace(.deviceRGB) else { - return - } - var red: CGFloat = 0.0, green: CGFloat = 0.0, blue: CGFloat = 0.0, alpha: CGFloat = 1.0 - color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) - result = Color(red: UInt16(red*65535), green: UInt16(green*65535), blue: UInt16(blue*65535)) - } - // Resolve dynamic colors (e.g. labelColor) in the app's effective appearance - // so they produce the correct value in dark mode - if let appearance = NSApp?.effectiveAppearance { - appearance.performAsCurrentDrawingAppearance(resolve) - } else { - resolve() + guard let color = self.usingColorSpace(.deviceRGB) else { + return Color.defaultForeground } - return result + + var red: CGFloat = 0.0, green: CGFloat = 0.0, blue: CGFloat = 0.0, alpha: CGFloat = 1.0 + color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + return Color(red: UInt16(red*65535), green: UInt16(green*65535), blue: UInt16(blue*65535)) } func inverseColor() -> NSColor { guard let color = self.usingColorSpace(.deviceRGB) else { From 36e5b280916aac22bbe87c28fe18919557f4970f Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Wed, 11 Feb 2026 17:06:31 -0800 Subject: [PATCH 10/13] revert --- Sources/SwiftTerm/Apple/AppleTerminalView.swift | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftTerm/Apple/AppleTerminalView.swift b/Sources/SwiftTerm/Apple/AppleTerminalView.swift index 8d0cef069..750058eef 100644 --- a/Sources/SwiftTerm/Apple/AppleTerminalView.swift +++ b/Sources/SwiftTerm/Apple/AppleTerminalView.swift @@ -1133,10 +1133,15 @@ extension TerminalView { y: lineOrigin.y + yOffset + ctPosition.y) } - if let color = runAttributes[.foregroundColor] as? TTColor { - color.setFill() - } else { - nativeForegroundColor.setFill() + nativeForegroundColor.set() + + if runAttributes.keys.contains(.foregroundColor) { + let color = runAttributes[.foregroundColor] as! TTColor + let cgColor = color.cgColor + if let colorSpace = cgColor.colorSpace { + context.setFillColorSpace(colorSpace) + } + context.setFillColor(cgColor) } CTFontDrawGlyphs(runFont, runGlyphs, &positions, positions.count, context) @@ -1929,4 +1934,4 @@ extension TerminalView { } #endif -#endif +#endif \ No newline at end of file From 73ee56284ef3e963a139b8417e24a3fcd4255667 Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Wed, 11 Feb 2026 18:42:21 -0800 Subject: [PATCH 11/13] fixed pasted background color when nativeBackgroundColor is .clear --- Sources/SwiftTerm/Mac/MacExtensions.swift | 8 ++++++++ TerminalApp/MacTerminal/ViewController.swift | 2 ++ 2 files changed, 10 insertions(+) diff --git a/Sources/SwiftTerm/Mac/MacExtensions.swift b/Sources/SwiftTerm/Mac/MacExtensions.swift index 90402c064..72d2d9220 100644 --- a/Sources/SwiftTerm/Mac/MacExtensions.swift +++ b/Sources/SwiftTerm/Mac/MacExtensions.swift @@ -26,6 +26,14 @@ extension NSColor { var red: CGFloat = 0.0, green: CGFloat = 0.0, blue: CGFloat = 0.0, alpha: CGFloat = 1.0 color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + + // For transparent colors (like .clear used as background), the mathematical + // inverse would also be invisible. Return a visible color based on appearance. + if alpha < 0.1 { + let isDark = NSAppearance.currentDrawing().bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + return isDark ? NSColor.white : NSColor.black + } + return NSColor(calibratedRed: 1.0 - red, green: 1.0 - green, blue: 1.0 - blue, alpha: alpha) } diff --git a/TerminalApp/MacTerminal/ViewController.swift b/TerminalApp/MacTerminal/ViewController.swift index 77bcb94f7..d655b5376 100644 --- a/TerminalApp/MacTerminal/ViewController.swift +++ b/TerminalApp/MacTerminal/ViewController.swift @@ -117,6 +117,8 @@ class ViewController: NSViewController, LocalProcessTerminalViewDelegate, NSUser super.viewDidLoad() terminal = LocalProcessTerminalView(frame: view.frame) terminal.caretColor = .systemGreen + terminal.nativeBackgroundColor = .clear + terminal.nativeForegroundColor = .labelColor terminal.getTerminal().setCursorStyle(.steadyBlock) zoomGesture = NSMagnificationGestureRecognizer(target: self, action: #selector(zoomGestureHandler)) terminal.addGestureRecognizer(zoomGesture!) From 53c2f19aac7f10419b9b673b13cbbc541d96a414 Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Fri, 10 Apr 2026 17:17:21 -0700 Subject: [PATCH 12/13] contexual menu --- Sources/SwiftTerm/Mac/MacTerminalView.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Sources/SwiftTerm/Mac/MacTerminalView.swift b/Sources/SwiftTerm/Mac/MacTerminalView.swift index c94cee973..e0aa06152 100644 --- a/Sources/SwiftTerm/Mac/MacTerminalView.swift +++ b/Sources/SwiftTerm/Mac/MacTerminalView.swift @@ -1604,6 +1604,24 @@ open class TerminalView: NSView, NSTextInputClient, NSUserInterfaceValidations, public func iTermContent (source: Terminal, content: ArraySlice) { terminalDelegate?.iTermContent(source: self, content: content) } + + // added by SNG for contextual menu + open override func menu(for event: NSEvent) -> NSMenu? { + let menu = NSMenu() + + let copyItem = menu.addItem(withTitle: "Copy", action: #selector(copy(_:)), keyEquivalent: "") + copyItem.target = self + + let pasteItem = menu.addItem(withTitle: "Paste", action: #selector(paste(_:)), keyEquivalent: "") + pasteItem.target = self + + menu.addItem(.separator()) + + let selectAllItem = menu.addItem(withTitle: "Select All", action: #selector(selectAll(_:)), keyEquivalent: "") + selectAllItem.target = self + + return menu + } } From 5dd5e77fa2d430ace5e1f0016a4398f48be55506 Mon Sep 17 00:00:00 2001 From: Steve Gehrman Date: Wed, 24 Jun 2026 21:15:17 -0700 Subject: [PATCH 13/13] fixed crash --- Sources/SwiftTerm/Pty.swift | 54 ++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/Sources/SwiftTerm/Pty.swift b/Sources/SwiftTerm/Pty.swift index 0e18402f5..c73bf092d 100644 --- a/Sources/SwiftTerm/Pty.swift +++ b/Sources/SwiftTerm/Pty.swift @@ -56,6 +56,16 @@ public class PseudoTerminalHelpers { } } + /* Like String.withCString, but tolerates a nil string by passing a nil pointer to the body. */ + static func withOptionalCString( + _ string: String?, _ body: (UnsafePointer?) -> R + ) -> R { + if let string { + return string.withCString { body($0) } + } + return body(nil) + } + /** * This method both forks and executes the provided command under a Pseudo Terminal (pty) * - Parameter andExec: the name of the executable to run @@ -68,25 +78,37 @@ public class PseudoTerminalHelpers { public static func fork (andExec: String, args: [String], env: [String], currentDirectory: String? = nil, desiredWindowSize: inout winsize) -> (pid: pid_t, masterFd: Int32)? { var master: Int32 = 0 - - let pid = forkpty(&master, nil, nil, &desiredWindowSize) - if pid < 0 { - return nil - } - if pid == 0 { - if let currentDirectory { - _ = currentDirectory.withCString { p in - chdir(p) + + // After fork() in a multi-threaded process only the calling thread survives in the + // child; any lock another thread held at fork time (notably the Swift runtime's + // os_unfair_lock conformance cache) is left locked forever and aborts the child. + // Between fork and execve we may therefore only call async-signal-safe functions. + // Materialize every C string here, BEFORE forkpty(), so the child path touches no + // Swift runtime machinery (no String/Array bridging, allocation, or conformance + // lookups) and does nothing but chdir + execve. The buffers stay alive for the + // duration of these closures and the child inherits them at the same addresses. + return andExec.withCString { execPath in + withArrayOfCStrings(args) { pargs in + withArrayOfCStrings(env) { penv in + withOptionalCString(currentDirectory) { cwd in + let pid = forkpty(&master, nil, nil, &desiredWindowSize) + if pid < 0 { + return nil + } + if pid == 0 { + if let cwd { + _ = chdir(cwd) + } + _ = execve(execPath, pargs, penv) + // execve only returns on failure; exit without unwinding back + // into the (fork-unsafe) Swift runtime. + _exit(127) + } + return (pid, master) + } } } - - withArrayOfCStrings(args, { pargs in - withArrayOfCStrings(env, { penv in - let _ = execve(andExec, pargs, penv) - }) - }) } - return (pid, master) } /**