nodes: better default display names
This commit is contained in:
@@ -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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import java.util.UUID
|
|||||||
class SecurePrefs(context: Context) {
|
class SecurePrefs(context: Context) {
|
||||||
companion object {
|
companion object {
|
||||||
val defaultWakeWords: List<String> = listOf("clawd", "claude")
|
val defaultWakeWords: List<String> = listOf("clawd", "claude")
|
||||||
|
private const val displayNameKey = "node.displayName"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val json = Json { ignoreUnknownKeys = true }
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
@@ -35,7 +36,8 @@ class SecurePrefs(context: Context) {
|
|||||||
private val _instanceId = MutableStateFlow(loadOrCreateInstanceId())
|
private val _instanceId = MutableStateFlow(loadOrCreateInstanceId())
|
||||||
val instanceId: StateFlow<String> = _instanceId
|
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
|
val displayName: StateFlow<String> = _displayName
|
||||||
|
|
||||||
private val _cameraEnabled = MutableStateFlow(prefs.getBoolean("camera.enabled", true))
|
private val _cameraEnabled = MutableStateFlow(prefs.getBoolean("camera.enabled", true))
|
||||||
@@ -68,7 +70,7 @@ class SecurePrefs(context: Context) {
|
|||||||
|
|
||||||
fun setDisplayName(value: String) {
|
fun setDisplayName(value: String) {
|
||||||
val trimmed = value.trim()
|
val trimmed = value.trim()
|
||||||
prefs.edit().putString("node.displayName", trimmed).apply()
|
prefs.edit().putString(displayNameKey, trimmed).apply()
|
||||||
_displayName.value = trimmed
|
_displayName.value = trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +118,17 @@ class SecurePrefs(context: Context) {
|
|||||||
return fresh
|
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>) {
|
fun setWakeWords(words: List<String>) {
|
||||||
val sanitized = WakeWords.sanitize(words, defaultWakeWords)
|
val sanitized = WakeWords.sanitize(words, defaultWakeWords)
|
||||||
val encoded =
|
val encoded =
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ final class BridgeConnectionController {
|
|||||||
private func makeHello(token: String) -> BridgeHello {
|
private func makeHello(token: String) -> BridgeHello {
|
||||||
let defaults = UserDefaults.standard
|
let defaults = UserDefaults.standard
|
||||||
let nodeId = defaults.string(forKey: "node.instanceId") ?? "ios-node"
|
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(
|
return BridgeHello(
|
||||||
nodeId: nodeId,
|
nodeId: nodeId,
|
||||||
@@ -139,6 +139,21 @@ final class BridgeConnectionController {
|
|||||||
caps: self.currentCaps())
|
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] {
|
private func currentCaps() -> [String] {
|
||||||
var caps = ["canvas"]
|
var caps = ["canvas"]
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ let package = Package(
|
|||||||
.product(name: "PeekabooBridge", package: "PeekabooCore"),
|
.product(name: "PeekabooBridge", package: "PeekabooCore"),
|
||||||
.product(name: "PeekabooAutomationKit", package: "PeekabooAutomationKit"),
|
.product(name: "PeekabooAutomationKit", package: "PeekabooAutomationKit"),
|
||||||
],
|
],
|
||||||
|
exclude: [
|
||||||
|
// Legacy web-based WebChat assets (SwiftUI-only WebChat doesn't use these).
|
||||||
|
"Resources/WebChat",
|
||||||
|
],
|
||||||
resources: [
|
resources: [
|
||||||
.copy("Resources/Clawdis.icns"),
|
.copy("Resources/Clawdis.icns"),
|
||||||
.copy("Resources/CanvasA2UI"),
|
.copy("Resources/CanvasA2UI"),
|
||||||
|
|||||||
40
docs/device-models.md
Normal file
40
docs/device-models.md
Normal 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
|
||||||
|
```
|
||||||
|
|
||||||
Reference in New Issue
Block a user