diff --git a/Common/Controllers/ColorBars.swift b/Common/Controllers/ColorBars.swift new file mode 100644 index 0000000..b4e3ae9 --- /dev/null +++ b/Common/Controllers/ColorBars.swift @@ -0,0 +1,77 @@ +// +// ColorBars.swift +// NewTerm Common +// +// Created by Adam Demasi on 23/9/21. +// + +import Foundation + +struct ColorBars { + + private struct RGB: CustomStringConvertible { + var r: Int, g: Int, b: Int + + init(_ r: Int, _ g: Int, _ b: Int) { + self.r = r + self.g = g + self.b = b + } + + var description: String { "\(r);\(g);\(b)" } + } + + private static let bars: [[RGB]] = [ + [RGB(192, 192, 192), RGB(192, 192, 0), RGB( 0, 192, 192), RGB( 0, 192, 0), RGB(192, 0, 192), RGB(192, 0, 0), RGB( 0, 0, 192)], + [RGB( 0, 0, 192), RGB( 19, 19, 19), RGB(192, 0, 192), RGB( 19, 19, 19), RGB( 0, 192, 192), RGB( 19, 19, 19), RGB(192, 192, 192)], + [RGB( 0, 33, 76), RGB(255, 255, 255), RGB( 50, 0, 106), RGB( 19, 19, 19), RGB( 9, 9, 9), RGB( 19, 19, 19), RGB( 29, 29, 29), RGB( 19, 19, 19)] + ] + + static func render(screenSize: ScreenSize, message: String) -> Data { + // Don’t bother drawing if the screen is too small + if screenSize.cols < 7 || screenSize.rows < 10 { + return Data() + } + + // Draw color bars + let firstSectionSize = ScreenSize(cols: UInt(Double(screenSize.cols) / 7), + rows: UInt(Double(screenSize.rows) * 0.66)) + let thirdSectionSize = ScreenSize(cols: UInt(Double(screenSize.cols) / 6), + rows: UInt(Double(screenSize.rows) * 0.25)) + let secondSectionSize = ScreenSize(cols: UInt(Double(screenSize.cols) / 7), + rows: screenSize.rows - firstSectionSize.rows - thirdSectionSize.rows - 1) + var data = "\u{1b}c".data(using: .utf8)! + let space = String(repeating: " ", count: Int(firstSectionSize.cols)) + for _ in 0..= 4 && i <= 6 ? finalInnerSpace : finalSpace + data.append("\u{1b}[48;2;\(bars[2][i])m\(space)".data(using: .utf8)!) + } + data.append("\u{1b}[0m\r\n".data(using: .utf8)!) + } + + // Draw error text + let textPosition = ScreenSize(cols: UInt(max(0, Double((Int(firstSectionSize.cols * 7) - message.count + 2) / 2).rounded(.toNearestOrEven))), + rows: UInt(Double(screenSize.rows / 2).rounded(.down))) + data.append("\u{1b}[\(textPosition.rows);\(textPosition.cols)H\u{1b}[1;7m \(message) \u{1b}[m\u{1b}[\(screenSize.cols);0H".data(using: .utf8)!) + + return data + } + +} diff --git a/Common/Controllers/SubProcess.swift b/Common/Controllers/SubProcess.swift index 859ce5d..1ae0fdb 100644 --- a/Common/Controllers/SubProcess.swift +++ b/Common/Controllers/SubProcess.swift @@ -11,7 +11,7 @@ import os.log enum SubProcessIllegalStateError: Error { case alreadyStarted, notStarted - case openPtyFailed, forkFailed, inSandbox + case openPtyFailed, forkFailed(errno: Int32) case deallocatedWhileRunning } @@ -64,12 +64,9 @@ class SubProcess: NSObject { switch pid { case -1: // Fork failed. - os_log("Fork failed: %{public errno}d", type: .error, errno) - if errno == EPERM { - throw SubProcessIllegalStateError.inSandbox - } else { - throw SubProcessIllegalStateError.forkFailed - } + let error = errno + os_log("Fork failed: %{public errno}d", type: .error, error) + throw SubProcessIllegalStateError.forkFailed(errno: error) case 0: // We’re in the fork. Execute the login shell. @@ -99,8 +96,9 @@ class SubProcess: NSObject { } if execve(path, args, env) == -1 { - os_log("%{public}@: exec failed: %{public errno}d", type: .error, path, errno) - throw SubProcessIllegalStateError.forkFailed + let error = errno + os_log("%{public}@: exec failed: %{public errno}d", type: .error, path, error) + throw SubProcessIllegalStateError.forkFailed(errno: error) } break diff --git a/Common/Controllers/TerminalController.swift b/Common/Controllers/TerminalController.swift index 4eab1d5..06cb960 100644 --- a/Common/Controllers/TerminalController.swift +++ b/Common/Controllers/TerminalController.swift @@ -43,6 +43,7 @@ public class TerminalController { internal var terminal: Terminal? private var subProcess: SubProcess? + private var subProcessFailureErrno: Int32? private let stringSupplier = StringSupplier() private var processLaunchDate: Date? @@ -171,7 +172,16 @@ public class TerminalController { subProcess = SubProcess() subProcess!.delegate = self processLaunchDate = Date() - try subProcess!.start() + do { + try subProcess!.start() + } catch { + if case SubProcessIllegalStateError.forkFailed(let forkError) = error { + subProcessFailureErrno = forkError + } else { + subProcessFailureErrno = -1 + } + throw error + } } public func stopSubProcess() throws { @@ -222,6 +232,11 @@ public class TerminalController { subProcess?.screenSize = screenSize terminal.resize(cols: Int(screenSize.cols), rows: Int(screenSize.rows)) + + if let error = subProcessFailureErrno { + let message = String(utf8String: strerror(error)) ?? "Error" + readInputStream(ColorBars.render(screenSize: screenSize, message: message)) + } } } diff --git a/NewTerm.xcodeproj/project.pbxproj b/NewTerm.xcodeproj/project.pbxproj index a86c5c2..412e901 100644 --- a/NewTerm.xcodeproj/project.pbxproj +++ b/NewTerm.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 4E2492D026230E59002C47CB /* TerminalController+ITermExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2492CF26230E59002C47CB /* TerminalController+ITermExtensions.swift */; }; 4E32378F26FB2D4400AEDA06 /* UIApplication+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E32378E26FB2D4400AEDA06 /* UIApplication+Additions.swift */; }; 4E32379026FB4B3300AEDA06 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E98081926184A2500E41883 /* String+Localization.swift */; }; + 4E3237FB26FC5FC300AEDA06 /* ColorBars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3237FA26FC5FC300AEDA06 /* ColorBars.swift */; }; 4E460121261EC8C0004DBCC2 /* UpdateCheckManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E460120261EC8C0004DBCC2 /* UpdateCheckManager.swift */; }; 4E460129261FF23F004DBCC2 /* SettingsSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E460128261FF23F004DBCC2 /* SettingsSceneDelegate.swift */; }; 4E479710262D44D8003CF48C /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 4E479713262D44D8003CF48C /* Localizable.stringsdict */; }; @@ -123,6 +124,7 @@ 4E0A97A32685D1CD0022F569 /* SettingsAcknowledgementsTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAcknowledgementsTextView.swift; sourceTree = ""; }; 4E2492CF26230E59002C47CB /* TerminalController+ITermExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TerminalController+ITermExtensions.swift"; sourceTree = ""; }; 4E32378E26FB2D4400AEDA06 /* UIApplication+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Additions.swift"; sourceTree = ""; }; + 4E3237FA26FC5FC300AEDA06 /* ColorBars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorBars.swift; sourceTree = ""; }; 4E460120261EC8C0004DBCC2 /* UpdateCheckManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCheckManager.swift; sourceTree = ""; }; 4E460128261FF23F004DBCC2 /* SettingsSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSceneDelegate.swift; sourceTree = ""; }; 4E479712262D44D8003CF48C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -312,6 +314,7 @@ CFBB96C222B5481B00585BE6 /* SubProcess.swift */, CFBB96C522B5481B00585BE6 /* TerminalController.swift */, 4E2492CF26230E59002C47CB /* TerminalController+ITermExtensions.swift */, + 4E3237FA26FC5FC300AEDA06 /* ColorBars.swift */, ); path = Controllers; sourceTree = ""; @@ -718,6 +721,7 @@ 4E9FB97226172079005AFCC8 /* Color+Additions.swift in Sources */, CF71BCDE22BB73C200AFB1E1 /* Global.swift in Sources */, CF71BCA822BB6E1100AFB1E1 /* NSLayoutConstraint+CompactConstraint.m in Sources */, + 4E3237FB26FC5FC300AEDA06 /* ColorBars.swift in Sources */, CF71BCA922BB6E1100AFB1E1 /* UIView+CompactConstraint.m in Sources */, CF71BCF122BB8AAB00AFB1E1 /* FontMetrics.swift in Sources */, CF71BC9722BB6E0300AFB1E1 /* Preferences.swift in Sources */, diff --git a/Resources/en.lproj/Localizable.strings b/Resources/en.lproj/Localizable.strings index f09d3bb..08d46dd 100644 --- a/Resources/en.lproj/Localizable.strings +++ b/Resources/en.lproj/Localizable.strings @@ -22,6 +22,10 @@ PASSWORD_MANAGER Password Manager + + TERMINAL_LAUNCH_FAILED + The shell process quit unexpectedly. + TERMINAL_LAUNCH_FAILED_TITLE Couldn’t start the terminal because the shell process quit unexpectedly.