Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2f7356d3f | ||
|
|
747af5a654 | ||
|
|
811c336bb7 | ||
|
|
b1baef2135 | ||
|
|
e901724619 | ||
|
|
19c1cd4688 | ||
|
|
c1e65b0826 | ||
|
|
b9d65002bc | ||
|
|
d4b778b458 | ||
|
|
17d0a5a7f4 | ||
|
|
db899fbb0f | ||
|
|
750bf6ee62 | ||
|
|
64f1007da3 | ||
|
|
cbe1fbad11 | ||
|
|
a9a408730e | ||
|
|
49564727fc | ||
|
|
a4afa8ef3b | ||
|
|
0b039f2c42 | ||
|
|
1cc9eb7b6f | ||
|
|
3b6e5d4a73 | ||
|
|
c4ba38c3bf | ||
|
|
5132b4f69c | ||
|
|
37e2165725 | ||
|
|
528f04d56b | ||
|
|
6462943636 | ||
|
|
cac4e9ec37 | ||
|
|
896159af31 | ||
|
|
606945e4e6 | ||
|
|
2ff111ae6a | ||
|
|
f4498b95da | ||
|
|
5b31db14e7 | ||
|
|
e5627a2aaa | ||
|
|
a907480d1e | ||
|
|
7b127127e7 | ||
|
|
0b83009b39 | ||
|
|
d26d564fda | ||
|
|
d1408ee5d5 | ||
|
|
97ef265cd2 | ||
|
|
623b2a001a | ||
|
|
6d3b7f2f64 | ||
|
|
d39f176f72 | ||
|
|
2331c55abf | ||
|
|
7e0a02fad3 | ||
|
|
02d8757547 | ||
|
|
a71c994103 | ||
|
|
01413df52f | ||
|
|
26064e15d4 | ||
|
|
b981cc3f54 | ||
|
|
8cc1340f3f | ||
|
|
0cd92ee12a | ||
|
|
68fb7d4844 | ||
|
|
ff02122bcc | ||
|
|
7c28cc79da | ||
|
|
efb6072af8 | ||
|
|
005b1ed9c9 | ||
|
|
4cd74c2633 | ||
|
|
4846831140 | ||
|
|
bcd52143cb | ||
|
|
76a6d9d2f2 | ||
|
|
262411049f | ||
|
|
28b92466a5 | ||
|
|
1f687f0b62 | ||
|
|
09a6e5a029 | ||
|
|
dc01156d9c | ||
|
|
18046d1208 | ||
|
|
cd822e1efc | ||
|
|
02131e798c | ||
|
|
ffc62b3f34 | ||
|
|
cf96b0e505 | ||
|
|
9cab6baea4 | ||
|
|
97aba7f818 | ||
|
|
6038b99b60 | ||
|
|
ed67afcf39 | ||
|
|
cbf67be9cc | ||
|
|
6836854972 | ||
|
|
7fb3ad93fb | ||
|
|
62debc38fb | ||
|
|
2da7667ee5 | ||
|
|
a80ff66f6e | ||
|
|
a5c6173951 | ||
|
|
e24ef96ccb | ||
|
|
f85ef3deaf | ||
|
|
cc028321db | ||
|
|
b0a20c74cc | ||
|
|
3f7f253f63 | ||
|
|
9e95f63494 | ||
|
|
99de1e12ef | ||
|
|
38ce138cea | ||
|
|
0a0d9cea9a | ||
|
|
a1d8edfb59 | ||
|
|
32861b7292 | ||
|
|
8ae9cc8ead | ||
|
|
4f92d34d1a | ||
|
|
f2d034b732 | ||
|
|
d4f31fda32 | ||
|
|
a7783eab47 | ||
|
|
d40ef8e03c | ||
|
|
f8951516e0 | ||
|
|
26ca95410b | ||
|
|
3ac6b8dde8 | ||
|
|
bebf08d085 | ||
|
|
18e5dbe9bf | ||
|
|
d1dd77fde3 | ||
|
|
7b4da73ffa | ||
|
|
a5563731f0 | ||
|
|
a454723727 | ||
|
|
657bd33eef | ||
|
|
9b1b04d755 | ||
|
|
6308ef1c2f | ||
|
|
2030c48ab3 | ||
|
|
3e45de2d32 | ||
|
|
b651c03364 | ||
|
|
e9ddf0c13e | ||
|
|
518946ace1 | ||
|
|
56ebf6c55d | ||
|
|
29a9d2b188 | ||
|
|
590c101db8 | ||
|
|
8fd10bb291 | ||
|
|
0b16e15044 | ||
|
|
2338d0d108 | ||
|
|
78bdb60350 | ||
|
|
171843c9a3 | ||
|
|
f9bf633616 | ||
|
|
c0d45cbd2a | ||
|
|
e8ad06b429 | ||
|
|
ca4a114c93 | ||
|
|
6c452b3961 | ||
|
|
95fd3dfe07 | ||
|
|
a6e59eec2c | ||
|
|
ce11f18455 | ||
|
|
4f9055b58f | ||
|
|
56c5634d9a | ||
|
|
587c85842f | ||
|
|
700c169e55 | ||
|
|
d7f498c164 | ||
|
|
4eb9cbb842 | ||
|
|
3e713b8561 | ||
|
|
d32356eaf6 | ||
|
|
c625b53195 | ||
|
|
bee12c8a39 | ||
|
|
693ef91fb8 | ||
|
|
100ffb0349 |
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "SwiftTerm"]
|
||||
path = SwiftTerm
|
||||
url = https://github.com/neon443/SwiftTerm
|
||||
branch = jelly
|
||||
@@ -5,8 +5,8 @@
|
||||
// Created by neon443 on 06/06/2025.
|
||||
//
|
||||
|
||||
VERSION = 1.6
|
||||
BUILD = 57
|
||||
VERSION = 1.10.2
|
||||
BUILD = 202
|
||||
|
||||
// Configuration settings file format documentation can be found at:
|
||||
// https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project
|
||||
|
||||
4
Privacy.md
Normal file
@@ -0,0 +1,4 @@
|
||||
We do not collect any data from your usage of this app.
|
||||
This means we cannot sell, use or leak any usage data as we simply do not have any.
|
||||
I am not responsible for any data loss.
|
||||
Feel free to inspect source code.
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
<br/>
|
||||
<p>
|
||||
<img src="https://github.com/neon443/ShhShell/blob/main/Resources/Assets.xcassets/AppIcon.appiconset/ShhShell.png?raw=true" title="icon" alt="icon" width="100" />
|
||||
<img src="https://files.catbox.moe/r55fjl.png" title="icon" alt="icon" width="100" />
|
||||
</p>
|
||||
<p>
|
||||
an ssh client for ios
|
||||
@@ -10,6 +10,11 @@
|
||||
made by neon443
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://apps.apple.com/app/id6746970159">
|
||||
<img src="https://github.com/neon443/ShhShell/blob/main/Resources/Download_on_the_App_Store_Badge_US-UK_blk_092917.png?raw=true" title="icon" alt="icon" width="100" />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://testflight.apple.com/join/uNKEAtSw">
|
||||
testflight
|
||||
|
||||
22
Resources/Assets.xcassets/ShhShell.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ShhShell-light.png",
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "ShhShell.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Resources/Assets.xcassets/ShhShell.imageset/ShhShell-light.png
vendored
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
Resources/Assets.xcassets/ShhShell.imageset/ShhShell.png
vendored
Normal file
|
After Width: | Height: | Size: 734 KiB |
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"symbols" : [
|
||||
{
|
||||
"filename" : "apple.terminal.on.rectangle.fill.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--Generator: Apple Native CoreSVG 341-->
|
||||
<!DOCTYPE svg
|
||||
PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 3300 2200">
|
||||
<!--glyph: "101E5C", point size: 100.0, font version: "20.0d10e1", template writer version: "138.0.0"-->
|
||||
<style>.SFSymbolsPreviewWireframe {fill:none;opacity:1.0;stroke:black;stroke-width:0.5}
|
||||
</style>
|
||||
<g id="Notes">
|
||||
<rect height="2200" id="artboard" style="fill:white;opacity:1" width="3300" x="0" y="0"/>
|
||||
<line style="fill:none;stroke:black;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="292" y2="292"/>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 263 322)">Weight/Scale Variations</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 559.711 322)">Ultralight</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 856.422 322)">Thin</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1153.13 322)">Light</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1449.84 322)">Regular</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1746.56 322)">Medium</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2043.27 322)">Semibold</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2339.98 322)">Bold</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2636.69 322)">Heavy</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2933.4 322)">Black</text>
|
||||
<line style="fill:none;stroke:black;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1903" y2="1903"/>
|
||||
<g transform="matrix(0.2 0 0 0.2 263 1933)">
|
||||
<path d="m46.2402 4.15039c21.7773 0 39.4531-17.627 39.4531-39.4043s-17.6758-39.4043-39.4531-39.4043c-21.7285 0-39.4043 17.627-39.4043 39.4043s17.6758 39.4043 39.4043 39.4043Zm0-7.42188c-17.6758 0-31.9336-14.3066-31.9336-31.9824s14.2578-31.9824 31.9336-31.9824 31.9824 14.3066 31.9824 31.9824-14.3066 31.9824-31.9824 31.9824Zm-17.9688-31.9824c0 2.14844 1.51367 3.61328 3.75977 3.61328h10.498v10.5957c0 2.19727 1.46484 3.71094 3.61328 3.71094 2.24609 0 3.71094-1.51367 3.71094-3.71094v-10.5957h10.5957c2.19727 0 3.71094-1.46484 3.71094-3.61328 0-2.19727-1.51367-3.71094-3.71094-3.71094h-10.5957v-10.5469c0-2.24609-1.46484-3.75977-3.71094-3.75977-2.14844 0-3.61328 1.51367-3.61328 3.75977v10.5469h-10.498c-2.24609 0-3.75977 1.51367-3.75977 3.71094Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.2 0 0 0.2 281.506 1933)">
|
||||
<path d="m58.5449 14.5508c27.4902 0 49.8047-22.3145 49.8047-49.8047s-22.3145-49.8047-49.8047-49.8047-49.8047 22.3145-49.8047 49.8047 22.3145 49.8047 49.8047 49.8047Zm0-8.30078c-22.9492 0-41.5039-18.5547-41.5039-41.5039s18.5547-41.5039 41.5039-41.5039 41.5039 18.5547 41.5039 41.5039-18.5547 41.5039-41.5039 41.5039Zm-22.6562-41.5039c0 2.39258 1.66016 4.00391 4.15039 4.00391h14.3555v14.4043c0 2.44141 1.66016 4.15039 4.05273 4.15039 2.44141 0 4.15039-1.66016 4.15039-4.15039v-14.4043h14.4043c2.44141 0 4.15039-1.61133 4.15039-4.00391 0-2.44141-1.70898-4.15039-4.15039-4.15039h-14.4043v-14.3555c0-2.49023-1.70898-4.19922-4.15039-4.19922-2.39258 0-4.05273 1.70898-4.05273 4.19922v14.3555h-14.3555c-2.49023 0-4.15039 1.70898-4.15039 4.15039Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.2 0 0 0.2 304.924 1933)">
|
||||
<path d="m74.8535 28.3203c35.1074 0 63.623-28.4668 63.623-63.5742s-28.5156-63.623-63.623-63.623-63.5742 28.5156-63.5742 63.623 28.4668 63.5742 63.5742 63.5742Zm0-9.08203c-30.127 0-54.4922-24.3652-54.4922-54.4922s24.3652-54.4922 54.4922-54.4922 54.4922 24.3652 54.4922 54.4922-24.3652 54.4922-54.4922 54.4922Zm-28.8574-54.4922c0 2.58789 1.85547 4.39453 4.58984 4.39453h19.7266v19.7754c0 2.68555 1.85547 4.58984 4.44336 4.58984 2.68555 0 4.54102-1.85547 4.54102-4.58984v-19.7754h19.7754c2.68555 0 4.58984-1.80664 4.58984-4.39453 0-2.73438-1.85547-4.58984-4.58984-4.58984h-19.7754v-19.7266c0-2.73438-1.85547-4.63867-4.54102-4.63867-2.58789 0-4.44336 1.9043-4.44336 4.63867v19.7266h-19.7266c-2.73438 0-4.58984 1.85547-4.58984 4.58984Z"/>
|
||||
</g>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 263 1953)">Design Variations</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1971)">Symbols are supported in up to nine weights and three scales.</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1989)">For optimal layout with text and other symbols, vertically align</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 2007)">symbols with the adjacent text.</text>
|
||||
<line style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="776" x2="776" y1="1919" y2="1933"/>
|
||||
<g transform="matrix(0.2 0 0 0.2 776 1933)">
|
||||
<path d="m16.5527 0.78125c2.58789 0 3.85742-0.976562 4.78516-3.71094l6.29883-17.2363h28.8086l6.29883 17.2363c0.927734 2.73438 2.19727 3.71094 4.73633 3.71094 2.58789 0 4.24805-1.5625 4.24805-4.00391 0-0.830078-0.146484-1.61133-0.537109-2.63672l-22.9004-60.9863c-1.12305-2.97852-3.125-4.49219-6.25-4.49219-3.02734 0-5.07812 1.46484-6.15234 4.44336l-22.9004 61.084c-0.390625 1.02539-0.537109 1.80664-0.537109 2.63672 0 2.44141 1.5625 3.95508 4.10156 3.95508Zm13.4766-28.3691 11.8652-32.8613h0.244141l11.8652 32.8613Z"/>
|
||||
</g>
|
||||
<line style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="792.836" x2="792.836" y1="1919" y2="1933"/>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 776 1953)">Margins</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 1971)">Leading and trailing margins on the left and right side of each symbol</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 1989)">can be adjusted by modifying the x-location of the margin guidelines.</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 2007)">Modifications are automatically applied proportionally to all</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 2025)">scales and weights.</text>
|
||||
<g transform="matrix(0.2 0 0 0.2 1289 1933)">
|
||||
<path d="m14.209 9.32617 8.49609 8.54492c4.29688 4.3457 9.22852 4.05273 13.8672-1.07422l53.4668-58.9355-4.83398-4.88281-53.0762 58.3984c-1.75781 2.00195-3.41797 2.49023-5.76172 0.146484l-5.85938-5.81055c-2.34375-2.29492-1.80664-4.00391 0.195312-5.81055l57.373-54.0039-4.88281-4.83398-57.959 54.4434c-4.93164 4.58984-5.32227 9.47266-1.02539 13.8184Zm32.0801-90.9668c-2.09961 2.05078-2.24609 4.93164-1.07422 6.88477 1.17188 1.80664 3.4668 2.97852 6.68945 2.14844 7.32422-1.70898 14.9414-2.00195 22.0703 2.68555l-2.92969 7.27539c-1.70898 4.15039-0.830078 7.08008 1.85547 9.81445l11.4746 11.5723c2.44141 2.44141 4.49219 2.53906 7.32422 2.05078l5.32227-0.976562 3.32031 3.36914-0.195312 2.7832c-0.195312 2.49023 0.439453 4.39453 2.88086 6.78711l3.80859 3.71094c2.39258 2.39258 5.46875 2.53906 7.8125 0.195312l14.5508-14.5996c2.34375-2.34375 2.24609-5.32227-0.146484-7.71484l-3.85742-3.80859c-2.39258-2.39258-4.24805-3.17383-6.64062-2.97852l-2.88086 0.244141-3.22266-3.17383 1.2207-5.61523c0.634766-2.83203-0.146484-5.0293-3.07617-7.95898l-10.9863-10.9375c-16.6992-16.6016-38.8672-16.2109-53.3203-1.75781Zm7.4707 1.85547c12.1582-8.88672 28.6133-7.37305 39.7461 3.75977l12.1582 12.0605c1.17188 1.17188 1.36719 2.09961 1.02539 3.80859l-1.61133 7.42188 7.51953 7.42188 4.93164-0.292969c1.26953-0.0488281 1.66016 0.0488281 2.63672 1.02539l2.88086 2.88086-12.207 12.207-2.88086-2.88086c-0.976562-0.976562-1.12305-1.36719-1.07422-2.68555l0.341797-4.88281-7.4707-7.42188-7.61719 1.26953c-1.61133 0.341797-2.34375 0.195312-3.56445-0.976562l-10.0098-10.0098c-1.26953-1.17188-1.41602-2.00195-0.634766-3.85742l4.39453-10.4492c-7.8125-7.27539-17.9688-10.4004-28.125-7.42188-0.78125 0.195312-1.07422-0.439453-0.439453-0.976562Z"/>
|
||||
</g>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 1289 1953)">Exporting</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 1289 1971)">Symbols should be outlined when exporting to ensure the</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 1289 1989)">design is preserved when submitting to Xcode.</text>
|
||||
<text id="template-version" style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1933)">Template v.6.0</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1951)">Requires Xcode 16 or greater</text>
|
||||
<text id="descriptive-name" style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1969)">Generated from apple.terminal.on.rectangle.fill</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1987)">Typeset at 100.0 points</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 726)">Small</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1156)">Medium</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1586)">Large</text>
|
||||
</g>
|
||||
<g id="Guides">
|
||||
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 696)">
|
||||
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
|
||||
</g>
|
||||
<line id="Baseline-S" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="696" y2="696"/>
|
||||
<line id="Capline-S" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="625.541" y2="625.541"/>
|
||||
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 1126)">
|
||||
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
|
||||
</g>
|
||||
<line id="Baseline-M" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1126" y2="1126"/>
|
||||
<line id="Capline-M" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1055.54" y2="1055.54"/>
|
||||
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 1556)">
|
||||
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
|
||||
</g>
|
||||
<line id="Baseline-L" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1556" y2="1556"/>
|
||||
<line id="Capline-L" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1485.54" y2="1485.54"/>
|
||||
<line id="right-margin-Black-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="2995.17" x2="2995.17" y1="600.785" y2="720.121"/>
|
||||
<line id="left-margin-Black-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="2871.63" x2="2871.63" y1="600.785" y2="720.121"/>
|
||||
<line id="right-margin-Regular-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="1507.61" x2="1507.61" y1="600.785" y2="720.121"/>
|
||||
<line id="left-margin-Regular-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="1392.08" x2="1392.08" y1="600.785" y2="720.121"/>
|
||||
<line id="right-margin-Ultralight-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="615.159" x2="615.159" y1="600.785" y2="720.121"/>
|
||||
<line id="left-margin-Ultralight-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="504.263" x2="504.263" y1="600.785" y2="720.121"/>
|
||||
</g>
|
||||
<g id="Symbols">
|
||||
<g id="Black-S" transform="matrix(1 0 0 1 2871.63 696)">
|
||||
<path class="SFSymbolsPreviewWireframe" d="M49.4629-29.1504L54.9316-32.5195L49.4141-35.8398C44.8242-38.5742 48.8281-45.6543 53.8086-42.5293L63.2324-36.6211C66.2109-34.7656 66.2598-30.2734 63.2324-28.418L53.8086-22.4121C48.8281-19.2871 44.8242-26.3184 49.4629-29.1504ZM64.502-23.584C64.502-25.7812 66.3086-27.4902 68.457-27.4902L81.1035-27.4902C83.3008-27.4902 85.0098-25.7812 85.0098-23.584C85.0098-21.4355 83.3008-19.6289 81.1035-19.6289L68.457-19.6289C66.2598-19.6289 64.502-21.4355 64.502-23.584ZM46.4844 9.08203L98.5352 9.08203C108.105 9.08203 113.77 3.41797 113.77-6.15234L113.77-43.8477C113.77-53.418 108.105-59.082 98.5352-59.082L46.4844-59.082C36.9141-59.082 31.25-53.418 31.25-43.8477L31.25-6.15234C31.25 3.41797 36.9141 9.08203 46.4844 9.08203ZM46.4844-66.4062L92.1875-66.4062L92.1875-66.4062C91.4062-74.707 85.9375-79.5898 77.0508-79.5898L25-79.5898C15.4297-79.5898 9.76562-73.9258 9.76562-64.3555L9.76562-26.6602C9.76562-17.4316 14.9902-11.8652 23.9258-11.4746L23.9258-11.4746L23.9258-43.8477C23.9258-57.4707 32.8613-66.4062 46.4844-66.4062Z"/>
|
||||
</g>
|
||||
<g id="Regular-S" transform="matrix(1 0 0 1 1392.08 696)">
|
||||
<path class="SFSymbolsPreviewWireframe" d="M42.627-30.6152L50.3418-35.3027L42.627-39.9902C39.5996-41.7969 42.1875-46.3379 45.459-44.2383L55.0781-38.2324C57.0801-36.9141 57.1777-33.6914 55.0781-32.373L45.459-26.3184C42.1875-24.2188 39.6484-28.8086 42.627-30.6152ZM57.1289-26.6602C57.1289-27.9785 58.1543-29.1016 59.5703-29.1016L72.4121-29.1016C73.8281-29.1016 74.9023-27.9785 74.9023-26.6602C74.9023-25.2441 73.8281-24.1211 72.4121-24.1211L59.5703-24.1211C58.1543-24.1211 57.1289-25.2441 57.1289-26.6602ZM41.6016 4.15039L93.3105 4.15039C101.611 4.15039 105.762 0.0488281 105.762-8.10547L105.762-44.043C105.762-52.2461 101.611-56.2988 93.3105-56.2988L41.6016-56.2988C33.252-56.2988 29.1504-52.2461 29.1504-44.043L29.1504-8.10547C29.1504 0.0976562 33.252 4.15039 41.6016 4.15039ZM41.6016-62.0117L86.4258-62.0117L86.4258-62.3535C86.4258-70.5566 82.2266-74.6582 73.9746-74.6582L22.2168-74.6582C13.8672-74.6582 9.76562-70.5566 9.76562-62.3535L9.76562-26.4648C9.76562-18.2617 13.8672-14.209 22.2168-14.209L23.4375-14.209L23.4375-44.043C23.4375-55.4199 30.127-62.0117 41.6016-62.0117Z"/>
|
||||
</g>
|
||||
<g id="Ultralight-S" transform="matrix(1 0 0 1 504.263 696)">
|
||||
<path class="SFSymbolsPreviewWireframe" d="M38.9033-32.9765L48.0713-38.4814L38.9033-43.9863C37.4653-44.7939 38.7364-46.7466 40.1006-45.9184L49.9014-39.7764C50.7227-39.23 50.9112-37.8237 49.9014-37.1411L40.1006-31.041C38.7364-30.2128 37.5596-32.1235 38.9033-32.9765ZM51.3619-30.2021C51.3619-30.7485 51.8423-31.2358 52.4864-31.2358L64.7379-31.2358C65.3819-31.2358 65.775-30.7485 65.775-30.2021C65.775-29.6035 65.3819-29.1162 64.7379-29.1162L52.4864-29.1162C51.8423-29.1162 51.3619-29.6035 51.3619-30.2021ZM36.5157 0.653836L91.1309 0.653836C97.6607 0.653836 101.13-2.8574 101.13-9.33153L101.13-46.2226C101.13-52.6548 97.6607-56.208 91.1309-56.208L36.5157-56.208C29.937-56.208 26.5166-52.7456 26.5166-46.2226L26.5166-9.33153C26.5166-2.76316 29.937 0.653836 36.5157 0.653836ZM36.5157-59.2871L84.3823-59.2871L84.3823-61.0366C84.3823-67.5142 80.9097-71.0254 74.3833-71.0254L19.7647-71.0254C13.2315-71.0254 9.76562-67.605 9.76562-61.0366L9.76562-24.1489C9.76562-17.626 13.2315-14.1636 19.7647-14.1636L23.4375-14.1636L23.4375-46.2226C23.4375-54.4663 28.2652-59.2871 36.5157-59.2871Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ShhShell.png",
|
||||
"filename" : "ShhShell 1.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ShhShell Blueprint@2x.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "tinted"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 558 KiB |
23
Resources/Assets.xcassets/betaBlueprint.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ShhShell Blueprint@1x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "ShhShell Blueprint@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ShhShell Blueprint@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Resources/Assets.xcassets/betaBlueprint.imageset/ShhShell Blueprint@1x.png
vendored
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
Resources/Assets.xcassets/betaBlueprint.imageset/ShhShell Blueprint@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 558 KiB |
BIN
Resources/Assets.xcassets/betaBlueprint.imageset/ShhShell Blueprint@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
Resources/Download_on_the_App_Store_Badge_US-UK_blk_092917.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
Resources/ShhShell.icon/Assets/$.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Resources/ShhShell.icon/Assets/>.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
Resources/ShhShell.icon/Assets/Image Layer.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
Resources/ShhShell.icon/Assets/Rectangle.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
Resources/ShhShell.icon/Assets/scanlines.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
Resources/ShhShell.icon/Assets/▮.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
78
Resources/ShhShell.icon/icon.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"fill" : {
|
||||
"automatic-gradient" : "extended-srgb:0.00000,0.53333,1.00000,1.00000"
|
||||
},
|
||||
"groups" : [
|
||||
{
|
||||
"layers" : [
|
||||
{
|
||||
"glass" : false,
|
||||
"image-name" : "scanlines.png",
|
||||
"name" : "scanlines",
|
||||
"opacity" : 0.4
|
||||
},
|
||||
{
|
||||
"glass" : true,
|
||||
"image-name" : "$.png",
|
||||
"name" : "$",
|
||||
"position" : {
|
||||
"scale" : 1,
|
||||
"translation-in-points" : [
|
||||
-210,
|
||||
-160
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"glass" : true,
|
||||
"image-name" : ">.png",
|
||||
"name" : ">",
|
||||
"position" : {
|
||||
"scale" : 1,
|
||||
"translation-in-points" : [
|
||||
-210,
|
||||
210
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"glass" : true,
|
||||
"image-name" : "▮.png",
|
||||
"name" : "▮",
|
||||
"position" : {
|
||||
"scale" : 1,
|
||||
"translation-in-points" : [
|
||||
70,
|
||||
210
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"glass" : true,
|
||||
"hidden" : false,
|
||||
"image-name" : "Image Layer.png",
|
||||
"name" : "Image Layer 4"
|
||||
},
|
||||
{
|
||||
"glass" : false,
|
||||
"image-name" : "Rectangle.png",
|
||||
"name" : "Rectangle 3"
|
||||
}
|
||||
],
|
||||
"shadow" : {
|
||||
"kind" : "neutral",
|
||||
"opacity" : 0.5
|
||||
},
|
||||
"translucency" : {
|
||||
"enabled" : true,
|
||||
"value" : 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"supported-platforms" : {
|
||||
"circles" : [
|
||||
"watchOS"
|
||||
],
|
||||
"squares" : "shared"
|
||||
}
|
||||
}
|
||||
BIN
ShhShell Blueprint.pxd
Normal file
BIN
ShhShell.pxd
@@ -19,9 +19,13 @@
|
||||
A90936B02E1AE9AB00856059 /* MesloLGS NF Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A90936A12E1AE9AB00856059 /* MesloLGS NF Italic.ttf */; };
|
||||
A90B38322E3E8FC9002B56FC /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90B38312E3E8FC9002B56FC /* AboutView.swift */; };
|
||||
A90B38342E3EA046002B56FC /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90B38332E3EA046002B56FC /* Bundle.swift */; };
|
||||
A91D27C72E54B97E00620B29 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91D27C62E54B97E00620B29 /* SettingsView.swift */; };
|
||||
A91D27CA2E54BED300620B29 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91D27C92E54BED300620B29 /* AppSettings.swift */; };
|
||||
A91F9E762E674928009FCB3A /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = A91F9E752E674928009FCB3A /* SwiftTerm */; };
|
||||
A923172A2E07113100ECE1E6 /* TerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92317292E07113100ECE1E6 /* TerminalController.swift */; };
|
||||
A923172D2E07138000ECE1E6 /* SSHTerminalDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A923172C2E07138000ECE1E6 /* SSHTerminalDelegate.swift */; };
|
||||
A923172F2E08851200ECE1E6 /* ShellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A923172E2E08851200ECE1E6 /* ShellView.swift */; };
|
||||
A9231A572E52041500974753 /* HostPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9231A562E52041500974753 /* HostPreview.swift */; };
|
||||
A92538C82DEE0742007E0A18 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92538C52DEE0742007E0A18 /* ContentView.swift */; };
|
||||
A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92538C62DEE0742007E0A18 /* ShhShellApp.swift */; };
|
||||
A92538CA2DEE0742007E0A18 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A92538C42DEE0742007E0A18 /* Assets.xcassets */; };
|
||||
@@ -39,11 +43,12 @@
|
||||
A9485C732E1AECD000209824 /* JetBrainsMonoNerdFontMono-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A9485C6F2E1AECD000209824 /* JetBrainsMonoNerdFontMono-BoldItalic.ttf */; };
|
||||
A9485C762E1AF59F00209824 /* FontManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9485C752E1AF59F00209824 /* FontManagerView.swift */; };
|
||||
A9485C782E1BFA5000209824 /* ThemeEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9485C772E1BFA5000209824 /* ThemeEditorView.swift */; };
|
||||
A94B832F2E5B929C00EBA09C /* Backgrounder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A94B832E2E5B929C00EBA09C /* Backgrounder.swift */; };
|
||||
A94B83312E5C79EE00EBA09C /* CRT.metal in Sources */ = {isa = PBXBuildFile; fileRef = A94B83302E5C79EE00EBA09C /* CRT.metal */; };
|
||||
A95FAA472DF3884B00DE2F5A /* Config.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = A95FAA462DF3884B00DE2F5A /* Config.xcconfig */; };
|
||||
A95FAA542DF4B62900DE2F5A /* LibSSH.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A95FAA502DF4B62100DE2F5A /* LibSSH.xcframework */; };
|
||||
A95FAA552DF4B62900DE2F5A /* LibSSH.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A95FAA502DF4B62100DE2F5A /* LibSSH.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
A95FAA562DF4B62A00DE2F5A /* openssl.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A95FAA512DF4B62100DE2F5A /* openssl.xcframework */; };
|
||||
A95FAA572DF4B62A00DE2F5A /* openssl.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A95FAA512DF4B62100DE2F5A /* openssl.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
A96A6A912E829C680084E249 /* OpenSSL-1.1.1m.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A96A6A902E829C680084E249 /* OpenSSL-1.1.1m.xcframework */; };
|
||||
A96BE6972E10846B00C0FEE9 /* catppuccinMocha.plist in Resources */ = {isa = PBXBuildFile; fileRef = A96BE68D2E10846B00C0FEE9 /* catppuccinMocha.plist */; };
|
||||
A96BE6982E10846B00C0FEE9 /* ubuntu.plist in Resources */ = {isa = PBXBuildFile; fileRef = A96BE6932E10846B00C0FEE9 /* ubuntu.plist */; };
|
||||
A96BE6992E10846B00C0FEE9 /* iTerm2SolarizedDark.plist in Resources */ = {isa = PBXBuildFile; fileRef = A96BE6902E10846B00C0FEE9 /* iTerm2SolarizedDark.plist */; };
|
||||
@@ -67,6 +72,7 @@
|
||||
A96C6B022E0C49E800F377FE /* CenteredLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6B012E0C49E800F377FE /* CenteredLabel.swift */; };
|
||||
A96C90A12E12B87A00724253 /* TextBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C90A02E12B87900724253 /* TextBox.swift */; };
|
||||
A96C90A32E12D53B00724253 /* KeyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C90A22E12D53900724253 /* KeyType.swift */; };
|
||||
A97AF1802E5D07BE00829443 /* CRTView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97AF17F2E5D07BE00829443 /* CRTView.swift */; };
|
||||
A9835C3C2E17CCA500969508 /* TrafficLights.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9835C3B2E17CCA500969508 /* TrafficLights.swift */; };
|
||||
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554542E05535F009051BD /* KeyManagerView.swift */; };
|
||||
A98554592E0553AA009051BD /* KeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554582E0553AA009051BD /* KeyManager.swift */; };
|
||||
@@ -74,8 +80,13 @@
|
||||
A98554612E058433009051BD /* HostsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554602E058433009051BD /* HostsManager.swift */; };
|
||||
A98554632E0587DF009051BD /* HostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554622E0587DF009051BD /* HostsView.swift */; };
|
||||
A98CAB442E4229F7005E4C42 /* HostSymbolPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98CAB432E4229F7005E4C42 /* HostSymbolPicker.swift */; };
|
||||
A9921DE12E5F5710009F72A8 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9921DE02E5F5710009F72A8 /* WelcomeView.swift */; };
|
||||
A994D64A2E5C94E200672395 /* ShaderTestingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A994D6492E5C94E200672395 /* ShaderTestingView.swift */; };
|
||||
A99D9F7B2E63513E00259166 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = A99D9F7A2E63513E00259166 /* SwiftTerm */; };
|
||||
A99D9F7E2E6351D100259166 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = A99D9F7D2E6351D100259166 /* SwiftTerm */; };
|
||||
A99E76202E7AD4BD00720621 /* ShhShell.icon in Resources */ = {isa = PBXBuildFile; fileRef = A99E761F2E7AD4BD00720621 /* ShhShell.icon */; };
|
||||
A9A2F4F62E3001D300D0AE9B /* AddSnippetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A2F4F52E3001D300D0AE9B /* AddSnippetView.swift */; };
|
||||
A9A587202E0BF220006B31E6 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = A9A5871F2E0BF220006B31E6 /* SwiftTerm */; };
|
||||
A9B1E5852E5F8E86009309E5 /* WelcomeChunk.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B1E5842E5F8E86009309E5 /* WelcomeChunk.swift */; };
|
||||
A9BA1D192E1D9AE1005BDCEF /* SwiftTerm.Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BA1D182E1D9AE1005BDCEF /* SwiftTerm.Color.swift */; };
|
||||
A9BA1D1E2E1EAD51005BDCEF /* SF-Mono-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = A9BA1D1C2E1EAD51005BDCEF /* SF-Mono-Bold.otf */; };
|
||||
A9BA1D1F2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = A9BA1D1D2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf */; };
|
||||
@@ -83,6 +94,8 @@
|
||||
A9C060ED2E3FBCD000CA9374 /* SnippetPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C060EC2E3FBCD000CA9374 /* SnippetPicker.swift */; };
|
||||
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; };
|
||||
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; };
|
||||
A9CC786B2E4E681400FAEE58 /* RecentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CC786A2E4E681400FAEE58 /* RecentsView.swift */; };
|
||||
A9CC786D2E4F534600FAEE58 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CC786C2E4F534600FAEE58 /* History.swift */; };
|
||||
A9D819292E0E904200442D38 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819282E0E904200442D38 /* Theme.swift */; };
|
||||
A9D8192D2E0E9EB500442D38 /* ThemeManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */; };
|
||||
A9D8192F2E0F1BEE00442D38 /* ThemeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8192E2E0F1BEE00442D38 /* ThemeButton.swift */; };
|
||||
@@ -124,7 +137,6 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
A95FAA572DF4B62A00DE2F5A /* openssl.xcframework in Embed Frameworks */,
|
||||
A95FAA552DF4B62900DE2F5A /* LibSSH.xcframework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
@@ -146,9 +158,12 @@
|
||||
A90936A52E1AE9AB00856059 /* SF-Mono-RegularItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-RegularItalic.otf"; sourceTree = "<group>"; };
|
||||
A90B38312E3E8FC9002B56FC /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||
A90B38332E3EA046002B56FC /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
|
||||
A91D27C62E54B97E00620B29 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||
A91D27C92E54BED300620B29 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
|
||||
A92317292E07113100ECE1E6 /* TerminalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalController.swift; sourceTree = "<group>"; };
|
||||
A923172C2E07138000ECE1E6 /* SSHTerminalDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHTerminalDelegate.swift; sourceTree = "<group>"; };
|
||||
A923172E2E08851200ECE1E6 /* ShellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ShellView.swift; path = ShhShell/Views/Terminal/ShellView.swift; sourceTree = SOURCE_ROOT; };
|
||||
A9231A562E52041500974753 /* HostPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostPreview.swift; sourceTree = "<group>"; };
|
||||
A925389A2DEE06DC007E0A18 /* ShhShell.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ShhShell.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A92538A72DEE06DE007E0A18 /* ShhShellTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ShhShellTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A92538B12DEE06DE007E0A18 /* ShhShellUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ShhShellUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -170,12 +185,14 @@
|
||||
A9485C702E1AECD000209824 /* JetBrainsMonoNerdFontMono-Italic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "JetBrainsMonoNerdFontMono-Italic.ttf"; sourceTree = "<group>"; };
|
||||
A9485C752E1AF59F00209824 /* FontManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontManagerView.swift; sourceTree = "<group>"; };
|
||||
A9485C772E1BFA5000209824 /* ThemeEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeEditorView.swift; sourceTree = "<group>"; };
|
||||
A94B832E2E5B929C00EBA09C /* Backgrounder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backgrounder.swift; sourceTree = "<group>"; };
|
||||
A94B83302E5C79EE00EBA09C /* CRT.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = CRT.metal; sourceTree = "<group>"; };
|
||||
A95FAA462DF3884B00DE2F5A /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = SOURCE_ROOT; };
|
||||
A95FAA502DF4B62100DE2F5A /* LibSSH.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = LibSSH.xcframework; path = Frameworks/LibSSH.xcframework; sourceTree = "<group>"; };
|
||||
A95FAA512DF4B62100DE2F5A /* openssl.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = openssl.xcframework; path = Frameworks/openssl.xcframework; sourceTree = "<group>"; };
|
||||
A95FAA5A2DF4B79900DE2F5A /* ci_post_clone.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = "<group>"; };
|
||||
A95FAA5B2DF4B7A000DE2F5A /* ci_pre_xcodebuild.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_pre_xcodebuild.sh; sourceTree = "<group>"; };
|
||||
A95FAA5C2DF4B7A300DE2F5A /* ci_prost_xcodebuild.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_prost_xcodebuild.sh; sourceTree = "<group>"; };
|
||||
A96A6A902E829C680084E249 /* OpenSSL-1.1.1m.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = "OpenSSL-1.1.1m.xcframework"; path = "Frameworks/OpenSSL-1.1.1m.xcframework"; sourceTree = "<group>"; };
|
||||
A96BE68B2E10846B00C0FEE9 /* 0x96f.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = 0x96f.plist; sourceTree = "<group>"; };
|
||||
A96BE68C2E10846B00C0FEE9 /* catppuccinFrappe.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = catppuccinFrappe.plist; sourceTree = "<group>"; };
|
||||
A96BE68D2E10846B00C0FEE9 /* catppuccinMocha.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = catppuccinMocha.plist; sourceTree = "<group>"; };
|
||||
@@ -199,6 +216,7 @@
|
||||
A96C6B012E0C49E800F377FE /* CenteredLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CenteredLabel.swift; sourceTree = "<group>"; };
|
||||
A96C90A02E12B87900724253 /* TextBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBox.swift; sourceTree = "<group>"; };
|
||||
A96C90A22E12D53900724253 /* KeyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyType.swift; sourceTree = "<group>"; };
|
||||
A97AF17F2E5D07BE00829443 /* CRTView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRTView.swift; sourceTree = "<group>"; };
|
||||
A9835C3B2E17CCA500969508 /* TrafficLights.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficLights.swift; sourceTree = "<group>"; };
|
||||
A98554542E05535F009051BD /* KeyManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManagerView.swift; sourceTree = "<group>"; };
|
||||
A98554582E0553AA009051BD /* KeyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManager.swift; sourceTree = "<group>"; };
|
||||
@@ -206,7 +224,11 @@
|
||||
A98554602E058433009051BD /* HostsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsManager.swift; sourceTree = "<group>"; };
|
||||
A98554622E0587DF009051BD /* HostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsView.swift; sourceTree = "<group>"; };
|
||||
A98CAB432E4229F7005E4C42 /* HostSymbolPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostSymbolPicker.swift; sourceTree = "<group>"; };
|
||||
A9921DE02E5F5710009F72A8 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
|
||||
A994D6492E5C94E200672395 /* ShaderTestingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShaderTestingView.swift; sourceTree = "<group>"; };
|
||||
A99E761F2E7AD4BD00720621 /* ShhShell.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = ShhShell.icon; sourceTree = "<group>"; };
|
||||
A9A2F4F52E3001D300D0AE9B /* AddSnippetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSnippetView.swift; sourceTree = "<group>"; };
|
||||
A9B1E5842E5F8E86009309E5 /* WelcomeChunk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeChunk.swift; sourceTree = "<group>"; };
|
||||
A9BA1D182E1D9AE1005BDCEF /* SwiftTerm.Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTerm.Color.swift; sourceTree = "<group>"; };
|
||||
A9BA1D1C2E1EAD51005BDCEF /* SF-Mono-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-Bold.otf"; sourceTree = "<group>"; };
|
||||
A9BA1D1D2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-BoldItalic.otf"; sourceTree = "<group>"; };
|
||||
@@ -214,6 +236,8 @@
|
||||
A9C060EC2E3FBCD000CA9374 /* SnippetPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnippetPicker.swift; sourceTree = "<group>"; };
|
||||
A9C4140B2E096DB7005E3047 /* SSHError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHError.swift; sourceTree = "<group>"; };
|
||||
A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = "<group>"; };
|
||||
A9CC786A2E4E681400FAEE58 /* RecentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentsView.swift; sourceTree = "<group>"; };
|
||||
A9CC786C2E4F534600FAEE58 /* History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = History.swift; sourceTree = "<group>"; };
|
||||
A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
||||
A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManagerView.swift; sourceTree = "<group>"; };
|
||||
A9D8192E2E0F1BEE00442D38 /* ThemeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeButton.swift; sourceTree = "<group>"; };
|
||||
@@ -236,11 +260,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A91F9E762E674928009FCB3A /* SwiftTerm in Frameworks */,
|
||||
A99D9F7E2E6351D100259166 /* SwiftTerm in Frameworks */,
|
||||
A99D9F7B2E63513E00259166 /* SwiftTerm in Frameworks */,
|
||||
A95FAA542DF4B62900DE2F5A /* LibSSH.xcframework in Frameworks */,
|
||||
A9A587202E0BF220006B31E6 /* SwiftTerm in Frameworks */,
|
||||
A93143BE2DF4D0B300FCD5DB /* libpthread.tbd in Frameworks */,
|
||||
A96A6A912E829C680084E249 /* OpenSSL-1.1.1m.xcframework in Frameworks */,
|
||||
A9083E402DF2226F0042906E /* libz.tbd in Frameworks */,
|
||||
A95FAA562DF4B62A00DE2F5A /* openssl.xcframework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -329,14 +355,20 @@
|
||||
path = About;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A91D27C82E54BEC700620B29 /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A91D27C92E54BED300620B29 /* AppSettings.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A923172B2E0712F200ECE1E6 /* Terminal */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A92317292E07113100ECE1E6 /* TerminalController.swift */,
|
||||
A923172E2E08851200ECE1E6 /* ShellView.swift */,
|
||||
A923172C2E07138000ECE1E6 /* SSHTerminalDelegate.swift */,
|
||||
A96BE6A92E116EC000C0FEE9 /* TerminalViewContainer.swift */,
|
||||
A9FD37682E16A6BF005319A8 /* ShellTabView.swift */,
|
||||
A97AF17F2E5D07BE00829443 /* CRTView.swift */,
|
||||
);
|
||||
path = Terminal;
|
||||
sourceTree = "<group>";
|
||||
@@ -370,11 +402,13 @@
|
||||
children = (
|
||||
A92538C62DEE0742007E0A18 /* ShhShellApp.swift */,
|
||||
A93143C12DF61E8500FCD5DB /* SSH */,
|
||||
A99604442E5A255E007CA460 /* Terminal */,
|
||||
A98554562E055394009051BD /* Host */,
|
||||
A98554572E055398009051BD /* Keys */,
|
||||
A9D8192A2E0E904900442D38 /* Themes */,
|
||||
A90936862E1AC4C600856059 /* Fonts */,
|
||||
A93F283F2E2A5EC80092B8D5 /* Snippets */,
|
||||
A91D27C82E54BEC700620B29 /* Settings */,
|
||||
A9C060E92E357FC400CA9374 /* Misc */,
|
||||
A92538D32DEE0749007E0A18 /* Views */,
|
||||
A90936852E1AC33C00856059 /* Info.plist */,
|
||||
@@ -404,6 +438,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A92538C52DEE0742007E0A18 /* ContentView.swift */,
|
||||
A9921DDF2E5F56F9009F72A8 /* Onboarding */,
|
||||
A923172B2E0712F200ECE1E6 /* Terminal */,
|
||||
A96C6B042E0C523E00F377FE /* Hosts */,
|
||||
A98554532E05534F009051BD /* Keys */,
|
||||
@@ -411,6 +446,7 @@
|
||||
A9D8192B2E0E9EA400442D38 /* Themes */,
|
||||
A9485C742E1AF58C00209824 /* Fonts */,
|
||||
A93F283E2E2A5DDE0092B8D5 /* Snippets */,
|
||||
A9FFD4ED2E53B032004CC4B8 /* Settings */,
|
||||
A90B38302E3E8FBA002B56FC /* About */,
|
||||
A96C6B032E0C523600F377FE /* Misc */,
|
||||
);
|
||||
@@ -423,6 +459,7 @@
|
||||
A90936822E1AC31100856059 /* fonts */,
|
||||
A92DDDE02E104CA400A87DB2 /* themes */,
|
||||
A92538C42DEE0742007E0A18 /* Assets.xcassets */,
|
||||
A99E761F2E7AD4BD00720621 /* ShhShell.icon */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
@@ -509,7 +546,6 @@
|
||||
A96C6B012E0C49E800F377FE /* CenteredLabel.swift */,
|
||||
A93143C52DF61FE300FCD5DB /* ViewModifiers.swift */,
|
||||
A96C90A02E12B87900724253 /* TextBox.swift */,
|
||||
A9DA97722E0D40C100142DDC /* HostSymbolPreview.swift */,
|
||||
A9835C3B2E17CCA500969508 /* TrafficLights.swift */,
|
||||
);
|
||||
path = Misc;
|
||||
@@ -521,6 +557,9 @@
|
||||
A98554622E0587DF009051BD /* HostsView.swift */,
|
||||
A985545C2E055D4D009051BD /* ConnectionView.swift */,
|
||||
A98CAB432E4229F7005E4C42 /* HostSymbolPicker.swift */,
|
||||
A9CC786A2E4E681400FAEE58 /* RecentsView.swift */,
|
||||
A9231A562E52041500974753 /* HostPreview.swift */,
|
||||
A9DA97722E0D40C100142DDC /* HostSymbolPreview.swift */,
|
||||
);
|
||||
path = Hosts;
|
||||
sourceTree = "<group>";
|
||||
@@ -542,6 +581,7 @@
|
||||
A93143BF2DF61B3200FCD5DB /* Host.swift */,
|
||||
A98554602E058433009051BD /* HostsManager.swift */,
|
||||
A9DA97702E0D30ED00142DDC /* HostSymbol.swift */,
|
||||
A9CC786C2E4F534600FAEE58 /* History.swift */,
|
||||
);
|
||||
path = Host;
|
||||
sourceTree = "<group>";
|
||||
@@ -557,11 +597,33 @@
|
||||
path = Keys;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A9921DDF2E5F56F9009F72A8 /* Onboarding */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A9921DE02E5F5710009F72A8 /* WelcomeView.swift */,
|
||||
A9B1E5842E5F8E86009309E5 /* WelcomeChunk.swift */,
|
||||
);
|
||||
path = Onboarding;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A99604442E5A255E007CA460 /* Terminal */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A923172C2E07138000ECE1E6 /* SSHTerminalDelegate.swift */,
|
||||
A96BE6A92E116EC000C0FEE9 /* TerminalViewContainer.swift */,
|
||||
A92317292E07113100ECE1E6 /* TerminalController.swift */,
|
||||
A94B83302E5C79EE00EBA09C /* CRT.metal */,
|
||||
A994D6492E5C94E200672395 /* ShaderTestingView.swift */,
|
||||
);
|
||||
path = Terminal;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A9C060E92E357FC400CA9374 /* Misc */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A9C060EA2E357FD300CA9374 /* Haptics.swift */,
|
||||
A90B38332E3EA046002B56FC /* Bundle.swift */,
|
||||
A94B832E2E5B929C00EBA09C /* Backgrounder.swift */,
|
||||
);
|
||||
path = Misc;
|
||||
sourceTree = "<group>";
|
||||
@@ -572,7 +634,7 @@
|
||||
A93143BD2DF4D0A700FCD5DB /* libpthread.tbd */,
|
||||
A9083E3F2DF2225A0042906E /* libz.tbd */,
|
||||
A95FAA502DF4B62100DE2F5A /* LibSSH.xcframework */,
|
||||
A95FAA512DF4B62100DE2F5A /* openssl.xcframework */,
|
||||
A96A6A902E829C680084E249 /* OpenSSL-1.1.1m.xcframework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -611,6 +673,14 @@
|
||||
path = KeychainLayer;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A9FFD4ED2E53B032004CC4B8 /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A91D27C62E54B97E00620B29 /* SettingsView.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -629,7 +699,9 @@
|
||||
);
|
||||
name = ShhShell;
|
||||
packageProductDependencies = (
|
||||
A9A5871F2E0BF220006B31E6 /* SwiftTerm */,
|
||||
A99D9F7A2E63513E00259166 /* SwiftTerm */,
|
||||
A99D9F7D2E6351D100259166 /* SwiftTerm */,
|
||||
A91F9E752E674928009FCB3A /* SwiftTerm */,
|
||||
);
|
||||
productName = ShhShell;
|
||||
productReference = A925389A2DEE06DC007E0A18 /* ShhShell.app */;
|
||||
@@ -709,7 +781,7 @@
|
||||
mainGroup = A92538912DEE06DC007E0A18;
|
||||
minimizedProjectReferenceProxies = 1;
|
||||
packageReferences = (
|
||||
A9A5871E2E0BF220006B31E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */,
|
||||
A91F9E742E674928009FCB3A /* XCLocalSwiftPackageReference "SwiftTerm" */,
|
||||
);
|
||||
productRefGroup = A925389B2DEE06DC007E0A18 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -742,6 +814,7 @@
|
||||
A96BE6A12E10846B00C0FEE9 /* catppuccinFrappe.plist in Resources */,
|
||||
A90936A72E1AE9AB00856059 /* CascadiaMono.ttf in Resources */,
|
||||
A90936A92E1AE9AB00856059 /* SF-Mono-Regular.otf in Resources */,
|
||||
A99E76202E7AD4BD00720621 /* ShhShell.icon in Resources */,
|
||||
A90936AA2E1AE9AB00856059 /* SF-Mono-RegularItalic.otf in Resources */,
|
||||
A90936AB2E1AE9AB00856059 /* MesloLGS NF Regular.ttf in Resources */,
|
||||
A90936AC2E1AE9AB00856059 /* MesloLGS NF Bold Italic.ttf in Resources */,
|
||||
@@ -789,14 +862,21 @@
|
||||
A9485C782E1BFA5000209824 /* ThemeEditorView.swift in Sources */,
|
||||
A93143C62DF61FE300FCD5DB /* ViewModifiers.swift in Sources */,
|
||||
A98554632E0587DF009051BD /* HostsView.swift in Sources */,
|
||||
A91D27CA2E54BED300620B29 /* AppSettings.swift in Sources */,
|
||||
A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */,
|
||||
A9FD37692E16A6BF005319A8 /* ShellTabView.swift in Sources */,
|
||||
A9921DE12E5F5710009F72A8 /* WelcomeView.swift in Sources */,
|
||||
A9DA97732E0D40C100142DDC /* HostSymbolPreview.swift in Sources */,
|
||||
A9CC786B2E4E681400FAEE58 /* RecentsView.swift in Sources */,
|
||||
A9CC786D2E4F534600FAEE58 /* History.swift in Sources */,
|
||||
A90B38342E3EA046002B56FC /* Bundle.swift in Sources */,
|
||||
A994D64A2E5C94E200672395 /* ShaderTestingView.swift in Sources */,
|
||||
A9FD376B2E16DABF005319A8 /* AnsiPickerView.swift in Sources */,
|
||||
A9C060ED2E3FBCD000CA9374 /* SnippetPicker.swift in Sources */,
|
||||
A91D27C72E54B97E00620B29 /* SettingsView.swift in Sources */,
|
||||
A96BE6A62E113DB000C0FEE9 /* ColorCodable.swift in Sources */,
|
||||
A92538C82DEE0742007E0A18 /* ContentView.swift in Sources */,
|
||||
A9231A572E52041500974753 /* HostPreview.swift in Sources */,
|
||||
A96BE6A42E113D9400C0FEE9 /* ThemeCodable.swift in Sources */,
|
||||
A9FD375F2E14648E005319A8 /* KeyImporterView.swift in Sources */,
|
||||
A93143C02DF61B3200FCD5DB /* Host.swift in Sources */,
|
||||
@@ -809,6 +889,7 @@
|
||||
A96C90A12E12B87A00724253 /* TextBox.swift in Sources */,
|
||||
A98CAB442E4229F7005E4C42 /* HostSymbolPicker.swift in Sources */,
|
||||
A9FD37652E169937005319A8 /* AuthType.swift in Sources */,
|
||||
A94B83312E5C79EE00EBA09C /* CRT.metal in Sources */,
|
||||
A96BE6A82E116E2B00C0FEE9 /* SessionsListView.swift in Sources */,
|
||||
A96C90A32E12D53B00724253 /* KeyType.swift in Sources */,
|
||||
A98554612E058433009051BD /* HostsManager.swift in Sources */,
|
||||
@@ -818,13 +899,16 @@
|
||||
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
|
||||
A93F283D2E2A5DCB0092B8D5 /* SnippetManagerView.swift in Sources */,
|
||||
A9C060EB2E357FD300CA9374 /* Haptics.swift in Sources */,
|
||||
A97AF1802E5D07BE00829443 /* CRTView.swift in Sources */,
|
||||
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */,
|
||||
A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */,
|
||||
A9B1E5852E5F8E86009309E5 /* WelcomeChunk.swift in Sources */,
|
||||
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */,
|
||||
A9A2F4F62E3001D300D0AE9B /* AddSnippetView.swift in Sources */,
|
||||
A923172D2E07138000ECE1E6 /* SSHTerminalDelegate.swift in Sources */,
|
||||
A90936882E1AC51100856059 /* Fonts.swift in Sources */,
|
||||
A9FD37552E143D23005319A8 /* SecKeyConvertible.swift in Sources */,
|
||||
A94B832F2E5B929C00EBA09C /* Backgrounder.swift in Sources */,
|
||||
A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */,
|
||||
A90B38322E3E8FC9002B56FC /* AboutView.swift in Sources */,
|
||||
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */,
|
||||
@@ -994,8 +1078,10 @@
|
||||
A92538BC2DEE06DE007E0A18 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "beta betaBlueprint";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = ShhShell;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = ShhShell/ShhShell.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(BUILD)";
|
||||
@@ -1006,6 +1092,8 @@
|
||||
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||
INFOPLIST_KEY_NSFaceIDUsageDescription = "ShhShell uses Face ID to verify your identity";
|
||||
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Needed to communicate with SSH Servers";
|
||||
INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "Required to keep SSH connections alive, if enabled";
|
||||
INFOPLIST_KEY_NSLocationUsageDescription = "Required to keep SSH connections alive, if enabled";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
@@ -1020,20 +1108,22 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.neon443.ShhShell;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
SWIFT_VERSION = 6.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
A92538BD2DEE06DE007E0A18 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "beta betaBlueprint";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = ShhShell;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = ShhShell/ShhShell.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(BUILD)";
|
||||
@@ -1044,6 +1134,8 @@
|
||||
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||
INFOPLIST_KEY_NSFaceIDUsageDescription = "ShhShell uses Face ID to verify your identity";
|
||||
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Needed to communicate with SSH Servers";
|
||||
INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "Required to keep SSH connections alive, if enabled";
|
||||
INFOPLIST_KEY_NSLocationUsageDescription = "Required to keep SSH connections alive, if enabled";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
@@ -1058,12 +1150,12 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.neon443.ShhShell;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
SWIFT_VERSION = 6.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1180,21 +1272,24 @@
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
A9A5871E2E0BF220006B31E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/migueldeicaza/SwiftTerm";
|
||||
requirement = {
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
A91F9E742E674928009FCB3A /* XCLocalSwiftPackageReference "SwiftTerm" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = SwiftTerm;
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
/* End XCLocalSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
A9A5871F2E0BF220006B31E6 /* SwiftTerm */ = {
|
||||
A91F9E752E674928009FCB3A /* SwiftTerm */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = SwiftTerm;
|
||||
};
|
||||
A99D9F7A2E63513E00259166 /* SwiftTerm */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = SwiftTerm;
|
||||
};
|
||||
A99D9F7D2E6351D100259166 /* SwiftTerm */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = A9A5871E2E0BF220006B31E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */;
|
||||
productName = SwiftTerm;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
@@ -1,13 +1,31 @@
|
||||
{
|
||||
"originHash" : "4d9d9af82f23f3c708bdd502fed3939413b4f2a95a79ae568364cc92bca1527e",
|
||||
"originHash" : "d415ccafb2cea88d848a27f2d46c96a1d9161addf9f04c1f3c88326f5d33b325",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "swiftterm",
|
||||
"identity" : "swift-argument-parser",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/migueldeicaza/SwiftTerm",
|
||||
"location" : "https://github.com/apple/swift-argument-parser",
|
||||
"state" : {
|
||||
"revision" : "cdd0ef3755280949551dc26dee5de9ddeda89f54",
|
||||
"version" : "1.6.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-subprocess",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/swiftlang/swift-subprocess",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "18b1ad0b8af10c4cfac5da599edbe1c67af95413"
|
||||
"revision" : "ab2072d457d1464b17166cc02125fc9fc0791e1a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-system",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-system",
|
||||
"state" : {
|
||||
"revision" : "395a77f0aa927f0ff73941d7ac35f2b46d47c9db",
|
||||
"version" : "1.6.3"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
16
ShhShell/Host/History.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// History.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 15/08/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct History: Identifiable, Codable {
|
||||
var id: UUID = UUID()
|
||||
|
||||
var host: Host
|
||||
var count: Int
|
||||
var lastConnect: Date = .now
|
||||
}
|
||||
@@ -32,6 +32,7 @@ struct Host: HostPr {
|
||||
var password: String
|
||||
var privateKeyID: UUID?
|
||||
var key: String?
|
||||
var startupSnippetID: UUID?
|
||||
|
||||
var description: String {
|
||||
if name.isEmpty && address.isEmpty {
|
||||
@@ -72,6 +73,7 @@ extension Host {
|
||||
static var blank: Host {
|
||||
Host(address: "")
|
||||
}
|
||||
|
||||
static var debug: Host {
|
||||
Host(
|
||||
name: "name for localhost",
|
||||
|
||||
@@ -15,8 +15,8 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
@Published var hosts: [Host] = []
|
||||
|
||||
@Published var themes: [Theme] = []
|
||||
@Published var selectedTheme: Theme = Theme.defaultTheme
|
||||
@Published var selectedAnsi: Int = 1
|
||||
@Published var selectedTheme: Theme = Theme.decodeLocalTheme(fileName: "xcodeDarkHC") ?? Theme.defaultTheme
|
||||
@Published var selectedAnsi: Int = 4
|
||||
|
||||
@Published var fonts: [UIFont] = []
|
||||
@Published var selectedFont: String = "SF Mono"
|
||||
@@ -24,15 +24,87 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
|
||||
@Published var snippets: [Snippet] = []
|
||||
|
||||
@Published var history: [History] = []
|
||||
@Published var settings: AppSettings = AppSettings()
|
||||
@Published var shownOnboarding: Bool = false
|
||||
|
||||
var tint: SwiftUI.Color {
|
||||
selectedTheme.ansi[selectedAnsi].suiColor
|
||||
}
|
||||
|
||||
init() {
|
||||
init(previews: Bool = false) {
|
||||
if previews {
|
||||
self.hosts = [Host.debug, Host.blank]
|
||||
self.themes = [Theme.defaultTheme]
|
||||
self.snippets = [Snippet(name: "kys", content: "ls\npwd\n")]
|
||||
self.history = [History(host: Host.debug, count: 3)]
|
||||
return
|
||||
}
|
||||
loadSettings()
|
||||
loadHosts()
|
||||
exportHosts()
|
||||
loadThemes()
|
||||
loadFonts()
|
||||
loadSnippets()
|
||||
loadHistory()
|
||||
withAnimation {
|
||||
self.shownOnboarding = UserDefaults.standard.bool(forKey: "shownOnboarding")
|
||||
}
|
||||
}
|
||||
|
||||
func setOnboarding(to newValue: Bool) {
|
||||
withAnimation { self.shownOnboarding = newValue }
|
||||
UserDefaults.standard.set(newValue, forKey: "shownOnboarding")
|
||||
}
|
||||
|
||||
func setAppIcon() {
|
||||
Task { @MainActor in
|
||||
guard UIApplication.shared.supportsAlternateIcons else { return }
|
||||
guard settings.appIcon.name != "ShhShell" else {
|
||||
UIApplication.shared.setAlternateIconName(nil)
|
||||
return
|
||||
}
|
||||
UIApplication.shared.setAlternateIconName("\(settings.appIcon.name)")
|
||||
}
|
||||
}
|
||||
|
||||
func loadSettings() {
|
||||
guard let data = userDefaults.data(forKey: "settings") else { return }
|
||||
guard let decoded = try? JSONDecoder().decode(AppSettings.self, from: data) else { return }
|
||||
self.settings = decoded
|
||||
}
|
||||
|
||||
func saveSettings() {
|
||||
guard let encoded = try? JSONEncoder().encode(settings) else { return }
|
||||
userDefaults.set(encoded, forKey: "settings")
|
||||
}
|
||||
|
||||
func loadHistory() {
|
||||
guard let data = userDefaults.data(forKey: "history") else { return }
|
||||
guard let decoded = try? JSONDecoder().decode([History].self, from: data) else { return }
|
||||
withAnimation { self.history = decoded }
|
||||
}
|
||||
|
||||
func addToHistory(_ host: Host) {
|
||||
if history.last?.host == host {
|
||||
guard var lastOne = history.popLast() else { return }
|
||||
lastOne.count += 1
|
||||
lastOne.lastConnect = .now
|
||||
history.append(lastOne)
|
||||
} else {
|
||||
history.append(History(host: host, count: 1))
|
||||
}
|
||||
saveHistory()
|
||||
}
|
||||
|
||||
func saveHistory() {
|
||||
let data = try? JSONEncoder().encode(history)
|
||||
userDefaults.set(data, forKey: "history")
|
||||
}
|
||||
|
||||
func removeFromHistory(_ toRemove: History) {
|
||||
history.removeAll(where: { $0.id == toRemove.id })
|
||||
saveHistory()
|
||||
}
|
||||
|
||||
func addSnippet(_ toAdd: Snippet) {
|
||||
@@ -223,16 +295,12 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
}
|
||||
|
||||
func updateHost(_ updatedHost: Host) {
|
||||
// let oldID = updatedHost.id
|
||||
var blankHost = Host.blank
|
||||
blankHost.id = updatedHost.id
|
||||
guard updatedHost != blankHost else { return }
|
||||
|
||||
if let index = hosts.firstIndex(where: { $0.id == updatedHost.id }) {
|
||||
withAnimation { hosts[index] = updatedHost }
|
||||
// var updateHostWithNewID = updatedHost
|
||||
// updateHostWithNewID.id = UUID()
|
||||
// withAnimation { hosts[index] = updateHostWithNewID }
|
||||
//
|
||||
// updateHostWithNewID.id = oldID
|
||||
// withAnimation { hosts[index] = updateHostWithNewID }
|
||||
saveHosts()
|
||||
} else {
|
||||
withAnimation { hosts.append(updatedHost) }
|
||||
@@ -243,6 +311,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
func duplicateHost(_ hostToDup: Host) {
|
||||
var hostNewID = hostToDup
|
||||
hostNewID.id = UUID()
|
||||
hostNewID.name = hostToDup.description.appending(" copy")
|
||||
if let index = hosts.firstIndex(where: { $0 == hostToDup }) {
|
||||
hosts.insert(hostNewID, at: index+1)
|
||||
Haptic.medium.trigger()
|
||||
@@ -272,6 +341,16 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
func exportHosts() {
|
||||
guard let encoded = try? JSONEncoder().encode(hosts) else { return }
|
||||
print(encoded.base64EncodedString())
|
||||
}
|
||||
|
||||
func importHosts(_ data: Data) {
|
||||
guard let decoedd = try? JSONDecoder().decode([Host].self, from: data) else { return }
|
||||
hosts = decoedd
|
||||
}
|
||||
|
||||
func removeHost(_ host: Host) {
|
||||
if let index = hosts.firstIndex(where: { $0.id == host.id }) {
|
||||
let _ = withAnimation { hosts.remove(at: index) }
|
||||
|
||||
@@ -22,5 +22,7 @@
|
||||
<string>JetBrainsMonoNerdFontMono-Italic.ttf</string>
|
||||
<string>JetBrainsMonoNerdFontMono-BoldItalic.ttf</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -52,7 +52,12 @@ struct Keypair: KeypairProtocol {
|
||||
}
|
||||
|
||||
var base64Pubkey: String {
|
||||
String(openSshPubkey.split(separator: " ")[1])
|
||||
let split = openSshPubkey.split(separator: " ")
|
||||
if split.count >= 2 {
|
||||
return String(split[1])
|
||||
} else {
|
||||
return "Error creating OpenSSH Publickey"
|
||||
}
|
||||
}
|
||||
|
||||
var base64Privkey: String {
|
||||
|
||||
62
ShhShell/Misc/Backgrounder.swift
Normal file
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Backgrounder.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 24/08/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreLocation
|
||||
|
||||
class Backgrounder: NSObject, CLLocationManagerDelegate, ObservableObject {
|
||||
private let manager = CLLocationManager()
|
||||
var tracking: Bool = false
|
||||
|
||||
@MainActor
|
||||
static var shared: Backgrounder = Backgrounder()
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
manager.delegate = self
|
||||
if checkPermsStatus() {
|
||||
manager.allowsBackgroundLocationUpdates = true
|
||||
}
|
||||
}
|
||||
|
||||
func startBgTracking() {
|
||||
guard !tracking else { return }
|
||||
guard checkPermsStatus() else { return }
|
||||
manager.allowsBackgroundLocationUpdates = true
|
||||
manager.pausesLocationUpdatesAutomatically = false
|
||||
manager.startMonitoringSignificantLocationChanges()
|
||||
tracking = true
|
||||
}
|
||||
|
||||
func stopBgTracking() {
|
||||
guard tracking else { return }
|
||||
manager.stopUpdatingLocation()
|
||||
manager.allowsBackgroundLocationUpdates = false
|
||||
tracking = false
|
||||
}
|
||||
|
||||
func requestPerms() {
|
||||
manager.requestAlwaysAuthorization()
|
||||
}
|
||||
|
||||
func checkPermsStatus() -> Bool {
|
||||
let status = manager.authorizationStatus
|
||||
|
||||
switch status {
|
||||
case .authorized, .notDetermined, .restricted, .denied, .authorizedWhenInUse:
|
||||
return false
|
||||
case .authorizedAlways:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
print("tracking started fr")
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import UIKit
|
||||
|
||||
enum Haptic {
|
||||
case success
|
||||
case warning
|
||||
case error
|
||||
case light
|
||||
case medium
|
||||
@@ -22,7 +23,7 @@ enum Haptic {
|
||||
switch self {
|
||||
case .light, .medium, .heavy, .soft, .rigid:
|
||||
return true
|
||||
case .success, .error:
|
||||
case .success, .warning, .error:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -57,6 +58,8 @@ enum Haptic {
|
||||
switch self {
|
||||
case .success:
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
case .warning:
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.warning)
|
||||
case .error:
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
default: print("idk atp")
|
||||
|
||||
@@ -25,3 +25,7 @@ enum KeyError: Error {
|
||||
case pubkeyRejected
|
||||
case privkeyRejected
|
||||
}
|
||||
|
||||
enum ReconnectError: Error {
|
||||
case alreadyConnected
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
var sessionID: UUID?
|
||||
|
||||
var scrollback: [String] = []
|
||||
// var scrollbackSize = 0.0
|
||||
|
||||
@Published var title: String = ""
|
||||
@Published var state: SSHState = .idle
|
||||
@@ -32,6 +31,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
}
|
||||
|
||||
@Published var testSuceeded: Bool? = nil
|
||||
@Published var forceDismissDisconnectedAlert: Bool = false
|
||||
|
||||
@Published var bell: Bool = false
|
||||
|
||||
@@ -59,10 +59,10 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
return String(cString: cString)
|
||||
}
|
||||
|
||||
func go() {
|
||||
func go(id: UUID? = nil) {
|
||||
guard !connected else { disconnect(); return }
|
||||
|
||||
do { try connect() } catch {
|
||||
do { try connect(id: id) } catch {
|
||||
print("error when connecting \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
@@ -75,8 +75,6 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
} catch { print("auth with none is not authed") }
|
||||
guard state != .authorized else { return }
|
||||
|
||||
|
||||
|
||||
for method in getAuthMethods() {
|
||||
guard state != .authorized else { break }
|
||||
switch method {
|
||||
@@ -110,10 +108,15 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
setTitle("\(host.username)@\(host.address)")
|
||||
}
|
||||
|
||||
func connect() throws(SSHError) {
|
||||
func connect(id: UUID?) throws(SSHError) {
|
||||
guard !host.address.isEmpty else { throw .connectionFailed("No address to connect to.") }
|
||||
withAnimation { state = .connecting }
|
||||
sessionID = UUID()
|
||||
if let id {
|
||||
sessionID = id
|
||||
} else {
|
||||
cleanup()
|
||||
sessionID = UUID()
|
||||
}
|
||||
|
||||
var verbosity: Int = 0
|
||||
// var verbosity: Int = SSH_LOG_FUNCTIONS
|
||||
@@ -141,22 +144,13 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
func reconnect() throws(ReconnectError) {
|
||||
guard !connected else { throw .alreadyConnected }
|
||||
go(id: sessionID!)
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
Task { @MainActor in
|
||||
self.hostkeyChanged = false
|
||||
withAnimation { self.state = .idle }
|
||||
withAnimation { self.testSuceeded = nil }
|
||||
}
|
||||
|
||||
if let sessionID {
|
||||
Task { @MainActor in
|
||||
container.sessions.removeValue(forKey: sessionID)
|
||||
self.sessionID = nil
|
||||
}
|
||||
}
|
||||
scrollback = []
|
||||
// scrollbackSize = 0
|
||||
|
||||
withAnimation { self.state = .idle }
|
||||
//send eof if open
|
||||
if ssh_channel_is_open(channel) == 1 {
|
||||
ssh_channel_send_eof(channel)
|
||||
@@ -171,6 +165,20 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
self.session = nil
|
||||
}
|
||||
|
||||
func cleanup() {
|
||||
self.hostkeyChanged = false
|
||||
withAnimation { self.state = .idle }
|
||||
withAnimation { self.testSuceeded = nil }
|
||||
scrollback = []
|
||||
|
||||
if let sessionID {
|
||||
Task { @MainActor in
|
||||
container.sessions.removeValue(forKey: sessionID)
|
||||
self.sessionID = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkHostkey(recieved: String?) {
|
||||
guard host.key == recieved else {
|
||||
self.hostkeyChanged = true
|
||||
@@ -361,23 +369,15 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
return nil
|
||||
}
|
||||
|
||||
var buffer: [CChar] = Array(repeating: 0, count: 1024)
|
||||
var buffer: [CChar] = Array(repeating: 0, count: 4096)
|
||||
let nbytes = ssh_channel_read_nonblocking(channel, &buffer, UInt32(buffer.count), 0)
|
||||
|
||||
guard nbytes > 0 else { return nil }
|
||||
|
||||
let data = Data(bytes: buffer, count: Int(nbytes))
|
||||
if let string = String(data: data, encoding: .utf8) {
|
||||
#if DEBUG
|
||||
// print(String(data: Data(bytes: buffer, count: Int(nbytes)), encoding: .utf8)!)
|
||||
#endif
|
||||
Task { @MainActor in
|
||||
scrollback.append(string)
|
||||
// if scrollbackSize/1024/1024 > 10 {
|
||||
// scrollback.remove(at: 0)
|
||||
// } else {
|
||||
// scrollbackSize += Double(string.lengthOfBytes(using: .utf8))
|
||||
// }
|
||||
}
|
||||
return string
|
||||
}
|
||||
@@ -387,7 +387,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
func writeToChannel(_ string: String?) {
|
||||
guard let string else { return }
|
||||
guard ssh_channel_is_open(channel) == 1 && ssh_channel_is_eof(channel) == 0 else {
|
||||
Task { disconnect() }
|
||||
Task { @MainActor in disconnect() }
|
||||
return
|
||||
}
|
||||
|
||||
@@ -407,19 +407,8 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
guard ssh_channel_is_eof(channel) == 0 else { throw .backendError("Channel is EOF") }
|
||||
|
||||
ssh_channel_change_pty_size(channel, Int32(toCols), Int32(toRows))
|
||||
// print("resized tty to \(toRows)rows and \(toCols)cols")
|
||||
}
|
||||
|
||||
// func prettyScrollbackSize() -> String {
|
||||
// if (scrollbackSize/1024/1024) > 1 {
|
||||
// return "\(scrollbackSize/1024/1024) MiB scrollback"
|
||||
// } else if scrollbackSize/1024 > 1 {
|
||||
// return "\(scrollbackSize/1024) KiB scrollback"
|
||||
// } else {
|
||||
// return "\(scrollbackSize) B scrollback"
|
||||
// }
|
||||
// }
|
||||
|
||||
private func logSshGetError() {
|
||||
guard var session = self.session else { return }
|
||||
logger.critical("\(String(cString: ssh_get_error(&session)))")
|
||||
|
||||
@@ -12,6 +12,7 @@ enum SSHState {
|
||||
case idle
|
||||
case connecting
|
||||
case authorizing
|
||||
case authorizingKbint
|
||||
case authorized
|
||||
case shellOpen
|
||||
|
||||
@@ -23,7 +24,7 @@ enum SSHState {
|
||||
case .idle:
|
||||
return .gray
|
||||
|
||||
case .connecting, .authorizing:
|
||||
case .connecting, .authorizing, .authorizingKbint:
|
||||
return .orange
|
||||
|
||||
case .authorized, .shellOpen:
|
||||
|
||||
123
ShhShell/Settings/AppSettings.swift
Normal file
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// AppSettings.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 19/08/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
@preconcurrency import SwiftTerm
|
||||
|
||||
struct AppSettings: Codable, Sendable, Equatable {
|
||||
var scrollback: CGFloat = 10_000
|
||||
var cursorType: CursorType = CursorType()
|
||||
var cursorAnimations: CursorAnimations = CursorAnimations()
|
||||
var locationPersist: Bool = false
|
||||
var bellSound: Bool = false
|
||||
var bellHaptic: Bool = true
|
||||
var caffeinate: Bool = false
|
||||
var filter: TerminalFilter = .none
|
||||
var appIcon: AppIcon = .regular
|
||||
}
|
||||
|
||||
enum CursorShape: Codable, CaseIterable, Equatable, CustomStringConvertible {
|
||||
case block
|
||||
case bar
|
||||
case underline
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .block:
|
||||
return "Block"
|
||||
case .bar:
|
||||
return "Bar"
|
||||
case .underline:
|
||||
return "Underline"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CursorType: Codable, Equatable, CustomStringConvertible {
|
||||
var cursorShape: CursorShape = .block
|
||||
var blink: Bool = true
|
||||
|
||||
var stCursorStyle: SwiftTerm.CursorStyle {
|
||||
switch cursorShape {
|
||||
case .block:
|
||||
return blink ? .blinkBlock : .steadyBlock
|
||||
case .bar:
|
||||
return blink ? .blinkBar : .steadyBar
|
||||
case .underline:
|
||||
return blink ? .blinkUnderline : .steadyUnderline
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return (blink ? "Blinking" : "Steady") + " " + cursorShape.description
|
||||
}
|
||||
}
|
||||
|
||||
enum TerminalFilter: Codable, CaseIterable, Equatable, CustomStringConvertible {
|
||||
case none
|
||||
case crt
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .none:
|
||||
return "None"
|
||||
case .crt:
|
||||
return "CRT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AppIcon: Codable, CaseIterable, Equatable, CustomStringConvertible {
|
||||
case regular
|
||||
case beta
|
||||
case betaBlueprint
|
||||
|
||||
var image: Image {
|
||||
return Image(self.name)
|
||||
}
|
||||
|
||||
var name: String {
|
||||
switch self {
|
||||
case .regular:
|
||||
return "ShhShell"
|
||||
case .beta:
|
||||
return "beta"
|
||||
case .betaBlueprint:
|
||||
return "betaBlueprint"
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .regular:
|
||||
return "Default"
|
||||
case .beta:
|
||||
return "Beta"
|
||||
case .betaBlueprint:
|
||||
return "Blueprint"
|
||||
}
|
||||
}
|
||||
|
||||
var isiOS26: Bool {
|
||||
switch self {
|
||||
case .regular:
|
||||
return true
|
||||
case .beta, .betaBlueprint:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var radius: CGFloat {
|
||||
switch isiOS26 {
|
||||
case true, false:
|
||||
return 17.578125
|
||||
// case false:
|
||||
// return 16.5
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
@main
|
||||
struct ShhShellApp: App {
|
||||
@StateObject var sshHandler: SSHHandler
|
||||
|
||||
|
||||
@StateObject var hostsManager: HostsManager = HostsManager()
|
||||
@StateObject var keyManager: KeyManager
|
||||
|
||||
@@ -22,13 +22,26 @@ struct ShhShellApp: App {
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView(
|
||||
handler: sshHandler,
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager
|
||||
)
|
||||
.colorScheme(hostsManager.selectedTheme.background.luminance > 0.5 ? .light : .dark)
|
||||
.tint(hostsManager.tint)
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
ContentView(
|
||||
handler: sshHandler,
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager
|
||||
)
|
||||
.colorScheme(hostsManager.selectedTheme.background.luminance > 0.5 ? .light : .dark)
|
||||
.tint(hostsManager.tint)
|
||||
if !hostsManager.shownOnboarding {
|
||||
WelcomeView(hostsManager: hostsManager)
|
||||
.animation(.default, value: hostsManager.shownOnboarding)
|
||||
.transition(.opacity)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(.black)
|
||||
}
|
||||
}
|
||||
.transition(.opacity)
|
||||
.animation(.default, value: hostsManager.shownOnboarding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
99
ShhShell/Terminal/CRT.metal
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// CRT.metal
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 25/08/2025.
|
||||
//
|
||||
|
||||
#include <metal_stdlib>
|
||||
#include <SwiftUI/SwiftUI_Metal.h>
|
||||
using namespace metal;
|
||||
|
||||
[[ stitchable ]] half4 crt(float2 pos, half4 color, float2 size, float time) {
|
||||
float2 uv = pos/size;
|
||||
|
||||
//scanlines
|
||||
half scanline = 0.5 + 0.5 * sin(uv.y * size.y*2);
|
||||
|
||||
half alpha = 1 - scanline;
|
||||
alpha *= 2;
|
||||
|
||||
return half4(color.xyz*scanline*alpha, alpha);
|
||||
}
|
||||
|
||||
//learning shaders stuff here
|
||||
[[ stitchable ]] half4 sinebow(float2 pos, half4 color, float2 size, float time) {
|
||||
float2 uv = (pos/size.x) * 2.0 - 1.0;
|
||||
uv.y += 0.15;
|
||||
float wave = sin(uv.x + time);
|
||||
wave *= wave * 25.0;
|
||||
|
||||
|
||||
half3 waveColor = half3(0);
|
||||
for (float i = 0.0; i < 10.0; i++) {
|
||||
float luma = abs(1.0 / (100.0 * uv.y + wave));
|
||||
float y = sin(uv.x * sin(i) + i * 0.2 + i);
|
||||
uv.y += y;
|
||||
half3 rainbow = half3(
|
||||
(sin(i * 0.6 + i) * 0.5 + 0.5),
|
||||
(sin(i * 0.6 + 2.0 + sin(i * 0.3)) * 0.5 + 0.5),
|
||||
(sin(i * 0.6 + 4.0 + i) * 0.5 + 0.5)
|
||||
);
|
||||
waveColor += rainbow * luma;
|
||||
}
|
||||
return half4(waveColor, 1);
|
||||
}
|
||||
|
||||
[[ stitchable ]] half4 loupe(float2 pos, SwiftUI::Layer layer, float2 size, float2 touch) {
|
||||
float maxDist = 0.1;
|
||||
float2 uv = pos/size;
|
||||
float2 center = touch/size;
|
||||
float2 delta = uv-center;
|
||||
float aspectRatio = size.x/size.y;
|
||||
float dist = (delta.x * delta.x) + (delta.y * delta.y) / aspectRatio;
|
||||
float totalZoom = 1;
|
||||
if(dist < maxDist) {
|
||||
totalZoom /= 2;
|
||||
}
|
||||
float2 newPos = delta * totalZoom + center;
|
||||
return layer.sample(newPos*size);
|
||||
}
|
||||
|
||||
[[ stitchable ]] float2 waveFlag(float2 pos, float time, float2 size) {
|
||||
float2 distance = pos/size;
|
||||
pos.y += sin(time*5 + pos.x/20) * 5 * distance.x;
|
||||
return pos;
|
||||
}
|
||||
|
||||
[[ stitchable ]] float2 wave(float2 pos, float time) {
|
||||
pos.y += sin(time*4 + pos.y/30) * 10;
|
||||
return pos;
|
||||
}
|
||||
|
||||
[[ stitchable ]] half4 rainbow(float2 pos, half4 color, float time) {
|
||||
if(color.a == 0) {
|
||||
return half4(0,0,0,0);
|
||||
}
|
||||
float angle = atan2(pos.y, pos.x) + time;
|
||||
return half4(
|
||||
sin(angle),
|
||||
sin(angle + 2),
|
||||
sin(angle + 4),
|
||||
color.a
|
||||
);
|
||||
}
|
||||
|
||||
[[ stitchable ]] half4 opacityInvert(float2 position, half4 color) {
|
||||
return half4(1, 0, 0, 1-color.a);
|
||||
}
|
||||
|
||||
[[ stitchable ]] half4 redify(float2 position, half4 color) {
|
||||
if (color.a == 0) {
|
||||
return half4(0,0,0,0);
|
||||
}
|
||||
return half4(1 * color.a, 0, 0, color.a);
|
||||
}
|
||||
|
||||
[[ stitchable ]] half4 passthrough(float2 position, half4 color) {
|
||||
return color;
|
||||
}
|
||||
@@ -14,6 +14,8 @@ final class SSHTerminalDelegate: TerminalView, Sendable, @preconcurrency Termina
|
||||
var handler: SSHHandler?
|
||||
var hostsManager: HostsManager?
|
||||
|
||||
var readTimer: Timer?
|
||||
|
||||
public convenience init(frame: CGRect, handler: SSHHandler, hostsManager: HostsManager) {
|
||||
self.init(frame: frame)
|
||||
|
||||
@@ -27,9 +29,14 @@ final class SSHTerminalDelegate: TerminalView, Sendable, @preconcurrency Termina
|
||||
restoreScrollback()
|
||||
if let hostsManager {
|
||||
font = UIFont(name: hostsManager.selectedFont, size: hostsManager.fontSize)!
|
||||
getTerminal().setCursorAnimations(hostsManager.settings.cursorAnimations)
|
||||
}
|
||||
applySelectedTheme()
|
||||
startFeedLoop()
|
||||
applyScrollbackLength()
|
||||
applyCursorType()
|
||||
getTerminal().registerOscHandler(code: 133, handler: { _ in })
|
||||
self.startFeedLoop()
|
||||
let _ = self.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,20 +54,36 @@ final class SSHTerminalDelegate: TerminalView, Sendable, @preconcurrency Termina
|
||||
}
|
||||
}
|
||||
|
||||
override func showCursor(source: Terminal) {
|
||||
super.showCursor(source: source)
|
||||
super.cursorStyleChanged(source: getTerminal(), newStyle: getTerminal().options.cursorStyle)
|
||||
print("showcursor called")
|
||||
}
|
||||
|
||||
override func hideCursor(source: Terminal) {
|
||||
super.hideCursor(source: source)
|
||||
print("hide cursor called")
|
||||
}
|
||||
|
||||
func startFeedLoop() {
|
||||
Task {
|
||||
guard let handler else { return }
|
||||
while checkShell(handler.state) {
|
||||
guard readTimer == nil else { return }
|
||||
readTimer = Timer(timeInterval: 0.01, repeats: true) { timer in
|
||||
Task(priority: .high) { @MainActor in
|
||||
guard let handler = self.handler else { return }
|
||||
guard let sessionID = handler.sessionID else { return }
|
||||
if let read = handler.readFromChannel() {
|
||||
await MainActor.run {
|
||||
Task { @MainActor in
|
||||
self.feed(text: read)
|
||||
}
|
||||
} else {
|
||||
try? await Task.sleep(nanoseconds: 10_000_000) //10ms
|
||||
}
|
||||
if !TerminalViewContainer.shared.sessionIDs.contains(sessionID) {
|
||||
Task(priority: .high) { @MainActor in
|
||||
TerminalViewContainer.shared.sessions[sessionID] = TerminalContainer(handler: handler, terminalView: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
print("task end?")
|
||||
}
|
||||
RunLoop.main.add(readTimer!, forMode: .common)
|
||||
}
|
||||
|
||||
func applySelectedTheme() {
|
||||
@@ -68,20 +91,34 @@ final class SSHTerminalDelegate: TerminalView, Sendable, @preconcurrency Termina
|
||||
applyTheme(hostsManager.selectedTheme)
|
||||
}
|
||||
|
||||
func applyCursorType() {
|
||||
guard let hostsManager else { return }
|
||||
getTerminal().setCursorStyle(hostsManager.settings.cursorType.stCursorStyle)
|
||||
}
|
||||
|
||||
func applyTheme(_ theme: Theme) {
|
||||
getTerminal().installPalette(colors: theme.ansi)
|
||||
getTerminal().foregroundColor = theme.foreground
|
||||
getTerminal().backgroundColor = theme.background
|
||||
|
||||
caretColor = theme.cursor.uiColor
|
||||
setCursorColor(source: getTerminal(), color: theme.cursor, textColor: theme.cursorText)
|
||||
selectedTextBackgroundColor = theme.selection.uiColor
|
||||
|
||||
// TODO: selectedtext and cursor colors
|
||||
// TODO: selectedtext color
|
||||
}
|
||||
|
||||
func applyScrollbackLength() {
|
||||
guard let scrollback = hostsManager?.settings.scrollback else {
|
||||
print("hey!")
|
||||
print("scrollback not found, setting to 1,000")
|
||||
getTerminal().options.scrollback = 1_000
|
||||
return
|
||||
}
|
||||
getTerminal().options.scrollback = Int(scrollback)
|
||||
print("set scrollback to \(Int(scrollback))")
|
||||
}
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
getTerminal().options.scrollback = 10_0000
|
||||
terminalDelegate = self
|
||||
}
|
||||
|
||||
89
ShhShell/Terminal/ShaderTestingView.swift
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// ShaderTestingView.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 25/08/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ShaderTestingView: View {
|
||||
@State private var start = Date.now
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 17, *) {
|
||||
TimelineView(.animation) { tl in
|
||||
let time = start.distance(to: tl.date)
|
||||
ZStack {
|
||||
VStack {
|
||||
HStack {
|
||||
Image(systemName: "figure.walk.circle")
|
||||
.resizable().scaledToFit()
|
||||
.foregroundStyle(.blue)
|
||||
Image(systemName: "arrow.right")
|
||||
.foregroundStyle(.gray)
|
||||
.frame(width: 25, height: 25)
|
||||
Image(systemName: "figure.walk.circle")
|
||||
.resizable().scaledToFit()
|
||||
.foregroundStyle(.blue)
|
||||
.colorEffect(ShaderLibrary.redify())
|
||||
}
|
||||
|
||||
Image(systemName: "figure.walk.circle")
|
||||
.resizable().scaledToFit()
|
||||
.foregroundStyle(.blue)
|
||||
.colorEffect(ShaderLibrary.rainbow(.float(time)))
|
||||
|
||||
Image(systemName: "figure.walk.circle")
|
||||
.resizable().scaledToFit()
|
||||
.foregroundStyle(.blue)
|
||||
.distortionEffect(
|
||||
ShaderLibrary.wave(.float(time)),
|
||||
maxSampleOffset: .zero
|
||||
)
|
||||
Rectangle()
|
||||
.padding(.vertical, 20)
|
||||
.foregroundStyle(.red)
|
||||
.compositingGroup()
|
||||
.visualEffect {
|
||||
content,
|
||||
proxy in
|
||||
content.distortionEffect(
|
||||
ShaderLibrary.waveFlag(.float(time), .float2(proxy.size)),
|
||||
maxSampleOffset: CGSize(width: 0, height: 40)
|
||||
)
|
||||
}
|
||||
|
||||
Rectangle()
|
||||
.colorEffect(
|
||||
ShaderLibrary.sinebow(.float2(300, 200), .float(time))
|
||||
)
|
||||
}
|
||||
.brightness(0.2)
|
||||
|
||||
Rectangle()
|
||||
.foregroundStyle(.black.opacity(1))
|
||||
.visualEffect { content, proxy in
|
||||
content
|
||||
.colorEffect(
|
||||
ShaderLibrary.crt(
|
||||
.float2(proxy.size),
|
||||
.float(time)
|
||||
)
|
||||
)
|
||||
}
|
||||
.opacity(0.75)
|
||||
.blendMode(.overlay)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Label("iOS 17 Required", systemImage: "exclamationmark.triangle.fill")
|
||||
.foregroundStyle(.yellow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ShaderTestingView()
|
||||
}
|
||||
@@ -39,7 +39,6 @@ struct TerminalController: UIViewRepresentable {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return tv
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ extension ColorCodable {
|
||||
let blue = CGFloat(self.blue)
|
||||
return Color(UIColor(red: red, green: green, blue: blue, alpha: 1))
|
||||
}
|
||||
set {
|
||||
mutating set {
|
||||
let cc = ColorCodable(color: newValue)
|
||||
self.red = cc.red
|
||||
self.green = cc.green
|
||||
|
||||
@@ -80,4 +80,12 @@ extension SwiftTerm.Color {
|
||||
let b = Double(blue)/65535
|
||||
return (0.2126*r + 0.7152*g + 0.0722*b)
|
||||
}
|
||||
|
||||
func luminanceRatio(with other: SwiftTerm.Color) -> Double {
|
||||
if self.luminance > other.luminance {
|
||||
return (self.luminance + 0.05) / (other.luminance + 0.05)
|
||||
} else {
|
||||
return (other.luminance + 0.05) / (self.luminance + 0.05)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,12 @@ struct Theme: Hashable, Equatable, Identifiable {
|
||||
static var builtinThemes: [Theme] {
|
||||
return ThemesBuiltin.allCases.map({ decodeLocalTheme(fileName: $0.rawValue)! })
|
||||
}
|
||||
|
||||
static var newTheme: Theme {
|
||||
var result = defaultTheme
|
||||
result.id = UUID().uuidString
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
enum ThemesBuiltin: String, CaseIterable, Hashable, Equatable {
|
||||
|
||||
@@ -11,13 +11,11 @@ struct AboutView: View {
|
||||
@ObservedObject var hostsManager: HostsManager
|
||||
|
||||
var body: some View {
|
||||
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
// List {
|
||||
VStack(alignment: .leading) {
|
||||
UIImage().appIcon
|
||||
VStack {
|
||||
hostsManager.settings.appIcon.image
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 100)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 22))
|
||||
@@ -33,7 +31,7 @@ struct AboutView: View {
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
Section("Thanks to") {
|
||||
HStack(spacing: 10) {
|
||||
Link(destination: URL(string: "https://libssh.org")!) {
|
||||
Text("LibSSH")
|
||||
.padding(10)
|
||||
@@ -51,12 +49,28 @@ struct AboutView: View {
|
||||
.shadow(radius: 2)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
hostsManager.setOnboarding(to: false)
|
||||
} label: {
|
||||
Text("Show Welcome")
|
||||
}
|
||||
.padding()
|
||||
|
||||
NavigationLink {
|
||||
ShaderTestingView()
|
||||
} label: {
|
||||
VStack {
|
||||
Text("Shader Playground")
|
||||
.bold()
|
||||
Text("A collection of shaders I made while learning!")
|
||||
.font(.caption2)
|
||||
}
|
||||
}
|
||||
}
|
||||
.transition(.scale)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
// }
|
||||
// .scrollContentBackground(.hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ struct ContentView: View {
|
||||
@ObservedObject var keyManager: KeyManager
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
NavigationSplitView {
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
@@ -24,6 +24,11 @@ struct ContentView: View {
|
||||
keyManager: keyManager
|
||||
)
|
||||
|
||||
RecentsView(
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager
|
||||
)
|
||||
|
||||
HostsView(
|
||||
handler: handler,
|
||||
hostsManager: hostsManager,
|
||||
@@ -64,6 +69,11 @@ struct ContentView: View {
|
||||
}
|
||||
|
||||
Section {
|
||||
NavigationLink {
|
||||
SettingsView(hostsManager: hostsManager, keyManager: keyManager)
|
||||
} label: {
|
||||
Label("Settings", systemImage: "gear")
|
||||
}
|
||||
NavigationLink {
|
||||
AboutView(hostsManager: hostsManager)
|
||||
} label: {
|
||||
@@ -73,6 +83,7 @@ struct ContentView: View {
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
}
|
||||
.navigationTitle("ShhShell")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
NavigationLink {
|
||||
@@ -86,13 +97,15 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
} detail: {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let keymanager = KeyManager()
|
||||
ContentView(
|
||||
ContentView(
|
||||
handler: SSHHandler(host: Host.debug, keyManager: keymanager),
|
||||
hostsManager: HostsManager(),
|
||||
keyManager: keymanager
|
||||
|
||||
@@ -13,56 +13,64 @@ struct FontManagerView: View {
|
||||
@State var testLine: String = "the lazy brown fox jumps over the lazy dog"
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
VStack {
|
||||
HStack {
|
||||
Text("Font Size")
|
||||
Spacer()
|
||||
Text("\(Int(hostsManager.fontSize))")
|
||||
.contentTransition(.numericText())
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Slider(value: $hostsManager.fontSize, in: 1...20, step: 1) {
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
List {
|
||||
VStack {
|
||||
HStack {
|
||||
Text("Font Size")
|
||||
Spacer()
|
||||
Text("\(Int(hostsManager.fontSize))")
|
||||
.contentTransition(.numericText())
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
} minimumValueLabel: {
|
||||
Label("", systemImage: "textformat.size.smaller")
|
||||
} maximumValueLabel: {
|
||||
Label("", systemImage: "textformat.size.larger")
|
||||
} onEditingChanged: { bool in
|
||||
hostsManager.saveFonts()
|
||||
Slider(value: $hostsManager.fontSize, in: 1...20, step: 1) {
|
||||
|
||||
} minimumValueLabel: {
|
||||
Image(systemName: "textformat.size.smaller")
|
||||
} maximumValueLabel: {
|
||||
Image(systemName: "textformat.size.larger")
|
||||
} onEditingChanged: { bool in
|
||||
hostsManager.saveFonts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ForEach(FontFamilies.allCasesRaw, id: \.self) { fontName in
|
||||
let selected = hostsManager.selectedFont == fontName
|
||||
Button() {
|
||||
hostsManager.selectFont(fontName)
|
||||
} label: {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
Text(fontName)
|
||||
.foregroundStyle(.gray)
|
||||
HStack {
|
||||
Circle()
|
||||
.frame(width: 20)
|
||||
.opacity(selected ? 1 : 0)
|
||||
.foregroundStyle(.green)
|
||||
.animation(.spring, value: selected)
|
||||
.transition(.scale)
|
||||
Text(testLine)
|
||||
.font(.custom(fontName, size: 15))
|
||||
.bold(selected)
|
||||
.opacity(selected ? 1 : 0.8)
|
||||
|
||||
ForEach(FontFamilies.allCasesRaw, id: \.self) { fontName in
|
||||
let selected = hostsManager.selectedFont == fontName
|
||||
Button() {
|
||||
hostsManager.selectFont(fontName)
|
||||
} label: {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
Text(fontName)
|
||||
.foregroundStyle(.gray)
|
||||
HStack {
|
||||
Circle()
|
||||
.frame(width: 20)
|
||||
.opacity(selected ? 1 : 0)
|
||||
.foregroundStyle(.green)
|
||||
.animation(.spring, value: selected)
|
||||
.transition(.scale)
|
||||
Text(testLine)
|
||||
.font(.custom(fontName, size: 15))
|
||||
.bold(selected)
|
||||
.opacity(selected ? 1 : 0.8)
|
||||
.contentTransition(.numericText())
|
||||
.animation(.default, value: testLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section("Test String") {
|
||||
TextEditor(text: $testLine)
|
||||
.fixedSize()
|
||||
|
||||
Section("Test String") {
|
||||
TextField("", text: $testLine)
|
||||
.fixedSize()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Fonts")
|
||||
.scrollContentBackground(.hidden)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,14 +57,29 @@ struct ConnectionView: View {
|
||||
Text("None")
|
||||
.tag(nil as UUID?)
|
||||
}
|
||||
.onAppear {
|
||||
guard keyManager.keyIDs.contains(where: { $0 == handler.host.privateKeyID }) else {
|
||||
handler.host.privateKeyID = nil
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button() {
|
||||
showTerminal.toggle()
|
||||
} label: {
|
||||
Label("Show Terminal", systemImage: "apple.terminal")
|
||||
Section() {
|
||||
Picker("Startup Snippet", selection: $handler.host.startupSnippetID) {
|
||||
ForEach(hostsManager.snippets) { snip in
|
||||
Text(snip.name).tag(snip.id as UUID?)
|
||||
}
|
||||
Divider()
|
||||
Text("None").tag(nil as UUID?)
|
||||
}
|
||||
.onAppear {
|
||||
guard hostsManager.snippets.contains(where: { $0.id == handler.host.startupSnippetID }) else {
|
||||
handler.host.startupSnippetID = nil
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(!checkShell(handler.state))
|
||||
|
||||
Button() {
|
||||
handler.testExec()
|
||||
@@ -86,8 +101,6 @@ struct ConnectionView: View {
|
||||
Button(role: .destructive) {
|
||||
handler.host.key = handler.getHostkey()
|
||||
handler.disconnect()
|
||||
// handler.go()
|
||||
// showTerminal = checkShell(handler.state)
|
||||
} label: {
|
||||
Text("Accept Hostkey")
|
||||
}
|
||||
@@ -116,7 +129,7 @@ Hostkey fingerprint is \(handler.getHostkey() ?? "nil")
|
||||
Button() {
|
||||
withAnimation { showIconPicker.toggle() }
|
||||
} label: {
|
||||
HostSymbolPreview(symbol: handler.host.symbol, label: handler.host.label, small: true)
|
||||
HostSymbolPreview(symbol: handler.host.symbol, label: handler.host.label, horizontal: true)
|
||||
.id(handler.host)
|
||||
}
|
||||
.popover(isPresented: $showIconPicker) {
|
||||
@@ -126,21 +139,31 @@ Hostkey fingerprint is \(handler.getHostkey() ?? "nil")
|
||||
}
|
||||
}
|
||||
|
||||
if #available(iOS 19, *) {
|
||||
ToolbarSpacer()
|
||||
}
|
||||
|
||||
ToolbarItem() {
|
||||
Button() {
|
||||
handler.go()
|
||||
showTerminal = checkShell(handler.state)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()+0.1) {
|
||||
guard checkShell(handler.state) else { return }
|
||||
hostsManager.addToHistory(handler.host)
|
||||
handler.writeToChannel(hostsManager.snippets.first(where: { $0.id == handler.host.startupSnippetID })?.content)
|
||||
}
|
||||
} label: {
|
||||
Label(
|
||||
handler.connected ? "Disconnect" : "Connect",
|
||||
systemImage: handler.connected ? "xmark.app.fill" : "power"
|
||||
)
|
||||
Label("Connect", systemImage: "power")
|
||||
}
|
||||
.disabled(handler.hostInvalid())
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $showTerminal) {
|
||||
ShellTabView(handler: handler, hostsManager: hostsManager)
|
||||
ShellTabView(
|
||||
handler: handler,
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
66
ShhShell/Views/Hosts/HostPreview.swift
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// HostPreview.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 17/08/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct HostPreview: View {
|
||||
@ObservedObject var hostsManager: HostsManager
|
||||
@ObservedObject var keyManager: KeyManager
|
||||
@State var host: Host
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
HStack {
|
||||
Image(systemName: "info.circle")
|
||||
Text("Info for \"\(host.description)\"")
|
||||
.monospaced()
|
||||
Spacer()
|
||||
HostSymbolPreview(symbol: host.symbol, label: host.label)
|
||||
.frame(maxHeight: 30)
|
||||
}
|
||||
|
||||
Section {
|
||||
TextBox(label: "Name", text: $host.name, prompt: "")
|
||||
|
||||
TextBox(label: "Address", text: $host.address, prompt: "")
|
||||
|
||||
TextBox(label: "Port", text: Binding(
|
||||
get: { String(host.port) },
|
||||
set: {
|
||||
if let input = Int($0) {
|
||||
host.port = input
|
||||
}
|
||||
}),
|
||||
prompt: "",
|
||||
keyboardType: .numberPad
|
||||
)
|
||||
}
|
||||
|
||||
Section {
|
||||
TextBox(label: "Username", text: $host.username, prompt: "")
|
||||
|
||||
TextBox(label: "Password", text: $host.password, prompt: "", secure: true)
|
||||
|
||||
let keypair = keyManager.keypairs.first(where: { $0.id == host.privateKeyID })
|
||||
TextBox(label: "Private key", text: .constant(keypair?.name ?? "None"), prompt: "")
|
||||
}
|
||||
|
||||
Section() {
|
||||
let startupSnip = hostsManager.snippets.first(where: { $0.id == host.startupSnippetID })
|
||||
TextBox(label: "Startup Snippet", text: .constant(startupSnip?.name ?? "None"), prompt: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
HostPreview(
|
||||
hostsManager: HostsManager(),
|
||||
keyManager: KeyManager(),
|
||||
host: Host.debug
|
||||
)
|
||||
}
|
||||
@@ -12,11 +12,20 @@ struct HostSymbolPicker: View {
|
||||
|
||||
@Environment(\.colorScheme) var cScheme
|
||||
|
||||
var innerR: CGFloat {
|
||||
if #available(iOS 19, *) {
|
||||
return 20
|
||||
} else {
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Rectangle()
|
||||
.foregroundStyle(cScheme == .dark ? .black : .gray)
|
||||
VStack(alignment: .center, spacing: 10) {
|
||||
.modifier(glassEffectCompat())
|
||||
// .foregroundStyle(cScheme == .dark ? .black : .gray)
|
||||
VStack(alignment: .center, spacing: 0) {
|
||||
ScrollView(.horizontal) {
|
||||
HStack {
|
||||
ForEach(HostSymbol.allCases, id: \.self) { symbol in
|
||||
@@ -24,7 +33,7 @@ struct HostSymbolPicker: View {
|
||||
if host.symbol == symbol {
|
||||
Rectangle()
|
||||
.fill(.gray.opacity(0.5))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 5))
|
||||
.clipShape(RoundedRectangle(cornerRadius: innerR))
|
||||
}
|
||||
HostSymbolPreview(symbol: symbol, label: host.label)
|
||||
.padding(10)
|
||||
@@ -37,15 +46,20 @@ struct HostSymbolPicker: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 50)
|
||||
}
|
||||
.scrollIndicators(.visible)
|
||||
|
||||
Spacer()
|
||||
Divider()
|
||||
Spacer()
|
||||
|
||||
TextBox(label: host.label.isEmpty ? "" : "Icon Label", text: $host.label, prompt: "Icon label")
|
||||
.padding(5)
|
||||
}
|
||||
.padding(10)
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
.scrollDisabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,17 @@ import SwiftUI
|
||||
struct HostSymbolPreview: View {
|
||||
@State var symbol: HostSymbol
|
||||
@State var label: String
|
||||
@State var small: Bool = false
|
||||
@State var horizontal: Bool = false
|
||||
|
||||
var body: some View {
|
||||
if small {
|
||||
if horizontal {
|
||||
HStack(alignment: .center, spacing: 5) {
|
||||
Text(label)
|
||||
.font(.headline)
|
||||
symbol.image
|
||||
.resizable().scaledToFit()
|
||||
.symbolRenderingMode(.monochrome)
|
||||
if !label.isEmpty {
|
||||
Text(label)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ZStack(alignment: .center) {
|
||||
@@ -40,4 +41,7 @@ struct HostSymbolPreview: View {
|
||||
|
||||
#Preview {
|
||||
HostSymbolPreview(symbol: HostSymbol.desktopcomputer, label: "lo0")
|
||||
.border(.red)
|
||||
HostSymbolPreview(symbol: HostSymbol.laptopcomputer, label: "lo1", horizontal: true)
|
||||
.border(.blue)
|
||||
}
|
||||
@@ -33,6 +33,21 @@ struct HostsView: View {
|
||||
}
|
||||
.id(host)
|
||||
.animation(.default, value: host)
|
||||
.contextMenu {
|
||||
Button() {
|
||||
hostsManager.duplicateHost(host)
|
||||
} label: {
|
||||
Label("Duplicate", systemImage: "square.filled.on.square")
|
||||
}
|
||||
Button(role: .destructive) {
|
||||
hostsManager.removeHost(host)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
} preview: {
|
||||
HostPreview(hostsManager: hostsManager, keyManager: keyManager, host: host)
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
hostsManager.removeHost(host)
|
||||
@@ -61,7 +76,7 @@ struct HostsView: View {
|
||||
#Preview {
|
||||
HostsView(
|
||||
handler: SSHHandler(host: Host.debug, keyManager: nil),
|
||||
hostsManager: HostsManager(),
|
||||
hostsManager: HostsManager(previews: true),
|
||||
keyManager: KeyManager()
|
||||
)
|
||||
}
|
||||
|
||||
130
ShhShell/Views/Hosts/RecentsView.swift
Normal file
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// RecentsView.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 14/08/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RecentsView: View {
|
||||
@ObservedObject var hostsManager: HostsManager
|
||||
@ObservedObject var keyManager: KeyManager
|
||||
|
||||
@State var historyLimit: Int = 1
|
||||
var historyLimitDisplay: String {
|
||||
let count = hostsManager.history.count
|
||||
if historyLimit == 0 {
|
||||
return "\(count) item\(plural(count))"
|
||||
} else if historyLimit > count {
|
||||
return "\(count)/\(count)"
|
||||
} else {
|
||||
return "\(historyLimit)/\(count)"
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if !hostsManager.history.isEmpty {
|
||||
Section("Recents") {
|
||||
ForEach(hostsManager.history.reversed().prefix(historyLimit)) { history in
|
||||
NavigationLink() {
|
||||
ConnectionView(
|
||||
handler: SSHHandler(
|
||||
host: history.host,
|
||||
keyManager: keyManager
|
||||
),
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager
|
||||
)
|
||||
} label: {
|
||||
Text("\(history.count)")
|
||||
.foregroundStyle(.gray)
|
||||
.padding(.trailing, 10)
|
||||
.font(.caption)
|
||||
VStack(alignment: .leading) {
|
||||
Text(history.host.description)
|
||||
.font(.body)
|
||||
Text("Last connected at " + history.lastConnect.formatted())
|
||||
.font(.caption)
|
||||
.foregroundStyle(.gray)
|
||||
}
|
||||
}
|
||||
.swipeActions {
|
||||
Button("Remove", systemImage: "trash", role: .destructive) {
|
||||
hostsManager.removeFromHistory(history)
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
HStack(alignment: .center) {
|
||||
Button() {
|
||||
var decrement: Int = 2
|
||||
if historyLimit < 2 { decrement = 1 }
|
||||
withAnimation(.spring) { historyLimit -= decrement }
|
||||
} label: {
|
||||
Image(systemName: "chevron.up")
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 20)
|
||||
.foregroundStyle(hostsManager.tint)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(historyLimit == 0)
|
||||
.padding(.trailing, 10)
|
||||
|
||||
Button() {
|
||||
withAnimation(.spring) { historyLimit += 2 }
|
||||
} label: {
|
||||
Image(systemName: "chevron.down")
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 20)
|
||||
.foregroundStyle(hostsManager.tint)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(historyLimit >= hostsManager.history.count)
|
||||
|
||||
Spacer()
|
||||
Text(historyLimitDisplay)
|
||||
.foregroundStyle(.gray)
|
||||
.font(.caption)
|
||||
.contentTransition(.numericText())
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
withAnimation(.spring) { historyLimit = Int.max }
|
||||
} label: {
|
||||
Image(systemName: "rectangle.expand.vertical")
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 20)
|
||||
.foregroundStyle(hostsManager.tint)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(historyLimit >= hostsManager.history.count)
|
||||
.padding(.trailing, 10)
|
||||
|
||||
Button {
|
||||
withAnimation(.spring) { historyLimit = 0 }
|
||||
} label: {
|
||||
Image(systemName: "rectangle.compress.vertical")
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 20)
|
||||
.foregroundStyle(hostsManager.tint)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(historyLimit == 0)
|
||||
}
|
||||
}
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
RecentsView(
|
||||
hostsManager: HostsManager(),
|
||||
keyManager: KeyManager()
|
||||
)
|
||||
}
|
||||
|
||||
func plural(_ num: Int) -> String {
|
||||
return num == 1 ? "" : "s"
|
||||
}
|
||||
@@ -14,58 +14,56 @@ struct HostkeysView: View {
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
NavigationStack {
|
||||
List {
|
||||
if hostsManager.hosts.isEmpty {
|
||||
List {
|
||||
if hostsManager.hosts.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Looking empty 'round here...")
|
||||
.font(.title3)
|
||||
.bold()
|
||||
.padding(.bottom)
|
||||
VStack(alignment: .leading) {
|
||||
Text("Looking empty 'round here...")
|
||||
.font(.title3)
|
||||
.bold()
|
||||
Text("Connect to some hosts to collect more hostkeys!")
|
||||
.padding(.bottom)
|
||||
VStack(alignment: .leading) {
|
||||
Text("Connect to some hosts to collect more hostkeys!")
|
||||
.padding(.bottom)
|
||||
Text("ShhShell remembers hostkey fingerprints for you, and will alert you if they change.")
|
||||
.font(.subheadline)
|
||||
Text("This could be due a man in the middle attack, where a bad actor tries to impersonate your server.")
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ForEach($hostsManager.hosts) { $host in
|
||||
VStack(alignment: .leading) {
|
||||
if !host.name.isEmpty {
|
||||
Text("name")
|
||||
.foregroundStyle(.gray)
|
||||
.font(.caption)
|
||||
Text(host.name)
|
||||
.bold()
|
||||
}
|
||||
Text("address")
|
||||
.foregroundStyle(.gray)
|
||||
.font(.caption)
|
||||
Text(host.address)
|
||||
.bold()
|
||||
Text(host.key ?? "nil")
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button(/*role: .destructive*/) {
|
||||
host.key = nil
|
||||
hostsManager.updateHost(host)
|
||||
} label: {
|
||||
Label("Forget", systemImage: "trash")
|
||||
}
|
||||
Text("ShhShell remembers hostkey fingerprints for you, and will alert you if they change.")
|
||||
.font(.subheadline)
|
||||
Text("This could be due a man in the middle attack, where a bad actor tries to impersonate your server.")
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ForEach($hostsManager.hosts) { $host in
|
||||
VStack(alignment: .leading) {
|
||||
if !host.name.isEmpty {
|
||||
Text("name")
|
||||
.foregroundStyle(.gray)
|
||||
.font(.caption)
|
||||
Text(host.name)
|
||||
.bold()
|
||||
}
|
||||
Text("address")
|
||||
.foregroundStyle(.gray)
|
||||
.font(.caption)
|
||||
Text(host.address)
|
||||
.bold()
|
||||
Text(host.key ?? "nil")
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button(/*role: .destructive*/) {
|
||||
host.key = nil
|
||||
hostsManager.updateHost(host)
|
||||
} label: {
|
||||
Label("Forget", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.navigationTitle("Hostkeys")
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.navigationTitle("Hostkeys")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
HostkeysView(hostsManager: HostsManager())
|
||||
HostkeysView(hostsManager: HostsManager())
|
||||
}
|
||||
|
||||
@@ -45,8 +45,10 @@ struct KeyImporterView: View {
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
TextEditor(text: $privkeyStr)
|
||||
.background(.black)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
.background(.gray.opacity(0.5))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
.padding(.bottom, 5)
|
||||
.padding(.horizontal, -8)
|
||||
}
|
||||
|
||||
if !keypair.openSshPubkey.isEmpty {
|
||||
@@ -55,20 +57,20 @@ struct KeyImporterView: View {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Button() {
|
||||
keyManager.importKey(type: keyType, priv: privkeyStr, name: keyName)
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("Import")
|
||||
.font(.title)
|
||||
.bold()
|
||||
.preferredColorScheme(.dark)
|
||||
.overlay(alignment: .bottom) {
|
||||
Button {
|
||||
keyManager.importKey(type: keyType, priv: privkeyStr, name: keyName)
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("Import")
|
||||
.font(.title)
|
||||
.bold()
|
||||
}
|
||||
.modifier(glassButton(prominent: true))
|
||||
.padding(.bottom, 15)
|
||||
}
|
||||
.onTapGesture {
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,58 +17,57 @@ struct KeyManagerView: View {
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
NavigationStack {
|
||||
List {
|
||||
Section() {
|
||||
ForEach(keyManager.keypairs) { kp in
|
||||
NavigationLink {
|
||||
KeyDetailView(
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager,
|
||||
keypair: kp
|
||||
)
|
||||
List {
|
||||
Section() {
|
||||
ForEach(keyManager.keypairs) { kp in
|
||||
NavigationLink {
|
||||
KeyDetailView(
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager,
|
||||
keypair: kp
|
||||
)
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "key")
|
||||
if kp.label.isEmpty {
|
||||
Text(kp.id.uuidString)
|
||||
} else {
|
||||
Text(kp.label)
|
||||
}
|
||||
Spacer()
|
||||
Text(kp.type.description)
|
||||
.foregroundStyle(.gray)
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
keyManager.deleteKey(kp)
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "key")
|
||||
if kp.label.isEmpty {
|
||||
Text(kp.id.uuidString)
|
||||
} else {
|
||||
Text(kp.label)
|
||||
}
|
||||
Spacer()
|
||||
Text(kp.type.description)
|
||||
.foregroundStyle(.gray)
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
keyManager.deleteKey(kp)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
.id(keyManager.keypairs)
|
||||
}
|
||||
|
||||
CenteredLabel(title: "Generate a key", systemName: "plus")
|
||||
.onTapGesture {
|
||||
let comment = UIDevice().model + " at " + Date().formatted(date: .numeric, time: .omitted)
|
||||
keyManager.generateKey(type: .ed25519, comment: comment)
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
CenteredLabel(title: "Import a key", systemName: "square.and.arrow.down")
|
||||
.onTapGesture {
|
||||
showImporter.toggle()
|
||||
}
|
||||
.sheet(isPresented: $showImporter) {
|
||||
KeyImporterView(keyManager: keyManager)
|
||||
}
|
||||
.id(keyManager.keypairs)
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.navigationTitle("Keys")
|
||||
|
||||
CenteredLabel(title: "Generate a key", systemName: "plus")
|
||||
.onTapGesture {
|
||||
let comment = UIDevice().model + " at " + Date().formatted(date: .numeric, time: .omitted)
|
||||
keyManager.generateKey(type: .ed25519, comment: comment)
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
CenteredLabel(title: "Import a key", systemName: "square.and.arrow.down")
|
||||
.onTapGesture {
|
||||
showImporter.toggle()
|
||||
}
|
||||
.sheet(isPresented: $showImporter) {
|
||||
KeyImporterView(keyManager: keyManager)
|
||||
.colorScheme(hostsManager.selectedTheme.background.luminance > 0.5 ? .light : .dark)
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.navigationTitle("Keys")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,40 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct glassEffectCompat: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
if #available(iOS 19, *) {
|
||||
content.glassEffect()
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct glassButton: ViewModifier {
|
||||
var prominent: Bool
|
||||
|
||||
init(prominent: Bool = false) {
|
||||
self.prominent = prominent
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if #available(iOS 19, *) {
|
||||
if prominent {
|
||||
content.buttonStyle(.glassProminent)
|
||||
} else {
|
||||
content.buttonStyle(.glass)
|
||||
}
|
||||
} else {
|
||||
if prominent {
|
||||
content.buttonStyle(.borderedProminent)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct foregroundColorStyle: ViewModifier {
|
||||
var color: Color
|
||||
|
||||
|
||||
74
ShhShell/Views/Onboarding/WelcomeChunk.swift
Normal file
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// WelcomeChunk.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 27/08/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct WelcomeChunk: View {
|
||||
@State var systemImage: String?
|
||||
@State var image: String?
|
||||
@State var title: String
|
||||
@State var para: String
|
||||
@State var delay: TimeInterval = 0
|
||||
|
||||
@State private var spawnDate: Date = .now
|
||||
|
||||
var imageUnwrapped: Image {
|
||||
if let systemImage {
|
||||
return Image(systemName: systemImage)
|
||||
} else if let image {
|
||||
return Image(image)
|
||||
} else {
|
||||
return Image(systemName: "questionmark")
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
TimelineView(.animation) { tl in
|
||||
let time = tl.date.timeIntervalSince(spawnDate)
|
||||
HStack(spacing: 25) {
|
||||
if time > delay {
|
||||
imageUnwrapped
|
||||
.resizable().scaledToFit()
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 50, height: 50)
|
||||
.transition(.scale)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
if time > delay+0.25 {
|
||||
Text(title)
|
||||
.bold()
|
||||
.font(.headline)
|
||||
.transition(.opacity)
|
||||
}
|
||||
if time > delay+0.75 && !para.isEmpty {
|
||||
Text(para)
|
||||
.foregroundStyle(.gray)
|
||||
.font(.footnote)
|
||||
.transition(.opacity)
|
||||
.lineLimit(nil)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
}
|
||||
.shadow(color: .white, radius: time > delay+0.75 ? 0 : 5)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.animation(.spring, value: time)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 30)
|
||||
.padding(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
WelcomeChunk(
|
||||
systemImage: "trash",
|
||||
title: "The Trash",
|
||||
para: "Here's to the crazy ones."
|
||||
)
|
||||
}
|
||||
108
ShhShell/Views/Onboarding/WelcomeView.swift
Normal file
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// WelcomeView.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 27/08/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct WelcomeView: View {
|
||||
@ObservedObject var hostsManager: HostsManager
|
||||
@State private var spawnDate: Date = .now
|
||||
|
||||
var body: some View {
|
||||
TimelineView(.animation) { tl in
|
||||
let time = tl.date.timeIntervalSince(spawnDate)
|
||||
VStack {
|
||||
VStack(spacing: 20) {
|
||||
if time > 0.1 {
|
||||
AppIcon.regular.image
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 100)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 22))
|
||||
.shadow(color: .white, radius: time > 0.25 ? 5 : 0)
|
||||
.transition(.scale)
|
||||
}
|
||||
if time > 2 {
|
||||
Text("Welcome")
|
||||
.monospaced()
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.transition(.opacity)
|
||||
.shadow(color: .white, radius: time > 2.25 ? 0 : 5)
|
||||
}
|
||||
}
|
||||
// .padding(.top, time > 3 ? 25 : 0)
|
||||
|
||||
if time > 3 {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
WelcomeChunk(
|
||||
systemImage: "bolt.fill",
|
||||
title: "Blazing Fast",
|
||||
para: "",
|
||||
delay: 4
|
||||
)
|
||||
WelcomeChunk(
|
||||
image: "apple.terminal.on.rectangle.fill",
|
||||
title: "Multiple Sessions",
|
||||
para: "Connect to the same host again and again, or different ones",
|
||||
delay: 5
|
||||
)
|
||||
WelcomeChunk(
|
||||
systemImage: "swatchpalette.fill",
|
||||
title: "Themes",
|
||||
para: "Customise ShhShell by importing themes, or make your own!",
|
||||
delay: 6
|
||||
)
|
||||
WelcomeChunk(
|
||||
systemImage: "lock.shield.fill",
|
||||
title: "Secure",
|
||||
para: "ShhShell uses secure Elliptic Curve keys, and keeps you safe by verifying hostkeys haven't changed",
|
||||
delay: 7
|
||||
)
|
||||
WelcomeChunk(
|
||||
systemImage: "ellipsis.circle",
|
||||
title: "And more...",
|
||||
para: "Snippets, iCloud Sync, Fonts, Terminal Filters, Connection History",
|
||||
delay: 8
|
||||
)
|
||||
|
||||
if time > 3 {
|
||||
Spacer()
|
||||
}
|
||||
if time > 9 {
|
||||
Button {
|
||||
hostsManager.setOnboarding(to: true)
|
||||
} label: {
|
||||
if #available(iOS 19, *) {
|
||||
Text("Continue")
|
||||
.monospaced()
|
||||
.font(.title)
|
||||
.foregroundStyle(.black)
|
||||
} else {
|
||||
ZStack {
|
||||
Color.terminalGreen
|
||||
Text("Continue")
|
||||
.monospaced()
|
||||
.bold()
|
||||
.foregroundStyle(.black)
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: 50))
|
||||
}
|
||||
}
|
||||
.tint(.terminalGreen)
|
||||
.modifier(glassButton(prominent: true))
|
||||
}
|
||||
}
|
||||
.animation(.spring, value: time)
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
WelcomeView(hostsManager: HostsManager(previews: true))
|
||||
}
|
||||
@@ -34,7 +34,13 @@ struct SessionView: View {
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $shellPresented) {
|
||||
ShellTabView(handler: nil, hostsManager: hostsManager, selectedID: key)
|
||||
//instancing handler here cos its non optional
|
||||
ShellTabView(
|
||||
handler: nil,
|
||||
hostsManager: hostsManager,
|
||||
keyManager: keyManager,
|
||||
selectedID: key
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
233
ShhShell/Views/Settings/SettingsView.swift
Normal file
@@ -0,0 +1,233 @@
|
||||
//
|
||||
// SettingsView.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 19/08/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftTerm
|
||||
|
||||
struct SettingsView: View {
|
||||
@ObservedObject var hostsManager: HostsManager
|
||||
@ObservedObject var keyManager: KeyManager
|
||||
|
||||
@State private var blinkCursor: Int = 0
|
||||
@State var blinkTimer: Timer?
|
||||
|
||||
func startBlinkingIfNeeded() {
|
||||
if hostsManager.settings.cursorType.blink {
|
||||
blinkTimer?.invalidate()
|
||||
blinkTimer = nil
|
||||
blinkTimer = Timer(timeInterval: 1, repeats: true) { timer in
|
||||
Task { @MainActor in
|
||||
blinkCursor += 1
|
||||
}
|
||||
}
|
||||
RunLoop.main.add(blinkTimer!, forMode: .common)
|
||||
} else {
|
||||
blinkTimer?.invalidate()
|
||||
if blinkCursor % 2 != 0 {
|
||||
blinkCursor += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
List {
|
||||
Section("Display") {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Label("Scrollback", systemImage: "scroll")
|
||||
Spacer()
|
||||
Text("\(Int(hostsManager.settings.scrollback))")
|
||||
.contentTransition(.numericText())
|
||||
}
|
||||
Slider(
|
||||
value: $hostsManager.settings.scrollback,
|
||||
in: 250...10_000,
|
||||
step: 250
|
||||
)
|
||||
}
|
||||
Toggle("Keep Display Awake", systemImage: "cup.and.saucer.fill", isOn: $hostsManager.settings.caffeinate)
|
||||
if #unavailable(iOS 17), hostsManager.settings.filter == .crt {
|
||||
Label("iOS 17 Required", systemImage: "exclamationmark.triangle.fill")
|
||||
.foregroundStyle(.yellow)
|
||||
.transition(.opacity)
|
||||
}
|
||||
Picker("", selection: $hostsManager.settings.filter) {
|
||||
ForEach(TerminalFilter.allCases, id: \.self) { filter in
|
||||
Text(filter.description).tag(filter)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.inline)
|
||||
.labelsHidden()
|
||||
}
|
||||
.animation(.spring, value: hostsManager.settings.filter)
|
||||
|
||||
Section("Cursor") {
|
||||
HStack(spacing: 20) {
|
||||
Text("neon443")
|
||||
.font(.largeTitle).monospaced()
|
||||
.foregroundStyle(.terminalGreen)
|
||||
Text("~")
|
||||
.font(.largeTitle).monospaced()
|
||||
.foregroundStyle(.blue)
|
||||
Text(">")
|
||||
.font(.largeTitle).monospaced()
|
||||
.foregroundStyle(.blue)
|
||||
ZStack {
|
||||
if hostsManager.settings.cursorType.cursorShape == .block {
|
||||
Rectangle()
|
||||
.frame(width: 20, height: 40)
|
||||
} else if hostsManager.settings.cursorType.cursorShape == .bar {
|
||||
Rectangle()
|
||||
.frame(width: 4, height: 40)
|
||||
} else if hostsManager.settings.cursorType.cursorShape == .underline {
|
||||
Rectangle()
|
||||
.frame(width: 20, height: 4)
|
||||
.padding(.top, 36)
|
||||
}
|
||||
}
|
||||
// .padding(.leading, 248)
|
||||
.id(hostsManager.settings.cursorType.cursorShape)
|
||||
.animation(.default, value: hostsManager.settings.cursorType.cursorShape)
|
||||
.transition(.opacity)
|
||||
.onChange(of: hostsManager.settings.cursorType.blink) { _ in
|
||||
startBlinkingIfNeeded()
|
||||
}
|
||||
.onAppear() {
|
||||
startBlinkingIfNeeded()
|
||||
}
|
||||
.opacity(blinkCursor % 2 == 0 ? 1 : 0)
|
||||
.animation(
|
||||
Animation.spring(duration: 1),
|
||||
value: blinkCursor
|
||||
)
|
||||
}
|
||||
|
||||
Picker("Blink", selection: $hostsManager.settings.cursorType.blink) {
|
||||
Text("Blink").tag(true)
|
||||
Text("Steady").tag(false)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
|
||||
Picker("Shape", selection: $hostsManager.settings.cursorType.cursorShape) {
|
||||
ForEach(CursorShape.allCases, id: \.self) { type in
|
||||
Text(type.description).tag(type)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.inline)
|
||||
.labelsHidden()
|
||||
}
|
||||
|
||||
Section("Cursor Animations") {
|
||||
Picker("Animation Type", selection: $hostsManager.settings.cursorAnimations.type) {
|
||||
ForEach(CursorAnimationType.allCases, id: \.self) { animType in
|
||||
Text(animType.description).tag(animType)
|
||||
}
|
||||
}
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
Text("Animation length")
|
||||
Spacer()
|
||||
Text("\(hostsManager.settings.cursorAnimations.length)s")
|
||||
.monospaced()
|
||||
.contentTransition(.numericText())
|
||||
}
|
||||
Slider(value: $hostsManager.settings.cursorAnimations.length, in: 0.05...0.5, label: {
|
||||
EmptyView()
|
||||
}, minimumValueLabel: {
|
||||
Text("0.05")
|
||||
}, maximumValueLabel: {
|
||||
Text("0.5")
|
||||
})
|
||||
.disabled(hostsManager.settings.cursorAnimations.type == .none)
|
||||
}
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
Text("Stretch Multiplier")
|
||||
Spacer()
|
||||
Text("\(hostsManager.settings.cursorAnimations.stretchMultiplier)x")
|
||||
.monospaced()
|
||||
.contentTransition(.numericText())
|
||||
}
|
||||
Slider(value: $hostsManager.settings.cursorAnimations.stretchMultiplier, in: 0.25...2, label: {
|
||||
EmptyView()
|
||||
}, minimumValueLabel: {
|
||||
Text("0.25")
|
||||
}, maximumValueLabel: {
|
||||
Text("2")
|
||||
})
|
||||
.disabled(hostsManager.settings.cursorAnimations.type != .stretchAndMove)
|
||||
}
|
||||
}
|
||||
|
||||
// Section("Keepalive") {
|
||||
// Toggle("Location Persistence", systemImage: "location.fill", isOn: $hostsManager.settings.locationPersist)
|
||||
// .onChange(of: hostsManager.settings.locationPersist) { _ in
|
||||
// if hostsManager.settings.locationPersist && !Backgrounder.shared.checkPermsStatus() {
|
||||
// Backgrounder.shared.requestPerms()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
Section("Bell Feedback") {
|
||||
Toggle("Sound", systemImage: "bell.and.waves.left.and.right", isOn: $hostsManager.settings.bellSound)
|
||||
Toggle("Haptic",systemImage: "iphone.radiowaves.left.and.right", isOn: $hostsManager.settings.bellHaptic)
|
||||
}
|
||||
|
||||
Section("App Icon") {
|
||||
ScrollView(.horizontal) {
|
||||
HStack {
|
||||
ForEach(AppIcon.allCases, id: \.self) { icon in
|
||||
let isSelected = hostsManager.settings.appIcon == icon
|
||||
ZStack(alignment: .top) {
|
||||
RoundedRectangle(cornerRadius: icon.radius + 5)
|
||||
.foregroundStyle(.gray.opacity(0.5))
|
||||
.opacity(isSelected ? 1 : 0)
|
||||
VStack(spacing: 0) {
|
||||
icon.image
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 85, height: 85)
|
||||
.clipShape(RoundedRectangle(cornerRadius: icon.radius))
|
||||
.padding(5)
|
||||
Text(icon.description).tag(icon)
|
||||
.font(.caption)
|
||||
.padding(.bottom, 5)
|
||||
.padding(.horizontal, 5)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
hostsManager.settings.appIcon = icon
|
||||
hostsManager.setAppIcon()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
.listStyle(.insetGrouped)
|
||||
.scrollContentBackground(.hidden)
|
||||
.onChange(of: hostsManager.settings) { _ in
|
||||
hostsManager.saveSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SettingsView(
|
||||
hostsManager: HostsManager(previews: true),
|
||||
keyManager: KeyManager()
|
||||
)
|
||||
}
|
||||
@@ -34,7 +34,7 @@ struct AddSnippetView: View {
|
||||
TextEditor(text: $content)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.background(.black)
|
||||
// .background(.black)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 5))
|
||||
.padding(.bottom)
|
||||
.frame(minHeight: 50)
|
||||
@@ -54,6 +54,7 @@ struct AddSnippetView: View {
|
||||
} label: {
|
||||
Label("Add", systemImage: "plus")
|
||||
}
|
||||
.disabled(name.isEmpty || content.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,78 +11,77 @@ struct SnippetManagerView: View {
|
||||
@ObservedObject var hostsManager: HostsManager
|
||||
@State var showSnippetAdder: Bool = false
|
||||
|
||||
var body: some View {
|
||||
var body: some View {
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
.ignoresSafeArea(.all)
|
||||
NavigationStack {
|
||||
List {
|
||||
if hostsManager.snippets.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
Image(systemName: "questionmark.square.dashed")
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 75)
|
||||
.foregroundStyle(hostsManager.tint)
|
||||
.shadow(color: hostsManager.tint, radius: 2)
|
||||
.padding(.bottom, 10)
|
||||
Text("No Snippets")
|
||||
.font(.title)
|
||||
.monospaced()
|
||||
Text("Snippets are strings of commands that can be run at once in a terminal.")
|
||||
.padding(.bottom)
|
||||
.foregroundStyle(.gray)
|
||||
.foregroundStyle(.foreground.opacity(0.7))
|
||||
}
|
||||
}
|
||||
ForEach(hostsManager.snippets) { snip in
|
||||
VStack(alignment: .leading) {
|
||||
Text(snip.name)
|
||||
.bold()
|
||||
.foregroundStyle(.gray)
|
||||
.font(.subheadline)
|
||||
Text(snip.content)
|
||||
.lineLimit(3)
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
hostsManager.deleteSnippet(snip)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
Button {
|
||||
hostsManager.duplicateSnippet(snip)
|
||||
} label: {
|
||||
Label("Duplicate", systemImage: "square.filled.on.square")
|
||||
}
|
||||
.tint(.blue)
|
||||
Button {
|
||||
UIPasteboard().string = snip.content
|
||||
} label: {
|
||||
Label("Copy", systemImage: "doc.on.clipboard")
|
||||
}
|
||||
.tint(.blue)
|
||||
}
|
||||
List {
|
||||
if hostsManager.snippets.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
Image(systemName: "questionmark.square.dashed")
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 75)
|
||||
.foregroundStyle(hostsManager.tint)
|
||||
.shadow(color: hostsManager.tint, radius: 2)
|
||||
.padding(.bottom, 10)
|
||||
Text("No Snippets")
|
||||
.font(.title)
|
||||
.monospaced()
|
||||
Text("Snippets are strings of commands that can be run at once in a terminal.")
|
||||
.padding(.bottom)
|
||||
.foregroundStyle(.gray)
|
||||
.foregroundStyle(.foreground.opacity(0.7))
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.sheet(isPresented: $showSnippetAdder) {
|
||||
AddSnippetView(hostsManager: hostsManager)
|
||||
.presentationDetents([.medium])
|
||||
}
|
||||
.toolbar {
|
||||
Button() {
|
||||
showSnippetAdder.toggle()
|
||||
} label: {
|
||||
Label("Add", systemImage: "plus")
|
||||
ForEach(hostsManager.snippets) { snip in
|
||||
VStack(alignment: .leading) {
|
||||
Text(snip.name)
|
||||
.bold()
|
||||
.foregroundStyle(.gray)
|
||||
.font(.subheadline)
|
||||
Text(snip.content)
|
||||
.lineLimit(3)
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
hostsManager.deleteSnippet(snip)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
Button {
|
||||
hostsManager.duplicateSnippet(snip)
|
||||
} label: {
|
||||
Label("Duplicate", systemImage: "square.filled.on.square")
|
||||
}
|
||||
.tint(.blue)
|
||||
Button {
|
||||
UIPasteboard().string = snip.content
|
||||
} label: {
|
||||
Label("Copy", systemImage: "doc.on.clipboard")
|
||||
}
|
||||
.tint(.blue)
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.sheet(isPresented: $showSnippetAdder) {
|
||||
AddSnippetView(hostsManager: hostsManager)
|
||||
.presentationDetents([.medium])
|
||||
}
|
||||
.toolbar {
|
||||
Button() {
|
||||
showSnippetAdder.toggle()
|
||||
} label: {
|
||||
Label("Add", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
.navigationTitle("Snippets")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SnippetManagerView(hostsManager: HostsManager())
|
||||
SnippetManagerView(hostsManager: HostsManager())
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,6 @@ struct SnippetPicker: View {
|
||||
Text("No Snippets")
|
||||
.font(.headline)
|
||||
.monospaced()
|
||||
Button() {
|
||||
showAdder.toggle()
|
||||
} label: {
|
||||
Label("Add", systemImage: "plus.circle.fill")
|
||||
}
|
||||
}
|
||||
ForEach(hostsManager.snippets) { snip in
|
||||
Button(snip.name) {
|
||||
@@ -34,6 +29,14 @@ struct SnippetPicker: View {
|
||||
callback?(snip)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Button() {
|
||||
showAdder.toggle()
|
||||
} label: {
|
||||
Label("Add", systemImage: "plus.circle.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showAdder) {
|
||||
AddSnippetView(hostsManager: hostsManager)
|
||||
|
||||
35
ShhShell/Views/Terminal/CRTView.swift
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// CRTView.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 25/08/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CRTView: View {
|
||||
@State var startTime: Date = .now
|
||||
|
||||
var body: some View {
|
||||
TimelineView(.animation) { tl in
|
||||
let time = tl.date.distance(to: startTime)
|
||||
if #available(iOS 17, *) {
|
||||
Rectangle()
|
||||
.foregroundStyle(.black.opacity(1))
|
||||
.visualEffect { content, proxy in
|
||||
content
|
||||
.colorEffect(
|
||||
ShaderLibrary.crt(
|
||||
.float2(proxy.size),
|
||||
.float(time)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
CRTView()
|
||||
}
|
||||
@@ -6,179 +6,199 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftTerm
|
||||
|
||||
struct ShellTabView: View {
|
||||
@State var handler: SSHHandler?
|
||||
@ObservedObject var hostsManager: HostsManager
|
||||
@ObservedObject var keyManager: KeyManager
|
||||
|
||||
@ObservedObject var container = TerminalViewContainer.shared
|
||||
@State var selectedID: UUID?
|
||||
var selectedHandler: SSHHandler {
|
||||
guard let selectedID, let contained = container.sessions[selectedID] else {
|
||||
guard let handler else { return SSHHandler(host: Host.blank, keyManager: nil) }
|
||||
return handler
|
||||
}
|
||||
return contained.handler
|
||||
}
|
||||
|
||||
@State var showSnippetPicker: Bool = false
|
||||
@State private var showSnippetPicker: Bool = false
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var foreground: Color {
|
||||
var foreground: SwiftUI.Color {
|
||||
let selectedTheme = hostsManager.selectedTheme
|
||||
let foreground = selectedTheme.foreground
|
||||
let background = selectedTheme.background
|
||||
let fg = selectedTheme.foreground
|
||||
let bg = selectedTheme.background
|
||||
let ansi = selectedTheme.ansi[hostsManager.selectedAnsi]
|
||||
var result: SwiftTerm.Color
|
||||
|
||||
if selectedTheme.ansi[hostsManager.selectedAnsi].luminance > 0.5 {
|
||||
if foreground.luminance > 0.5 {
|
||||
return background.suiColor
|
||||
} else {
|
||||
return foreground.suiColor
|
||||
}
|
||||
if fg.luminanceRatio(with: ansi) > bg.luminanceRatio(with: ansi) {
|
||||
result = fg
|
||||
} else {
|
||||
if foreground.luminance > 0.5 {
|
||||
return foreground.suiColor
|
||||
} else {
|
||||
return background.suiColor
|
||||
}
|
||||
result = bg
|
||||
}
|
||||
|
||||
guard result.luminanceRatio(with: ansi) > 4.5 else {
|
||||
return ansi.luminance > 0.5 ? .black : .white
|
||||
}
|
||||
return result.suiColor
|
||||
}
|
||||
var background: Color { hostsManager.selectedTheme.background.suiColor }
|
||||
var background: SwiftUI.Color { hostsManager.selectedTheme.background.suiColor }
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
background
|
||||
.ignoresSafeArea(.all)
|
||||
GeometryReader { geo in
|
||||
VStack(spacing: 0) {
|
||||
let oneTabWidth = max(60, (geo.size.width)/CGFloat(container.sessionIDs.count))
|
||||
|
||||
HStack(alignment: .center, spacing: 10) {
|
||||
Button() {
|
||||
for session in container.sessions.values {
|
||||
session.handler.disconnect()
|
||||
}
|
||||
dismiss()
|
||||
} label: {
|
||||
TrafficLightRed()
|
||||
VStack(spacing: 0) {
|
||||
//header
|
||||
HStack(alignment: .center, spacing: 10) {
|
||||
Button() {
|
||||
dismiss()
|
||||
for session in container.sessions.values {
|
||||
session.handler.disconnect()
|
||||
session.handler.cleanup()
|
||||
}
|
||||
Button() {
|
||||
dismiss()
|
||||
} label: {
|
||||
TrafficLightYellow()
|
||||
}
|
||||
Spacer()
|
||||
VStack {
|
||||
Text(container.sessions[selectedID ?? UUID()]?.handler.title ?? handler?.title ?? "")
|
||||
} label: {
|
||||
TrafficLightRed()
|
||||
}
|
||||
Button() {
|
||||
dismiss()
|
||||
} label: {
|
||||
TrafficLightYellow()
|
||||
}
|
||||
Spacer()
|
||||
VStack {
|
||||
Text(selectedHandler.title)
|
||||
.bold()
|
||||
.foregroundStyle(foreground)
|
||||
.monospaced()
|
||||
.contentTransition(.numericText())
|
||||
.strikethrough(selectedHandler.state != .shellOpen)
|
||||
if container.sessionIDs.count == 1 {
|
||||
Text(selectedHandler.host.description)
|
||||
.bold()
|
||||
.foregroundStyle(foreground)
|
||||
.monospaced()
|
||||
.contentTransition(.numericText())
|
||||
if container.sessionIDs.count == 1 {
|
||||
Text(container.sessions[selectedID ?? UUID()]?.handler.host.description ?? handler?.host.description ?? "")
|
||||
.bold()
|
||||
.foregroundStyle(foreground)
|
||||
.monospaced()
|
||||
.font(.caption2)
|
||||
}
|
||||
.font(.caption2)
|
||||
.strikethrough(selectedHandler.state != .shellOpen)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
|
||||
if selectedHandler.state != .shellOpen {
|
||||
Button() {
|
||||
showSnippetPicker.toggle()
|
||||
withAnimation { selectedHandler.forceDismissDisconnectedAlert = false }
|
||||
} label: {
|
||||
Image(systemName: "paperclip")
|
||||
Image(systemName: "wifi.exclamationmark")
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
.foregroundStyle(foreground)
|
||||
.popover(isPresented: $showSnippetPicker) {
|
||||
SnippetPicker(hostsManager: hostsManager) {
|
||||
container.sessions[selectedID ?? UUID()]?.handler.writeToChannel($0.content)
|
||||
}
|
||||
.frame(minWidth: 300, minHeight: 400)
|
||||
.modifier(presentationCompactPopover())
|
||||
}
|
||||
.id(selectedID)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 10)
|
||||
.background(hostsManager.tint, ignoresSafeAreaEdges: .all)
|
||||
.frame(height: 40)
|
||||
|
||||
if container.sessionIDs.count > 1 {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 0) {
|
||||
ForEach(container.sessionIDs, id: \.self) { id in
|
||||
let selected: Bool = selectedID == id
|
||||
ZStack {
|
||||
Rectangle()
|
||||
.fill(selected ? hostsManager.tint : background)
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
if !selected {
|
||||
Text(container.sessions[id]!.handler.title)
|
||||
.monospaced()
|
||||
.foregroundStyle(selected ? foreground : hostsManager.tint)
|
||||
.opacity(0.7)
|
||||
.font(.callout)
|
||||
}
|
||||
Text(container.sessions[id]!.handler.host.description)
|
||||
.foregroundStyle(selected ? foreground : hostsManager.tint)
|
||||
.opacity(selected ? 1 : 0.7)
|
||||
Button() {
|
||||
showSnippetPicker.toggle()
|
||||
} label: {
|
||||
Image(systemName: "paperclip")
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
.foregroundStyle(foreground)
|
||||
.popover(isPresented: $showSnippetPicker) {
|
||||
SnippetPicker(hostsManager: hostsManager) {
|
||||
selectedHandler.writeToChannel($0.content)
|
||||
}
|
||||
.frame(minWidth: 200, minHeight: 300)
|
||||
.modifier(presentationCompactPopover())
|
||||
}
|
||||
Menu {
|
||||
Button() {
|
||||
UIPasteboard.general.string = selectedHandler.scrollback.joined()
|
||||
Haptic.success.trigger()
|
||||
} label: {
|
||||
Label("Copy Scrollback", systemImage: "document.on.document")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis")
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
.foregroundStyle(foreground)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 10)
|
||||
.background(hostsManager.tint, ignoresSafeAreaEdges: .all)
|
||||
.frame(height: 40)
|
||||
|
||||
//tab strip
|
||||
if container.sessionIDs.count > 1 {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
let oneTabWidth: CGFloat = max(100, (UIScreen.main.bounds.width)/CGFloat(container.sessionIDs.count))
|
||||
HStack(spacing: 0) {
|
||||
ForEach(container.sessionIDs, id: \.self) { id in
|
||||
let selected: Bool = id == selectedHandler.sessionID ?? UUID()
|
||||
let thisHandler: SSHHandler = container.sessions[id]!.handler
|
||||
ZStack {
|
||||
Rectangle()
|
||||
.fill(selected ? hostsManager.tint : background)
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
if !selected {
|
||||
Text(container.sessions[id]!.handler.title)
|
||||
.monospaced()
|
||||
.bold(selected)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(selected ? foreground : hostsManager.tint)
|
||||
.opacity(0.7)
|
||||
.font(.callout)
|
||||
.strikethrough(thisHandler.state != .shellOpen)
|
||||
}
|
||||
Spacer()
|
||||
Text(container.sessions[id]!.handler.host.description)
|
||||
.foregroundStyle(selected ? foreground : hostsManager.tint)
|
||||
.opacity(selected ? 1 : 0.7)
|
||||
.monospaced()
|
||||
.bold(selected)
|
||||
.font(.caption2)
|
||||
.strikethrough(thisHandler.state != .shellOpen)
|
||||
}
|
||||
}
|
||||
.frame(width: oneTabWidth)
|
||||
.onTapGesture {
|
||||
withAnimation { selectedID = id }
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 30)
|
||||
.onAppear {
|
||||
if selectedID == nil {
|
||||
if let handler {
|
||||
selectedID = handler.sessionID
|
||||
} else {
|
||||
dismiss()
|
||||
.frame(width: oneTabWidth)
|
||||
.onTapGesture {
|
||||
withAnimation { selectedID = id }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//the acc terminal lol
|
||||
if let selectedID,
|
||||
let session = container.sessions[selectedID] {
|
||||
.frame(height: 30)
|
||||
}
|
||||
|
||||
//the acc terminal lol
|
||||
// Group {
|
||||
// if selectedID != nil {
|
||||
ShellView(
|
||||
handler: session.handler,
|
||||
handler: selectedHandler,
|
||||
hostsManager: hostsManager
|
||||
)
|
||||
.onDisappear {
|
||||
if !checkShell(session.handler.state) {
|
||||
if let lastSession = container.sessionIDs.last {
|
||||
withAnimation { self.selectedID = lastSession }
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.id(selectedID)
|
||||
.transition(.opacity)
|
||||
} else {
|
||||
if let handler {
|
||||
ShellView(
|
||||
handler: handler,
|
||||
hostsManager: hostsManager
|
||||
)
|
||||
.onAppear {
|
||||
if selectedID == nil {
|
||||
selectedID = handler.sessionID
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("No Session")
|
||||
}
|
||||
// }
|
||||
// }
|
||||
.onAppear {
|
||||
UIApplication.shared.isIdleTimerDisabled = hostsManager.settings.caffeinate
|
||||
if hostsManager.settings.locationPersist {
|
||||
Backgrounder.shared.startBgTracking()
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
UIApplication.shared.isIdleTimerDisabled = false
|
||||
if container.sessions.isEmpty {
|
||||
Backgrounder.shared.stopBgTracking()
|
||||
}
|
||||
}
|
||||
.id(selectedID)
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,6 +207,7 @@ struct ShellTabView: View {
|
||||
#Preview {
|
||||
ShellTabView(
|
||||
handler: SSHHandler(host: Host.blank, keyManager: nil),
|
||||
hostsManager: HostsManager()
|
||||
hostsManager: HostsManager(),
|
||||
keyManager: KeyManager()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AudioToolbox
|
||||
|
||||
struct ShellView: View {
|
||||
@ObservedObject var handler: SSHHandler
|
||||
@ObservedObject var hostsManager: HostsManager
|
||||
|
||||
@ObservedObject var container = TerminalViewContainer.shared
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@@ -20,16 +20,89 @@ struct ShellView: View {
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor
|
||||
.ignoresSafeArea(.all)
|
||||
TerminalController(handler: handler, hostsManager: hostsManager)
|
||||
|
||||
ZStack(alignment: .topLeading) {
|
||||
TerminalController(handler: handler, hostsManager: hostsManager)
|
||||
.brightness(hostsManager.settings.filter == .crt ? 0.2 : 0.0)
|
||||
|
||||
if #available(iOS 17, *), hostsManager.settings.filter == .crt {
|
||||
CRTView()
|
||||
.opacity(0.75)
|
||||
.brightness(-0.2)
|
||||
.blendMode(.overlay)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
withAnimation { handler.forceDismissDisconnectedAlert = false }
|
||||
}
|
||||
|
||||
Group {
|
||||
Color.gray.opacity(0.2)
|
||||
.transition(.opacity)
|
||||
Image(systemName: "bell.fill")
|
||||
.foregroundStyle(
|
||||
hostsManager.selectedTheme.background.luminance > 0.5 ?
|
||||
.black : .white
|
||||
)
|
||||
.font(.largeTitle)
|
||||
.shadow(color: .black, radius: 5)
|
||||
}
|
||||
.opacity(handler.bell ? 1 : 0)
|
||||
.onChange(of: handler.bell) { _ in
|
||||
guard handler.bell else { return }
|
||||
if hostsManager.settings.bellHaptic {
|
||||
Haptic.warning.trigger()
|
||||
}
|
||||
if hostsManager.settings.bellSound {
|
||||
AudioServicesPlaySystemSound(1103)
|
||||
}
|
||||
}
|
||||
|
||||
if handler.state != .shellOpen && !handler.forceDismissDisconnectedAlert {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 25)
|
||||
.fill(hostsManager.selectedTheme.foreground.suiColor)
|
||||
.opacity(0.5)
|
||||
.blur(radius: 2)
|
||||
.shadow(color: hostsManager.selectedTheme.foreground.suiColor, radius: 5)
|
||||
VStack {
|
||||
HStack {
|
||||
Image(systemName: "wifi.exclamationmark")
|
||||
.resizable().scaledToFit()
|
||||
.foregroundStyle(hostsManager.selectedTheme.background.suiColor)
|
||||
.frame(width: 30)
|
||||
Text("Disconnected")
|
||||
.foregroundStyle(hostsManager.selectedTheme.background.suiColor)
|
||||
.font(.title)
|
||||
}
|
||||
Button {
|
||||
try! handler.reconnect()
|
||||
} label: {
|
||||
Text("Connect")
|
||||
.foregroundStyle(hostsManager.selectedTheme.background.suiColor)
|
||||
.padding(5)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(.tint)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 15))
|
||||
}
|
||||
Button {
|
||||
withAnimation { handler.forceDismissDisconnectedAlert = true }
|
||||
} label: {
|
||||
Text("Cancel")
|
||||
.foregroundStyle(hostsManager.selectedTheme.background.suiColor)
|
||||
.padding(5)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(.tint.opacity(0.5))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 15))
|
||||
}
|
||||
}
|
||||
.padding(10)
|
||||
}
|
||||
.fixedSize()
|
||||
.transition(.opacity)
|
||||
.animation(.spring, value: checkShell(handler.state))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ struct ThemeButton: View {
|
||||
@Binding var theme: Theme
|
||||
@State var canModify: Bool
|
||||
|
||||
@Binding var themeToEdit: Theme
|
||||
@Binding var showThemeEditor: Bool
|
||||
|
||||
@State private var showRenameAlert: Bool = false
|
||||
@State private var rename: String = ""
|
||||
|
||||
@@ -79,8 +82,9 @@ struct ThemeButton: View {
|
||||
.clipShape(RoundedRectangle(cornerRadius: outerR))
|
||||
.contextMenu {
|
||||
if canModify {
|
||||
NavigationLink {
|
||||
ThemeEditorView(hostsManager: hostsManager, theme: $theme)
|
||||
Button {
|
||||
themeToEdit = theme
|
||||
showThemeEditor = true
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
@@ -122,7 +126,9 @@ struct ThemeButton: View {
|
||||
ThemeButton(
|
||||
hostsManager: HostsManager(),
|
||||
theme: .constant(Theme.defaultTheme),
|
||||
canModify: true
|
||||
canModify: true,
|
||||
themeToEdit: .constant(Theme.defaultTheme),
|
||||
showThemeEditor: .constant(false)
|
||||
)
|
||||
.border(Color.red)
|
||||
}
|
||||
|
||||
@@ -17,18 +17,38 @@ struct ThemeEditorView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||
hostsManager.selectedTheme.background.suiColor.opacity(0.5)
|
||||
.ignoresSafeArea(.all)
|
||||
NavigationStack {
|
||||
ThemeButton(hostsManager: hostsManager, theme: $theme, canModify: false)
|
||||
.id(theme)
|
||||
.padding(.bottom)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.onChange(of: theme) { _ in
|
||||
print(theme)
|
||||
}
|
||||
// ZStack {
|
||||
// RoundedRectangle(cornerRadius: 20)
|
||||
// .foregroundStyle(theme.background.suiColor)
|
||||
// VStack {
|
||||
// Text(theme.name)
|
||||
// .foregroundStyle(theme.foreground.suiColor)
|
||||
// .font(.headline)
|
||||
// .lineLimit(1)
|
||||
// Spacer()
|
||||
// VStack(spacing: 0) {
|
||||
// ForEach(0...1, id: \.self) { row in
|
||||
// HStack(spacing: 0) {
|
||||
// let range = row == 0 ? 0..<8 : 8..<16
|
||||
// ForEach(range, id: \.self) { col in
|
||||
// Rectangle()
|
||||
// .aspectRatio(1, contentMode: .fit)
|
||||
// .foregroundStyle(theme.ansi[col].suiColor)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
// }
|
||||
// .padding(10)
|
||||
// }
|
||||
// .fixedSize(horizontal: false, vertical: true)
|
||||
// .padding(.horizontal)
|
||||
|
||||
List {
|
||||
List {
|
||||
Section("Main Colors") {
|
||||
ColorPicker("Text", selection: $theme.foreground.suiColor, supportsOpacity: false)
|
||||
ColorPicker("Background", selection: $theme.background.suiColor, supportsOpacity: false)
|
||||
@@ -53,6 +73,7 @@ struct ThemeEditorView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.navigationTitle("Edit Theme")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
||||
@@ -79,7 +79,13 @@ struct ThemeManagerView: View {
|
||||
} else {
|
||||
LazyVGrid(columns: layout, alignment: .center, spacing: 8) {
|
||||
ForEach($hostsManager.themes) { $theme in
|
||||
ThemeButton(hostsManager: hostsManager, theme: $theme, canModify: true)
|
||||
ThemeButton(
|
||||
hostsManager: hostsManager,
|
||||
theme: $theme,
|
||||
canModify: true,
|
||||
themeToEdit: $newTheme,
|
||||
showThemeEditor: $showNewThemeEditor
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
@@ -95,7 +101,13 @@ struct ThemeManagerView: View {
|
||||
}
|
||||
LazyVGrid(columns: layout, alignment: .center, spacing: 8) {
|
||||
ForEach(Theme.builtinThemes) { theme in
|
||||
ThemeButton(hostsManager: hostsManager, theme: .constant(theme), canModify: false)
|
||||
ThemeButton(
|
||||
hostsManager: hostsManager,
|
||||
theme: .constant(theme),
|
||||
canModify: false,
|
||||
themeToEdit: .constant(Theme.defaultTheme),
|
||||
showThemeEditor: .constant(false)
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
@@ -115,31 +127,37 @@ struct ThemeManagerView: View {
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem() {
|
||||
ToolbarItemGroup {
|
||||
Button() {
|
||||
UIApplication.shared.open(URL(string: "https://iterm2colorschemes.com")!)
|
||||
} label: {
|
||||
Label("Open themes site", systemImage: "safari")
|
||||
Label("Browse", systemImage: "safari")
|
||||
}
|
||||
}
|
||||
ToolbarItem() {
|
||||
Button() {
|
||||
showAlert.toggle()
|
||||
} label: {
|
||||
Label("From URL", systemImage: "link")
|
||||
Label("URL", systemImage: "link")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if #available(iOS 19, *) {
|
||||
ToolbarSpacer()
|
||||
}
|
||||
|
||||
ToolbarItem() {
|
||||
Button() {
|
||||
newTheme = Theme.defaultTheme
|
||||
let createdTheme = Theme.newTheme
|
||||
hostsManager.themes.append(createdTheme)
|
||||
newTheme = createdTheme
|
||||
showNewThemeEditor = true
|
||||
} label: {
|
||||
Label("New", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationDestination(isPresented: $showNewThemeEditor) {
|
||||
.sheet(isPresented: $showNewThemeEditor) {
|
||||
hostsManager.updateTheme(newTheme)
|
||||
} content: {
|
||||
ThemeEditorView(hostsManager: hostsManager, theme: $newTheme)
|
||||
}
|
||||
}
|
||||
|
||||
1
SwiftTerm
Submodule
@@ -8,7 +8,10 @@
|
||||
|
||||
#working dir starts at /Volumes/workspace/repository/ci_scripts
|
||||
cd ..
|
||||
git submodule init
|
||||
rm -rf SwiftTerm
|
||||
git clone https://github.com/neon443/SwiftTerm -b jelly
|
||||
mkdir Frameworks; cd Frameworks
|
||||
curl -o frameworks.tar.xz https://files.catbox.moe/hd2s6n.xz
|
||||
curl -o frameworks.tar.xz https://files.catbox.moe/f4pe0u.xz
|
||||
tar xzf frameworks.tar.xz
|
||||
rm frameworks.tar.xz
|
||||
|
||||