mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 09:00:35 +02:00
An iPhone can't run Python or ssh, but the Frame can. The app (ios/, SwiftUI) connects with its own SSH key (Citadel), copies the server and helpers to ~/.cache/frame-control/<version> on the Frame once per version, starts ui/server.py there with FRAME_LOCAL=1 on the Frame's 127.0.0.1, and shows the page through an SSH tunnel. The server exits when the phone disconnects. Server: FRAME_LOCAL=1 puts ui/local-bin on PATH, whose ssh stand-in runs each `ssh frame COMMAND` locally (and serves as rsync's transport), so desktop and phone share one code path. Android display goes through podman exec there, as the Frame has no adb. FRAME_UI_KEY replaces the fixed X-Frame-UI value with a per-session key. Power actions take the Developer Mode password via sudo -S. --port 0 now prints the port it took. Page: a bottom tab bar and safe areas on phones, Play buttons visible on touch screens, saving through the share sheet, SSH/SFTP/Steam Link/remote desktop opening in their iOS apps, and a password dialog for power. App: pairing with the Developer Mode password once (never stored) or with a key the user adds; host key pinned on first use; plain-language connection errors with quiet retries; frame-control://install links; alerts and confirms. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
35 lines
1.4 KiB
Swift
35 lines
1.4 KiB
Swift
import SwiftUI
|
|
|
|
@main
|
|
struct FrameControlApp: App {
|
|
@StateObject private var model = AppModel()
|
|
@Environment(\.scenePhase) private var scenePhase
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
RootView(model: model)
|
|
.task {
|
|
#if DEBUG
|
|
// Simulator testing without the pairing screen: print this device's key,
|
|
// and connect to FRAME_TEST_HOST with it (`simctl launch` passes
|
|
// SIMCTL_CHILD_FRAME_TEST_HOST through as FRAME_TEST_HOST).
|
|
print("FRAME_CONTROL_KEY: \(model.authorizedKeysLine)")
|
|
if model.settings == nil, let host = ProcessInfo.processInfo.environment["FRAME_TEST_HOST"] {
|
|
await model.useKey(host: host, user: "steamos")
|
|
return
|
|
}
|
|
#endif
|
|
if model.settings != nil { await model.connect() }
|
|
}
|
|
.onOpenURL { url in
|
|
// frame-control://install?… from a website (docs/web-install.md).
|
|
guard let link = InstallLink(url.absoluteString), model.pendingInstallLinks.count < 5 else { return }
|
|
model.pendingInstallLinks.append(link)
|
|
}
|
|
.onChange(of: scenePhase) { _, phase in
|
|
if phase == .active { model.resume() }
|
|
}
|
|
}
|
|
}
|
|
}
|