swkdb: add support for setting the header. save: add support for setting the name for the save file.

This commit is contained in:
ITotalJustice
2025-10-09 14:45:08 +01:00
parent 7d56c8a381
commit 444ff3e2d1
10 changed files with 126 additions and 60 deletions

View File

@@ -6,7 +6,7 @@
namespace sphaira::swkbd {
Result ShowText(std::string& out, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = PATH_MAX);
Result ShowNumPad(s64& out, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = PATH_MAX);
Result ShowText(std::string& out, const char* header = nullptr, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = PATH_MAX);
Result ShowNumPad(s64& out, const char* header = nullptr, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = PATH_MAX);
} // namespace sphaira::swkbd

View File

@@ -12,6 +12,14 @@
namespace sphaira::ui::menu::save {
enum BackupFlag {
BackupFlag_None = 0,
// option to allow the user to set the save file name.
BackupFlag_SetName = 1 << 0,
// set if this is a auto backup (on restore).
BackupFlag_IsAuto = 1 << 1,
};
struct Entry final : FsSaveDataInfo {
NacpLanguageEntry lang{};
int image{};
@@ -82,13 +90,13 @@ private:
void DisplayOptions();
void BackupSaves(std::vector<std::reference_wrapper<Entry>>& entries);
void BackupSaves(std::vector<std::reference_wrapper<Entry>>& entries, u32 flags);
void RestoreSave();
auto BuildSavePath(const Entry& e, bool is_auto) const -> fs::FsPath;
Result RestoreSaveInternal(ProgressBox* pbox, const Entry& e, const fs::FsPath& path) const;
Result BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, Entry& e, bool compressed, bool is_auto = false) const;
Result BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, std::span<const std::reference_wrapper<Entry>> entries, bool compressed, bool is_auto = false) const;
auto BuildSavePath(const Entry& e, u32 flags) const -> fs::FsPath;
Result RestoreSaveInternal(ProgressBox* pbox, const Entry& e, const fs::FsPath& path);
Result BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, Entry& e, u32 flags);
Result BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, std::span<const std::reference_wrapper<Entry>> entries, u32 flags);
Result MountSaveFs();

View File

@@ -188,9 +188,9 @@ public:
public:
// uses normal keyboard.
explicit SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& guide = {}, s64 len_min = -1, s64 len_max = PATH_MAX, const std::string& info = "", const Callback& callback = nullptr);
explicit SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& header = {}, const std::string& guide = {}, s64 len_min = -1, s64 len_max = PATH_MAX, const std::string& info = "", const Callback& callback = nullptr);
// uses numpad.
explicit SidebarEntryTextInput(const std::string& title, s64 value, const std::string& guide = {}, s64 len_min = -1, s64 len_max = PATH_MAX, const std::string& info = "", const Callback& callback = nullptr);
explicit SidebarEntryTextInput(const std::string& title, s64 value, const std::string& header = {}, const std::string& guide = {}, s64 len_min = -1, s64 len_max = PATH_MAX, const std::string& info = "", const Callback& callback = nullptr);
auto GetNumValue() const -> s64 {
return std::stoul(GetValue());
@@ -200,6 +200,7 @@ public:
SetValue(std::to_string(value));
}
private:
const std::string m_header;
const std::string m_guide;
const s64 m_len_min;
const s64 m_len_max;

View File

