135 Commits
v1.5 ... 1.8

Author SHA1 Message Date
Nihaal Sharma
b9d65002bc Update README.md 2025-11-07 12:47:24 +00:00
neon443
d4b778b458 fix closing a session crash
remove theme preview thingy cos it doesnt work 😭
padding on aboutview
2025-11-07 11:12:41 +00:00
neon443
17d0a5a7f4 maybe fixed the cursor disappearing on uniminimise 2025-11-07 10:28:52 +00:00
neon443
db899fbb0f fix the key, hostskeys, snippet views being black sometimes 2025-11-07 10:23:14 +00:00
neon443
750bf6ee62 slight tweaks 2025-11-07 10:19:39 +00:00
neon443
64f1007da3 fixed themeEditor preview not updating
static var newTheme to create a new one
theme editing shows as a sheet to fix navigation bugs
mutating set on colorcodable idk if it helped
2025-11-07 10:15:40 +00:00
neon443
cbe1fbad11 rname icon 2025-09-27 17:41:31 +01:00
neon443
a9a408730e updated privacy formatting 2025-09-26 11:35:54 +01:00
neon443
49564727fc updated privacy to discalim data loss 2025-09-26 11:29:09 +01:00
neon443
a4afa8ef3b s 2025-09-26 11:22:52 +01:00
neon443
0b039f2c42 xros hehe 2025-09-26 10:53:21 +01:00
neon443
1cc9eb7b6f Merge remote-tracking branch 'refs/remotes/origin/main' 2025-09-24 22:22:52 +01:00
neon443
3b6e5d4a73 small upds 2025-09-24 22:22:33 +01:00
Nihaal Sharma
c4ba38c3bf add privacy 2025-09-24 12:36:19 +01:00
neon443
5132b4f69c glass effect compat 2025-09-24 12:29:21 +01:00
neon443
37e2165725 minor update 2025-09-23 11:23:33 +01:00
neon443
528f04d56b Merge remote-tracking branch 'refs/remotes/origin/main' 2025-09-23 10:42:50 +01:00
neon443
6462943636 update openssl.xcframework, recompiled with iphoneos26.0sdk
update icon
2025-09-23 10:14:49 +01:00
neon443
cac4e9ec37 update openssl.xcframework, recompiled with iphoneos26.0sdk
update icon
2025-09-23 10:11:08 +01:00
neon443
896159af31 fix openssl having an incorrect cfbundleshortversionstring 2025-09-20 01:34:56 +05:30
neon443
606945e4e6 okay updated the catbox link with a 5mb (40mb) tar.xz by removing openssl and adding openssl as a swift package
also this should fix crashing when it cant find openssl dylib
2025-09-20 01:01:23 +05:30
neon443
2ff111ae6a hmm maybe i didnt need to embed openssl?? 2025-09-20 00:44:00 +05:30
neon443
f4498b95da add liquid glass icon 2025-09-17 18:06:42 +05:30
neon443
5b31db14e7 fix corner radius on hostsymbolpickerview 2025-09-10 21:24:46 +01:00
neon443
e5627a2aaa Merge remote-tracking branch 'refs/remotes/origin/main' 2025-09-10 21:10:57 +01:00
neon443
a907480d1e solarium complete
idr most of ts
cleaned up stuff
remove glassbutton
2025-09-10 21:10:51 +01:00
neon443
7b127127e7 solarium complete
idr most of ts
cleaned up stuff
remove glassbutton
2025-09-10 21:09:28 +01:00
neon443
0b83009b39 working onsolarium support
new ios 26 button for keyimporter view (currently kaput)
added glassbutton viewmodifier
added glassbutton view might remove
cleaned up wording and stuff in settingsview
fix extra spacing on font size slider labels
centre aligned about view vs left align
toolbar grouping
2025-09-10 20:54:43 +01:00
neon443
d26d564fda updateSwiftTerm 2025-09-07 20:14:39 +01:00
neon443
d1408ee5d5 fix crash when opensshpubkey cant be made
fix expand button not enabled sometimes
2025-09-06 16:51:33 +01:00
neon443
97ef265cd2 fix_ci?? 2025-09-05 21:22:44 +01:00
neon443
623b2a001a updated sliders to show more info like what it does 💀
and minium/max and what it curretnly is
2025-09-05 16:53:57 +01:00
neon443
6d3b7f2f64 Add settings 2025-09-04 21:02:41 +01:00
neon443
d39f176f72 fix crash 2025-09-03 20:50:19 +01:00
neon443
2331c55abf Merge remote-tracking branch 'refs/remotes/origin/main' 2025-09-03 20:48:47 +01:00
neon443
7e0a02fad3 redid logic on the shellview selection
fixed the colours being unreadable sometimes on title bar of the shellview
luminance ratio func
forcedismissdisconnectedalert is now in handler
2025-09-03 20:39:55 +01:00
neon443
02d8757547 redid logic on the shellview selection
fixed the colours being unreadable sometimes on title bar of the shellview
luminance ratio func
forcedismissdisconnectedalert is now in handler
2025-09-03 20:33:19 +01:00
neon443
a71c994103 mini cleanup + switch to new CursorAnimations 2025-09-02 18:48:32 +01:00
neon443
01413df52f update submodule 2025-09-02 16:48:40 +01:00
neon443
26064e15d4 add neon443/swiftterm as a submodule 2025-09-02 16:43:32 +01:00
neon443
b981cc3f54 add support for cursor text colors
fix no tabs being selected when opening a 2nd session 💀
2025-09-01 22:04:23 +01:00
neon443
8cc1340f3f lol workaround for cursor disappearing when unminimizing: set the cursor style to the current style 2025-09-01 21:43:34 +01:00
neon443
0cd92ee12a using a strikethrough for disconnected sessions
add cancel to disconnected alert
stop jumping to last session if disconnected
re-add to TerminalViewContainer if reconnect happens
move ssh cleanup stuff to cleanup func, only run if we indicate we want a new session
2025-09-01 21:40:01 +01:00
neon443
68fb7d4844 Added support for reconnecting to a server, using the same terminal
added reconnect() to sshhandler
added support to go() and connect() to use an arbritrary sessionID
redid readloop in sshterminaldelegate to use a timer, instead of a while loop in a Task, allowsfor one readloop per terminal instead
updated the ui for the disconnected alert
increased max read size from 1024 to
added reconnecterror
updated tracker to remove print statements and exit start/stop tracking funcs early if tracking/not tracking
2025-09-01 18:49:48 +01:00
neon443
ff02122bcc added a disconnected info thingy, with a reconnect button that umm kinda doesnt let u use the terminal 💀
fix runtime errors when calling handler.disconnect() from bg threads
2025-09-01 18:18:48 +01:00
neon443
7c28cc79da added full expand and full close buttons, with new symbols that support ios 13+
added historylimitdisplay computed property
made disconnect synchronous
2025-09-01 17:46:43 +01:00
neon443
efb6072af8 fix crtview making it look gray 2025-09-01 17:21:47 +01:00
neon443
005b1ed9c9 fix 2025-09-01 17:17:38 +01:00
neon443
4cd74c2633 FINALLY FIXED iOS 17 AND BELOW 2025-09-01 16:11:59 +01:00
neon443
4846831140 code cleanup 2025-09-01 14:42:22 +01:00
neon443
bcd52143cb improve ios 16 ux:
fix missing symbol in onboarding
fix settings empty headers
2025-09-01 12:01:29 +01:00
neon443
76a6d9d2f2 add ios 17 warnings 2025-08-31 22:44:56 +01:00
neon443
262411049f re add ios 16 support
shaders only show if 17+
blurReplace transitions are opacity with a white shadow for a short time
2025-08-31 22:42:43 +01:00
neon443
28b92466a5 remove overlay jelly cursor implementation, check jelly branch on https://github.com/neon443/SwiftTerm
add empty handler for 133 iterm2
2025-08-31 22:23:17 +01:00
neon443
1f687f0b62 fully working v1 of jelly cursor
i plan to redo this cos its an overlay and not actually there, so it kinda breaks the illusion if scrolling
2025-08-30 09:48:54 +01:00
neon443
09a6e5a029 hardcoded a wip jellycursor 2025-08-29 23:38:31 +01:00
neon443
dc01156d9c remove geometryreader 2025-08-28 21:58:54 +01:00
neon443
18046d1208 implemented shader testing view (shaderplayground) 2025-08-28 21:00:49 +01:00
neon443
cd822e1efc reimplemented onboarding via hostsManager.shownOnboarding 2025-08-28 20:35:02 +01:00
neon443
02131e798c implement onboarding
update welcomeview to fit everything on screen
2025-08-28 20:09:57 +01:00
neon443
ffc62b3f34 finished welcome view 2025-08-28 19:46:17 +01:00
neon443
cf96b0e505 working on onboarding
got animations and stuff on the onboarding
2025-08-27 20:32:52 +01:00
neon443
9cab6baea4 startedOnOnboarding 2025-08-27 18:51:41 +01:00
neon443
97aba7f818 i like how its turned out 2025-08-27 15:12:48 +01:00
neon443
6038b99b60 AAGHDHDHDHDHFhioheoihwioe 2025-08-27 15:11:06 +01:00
neon443
ed67afcf39 skdjh 2025-08-27 14:36:05 +01:00
neon443
cbf67be9cc okay now brightened up the terminal, but fucking scanlines are grey ahh
also removed the scanwave its barely visible anyway
2025-08-27 12:10:41 +01:00
neon443
6836854972 made the scanlines stronger and the terminal brighter
removed scanwave its barely visible anyway
2025-08-27 11:58:50 +01:00
neon443
7fb3ad93fb ajskdlhjaksdgiuaheuighgaxscgfasdgfasdfgs 2025-08-27 11:50:16 +01:00
neon443
62debc38fb changed how the crt effect and terminal blend, now the colors arent washed out 2025-08-27 11:21:46 +01:00
neon443
2da7667ee5 updated the shader 2025-08-26 20:05:53 +01:00
neon443
a80ff66f6e update the entite like crt shader it acc works and imrpoved blending of
the orverlay
its a lil dim but oh well
moved learning shader stuff to ShaderTestggvirw maybe integrate it
later?
2025-08-26 17:44:06 +01:00
neon443
a5c6173951 tweak 2025-08-26 16:14:24 +01:00
neon443
e24ef96ccb got it working by overlaying another view on top, still need to fix the brightness issue tho 2025-08-26 16:04:07 +01:00
neon443
f85ef3deaf apply to terimal 2025-08-25 21:32:53 +01:00
neon443
cc028321db okay tweak stuff 2025-08-25 21:24:42 +01:00
neon443
b0a20c74cc bro adding zero in 2 places 💀 2025-08-25 21:22:32 +01:00
neon443
3f7f253f63 okay actually got scanlines
but fucked it over when applying it to the app
2025-08-25 21:04:45 +01:00
neon443
9e95f63494 i think i fucked the sinebow 2025-08-25 19:57:37 +01:00
neon443
99de1e12ef sinebow yay 2025-08-25 19:22:53 +01:00
neon443
38ce138cea idk what im doing 😭 2025-08-25 17:05:14 +01:00
neon443
0a0d9cea9a more shaders 2025-08-25 16:56:33 +01:00
neon443
a1d8edfb59 more shaders 2025-08-25 15:53:14 +01:00
neon443
32861b7292 okay finally got fucking shaders working 2025-08-25 15:02:35 +01:00
neon443
8ae9cc8ead shaderstuff 2025-08-25 14:12:03 +01:00
neon443
4f92d34d1a simplified the cursor preview view 2025-08-25 11:03:39 +01:00
neon443
f2d034b732 added haptics and sounds for the bell
its the warning haptic and the Tink.caf 1103 system sound
added warning haptic
2025-08-24 21:44:35 +01:00
neon443
d4f31fda32 okay i got it to work
had to enable bg modes and stuff
and made it call on appear of the shell and not the tab strip 💀
2025-08-24 21:00:59 +01:00
neon443
a7783eab47 added backgrounder.swift
more stuff
and stuff

