From 3350c4b1541d626189bc6554a774c6503e4c2f52 Mon Sep 17 00:00:00 2001 From: Luke Hoersten Date: Fri, 17 Jul 2026 20:01:45 -0500 Subject: scrypted: name viewports from discovery; editable name on settings page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating a viewport with the name field blank fell back to "viewport" with no way to change it afterward — display_name is deliberately the canonical name (v.name drifts on reload), so the Scrypted device-name pencil never propagated. Two fixes: - createDevice: an empty name now inherits the discovered device's advertised TXT name, so picking "10.0.13.83 — kitchen (…)" from the host dropdown names the viewport "kitchen" without retyping. - child settings gain a "Viewport name" field: updates display_name, mirrors the name onto the Scrypted device record, and re-registers so the firmware name + mDNS hostname follow. --- scrypted/scrypted-viewport.ts | 45 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/scrypted/scrypted-viewport.ts b/scrypted/scrypted-viewport.ts index 0c788da..bfb776d 100644 --- a/scrypted/scrypted-viewport.ts +++ b/scrypted/scrypted-viewport.ts @@ -423,6 +423,14 @@ class Viewport extends ScryptedDeviceBase implements Settings { // on any failure — manual entry always works). const discovered = await this.provider.browseCached(); const settings: Setting[] = [ + { + group: "Binding", + key: "display_name", + title: "Viewport name", + description: "Friendly routing key used in firmware registration and callbacks. Renaming re-registers the device (its mDNS hostname follows). This — not the Scrypted device-name pencil — is the name the firmware knows.", + placeholder: "mudroom", + value: this.storage.getItem("display_name") || this.name || "", + } as any, { group: "Binding", key: "host", @@ -601,6 +609,29 @@ class Viewport extends ScryptedDeviceBase implements Settings { } return; } + if (key === "display_name") { + // The stored display_name is the canonical name (v.name drifts + // on reload, so registration deliberately ignores it). Renames + // therefore must land here — update storage, mirror the name + // onto the Scrypted device record so the UI matches, and + // re-register so the firmware + its mDNS hostname follow. + const newName = String(value ?? "").trim(); + if (!newName) return; // name is load-bearing; ignore empty + this.storage.setItem("display_name", newName); + try { + await deviceManager.onDeviceDiscovered({ + providerNativeId: this.provider.nativeId, + nativeId: this.nativeId, + name: newName, + type: ScryptedDeviceType.SmartDisplay, + interfaces: [ScryptedInterface.Settings], + }); + } catch (e) { + this.console.warn(`rename: device record update failed: ${(e as Error).message}`); + } + await this.provider.onBindingChanged(this); + return; + } if (key === "triggers") { // multi-select arrives as array; serialise to JSON for storage. // Strip "doorbell" if the bound camera can't ring (mirror of @@ -963,7 +994,17 @@ class ScryptedViewportProvider extends ScryptedDeviceBase } async createDevice(settings: DeviceCreatorSettings): Promise { - const name = String(settings.name || "viewport").trim(); + const host = parseHostInput(settings.host); + // Name precedence: what the user typed > the discovered device's + // advertised TXT name (so picking "10.0.13.83 — kitchen (…)" from + // the dropdown names the viewport "kitchen" without retyping) > + // the "viewport" fallback. + let name = String(settings.name || "").trim(); + if (!name) { + const discovered = await this.browseCached(); + const d = discovered.find(x => x.ip === host || x.hostname === host); + name = d?.name || "viewport"; + } const nativeId = `vp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`; // 1. Register the device with Scrypted FIRST. deviceManager @@ -987,7 +1028,7 @@ class ScryptedViewportProvider extends ScryptedDeviceBase childStore.setItem("display_name", name); // A discovered-choice pick arrives as "ip — name (v..)"; store the // address token only. Manual IPs/hostnames pass through unchanged. - childStore.setItem("host", parseHostInput(settings.host)); + childStore.setItem("host", host); childStore.setItem("cameraId", String(settings.cameraId || "")); childStore.setItem("orientation", String(settings.orientation || "portrait")); // settings.triggers arrives as an array from the multi-select. -- cgit v1.2.3