Skip to content

Commit 0948b01

Browse files
committed
feat(iSH): implement iSH integration with terminal session management and output handling
Signed-off-by: 7HR4IZ3 <90985774+7HR4IZ3@users.noreply.github.com>
1 parent 77404c5 commit 0948b01

13 files changed

Lines changed: 911 additions & 100 deletions

File tree

config.xml

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<allow-intent href="mailto:*" />
2222
<allow-intent href="geo:*" />
2323

24-
<platform name="android">
24+
<platform name="android">
2525
<allow-intent href="market:*" />
2626
<preference name="fullscreen" value="false"/>
2727
<preference name="SplashScreen" value="none"/>
@@ -72,7 +72,24 @@
7272
</config-file>
7373

7474
<hook type="before_prepare" src="hooks/modify-java-files.js" />
75-
<hook type="after_prepare" src="hooks/post-process.js" />
76-
</platform>
77-
<preference name="AndroidBlacklistSecureSocketProtocols" value="SSLv3,TLSv1" />
78-
</widget>
75+
<hook type="after_prepare" src="hooks/post-process.js" />
76+
</platform>
77+
<platform name="ios">
78+
<edit-config file="*-Info.plist" mode="merge" target="NSCameraUsageDescription">
79+
<string>Camera access is needed for capturing media.</string>
80+
</edit-config>
81+
<edit-config file="*-Info.plist" mode="merge" target="NSMicrophoneUsageDescription">
82+
<string>Microphone access is needed for capturing audio.</string>
83+
</edit-config>
84+
<edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryUsageDescription">
85+
<string>Photo library access is needed to select files.</string>
86+
</edit-config>
87+
<edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryAddUsageDescription">
88+
<string>Photo library access is needed to save files.</string>
89+
</edit-config>
90+
<edit-config file="*-Info.plist" mode="merge" target="NSLocalNetworkUsageDescription">
91+
<string>Local network access is needed for device-to-device connections.</string>
92+
</edit-config>
93+
</platform>
94+
<preference name="AndroidBlacklistSecureSocketProtocols" value="SSLv3,TLSv1" />
95+
</widget>

docs/ISH_INTEGRATION.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# iSH Integration Plan
2+
3+
Goal: provide an iSH-style x86 Alpine environment on iOS and bridge it to the existing Terminal/Executor JS API.
4+
5+
## Current status
6+
7+
- iSH is vendored in `third_party/ish`.
8+
- `IshBridge` and `Executor` are wired to iSH headers when available.
9+
- Output streaming is hooked by swizzling `Terminal sendOutput:length:` and forwarding to Cordova callbacks.
10+
- Rootfs bootstrap copies `ish-rootfs` from app bundle to `Documents/ish-rootfs` if present.
11+
- Rootfs path is provided via `DefaultRootPath()` (weak symbol in `IshRootfs.c`).
12+
13+
## Key iSH entry points
14+
15+
- `third_party/ish/app/LinuxInterop.h`
16+
- `linux_start_session(...)`
17+
- `actuate_kernel(...)`
18+
- `third_party/ish/app/LinuxPTY.c`
19+
- Output is written via `Terminal_sendOutput_length(...)`
20+
- `third_party/ish/app/Terminal.m`
21+
- `sendOutput:length:` is called for PTY output
22+
- `sendInput:` sends keyboard input
23+
24+
## Integration steps
25+
26+
1) Kernel + rootfs bootstrap
27+
- Import or unpack Alpine rootfs into app sandbox.
28+
- Ensure `/bin/sh` exists in the iSH root.
29+
- Call `actuate_kernel("")` once (see `IshBridge startKernelIfNeeded`).
30+
31+
2) Start sessions
32+
- `IshBridge startWithCommand:` calls `linux_start_session` with `/bin/sh -lc <command>`.
33+
- Session id is the `Terminal` UUID.
34+
35+
3) I/O bridge
36+
- Output: `Terminal sendOutput:length:` is swizzled to call `IshBridge` event handler.
37+
- Input: `Terminal sendInput:` called from `IshBridge writeToSession:`.
38+
39+
4) Cordova glue
40+
- `Executor.start` returns session id and keeps callback for streaming.
41+
- `Executor.write` sends input to iSH.
42+
- `Executor.stop` destroys the terminal.
43+
44+
## Build notes
45+
46+
- iSH has its own Xcode project (`third_party/ish/iSH.xcodeproj`).
47+
- For Cordova, you’ll need to either:
48+
- Build iSH as a static library and link it into the Cordova iOS app, or
49+
- Add required iSH sources to the Cordova build (large change).
50+
Scripts:
51+
- `scripts/build-ish.sh` for building iSH (macOS + Xcode).
52+
- `scripts/sync-ish-headers.sh` for syncing required headers.
53+
54+
## Next TODOs
55+
56+
- Bundle Alpine rootfs under `ish-rootfs` and validate `/bin/sh`.
57+
- Link iSH static lib into Cordova build.
58+
- Add exit event callbacks when sessions terminate.

scripts/fetch-ish.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
DEST="${1:-third_party/ish}"
5+
6+
if [ -e "$DEST" ]; then
7+
echo "Destination already exists: $DEST"
8+
exit 1
9+
fi
10+
11+
git clone https://github.com/ish-app/ish "$DEST"

src/plugins/sdcard/src/ios/SDcard.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,11 @@ - (void)stats:(CDVInvokedUrlCommand *)command {
300300
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result] callbackId:command.callbackId];
301301
}
302302