tried to add background location tracking
 - it didnt work
2025-08-24 20:38:12 +01:00
neon443
d40ef8e03c add idle timer disablied thingy 2025-08-24 19:03:00 +01:00
neon443
f8951516e0 delete miniterminal 2025-08-24 17:02:31 +01:00
neon443
26ca95410b make the fade in faster
fix the cursor just disappearing if u set it to steady as its fading
2025-08-24 16:54:38 +01:00
neon443
3ac6b8dde8 update animation 2025-08-24 16:43:39 +01:00
neon443
bebf08d085 ok scrap miniterminal i did some custom ui to represent cursors 2025-08-24 16:36:21 +01:00
neon443
18e5dbe9bf i tried so hard 😭 2025-08-24 15:29:49 +01:00
neon443
d1dd77fde3 added miniterminalcontroller
fix miniterminaldelegate
moved stuff around
2025-08-24 12:09:43 +01:00
neon443
7b4da73ffa working on a miniterminalview :yay:
for stuff like static previews of how shit should look
 - eg cursor style previews
 - terminal effect shader previews
2025-08-23 17:14:28 +01:00
neon443
a5563731f0 fix about view icon 2025-08-23 13:25:02 +01:00
neon443
a454723727 add apply cursor type
make it focus the terminal when spawning
fix step in the scrollback slider
2025-08-23 00:20:26 +01:00
neon443
657bd33eef added cursor type stuff
updated picker
2025-08-22 23:50:04 +01:00
neon443
9b1b04d755 updated swiftterm i think
fix scrollback preferences not being set
2025-08-22 19:39:53 +01:00
neon443
6308ef1c2f added a scrollbiew for icons and updated the label icon for haptics 2025-08-22 15:46:17 +01:00
neon443
2030c48ab3 updated the icon picker ui to look cleaner
updated logic for previews hostsmanager
2025-08-22 14:03:10 +01:00
neon443
3e45de2d32 added changing app icons
added setappicon function
2025-08-22 12:46:17 +01:00
neon443
b651c03364 okay more icon stuff 2025-08-21 20:10:21 +01:00
neon443
e9ddf0c13e update asset catalog 2025-08-21 19:26:53 +01:00
neon443
518946ace1 COOKED on this new icon 2025-08-21 19:23:26 +01:00
neon443
56ebf6c55d asdfg 2025-08-21 17:20:13 +01:00
neon443
29a9d2b188 updated the blueprint icon 2025-08-20 20:33:03 +01:00
neon443
590c101db8 redid icon picker ui
added applyscrollbacklenght with fallbacks
added blueprint icon variant
2025-08-20 19:57:23 +01:00
neon443
8fd10bb291 save settings on modify settings
updated icon picker
2025-08-20 17:16:32 +01:00
neon443
0b16e15044 updated the ui for the settings, made it actually link to the settings
add a load/savesettings function
2025-08-20 16:32:24 +01:00
neon443
2338d0d108 add dummy settings ui for everything 2025-08-19 19:33:54 +01:00
neon443
78bdb60350 add settingsview
add setting struct
add export/importhosts fumction
2025-08-19 16:48:36 +01:00
neon443
171843c9a3 vbump 2025-08-18 15:58:27 +01:00
neon443
f9bf633616 updated addtohistory to fix stuff not adding or incrementing
remove format history
2025-08-18 15:58:04 +01:00
neon443
c0d45cbd2a fix duplicated history 2025-08-18 15:46:57 +01:00
neon443
e8ad06b429 fix crash when deleting a recent
make history ordered correctly
2025-08-18 15:36:15 +01:00
neon443
ca4a114c93 vbump 2025-08-17 20:04:08 +01:00
neon443
6c452b3961 change animation on the recents expander/collapser
fontmanager respecs theme bg
move the colorscheme thing back
2025-08-17 19:57:18 +01:00
neon443
95fd3dfe07 updated ui for recents when fully collapsed: instead of 0/10 its 10 items 2025-08-17 18:35:45 +01:00
neon443
a6e59eec2c add dup button to long press 2025-08-17 18:26:25 +01:00
neon443
ce11f18455 fix teststring textbox in fontsmanager 2025-08-17 17:47:25 +01:00
neon443
4f9055b58f added hostpreview to preview hosts
added a thingy in hostsmanager to load test data if inited with previews = true
added a context menu to hosts with a nice preview
2025-08-17 14:14:55 +01:00
neon443
56c5634d9a ok fix stupid things in recents:
- crash when expanding recents but theres only one more
 - ui improvements make the down arrow grayed out if its all the way down
 - up arrow disabled if fully collapsed
 - something else i forgot
