diff --git a/Common/Controllers/Preferences.swift b/Common/Controllers/Preferences.swift index 21617f2..e221806 100644 --- a/Common/Controllers/Preferences.swift +++ b/Common/Controllers/Preferences.swift @@ -35,7 +35,7 @@ public class Preferences: NSObject { let themesPlist = NSDictionary(contentsOf: Bundle.main.url(forResource: "Themes", withExtension: "plist")!)! public var fontMetrics: FontMetrics! - @objc public var colorMap: VT100ColorMap! + public var colorMap: ColorMap! private var kvoContext = 0 @@ -153,17 +153,34 @@ public class Preferences: NSObject { private func fontMetricsChanged() { var regularFont: Font? var boldFont: Font? + var italicFont: Font? + var boldItalicFont: Font? if fontName == "SF Mono" { if #available(iOS 13, macOS 10.15, *) { regularFont = Font.monospacedSystemFont(ofSize: fontSize, weight: .regular) boldFont = Font.monospacedSystemFont(ofSize: fontSize, weight: .bold) + + if let fontDescriptor = regularFont?.fontDescriptor.withSymbolicTraits(.traitItalic) { + italicFont = Font(descriptor: fontDescriptor, size: fontSize) + } + if let fontDescriptor = boldFont?.fontDescriptor.withSymbolicTraits(.traitItalic) { + boldItalicFont = Font(descriptor: fontDescriptor, size: fontSize) + } } } else { if let family = fontsPlist[fontName] as? [String: String] { - if family["Regular"] != nil && family["Bold"] != nil { - regularFont = Font(name: family["Regular"]!, size: fontSize) - boldFont = Font(name: family["Bold"]!, size: fontSize) + if let name = family["Regular"] { + regularFont = Font(name: name, size: fontSize) + } + if let name = family["Bold"] { + boldFont = Font(name: name, size: fontSize) + } + if let name = family["Italic"] { + italicFont = Font(name: name, size: fontSize) + } + if let name = family["BoldItalic"] { + boldItalicFont = Font(name: name, size: fontSize) } } } @@ -178,7 +195,10 @@ public class Preferences: NSObject { return } - fontMetrics = FontMetrics(regularFont: regularFont!, boldFont: boldFont!) + fontMetrics = FontMetrics(regularFont: regularFont!, + boldFont: boldFont!, + italicFont: italicFont ?? regularFont!, + boldItalicFont: boldItalicFont ?? boldFont!) } private func colorMapChanged() { @@ -190,7 +210,7 @@ public class Preferences: NSObject { return } - colorMap = VT100ColorMap(dictionary: theme) + colorMap = ColorMap(dictionary: theme) #if os(macOS) NSApp.appearance = NSAppearance(named: colorMap.appearanceStyle) diff --git a/Common/Controllers/SubProcess.swift b/Common/Controllers/SubProcess.swift index 5d24d39..ebf95e9 100644 --- a/Common/Controllers/SubProcess.swift +++ b/Common/Controllers/SubProcess.swift @@ -88,7 +88,7 @@ class SubProcess: NSObject { let bashArgs = ([ "bash", "--login", "-i" ] as NSArray).cStringArray()! let env = ([ - "TERM=xterm-color", + "TERM=xterm-256color", "LANG=\(localeCode)", "TERM_PROGRAM=NewTerm", "LC_TERMINAL=NewTerm" diff --git a/Common/Controllers/TerminalController.swift b/Common/Controllers/TerminalController.swift index d8980c6..bcb03dd 100644 --- a/Common/Controllers/TerminalController.swift +++ b/Common/Controllers/TerminalController.swift @@ -11,11 +11,14 @@ import AppKit #else import UIKit #endif +import SwiftTerm public protocol TerminalControllerDelegate: AnyObject { - func refresh(attributedString: NSAttributedString, backgroundColor: Color) + func refresh(attributedString: NSAttributedString, backgroundColor: UIColor) func activateBell() + func titleDidChange(_ title: String?) + func close() func didReceiveError(error: Error) @@ -23,16 +26,15 @@ public protocol TerminalControllerDelegate: AnyObject { } -public class TerminalController: VT100 { +public class TerminalController { public weak var delegate: TerminalControllerDelegate? - private var isDirty = false private var updateTimer: Timer? - private var stringSupplier = VT100StringSupplier() + private var stringSupplier = StringSupplier() - public var colorMap: VT100ColorMap { + public var colorMap: ColorMap { get { return stringSupplier.colorMap! } set { stringSupplier.colorMap = newValue } } @@ -42,26 +44,31 @@ public class TerminalController: VT100 { set { stringSupplier.fontMetrics = newValue } } + private var terminal: Terminal? private var subProcess: SubProcess? private var processLaunchDate: Date? - override public var screenSize: ScreenSize { - get { return super.screenSize } - - set { - super.screenSize = newValue - + public var screenSize: ScreenSize? { + didSet { // Send the terminal the actual size of our vt100 view. This should be called any time we // change the size of the view. This should be a no-op if the size has not changed since the // last time we called it. - subProcess?.screenSize = screenSize + if let screenSize = screenSize { + subProcess?.screenSize = screenSize + terminal?.resize(cols: Int(screenSize.width), + rows: Int(screenSize.height)) + } } } - public override init() { - super.init() + public var scrollbackLines: Int { (terminal?.rows ?? 0) - (terminal?.getTopVisibleRow() ?? 0) } - stringSupplier.screenBuffer = self + public init() { + let options = TerminalOptions(termName: "xterm-256color", + scrollback: 1000) + terminal = Terminal(delegate: self, options: options) + + stringSupplier.terminal = terminal NotificationCenter.default.addObserver(self, selector: #selector(self.preferencesUpdated), name: Preferences.didChangeNotification, object: nil) preferencesUpdated() @@ -74,7 +81,7 @@ public class TerminalController: VT100 { stringSupplier.colorMap = preferences.colorMap stringSupplier.fontMetrics = preferences.fontMetrics! - refresh() + terminal?.refresh(startRow: 0, endRow: terminal?.rows ?? 0) } // MARK: - Sub Process @@ -90,50 +97,23 @@ public class TerminalController: VT100 { try subProcess!.stop() } - // MARK: - Object lifecycle - - deinit { - updateTimer?.invalidate() - } - -} - -extension TerminalController: TerminalInputProtocol { - - public func receiveKeyboardInput(data: Data) { - // Forward the data from the keyboard directly to the subprocess - subProcess!.write(data: data) - } - - public func openSettings() { - delegate!.openSettings() - } - -} - -// ScreenBufferRefreshDelegate -extension TerminalController { - - override public func refresh() { - super.refresh() - - // TODO: This is called due to -[VT100 init], and we aren’t ready yet… We’ll be called when we - // are anyway, so don’t worry about it - if updateTimer == nil { - return - } + // MARK: - Terminal - isDirty = true + public func readInputStream(_ data: Data) { + let bytes = Array(data) + terminal?.feed(byteArray: bytes) } @objc private func updateTimerFired() { - if !isDirty { + if terminal?.getUpdateRange() == nil { return } + terminal?.clearUpdateRange() + // TODO: We should handle the scrollback separately so it only appears if the user scrolls - let attributedString = self.stringSupplier.attributedString()! - let backgroundColor = self.stringSupplier.colorMap!.background! + let attributedString = self.stringSupplier.attributedString() + let backgroundColor = self.stringSupplier.colorMap!.background self.delegate?.refresh(attributedString: attributedString, backgroundColor: backgroundColor) @@ -144,15 +124,54 @@ extension TerminalController { // self.delegate?.refresh(attributedString: attributedString, backgroundColor: backgroundColor) // } // } + } + + // MARK: - Object lifecycle - self.isDirty = false + deinit { + updateTimer?.invalidate() } - override public func activateBell() { - super.activateBell() +} + +extension TerminalController: TerminalDelegate { + + public func send(source: Terminal, data: ArraySlice) { + subProcess?.write(data: Data(data)) + } + + public func bell(source: Terminal) { delegate?.activateBell() } + public func sizeChanged(source: Terminal) { + // TODO +// let screenSize = ScreenSize(width: UInt16(source.cols), +// height: UInt16(source.rows)) +// if self.screenSize != screenSize { +// self.screenSize = screenSize +// } + } + + public func setTerminalTitle(source: Terminal, title: String) { + delegate?.titleDidChange(title) + } + +} + +extension TerminalController: TerminalInputProtocol { + + public var applicationCursor: Bool { terminal?.applicationCursor ?? false } + + public func receiveKeyboardInput(data: Data) { + // Forward the data from the keyboard directly to the subprocess + subProcess!.write(data: data) + } + + public func openSettings() { + delegate!.openSettings() + } + } extension TerminalController: SubProcessDelegate { diff --git a/Common/Extensions/Color+Additions.swift b/Common/Extensions/Color+Additions.swift new file mode 100644 index 0000000..c713213 --- /dev/null +++ b/Common/Extensions/Color+Additions.swift @@ -0,0 +1,22 @@ +// +// Color+Additions.swift +// NewTerm Common +// +// Created by Adam Demasi on 2/4/21. +// + +import SwiftTerm + +extension Color { + + convenience init(_ uiColor: UIColor) { + var r: CGFloat = 0 + var g: CGFloat = 0 + var b: CGFloat = 0 + uiColor.getRed(&r, green: &g, blue: &b, alpha: nil) + self.init(red: UInt16(r * 255), + green: UInt16(g * 255), + blue: UInt16(b * 255)) + } + +} diff --git a/Common/UI/CrossPlatformUI.swift b/Common/UI/CrossPlatformUI.swift index d7843e9..3a52963 100644 --- a/Common/UI/CrossPlatformUI.swift +++ b/Common/UI/CrossPlatformUI.swift @@ -8,13 +8,12 @@ #if os(macOS) import AppKit -public typealias Color = NSColor -public typealias Font = NSFont -public typealias UIFont = NSFont +public typealias UIColor = NSColor +public typealias Font = NSFont +public typealias UIFont = NSFont public typealias UIFontDescriptor = NSFontDescriptor #else import UIKit -public typealias Color = UIColor public typealias Font = UIFont #endif diff --git a/Common/VT100/ColorMap.swift b/Common/VT100/ColorMap.swift new file mode 100644 index 0000000..68e2a2f --- /dev/null +++ b/Common/VT100/ColorMap.swift @@ -0,0 +1,129 @@ +// +// ColorMap.swift +// NewTerm Common +// +// Created by Adam Demasi on 2/4/21. +// + +#if os(iOS) +import UIKit +#else +import AppKit +#endif + +import SwiftTerm +import os.log + +open class ColorMap { + + public let background: UIColor + public let foreground: UIColor + public let foregroundBold: UIColor + public let foregroundCursor: UIColor + public let backgroundCursor: UIColor + + public let ansiColors: [UIColor] + + public let isDark: Bool + + #if os(iOS) + open var userInterfaceStyle: UIUserInterfaceStyle { isDark ? .dark : .light } + #else + open var appearanceStyle: NSAppearanceName { isDark ? .darkAqua : .aqua } + #endif + + public init(dictionary: [String: Any]) { + if let color = dictionary["Background"] as? String { + self.background = UIColor(propertyListValue: color) + } else { + self.background = .black + } + if let color = dictionary["Text"] as? String { + self.foreground = UIColor(propertyListValue: color) + } else { + self.foreground = UIColor(white: 0.95, alpha: 1) + } + if let color = dictionary["BoldText"] as? String { + self.foregroundBold = UIColor(propertyListValue: color) + } else { + self.foregroundBold = .white + } + if let color = dictionary["Cursor"] as? String { + self.foregroundCursor = UIColor(propertyListValue: color) + self.backgroundCursor = self.foregroundCursor + } else { + self.foregroundCursor = UIColor(white: 0.95, alpha: 1) + self.backgroundCursor = UIColor(white: 0.4, alpha: 1) + } + if let isDark = dictionary["IsDark"] as? Bool { + self.isDark = isDark + } else { + self.isDark = true + } + if let colorTable = dictionary["ColorTable"] as? [String], + colorTable.count == COLOR_MAP_MAX_COLORS { + ansiColors = colorTable.map { item in UIColor(propertyListValue: item) } + } else { + // System 7.5 colors, why not? + ansiColors = [ + UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1), + UIColor(red: 0.6, green: 0.0, blue: 0.0, alpha: 1), + UIColor(red: 0.0, green: 0.6, blue: 0.0, alpha: 1), + UIColor(red: 0.6, green: 0.4, blue: 0.0, alpha: 1), + UIColor(red: 0.0, green: 0.0, blue: 0.6, alpha: 1), + UIColor(red: 0.6, green: 0.0, blue: 0.6, alpha: 1), + UIColor(red: 0.0, green: 0.6, blue: 0.6, alpha: 1), + UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1), + UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1), + UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1), + UIColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1), + UIColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 1), + UIColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1), + UIColor(red: 1.0, green: 0.0, blue: 1.0, alpha: 1), + UIColor(red: 0.0, green: 1.0, blue: 1.0, alpha: 1), + UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1) + ] + } + } + + open func color(for termColor: Attribute.Color, isForeground: Bool, isBold: Bool) -> UIColor { + switch termColor { + case .defaultColor: + return isForeground ? foreground : background + + case .defaultInvertedColor: + return isForeground ? background : foreground + + case .ansi256(let ansi): + let index = Int(ansi) + (isBold && ansi < 248 ? 8 : 0) + if index < 16 { + // ANSI color (0-15) + return ansiColors[index] + } else if index < 232 { + // 256-color table (16-231) + let tableIndex = index - 16 + let r = tableIndex / 36 == 0 ? 0 : ((tableIndex / 36) * 40 + 55) + let g = tableIndex % 36 / 6 == 0 ? 0 : ((tableIndex % 36 / 6) * 40 + 55) + let b = tableIndex % 6 == 0 ? 0 : (tableIndex % 6 * 40 + 55) + return UIColor(red: CGFloat(r) / 255, + green: CGFloat(g) / 255, + blue: CGFloat(b) / 255, + alpha: 1) + } else if index < 256 { + // Greys (232-255) + return UIColor(white: ((CGFloat(index) - 232) * 10 + 8) / 255, + alpha: 1) + } else { + os_log("Unexpected color index: %{public}i", index) + return foreground + } + + case .trueColor(let r, let g, let b): + return UIColor(red: CGFloat(r) / 255, + green: CGFloat(g) / 255, + blue: CGFloat(b) / 255, + alpha: 1) + } + } + +} diff --git a/Common/VT100/FontMetrics.swift b/Common/VT100/FontMetrics.swift index 3e16efe..aaa3681 100644 --- a/Common/VT100/FontMetrics.swift +++ b/Common/VT100/FontMetrics.swift @@ -14,6 +14,8 @@ import os.log @objc public let regularFont: Font @objc public let boldFont: Font + @objc public let italicFont: Font + @objc public let boldItalicFont: Font @objc public let ascent: CGFloat @objc public let descent: CGFloat @@ -48,9 +50,11 @@ import os.log } } - init(regularFont: Font, boldFont: Font) { + init(regularFont: Font, boldFont: Font, italicFont: Font, boldItalicFont: Font) { self.regularFont = regularFont self.boldFont = boldFont + self.italicFont = italicFont + self.boldItalicFont = boldItalicFont let attributedString = NSAttributedString(string: "A", attributes: [ .font: regularFont @@ -69,7 +73,7 @@ import os.log } override public var description: String { - return "FontMetrics: regularFont = \(regularFont); boldFont = \(boldFont); boundingBox = \(boundingBox)" + return "FontMetrics: regularFont = \(regularFont); boldFont = \(boldFont); italicFont = \(italicFont); boldItalicFont = \(boldItalicFont); boundingBox = \(boundingBox)" } } diff --git a/Common/VT100/StringSupplier.swift b/Common/VT100/StringSupplier.swift new file mode 100644 index 0000000..f05cad2 --- /dev/null +++ b/Common/VT100/StringSupplier.swift @@ -0,0 +1,128 @@ +// +// StringSupplier.swift +// NewTerm Common +// +// Created by Adam Demasi on 2/4/21. +// + +import Foundation +import SwiftTerm + +open class StringSupplier: AttributedStringSupplier { + + open var terminal: Terminal? + open var colorMap: ColorMap? + open var fontMetrics: FontMetrics? + + public func attributedString() -> NSAttributedString { + guard let terminal = terminal else { + fatalError() + } + + // TODO + let cursorPosition = terminal.getCursorLocation() + + let attributedString = NSMutableAttributedString() + + var lines = [String]() + var lastAttribute = Attribute.empty + for i in 0.. [NSAttributedString.Key: Any] { + var stringAttributes = [NSAttributedString.Key: Any]() + var fgColor = attribute.fg + var bgColor = attribute.bg + + if attribute.style.contains(.inverse) { + swap(&bgColor, &fgColor) + if fgColor == .defaultColor { + fgColor = .defaultInvertedColor + } + if bgColor == .defaultColor { + bgColor = .defaultInvertedColor + } + } + + stringAttributes[.foregroundColor] = colorMap?.color(for: fgColor, + isForeground: true, + isBold: attribute.style.contains(.bold)) + stringAttributes[.backgroundColor] = colorMap?.color(for: bgColor, + isForeground: false, + isBold: false) + + if attribute.style.contains(.underline) { + stringAttributes[.underlineStyle] = NSUnderlineStyle.single.rawValue + } + if attribute.style.contains(.crossedOut) { + stringAttributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue + } + + let font: UIFont? + if attribute.style.contains(.bold) { + if attribute.style.contains(.italic) { + font = fontMetrics?.boldItalicFont + } else { + font = fontMetrics?.boldFont + } + } else { + if attribute.style.contains(.italic) { + font = fontMetrics?.italicFont + } else { + font = fontMetrics?.regularFont + } + } + stringAttributes[.font] = font + + return stringAttributes + } + + // TODO + public func rowCount() -> Int32 { 0 } + public func string(forLine rowIndex: Int32) -> String! { nil } + +} diff --git a/Makefile b/Makefile index 4f8a098..97a02c7 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ ifeq ($(PLATFORM),mac) export TARGET = uikitformac:latest:13.0 else -export TARGET = iphone:13.7:10.0 -export ARCHS = armv7 arm64 +export TARGET = iphone:13.7:13.0 +export ARCHS = arm64 LINK_CEPHEI := 1 endif diff --git a/NewTerm.xcodeproj/project.pbxproj b/NewTerm.xcodeproj/project.pbxproj index b0ee951..eaf1173 100644 --- a/NewTerm.xcodeproj/project.pbxproj +++ b/NewTerm.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 48; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -11,6 +11,10 @@ 4E9B32DC260C65AE006A1FBC /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 4E9B32DA260C65AE006A1FBC /* Localizable.stringsdict */; }; 4E9B32E0260C66B5006A1FBC /* Bundle+NewTermAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9B32DF260C66B5006A1FBC /* Bundle+NewTermAdditions.swift */; }; 4E9B32E1260C66B5006A1FBC /* Bundle+NewTermAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9B32DF260C66B5006A1FBC /* Bundle+NewTermAdditions.swift */; }; + 4E9FB96C26171A01005AFCC8 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 4E9FB96B26171A01005AFCC8 /* SwiftTerm */; }; + 4E9FB97226172079005AFCC8 /* Color+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9FB97126172079005AFCC8 /* Color+Additions.swift */; }; + 4E9FB97526172293005AFCC8 /* StringSupplier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9FB97426172293005AFCC8 /* StringSupplier.swift */; }; + 4E9FB978261724FA005AFCC8 /* ColorMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9FB977261724FA005AFCC8 /* ColorMap.swift */; }; 4ECEE6ED25F9B87E00FAD26B /* TerminalPasswordInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECEE6EC25F9B87E00FAD26B /* TerminalPasswordInputView.swift */; }; 4ECFC23825FA3D2C007B0F51 /* LayoutGuide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECFC23725FA3D2C007B0F51 /* LayoutGuide.swift */; }; 4ECFC24625FCA67D007B0F51 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBB969622B5481B00585BE6 /* RootViewController.swift */; }; @@ -143,6 +147,9 @@ /* Begin PBXFileReference section */ 4E9B32DA260C65AE006A1FBC /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; 4E9B32DF260C66B5006A1FBC /* Bundle+NewTermAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+NewTermAdditions.swift"; sourceTree = ""; }; + 4E9FB97126172079005AFCC8 /* Color+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Additions.swift"; sourceTree = ""; }; + 4E9FB97426172293005AFCC8 /* StringSupplier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringSupplier.swift; sourceTree = ""; }; + 4E9FB977261724FA005AFCC8 /* ColorMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorMap.swift; sourceTree = ""; }; 4ECEE6EC25F9B87E00FAD26B /* TerminalPasswordInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalPasswordInputView.swift; sourceTree = ""; }; 4ECFC23725FA3D2C007B0F51 /* LayoutGuide.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutGuide.swift; sourceTree = ""; }; 4ECFC23B25FCA592007B0F51 /* NewTerm.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NewTerm.entitlements; sourceTree = ""; }; @@ -252,6 +259,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4E9FB96C26171A01005AFCC8 /* SwiftTerm in Frameworks */, CF71BCC322BB727B00AFB1E1 /* libcurses.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -458,6 +466,7 @@ CFBB969422B5481B00585BE6 /* NSArray+Additions.m */, CFBB967F22B5481B00585BE6 /* UIColor+HBAdditions.h */, CFBB968022B5481B00585BE6 /* UIColor+HBAdditions.m */, + 4E9FB97126172079005AFCC8 /* Color+Additions.swift */, ); path = Extensions; sourceTree = ""; @@ -497,6 +506,8 @@ CFBB969F22B5481B00585BE6 /* VT100StringSupplier.m */, CFBB96AE22B5481B00585BE6 /* VT100Types.h */, CF71BC6222BB69EF00AFB1E1 /* TerminalInputProtocol.swift */, + 4E9FB97426172293005AFCC8 /* StringSupplier.swift */, + 4E9FB977261724FA005AFCC8 /* ColorMap.swift */, ); path = VT100; sourceTree = ""; @@ -663,6 +674,9 @@ dependencies = ( ); name = "NewTerm Common"; + packageProductDependencies = ( + 4E9FB96B26171A01005AFCC8 /* SwiftTerm */, + ); productName = "NewTerm Common"; productReference = CF71BC8422BB6DBA00AFB1E1 /* NewTermCommon.framework */; productType = "com.apple.product-type.framework"; @@ -721,6 +735,9 @@ Base, ); mainGroup = CF22F9A81FFB97F7003175DE; + packageReferences = ( + 4E9FB96A26171A01005AFCC8 /* XCRemoteSwiftPackageReference "SwiftTerm" */, + ); productRefGroup = CF22F9A81FFB97F7003175DE; projectDirPath = ""; projectRoot = ""; @@ -797,6 +814,7 @@ CF71BCA122BB6E1100AFB1E1 /* ScreenChar.m in Sources */, CF71BCA222BB6E1100AFB1E1 /* VT100.m in Sources */, CF71BCA322BB6E1100AFB1E1 /* VT100ColorMap.m in Sources */, + 4E9FB97226172079005AFCC8 /* Color+Additions.swift in Sources */, CF71BCDE22BB73C200AFB1E1 /* Global.swift in Sources */, CF71BCA422BB6E1100AFB1E1 /* VT100Screen.m in Sources */, CF71BCA522BB6E1100AFB1E1 /* VT100Terminal.m in Sources */, @@ -807,7 +825,9 @@ CF71BCF122BB8AAB00AFB1E1 /* FontMetrics.swift in Sources */, CF71BC9722BB6E0300AFB1E1 /* Preferences.swift in Sources */, CF71BC9822BB6E0300AFB1E1 /* SubProcess.swift in Sources */, + 4E9FB97526172293005AFCC8 /* StringSupplier.swift in Sources */, CF71BC9922BB6E0300AFB1E1 /* TerminalController.swift in Sources */, + 4E9FB978261724FA005AFCC8 /* ColorMap.swift in Sources */, CF71BC9A22BB6E0300AFB1E1 /* CrossPlatformUI.swift in Sources */, CF71BC9B22BB6E0300AFB1E1 /* TerminalInputProtocol.swift in Sources */, ); @@ -917,7 +937,7 @@ Common/External, Common/External/ncurses, ); - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(SWIFT_OLD_RPATH)"; LIBRARY_SEARCH_PATHS = Common/External; MACOSX_DEPLOYMENT_TARGET = 11.0; @@ -959,7 +979,7 @@ Common/External, Common/External/ncurses, ); - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(SWIFT_OLD_RPATH)"; LIBRARY_SEARCH_PATHS = Common/External; MACOSX_DEPLOYMENT_TARGET = 11.0; @@ -1018,7 +1038,10 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "Mac/Supporting Files/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = ws.hbang.NewTermMac; @@ -1060,7 +1083,10 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "Mac/Supporting Files/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = ws.hbang.NewTermMac; @@ -1111,7 +1137,11 @@ HEADER_SEARCH_PATHS = "Common/**"; INFOPLIST_FILE = "Common/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "ws.hbang.NewTerm-Common"; @@ -1161,7 +1191,11 @@ HEADER_SEARCH_PATHS = "Common/**"; INFOPLIST_FILE = "Common/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "ws.hbang.NewTerm-Common"; @@ -1245,9 +1279,10 @@ iOS, ); INFOPLIST_FILE = "iOS/Supporting Files/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(THEOS_VENDOR_LIBRARY_PATH)", @@ -1335,9 +1370,10 @@ iOS, ); INFOPLIST_FILE = "iOS/Supporting Files/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(THEOS_VENDOR_LIBRARY_PATH)", @@ -1353,8 +1389,9 @@ PRODUCT_NAME = NewTerm; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OBJC_BRIDGING_HEADER = "iOS/Supporting Files/BridgingHeader.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,6"; VALIDATE_PRODUCT = YES; @@ -1410,6 +1447,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 4E9FB96A26171A01005AFCC8 /* XCRemoteSwiftPackageReference "SwiftTerm" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/migueldeicaza/SwiftTerm.git"; + requirement = { + branch = master; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 4E9FB96B26171A01005AFCC8 /* SwiftTerm */ = { + isa = XCSwiftPackageProductDependency; + package = 4E9FB96A26171A01005AFCC8 /* XCRemoteSwiftPackageReference "SwiftTerm" */; + productName = SwiftTerm; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = CF22F9A91FFB97F7003175DE /* Project object */; } diff --git a/NewTerm.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/NewTerm.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..39f8026 --- /dev/null +++ b/NewTerm.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "SwiftTerm", + "repositoryURL": "https://github.com/migueldeicaza/SwiftTerm.git", + "state": { + "branch": "master", + "revision": "73e87770446114bb1fdc2b60485912b80a40232e", + "version": null + } + } + ] + }, + "version": 1 +} diff --git a/iOS/UI/Terminal/TerminalSampleView.swift b/iOS/UI/Terminal/TerminalSampleView.swift index eb3156e..e8a5026 100644 --- a/iOS/UI/Terminal/TerminalSampleView.swift +++ b/iOS/UI/Terminal/TerminalSampleView.swift @@ -39,7 +39,8 @@ class TerminalSampleView: UIView { @objc func preferencesUpdated() { let preferences = Preferences.shared - stringSupplier.colorMap = preferences.colorMap + // TODO +// stringSupplier.colorMap = preferences.colorMap stringSupplier.fontMetrics = preferences.fontMetrics textView.backgroundColor = stringSupplier.colorMap.background textView.attributedText = stringSupplier.attributedString() diff --git a/iOS/UI/Top Bar/TabToolbarViewController.swift b/iOS/UI/Top Bar/TabToolbarViewController.swift index d95fe8d..4e5174b 100644 --- a/iOS/UI/Top Bar/TabToolbarViewController.swift +++ b/iOS/UI/Top Bar/TabToolbarViewController.swift @@ -232,6 +232,20 @@ class TabToolbarViewController: UIViewController { tabsCollectionView.layoutIfNeeded() } + func tabDidUpdate(at index: Int) { + tabsCollectionView.reloadData() + tabsCollectionView.layoutIfNeeded() + + if dataSource!.selectedTerminalIndex() == index { + titleLabel.text = dataSource?.terminalName(at: index) + } + } + + private func selectTerminal(at index: Int) { + delegate?.selectTerminal(at: index) + titleLabel.text = dataSource?.terminalName(at: index) + } + } extension TabToolbarViewController: UIToolbarDelegate { @@ -260,11 +274,11 @@ extension TabToolbarViewController: UICollectionViewDataSource, UICollectionView } func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return CGSize(width: 94, height: tabsCollectionView.frame.size.height) + return CGSize(width: 130, height: tabsCollectionView.frame.size.height) } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - delegate?.selectTerminal(at: indexPath.item) + selectTerminal(at: indexPath.item) } } diff --git a/iOS/View Controllers/RootViewController.swift b/iOS/View Controllers/RootViewController.swift index 61c2da9..d23c7d8 100644 --- a/iOS/View Controllers/RootViewController.swift +++ b/iOS/View Controllers/RootViewController.swift @@ -10,10 +10,12 @@ import UIKit class RootViewController: UIViewController { - var terminals: [TerminalSessionViewController] = [] - var selectedTabIndex = Int(0) + private var terminals: [TerminalSessionViewController] = [] + private var selectedTabIndex = Int(0) - let tabToolbar = TabToolbarViewController() + private let tabToolbar = TabToolbarViewController() + + private var titleObservers = [NSKeyValueObservation]() override func loadView() { super.loadView() @@ -78,8 +80,13 @@ class RootViewController: UIViewController { terminals.append(terminalViewController) - tabToolbar.didAddTab(at: terminals.count - 1) - selectTerminal(at: terminals.count - 1) + let index = terminals.count - 1 + tabToolbar.didAddTab(at: index) + selectTerminal(at: index) + + titleObservers.append(terminalViewController.observe(\.title, changeHandler: { viewController, _ in + self.tabToolbar.tabDidUpdate(at: index) + })) } func removeTerminal(terminal terminalViewController: TerminalSessionViewController) { @@ -92,6 +99,7 @@ class RootViewController: UIViewController { terminalViewController.view.removeFromSuperview() terminals.remove(at: index) + titleObservers.remove(at: index) // If this was the last tab, close the window (or make a new tab if not supported). Otherwise // select the closest tab we have available diff --git a/iOS/View Controllers/TerminalSessionViewController.swift b/iOS/View Controllers/TerminalSessionViewController.swift index 43c42a8..902d9e9 100644 --- a/iOS/View Controllers/TerminalSessionViewController.swift +++ b/iOS/View Controllers/TerminalSessionViewController.swift @@ -231,7 +231,7 @@ class TerminalSessionViewController: UIViewController { insets.top += view.safeAreaInsets.top } - offset.y = terminalController.scrollbackLines() == 0 ? -insets.top : bottom + textView.contentSize.height - textView.frame.size.height + offset.y = terminalController.scrollbackLines == 0 ? -insets.top : bottom + textView.contentSize.height - textView.frame.size.height // If the offset has changed, update it and our lastAutomaticScrollOffset if textView.contentOffset.y != offset.y { @@ -347,6 +347,10 @@ extension TerminalSessionViewController: TerminalControllerDelegate { } } + func titleDidChange(_ title: String?) { + self.title = title + } + @objc func activatePasswordManager() { if #available(iOS 11, *) { keyInput.activatePasswordManager() diff --git a/prefs/HBNTPreferencesRootListController.m b/prefs/HBNTPreferencesRootListController.m index 9105af7..b944c79 100644 --- a/prefs/HBNTPreferencesRootListController.m +++ b/prefs/HBNTPreferencesRootListController.m @@ -25,7 +25,6 @@ - (void)loadView { self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(dismiss)]; HBAppearanceSettings *appearance = [[HBAppearanceSettings alloc] init]; - appearance.tintColor = [UIApplication sharedApplication].keyWindow.tintColor; if (@available(iOS 13.0, *)) { } else { appearance.translucentNavigationBar = YES;