From 17e0edb61ccf00f94735755cf27eb7e2e6388823 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Thu, 18 Apr 2024 21:58:47 +0200 Subject: [PATCH] [ci skip] OptionsTab: add option to unmount UMS devices Uses a SelectListItem element with displayValue set to false to avoid displaying a value string. Furthermore, the default click event callback is replaced to check if any UMS devices are mounted before attempting to display the dropdown menu. Other changes include: * UmsTask: define UmsDeviceVectorEntry. * UmsTask: redefine UmsDeviceVector as a UmsDeviceVectorEntry vector. * UmsTask: migrate UMS device string generation logic from DumpOptionsFrame::UpdateStorages() to UmsTask::PopulateUmsDeviceVector(). * DumpOptionsFrame: simplify UpdateStorages() to reflect the changes made to UmsTask. --- include/dump_options_frame.hpp | 7 ++-- include/options_tab.hpp | 6 ++- include/tasks.hpp | 11 ++++-- libs/borealis | 2 +- romfs/i18n/en-US/dump_options.json | 4 +- romfs/i18n/en-US/options_tab.json | 8 ++++ source/gamecard_tab.cpp | 4 +- source/options_tab.cpp | 60 ++++++++++++++++++++++++++++++ source/tasks.cpp | 43 ++++++++++++++------- 9 files changed, 119 insertions(+), 26 deletions(-) diff --git a/include/dump_options_frame.hpp b/include/dump_options_frame.hpp index 6ec833e..dadbcd3 100644 --- a/include/dump_options_frame.hpp +++ b/include/dump_options_frame.hpp @@ -91,7 +91,8 @@ namespace nxdt::views u64 total_sz = 0, free_sz = 0; char total_sz_str[64] = {0}, free_sz_str[64] = {0}; - const UsbHsFsDevice *cur_ums_device = (i >= ConfigOutputStorage_Count ? &(ums_devices.at(i - ConfigOutputStorage_Count)) : nullptr); + const nxdt::tasks::UmsDeviceVectorEntry *ums_device_entry = (i >= ConfigOutputStorage_Count ? &(ums_devices.at(i - ConfigOutputStorage_Count)) : nullptr); + const UsbHsFsDevice *cur_ums_device = (ums_device_entry ? ums_device_entry->first : nullptr); sprintf(total_sz_str, "%s/", cur_ums_device ? cur_ums_device->name : DEVOPTAB_SDMC_DEVICE); utilsGetFileSystemStatsByPath(total_sz_str, &total_sz, &free_sz); @@ -100,9 +101,7 @@ namespace nxdt::views if (cur_ums_device) { - std::string ums_extra_info = (cur_ums_device->product_name[0] ? (std::string(cur_ums_device->product_name) + ", ") : ""); - ums_extra_info += fmt::format("LUN {}, FS #{}, {}", cur_ums_device->lun, cur_ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(cur_ums_device->fs_type)); - storages.push_back(brls::i18n::getStr("dump_options/output_storage/value_02", static_cast(strlen(cur_ums_device->name + 3) - 1), cur_ums_device->name + 3, free_sz_str, total_sz_str, ums_extra_info)); + storages.push_back(brls::i18n::getStr("dump_options/output_storage/value_02", ums_device_entry->second, free_sz_str, total_sz_str)); } else { storages.push_back(brls::i18n::getStr("dump_options/output_storage/value_00", free_sz_str, total_sz_str)); } diff --git a/include/options_tab.hpp b/include/options_tab.hpp index 312a7f2..58ab4d5 100644 --- a/include/options_tab.hpp +++ b/include/options_tab.hpp @@ -73,9 +73,13 @@ namespace nxdt::views private: RootView *root_view = nullptr; + brls::SelectListItem *unmount_ums_device = nullptr; + nxdt::tasks::UmsDeviceVector ums_devices{}; + nxdt::tasks::UmsEvent::Subscription ums_task_sub; + bool display_notification = true; brls::menu_timer_t notification_timer = 0.0f; - brls::menu_timer_ctx_entry_t notification_timer_ctx = {0}; + brls::menu_timer_ctx_entry_t notification_timer_ctx{}; void DisplayNotification(const std::string& str); public: diff --git a/include/tasks.hpp b/include/tasks.hpp index c5b5ceb..e80de0e 100644 --- a/include/tasks.hpp +++ b/include/tasks.hpp @@ -52,8 +52,9 @@ namespace nxdt::tasks /* Used to hold pointers to application metadata entries. */ typedef std::vector TitleApplicationMetadataVector; - /* Used to hold UMS devices. */ - typedef std::vector UmsDeviceVector; + /* Used to hold information from UMS devices. */ + typedef std::pair UmsDeviceVectorEntry; + typedef std::vector UmsDeviceVector; /* Custom event types. */ typedef brls::Event StatusInfoEvent; @@ -133,7 +134,11 @@ namespace nxdt::tasks { private: UmsEvent ums_event; - UmsDeviceVector ums_devices{}; + + UsbHsFsDevice *ums_devices = nullptr; + u32 ums_devices_count = 0; + + UmsDeviceVector ums_devices_vector{}; void PopulateUmsDeviceVector(void); diff --git a/libs/borealis b/libs/borealis index ed6f2ef..e5cbe0d 160000 --- a/libs/borealis +++ b/libs/borealis @@ -1 +1 @@ -Subproject commit ed6f2ef76a136b0f77ce599e8401dbdf6bd6d11a +Subproject commit e5cbe0d97c32d1102a6ea253ed68a3a8ecfdc3a9 diff --git a/romfs/i18n/en-US/dump_options.json b/romfs/i18n/en-US/dump_options.json index 84d9007..2291d43 100644 --- a/romfs/i18n/en-US/dump_options.json +++ b/romfs/i18n/en-US/dump_options.json @@ -8,8 +8,8 @@ "label": "Output storage", "description": "Storage where the dumped data will be written to. Changing it will automatically update the output filename to better suit the output filesystem limitations.\nUsing a connected USB host requires a libusb-based driver, as well as the Python host script. For more information, please visit \"{0}\".", "value_00": "SD card ({0} free / {1} total)", - "value_01": "USB host", - "value_02": "USB Mass Storage {1:.{0}} ({2} free / {3} total) ({4})" + "value_01": "USB host (PC)", + "value_02": "{0} ({1} free / {2} total)" }, "prepend_key_area": { diff --git a/romfs/i18n/en-US/options_tab.json b/romfs/i18n/en-US/options_tab.json index 4460c54..517b285 100644 --- a/romfs/i18n/en-US/options_tab.json +++ b/romfs/i18n/en-US/options_tab.json @@ -13,6 +13,11 @@ "value_01": "ID and version only" }, + "unmount_ums_device": { + "label": "Unmount USB Mass Storage device", + "description": "Safely unmount any USB Mass Storage devices that are currently connected and mounted by {0}.\n\nIf a UMS device has more than one mounted volume, selecting a single one will unmount all volumes from that device.\n\nUMS devices are always safely unmounted at exit." + }, + "update_nswdb_xml": { "label": "Update NSWDB XML", "description": "Retrieves the latest NSWDB XML, which can be optionally used to validate checksums from gamecard dumps. Requires Internet connectivity." @@ -35,6 +40,9 @@ }, "notifications": { + "no_ums_devices": "No USB Mass Storage devices available.", + "ums_device_unmount_success": "USB Mass Storage device successfully unmounted!", + "ums_device_unmount_failure": "Failed to unmount USB Mass Storage device!", "no_internet_connection": "Internet connection unavailable. Unable to update.", "update_failed": "Update failed! Check the logfile for more info.", "nswdb_xml_updated": "NSWDB XML successfully updated!", diff --git a/source/gamecard_tab.cpp b/source/gamecard_tab.cpp index dd7b4ae..233cd6d 100644 --- a/source/gamecard_tab.cpp +++ b/source/gamecard_tab.cpp @@ -277,7 +277,7 @@ namespace nxdt::views brls::ListItem *dump_initial_data = new brls::ListItem("gamecard_tab/list/dump_initial_data/label"_i18n, "gamecard_tab/list/dump_initial_data/description"_i18n); this->list->addView(dump_initial_data); - brls::ListItem *dump_certificate = new brls::ListItem("gamecard_tab/list/dump_certificate/label"_i18n, brls::i18n::getStr("gamecard_tab/list/dump_certificate/description", GAMECARD_CERTIFICATE_OFFSET / GAMECARD_PAGE_SIZE)); + brls::ListItem *dump_certificate = new brls::ListItem("gamecard_tab/list/dump_certificate/label"_i18n, i18n::getStr("gamecard_tab/list/dump_certificate/description", GAMECARD_CERTIFICATE_OFFSET / GAMECARD_PAGE_SIZE)); this->list->addView(dump_certificate); brls::ListItem *dump_card_id_set = new brls::ListItem("gamecard_tab/list/dump_card_id_set/label"_i18n, "gamecard_tab/list/dump_card_id_set/description"_i18n); @@ -286,7 +286,7 @@ namespace nxdt::views brls::ListItem *dump_card_uid = new brls::ListItem("gamecard_tab/list/dump_card_uid/label"_i18n, "gamecard_tab/list/dump_card_uid/description"_i18n); this->list->addView(dump_card_uid); - brls::ListItem *dump_header = new brls::ListItem("gamecard_tab/list/dump_header/label"_i18n, brls::i18n::getStr("gamecard_tab/list/dump_header/description", 0)); + brls::ListItem *dump_header = new brls::ListItem("gamecard_tab/list/dump_header/label"_i18n, i18n::getStr("gamecard_tab/list/dump_header/description", 0)); this->list->addView(dump_header); brls::ListItem *dump_plaintext_cardinfo = new brls::ListItem("gamecard_tab/list/dump_plaintext_cardinfo/label"_i18n, "gamecard_tab/list/dump_plaintext_cardinfo/description"_i18n); diff --git a/source/options_tab.cpp b/source/options_tab.cpp index 9757f10..8a908f3 100644 --- a/source/options_tab.cpp +++ b/source/options_tab.cpp @@ -317,6 +317,62 @@ namespace nxdt::views this->addView(naming_convention); + /* Unmount UMS devices. */ + /* We will replace its default click event with a new one that will: */ + /* 1. Check if any UMS devices are available before displaying the dropdown and display a notification if there are none. */ + /* 2. Generate the string vector required by the dropdown. */ + /* 3. Initialize the dropdown and pass a custom callback that will take care of unmounting the selected device. */ + this->unmount_ums_device = new brls::SelectListItem("options_tab/unmount_ums_device/label"_i18n, { "dummy" }, 0, + i18n::getStr("options_tab/unmount_ums_device/description"_i18n, APP_TITLE), false); + + this->unmount_ums_device->getClickEvent()->unsubscribeAll(); + + this->unmount_ums_device->getClickEvent()->subscribe([this](brls::View* view) { + if (this->ums_devices.empty()) + { + /* Display a notification if we haven't mounted any UMS devices at all. */ + this->DisplayNotification("options_tab/notifications/no_ums_devices"_i18n); + return; + } + + /* Generate values vector for the dropdown. */ + std::vector values{}; + for(nxdt::tasks::UmsDeviceVectorEntry ums_device_entry : this->ums_devices) values.push_back(ums_device_entry.second); + + /* Display dropdown. */ + brls::Dropdown::open(this->unmount_ums_device->getLabel(), values, [this](int idx) { + /* Make sure the current value isn't out of bounds. */ + if (idx < 0 || idx >= static_cast(this->ums_devices.size())) return; + + /* Unmount UMS device. */ + if (umsUnmountDevice(this->ums_devices.at(idx).first)) + { + this->DisplayNotification("options_tab/notifications/ums_device_unmount_success"_i18n); + } else { + this->DisplayNotification("options_tab/notifications/ums_device_unmount_failure"_i18n); + } + }); + }); + + /* Update UMS devices vector. */ + this->ums_devices = this->root_view->GetUmsDevices(); + + /* Subscribe to the UMS device event. */ + this->ums_task_sub = this->root_view->RegisterUmsTaskListener([this](const nxdt::tasks::UmsDeviceVector& ums_devices) { + /* Update UMS devices vector. */ + this->ums_devices = this->root_view->GetUmsDevices(); + + /* Generate values vector for the dropdown. */ + std::vector values{}; + for(nxdt::tasks::UmsDeviceVectorEntry ums_device_entry : this->ums_devices) values.push_back(ums_device_entry.second); + + /* Update SelectListItem values. */ + /* If the dropdown menu is already being displayed, it'll be reloaded or popped from the view stack, depending on whether the provided vector is empty or not. */ + this->unmount_ums_device->updateValues(values); + }); + + this->addView(unmount_ums_device); + /* Update NSWDB XML. */ brls::ListItem *update_nswdb_xml = new brls::ListItem("options_tab/update_nswdb_xml/label"_i18n, "options_tab/update_nswdb_xml/description"_i18n); @@ -367,6 +423,10 @@ namespace nxdt::views OptionsTab::~OptionsTab(void) { + this->root_view->UnregisterUmsTaskListener(this->ums_task_sub); + + this->ums_devices.clear(); + brls::menu_timer_kill(&(this->notification_timer)); } diff --git a/source/tasks.cpp b/source/tasks.cpp index 1233a8e..ace58b4 100644 --- a/source/tasks.cpp +++ b/source/tasks.cpp @@ -212,7 +212,10 @@ namespace nxdt::tasks UmsTask::~UmsTask(void) { /* Clear UMS device vector. */ - this->ums_devices.clear(); + this->ums_devices_vector.clear(); + + /* Free UMS devices buffer. */ + if (this->ums_devices) free(this->ums_devices); LOG_MSG_DEBUG("UMS task stopped."); } @@ -230,35 +233,49 @@ namespace nxdt::tasks this->PopulateUmsDeviceVector(); /* Fire task event. */ - this->ums_event.fire(this->ums_devices); + this->ums_event.fire(this->ums_devices_vector); } } const UmsDeviceVector& UmsTask::GetUmsDevices(void) { - return this->ums_devices; + return this->ums_devices_vector; } void UmsTask::PopulateUmsDeviceVector(void) { - UsbHsFsDevice *ums_devices = nullptr; - u32 ums_device_count = 0; - /* Clear UMS device vector. */ - this->ums_devices.clear(); + this->ums_devices_vector.clear(); + + /* Free UMS devices buffer. */ + if (this->ums_devices) free(this->ums_devices); + + /* Reset UMS devices counter. */ + this->ums_devices_count = 0; /* Get UMS devices. */ - ums_devices = umsGetDevices(&ums_device_count); - if (ums_devices) + this->ums_devices = umsGetDevices(&(this->ums_devices_count)); + if (this->ums_devices) { /* Fill UMS device vector. */ - for(u32 i = 0; i < ums_device_count; i++) this->ums_devices.push_back(ums_devices[i]); + for(u32 i = 0; i < this->ums_devices_count; i++) + { + const UsbHsFsDevice *cur_ums_device = &(this->ums_devices[i]); + int name_len = static_cast(strlen(cur_ums_device->name) - 1); + std::string ums_info{}; - /* Free UMS devices array. */ - free(ums_devices); + if (cur_ums_device->product_name[0]) + { + ums_info = fmt::format("{1:.{0}} ({2}, LUN #{3}, FS#{4}, {5})", name_len, cur_ums_device->name, cur_ums_device->product_name, cur_ums_device->lun, cur_ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(cur_ums_device->fs_type)); + } else { + ums_info = fmt::format("{1:.{0}} (LUN #{2}, FS#{3}, {4})", name_len, cur_ums_device->name, cur_ums_device->lun, cur_ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(cur_ums_device->fs_type)); + } + + this->ums_devices_vector.push_back(std::make_pair(cur_ums_device, ums_info)); + } } - LOG_MSG_DEBUG("Retrieved info for %u UMS %s.", ums_device_count, ums_device_count == 1 ? "device" : "devices"); + LOG_MSG_DEBUG("Retrieved info for %u UMS %s.", this->ums_devices_count, this->ums_devices_count == 1 ? "device" : "devices"); } /* USB host device connection task. */