2025-08-16 22:24:50 +01:00
neon443
587c85842f update shelltabview - remove geometryreader and make the smallest tab size larger
update keyimporterview to make the button not look odd
fix the textbox having illegible text
fix hostsymbol picker looking shit in light mode
2025-08-16 11:25:18 +01:00
neon443
700c169e55 remove hiding hte label if its empty 2025-08-16 10:31:37 +01:00
neon443
d7f498c164 added collapsible recents
added last connect date
added more and collapse button
added a thingy that says how many are displayed and the total
updated funcs
2025-08-15 19:54:25 +01:00
neon443
4eb9cbb842 add addtohistory function
update formathistory
made history have an array of history
made ts codabel
2025-08-15 13:16:41 +01:00
neon443
3e713b8561 added history view
added history loading and saving functions
added a thingy that will combine multiple entries like the phone app
added history data struct
fix hosts with no name just bneing called " copy" when duplicated
remove the showterminal button
fix crash when closing the terminal
2025-08-15 13:05:57 +01:00
neon443
d32356eaf6 tiny change 2025-08-14 11:31:30 +01:00
neon443
c625b53195 added a menu to copy scrollback, need to strip the excape sequances next
made the snippet adder button always show
2025-08-08 15:06:20 +01:00
neon443
bee12c8a39 added startup snippets - run a snippet on conect 2025-08-08 13:55:58 +01:00
neon443
693ef91fb8 very wip auth with kbint (cant actually do anything cos i cba to make a server with kbint auth) 2025-08-08 09:51:53 +01:00
neon443
100ffb0349 snippets can only be added if both name and content arent empty
hosts only get added if they have been modified
updated the popover to have concentric corner radiuses for the selection thingy
added navigation title shhshell
2025-08-08 08:46:28 +01:00
80 changed files with 2350 additions and 512 deletions

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "SwiftTerm"]
path = SwiftTerm
url = https://github.com/neon443/SwiftTerm
branch = jelly

