Skip to content

Commit

Permalink
[common] Implement a cute error screen when the shell fails to launch
Browse files Browse the repository at this point in the history
  • Loading branch information
kirb committed Nov 20, 2021
1 parent 365c290 commit 3891004
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 10 deletions.
77 changes: 77 additions & 0 deletions Common/Controllers/ColorBars.swift
Original file line number Diff line number Diff line change
@@ -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..<firstSectionSize.rows {
for color in bars[0] {
data.append("\u{1b}[48;2;\(color)m\(space)".data(using: .utf8)!)
}
data.append("\u{1b}[0m\r\n".data(using: .utf8)!)
}
for _ in 0..<secondSectionSize.rows {
for color in bars[1] {
data.append("\u{1b}[48;2;\(color)m\(space)".data(using: .utf8)!)
}
data.append("\u{1b}[0m\r\n".data(using: .utf8)!)
}
let finalSpace = String(repeating: " ", count: Int(thirdSectionSize.cols))
let finalInnerWidth = Int(Double(thirdSectionSize.cols) / 3)
let finalInnerSpace = String(repeating: " ", count: finalInnerWidth)
for _ in 0..<thirdSectionSize.rows {
for i in 0..<bars[2].count {
// Special case: There is a gradient of 3 colors in the second-last rectangle.
let space = i >= 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
}

}
16 changes: 7 additions & 9 deletions Common/Controllers/SubProcess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import os.log

enum SubProcessIllegalStateError: Error {
case alreadyStarted, notStarted
case openPtyFailed, forkFailed, inSandbox
case openPtyFailed, forkFailed(errno: Int32)
case deallocatedWhileRunning
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down
17 changes: 16 additions & 1 deletion Common/Controllers/TerminalController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions NewTerm.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -123,6 +124,7 @@
4E0A97A32685D1CD0022F569 /* SettingsAcknowledgementsTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAcknowledgementsTextView.swift; sourceTree = "<group>"; };
4E2492CF26230E59002C47CB /* TerminalController+ITermExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TerminalController+ITermExtensions.swift"; sourceTree = "<group>"; };
4E32378E26FB2D4400AEDA06 /* UIApplication+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Additions.swift"; sourceTree = "<group>"; };
4E3237FA26FC5FC300AEDA06 /* ColorBars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorBars.swift; sourceTree = "<group>"; };
4E460120261EC8C0004DBCC2 /* UpdateCheckManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCheckManager.swift; sourceTree = "<group>"; };
4E460128261FF23F004DBCC2 /* SettingsSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSceneDelegate.swift; sourceTree = "<group>"; };
4E479712262D44D8003CF48C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
Expand Down Expand Up @@ -312,6 +314,7 @@
CFBB96C222B5481B00585BE6 /* SubProcess.swift */,
CFBB96C522B5481B00585BE6 /* TerminalController.swift */,
4E2492CF26230E59002C47CB /* TerminalController+ITermExtensions.swift */,
4E3237FA26FC5FC300AEDA06 /* ColorBars.swift */,
);
path = Controllers;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 */,
Expand Down
4 changes: 4 additions & 0 deletions Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
<key>PASSWORD_MANAGER</key>
<string>Password Manager</string>

<!-- Message displayed in the terminal when it fails to start. -->
<key>TERMINAL_LAUNCH_FAILED</key>
<string>The shell process quit unexpectedly.</string>

<!-- Alert title displayed when a terminal could not be launched. -->
<key>TERMINAL_LAUNCH_FAILED_TITLE</key>
<string>Couldn’t start the terminal because the shell process quit unexpectedly.</string>
Expand Down

0 comments on commit 3891004

Please sign in to comment.