@@ -2338,7 +2338,7 @@ void App::DisplayFtpOptions(bool left_side) {
}, "Enable FTP server to run in the background."_i18n);
options->Add<ui::SidebarEntryTextInput>(
"Port", App::GetApp()->m_ftp_port.Get(), "Port number", 1, 5,
"Port", App::GetApp()->m_ftp_port.Get(), "", "", 1, 5,
"Opens the FTP server on this port."_i18n,
[](auto* input){
App::GetApp()->m_ftp_port.Set(input->GetNumValue());
@@ -2352,7 +2352,7 @@ void App::DisplayFtpOptions(bool left_side) {
);
options->Add<ui::SidebarEntryTextInput>(
"User", App::GetApp()->m_ftp_user.Get(), "Username", -1, 64,
"User", App::GetApp()->m_ftp_user.Get(), "", "", -1, 64,
"Sets the username, must be set if anon is disabled."_i18n,
[](auto* input){
App::GetApp()->m_ftp_user.Set(input->GetValue());
@@ -2360,7 +2360,7 @@ void App::DisplayFtpOptions(bool left_side) {
);
options->Add<ui::SidebarEntryTextInput>(
"Pass", App::GetApp()->m_ftp_pass.Get(), "Password", -1, 64,
"Pass", App::GetApp()->m_ftp_pass.Get(), "", "", -1, 64,
"Sets the password, must be set if anon is disabled."_i18n,
[](auto* input){
App::GetApp()->m_ftp_pass.Set(input->GetValue());

View File

@@ -10,7 +10,7 @@ struct Config {
bool numpad{};
};
Result ShowInternal(Config& cfg, const char* guide, const char* initial, s64 len_min, s64 len_max) {
Result ShowInternal(Config& cfg, const char* header, const char* guide, const char* initial, s64 len_min, s64 len_max) {
SwkbdConfig c;
R_TRY(swkbdCreate(&c, 0));
swkbdConfigMakePresetDefault(&c);
@@ -20,7 +20,17 @@ Result ShowInternal(Config& cfg, const char* guide, const char* initial, s64 len
swkbdConfigSetType(&c, SwkbdType_NumPad);
}
// only works if len_max <= 32.
if (header) {
swkbdConfigSetHeaderText(&c, header);
}
if (guide) {
// only works if len_max <= 32.
if (header) {
swkbdConfigSetSubText(&c, guide);
}
swkbdConfigSetGuideText(&c, guide);
}
@@ -41,17 +51,17 @@ Result ShowInternal(Config& cfg, const char* guide, const char* initial, s64 len
} // namespace
Result ShowText(std::string& out, const char* guide, const char* initial, s64 len_min, s64 len_max) {
Result ShowText(std::string& out, const char* header, const char* guide, const char* initial, s64 len_min, s64 len_max) {
Config cfg{};
R_TRY(ShowInternal(cfg, guide, initial, len_min, len_max));
R_TRY(ShowInternal(cfg, header, guide, initial, len_min, len_max));
out = cfg.out_text;
R_SUCCEED();
}
Result ShowNumPad(s64& out, const char* guide, const char* initial, s64 len_min, s64 len_max) {
Result ShowNumPad(s64& out, const char* header, const char* guide, const char* initial, s64 len_min, s64 len_max) {
Config cfg{};
cfg.numpad = true;
R_TRY(ShowInternal(cfg, guide, initial, len_min, len_max));
R_TRY(ShowInternal(cfg, header, guide, initial, len_min, len_max));
out = std::atoll(cfg.out_text);
R_SUCCEED();
}

View File

@@ -599,7 +599,8 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
options->Add<SidebarEntryCallback>("Leave Feedback"_i18n, [this](){
std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out)) && !out.empty()) {
std::string header = "Leave feedback for " + m_entry.title;
if (R_SUCCEEDED(swkbd::ShowText(out, header.c_str())) && !out.empty()) {
const auto post = "name=" "switch_user" "&package=" + m_entry.name + "&message=" + out;
const auto file = BuildFeedbackCachePath(m_entry);
@@ -970,7 +971,7 @@ Menu::Menu(u32 flags) : grid::Menu{"AppStore"_i18n, flags} {
options->Add<SidebarEntryCallback>("Search"_i18n, [this](){
std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out)) && !out.empty()) {
if (R_SUCCEEDED(swkbd::ShowText(out, "Search for app")) && !out.empty()) {
SetSearch(out);
log_write("got %s\n", out.c_str());
}

View File

@@ -315,17 +315,17 @@ ForwarderForm::ForwarderForm(const FileAssocEntry& assoc, const RomDatabaseIndex
const auto icon = m_assoc.path;
m_name = this->Add<SidebarEntryTextInput>(
"Name", name, "", -1, sizeof(NacpLanguageEntry::name) - 1,
"Name", name, "", "", -1, sizeof(NacpLanguageEntry::name) - 1,
"Set the name of the application"_i18n
);
m_author = this->Add<SidebarEntryTextInput>(
"Author", author, "", -1, sizeof(NacpLanguageEntry::author) - 1,
"Author", author, "", "", -1, sizeof(NacpLanguageEntry::author) - 1,
"Set the author of the application"_i18n
);
m_version = this->Add<SidebarEntryTextInput>(
"Version", version, "", -1, sizeof(NacpStruct::display_version) - 1,
"Version", version, "", "", -1, sizeof(NacpStruct::display_version) - 1,
"Set the display version of the application"_i18n
);
@@ -1711,7 +1711,8 @@ void FsView::DisplayOptions() {
std::string out;
const auto& entry = GetEntry();
const auto name = entry.GetName();
if (R_SUCCEEDED(swkbd::ShowText(out, "Set New File Name"_i18n.c_str(), name.c_str())) && !out.empty() && out != name) {
const auto header = "Set new name"_i18n;
if (R_SUCCEEDED(swkbd::ShowText(out, header.c_str(), header.c_str(), name.c_str())) && !out.empty() && out != name) {
App::PopToMenu();
const auto src_path = GetNewPath(entry);
@@ -1804,7 +1805,7 @@ void FsView::DisplayOptions() {
options->Add<SidebarEntryCallback>("Extract to..."_i18n, [this](){
std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out, "Enter the path to the folder to extract into", fs::AppendPath(m_path, ""))) && !out.empty()) {
if (R_SUCCEEDED(swkbd::ShowText(out, "Extract path", "Enter the path to the folder to extract into", fs::AppendPath(m_path, ""))) && !out.empty()) {
UnzipFiles(out);
}
});
@@ -1822,7 +1823,7 @@ void FsView::DisplayOptions() {
options->Add<SidebarEntryCallback>("Compress to..."_i18n, [this](){
std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out, "Enter the path to the folder to extract into", m_path)) && !out.empty()) {
if (R_SUCCEEDED(swkbd::ShowText(out, "Compress path", "Enter the path to the folder to compress into", m_path)) && !out.empty()) {
ZipFiles(out);
}
});
@@ -1842,7 +1843,8 @@ void FsView::DisplayAdvancedOptions() {
if (!m_fs_entry.IsReadOnly()) {
options->Add<SidebarEntryCallback>("Create File"_i18n, [this](){
std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out, "Set File Name"_i18n.c_str(), fs::AppendPath(m_path, ""))) && !out.empty()) {
const auto header = "Set File Name"_i18n;
if (R_SUCCEEDED(swkbd::ShowText(out, header.c_str(), header.c_str(), fs::AppendPath(m_path, ""))) && !out.empty()) {
App::PopToMenu();
fs::FsPath full_path;
@@ -1864,7 +1866,8 @@ void FsView::DisplayAdvancedOptions() {
options->Add<SidebarEntryCallback>("Create Folder"_i18n, [this](){
std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out, "Set Folder Name"_i18n.c_str(), fs::AppendPath(m_path, ""))) && !out.empty()) {
const auto header = "Set Folder Name"_i18n;
if (R_SUCCEEDED(swkbd::ShowText(out, header.c_str(), header.c_str(), fs::AppendPath(m_path, ""))) && !out.empty()) {
App::PopToMenu();
fs::FsPath full_path;

View File

@@ -9,6 +9,7 @@
#include "threaded_file_transfer.hpp"
#include "minizip_helper.hpp"
#include "dumper.hpp"
#include "swkbd.hpp"
#include "utils/devoptab.hpp"
@@ -689,13 +690,38 @@ void Menu::DisplayOptions() {
entries.emplace_back(m_entries[m_index]);
}
BackupSaves(entries);
}, true);
BackupSaves(entries, BackupFlag_None);
}, true,
"Backup the selected save(s) to a location of your choice."_i18n
);
if (!m_selected_count || m_selected_count == 1) {
options->Add<SidebarEntryCallback>("Backup to..."_i18n, [this](){
std::vector<std::reference_wrapper<Entry>> entries;
if (m_selected_count) {
for (auto& e : m_entries) {
if (e.selected) {
entries.emplace_back(e);
}
}
} else {
entries.emplace_back(m_entries[m_index]);
}
BackupSaves(entries, BackupFlag_SetName);
}, true,
"Backup the selected save(s) to a location of your choice, and set the name of the backup."_i18n
);
}
if (m_entries[m_index].save_data_type == FsSaveDataType_Account || m_entries[m_index].save_data_type == FsSaveDataType_Bcat) {
options->Add<SidebarEntryCallback>("Restore"_i18n, [this](){
RestoreSave();
}, true);
}, true,
"Restore the save for the current title.\n"
"if \"Auto backup\" is enabled, the save will first be backed up and then restored. "
"Saves that are auto backed up will have \"Auto\" in their name."_i18n
);
}
}
@@ -705,18 +731,21 @@ void Menu::DisplayOptions() {
options->Add<SidebarEntryBool>("Auto backup on restore"_i18n, m_auto_backup_on_restore.Get(), [this](bool& v_out){
m_auto_backup_on_restore.Set(v_out);
});
}, "If enabled, when restoring a save, the current save will first be backed up."_i18n);
options->Add<SidebarEntryBool>("Compress backup"_i18n, m_compress_save_backup.Get(), [this](bool& v_out){
m_compress_save_backup.Set(v_out);
});
}, "If enabled, backups will be compressed to a zip file.\n\n"
"NOTE: Disabling this option does not disable the zip file, it only disables compressing "
"the files stored in the zip.\n"
"Disabling will result in a much faster backup, at the cost of the file size."_i18n);
});
}
void Menu::BackupSaves(std::vector<std::reference_wrapper<Entry>>& entries) {
dump::DumpGetLocation("Select backup location"_i18n, dump::DumpLocationFlag_SdCard|dump::DumpLocationFlag_Stdio|dump::DumpLocationFlag_Usb, [this, entries](const dump::DumpLocation& location){
App::Push<ProgressBox>(0, "Backup"_i18n, "", [this, entries, location](auto pbox) -> Result {
return BackupSaveInternal(pbox, location, entries, m_compress_save_backup.Get());
void Menu::BackupSaves(std::vector<std::reference_wrapper<Entry>>& entries, u32 flags) {
dump::DumpGetLocation("Select backup location"_i18n, dump::DumpLocationFlag_SdCard|dump::DumpLocationFlag_Stdio|dump::DumpLocationFlag_Usb, [this, entries, flags](const dump::DumpLocation& location){
App::Push<ProgressBox>(0, "Backup"_i18n, "", [this, entries, location, flags](auto pbox) -> Result {
return BackupSaveInternal(pbox, location, entries, flags);
}, [](Result rc){
App::PushErrorBox(rc, "Backup failed!"_i18n);
@@ -794,7 +823,7 @@ void Menu::RestoreSave() {
if (m_auto_backup_on_restore.Get()) {
pbox->SetActionName("Auto backup"_i18n);
R_TRY(BackupSaveInternal(pbox, location, m_entries[m_index], m_compress_save_backup.Get(), true));
R_TRY(BackupSaveInternal(pbox, location, m_entries[m_index], BackupFlag_IsAuto));
}
pbox->SetActionName("Restore"_i18n);
@@ -814,7 +843,7 @@ void Menu::RestoreSave() {
});
}
auto Menu::BuildSavePath(const Entry& e, bool is_auto) const -> fs::FsPath {
auto Menu::BuildSavePath(const Entry& e, u32 flags) const -> fs::FsPath {
const auto t = std::time(NULL);
const auto tm = std::localtime(&t);
const auto base = BuildSaveBasePath(e);
@@ -822,27 +851,39 @@ auto Menu::BuildSavePath(const Entry& e, bool is_auto) const -> fs::FsPath {
char time[64];
std::snprintf(time, sizeof(time), "%u.%02u.%02u @ %02u.%02u.%02u", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
fs::FsPath path;
fs::FsPath name;
if (e.save_data_type == FsSaveDataType_Account) {
const auto acc = m_accounts[m_account_index];
fs::FsPath name_buf;
if (is_auto) {
if (flags & BackupFlag_IsAuto) {
std::snprintf(name_buf, sizeof(name_buf), "AUTO - %s", acc.nickname);
} else {
std::snprintf(name_buf, sizeof(name_buf), "%s", acc.nickname);
}
title::utilsReplaceIllegalCharacters(name_buf, true);
std::snprintf(path, sizeof(path), "%s/%s - %s.zip", base.s, name_buf.s, time);
std::snprintf(name, sizeof(name), "%s - %s.zip", name_buf.s, time);
} else {
std::snprintf(path, sizeof(path), "%s/%s.zip", base.s, time);
std::snprintf(name, sizeof(name), "%s.zip", time);
}
return path;
if (flags & BackupFlag_SetName) {
std::string out;
while (out.empty()) {
const auto header = "Set name for "_i18n + e.GetName();
if (R_FAILED(swkbd::ShowText(out, header.c_str(), "Set backup name", name, 1, 128))) {
out.clear();
}
}
name = out;
}
return fs::AppendPath(base, name);
}
Result Menu::RestoreSaveInternal(ProgressBox* pbox, const Entry& e, const fs::FsPath& path) const {
Result Menu::RestoreSaveInternal(ProgressBox* pbox, const Entry& e, const fs::FsPath& path) {
pbox->SetTitle(e.GetName());
if (e.image) {
pbox->SetImage(e.image);
@@ -960,14 +1001,15 @@ Result Menu::RestoreSaveInternal(ProgressBox* pbox, const Entry& e, const fs::Fs
R_SUCCEED();
}
Result Menu::BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, std::span<const std::reference_wrapper<Entry>> entries, bool compressed, bool is_auto) const {
Result Menu::BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, std::span<const std::reference_wrapper<Entry>> entries, u32 flags) {
std::vector<fs::FsPath> paths;
for (auto& e : entries) {
// ensure that we have title name and icon loaded.
LoadControlEntry(e);
paths.emplace_back(BuildSavePath(e, is_auto));
paths.emplace_back(BuildSavePath(e, flags));
}
const auto compressed = m_compress_save_backup.Get();
auto source = std::make_shared<DumpSource>(entries, paths);
return dump::Dump(pbox, source, location, paths, [&](ui::ProgressBox* pbox, dump::BaseSource* _source, dump::WriteSource* writer, const fs::FsPath& path) -> Result {
@@ -1116,11 +1158,11 @@ Result Menu::BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& loc
});
}
Result Menu::BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, Entry& e, bool compressed, bool is_auto) const {
Result Menu::BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, Entry& e, u32 flags) {
std::vector<std::reference_wrapper<Entry>> entries;
entries.emplace_back(e);
return BackupSaveInternal(pbox, location, entries, compressed, is_auto);
return BackupSaveInternal(pbox, location, entries, flags);
}
Result Menu::MountSaveFs() {

View File

@@ -309,16 +309,17 @@ void SidebarEntryTextBase::Draw(NVGcontext* vg, Theme* theme, const Vec4& root_p
SidebarEntryBase::DrawEntry(vg, theme, m_title, m_value, true);
}
SidebarEntryTextInput::SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& guide, s64 len_min, s64 len_max, const std::string& info, const Callback& callback)
SidebarEntryTextInput::SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& header, const std::string& guide, s64 len_min, s64 len_max, const std::string& info, const Callback& callback)
: SidebarEntryTextBase{title, value, {}, info}
, m_guide{guide}
, m_header{header.empty() ? title : header}
, m_guide{guide.empty() ? title : guide}
, m_len_min{len_min}
, m_len_max{len_max}
, m_callback{callback} {
SetCallback([this](){
std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out, m_guide.c_str(), GetValue().c_str(), m_len_min, m_len_max))) {
if (R_SUCCEEDED(swkbd::ShowText(out, m_header.c_str(), m_guide.c_str(), GetValue().c_str(), m_len_min, m_len_max))) {
SetValue(out);
if (m_callback) {
@@ -328,11 +329,11 @@ SidebarEntryTextInput::SidebarEntryTextInput(const std::string& title, const std
});
}
SidebarEntryTextInput::SidebarEntryTextInput(const std::string& title, s64 value, const std::string& guide, s64 len_min, s64 len_max, const std::string& info, const Callback& callback)
: SidebarEntryTextInput{title, std::to_string(value), guide, len_min, len_max, info, callback} {
SidebarEntryTextInput::SidebarEntryTextInput(const std::string& title, s64 value, const std::string& header, const std::string& guide, s64 len_min, s64 len_max, const std::string& info, const Callback& callback)
: SidebarEntryTextInput{title, std::to_string(value), header, guide, len_min, len_max, info, callback} {
SetCallback([this](){
s64 out = std::stoul(GetValue());
if (R_SUCCEEDED(swkbd::ShowNumPad(out, m_guide.c_str(), GetValue().c_str(), m_len_min, m_len_max))) {
if (R_SUCCEEDED(swkbd::ShowNumPad(out, m_header.c_str(), m_guide.c_str(), GetValue().c_str(), m_len_min, m_len_max))) {
SetValue(std::to_string(out));
if (m_callback) {

View File

@@ -167,17 +167,17 @@ void DevoptabForm::SetupButtons(bool type_change) {
}
m_name = this->Add<SidebarEntryTextInput>(
"Name", m_config.name, "", -1, 32,
"Name", m_config.name, "", "", -1, 32,
"Set the name of the application"_i18n
);
m_url = this->Add<SidebarEntryTextInput>(
"URL", m_config.url, "", -1, PATH_MAX,
"URL", m_config.url, "", "", -1, PATH_MAX,
"Set the URL of the application"_i18n
);
m_port = this->Add<SidebarEntryTextInput>(
"Port", m_config.port, "Port number", 1, 5,
"Port", m_config.port, "", "", 1, 5,
"Optional: Set the port of the server. If left empty, the default port for the protocol will be used."_i18n
);
@@ -189,17 +189,17 @@ void DevoptabForm::SetupButtons(bool type_change) {
#endif
m_user = this->Add<SidebarEntryTextInput>(
"User", m_config.user, "", -1, PATH_MAX,
"User", m_config.user, "", "", -1, PATH_MAX,
"Optional: Set the username of the application"_i18n
);
m_pass = this->Add<SidebarEntryTextInput>(
"Pass", m_config.pass, "", -1, PATH_MAX,
"Pass", m_config.pass, "", "", -1, PATH_MAX,
"Optional: Set the password of the application"_i18n
);
m_dump_path = this->Add<SidebarEntryTextInput>(
"Dump path", m_config.dump_path, "", -1, PATH_MAX,
"Dump path", m_config.dump_path, "", "", -1, PATH_MAX,
"Optional: Set the dump path used when exporting games and saves."_i18n
);