303+
- (void)listEncodings:(CDVInvokedUrlCommand *)command {
304+
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]];
305+
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
306+
}
307+
303308
- (void)watchFile:(CDVInvokedUrlCommand *)command {
304309
NSString *path = command.arguments.count > 0 ? command.arguments[0] : @"";
305310
NSString *observerId = command.arguments.count > 1 ? command.arguments[1] : @"";

src/plugins/sdcard/www/plugin.js

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
1+
const isIOS = typeof cordova !== "undefined" && cordova.platformId === "ios";
2+
const actionMap = {
3+
"create directory": "createDir",
4+
"create file": "createFile",
5+
"open document file": "openDocumentFile",
6+
"get image": "getImage",
7+
"list volumes": "listStorages",
8+
"storage permission": "getStorageAccessPermission",
9+
"list directory": "listDir",
10+
"format uri": "formatUri",
11+
"get path": "getPath",
12+
"watch file": "watchFile",
13+
"unwatch file": "unwatchFile",
14+
"list encodings": "listEncodings"
15+
};
16+
const action = (name) => (isIOS && actionMap[name]) ? actionMap[name] : name;
17+
118
module.exports = {
219
copy: function (srcPathname, destPathname, onSuccess, onFail) {
320
cordova.exec(onSuccess, onFail, 'SDcard', 'copy', [srcPathname, destPathname]);
421
},
522
createDir: function (pathname, dir, onSuccess, onFail) {
6-
cordova.exec(onSuccess, onFail, 'SDcard', 'create directory', [pathname, dir]);
23+
cordova.exec(onSuccess, onFail, 'SDcard', action('create directory'), [pathname, dir]);
724
},
825
createFile: function (pathname, file, onSuccess, onFail) {
9-
cordova.exec(onSuccess, onFail, 'SDcard', 'create file', [pathname, file]);
26+
cordova.exec(onSuccess, onFail, 'SDcard', action('create file'), [pathname, file]);
1027
},
1128
delete: function (pathname, onSuccess, onFail) {
1229
cordova.exec(onSuccess, onFail, 'SDcard', 'delete', [pathname]);
@@ -15,28 +32,28 @@ module.exports = {
1532
cordova.exec(onSuccess, onFail, 'SDcard', 'exists', [pathName]);
1633
},
1734
formatUri: function (pathName, onSuccess, onFail) {
18-
cordova.exec(onSuccess, onFail, 'SDcard', 'format uri', [pathName]);
35+
cordova.exec(onSuccess, onFail, 'SDcard', action('format uri'), [pathName]);
1936
},
2037
getPath: function (uri, filename, onSuccess, onFail) {
21-
cordova.exec(onSuccess, onFail, 'SDcard', 'get path', [uri, filename]);
38+
cordova.exec(onSuccess, onFail, 'SDcard', action('get path'), [uri, filename]);
2239
},
2340
getStorageAccessPermission: function (uuid, onSuccess, onFail) {
24-
cordova.exec(onSuccess, onFail, 'SDcard', 'storage permission', [uuid]);
41+
cordova.exec(onSuccess, onFail, 'SDcard', action('storage permission'), [uuid]);
2542
},
2643
listStorages: function (onSuccess, onFail) {
27-
cordova.exec(onSuccess, onFail, 'SDcard', 'list volumes', []);
44+
cordova.exec(onSuccess, onFail, 'SDcard', action('list volumes'), []);
2845
},
2946
listDir: function (src, onSuccess, onFail) {
30-
cordova.exec(onSuccess, onFail, 'SDcard', 'list directory', [src]);
47+
cordova.exec(onSuccess, onFail, 'SDcard', action('list directory'), [src]);
3148
},
3249
move: function (srcPathname, destPathname, onSuccess, onFail) {
3350
cordova.exec(onSuccess, onFail, 'SDcard', 'move', [srcPathname, destPathname]);
3451
},
3552
openDocumentFile: function (onSuccess, onFail, mimeType) {
36-
cordova.exec(onSuccess, onFail, 'SDcard', 'open document file', mimeType ? [mimeType] : []);
53+
cordova.exec(onSuccess, onFail, 'SDcard', action('open document file'), mimeType ? [mimeType] : []);
3754
},
3855
getImage: function (onSuccess, onFail, mimeType) {
39-
cordova.exec(onSuccess, onFail, 'SDcard', 'get image', mimeType ? [mimeType] : []);
56+
cordova.exec(onSuccess, onFail, 'SDcard', action('get image'), mimeType ? [mimeType] : []);
4057
},
4158
rename: function (pathname, newFilename, onSuccess, onFail) {
4259
cordova.exec(onSuccess, onFail, 'SDcard', 'rename', [pathname, newFilename]);
@@ -53,14 +70,14 @@ module.exports = {
5370
},
5471
watchFile: function (filename, listener, onFail) {
5572
var id = parseInt(Date.now() + Math.random() * 1000000) + '';
56-
cordova.exec(listener, onFail, 'SDcard', 'watch file', [filename, id]);
73+
cordova.exec(listener, onFail, 'SDcard', action('watch file'), [filename, id]);
5774
return {
5875
unwatch: function () {
59-
cordova.exec(null, null, 'SDcard', 'unwatch file', [id]);
76+
cordova.exec(null, null, 'SDcard', action('unwatch file'), [id]);
6077
}
6178
};
6279
},
6380
listEncodings: function (onSuccess, onFail) {
64-
cordova.exec(onSuccess, onFail, 'SDcard', 'list encodings', []);
81+
cordova.exec(onSuccess, onFail, 'SDcard', action('list encodings'), []);
6582
}
66-
};
83+
};

src/plugins/system/plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,6 @@
5252

5353
<header-file src="src/ios/System.h"/>
5454
<source-file src="src/ios/System.m"/>
55+
<framework src="UserNotifications.framework" />
5556
</platform>
5657
</plugin>

0 commit comments

Comments
 (0)