View File

@@ -5,8 +5,8 @@
// Created by neon443 on 06/06/2025.
//
VERSION = 1.6
BUILD = 57
VERSION = 1.10
BUILD = 186
// 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
View 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.

View File

@@ -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

View 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
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 KiB

View File

@@ -0,0 +1,12 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"symbols" : [
{
"filename" : "apple.terminal.on.rectangle.fill.svg",
"idiom" : "universal"
}
]
}

View File

@@ -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

View File

@@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "ShhShell.png",
"filename" : "ShhShell 1.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"

View File

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View File

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -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
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 KiB

View 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
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View 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
View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -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,10 @@
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_NSLocationAlwaysAndWhenInUseUsageDescription = "Required to keep SSH connections alive, if enabled";
INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "Required to keep SSH connections alive, if enabled";
INFOPLIST_KEY_NSLocationUsageDescription = "Required to keep SSH connections alive, if enabled";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Required to keep SSH connections alive, if enabled";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -1020,20 +1110,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 +1136,10 @@
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_NSLocationAlwaysAndWhenInUseUsageDescription = "Required to keep SSH connections alive, if enabled";
INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "Required to keep SSH connections alive, if enabled";
INFOPLIST_KEY_NSLocationUsageDescription = "Required to keep SSH connections alive, if enabled";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Required to keep SSH connections alive, if enabled";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -1058,12 +1154,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 +1276,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 */

