feat(qconnect): restore the device-name setting from Tauri
Tauri let the user rename how QBZ announces itself to Qobuz Connect;
the Slint port lost the UI entirely while keeping the persistence and
the announce path (same device_name key in qconnect_settings.db, read
by connect() -> bootstrap_remote_presence). Restored 1:1: a text row
in Settings > Playback next to the QConnect startup select (LineEdit
mirroring the ListenBrainz token row, placeholder = the resolved
default 'Qbz - {hostname}'), a new settings-string callback channel
through the settings chain, and a live-service setter so a rename
takes effect on the next connection without an app restart — exactly
Tauri's semantics (no live re-announce; the row description says so).
Trim-only validation like Tauri; empty clears the override. Two new
msgids across the 8 locales.
cc
This commit is contained in:
16 files changed
+186
-1
No files matched your search
@@ -4482,3 +4482,10 @@ msgstr "Standardmäßig aus"
|
||||
|
||||
msgid "QBZ recovered from repeated startup crashes — some restored state was reset"
|
||||
msgstr "QBZ hat sich von wiederholten Startabstürzen erholt — einige wiederhergestellte Zustände wurden zurückgesetzt"
|
||||
|
||||
# QConnect device name (Settings > Playback, ported from Tauri)
|
||||
msgid "Qobuz Connect device name"
|
||||
msgstr "Qobuz Connect-Gerätename"
|
||||
|
||||
msgid "The name other Qobuz Connect apps see for this device. Applies on the next connection."
|
||||
msgstr "Der Name, den andere Qobuz Connect-Apps für dieses Gerät sehen. Wird bei der nächsten Verbindung übernommen."
|
||||
@@ -251,3 +251,10 @@ msgstr "Remove album from collection"
|
||||
|
||||
msgid "QBZ recovered from repeated startup crashes — some restored state was reset"
|
||||
msgstr "QBZ recovered from repeated startup crashes — some restored state was reset"
|
||||
|
||||
# QConnect device name (Settings > Playback, ported from Tauri)
|
||||
msgid "Qobuz Connect device name"
|
||||
msgstr "Qobuz Connect device name"
|
||||
|
||||
msgid "The name other Qobuz Connect apps see for this device. Applies on the next connection."
|
||||
msgstr "The name other Qobuz Connect apps see for this device. Applies on the next connection."
|
||||
@@ -4512,3 +4512,10 @@ msgstr "Desactivado de forma predeterminada"
|
||||
|
||||
msgid "QBZ recovered from repeated startup crashes — some restored state was reset"
|
||||
msgstr "QBZ se recuperó de fallos repetidos al iniciar — se restableció parte del estado guardado"
|
||||
|
||||
# QConnect device name (Settings > Playback, ported from Tauri)
|
||||
msgid "Qobuz Connect device name"
|
||||
msgstr "Nombre del dispositivo en Qobuz Connect"
|
||||
|
||||
msgid "The name other Qobuz Connect apps see for this device. Applies on the next connection."
|
||||
msgstr "El nombre que otras apps de Qobuz Connect ven para este dispositivo. Se aplica en la próxima conexión."
|
||||
@@ -4482,3 +4482,10 @@ msgstr "Désactivé par défaut"
|
||||
|
||||
msgid "QBZ recovered from repeated startup crashes — some restored state was reset"
|
||||
msgstr "QBZ s'est remis de plantages répétés au démarrage — une partie de l'état restauré a été réinitialisée"
|
||||
|
||||
# QConnect device name (Settings > Playback, ported from Tauri)
|
||||
msgid "Qobuz Connect device name"
|
||||
msgstr "Nom d'appareil Qobuz Connect"
|
||||
|
||||
msgid "The name other Qobuz Connect apps see for this device. Applies on the next connection."
|
||||
msgstr "Le nom que les autres applications Qobuz Connect voient pour cet appareil. Appliqué à la prochaine connexion."
|
||||
@@ -4471,3 +4471,10 @@ msgstr "デフォルトでオフ"
|
||||
|
||||
msgid "QBZ recovered from repeated startup crashes — some restored state was reset"
|
||||
msgstr "QBZ は起動時の連続クラッシュから復旧しました — 復元された状態の一部がリセットされました"
|
||||
|
||||
# QConnect device name (Settings > Playback, ported from Tauri)
|
||||
msgid "Qobuz Connect device name"
|
||||
msgstr "Qobuz Connectのデバイス名"
|
||||
|
||||
msgid "The name other Qobuz Connect apps see for this device. Applies on the next connection."
|
||||
msgstr "他のQobuz Connectアプリに表示されるこのデバイスの名前です。次回の接続時に適用されます。"
|
||||
@@ -4494,3 +4494,10 @@ msgstr "Standaard uit"
|
||||
|
||||
msgid "QBZ recovered from repeated startup crashes — some restored state was reset"
|
||||
msgstr "QBZ is hersteld van herhaalde crashes bij het opstarten — een deel van de herstelde staat is gereset"
|
||||
|
||||
# QConnect device name (Settings > Playback, ported from Tauri)
|
||||
msgid "Qobuz Connect device name"
|
||||
msgstr "Qobuz Connect-apparaatnaam"
|
||||
|
||||
msgid "The name other Qobuz Connect apps see for this device. Applies on the next connection."
|
||||
msgstr "De naam die andere Qobuz Connect-apps voor dit apparaat zien. Wordt toegepast bij de volgende verbinding."
|
||||
@@ -4482,3 +4482,10 @@ msgstr "Desligado por padrão"
|
||||
|
||||
msgid "QBZ recovered from repeated startup crashes — some restored state was reset"
|
||||
msgstr "O QBZ se recuperou de falhas repetidas na inicialização — parte do estado restaurado foi redefinida"
|
||||
|
||||
# QConnect device name (Settings > Playback, ported from Tauri)
|
||||
msgid "Qobuz Connect device name"
|
||||
msgstr "Nome do dispositivo no Qobuz Connect"
|
||||
|
||||
msgid "The name other Qobuz Connect apps see for this device. Applies on the next connection."
|
||||
msgstr "O nome que outros aplicativos Qobuz Connect veem para este dispositivo. Aplicado na próxima conexão."
|
||||
@@ -4495,3 +4495,10 @@ msgstr "Выключено по умолчанию"
|
||||
|
||||
msgid "QBZ recovered from repeated startup crashes — some restored state was reset"
|
||||
msgstr "QBZ восстановился после повторных сбоев при запуске — часть восстановленного состояния была сброшена"
|
||||
|
||||
# QConnect device name (Settings > Playback, ported from Tauri)
|
||||
msgid "Qobuz Connect device name"
|
||||
msgstr "Имя устройства в Qobuz Connect"
|
||||
|
||||
msgid "The name other Qobuz Connect apps see for this device. Applies on the next connection."
|
||||
msgstr "Имя, под которым это устройство видят другие приложения Qobuz Connect. Применяется при следующем подключении."
|
||||
@@ -98,6 +98,7 @@ export component AppWindow inherits Window {
|
||||
callback settings-bool(string, bool);
|
||||
callback settings-select(string, int);
|
||||
callback settings-slider(string, int);
|
||||
callback settings-string(string, string);
|
||||
callback settings-reset();
|
||||
callback settings-release-device();
|
||||
callback logout();
|
||||
@@ -158,6 +159,9 @@ export component AppWindow inherits Window {
|
||||
settings-slider(k, v) => {
|
||||
root.settings-slider(k, v);
|
||||
}
|
||||
settings-string(k, s) => {
|
||||
root.settings-string(k, s);
|
||||
}
|
||||
settings-reset => {
|
||||
root.settings-reset();
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
// SettingsState global and persisted through the Rust settings
|
||||
// controller.
|
||||
|
||||
import { LineEdit } from "std-widgets.slint";
|
||||
import { Theme } from "../foundation/semantic-colors.slint";
|
||||
import { Typography } from "../foundation/typography.slint";
|
||||
import { SettingsState } from "../state.slint";
|
||||
import { SettingsState, UiFocusState } from "../state.slint";
|
||||
import { QbzToggle } from "../primitives/QbzToggle.slint";
|
||||
import { QbzSelect } from "../primitives/QbzSelect.slint";
|
||||
import { QbzSlider } from "../primitives/QbzSlider.slint";
|
||||
@@ -27,6 +28,7 @@ export component PlaybackSettings inherits VerticalLayout {
|
||||
callback settings-bool(string, bool);
|
||||
callback settings-select(string, int);
|
||||
callback settings-slider(string, int);
|
||||
callback settings-string(string, string);
|
||||
|
||||
spacing: 4px;
|
||||
|
||||
@@ -112,6 +114,37 @@ export component PlaybackSettings inherits VerticalLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: @tr("Qobuz Connect device name");
|
||||
description: @tr("The name other Qobuz Connect apps see for this device. Applies on the next connection.");
|
||||
// Same fixed width as the select rows so the input right-aligns
|
||||
// flush with the rest of the panel (alignment standard).
|
||||
HorizontalLayout {
|
||||
width: 240px;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
LineEdit {
|
||||
text: SettingsState.qconnect-device-name;
|
||||
placeholder-text: SettingsState.qconnect-device-name-default;
|
||||
// Commit on focus loss too (mirrors the Tauri onchange
|
||||
// semantics: blur or Enter), not only on Enter.
|
||||
changed has-focus => {
|
||||
UiFocusState.text-input-focused = self.has-focus;
|
||||
if (!self.has-focus) {
|
||||
root.settings-string("qconnect-device-name", self.text);
|
||||
}
|
||||
}
|
||||
edited(s) => {
|
||||
SettingsState.qconnect-device-name = s;
|
||||
}
|
||||
accepted(s) => {
|
||||
root.settings-string("qconnect-device-name", s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { height: 12px; }
|
||||
Divider { }
|
||||
|
||||
@@ -67,6 +67,7 @@ export component SettingsView inherits Rectangle {
|
||||
callback settings-bool(string, bool);
|
||||
callback settings-select(string, int);
|
||||
callback settings-slider(string, int);
|
||||
callback settings-string(string, string);
|
||||
callback settings-reset();
|
||||
callback settings-release-device();
|
||||
|
||||
@@ -248,6 +249,9 @@ export component SettingsView inherits Rectangle {
|
||||
settings-slider(k, v) => {
|
||||
root.settings-slider(k, v);
|
||||
}
|
||||
settings-string(k, s) => {
|
||||
root.settings-string(k, s);
|
||||
}
|
||||
}
|
||||
// Appearance — a structural replica; rows bind to
|
||||
// AppearanceState and emit its own callbacks, so no
|
||||
|
||||
@@ -94,6 +94,7 @@ export component AppShell inherits Rectangle {
|
||||
callback settings-bool(string, bool);
|
||||
callback settings-select(string, int);
|
||||
callback settings-slider(string, int);
|
||||
callback settings-string(string, string);
|
||||
callback settings-reset();
|
||||
callback settings-release-device();
|
||||
callback logout();
|
||||
@@ -502,6 +503,9 @@ export component AppShell inherits Rectangle {
|
||||
settings-slider(k, v) => {
|
||||
root.settings-slider(k, v);
|
||||
}
|
||||
settings-string(k, s) => {
|
||||
root.settings-string(k, s);
|
||||
}
|
||||
settings-reset => {
|
||||
root.settings-reset();
|
||||
}
|
||||
|
||||
@@ -2891,6 +2891,12 @@ export global SettingsState {
|
||||
// Rust controller, which owns the index -> mode mapping.
|
||||
in-out property <[string]> qconnect-startup-modes: [];
|
||||
in-out property <int> qconnect-startup-index: 0;
|
||||
// Playback — Qobuz Connect device name. The persisted custom override
|
||||
// ("" = unset) plus the effective default name shown as the input
|
||||
// placeholder ("Qbz - {hostname}"). Backed by the QConnect settings DB
|
||||
// via the settings-string callback; applies on the next connect.
|
||||
in-out property <string> qconnect-device-name: "";
|
||||
in-out property <string> qconnect-device-name-default: "";
|
||||
|
||||
// --- OFFLINE MODE --------------------------------------------------------
|
||||
|
||||
|
||||
@@ -9029,6 +9029,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
});
|
||||
}
|
||||
|
||||
// Settings — a text input committed (QConnect device name): persist it
|
||||
// and refresh the live QConnect service cache; the new name is announced
|
||||
// on the next connect.
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let handle = tokio_rt.handle().clone();
|
||||
window.on_settings_string(move |key, value| {
|
||||
let weak = weak.clone();
|
||||
let key = key.to_string();
|
||||
let value = value.to_string();
|
||||
handle.spawn(async move {
|
||||
settings::handle_string(weak, key, value).await;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Settings — Reset: restore Audio + Playback defaults, rebuild the
|
||||
// snapshot, and re-apply the audio settings to the player.
|
||||
{
|
||||
|
||||
@@ -457,6 +457,16 @@ impl SlintQconnectService {
|
||||
self.inner.lock().await.runtime.is_some()
|
||||
}
|
||||
|
||||
/// Update the cached custom device name (Settings > Playback). The name
|
||||
/// is only announced by `bootstrap_remote_presence` during `connect()`,
|
||||
/// so — mirroring the Tauri `v2_qconnect_set_device_name` — a rename
|
||||
/// takes effect on the NEXT connect; the live session is untouched.
|
||||
/// `None` clears the override (falls back to "Qbz - {hostname}").
|
||||
/// Persistence is the caller's job (`qconnect_transport::persist_device_name`).
|
||||
pub async fn set_custom_device_name(&self, name: Option<String>) {
|
||||
*self.custom_device_name.write().await = name;
|
||||
}
|
||||
|
||||
/// Pure read of the live session for the Diagnostics panel (Settings >
|
||||
/// Developer). Mirrors the Tauri `snapshotQconnect`: reads `inner.last_error`,
|
||||
/// the transport-connected flag (`app.state_handle().transport_connected`),
|
||||
|
||||
@@ -187,6 +187,10 @@ pub struct SettingsSnapshot {
|
||||
retry_behavior_index: i32,
|
||||
qconnect_startup_modes: Vec<String>,
|
||||
qconnect_startup_index: i32,
|
||||
// QConnect device name — persisted custom override ("" = unset) + the
|
||||
// effective default used as the input's placeholder.
|
||||
qconnect_device_name: String,
|
||||
qconnect_device_name_default: String,
|
||||
// Now-playing output indicators (backend + effective bit-perfect mode).
|
||||
output_backend_label: String,
|
||||
output_mode_label: String,
|
||||
@@ -512,6 +516,13 @@ fn build_snapshot(
|
||||
.iter()
|
||||
.position(|(_, m)| *m == qconnect_startup_mode)
|
||||
.unwrap_or(QCONNECT_STARTUP_MODES.len() - 1); // last entry = Off (default)
|
||||
// QConnect device name — same DB (and same blocking-SQLite caveat) as the
|
||||
// startup mode above. Empty = no custom override; the placeholder shows
|
||||
// the name that will actually be announced (env var -> "Qbz - {hostname}").
|
||||
let qconnect_device_name =
|
||||
crate::qconnect_transport::load_persisted_device_name().unwrap_or_default();
|
||||
let qconnect_device_name_default =
|
||||
crate::qconnect_transport::resolve_qconnect_friendly_name(None);
|
||||
|
||||
let backend_is_alsa = active_backend == AudioBackendType::Alsa;
|
||||
let backend_is_pipewire = active_backend == AudioBackendType::PipeWire;
|
||||
@@ -583,6 +594,8 @@ fn build_snapshot(
|
||||
.map(|(l, _)| qbz_i18n::t(l))
|
||||
.collect(),
|
||||
qconnect_startup_index: qconnect_startup_index as i32,
|
||||
qconnect_device_name,
|
||||
qconnect_device_name_default,
|
||||
output_backend_label: out_backend_label,
|
||||
output_mode_label: out_mode_label,
|
||||
output_backend_active: out_backend_active,
|
||||
@@ -673,6 +686,8 @@ pub fn apply_snapshot(window: &AppWindow, snap: SettingsSnapshot) {
|
||||
st.set_retry_behavior_index(snap.retry_behavior_index);
|
||||
st.set_qconnect_startup_modes(string_model(snap.qconnect_startup_modes));
|
||||
st.set_qconnect_startup_index(snap.qconnect_startup_index);
|
||||
st.set_qconnect_device_name(snap.qconnect_device_name.into());
|
||||
st.set_qconnect_device_name_default(snap.qconnect_device_name_default.into());
|
||||
st.set_loading(false);
|
||||
}
|
||||
|
||||
@@ -1068,6 +1083,43 @@ pub fn handle_slider(
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a text-input commit (Enter or focus loss). Currently only the
|
||||
/// QConnect device name — mirrors the Tauri `v2_qconnect_set_device_name`:
|
||||
/// trim; empty clears the override so the announced name falls back to the
|
||||
/// default ("Qbz - {hostname}"). Persisted in the QConnect settings DB and
|
||||
/// pushed into the live service's cache; the name is only announced during
|
||||
/// `connect()`, so a rename takes effect on the next connection.
|
||||
pub async fn handle_string(weak: slint::Weak<AppWindow>, key: String, value: String) {
|
||||
match key.as_str() {
|
||||
"qconnect-device-name" => {
|
||||
let trimmed = value.trim().to_string();
|
||||
let stored = (!trimmed.is_empty()).then(|| trimmed.clone());
|
||||
// Persist (blocking SQLite) off the async runtime.
|
||||
let to_persist = stored.clone();
|
||||
if let Err(e) = tokio::task::spawn_blocking(move || {
|
||||
crate::qconnect_transport::persist_device_name(to_persist.as_deref())
|
||||
})
|
||||
.await
|
||||
{
|
||||
log::error!("[qbz-slint] persist qconnect device name failed: {e}");
|
||||
}
|
||||
// Update the live service cache so the next connect announces the
|
||||
// new name without an app restart (it loads the DB only once, at
|
||||
// construction).
|
||||
if let Some(svc) = crate::qconnect_service::service() {
|
||||
svc.set_custom_device_name(stored).await;
|
||||
}
|
||||
// Push the trimmed value back so a whitespace-only entry visibly
|
||||
// resets the input to the placeholder/default state.
|
||||
let _ = weak.upgrade_in_event_loop(move |w| {
|
||||
w.global::<SettingsState>()
|
||||
.set_qconnect_device_name(trimmed.into());
|
||||
});
|
||||
}
|
||||
other => log::warn!("[qbz-slint] unknown settings string key: {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a dropdown change: persist it, apply audio ones to the player,
|
||||
/// and — for a backend switch — re-enumerate devices and recompute the
|
||||
/// conditional flags into `SettingsState`.
|
||||
|
||||
Reference in new issue
Block a user