From 24687b1f794b49ed3a1601a9b0846ad5ade2e087 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:17:58 -0600 Subject: [PATCH] Customizable Boot Logo --- common/params.cc | 3 + frogpilot/assets/theme_manager.py | 99 ++++++++++++++++++++--- frogpilot/common/frogpilot_variables.py | 2 + frogpilot/ui/qt/offroad/theme_settings.cc | 59 ++++++++++++++ frogpilot/ui/qt/offroad/theme_settings.h | 7 +- 5 files changed, 157 insertions(+), 13 deletions(-) diff --git a/common/params.cc b/common/params.cc index fe80e6091b..a444fd2d4b 100644 --- a/common/params.cc +++ b/common/params.cc @@ -247,6 +247,7 @@ std::unordered_map keys = { {"BlacklistedModels", PERSISTENT}, {"BlindSpotMetrics", PERSISTENT}, {"BlindSpotPath", PERSISTENT}, + {"BootLogo", PERSISTENT}, {"BorderMetrics", PERSISTENT}, {"CalibratedLateralAcceleration", PERSISTENT}, {"CalibrationProgress", PERSISTENT}, @@ -272,6 +273,7 @@ std::unordered_map keys = { {"CEStatus", CLEAR_ON_OFFROAD_TRANSITION}, {"CEStoppedLead", PERSISTENT}, {"ClusterOffset", PERSISTENT}, + {"BootLogoToDownload", CLEAR_ON_MANAGER_START}, {"ColorToDownload", CLEAR_ON_MANAGER_START}, {"Compass", PERSISTENT}, {"ConditionalExperimental", PERSISTENT}, @@ -310,6 +312,7 @@ std::unordered_map keys = { {"DisengageVolume", PERSISTENT}, {"DoToggleReset", PERSISTENT}, {"DoToggleResetStock", PERSISTENT}, + {"DownloadableBootLogos", PERSISTENT}, {"DownloadableColors", PERSISTENT}, {"DownloadableDistanceIcons", PERSISTENT}, {"DownloadableIcons", PERSISTENT}, diff --git a/frogpilot/assets/theme_manager.py b/frogpilot/assets/theme_manager.py index 50ba3c22ba..c674e814f3 100644 --- a/frogpilot/assets/theme_manager.py +++ b/frogpilot/assets/theme_manager.py @@ -37,6 +37,7 @@ HOLIDAY_SLUGS = { } THEME_COMPONENT_PARAMS = { + "boot_logos": "BootLogoToDownload", "colors": "ColorToDownload", "distance_icons": "DistanceIconToDownload", "icons": "IconToDownload", @@ -93,6 +94,13 @@ class ThemeManager: steering_wheel_save_path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(steering_wheel_image_path, steering_wheel_save_path) + default_boot_logo_path = Path(__file__).parent / "other_images/frogpilot_boot_logo.png" + for default_name in ("starpilot.png", "frogpilot.png"): + boot_logo_save_path = THEME_SAVE_PATH / "bootlogos" / default_name + boot_logo_save_path.parent.mkdir(parents=True, exist_ok=True) + if not boot_logo_save_path.exists(): + shutil.copy2(default_boot_logo_path, boot_logo_save_path) + def download_theme(self, theme_component, theme_name, asset_param, frogpilot_toggles): self.downloading_theme = True @@ -102,7 +110,11 @@ class ThemeManager: self.downloading_theme = False return - if theme_component == "distance_icons": + if theme_component == "boot_logos": + download_link = f"{repo_url}/Themes/bootlogo/{theme_name}" + download_path = THEME_SAVE_PATH / "bootlogos" / theme_name + extensions = [".png", ".jpg", ".jpeg"] + elif theme_component == "distance_icons": download_link = f"{repo_url}/Distance-Icons/{theme_name}" download_path = THEME_SAVE_PATH / "theme_packs" / theme_name / theme_component extensions = [".zip"] @@ -158,7 +170,7 @@ class ThemeManager: repo_encoded = quote_plus(RESOURCES_REPO) - assets = {"themes": {}, "wheels": []} + assets = {"boot_logos": [], "themes": {}, "wheels": []} try: def list_files(branch): if is_github: @@ -234,6 +246,20 @@ class ThemeManager: theme_name, sub_path = item["path"].split("/", 1) theme_path = sub_path.lower() + if theme_name.lower() == "bootlogo": + if Path(sub_path).suffix.lower() not in (".png", ".jpg", ".jpeg"): + continue + + assets["boot_logos"].append(sub_path) + logo_name = Path(sub_path).stem + local_files = list((THEME_SAVE_PATH / "bootlogos").glob(f"{logo_name}.*")) + if local_files and expected_size > 0: + local_size = self.theme_sizes.get("boot_logos", {}).get(logo_name) + if local_size != expected_size: + print(f"boot logo {logo_name} is outdated, redownloading...") + self.download_theme("boot_logos", logo_name, THEME_COMPONENT_PARAMS["boot_logos"], frogpilot_toggles) + continue + for key in ("colors", "icons", "signals", "sounds"): if key in theme_path: assets["themes"].setdefault(theme_name, set()).add(key) @@ -246,6 +272,7 @@ class ThemeManager: self.download_theme(key, theme_name, THEME_COMPONENT_PARAMS[key], frogpilot_toggles) break + assets["boot_logos"].sort() assets["themes"] = {key: sorted(list(value)) for key, value in assets["themes"].items()} assets["wheels"].sort() return assets @@ -265,7 +292,7 @@ class ThemeManager: parts = base.replace("_", "-").split("-") capitalized_parts = [part.capitalize() for part in parts if part] - if len(capitalized_parts) > 1 and component != "steering_wheels": + if len(capitalized_parts) > 1 and component not in {"boot_logos", "steering_wheels"}: display = f"{capitalized_parts[0]} ({' '.join(capitalized_parts[1:])})" else: display = " ".join(capitalized_parts) @@ -321,7 +348,9 @@ class ThemeManager: } def handle_verification_failure(self, extension, theme_component, theme_name, asset_param, theme_path, download_path, frogpilot_toggles): - if theme_component == "distance_icons": + if theme_component == "boot_logos": + download_link = f"{GITLAB_URL}/Themes/bootlogo/{theme_name}" + elif theme_component == "distance_icons": download_link = f"{GITLAB_URL}/Distance-Icons/{theme_name}" elif theme_component == "steering_wheels": download_link = f"{GITLAB_URL}/Steering-Wheels/{theme_name}" @@ -350,8 +379,7 @@ class ThemeManager: self.update_themes(frogpilot_toggles) return True - handle_error(None, "Download failed...", "Download failed...", asset_param, DOWNLOAD_PROGRESS_PARAM, params_memory) - self.downloading_theme = False + # Let the caller continue trying alternate extensions before surfacing failure. return False @staticmethod @@ -415,6 +443,8 @@ class ThemeManager: return random.choice(candidates) if candidates else "stock" def update_active_theme(self, time_validated, frogpilot_toggles, boot_run=False, randomize_theme=False): + boot_logo = getattr(frogpilot_toggles, "boot_logo", "stock") + if time_validated and frogpilot_toggles.holiday_themes: self.holiday_theme = self.update_holiday() else: @@ -422,6 +452,7 @@ class ThemeManager: if self.holiday_theme != "stock": asset_mappings = { + "boot_logo": ("boot_logo", boot_logo), "color_scheme": ("colors", self.holiday_theme), "distance_icons": ("distance_icons", self.holiday_theme), "icon_pack": ("icons", self.holiday_theme), @@ -434,6 +465,7 @@ class ThemeManager: selected_theme = self.randomize_theme_asset(available_themes) asset_mappings = { + "boot_logo": ("boot_logo", boot_logo), "color_scheme": ("colors", selected_theme.replace("-animated", "")), "distance_icons": ("distance_icons", self.randomize_distance_icons(available_themes, selected_theme.replace("-animated", ""))), "icon_pack": ("icons", selected_theme), @@ -444,6 +476,7 @@ class ThemeManager: elif not frogpilot_toggles.random_themes: asset_mappings = { + "boot_logo": ("boot_logo", boot_logo), "color_scheme": ("colors", frogpilot_toggles.color_scheme), "distance_icons": ("distance_icons", frogpilot_toggles.distance_icons), "icon_pack": ("icons", frogpilot_toggles.icon_pack), @@ -458,7 +491,9 @@ class ThemeManager: for asset, (asset_type, current_value) in asset_mappings.items(): print(f"Updating {asset}: {asset_type} with value {current_value}") - if asset_type == "wheel_image": + if asset_type == "boot_logo": + self.update_boot_logo(current_value) + elif asset_type == "wheel_image": self.update_wheel_image(current_value, boot_run=boot_run) else: self.update_theme_asset(asset_type, current_value, boot_run=boot_run) @@ -499,9 +534,12 @@ class ThemeManager: save_location.symlink_to(asset_location, target_is_directory=True) print(f"Linked {save_location} to {asset_location}") - def update_theme_params(self, downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels): + def update_theme_params(self, downloadable_boot_logos, downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels): def update_param(key, assets, subfolder): - if subfolder == "steering_wheels": + if subfolder == "boot_logos": + themes_path = THEME_SAVE_PATH / "bootlogos" + existing_assets = {self.format_name(item.name, "boot_logos") for item in themes_path.glob("*") if item.is_file()} + elif subfolder == "steering_wheels": themes_path = THEME_SAVE_PATH / subfolder existing_assets = {self.format_name(item.name, "steering_wheels") for item in themes_path.glob("*") if item.is_file()} else: @@ -511,6 +549,7 @@ class ThemeManager: params.put(key, ",".join(sorted(set(assets) - existing_assets))) print(f"{key} updated successfully") + update_param("DownloadableBootLogos", downloadable_boot_logos, "boot_logos") update_param("DownloadableColors", downloadable_colors, "colors") update_param("DownloadableDistanceIcons", downloadable_distance_icons, "distance_icons") update_param("DownloadableIcons", downloadable_icons, "icons") @@ -529,12 +568,20 @@ class ThemeManager: theme_name = self.format_name(theme_dir.name, "theme_packs") downloaded_themes[theme_name] = sorted(components) + (THEME_SAVE_PATH / "bootlogos").mkdir(parents=True, exist_ok=True) + + downloaded_boot_logos = [] + for boot_logo_file in (THEME_SAVE_PATH / "bootlogos").iterdir(): + if boot_logo_file.is_file(): + downloaded_boot_logos.append(self.format_name(boot_logo_file.name, "boot_logos")) + downloaded_wheels = [] for wheel_file in (THEME_SAVE_PATH / "steering_wheels").iterdir(): if wheel_file.is_file(): downloaded_wheels.append(self.format_name(wheel_file.name, "steering_wheels")) params.put("ThemesDownloaded", json.dumps({ + "boot_logos": sorted(downloaded_boot_logos), "themes": {key: downloaded_themes[key] for key in sorted(downloaded_themes)}, "steering_wheels": sorted(downloaded_wheels) })) @@ -542,7 +589,9 @@ class ThemeManager: print("ThemesDownloaded updated successfully") def update_theme_size(self, theme_component, theme_name, file_size): - if theme_component == "steering_wheels": + if theme_component == "boot_logos": + key = "boot_logos" + elif theme_component == "steering_wheels": key = "wheels" else: key = "themes" @@ -550,7 +599,7 @@ class ThemeManager: if key not in self.theme_sizes: self.theme_sizes[key] = {} - if key == "wheels": + if key in {"boot_logos", "wheels"}: self.theme_sizes[key][theme_name] = file_size else: if theme_name not in self.theme_sizes[key]: @@ -572,6 +621,7 @@ class ThemeManager: if not assets: return + downloadable_boot_logos = [] downloadable_colors = [] downloadable_distance_icons = [] downloadable_icons = [] @@ -593,8 +643,10 @@ class ThemeManager: if "sounds" in available_assets: downloadable_sounds.append(theme_name) + downloadable_boot_logos = [self.format_name(boot_logo, "boot_logos") for boot_logo in assets["boot_logos"]] downloadable_wheels = [self.format_name(wheel, "steering_wheels") for wheel in assets["wheels"]] + print(f"Downloadable Boot Logos: {downloadable_boot_logos}") print(f"Downloadable Colors: {downloadable_colors}") print(f"Downloadable Icons: {downloadable_icons}") print(f"Downloadable Signals: {downloadable_signals}") @@ -605,7 +657,21 @@ class ThemeManager: if boot_run: self.validate_themes(downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels, frogpilot_toggles) - self.update_theme_params(downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels) + self.update_theme_params(downloadable_boot_logos, downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels) + + @staticmethod + def update_boot_logo(image): + boot_logo_location = Path(__file__).parent / "other_images/frogpilot_boot_logo.png" + image_name = image.replace(" ", "_").lower() + source_file = next((file for file in (THEME_SAVE_PATH / "bootlogos").glob("*") if file.is_file() and file.stem.lower() == image_name), boot_logo_location) + + # Avoid SameFileError when the selected/fallback source is already the active boot logo. + if source_file.resolve() == boot_logo_location.resolve(): + print(f"Boot logo unchanged: {boot_logo_location}") + return + + shutil.copy2(source_file, boot_logo_location) + print(f"Copied {source_file} to {boot_logo_location}") def update_wheel_image(self, image, boot_run=False, random_event=False): wheel_save_location = ACTIVE_THEME_PATH / "steering_wheel" @@ -641,6 +707,15 @@ class ThemeManager: def validate_themes(self, downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels, frogpilot_toggles): downloaded_data = json.loads(params.get("ThemesDownloaded") or "{}") + boot_logos_path = THEME_SAVE_PATH / "bootlogos" + for display_name in downloaded_data.get("boot_logos", []): + file_stem = display_name.replace(" ", "_").lower() + matching_files = list(boot_logos_path.glob(f"{file_stem}.*")) + if not matching_files: + print(f"Missing boot logo '{display_name}'. Downloading...") + self.download_theme("boot_logos", file_stem, THEME_COMPONENT_PARAMS["boot_logos"], frogpilot_toggles) + self.update_active_theme(True, frogpilot_toggles) + for display_name, components in downloaded_data.get("themes", {}).items(): raw_name = display_name.lower().replace(" ", "_").replace("(", "").replace(")", "") theme_folder_name = raw_name.replace("_animated", "-animated") diff --git a/frogpilot/common/frogpilot_variables.py b/frogpilot/common/frogpilot_variables.py index 589b222f92..30ee417454 100644 --- a/frogpilot/common/frogpilot_variables.py +++ b/frogpilot/common/frogpilot_variables.py @@ -160,6 +160,7 @@ frogpilot_default_params: list[tuple[str, str | bytes, int, str]] = [ ("BlacklistedModels", "", 2, ""), ("BlindSpotMetrics", "1", 3, "0"), ("BlindSpotPath", "1", 1, "0"), + ("BootLogo", "starpilot", 0, "stock"), ("BorderMetrics", "1", 3, "0"), ("CalibratedLateralAcceleration", str(DEFAULT_LATERAL_ACCELERATION), 2, str(DEFAULT_LATERAL_ACCELERATION)), ("CalibrationProgress", "0", 3, "0"), @@ -935,6 +936,7 @@ class FrogPilotVariables: toggle.old_long_api |= toggle.openpilot_longitudinal and toggle.car_make == "hyundai" and not (params.get_bool("NewLongAPI") if tuning_level >= level["NewLongAPI"] else default.get_bool("NewLongAPI")) personalize_openpilot = params.get_bool("PersonalizeOpenpilot") if tuning_level >= level["PersonalizeOpenpilot"] else default.get_bool("PersonalizeOpenpilot") + toggle.boot_logo = params.get("BootLogo", encoding="utf-8") or "starpilot" toggle.color_scheme = toggle.current_holiday_theme if toggle.current_holiday_theme != "stock" else params.get("CustomColors", encoding="utf-8") if personalize_openpilot else "stock" toggle.distance_icons = toggle.current_holiday_theme if toggle.current_holiday_theme != "stock" else params.get("CustomDistanceIcons", encoding="utf-8") if personalize_openpilot else "stock" toggle.icon_pack = toggle.current_holiday_theme if toggle.current_holiday_theme != "stock" else params.get("CustomIcons", encoding="utf-8") if personalize_openpilot else "stock" diff --git a/frogpilot/ui/qt/offroad/theme_settings.cc b/frogpilot/ui/qt/offroad/theme_settings.cc index a08062a0d5..fe10dc6930 100644 --- a/frogpilot/ui/qt/offroad/theme_settings.cc +++ b/frogpilot/ui/qt/offroad/theme_settings.cc @@ -228,6 +228,7 @@ FrogPilotThemesPanel::FrogPilotThemesPanel(FrogPilotSettingsWindow *parent) : Fr const std::vector> themeToggles { {"PersonalizeOpenpilot", tr("Custom Themes"), tr("The overall look and feel of openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), "../../frogpilot/assets/toggle_icons/icon_frog.png"}, + {"BootLogo", tr("Boot Logo"), tr("The boot logo shown while the device starts."), ""}, {"CustomColors", tr("Color Scheme"), tr("The color scheme used throughout openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""}, {"CustomDistanceIcons", tr("Distance Button"), tr("The distance button icons shown on the driving screen. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""}, {"CustomIcons", tr("Icon Pack"), tr("The icon style used across openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""}, @@ -252,6 +253,56 @@ FrogPilotThemesPanel::FrogPilotThemesPanel(FrogPilotSettingsWindow *parent) : Fr themesLayout->setCurrentWidget(customThemesPanel); }); themeToggle = personalizeOpenpilotToggle; + } else if (param == "BootLogo") { + manageBootLogosButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}); + QObject::connect(manageBootLogosButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + QStringList bootLogos = getThemeList(false, QDir(bootLogosDirectory.path()), "", "BootLogo", params); + + if (id == 0) { + QString bootLogoToDelete = MultiOptionDialog::getSelection(tr("Select a boot logo to delete"), bootLogos, "", this); + if (!bootLogoToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" boot logo?").arg(bootLogoToDelete), tr("Delete"), this)) { + bootLogosDownloaded = false; + + deleteThemeAsset(bootLogosDirectory, "", "DownloadableBootLogos", bootLogoToDelete, params); + } + } else if (id == 1) { + if (bootLogoDownloading) { + cancellingDownload = true; + + params_memory.putBool("CancelThemeDownload", true); + + QTimer::singleShot(2500, [this]() { + bootLogoDownloading = false; + cancellingDownload = false; + themeDownloading = false; + + params_memory.putBool("CancelThemeDownload", false); + }); + } else { + QStringList downloadableBootLogos = QString::fromStdString(params.get("DownloadableBootLogos")).split(","); + bootLogoToDownload = MultiOptionDialog::getSelection(tr("Select a boot logo to download"), downloadableBootLogos, "", this); + if (!bootLogoToDownload.isEmpty()) { + manageBootLogosButton->setValue(storeThemeName(bootLogoToDownload, "BootLogo", params)); + + bootLogoDownloading = true; + themeDownloading = true; + + params_memory.put("ThemeDownloadProgress", "Downloading..."); + + downloadThemeAsset(bootLogoToDownload, "BootLogoToDownload", "DownloadableBootLogos", params, params_memory); + + downloadStatusLabel->setText("Downloading..."); + } + } + } else if (id == 2) { + QString bootLogoToSelect = MultiOptionDialog::getSelection(tr("Select a boot logo"), bootLogos, getThemeName("BootLogo", params), this); + if (!bootLogoToSelect.isEmpty()) { + manageBootLogosButton->setValue(storeThemeName(bootLogoToSelect, "BootLogo", params)); + } + } + }); + manageBootLogosButton->setValue(getThemeName(param.toStdString(), params)); + themeToggle = manageBootLogosButton; } else if (param == "CustomColors") { manageCustomColorsButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}); QObject::connect(manageCustomColorsButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) { @@ -704,6 +755,7 @@ FrogPilotThemesPanel::FrogPilotThemesPanel(FrogPilotSettingsWindow *parent) : Fr } void FrogPilotThemesPanel::showEvent(QShowEvent *event) { + bootLogosDownloaded = params.get("DownloadableBootLogos").empty(); colorsDownloaded = params.get("DownloadableColors").empty(); distanceIconsDownloaded = params.get("DownloadableDistanceIcons").empty(); iconsDownloaded = params.get("DownloadableIcons").empty(); @@ -756,6 +808,7 @@ void FrogPilotThemesPanel::updateState(const UIState &s, const FrogPilotUIState finalizingDownload = true; QTimer::singleShot(2500, [this]() { + bootLogoDownloading = false; colorDownloading = false; distanceIconDownloading = false; finalizingDownload = false; @@ -765,6 +818,7 @@ void FrogPilotThemesPanel::updateState(const UIState &s, const FrogPilotUIState themeDownloading = false; wheelDownloading = false; + bootLogosDownloaded = params.get("DownloadableBootLogos").empty(); colorsDownloaded = params.get("DownloadableColors").empty(); distanceIconsDownloaded = params.get("DownloadableDistanceIcons").empty(); iconsDownloaded = params.get("DownloadableIcons").empty(); @@ -782,6 +836,11 @@ void FrogPilotThemesPanel::updateState(const UIState &s, const FrogPilotUIState bool parked = !s.scene.started || fs.frogpilot_scene.parked || fs.frogpilot_toggles.value("frogs_go_moo").toBool(); + manageBootLogosButton->setText(1, bootLogoDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageBootLogosButton->setEnabledButtons(0, !themeDownloading); + manageBootLogosButton->setEnabledButtons(1, fs.frogpilot_scene.online && (!themeDownloading || bootLogoDownloading) && !cancellingDownload && !finalizingDownload && !bootLogosDownloaded && parked); + manageBootLogosButton->setEnabledButtons(2, !themeDownloading); + manageCustomColorsButton->setText(1, colorDownloading ? tr("CANCEL") : tr("DOWNLOAD")); manageCustomColorsButton->setEnabledButtons(0, !themeDownloading); manageCustomColorsButton->setEnabledButtons(1, fs.frogpilot_scene.online && (!themeDownloading || colorDownloading) && !cancellingDownload && !finalizingDownload && !colorsDownloaded && parked); diff --git a/frogpilot/ui/qt/offroad/theme_settings.h b/frogpilot/ui/qt/offroad/theme_settings.h index 876edb2306..ade7e341f1 100644 --- a/frogpilot/ui/qt/offroad/theme_settings.h +++ b/frogpilot/ui/qt/offroad/theme_settings.h @@ -19,6 +19,8 @@ private: void updateToggles(); bool cancellingDownload; + bool bootLogoDownloading = false; + bool bootLogosDownloaded = false; bool colorDownloading; bool colorsDownloaded; bool distanceIconDownloading; @@ -40,10 +42,11 @@ private: std::map toggles; - QSet customThemeKeys = {"CustomColors", "CustomDistanceIcons", "CustomIcons", "CustomSignals", "CustomSounds", "DownloadStatusLabel", "WheelIcon"}; + QSet customThemeKeys = {"BootLogo", "CustomColors", "CustomDistanceIcons", "CustomIcons", "CustomSignals", "CustomSounds", "DownloadStatusLabel", "WheelIcon"}; QSet parentKeys; + FrogPilotButtonsControl *manageBootLogosButton; FrogPilotButtonsControl *manageCustomColorsButton; FrogPilotButtonsControl *manageCustomIconsButton; FrogPilotButtonsControl *manageCustomSignalsButton; @@ -55,11 +58,13 @@ private: LabelControl *downloadStatusLabel; + QDir bootLogosDirectory{"/data/themes/bootlogos/"}; QDir themePacksDirectory{"/data/themes/theme_packs/"}; QDir wheelsDirectory{"/data/themes/steering_wheels/"}; QJsonObject frogpilotToggleLevels; + QString bootLogoToDownload; QString colorSchemeToDownload; QString distanceIconPackToDownload; QString iconPackToDownload;