View File

@@ -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" : "309a47b2b1d9b5e991f36961c983ecec72275be3",
"version" : "1.6.1"
}
},
{
"identity" : "swift-subprocess",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-subprocess",
"state" : {
"branch" : "main",
"revision" : "18b1ad0b8af10c4cfac5da599edbe1c67af95413"
"revision" : "680621bba49ca5c6b1df8d1693684dfd83351af4"
}
},
{
"identity" : "swift-system",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-system",
"state" : {
"revision" : "61e4ca4b81b9e09e2ec863b00c340eb13497dac6",
"version" : "1.5.0"
}
}
],

View 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
}

View File

@@ -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",

View File

@@ -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) }

View File

@@ -22,5 +22,9 @@
<string>JetBrainsMonoNerdFontMono-Italic.ttf</string>
<string>JetBrainsMonoNerdFontMono-BoldItalic.ttf</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
</dict>
</plist>

View File

@@ -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 {

View 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")
}
}

View File

@@ -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")

View File

@@ -25,3 +25,7 @@ enum KeyError: Error {
case pubkeyRejected
case privkeyRejected
}
enum ReconnectError: Error {
case alreadyConnected
}

View File

@@ -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)))")

View File

@@ -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:

View File

@@ -0,0 +1,105 @@
//
// 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"
}
}
}

