diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index cd6ae600e..b3fb84e32 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -7,6 +7,7 @@ from openpilot.selfdrive.ui.widgets.offroad_alerts import UpdateAlert, OffroadAl from openpilot.selfdrive.ui.widgets.exp_mode_button import ExperimentalModeButton from openpilot.selfdrive.ui.widgets.prime import PrimeWidget from openpilot.selfdrive.ui.widgets.setup import SetupWidget +from openpilot.selfdrive.ui.lib.starpilot_version import starpilot_display_description from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr, trn @@ -228,5 +229,5 @@ class HomeLayout(Widget): def _get_version_text(self) -> str: brand = "openpilot" - description = self.params.get("UpdaterCurrentDescription") + description = starpilot_display_description(self.params.get("UpdaterCurrentDescription")) return f"{brand} {description}" if description else brand diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index d00a5038f..39efbe4a4 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -3,6 +3,7 @@ import time import datetime from pathlib import Path from openpilot.common.time_helpers import system_time_valid +from openpilot.selfdrive.ui.lib.starpilot_version import starpilot_display_description from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr, trn @@ -54,7 +55,7 @@ class SoftwareLayout(Widget): super().__init__() self._onroad_label = ListItem(lambda: tr("Updates are only downloaded while the car is off.")) - self._version_item = text_item(lambda: tr("Current Version"), ui_state.params.get("UpdaterCurrentDescription") or "") + self._version_item = text_item(lambda: tr("Current Version"), starpilot_display_description(ui_state.params.get("UpdaterCurrentDescription"))) self._auto_updates_toggle = toggle_item( lambda: tr("Automatically Install Updates"), lambda: tr("Automatically install updates when parked with an active internet connection."), @@ -103,7 +104,7 @@ class SoftwareLayout(Widget): self._onroad_label.set_visible(ui_state.is_onroad()) # Update current version and release notes - current_desc = ui_state.params.get("UpdaterCurrentDescription") or "" + current_desc = starpilot_display_description(ui_state.params.get("UpdaterCurrentDescription")) current_release_notes = (ui_state.params.get("UpdaterCurrentReleaseNotes") or b"").decode("utf-8", "replace") self._version_item.action_item.set_text(current_desc) self._version_item.set_description(current_release_notes) @@ -154,7 +155,7 @@ class SoftwareLayout(Widget): # Update install button self._install_btn.set_visible(ui_state.is_offroad() and update_available) if update_available: - new_desc = ui_state.params.get("UpdaterNewDescription") or "" + new_desc = starpilot_display_description(ui_state.params.get("UpdaterNewDescription")) new_release_notes = (ui_state.params.get("UpdaterNewReleaseNotes") or b"").decode("utf-8", "replace") self._install_btn.action_item.set_text(tr("INSTALL")) self._install_btn.action_item.set_value(new_desc) diff --git a/selfdrive/ui/lib/starpilot_version.py b/selfdrive/ui/lib/starpilot_version.py new file mode 100644 index 000000000..eb1abc669 --- /dev/null +++ b/selfdrive/ui/lib/starpilot_version.py @@ -0,0 +1,13 @@ +STARPILOT_DISPLAY_VERSION = "7.26" + + +def starpilot_display_description(description: str | None) -> str: + if not description: + return "" + + parts = [part.strip() for part in description.split(" / ")] + if not parts: + return STARPILOT_DISPLAY_VERSION + + parts[0] = STARPILOT_DISPLAY_VERSION + return " / ".join(parts) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 820afd04c..d5106cb67 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -10,6 +10,7 @@ from openpilot.system.ui.widgets.layouts import HBoxLayout from openpilot.system.ui.widgets.icon_widget import IconWidget from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.selfdrive.ui.lib.starpilot_version import STARPILOT_DISPLAY_VERSION from openpilot.selfdrive.ui.ui_state import ui_state HEAD_BUTTON_FONT_SIZE = 40 @@ -166,11 +167,10 @@ class MiciHomeLayout(Widget): self._did_long_press = False def _get_version_text(self) -> tuple[str, str, str, str] | None: - version = ui_state.params.get("Version") branch = ui_state.params.get("GitBranch") commit = ui_state.params.get("GitCommit") - if not all((version, branch, commit)): + if not all((branch, commit)): return None commit_date_raw = ui_state.params.get("GitCommitDate") @@ -181,7 +181,7 @@ class MiciHomeLayout(Widget): except (ValueError, IndexError, TypeError, AttributeError): date_str = "" - return version, branch, commit[:7], date_str + return STARPILOT_DISPLAY_VERSION, branch, commit[:7], date_str def _render(self, _): # TODO: why is there extra space here to get it to be flush? diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index 0dae5d207..f2952214b 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -5,6 +5,7 @@ from dataclasses import dataclass from enum import IntEnum from openpilot.common.params import Params from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS +from openpilot.selfdrive.ui.lib.starpilot_version import STARPILOT_DISPLAY_VERSION from openpilot.system.hardware import HARDWARE from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import UnifiedLabel @@ -252,7 +253,8 @@ class MiciOffroadAlerts(Scroller): # format: "version / branch / commit / date" parts = new_desc.split(" / ") if len(parts) > 3: - version, date = parts[0], parts[3] + date = parts[3] + version = STARPILOT_DISPLAY_VERSION version_string = f"\nopenpilot {version}, {date}\n" update_alert_data.text = f"Update available {version_string}. Click to update. Read the release notes at blog.comma.ai." diff --git a/selfdrive/ui/mici/layouts/settings/software.py b/selfdrive/ui/mici/layouts/settings/software.py index 34c82c4a9..caec6bdca 100644 --- a/selfdrive/ui/mici/layouts/settings/software.py +++ b/selfdrive/ui/mici/layouts/settings/software.py @@ -6,6 +6,7 @@ from enum import IntEnum import pyray as rl from openpilot.common.time_helpers import system_time_valid +from openpilot.selfdrive.ui.lib.starpilot_version import STARPILOT_DISPLAY_VERSION from openpilot.selfdrive.ui.mici.layouts.settings.device import EngagedConfirmationButton from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigParamControl from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog, BigDialog @@ -71,11 +72,11 @@ class SoftwareInfoLayoutMici(Widget): def _update_state(self): desc = _split_description(ui_state.params.get("UpdaterCurrentDescription") or "") if desc is not None: - version, branch, commit, date = desc - self._version_text_label.set_text(f"{version} ({date})") + _, branch, commit, date = desc + self._version_text_label.set_text(f"{STARPILOT_DISPLAY_VERSION} ({date})") self._branch_text_label.set_text(f"{branch} ({commit})") else: - self._version_text_label.set_text(ui_state.params.get("Version") or "N/A") + self._version_text_label.set_text(STARPILOT_DISPLAY_VERSION if ui_state.params.get("Version") else "N/A") self._branch_text_label.set_text(ui_state.params.get("GitBranch") or "N/A") def _render(self, _): @@ -201,7 +202,7 @@ class InstallUpdateButton(BigButton): super()._update_state() desc = _split_description(ui_state.params.get("UpdaterNewDescription") or "") - value = f"{desc[0]} ({desc[1]})" if desc is not None else "" + value = f"{STARPILOT_DISPLAY_VERSION} ({desc[1]})" if desc is not None else "" if self.get_value() != value: self.set_value(value) diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index fe5de1476..e04c6e8bd 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -337,12 +337,8 @@ void OffroadHome::refresh() { date->setVisible(util::system_time_valid() && !simple_mode); if (simple_mode) { - version->setText(getBrand() + " " + QString::fromStdString(params.get("UpdaterCurrentDescription"))); + version->setText(getBrand() + " " + formatStarPilotDisplayVersionDescription(QString::fromStdString(params.get("UpdaterCurrentDescription")))); } else { - QString versionText = getVersion().left(14).trimmed(); - if (!versionText.startsWith("v", Qt::CaseInsensitive)) { - versionText.prepend("v"); - } - version->setText(getBrand() + " - " + versionText + " - " + cleanModelName(starpilot_toggles.value("model_name").toString())); + version->setText(getBrand() + " - " + getStarPilotDisplayVersion() + " - " + cleanModelName(starpilot_toggles.value("model_name").toString())); } } diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 62d6ba1a9..efb595286 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -191,11 +191,11 @@ void SoftwarePanel::updateLabels() { targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch"))); // current + new versions - versionLbl->setText(QString::fromStdString(params.get("UpdaterCurrentDescription"))); + versionLbl->setText(formatStarPilotDisplayVersionDescription(QString::fromStdString(params.get("UpdaterCurrentDescription")))); versionLbl->setDescription(QString::fromStdString(params.get("UpdaterCurrentReleaseNotes"))); installBtn->setVisible((!is_onroad || parked) && params.getBool("UpdateAvailable")); - installBtn->setValue(QString::fromStdString(params.get("UpdaterNewDescription"))); + installBtn->setValue(formatStarPilotDisplayVersionDescription(QString::fromStdString(params.get("UpdaterNewDescription")))); installBtn->setDescription(QString::fromStdString(params.get("UpdaterNewReleaseNotes"))); update(); diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index c1178a9e8..64240fdcb 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,24 @@ QString getBrand() { return QObject::tr("StarPilot"); } +QString getStarPilotDisplayVersion() { + return "7.26"; +} + +QString formatStarPilotDisplayVersionDescription(const QString &description) { + if (description.isEmpty()) { + return ""; + } + + QStringList parts = description.split(" / "); + if (parts.isEmpty()) { + return getStarPilotDisplayVersion(); + } + + parts[0] = getStarPilotDisplayVersion(); + return parts.join(" / "); +} + QString getUserAgent() { return "openpilot-" + getVersion(); } diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h index 2bf1a70a6..9d62b34cc 100644 --- a/selfdrive/ui/qt/util.h +++ b/selfdrive/ui/qt/util.h @@ -15,6 +15,8 @@ QString getVersion(); QString getBrand(); +QString getStarPilotDisplayVersion(); +QString formatStarPilotDisplayVersionDescription(const QString &description); QString getUserAgent(); std::optional getDongleId(); QMap getSupportedLanguages(); diff --git a/starpilot/system/the_galaxy/assets/components/navigation/navigation_keys.css b/starpilot/system/the_galaxy/assets/components/navigation/navigation_keys.css index 65fbcce9d..b28794beb 100644 --- a/starpilot/system/the_galaxy/assets/components/navigation/navigation_keys.css +++ b/starpilot/system/the_galaxy/assets/components/navigation/navigation_keys.css @@ -2,6 +2,7 @@ all: unset; background-color: var(--main-fg); border-radius: var(--border-radius-sm); + color: var(--text-on-primary); cursor: pointer; padding: var(--padding-xs) var(--padding-xs); transition: background-color var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-fast); @@ -41,6 +42,37 @@ transform: var(--hover-scale-sm); } +.navkeys-app-actions { + display: flex; + justify-content: flex-end; + margin-bottom: var(--margin-base); +} + +.navkeys-app-container { + flex-basis: min(100%, calc((var(--width-lg) * 2) + var(--gap-lg))); + max-width: calc((var(--width-lg) * 2) + var(--gap-lg)); +} + +.navkeys-copy-btn, +.navkeys-link-btn { + align-items: center; + display: inline-flex; + gap: var(--gap-xs); + justify-content: center; + white-space: nowrap; +} + +.navkeys-copy-btn { + min-width: 5.5rem; +} + +.navkeys-icon-btn { + align-items: center; + display: inline-flex; + justify-content: center; + min-width: 2.25rem; +} + .navkeys-error { color: var(--danger-bg); font-size: var(--font-size-sm); @@ -72,6 +104,7 @@ cursor: text; flex: 1; font-size: var(--font-size-base); + min-width: 0; padding: var(--padding-xs) var(--padding-sm); transition: border-color var(--transition-fast), box-shadow var(--transition-fast), transform var(--transition-fast); } @@ -87,6 +120,11 @@ color: var(--text-muted); } +.navkeys-token-input { + font-family: var(--font-mono); + font-size: var(--font-size-sm); +} + .navkeys-label { display: block; font-size: var(--font-size-sm); @@ -94,6 +132,10 @@ margin-bottom: var(--margin-xs); } +.navkeys-link-btn { + text-decoration: none; +} + .navkeys-message { color: var(--success-bg); font-size: var(--font-size-sm); diff --git a/starpilot/system/the_galaxy/assets/components/navigation/navigation_keys.js b/starpilot/system/the_galaxy/assets/components/navigation/navigation_keys.js index 67ba52415..67e5c387a 100644 --- a/starpilot/system/the_galaxy/assets/components/navigation/navigation_keys.js +++ b/starpilot/system/the_galaxy/assets/components/navigation/navigation_keys.js @@ -1,5 +1,7 @@ import { html, reactive } from "/assets/vendor/arrow-core.js" import { Modal } from "/assets/components/modal.js"; + +const DEFAULT_PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=com.embaucha.galaxynav&hl=en-US&ah=9FldHJ99kxL8oNbSlO5F4sQqwC4" export function NavKeys() { const state = reactive({ @@ -20,6 +22,12 @@ export function NavKeys() { publicKey: "", secretKey: "", editPublic: false, editSecret: false, savedPublic: false, savedSecret: false, + + galaxyCookieName: "galaxy_session", + galaxySessionToken: "", + galaxyAppUrl: DEFAULT_PLAY_STORE_URL, + galaxyPaired: false, + galaxySessionVisible: false, showDeleteModal: false, keyToDelete: null, @@ -60,6 +68,34 @@ export function NavKeys() { req: async (url, opts) => { const response = await fetch(url, opts) return { ok: response.ok, data: await response.json().catch(() => ({})) } + }, + + copyText: async (text) => { + if (!text) { + throw new Error("Nothing to copy") + } + + if (navigator.clipboard?.writeText && window.isSecureContext) { + await navigator.clipboard.writeText(text) + return + } + + const textarea = document.createElement("textarea") + textarea.value = text + textarea.setAttribute("readonly", "") + textarea.style.position = "fixed" + textarea.style.left = "-9999px" + textarea.style.opacity = "0" + document.body.appendChild(textarea) + textarea.select() + + try { + if (!document.execCommand("copy")) { + throw new Error("Copy command failed") + } + } finally { + textarea.remove() + } } } @@ -96,6 +132,7 @@ export function NavKeys() { const api = { path: { + galaxy: "/api/galaxy/session", key: "/api/navigation_key", nav: "/api/navigation" }, @@ -103,7 +140,9 @@ export function NavKeys() { load: async () => { const { ok, data } = await util.req(api.path.nav) if (!ok) { - return showMessage("error", "Failed to load keys...", "") + showMessage("error", "Failed to load keys...", "") + await api.loadGalaxySession() + return } state.amap1Key = data.amap1Key ?? "" @@ -119,6 +158,29 @@ export function NavKeys() { state.initialMapboxComplete = state.savedPublic && state.savedSecret bumpImageVersion() + await api.loadGalaxySession() + }, + + loadGalaxySession: async () => { + const { ok, data } = await util.req(api.path.galaxy) + if (!ok) { + return showMessage("error", "Failed to load Galaxy session...", "app") + } + + state.galaxyAppUrl = data.appUrl || DEFAULT_PLAY_STORE_URL + state.galaxyCookieName = data.cookieName || "galaxy_session" + state.galaxyPaired = !!data.paired + state.galaxySessionToken = data.sessionToken || "" + state.galaxySessionVisible = false + }, + + copyGalaxySession: async () => { + try { + await util.copyText(state.galaxySessionToken) + showMessage("message", "Session token copied!", "app") + } catch (e) { + showMessage("error", "Copy failed...", "app") + } }, save: (kind) => async () => { @@ -295,6 +357,60 @@ export function NavKeys() { ` } + function renderAppKeys() { + return html` + + + + + + + + + + ` + } + return html`