@wangyaoshen/remux 0.3.8-dev.bab6c95 → 0.3.10-dev.19fb76c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +191 -17
- package/apps/ios/Remux.xcodeproj/project.pbxproj +21 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/Contents.json +23 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_120x120.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_152x152.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_167x167.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_180x180.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_20x20.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_29x29.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_40x40.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_58x58.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_60x60.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_76x76.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_80x80.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_87x87.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/Contents.json +6 -0
- package/apps/ios/Sources/Remux/RootView.swift +2 -2
- package/apps/ios/Sources/Remux/Views/Settings/MeView.swift +11 -4
- package/apps/macos/Package.swift +5 -0
- package/apps/macos/Sources/Remux/AppCommand.swift +114 -0
- package/apps/macos/Sources/Remux/AppDelegate.swift +26 -0
- package/apps/macos/Sources/Remux/MainContentView.swift +56 -0
- package/apps/macos/Sources/Remux/MenuBarManager.swift +18 -26
- package/apps/macos/Sources/Remux/NotificationManager.swift +52 -7
- package/apps/macos/Sources/Remux/Views/ConnectionView.swift +4 -8
- package/apps/macos/Sources/Remux/Views/SplitTree/SplitView.swift +1 -1
- package/apps/macos/Sources/Remux/Views/Terminal/GhosttyNativeView.swift +10 -4
- package/apps/macos/Sources/Remux/Views/Terminal/TerminalContainerView.swift +35 -5
- package/apps/macos/Sources/Remux/WindowObserver.swift +38 -0
- package/apps/macos/Tests/RemuxTests/AppCommandTests.swift +30 -0
- package/apps/macos/Tests/RemuxTests/NotificationManagerTests.swift +28 -0
- package/package.json +1 -1
- package/packages/RemuxKit/Sources/RemuxKit/Models/ProtocolModels.swift +64 -0
- package/packages/RemuxKit/Sources/RemuxKit/Networking/MessageRouter.swift +88 -9
- package/packages/RemuxKit/Sources/RemuxKit/Networking/RemuxConnection.swift +47 -8
- package/packages/RemuxKit/Sources/RemuxKit/State/RemuxState.swift +81 -8
- package/packages/RemuxKit/Sources/RemuxKit/Storage/KeychainStore.swift +20 -1
- package/packages/RemuxKit/Tests/RemuxKitTests/ConnectionIntegrationTest.swift +16 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/KeychainStoreTests.swift +26 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/ProtocolModelsTests.swift +41 -7
- package/packages/RemuxKit/Tests/RemuxKitTests/RemuxStateTests.swift +20 -2
- package/pty-daemon.js +17 -11
- package/scripts/setup-ci-secrets.sh +80 -0
- package/scripts/upload-testflight.sh +100 -0
- package/server.js +146 -872
- package/src/pty-daemon.ts +17 -11
- package/src/server.ts +96 -859
- package/src/session.ts +42 -4
- package/tests/auth.test.js +1 -1
- package/tests/e2e/app.spec.js +44 -4
- package/tests/pty-daemon.test.js +20 -1
- package/tests/server.test.js +50 -12
- package/vitest.config.js +1 -0
|
@@ -55,37 +55,214 @@ jobs:
|
|
|
55
55
|
tags: ${{ steps.meta.outputs.tags }}
|
|
56
56
|
labels: ${{ steps.meta.outputs.labels }}
|
|
57
57
|
|
|
58
|
-
# ──
|
|
59
|
-
|
|
58
|
+
# ── iOS → TestFlight ─────────────────────────────────
|
|
59
|
+
ios:
|
|
60
60
|
runs-on: macos-15
|
|
61
61
|
permissions:
|
|
62
|
-
contents:
|
|
62
|
+
contents: read
|
|
63
63
|
steps:
|
|
64
64
|
- uses: actions/checkout@v4
|
|
65
65
|
with:
|
|
66
66
|
submodules: recursive
|
|
67
67
|
|
|
68
|
-
- name:
|
|
69
|
-
|
|
68
|
+
- name: Set up signing
|
|
69
|
+
env:
|
|
70
|
+
CERTIFICATES_P12: ${{ secrets.APPLE_CERTIFICATES_P12 }}
|
|
71
|
+
CERTIFICATES_PASSWORD: ${{ secrets.APPLE_CERTIFICATES_PASSWORD }}
|
|
72
|
+
API_KEY_P8: ${{ secrets.APP_STORE_CONNECT_API_KEY_P8 }}
|
|
73
|
+
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
74
|
+
run: |
|
|
75
|
+
# Create temporary keychain
|
|
76
|
+
KEYCHAIN_PATH="$RUNNER_TEMP/signing.keychain-db"
|
|
77
|
+
KEYCHAIN_PASSWORD="$(openssl rand -hex 16)"
|
|
78
|
+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
79
|
+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
|
80
|
+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
81
|
+
|
|
82
|
+
# Import certificates
|
|
83
|
+
echo "$CERTIFICATES_P12" | base64 --decode > "$RUNNER_TEMP/certs.p12"
|
|
84
|
+
security import "$RUNNER_TEMP/certs.p12" \
|
|
85
|
+
-P "$CERTIFICATES_PASSWORD" \
|
|
86
|
+
-A -t cert -f pkcs12 \
|
|
87
|
+
-k "$KEYCHAIN_PATH"
|
|
88
|
+
security set-key-partition-list -S apple-tool:,apple:,codesign: \
|
|
89
|
+
-s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
90
|
+
|
|
91
|
+
# Add keychain to search list
|
|
92
|
+
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
|
|
93
|
+
|
|
94
|
+
# Install Apple WWDR G3 intermediate cert
|
|
95
|
+
curl -sO https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer
|
|
96
|
+
sudo security add-certificates -k /Library/Keychains/System.keychain AppleWWDRCAG3.cer
|
|
97
|
+
|
|
98
|
+
# Set up API key
|
|
99
|
+
mkdir -p ~/.private_keys
|
|
100
|
+
echo "$API_KEY_P8" | base64 --decode > ~/.private_keys/AuthKey_${API_KEY_ID}.p8
|
|
101
|
+
|
|
102
|
+
# Verify
|
|
103
|
+
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
|
|
104
|
+
|
|
105
|
+
- name: Archive
|
|
106
|
+
env:
|
|
107
|
+
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
108
|
+
API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
109
|
+
run: |
|
|
110
|
+
TAG="${GITHUB_REF#refs/tags/}"
|
|
111
|
+
VERSION="${TAG#v}"
|
|
112
|
+
|
|
113
|
+
xcodebuild archive \
|
|
114
|
+
-project apps/ios/Remux.xcodeproj \
|
|
115
|
+
-scheme Remux \
|
|
116
|
+
-destination 'generic/platform=iOS' \
|
|
117
|
+
-archivePath "$RUNNER_TEMP/Remux.xcarchive" \
|
|
118
|
+
-allowProvisioningUpdates \
|
|
119
|
+
-authenticationKeyPath ~/.private_keys/AuthKey_${API_KEY_ID}.p8 \
|
|
120
|
+
-authenticationKeyID "$API_KEY_ID" \
|
|
121
|
+
-authenticationKeyIssuerID "$API_ISSUER_ID" \
|
|
122
|
+
CURRENT_PROJECT_VERSION="$(git rev-list --count HEAD)" \
|
|
123
|
+
MARKETING_VERSION="$VERSION" \
|
|
124
|
+
-quiet
|
|
125
|
+
|
|
126
|
+
- name: Export IPA
|
|
127
|
+
env:
|
|
128
|
+
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
129
|
+
API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
130
|
+
run: |
|
|
131
|
+
cat > "$RUNNER_TEMP/ExportOptions.plist" << 'PLIST'
|
|
132
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
133
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
134
|
+
<plist version="1.0">
|
|
135
|
+
<dict>
|
|
136
|
+
<key>method</key>
|
|
137
|
+
<string>app-store-connect</string>
|
|
138
|
+
<key>signingStyle</key>
|
|
139
|
+
<string>automatic</string>
|
|
140
|
+
<key>uploadSymbols</key>
|
|
141
|
+
<true/>
|
|
142
|
+
<key>manageAppVersionAndBuildNumber</key>
|
|
143
|
+
<true/>
|
|
144
|
+
</dict>
|
|
145
|
+
</plist>
|
|
146
|
+
PLIST
|
|
147
|
+
|
|
148
|
+
xcodebuild -exportArchive \
|
|
149
|
+
-archivePath "$RUNNER_TEMP/Remux.xcarchive" \
|
|
150
|
+
-exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" \
|
|
151
|
+
-exportPath "$RUNNER_TEMP/export" \
|
|
152
|
+
-allowProvisioningUpdates \
|
|
153
|
+
-authenticationKeyPath ~/.private_keys/AuthKey_${API_KEY_ID}.p8 \
|
|
154
|
+
-authenticationKeyID "$API_KEY_ID" \
|
|
155
|
+
-authenticationKeyIssuerID "$API_ISSUER_ID" \
|
|
156
|
+
-quiet
|
|
157
|
+
|
|
158
|
+
- name: Upload to TestFlight
|
|
159
|
+
env:
|
|
160
|
+
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
161
|
+
API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
162
|
+
run: |
|
|
163
|
+
IPA=$(find "$RUNNER_TEMP/export" -name "*.ipa" | head -1)
|
|
164
|
+
xcrun altool --upload-app --type ios \
|
|
165
|
+
--file "$IPA" \
|
|
166
|
+
--apiKey "$API_KEY_ID" \
|
|
167
|
+
--apiIssuer "$API_ISSUER_ID"
|
|
168
|
+
|
|
169
|
+
- name: Set export compliance
|
|
170
|
+
run: |
|
|
171
|
+
pip3 install PyJWT cryptography
|
|
172
|
+
python3 << 'PYEOF'
|
|
173
|
+
import jwt, time, json, urllib.request, os
|
|
174
|
+
|
|
175
|
+
with open(os.path.expanduser(f"~/.private_keys/AuthKey_{os.environ['API_KEY_ID']}.p8")) as f:
|
|
176
|
+
key = f.read()
|
|
177
|
+
token = jwt.encode(
|
|
178
|
+
{"iss": os.environ["API_ISSUER_ID"], "iat": int(time.time()),
|
|
179
|
+
"exp": int(time.time()) + 1200, "aud": "appstoreconnect-v1"},
|
|
180
|
+
key, algorithm="ES256",
|
|
181
|
+
headers={"kid": os.environ["API_KEY_ID"], "typ": "JWT"})
|
|
182
|
+
|
|
183
|
+
# Find latest build
|
|
184
|
+
req = urllib.request.Request(
|
|
185
|
+
f"https://api.appstoreconnect.apple.com/v1/builds?filter[app]=6761521429&sort=-uploadedDate&limit=1",
|
|
186
|
+
headers={"Authorization": f"Bearer {token}"})
|
|
187
|
+
data = json.loads(urllib.request.urlopen(req).read())
|
|
188
|
+
build_id = data["data"][0]["id"]
|
|
189
|
+
|
|
190
|
+
# Set usesNonExemptEncryption = false
|
|
191
|
+
body = json.dumps({"data": {"type": "builds", "id": build_id,
|
|
192
|
+
"attributes": {"usesNonExemptEncryption": False}}}).encode()
|
|
193
|
+
req = urllib.request.Request(
|
|
194
|
+
f"https://api.appstoreconnect.apple.com/v1/builds/{build_id}",
|
|
195
|
+
data=body, method="PATCH",
|
|
196
|
+
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"})
|
|
197
|
+
urllib.request.urlopen(req)
|
|
198
|
+
print(f"✓ Export compliance set for build {build_id}")
|
|
199
|
+
PYEOF
|
|
200
|
+
env:
|
|
201
|
+
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
202
|
+
API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
203
|
+
|
|
204
|
+
- name: Clean up keychain
|
|
205
|
+
if: always()
|
|
206
|
+
run: security delete-keychain "$RUNNER_TEMP/signing.keychain-db" 2>/dev/null || true
|
|
207
|
+
|
|
208
|
+
# ── macOS app (.dmg) → signed + notarized + GitHub Release ──
|
|
209
|
+
# Runs on self-hosted runner (Mac Mini) which has Developer ID cert installed
|
|
210
|
+
macos:
|
|
211
|
+
runs-on: [self-hosted, remux-deploy]
|
|
212
|
+
permissions:
|
|
213
|
+
contents: write
|
|
214
|
+
steps:
|
|
215
|
+
- uses: actions/checkout@v4
|
|
70
216
|
with:
|
|
71
|
-
|
|
217
|
+
submodules: recursive
|
|
72
218
|
|
|
73
|
-
- name:
|
|
219
|
+
- name: Verify signing identity
|
|
74
220
|
run: |
|
|
75
|
-
|
|
76
|
-
zig build -Demit-xcframework=true -Dxcframework-target=native -Doptimize=ReleaseFast
|
|
221
|
+
security find-identity -v -p codesigning | grep "Developer ID Application"
|
|
77
222
|
|
|
78
|
-
- name: Build
|
|
223
|
+
- name: Build GhosttyKit xcframework (if needed)
|
|
79
224
|
run: |
|
|
80
|
-
|
|
81
|
-
|
|
225
|
+
XCFW="vendor/ghostty/macos/GhosttyKit.xcframework"
|
|
226
|
+
if [ ! -d "$XCFW" ] || [ ! -f "$XCFW/Info.plist" ]; then
|
|
227
|
+
cd vendor/ghostty
|
|
228
|
+
zig build -Demit-xcframework=true -Dxcframework-target=native -Doptimize=ReleaseFast
|
|
229
|
+
fi
|
|
82
230
|
|
|
83
|
-
- name:
|
|
231
|
+
- name: Build, sign, and notarize DMG
|
|
232
|
+
env:
|
|
233
|
+
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
|
234
|
+
API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
235
|
+
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
84
236
|
run: |
|
|
85
237
|
TAG="${GITHUB_REF#refs/tags/}"
|
|
86
238
|
VERSION="${TAG#v}"
|
|
239
|
+
|
|
240
|
+
# Build .app and DMG
|
|
87
241
|
bash scripts/build-dmg.sh "$VERSION"
|
|
88
242
|
|
|
243
|
+
# Sign with Developer ID + hardened runtime + timestamp
|
|
244
|
+
codesign --force --deep --options runtime --timestamp \
|
|
245
|
+
--sign "Developer ID Application: Yaoshen Wang ($TEAM_ID)" \
|
|
246
|
+
"build/Remux.app"
|
|
247
|
+
|
|
248
|
+
# Rebuild DMG with signed app
|
|
249
|
+
DMG="build/Remux-${VERSION}-arm64.dmg"
|
|
250
|
+
rm -f "$DMG"
|
|
251
|
+
hdiutil create -volname "Remux" \
|
|
252
|
+
-srcfolder build/Remux.app \
|
|
253
|
+
-ov -format UDZO "$DMG"
|
|
254
|
+
codesign --force --timestamp \
|
|
255
|
+
--sign "Developer ID Application: Yaoshen Wang ($TEAM_ID)" "$DMG"
|
|
256
|
+
|
|
257
|
+
# Notarize
|
|
258
|
+
xcrun notarytool submit "$DMG" \
|
|
259
|
+
--key ~/.private_keys/AuthKey_${API_KEY_ID}.p8 \
|
|
260
|
+
--key-id "$API_KEY_ID" \
|
|
261
|
+
--issuer "$API_ISSUER_ID" \
|
|
262
|
+
--wait
|
|
263
|
+
|
|
264
|
+
xcrun stapler staple "$DMG"
|
|
265
|
+
|
|
89
266
|
- name: Upload DMG to GitHub Release
|
|
90
267
|
env:
|
|
91
268
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -93,13 +270,10 @@ jobs:
|
|
|
93
270
|
TAG="${GITHUB_REF#refs/tags/}"
|
|
94
271
|
VERSION="${TAG#v}"
|
|
95
272
|
DMG="build/Remux-${VERSION}-arm64.dmg"
|
|
273
|
+
|
|
96
274
|
if [ -f "$DMG" ]; then
|
|
97
275
|
gh release upload "$TAG" "$DMG" --clobber
|
|
98
276
|
echo "✓ Uploaded $DMG to release $TAG"
|
|
99
|
-
else
|
|
100
|
-
echo "⚠ DMG not found, uploading .app as zip"
|
|
101
|
-
cd build && zip -r "Remux-${VERSION}-arm64.zip" Remux.app
|
|
102
|
-
gh release upload "$TAG" "Remux-${VERSION}-arm64.zip" --clobber
|
|
103
277
|
fi
|
|
104
278
|
|
|
105
279
|
# ── Update Homebrew tap ─────────────────────────────
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
ECAFCC766BD77CCD21821612 /* ManualConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3AE7922D1E43AE099D1D40 /* ManualConnectView.swift */; };
|
|
22
22
|
EE0A712F52F48C1AB61D7AEC /* RemuxKit in Frameworks */ = {isa = PBXBuildFile; productRef = EF63343AE24B477D7365EE17 /* RemuxKit */; };
|
|
23
23
|
F136D905F6E9BA7F2C1816F2 /* InspectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED7C2F66BB76FB8DE51725E /* InspectView.swift */; };
|
|
24
|
+
AA1234560000000000000001 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA1234560000000000000002 /* Assets.xcassets */; };
|
|
24
25
|
/* End PBXBuildFile section */
|
|
25
26
|
|
|
26
27
|
/* Begin PBXFileReference section */
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
D548B0C91D3AABA26641D898 /* ControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlView.swift; sourceTree = "<group>"; };
|
|
41
42
|
DBE379B0C119DDC9C8B324E5 /* NowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowView.swift; sourceTree = "<group>"; };
|
|
42
43
|
DEEEA01EB80CE2C1601F6228 /* InspectCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectCache.swift; sourceTree = "<group>"; };
|
|
44
|
+
AA1234560000000000000002 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
|
43
45
|
/* End PBXFileReference section */
|
|
44
46
|
|
|
45
47
|
/* Begin PBXFrameworksBuildPhase section */
|
|
@@ -67,6 +69,7 @@
|
|
|
67
69
|
0E70947CCFEAEFEDCCF14E35 /* Remux */ = {
|
|
68
70
|
isa = PBXGroup;
|
|
69
71
|
children = (
|
|
72
|
+
AA1234560000000000000002 /* Assets.xcassets */,
|
|
70
73
|
48C3DD287E421F1077A8923A /* MainTabView.swift */,
|
|
71
74
|
6C491FD55D84AC2BED5C5EBD /* Remux.entitlements */,
|
|
72
75
|
662FEE0E78ADAADEA731106C /* RemuxiOSApp.swift */,
|
|
@@ -174,6 +177,7 @@
|
|
|
174
177
|
buildPhases = (
|
|
175
178
|
204B591A381758166DA82963 /* Sources */,
|
|
176
179
|
03BDC461530EDD09CDB7FF29 /* Frameworks */,
|
|
180
|
+
AA1234560000000000000003 /* Resources */,
|
|
177
181
|
);
|
|
178
182
|
buildRules = (
|
|
179
183
|
);
|
|
@@ -224,6 +228,17 @@
|
|
|
224
228
|
};
|
|
225
229
|
/* End PBXProject section */
|
|
226
230
|
|
|
231
|
+
/* Begin PBXResourcesBuildPhase section */
|
|
232
|
+
AA1234560000000000000003 /* Resources */ = {
|
|
233
|
+
isa = PBXResourcesBuildPhase;
|
|
234
|
+
buildActionMask = 2147483647;
|
|
235
|
+
files = (
|
|
236
|
+
AA1234560000000000000001 /* Assets.xcassets in Resources */,
|
|
237
|
+
);
|
|
238
|
+
runOnlyForDeploymentPostprocessing = 0;
|
|
239
|
+
};
|
|
240
|
+
/* End PBXResourcesBuildPhase section */
|
|
241
|
+
|
|
227
242
|
/* Begin PBXSourcesBuildPhase section */
|
|
228
243
|
204B591A381758166DA82963 /* Sources */ = {
|
|
229
244
|
isa = PBXSourcesBuildPhase;
|
|
@@ -378,6 +393,7 @@
|
|
|
378
393
|
CODE_SIGN_ENTITLEMENTS = Sources/Remux/Remux.entitlements;
|
|
379
394
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
|
380
395
|
GENERATE_INFOPLIST_FILE = YES;
|
|
396
|
+
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
|
381
397
|
INFOPLIST_KEY_NSCameraUsageDescription = "Remux uses the camera to scan QR codes for server pairing.";
|
|
382
398
|
INFOPLIST_KEY_NSFaceIDUsageDescription = "Remux uses Face ID to protect your terminal sessions.";
|
|
383
399
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
|
@@ -387,6 +403,8 @@
|
|
|
387
403
|
"$(inherited)",
|
|
388
404
|
"@executable_path/Frameworks",
|
|
389
405
|
);
|
|
406
|
+
CURRENT_PROJECT_VERSION = 1;
|
|
407
|
+
MARKETING_VERSION = 0.3.8;
|
|
390
408
|
PRODUCT_BUNDLE_IDENTIFIER = com.remux.ios;
|
|
391
409
|
SDKROOT = iphoneos;
|
|
392
410
|
TARGETED_DEVICE_FAMILY = "1,2";
|
|
@@ -400,6 +418,7 @@
|
|
|
400
418
|
CODE_SIGN_ENTITLEMENTS = Sources/Remux/Remux.entitlements;
|
|
401
419
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
|
402
420
|
GENERATE_INFOPLIST_FILE = YES;
|
|
421
|
+
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
|
403
422
|
INFOPLIST_KEY_NSCameraUsageDescription = "Remux uses the camera to scan QR codes for server pairing.";
|
|
404
423
|
INFOPLIST_KEY_NSFaceIDUsageDescription = "Remux uses Face ID to protect your terminal sessions.";
|
|
405
424
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
|
@@ -409,6 +428,8 @@
|
|
|
409
428
|
"$(inherited)",
|
|
410
429
|
"@executable_path/Frameworks",
|
|
411
430
|
);
|
|
431
|
+
CURRENT_PROJECT_VERSION = 1;
|
|
432
|
+
MARKETING_VERSION = 0.3.8;
|
|
412
433
|
PRODUCT_BUNDLE_IDENTIFIER = com.remux.ios;
|
|
413
434
|
SDKROOT = iphoneos;
|
|
414
435
|
TARGETED_DEVICE_FAMILY = "1,2";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"images" : [
|
|
3
|
+
{ "filename" : "icon_40x40.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" },
|
|
4
|
+
{ "filename" : "icon_60x60.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" },
|
|
5
|
+
{ "filename" : "icon_58x58.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" },
|
|
6
|
+
{ "filename" : "icon_87x87.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" },
|
|
7
|
+
{ "filename" : "icon_80x80.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" },
|
|
8
|
+
{ "filename" : "icon_120x120.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" },
|
|
9
|
+
{ "filename" : "icon_120x120.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" },
|
|
10
|
+
{ "filename" : "icon_180x180.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" },
|
|
11
|
+
{ "filename" : "icon_20x20.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" },
|
|
12
|
+
{ "filename" : "icon_40x40.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" },
|
|
13
|
+
{ "filename" : "icon_29x29.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" },
|
|
14
|
+
{ "filename" : "icon_58x58.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" },
|
|
15
|
+
{ "filename" : "icon_40x40.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" },
|
|
16
|
+
{ "filename" : "icon_80x80.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" },
|
|
17
|
+
{ "filename" : "icon_76x76.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" },
|
|
18
|
+
{ "filename" : "icon_152x152.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" },
|
|
19
|
+
{ "filename" : "icon_167x167.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" },
|
|
20
|
+
{ "filename" : "icon_1024x1024.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" }
|
|
21
|
+
],
|
|
22
|
+
"info" : { "author" : "xcode", "version" : 1 }
|
|
23
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -96,9 +96,9 @@ struct RootView: View {
|
|
|
96
96
|
private func tryAutoConnect() {
|
|
97
97
|
let servers = keychain.savedServers()
|
|
98
98
|
if let server = servers.first,
|
|
99
|
-
let
|
|
99
|
+
let credential = keychain.preferredCredential(forServer: server),
|
|
100
100
|
let url = URL(string: server) {
|
|
101
|
-
state.connect(url: url, credential:
|
|
101
|
+
state.connect(url: url, credential: credential)
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
}
|
|
@@ -65,13 +65,13 @@ struct MeView: View {
|
|
|
65
65
|
VStack(alignment: .leading) {
|
|
66
66
|
Text(device.name ?? device.id.prefix(8).description)
|
|
67
67
|
.font(.subheadline)
|
|
68
|
-
Text(device.
|
|
68
|
+
Text(device.trust)
|
|
69
69
|
.font(.caption)
|
|
70
|
-
.foregroundStyle(device.
|
|
70
|
+
.foregroundStyle(device.trust == "trusted" ? .green : .orange)
|
|
71
71
|
}
|
|
72
72
|
Spacer()
|
|
73
73
|
if let lastSeen = device.lastSeen {
|
|
74
|
-
Text(
|
|
74
|
+
Text(Self.lastSeenFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(lastSeen) / 1000)))
|
|
75
75
|
.font(.caption2)
|
|
76
76
|
.foregroundStyle(.tertiary)
|
|
77
77
|
}
|
|
@@ -90,7 +90,7 @@ struct MeView: View {
|
|
|
90
90
|
HStack {
|
|
91
91
|
Text("Version")
|
|
92
92
|
Spacer()
|
|
93
|
-
Text(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.3.
|
|
93
|
+
Text(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.3.9")
|
|
94
94
|
.foregroundStyle(.secondary)
|
|
95
95
|
}
|
|
96
96
|
}
|
|
@@ -112,6 +112,13 @@ struct MeView: View {
|
|
|
112
112
|
default: "questionmark.circle"
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
+
|
|
116
|
+
private static let lastSeenFormatter: DateFormatter = {
|
|
117
|
+
let formatter = DateFormatter()
|
|
118
|
+
formatter.dateStyle = .short
|
|
119
|
+
formatter.timeStyle = .short
|
|
120
|
+
return formatter
|
|
121
|
+
}()
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
extension ConnectionStatus {
|
package/apps/macos/Package.swift
CHANGED
|
@@ -29,6 +29,11 @@ let package = Package(
|
|
|
29
29
|
.linkedLibrary("z"),
|
|
30
30
|
]
|
|
31
31
|
),
|
|
32
|
+
.testTarget(
|
|
33
|
+
name: "RemuxTests",
|
|
34
|
+
dependencies: ["Remux"],
|
|
35
|
+
path: "Tests/RemuxTests"
|
|
36
|
+
),
|
|
32
37
|
.binaryTarget(
|
|
33
38
|
name: "GhosttyKit",
|
|
34
39
|
path: "../../vendor/ghostty/macos/GhosttyKit.xcframework"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
enum WindowCommandAction: String, Sendable {
|
|
4
|
+
case splitRight
|
|
5
|
+
case splitDown
|
|
6
|
+
case closePane
|
|
7
|
+
case focusNextPane
|
|
8
|
+
case focusPreviousPane
|
|
9
|
+
case newBrowserPane
|
|
10
|
+
case newMarkdownPane
|
|
11
|
+
case commandPalette
|
|
12
|
+
case copyMode
|
|
13
|
+
case findInTerminal
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
struct WindowCommand: Equatable, Sendable {
|
|
17
|
+
private static let actionKey = "action"
|
|
18
|
+
private static let targetWindowNumberKey = "targetWindowNumber"
|
|
19
|
+
|
|
20
|
+
let action: WindowCommandAction
|
|
21
|
+
let targetWindowNumber: Int
|
|
22
|
+
|
|
23
|
+
init(action: WindowCommandAction, targetWindowNumber: Int) {
|
|
24
|
+
self.action = action
|
|
25
|
+
self.targetWindowNumber = targetWindowNumber
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
init?(notification: Notification) {
|
|
29
|
+
guard notification.name == .remuxWindowCommand,
|
|
30
|
+
let rawAction = notification.userInfo?[Self.actionKey] as? String,
|
|
31
|
+
let action = WindowCommandAction(rawValue: rawAction),
|
|
32
|
+
let targetWindowNumber = notification.userInfo?[Self.targetWindowNumberKey] as? Int else {
|
|
33
|
+
return nil
|
|
34
|
+
}
|
|
35
|
+
self.init(action: action, targetWindowNumber: targetWindowNumber)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func matches(windowNumber: Int?) -> Bool {
|
|
39
|
+
guard let windowNumber else { return false }
|
|
40
|
+
return targetWindowNumber == windowNumber
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func post() {
|
|
44
|
+
NotificationCenter.default.post(
|
|
45
|
+
name: .remuxWindowCommand,
|
|
46
|
+
object: nil,
|
|
47
|
+
userInfo: [
|
|
48
|
+
Self.actionKey: action.rawValue,
|
|
49
|
+
Self.targetWindowNumberKey: targetWindowNumber,
|
|
50
|
+
]
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
enum TerminalCommandAction: String, Sendable {
|
|
56
|
+
case showSearch
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
struct TerminalCommand: Equatable, Sendable {
|
|
60
|
+
private static let actionKey = "action"
|
|
61
|
+
private static let targetWindowNumberKey = "targetWindowNumber"
|
|
62
|
+
private static let leafIDKey = "leafID"
|
|
63
|
+
|
|
64
|
+
let action: TerminalCommandAction
|
|
65
|
+
let targetWindowNumber: Int
|
|
66
|
+
let leafID: UUID?
|
|
67
|
+
|
|
68
|
+
init(action: TerminalCommandAction, targetWindowNumber: Int, leafID: UUID?) {
|
|
69
|
+
self.action = action
|
|
70
|
+
self.targetWindowNumber = targetWindowNumber
|
|
71
|
+
self.leafID = leafID
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
init?(notification: Notification) {
|
|
75
|
+
guard notification.name == .remuxTerminalCommand,
|
|
76
|
+
let rawAction = notification.userInfo?[Self.actionKey] as? String,
|
|
77
|
+
let action = TerminalCommandAction(rawValue: rawAction),
|
|
78
|
+
let targetWindowNumber = notification.userInfo?[Self.targetWindowNumberKey] as? Int else {
|
|
79
|
+
return nil
|
|
80
|
+
}
|
|
81
|
+
self.init(
|
|
82
|
+
action: action,
|
|
83
|
+
targetWindowNumber: targetWindowNumber,
|
|
84
|
+
leafID: notification.userInfo?[Self.leafIDKey] as? UUID
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func matches(windowNumber: Int?, leafID: UUID?) -> Bool {
|
|
89
|
+
guard let windowNumber, targetWindowNumber == windowNumber else {
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
return self.leafID == leafID
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func post() {
|
|
96
|
+
var userInfo: [String: Any] = [
|
|
97
|
+
Self.actionKey: action.rawValue,
|
|
98
|
+
Self.targetWindowNumberKey: targetWindowNumber,
|
|
99
|
+
]
|
|
100
|
+
if let leafID {
|
|
101
|
+
userInfo[Self.leafIDKey] = leafID
|
|
102
|
+
}
|
|
103
|
+
NotificationCenter.default.post(
|
|
104
|
+
name: .remuxTerminalCommand,
|
|
105
|
+
object: nil,
|
|
106
|
+
userInfo: userInfo
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
extension Notification.Name {
|
|
112
|
+
static let remuxWindowCommand = Notification.Name("remuxWindowCommand")
|
|
113
|
+
static let remuxTerminalCommand = Notification.Name("remuxTerminalCommand")
|
|
114
|
+
}
|
|
@@ -38,6 +38,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
38
38
|
finderIntegration = FinderIntegration(state: state)
|
|
39
39
|
finderIntegration?.registerServices()
|
|
40
40
|
|
|
41
|
+
NotificationCenter.default.addObserver(
|
|
42
|
+
self,
|
|
43
|
+
selector: #selector(handleTerminalDataNotification(_:)),
|
|
44
|
+
name: .remuxTerminalData,
|
|
45
|
+
object: nil
|
|
46
|
+
)
|
|
47
|
+
|
|
41
48
|
// Start autosave
|
|
42
49
|
SessionPersistence.shared.startAutosave { [weak self] in
|
|
43
50
|
guard let self else {
|
|
@@ -60,6 +67,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
func applicationWillTerminate(_ notification: Notification) {
|
|
70
|
+
NotificationCenter.default.removeObserver(self)
|
|
71
|
+
|
|
63
72
|
// Stop socket controller
|
|
64
73
|
socketController?.stop()
|
|
65
74
|
|
|
@@ -254,4 +263,21 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
254
263
|
}
|
|
255
264
|
detachedWindows.removeAll()
|
|
256
265
|
}
|
|
266
|
+
|
|
267
|
+
@objc private func handleTerminalDataNotification(_ notification: Notification) {
|
|
268
|
+
guard let data = notification.userInfo?["data"] as? Data else { return }
|
|
269
|
+
|
|
270
|
+
let oscNotifications = OSCNotificationParser.parse(data)
|
|
271
|
+
guard !oscNotifications.isEmpty else { return }
|
|
272
|
+
|
|
273
|
+
let sessionName = state.currentSession.isEmpty ? "Remux" : state.currentSession
|
|
274
|
+
for oscNotification in oscNotifications {
|
|
275
|
+
notificationManager?.handleNotification(.init(
|
|
276
|
+
title: oscNotification.title,
|
|
277
|
+
body: oscNotification.body,
|
|
278
|
+
tabIndex: state.activeTabIndex,
|
|
279
|
+
sessionName: sessionName
|
|
280
|
+
))
|
|
281
|
+
}
|
|
282
|
+
}
|
|
257
283
|
}
|