View File

@@ -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)
}
}
}

View 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;
}

View File

@@ -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 color
}
// TODO: selectedtext and cursor colors
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
}

View 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()
}

View File

@@ -39,7 +39,6 @@ struct TerminalController: UIViewRepresentable {
)
}
}
return tv
}

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -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 {

View File

@@ -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)
}
}
}

View File

@@ -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

View File

@@ -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())
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)
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()
}
}
.padding(.horizontal)
Slider(value: $hostsManager.fontSize, in: 1...20, step: 1) {
} minimumValueLabel: {
Label("", systemImage: "textformat.size.smaller")
} maximumValueLabel: {
Label("", systemImage: "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)
}
}

View File

@@ -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
)
}
}
}

View 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
)
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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()
)
}

View 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"
}

View File

@@ -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())
}

View File

@@ -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()
}
}

View File

@@ -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")
}
}
}

View File

@@ -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

View 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."
)
}

View 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))
}

View File

@@ -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
)
}
}
}

View File

@@ -0,0 +1,237 @@
//
// 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("Terminal") {
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
)
}
}
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()
}
}
Toggle("Keep Display Awake", systemImage: "cup.and.saucer.fill", isOn: $hostsManager.settings.caffeinate)
}
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("Terminal Filter") {
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("App Icon") {
ScrollView(.horizontal) {
HStack {
ForEach(AppIcon.allCases, id: \.self) { icon in
let isSelected = hostsManager.settings.appIcon == icon
ZStack(alignment: .top) {
RoundedRectangle(cornerRadius: 21.5)
.foregroundStyle(.gray.opacity(0.5))
.opacity(isSelected ? 1 : 0)
VStack(spacing: 0) {
icon.image
.resizable().scaledToFit()
.clipShape(RoundedRectangle(cornerRadius: 16.5))
.padding(5)
Text(icon.description).tag(icon)
.font(.caption)
.padding(.bottom, 5)
.padding(.horizontal, 5)
.multilineTextAlignment(.center)
}
}
.frame(maxWidth: 85, maxHeight: 110)
.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()
)
}

View File

@@ -54,6 +54,7 @@ struct AddSnippetView: View {
} label: {
Label("Add", systemImage: "plus")
}
.disabled(name.isEmpty || content.isEmpty)
}
}
}

View File

@@ -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())
}

View File

@@ -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)

View 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()
}

View File

@@ -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 }
}
}
}
}
.frame(height: 30)
}
//the acc terminal lol
if let selectedID,
let session = container.sessions[selectedID] {
//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()
)
}

View File

@@ -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))
}
}
}
}

View File

@@ -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)
}

View File

@@ -17,16 +17,36 @@ 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 {
Section("Main Colors") {
@@ -53,6 +73,7 @@ struct ThemeEditorView: View {
}
}
}
.scrollContentBackground(.hidden)
.navigationTitle("Edit Theme")
.navigationBarTitleDisplayMode(.inline)
.toolbar {

View File

@@ -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

Submodule SwiftTerm added at cdace6f038

View File

@@ -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