diff --git a/ShhShell.xcodeproj/project.pbxproj b/ShhShell.xcodeproj/project.pbxproj index 9d9c7b8..8a540d9 100644 --- a/ShhShell.xcodeproj/project.pbxproj +++ b/ShhShell.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B15A992E0ABA0400F66E02 /* DialogView.swift */; }; A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; }; A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; }; + A9D819292E0E904200442D38 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819282E0E904200442D38 /* Theme.swift */; }; A9DA97712E0D30ED00142DDC /* HostSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97702E0D30ED00142DDC /* HostSymbol.swift */; }; A9DA97732E0D40C100142DDC /* SymbolPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97722E0D40C100142DDC /* SymbolPreview.swift */; }; /* End PBXBuildFile section */ @@ -112,6 +113,7 @@ A9B15A992E0ABA0400F66E02 /* DialogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogView.swift; sourceTree = ""; }; A9C4140B2E096DB7005E3047 /* SSHError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHError.swift; sourceTree = ""; }; A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = ""; }; + A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; A9DA97702E0D30ED00142DDC /* HostSymbol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostSymbol.swift; sourceTree = ""; }; A9DA97722E0D40C100142DDC /* SymbolPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolPreview.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -185,6 +187,7 @@ children = ( A92538C62DEE0742007E0A18 /* ShhShellApp.swift */, A93143C22DF61F5700FCD5DB /* ShhShell.entitlements */, + A9D8192A2E0E904900442D38 /* Themes */, A98554572E055398009051BD /* Keys */, A98554562E055394009051BD /* Host */, A93143C12DF61E8500FCD5DB /* SSH */, @@ -310,6 +313,14 @@ name = Frameworks; sourceTree = ""; }; + A9D8192A2E0E904900442D38 /* Themes */ = { + isa = PBXGroup; + children = ( + A9D819282E0E904200442D38 /* Theme.swift */, + ); + path = Themes; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -455,6 +466,7 @@ A96C6B022E0C49E800F377FE /* CenteredLabel.swift in Sources */, A923172F2E08851200ECE1E6 /* ShellView.swift in Sources */, A985545F2E056EDD009051BD /* KeychainLayer.swift in Sources */, + A9D819292E0E904200442D38 /* Theme.swift in Sources */, A93143C62DF61FE300FCD5DB /* ViewModifiers.swift in Sources */, A98554632E0587DF009051BD /* HostsView.swift in Sources */, A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */, diff --git a/ShhShell/ShhShellApp.swift b/ShhShell/ShhShellApp.swift index d4a41ee..6ba0983 100644 --- a/ShhShell/ShhShellApp.swift +++ b/ShhShell/ShhShellApp.swift @@ -21,6 +21,224 @@ struct ShhShellApp: App { keyManager: keyManager ) .colorScheme(.dark) + .onAppear { + let data = """ + + + + + Ansi 0 Color + + Blue Component + 0.0 + Green Component + 0.0 + Red Component + 0.0 + + Ansi 1 Color + + Blue Component + 0.40000000000000002 + Green Component + 0.40000000000000002 + Red Component + 0.80000000000000004 + + Ansi 10 Color + + Blue Component + 0.40784313729999999 + Green Component + 0.74117647060000003 + Red Component + 0.70980392160000005 + + Ansi 11 Color + + Blue Component + 0.4549019608 + Green Component + 0.77647058820000003 + Red Component + 0.94117647059999998 + + Ansi 12 Color + + Blue Component + 0.74509803919999995 + Green Component + 0.63529411759999999 + Red Component + 0.50588235290000005 + + Ansi 13 Color + + Blue Component + 0.73333333329999995 + Green Component + 0.58039215690000001 + Red Component + 0.69803921570000005 + + Ansi 14 Color + + Blue Component + 0.71764705880000002 + Green Component + 0.74509803919999995 + Red Component + 0.54117647059999996 + + Ansi 15 Color + + Blue Component + 0.99999129772186279 + Green Component + 0.99997437000274658 + Red Component + 1 + + Ansi 2 Color + + Blue Component + 0.40784313725490196 + Green Component + 0.74117647058823533 + Red Component + 0.70980392156862748 + + Ansi 3 Color + + Blue Component + 0.45490196078431372 + Green Component + 0.77647058823529413 + Red Component + 0.94117647058823528 + + Ansi 4 Color + + Blue Component + 0.74509803921568629 + Green Component + 0.63529411764705879 + Red Component + 0.50588235294117645 + + Ansi 5 Color + + Blue Component + 0.73333333333333328 + Green Component + 0.58039215686274503 + Red Component + 0.69803921568627447 + + Ansi 6 Color + + Blue Component + 0.71764705882352942 + Green Component + 0.74509803921568629 + Red Component + 0.54117647058823526 + + Ansi 7 Color + + Blue Component + 0.99999129772186279 + Green Component + 0.99997437000274658 + Red Component + 1 + + Ansi 8 Color + + Blue Component + 0.0 + Green Component + 0.0 + Red Component + 0.0 + + Ansi 9 Color + + Blue Component + 0.40000000000000002 + Green Component + 0.40000000000000002 + Red Component + 0.80000000000000004 + + Background Color + + Blue Component + 0.12941177189350128 + Green Component + 0.12156862765550613 + Red Component + 0.11372549086809158 + + Bold Color + + Blue Component + 0.77647058820000003 + Green Component + 0.7843137255 + Red Component + 0.7725490196 + + Cursor Color + + Blue Component + 0.77647058820000003 + Green Component + 0.7843137255 + Red Component + 0.7725490196 + + Cursor Text Color + + Blue Component + 0.12941177189350128 + Green Component + 0.12156862765550613 + Red Component + 0.11372549086809158 + + Foreground Color + + Blue Component + 0.77647058823529413 + Green Component + 0.78431372549019607 + Red Component + 0.77254901960784317 + + Selected Text Color + + Blue Component + 0.77647058820000003 + Green Component + 0.7843137255 + Red Component + 0.7725490196 + + Selection Color + + Blue Component + 0.25490196078431371 + Green Component + 0.23137254901960785 + Red Component + 0.21568627450980393 + + + + """.data(using: .utf8) + print("theme \(Theme.fromiTermColors(name: "tomorrow night", data: data))") + } } } } diff --git a/ShhShell/Themes/Theme.swift b/ShhShell/Themes/Theme.swift new file mode 100644 index 0000000..26c71c2 --- /dev/null +++ b/ShhShell/Themes/Theme.swift @@ -0,0 +1,125 @@ +// +// Theme.swift +// ShhShell +// +// Created by neon443 on 27/06/2025. +// + +import Foundation +import SwiftTerm + +struct Theme: Hashable, Equatable { + var name: String + var ansi: [SwiftTerm.Color] + var foreground: SwiftTerm.Color + var background: SwiftTerm.Color + var cursor: SwiftTerm.Color + var cursorText: SwiftTerm.Color + var bold: SwiftTerm.Color + var selectedText: SwiftTerm.Color + var selection: SwiftTerm.Color + + static func fromiTermColors(name: String, data: Data?) -> Theme? { + guard let data else { return nil } + guard let string = String(data: data, encoding: .utf8) else { return nil } + + let decoder = PropertyListDecoder() + + guard let decoded = try? decoder.decode(ThemeCodable.self, from: data) else { return nil } + + let theme = Theme( + name: name, + ansi: decoded.ansi, + foreground: Color(decoded.foreground), + background: Color(decoded.background), + cursor: Color(decoded.cursor), + cursorText: Color(decoded.cursorText), + bold: Color(decoded.bold), + selectedText: Color(decoded.selectedText), + selection: Color(decoded.selection) + ) + return theme + } +} + + +struct ThemeCodable: Codable { + var ansi0: ColorCodable + var ansi1: ColorCodable + var ansi2: ColorCodable + var ansi3: ColorCodable + var ansi4: ColorCodable + var ansi5: ColorCodable + var ansi6: ColorCodable + var ansi7: ColorCodable + var ansi8: ColorCodable + var ansi9: ColorCodable + var ansi10: ColorCodable + var ansi11: ColorCodable + var ansi12: ColorCodable + var ansi13: ColorCodable + var ansi14: ColorCodable + var ansi15: ColorCodable + var foreground: ColorCodable + var background: ColorCodable + var cursor: ColorCodable + var cursorText: ColorCodable + var bold: ColorCodable + var selectedText: ColorCodable + var selection: ColorCodable + + enum CodingKeys: String, CodingKey { + case ansi0 = "Ansi 0 Color" + case ansi1 = "Ansi 1 Color" + case ansi2 = "Ansi 2 Color" + case ansi3 = "Ansi 3 Color" + case ansi4 = "Ansi 4 Color" + case ansi5 = "Ansi 5 Color" + case ansi6 = "Ansi 6 Color" + case ansi7 = "Ansi 7 Color" + case ansi8 = "Ansi 8 Color" + case ansi9 = "Ansi 9 Color" + case ansi10 = "Ansi 10 Color" + case ansi11 = "Ansi 11 Color" + case ansi12 = "Ansi 12 Color" + case ansi13 = "Ansi 13 Color" + case ansi14 = "Ansi 14 Color" + case ansi15 = "Ansi 15 Color" + case foreground = "Foreground Color" + case background = "Background Color" + case cursor = "Cursor Color" + case cursorText = "Cursor Text Color" + case bold = "Bold Color" + case selectedText = "Selected Text Color" + case selection = "Selection Color" + } +} + +extension ThemeCodable { + var ansi: [Color] { + let arr = [ansi0, ansi1, ansi2, ansi3, ansi4, ansi5, ansi6, ansi7, ansi8, ansi9, ansi10, ansi11, ansi12, ansi13, ansi14, ansi15] + return arr.map(SwiftTerm.Color.init) + } +} + +struct ColorCodable: Codable { + var red: Double + var green: Double + var blue: Double + + enum CodingKeys: String, CodingKey { + case red = "Red Component" + case green = "Green Component" + case blue = "Blue Component" + } +} + + +extension SwiftTerm.Color { + convenience init(_ colorCodable: ColorCodable) { + let red = UInt16(colorCodable.red * 65535) + let green = UInt16(colorCodable.green * 65535) + let blue = UInt16(colorCodable.blue * 65535) + self.init(red: red, green: green, blue: blue) + } +} diff --git a/ShhShell/Views/Terminal/SSHTerminalView.swift b/ShhShell/Views/Terminal/SSHTerminalView.swift index 077e6ff..db6a072 100644 --- a/ShhShell/Views/Terminal/SSHTerminalView.swift +++ b/ShhShell/Views/Terminal/SSHTerminalView.swift @@ -34,6 +34,20 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie } } + func restoreScrollback() { + guard let scrollback = handler?.scrollback else { return } + guard !scrollback.isEmpty else { return } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { + self.getTerminal().resetToInitialState() + for line in scrollback { + self.feed(text: line) + } + self.setNeedsLayout() + self.setNeedsDisplay() + } + } + public override init(frame: CGRect) { super.init(frame: frame) terminalDelegate = self @@ -82,18 +96,4 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie public func bell(source: TerminalView) { handler?.ring() } - - func restoreScrollback() { - guard let scrollback = handler?.scrollback else { return } - guard !scrollback.isEmpty else { return } - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { - self.getTerminal().resetToInitialState() - for line in scrollback { - self.feed(text: line) - } - self.setNeedsLayout() - self.setNeedsDisplay() - } - } }