Let the user name the iPhone in Settings
This commit is contained in:
@@ -1,6 +1,4 @@
|
|||||||
# Bugs
|
# Bugs
|
||||||
- Enable the iPhone device-name entitlement
|
|
||||||
Once Apple grants User Assigned Device Name to the App ID `run.fc.byre.ios` (Certificates, Identifiers & Profiles, Capability Requests), turn it on there and add `com.apple.developer.device-information.user-assigned-device-name` to `mac/phone/byre.entitlements`. Until then `UIDevice.current.name` returns `iPhone` on a real phone. Check on a TestFlight build that the Status tab lists the phone by its own name.
|
|
||||||
|
|
||||||
# M1 — One agent end to end
|
# M1 — One agent end to end
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -17,5 +17,5 @@ The simulator's window (Xcode's Device Hub) lists the app's own accessibility el
|
|||||||
## Platform facts
|
## Platform facts
|
||||||
|
|
||||||
- CloudKit on iOS requires the `remote-notification` background mode in `Info.plist`, even though the app sets up no push of its own.
|
- CloudKit on iOS requires the `remote-notification` background mode in `Info.plist`, even though the app sets up no push of its own.
|
||||||
- The phone's tailnet node is always `byre-app-iphone`; the phone's own name stays inside byre. `Byre-Host`, which the server shows for the phone, takes its name from `UIDevice.current.name`, which iOS reads only on the main actor (so the app delegate reads it first) and returns as just `iPhone` unless the app has the `com.apple.developer.device-information.user-assigned-device-name` entitlement. Apple grants that entitlement on request (developer.apple.com/contact/request/user-assigned-device-name); it then has to be enabled on the App ID and added to `mac/phone/byre.entitlements`. The simulator returns the simulator device's own name without it.
|
- The phone's name is the one typed in Settings, kept in the phone's own `UserDefaults` (not synced through iCloud, since each phone has its own), and `iPhone` until one is typed. It goes in `Byre-Host` and the tailnet node's name, both fixed when the app starts. `UIDevice.current.name` returns just `iPhone` without Apple's `com.apple.developer.device-information.user-assigned-device-name` entitlement, which Apple refused because byre shows the name to other people and sends it to Tailscale and to the agents' model vendors.
|
||||||
- SwiftTerm's iOS `TerminalViewDelegate` requires `requestOpenLink`, which its macOS one provides a default for.
|
- SwiftTerm's iOS `TerminalViewDelegate` requires `requestOpenLink`, which its macOS one provides a default for.
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ private let clientID = newID("cli")
|
|||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
let deviceName = Host.current().localizedName ?? ProcessInfo.processInfo.hostName
|
let deviceName = Host.current().localizedName ?? ProcessInfo.processInfo.hostName
|
||||||
#else
|
#else
|
||||||
let deviceName = MainActor.assumeIsolated { UIDevice.current.name }
|
let deviceNameKey = "deviceName"
|
||||||
|
let deviceName = UserDefaults.standard.string(forKey: deviceNameKey) ?? deviceKind
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
final class API: Sendable {
|
final class API: Sendable {
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import SwiftUI
|
|||||||
|
|
||||||
final class PhoneDelegate: NSObject, UIApplicationDelegate {
|
final class PhoneDelegate: NSObject, UIApplicationDelegate {
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||||
// deviceName reads UIDevice, which is main-actor only, so it must first be read here.
|
|
||||||
_ = deviceName
|
|
||||||
Notifier.shared.install()
|
Notifier.shared.install()
|
||||||
UITextView.appearance().textContainerInset = .zero
|
UITextView.appearance().textContainerInset = .zero
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ struct SettingsView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
#if os(iOS)
|
||||||
|
heading("This iPhone's name")
|
||||||
|
Text("byre shows this iPhone by this name, and names its tailnet node after it. A new name takes effect the next time byre opens.")
|
||||||
|
.foregroundStyle(Palette.muted)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
DeviceNameField()
|
||||||
|
#endif
|
||||||
heading("Window color")
|
heading("Window color")
|
||||||
Text("Windows for this server have a border in this color, on every device you use.")
|
Text("Windows for this server have a border in this color, on every device you use.")
|
||||||
.foregroundStyle(Palette.muted)
|
.foregroundStyle(Palette.muted)
|
||||||
@@ -79,6 +86,40 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
private struct DeviceNameField: View {
|
||||||
|
@ViewState private var name = UserDefaults.standard.string(forKey: deviceNameKey) ?? deviceKind
|
||||||
|
@ViewState private var saved = UserDefaults.standard.string(forKey: deviceNameKey) ?? deviceKind
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let trimmed = name.trimmingCharacters(in: .whitespaces)
|
||||||
|
let valid = !slug(trimmed).isEmpty && trimmed != saved
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
TextField("Name", text: $name)
|
||||||
|
.modifier(FieldStyle())
|
||||||
|
.onSubmit {
|
||||||
|
save(trimmed, valid)
|
||||||
|
}
|
||||||
|
Button("Save") {
|
||||||
|
save(trimmed, valid)
|
||||||
|
}
|
||||||
|
.buttonStyle(WebButtonStyle())
|
||||||
|
.disabled(!valid)
|
||||||
|
}
|
||||||
|
.padding(.top, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func save(_ trimmed: String, _ valid: Bool) {
|
||||||
|
if !valid {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
UserDefaults.standard.set(trimmed, forKey: deviceNameKey)
|
||||||
|
saved = trimmed
|
||||||
|
name = trimmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private struct ServerColorPicker: View {
|
private struct ServerColorPicker: View {
|
||||||
@Environment(Store.self) private var store
|
@Environment(Store.self) private var store
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,7 @@ func slug(_ text: String) -> String {
|
|||||||
|
|
||||||
final class TailnetNode: Sendable {
|
final class TailnetNode: Sendable {
|
||||||
let sd: tailscale
|
let sd: tailscale
|
||||||
#if os(macOS)
|
|
||||||
let hostname = "byre-app-\(slug(deviceName))"
|
let hostname = "byre-app-\(slug(deviceName))"
|
||||||
#else
|
|
||||||
let hostname = "byre-app-\(slug(deviceKind))"
|
|
||||||
#endif
|
|
||||||
private let socks = TailnetProxy()
|
private let socks = TailnetProxy()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
|||||||
Reference in New Issue
Block a user