nodes: better default display names

This commit is contained in:
Peter Steinberger
2025-12-17 23:15:15 +01:00
parent 875cf9a054
commit b3e466ccb6
5 changed files with 101 additions and 3 deletions

View File

@@ -0,0 +1,26 @@
package com.steipete.clawdis.node
import android.content.Context
import android.os.Build
import android.provider.Settings
object DeviceNames {
fun bestDefaultNodeName(context: Context): String {
val deviceName =
runCatching {
Settings.Global.getString(context.contentResolver, "device_name")
}
.getOrNull()
?.trim()
.orEmpty()
if (deviceName.isNotEmpty()) return deviceName
val model =
listOfNotNull(Build.MANUFACTURER?.takeIf { it.isNotBlank() }, Build.MODEL?.takeIf { it.isNotBlank() })
.joinToString(" ")
.trim()
return model.ifEmpty { "Android Node" }
}
}

View File

@@ -14,6 +14,7 @@ import java.util.UUID
class SecurePrefs(context: Context) {
companion object {
val defaultWakeWords: List<String> = listOf("clawd", "claude")
private const val displayNameKey = "node.displayName"
}
private val json = Json { ignoreUnknownKeys = true }
@@ -35,7 +36,8 @@ class SecurePrefs(context: Context) {
private val _instanceId = MutableStateFlow(loadOrCreateInstanceId())
val instanceId: StateFlow<String> = _instanceId
private val _displayName = MutableStateFlow(prefs.getString("node.displayName", "Android Node")!!)
private val _displayName =
MutableStateFlow(loadOrMigrateDisplayName(context = context))
val displayName: StateFlow<String> = _displayName
private val _cameraEnabled = MutableStateFlow(prefs.getBoolean("camera.enabled", true))
@@ -68,7 +70,7 @@ class SecurePrefs(context: Context) {
fun setDisplayName(value: String) {
val trimmed = value.trim()
prefs.edit().putString("node.displayName", trimmed).apply()
prefs.edit().putString(displayNameKey, trimmed).apply()
_displayName.value = trimmed
}
@@ -116,6 +118,17 @@ class SecurePrefs(context: Context) {
return fresh
}
private fun loadOrMigrateDisplayName(context: Context): String {
val existing = prefs.getString(displayNameKey, null)?.trim().orEmpty()
if (existing.isNotEmpty() && existing != "Android Node") return existing
val candidate = DeviceNames.bestDefaultNodeName(context).trim()
val resolved = candidate.ifEmpty { "Android Node" }
prefs.edit().putString(displayNameKey, resolved).apply()
return resolved
}
fun setWakeWords(words: List<String>) {
val sanitized = WakeWords.sanitize(words, defaultWakeWords)
val encoded =

View File

@@ -126,7 +126,7 @@ final class BridgeConnectionController {
private func makeHello(token: String) -> BridgeHello {
let defaults = UserDefaults.standard
let nodeId = defaults.string(forKey: "node.instanceId") ?? "ios-node"
let displayName = defaults.string(forKey: "node.displayName") ?? "iOS Node"
let displayName = self.resolvedDisplayName(defaults: defaults)
return BridgeHello(
nodeId: nodeId,
@@ -139,6 +139,21 @@ final class BridgeConnectionController {
caps: self.currentCaps())
}
private func resolvedDisplayName(defaults: UserDefaults) -> String {
let key = "node.displayName"
let existing = defaults.string(forKey: key)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !existing.isEmpty, existing != "iOS Node" { return existing }
let deviceName = UIDevice.current.name.trimmingCharacters(in: .whitespacesAndNewlines)
let candidate = deviceName.isEmpty ? "iOS Node" : deviceName
if existing.isEmpty || existing == "iOS Node" {
defaults.set(candidate, forKey: key)
}
return candidate
}
private func currentCaps() -> [String] {
var caps = ["canvas"]

View File

@@ -48,6 +48,10 @@ let package = Package(
.product(name: "PeekabooBridge", package: "PeekabooCore"),
.product(name: "PeekabooAutomationKit", package: "PeekabooAutomationKit"),
],
exclude: [
// Legacy web-based WebChat assets (SwiftUI-only WebChat doesn't use these).
"Resources/WebChat",
],
resources: [
.copy("Resources/Clawdis.icns"),
.copy("Resources/CanvasA2UI"),

40
docs/device-models.md Normal file
View File

@@ -0,0 +1,40 @@
# Device model database (friendly names)
The macOS companion app shows friendly Apple device model names in the **Instances** UI by mapping Apple model identifiers (e.g. `iPad16,6`, `Mac16,6`) to human-readable names.
The mapping is vendored as JSON under:
- `apps/macos/Sources/Clawdis/Resources/DeviceModels/`
## Data source
We currently vendor the mapping from the MIT-licensed repository:
- `kyle-seongwoo-jun/apple-device-identifiers`
To keep builds deterministic, the JSON files are pinned to specific upstream commits (recorded in `apps/macos/Sources/Clawdis/Resources/DeviceModels/NOTICE.md`).
## Updating the database
1. Pick the upstream commits you want to pin to (one for iOS, one for macOS).
2. Update the commit hashes in `apps/macos/Sources/Clawdis/Resources/DeviceModels/NOTICE.md`.
3. Re-download the JSON files, pinned to those commits:
```bash
IOS_COMMIT="<commit sha for ios-device-identifiers.json>"
MAC_COMMIT="<commit sha for mac-device-identifiers.json>"
curl -fsSL "https://raw.githubusercontent.com/kyle-seongwoo-jun/apple-device-identifiers/${IOS_COMMIT}/ios-device-identifiers.json" \
-o apps/macos/Sources/Clawdis/Resources/DeviceModels/ios-device-identifiers.json
curl -fsSL "https://raw.githubusercontent.com/kyle-seongwoo-jun/apple-device-identifiers/${MAC_COMMIT}/mac-device-identifiers.json" \
-o apps/macos/Sources/Clawdis/Resources/DeviceModels/mac-device-identifiers.json
```
4. Ensure `apps/macos/Sources/Clawdis/Resources/DeviceModels/LICENSE.apple-device-identifiers.txt` still matches upstream (replace it if the upstream license changes).
5. Verify the macOS app builds cleanly (no warnings):
```bash
swift build --package-path apps/macos
```