support for filtering mtp/ftp mount options. use builtin config for ftp port,user,pass.
This commit is contained in:
@@ -341,7 +341,7 @@ endif()
|
||||
if (ENABLE_FTPSRV)
|
||||
FetchContent_Declare(ftpsrv
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
|
||||
GIT_TAG a7c2283
|
||||
GIT_TAG 7770dab
|
||||
SOURCE_SUBDIR NONE
|
||||
)
|
||||
|
||||
@@ -358,7 +358,7 @@ if (ENABLE_FTPSRV)
|
||||
USE_VFS_STORAGE=$<BOOL:TRUE>
|
||||
# disabled as it may conflict with the gamecard menu.
|
||||
USE_VFS_GC=$<BOOL:FALSE>
|
||||
USE_VFS_USBHSFS=$<BOOL:${ENABLE_LIBUSBHSFS}>
|
||||
USE_VFS_USBHSFS=$<BOOL:FALSE>
|
||||
VFS_NX_BUFFER_IO=$<BOOL:TRUE>
|
||||
# let sphaira handle init / closing of the hdd.
|
||||
USE_VFS_USBHSFS_INIT=$<BOOL:FALSE>
|
||||
@@ -378,14 +378,7 @@ if (ENABLE_FTPSRV)
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/utils.c
|
||||
)
|
||||
|
||||
if (ENABLE_LIBUSBHSFS)
|
||||
target_sources(ftpsrv_helper PRIVATE
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_stdio.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_hdd.c
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(ftpsrv_helper PUBLIC ftpsrv libusbhsfs)
|
||||
target_link_libraries(ftpsrv_helper PUBLIC ftpsrv)
|
||||
target_include_directories(ftpsrv_helper PUBLIC ${ftpsrv_SOURCE_DIR}/src/platform)
|
||||
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_FTPSRV)
|
||||
|
||||
@@ -131,6 +131,9 @@ public:
|
||||
static void DisplayAdvancedOptions(bool left_side = true);
|
||||
static void DisplayInstallOptions(bool left_side = true);
|
||||
static void DisplayDumpOptions(bool left_side = true);
|
||||
static void DisplayFtpOptions(bool left_side = true);
|
||||
static void DisplayMtpOptions(bool left_side = true);
|
||||
static void DisplayHddOptions(bool left_side = true);
|
||||
|
||||
// helper for sidebar options to toggle install on/off
|
||||
static void ShowEnableInstallPromptOption(option::OptionBool& option, bool& enable);
|
||||
@@ -342,9 +345,38 @@ public:
|
||||
// todo: move this into it's own menu
|
||||
option::OptionLong m_text_scroll_speed{"accessibility", "text_scroll_speed", 1}; // normal
|
||||
|
||||
// ftp options.
|
||||
option::OptionLong m_ftp_port{"ftp", "port", 5000};
|
||||
option::OptionBool m_ftp_anon{"ftp", "anon", true};
|
||||
option::OptionString m_ftp_user{"ftp", "user", ""};
|
||||
option::OptionString m_ftp_pass{"ftp", "pass", ""};
|
||||
option::OptionBool m_ftp_show_album{"ftp", "show_album", true};
|
||||
option::OptionBool m_ftp_show_ams_contents{"ftp", "show_ams_contents", false};
|
||||
option::OptionBool m_ftp_show_bis_storage{"ftp", "show_bis_storage", false};
|
||||
option::OptionBool m_ftp_show_bis_fs{"ftp", "show_bis_fs", false};
|
||||
option::OptionBool m_ftp_show_content_system{"ftp", "show_content_system", false};
|
||||
option::OptionBool m_ftp_show_content_user{"ftp", "show_content_user", false};
|
||||
option::OptionBool m_ftp_show_content_sd{"ftp", "show_content_sd", false};
|
||||
// option::OptionBool m_ftp_show_content_sd0{"ftp", "show_content_sd0", false};
|
||||
// option::OptionBool m_ftp_show_custom_system{"ftp", "show_custom_system", false};
|
||||
// option::OptionBool m_ftp_show_custom_sd{"ftp", "show_custom_sd", false};
|
||||
option::OptionBool m_ftp_show_games{"ftp", "show_games", true};
|
||||
option::OptionBool m_ftp_show_install{"ftp", "show_install", true};
|
||||
option::OptionBool m_ftp_show_mounts{"ftp", "show_mounts", false};
|
||||
option::OptionBool m_ftp_show_switch{"ftp", "show_switch", false};
|
||||
|
||||
// mtp options.
|
||||
option::OptionLong m_mtp_vid{"mtp", "vid", 0x057e}; // nintendo
|
||||
option::OptionLong m_mtp_pid{"mtp", "pid", 0x201d}; // switch
|
||||
option::OptionLong m_mtp_vid{"mtp", "vid", 0x057e}; // nintendo (hidden from ui)
|
||||
option::OptionLong m_mtp_pid{"mtp", "pid", 0x201d}; // switch (hidden from ui)
|
||||
option::OptionBool m_mtp_allocate_file{"mtp", "allocate_file", true};
|
||||
option::OptionBool m_mtp_show_album{"mtp", "show_album", true};
|
||||
option::OptionBool m_mtp_show_content_sd{"mtp", "show_content_sd", false};
|
||||
option::OptionBool m_mtp_show_content_system{"mtp", "show_content_system", false};
|
||||
option::OptionBool m_mtp_show_content_user{"mtp", "show_content_user", false};
|
||||
option::OptionBool m_mtp_show_games{"mtp", "show_games", true};
|
||||
option::OptionBool m_mtp_show_install{"mtp", "show_install", true};
|
||||
option::OptionBool m_mtp_show_mounts{"mtp", "show_mounts", false};
|
||||
option::OptionBool m_mtp_show_speedtest{"mtp", "show_speedtest", false};
|
||||
|
||||
std::shared_ptr<fs::FsNativeSd> m_fs{};
|
||||
audio::SongID m_background_music{};
|
||||
|
||||
@@ -44,6 +44,14 @@ public:
|
||||
m_depends_click = depends_click;
|
||||
}
|
||||
|
||||
void SetDirty(bool dirty = true) {
|
||||
m_dirty = dirty;
|
||||
}
|
||||
|
||||
auto IsDirty() const -> bool {
|
||||
return m_dirty;
|
||||
}
|
||||
|
||||
protected:
|
||||
auto IsEnabled() const -> bool {
|
||||
if (m_depends_callback) {
|
||||
@@ -69,6 +77,7 @@ private:
|
||||
DependsClickCallback m_depends_click{};
|
||||
ScrollingText m_scolling_title{};
|
||||
ScrollingText m_scolling_value{};
|
||||
bool m_dirty{};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
@@ -174,11 +183,14 @@ private:
|
||||
};
|
||||
|
||||
class SidebarEntryTextInput final : public SidebarEntryTextBase {
|
||||
public:
|
||||
using Callback = std::function<void(SidebarEntryTextInput* input)>;
|
||||
|
||||
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 = "");
|
||||
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);
|
||||
// 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 = "");
|
||||
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);
|
||||
|
||||
auto GetNumValue() const -> s64 {
|
||||
return std::stoul(GetValue());
|
||||
@@ -191,6 +203,7 @@ private:
|
||||
const std::string m_guide;
|
||||
const s64 m_len_min;
|
||||
const s64 m_len_max;
|
||||
const Callback m_callback;
|
||||
};
|
||||
|
||||
class SidebarEntryFilePicker final : public SidebarEntryTextBase {
|
||||
@@ -210,10 +223,12 @@ class Sidebar : public Widget {
|
||||
public:
|
||||
enum class Side { LEFT, RIGHT };
|
||||
using Items = std::vector<std::unique_ptr<SidebarEntryBase>>;
|
||||
using OnExitWhenChangedCallback = std::function<void()>;
|
||||
|
||||
public:
|
||||
explicit Sidebar(const std::string& title, Side side, float width = 450.f);
|
||||
explicit Sidebar(const std::string& title, const std::string& sub, Side side, float width = 450.f);
|
||||
~Sidebar();
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
@@ -227,6 +242,12 @@ public:
|
||||
return (T*)Add(std::make_unique<T>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
// sets a callback that is called on exit when the any options were changed.
|
||||
// the change detection isn't perfect, it just checks if the A button was pressed...
|
||||
void SetOnExitWhenChanged(const OnExitWhenChangedCallback& cb) {
|
||||
m_on_exit_when_changed = cb;
|
||||
}
|
||||
|
||||
private:
|
||||
void SetIndex(s64 index);
|
||||
void SetupButtons();
|
||||
@@ -235,16 +256,18 @@ private:
|
||||
const std::string m_title;
|
||||
const std::string m_sub;
|
||||
const Side m_side;
|
||||
Items m_items;
|
||||
Items m_items{};
|
||||
s64 m_index{};
|
||||
|
||||
std::unique_ptr<List> m_list;
|
||||
std::unique_ptr<List> m_list{};
|
||||
|
||||
Vec4 m_top_bar{};
|
||||
Vec4 m_bottom_bar{};
|
||||
Vec2 m_title_pos{};
|
||||
Vec4 m_base_pos{};
|
||||
|
||||
OnExitWhenChangedCallback m_on_exit_when_changed{};
|
||||
|
||||
static constexpr float m_title_size{28.f};
|
||||
// static constexpr Vec2 box_size{380.f, 70.f};
|
||||
static constexpr Vec2 m_box_size{400.f, 70.f};
|
||||
|
||||
@@ -1549,9 +1549,37 @@ App::App(const char* argv0) {
|
||||
else if (app->m_nsz_compress_ldm.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_nsz_compress_block.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_nsz_compress_block_exponent.LoadFrom(Key, Value)) {}
|
||||
} else if (!std::strcmp(Section, "ftp")) {
|
||||
if (app->m_ftp_port.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_anon.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_user.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_pass.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_show_album.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_show_ams_contents.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_show_bis_storage.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_show_bis_fs.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_show_content_system.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_show_content_user.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_show_content_sd.LoadFrom(Key, Value)) {}
|
||||
// else if (app->m_ftp_show_content_sd0.LoadFrom(Key, Value)) {}
|
||||
// else if (app->m_ftp_show_custom_system.LoadFrom(Key, Value)) {}
|
||||
// else if (app->m_ftp_show_custom_sd.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_show_games.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_show_install.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_show_mounts.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_show_switch.LoadFrom(Key, Value)) {}
|
||||
} else if (!std::strcmp(Section, "mtp")) {
|
||||
if (app->m_mtp_vid.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_mtp_pid.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_mtp_allocate_file.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_mtp_show_album.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_mtp_show_content_sd.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_mtp_show_content_system.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_mtp_show_content_user.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_mtp_show_games.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_mtp_show_install.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_mtp_show_mounts.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_mtp_show_speedtest.LoadFrom(Key, Value)) {}
|
||||
}
|
||||
|
||||
return 1;
|
||||
@@ -2271,6 +2299,236 @@ void App::DisplayDumpOptions(bool left_side) {
|
||||
block_size_option->Depends(App::GetApp()->m_nsz_compress_block, "NSZ block compression is disabled."_i18n);
|
||||
}
|
||||
|
||||
void App::DisplayFtpOptions(bool left_side) {
|
||||
// todo: prompt on exit to restart ftp server if options were changed.
|
||||
auto options = std::make_unique<ui::Sidebar>("FTP Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(std::move(options)));
|
||||
|
||||
options->SetOnExitWhenChanged([](){
|
||||
if (App::GetFtpEnable()) {
|
||||
App::Notify("Restarting FTP server..."_i18n);
|
||||
App::SetFtpEnable(false);
|
||||
App::SetFtpEnable(true);
|
||||
}
|
||||
});
|
||||
|
||||
options->Add<ui::SidebarEntryBool>("Enable"_i18n, App::GetFtpEnable(), [](bool& enable){
|
||||
App::SetFtpEnable(enable);
|
||||
}, "Enable FTP server to run in the background."_i18n);
|
||||
|
||||
options->Add<ui::SidebarEntryTextInput>(
|
||||
"Port", App::GetApp()->m_ftp_port.Get(), "Port number", 1, 5,
|
||||
"Opens the FTP server on this port."_i18n,
|
||||
[](auto* input){
|
||||
App::GetApp()->m_ftp_port.Set(input->GetNumValue());
|
||||
}
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Anon"_i18n, App::GetApp()->m_ftp_anon,
|
||||
"Allows you to login without setting a username and password.\n"
|
||||
"If disabled, you must set a user name and password below!"_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryTextInput>(
|
||||
"User", App::GetApp()->m_ftp_user.Get(), "Username", -1, 64,
|
||||
"Sets the username, must be set if anon is disabled."_i18n,
|
||||
[](auto* input){
|
||||
App::GetApp()->m_ftp_user.Set(input->GetValue());
|
||||
}
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryTextInput>(
|
||||
"Pass", App::GetApp()->m_ftp_pass.Get(), "Password", -1, 64,
|
||||
"Sets the password, must be set if anon is disabled."_i18n,
|
||||
[](auto* input){
|
||||
App::GetApp()->m_ftp_pass.Set(input->GetValue());
|
||||
}
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show album"_i18n, App::GetApp()->m_ftp_show_album,
|
||||
"Shows the microSD card album folder."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show Atmosphere contents"_i18n, App::GetApp()->m_ftp_show_ams_contents,
|
||||
"Shows the shortcut for the /atmosphere/contents folder."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show bis storage"_i18n, App::GetApp()->m_ftp_show_bis_storage,
|
||||
"Shows the bis folder which contains the following:\n"
|
||||
"- BootPartition1Root.bin\n"
|
||||
"- BootPartition2Root.bin\n"
|
||||
"- UserDataRoot.bin\n"
|
||||
"- BootConfigAndPackage2Part1.bin\n"
|
||||
"- BootConfigAndPackage2Part2.bin\n"
|
||||
"- BootConfigAndPackage2Part3.bin\n"
|
||||
"- BootConfigAndPackage2Part4.bin\n"
|
||||
"- BootConfigAndPackage2Part5.bin\n"
|
||||
"- BootConfigAndPackage2Part6.bin\n"
|
||||
"- CalibrationFile.bin\n"
|
||||
"- SafeMode.bin\n"
|
||||
"- User.bin\n"
|
||||
"- System.bin\n"
|
||||
"- SystemProperEncryption.bin"_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show bis file systems"_i18n, App::GetApp()->m_ftp_show_bis_fs,
|
||||
"Shows the following bis file systems:\n"
|
||||
"- bis_calibration_file\n"
|
||||
"- bis_safe_mode\n"
|
||||
"- bis_user\n"
|
||||
"- bis_system"_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show system contents"_i18n, App::GetApp()->m_ftp_show_content_system,
|
||||
"Shows the system contents folder."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show user contents"_i18n, App::GetApp()->m_ftp_show_content_user,
|
||||
"Shows the user contents folder."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show microSD contents"_i18n, App::GetApp()->m_ftp_show_content_sd,
|
||||
"Shows the microSD contents folder.\n\n"
|
||||
"NOTE: This is not the normal microSD card storage, it is instead "
|
||||
"the location where NCA's are stored. The normal microSD card is always mounted."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show games"_i18n, App::GetApp()->m_ftp_show_games,
|
||||
"Shows the games folder.\n\n"
|
||||
"This folder contains all of your installed games, allowing you to create "
|
||||
"backups over FTP!\n\n"
|
||||
"NOTE: This folder is read-only. You cannot delete games over FTP."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show install"_i18n, App::GetApp()->m_ftp_show_install,
|
||||
"Shows the install folder.\n\n"
|
||||
"This folder is used for installing games via FTP.\n\n"
|
||||
"NOTE: You must open the \"FTP Install\" menu when trying to install a game!"_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show mounts"_i18n, App::GetApp()->m_ftp_show_mounts,
|
||||
"Shows the mounts folder.\n\n"
|
||||
"This folder is contains all of the mounts added to Sphaira, allowing you to acces them over FTP!\n"
|
||||
"For example, you can access your SMB, WebDav or other FTP mounts over FTP."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show switch"_i18n, App::GetApp()->m_ftp_show_switch,
|
||||
"Shows the shortcut for the /switch folder."
|
||||
"This is the folder that contains all your homebrew (NRO's)."_i18n
|
||||
);
|
||||
}
|
||||
|
||||
void App::DisplayMtpOptions(bool left_side) {
|
||||
// todo: prompt on exit to restart ftp server if options were changed.
|
||||
auto options = std::make_unique<ui::Sidebar>("MTP Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(std::move(options)));
|
||||
|
||||
options->SetOnExitWhenChanged([](){
|
||||
if (App::GetMtpEnable()) {
|
||||
App::Notify("Restarting MTP server..."_i18n);
|
||||
App::SetMtpEnable(false);
|
||||
App::SetMtpEnable(true);
|
||||
}
|
||||
});
|
||||
|
||||
options->Add<ui::SidebarEntryBool>("Enable"_i18n, App::GetMtpEnable(), [](bool& enable){
|
||||
App::SetMtpEnable(enable);
|
||||
}, "Enable MTP server to run in the background."_i18n);
|
||||
|
||||
// not sure if i want to expose this to users yet.
|
||||
// its also stubbed currently.
|
||||
#if 0
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Pre-allocate file"_i18n, App::GetApp()->m_mtp_allocate_file,
|
||||
"Enables pre-allocating the file size before writing.\n"
|
||||
"This speeds up file writes, however, this can cause timeouts if all these conditions are met:\n"
|
||||
"- using Windows\n"
|
||||
"- using emuMMC\n"
|
||||
"- transferring a large file (>1GB)\n\n"
|
||||
"This option should be left enabled, however if you use the above and experience timeouts, "
|
||||
"then try again with this option disabled."_i18n
|
||||
);
|
||||
#endif
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show album"_i18n, App::GetApp()->m_mtp_show_album,
|
||||
"Shows the microSD card album folder."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show microSD contents"_i18n, App::GetApp()->m_mtp_show_content_sd,
|
||||
"Shows the microSD contents folder.\n\n"
|
||||
"NOTE: This is not the normal microSD card storage, it is instead "
|
||||
"the location where NCA's are stored. The normal microSD card is always mounted."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show system contents"_i18n, App::GetApp()->m_mtp_show_content_system,
|
||||
"Shows the system contents folder."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show user contents"_i18n, App::GetApp()->m_mtp_show_content_user,
|
||||
"Shows the user contents folder."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show games"_i18n, App::GetApp()->m_mtp_show_games,
|
||||
"Shows the games folder.\n\n"
|
||||
"This folder contains all of your installed games, allowing you to create "
|
||||
"backups over MTP!\n\n"
|
||||
"NOTE: This folder is read-only. You cannot delete games over MTP."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show install"_i18n, App::GetApp()->m_mtp_show_install,
|
||||
"Shows the install folder.\n\n"
|
||||
"This folder is used for installing games via MTP.\n\n"
|
||||
"NOTE: You must open the \"MTP Install\" menu when trying to install a game!"_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show mounts"_i18n, App::GetApp()->m_mtp_show_mounts,
|
||||
"Shows the mounts folder.\n\n"
|
||||
"This folder is contains all of the mounts added to Sphaira, allowing you to acces them over MTP!\n"
|
||||
"For example, you can access your SMB, WebDav and FTP mounts over MTP."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>(
|
||||
"Show DevNull"_i18n, App::GetApp()->m_mtp_show_speedtest,
|
||||
"Shows the DevNull (Speed Test) folder.\n\n"
|
||||
"This folder is used for benchmarking USB uploads. "
|
||||
"This ia virtual folder, nothing is actally written to disk."_i18n
|
||||
);
|
||||
}
|
||||
|
||||
void App::DisplayHddOptions(bool left_side) {
|
||||
auto options = std::make_unique<ui::Sidebar>("HDD Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(std::move(options)));
|
||||
|
||||
options->Add<ui::SidebarEntryBool>("Enable"_i18n, App::GetHddEnable(), [](bool& enable){
|
||||
App::SetHddEnable(enable);
|
||||
}, "Enable mounting of connected USB/HDD devices. "
|
||||
"Connected devices can be used in the FileBrowser, as well as a backup location when dumping games and saves."_i18n
|
||||
);
|
||||
|
||||
options->Add<ui::SidebarEntryBool>("HDD write protect"_i18n, App::GetWriteProtect(), [](bool& enable){
|
||||
App::SetWriteProtect(enable);
|
||||
}, "Makes the connected HDD read-only."_i18n);
|
||||
}
|
||||
|
||||
void App::ShowEnableInstallPrompt() {
|
||||
// warn the user the dangers of installing.
|
||||
App::Push<ui::OptionBox>(
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "utils/thread.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <minIni.h>
|
||||
#include <ftpsrv.h>
|
||||
#include <ftpsrv_vfs.h>
|
||||
#include <nx/vfs_nx.h>
|
||||
@@ -30,11 +29,12 @@ struct InstallSharedData {
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
const char* INI_PATH = "/config/ftpsrv/config.ini";
|
||||
FtpSrvConfig g_ftpsrv_config = {0};
|
||||
std::atomic_bool g_should_exit = false;
|
||||
bool g_is_running{false};
|
||||
Thread g_thread;
|
||||
FtpSrvConfig g_ftpsrv_config{};
|
||||
int g_ftpsrv_mount_flags{};
|
||||
std::vector<VfsNxCustomPath> g_custom_vfs{};
|
||||
std::atomic_bool g_should_exit{};
|
||||
bool g_is_running{};
|
||||
Thread g_thread{};
|
||||
Mutex g_mutex{};
|
||||
|
||||
void ftp_log_callback(enum FTP_API_LOG_TYPE type, const char* msg) {
|
||||
@@ -468,74 +468,12 @@ FtpVfs g_vfs_stdio = {
|
||||
void loop(void* arg) {
|
||||
log_write("[FTP] loop entered\n");
|
||||
|
||||
// load config.
|
||||
{
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
|
||||
g_ftpsrv_config.log_callback = ftp_log_callback;
|
||||
g_ftpsrv_config.progress_callback = ftp_progress_callback;
|
||||
g_ftpsrv_config.anon = ini_getbool("Login", "anon", 0, INI_PATH);
|
||||
int user_len = ini_gets("Login", "user", "", g_ftpsrv_config.user, sizeof(g_ftpsrv_config.user), INI_PATH);
|
||||
int pass_len = ini_gets("Login", "pass", "", g_ftpsrv_config.pass, sizeof(g_ftpsrv_config.pass), INI_PATH);
|
||||
g_ftpsrv_config.port = ini_getl("Network", "port", 5000, INI_PATH); // 5000 to keep compat with older sphaira
|
||||
g_ftpsrv_config.timeout = ini_getl("Network", "timeout", 0, INI_PATH);
|
||||
g_ftpsrv_config.use_localtime = ini_getbool("Misc", "use_localtime", 0, INI_PATH);
|
||||
bool log_enabled = ini_getbool("Log", "log", 0, INI_PATH);
|
||||
|
||||
// get nx config
|
||||
bool mount_devices = ini_getbool("Nx", "mount_devices", 1, INI_PATH);
|
||||
bool mount_bis = ini_getbool("Nx", "mount_bis", 0, INI_PATH);
|
||||
bool save_writable = ini_getbool("Nx", "save_writable", 0, INI_PATH);
|
||||
g_ftpsrv_config.port = ini_getl("Nx", "app_port", g_ftpsrv_config.port, INI_PATH); // compat
|
||||
|
||||
// get Nx-App overrides
|
||||
g_ftpsrv_config.anon = ini_getbool("Nx-App", "anon", g_ftpsrv_config.anon, INI_PATH);
|
||||
user_len = ini_gets("Nx-App", "user", g_ftpsrv_config.user, g_ftpsrv_config.user, sizeof(g_ftpsrv_config.user), INI_PATH);
|
||||
pass_len = ini_gets("Nx-App", "pass", g_ftpsrv_config.pass, g_ftpsrv_config.pass, sizeof(g_ftpsrv_config.pass), INI_PATH);
|
||||
g_ftpsrv_config.port = ini_getl("Nx-App", "port", g_ftpsrv_config.port, INI_PATH);
|
||||
g_ftpsrv_config.timeout = ini_getl("Nx-App", "timeout", g_ftpsrv_config.timeout, INI_PATH);
|
||||
g_ftpsrv_config.use_localtime = ini_getbool("Nx-App", "use_localtime", g_ftpsrv_config.use_localtime, INI_PATH);
|
||||
log_enabled = ini_getbool("Nx-App", "log", log_enabled, INI_PATH);
|
||||
mount_devices = ini_getbool("Nx-App", "mount_devices", mount_devices, INI_PATH);
|
||||
mount_bis = ini_getbool("Nx-App", "mount_bis", mount_bis, INI_PATH);
|
||||
save_writable = ini_getbool("Nx-App", "save_writable", save_writable, INI_PATH);
|
||||
|
||||
g_should_exit = false;
|
||||
mount_devices = true;
|
||||
g_ftpsrv_config.timeout = 0;
|
||||
|
||||
if (!g_ftpsrv_config.port) {
|
||||
g_ftpsrv_config.port = 5000;
|
||||
log_write("[FTP] no port config, defaulting to 5000\n");
|
||||
}
|
||||
|
||||
// keep compat with older sphaira
|
||||
if (!user_len && !pass_len) {
|
||||
g_ftpsrv_config.anon = true;
|
||||
log_write("[FTP] no user pass, defaulting to anon\n");
|
||||
}
|
||||
|
||||
fsdev_wrapMountSdmc();
|
||||
|
||||
static const VfsNxCustomPath custom_vfs[] = {
|
||||
{
|
||||
.name = "games",
|
||||
.user = NULL,
|
||||
.func = &g_vfs_stdio,
|
||||
},
|
||||
{
|
||||
.name = "mounts",
|
||||
.user = NULL,
|
||||
.func = &g_vfs_stdio,
|
||||
},
|
||||
{
|
||||
.name = "install",
|
||||
.user = NULL,
|
||||
.func = &g_vfs_install,
|
||||
},
|
||||
};
|
||||
|
||||
vfs_nx_init(custom_vfs, std::size(custom_vfs), mount_devices, save_writable, mount_bis, false);
|
||||
vfs_nx_init(g_custom_vfs.data(), std::size(g_custom_vfs), g_ftpsrv_mount_flags, false);
|
||||
}
|
||||
|
||||
ON_SCOPE_EXIT(
|
||||
@@ -566,13 +504,90 @@ bool Init() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if (R_FAILED(fsdev_wrapMountSdmc())) {
|
||||
// log_write("[FTP] cannot mount sdmc\n");
|
||||
// return false;
|
||||
// }
|
||||
g_ftpsrv_mount_flags = 0;
|
||||
g_ftpsrv_config = {};
|
||||
g_custom_vfs.clear();
|
||||
|
||||
// todo: replace everything with ini_browse for faster loading.
|
||||
// or load everything in the init thread.
|
||||
auto app = App::GetApp();
|
||||
g_ftpsrv_config.log_callback = ftp_log_callback;
|
||||
g_ftpsrv_config.progress_callback = ftp_progress_callback;
|
||||
g_ftpsrv_config.anon = app->m_ftp_anon.Get();
|
||||
std::strncpy(g_ftpsrv_config.user, app->m_ftp_user.Get().c_str(), sizeof(g_ftpsrv_config.user) - 1);
|
||||
std::strncpy(g_ftpsrv_config.pass, app->m_ftp_pass.Get().c_str(), sizeof(g_ftpsrv_config.pass) - 1);
|
||||
g_ftpsrv_config.port = app->m_ftp_port.Get();
|
||||
|
||||
if (app->m_ftp_show_album.Get()) {
|
||||
g_ftpsrv_mount_flags |= VfsNxMountFlag_ALBUM;
|
||||
}
|
||||
if (app->m_ftp_show_ams_contents.Get()) {
|
||||
g_ftpsrv_mount_flags |= VfsNxMountFlag_AMS_CONTENTS;
|
||||
}
|
||||
if (app->m_ftp_show_bis_storage.Get()) {
|
||||
g_ftpsrv_mount_flags |= VfsNxMountFlag_BIS_STORAGE;
|
||||
}
|
||||
if (app->m_ftp_show_bis_fs.Get()) {
|
||||
g_ftpsrv_mount_flags |= VfsNxMountFlag_BIS_FS;
|
||||
}
|
||||
if (app->m_ftp_show_content_system.Get()) {
|
||||
g_ftpsrv_mount_flags |= VfsNxMountFlag_CONTENT_SYSTEM;
|
||||
}
|
||||
if (app->m_ftp_show_content_user.Get()) {
|
||||
g_ftpsrv_mount_flags |= VfsNxMountFlag_CONTENT_USER;
|
||||
}
|
||||
if (app->m_ftp_show_content_sd.Get()) {
|
||||
g_ftpsrv_mount_flags |= VfsNxMountFlag_CONTENT_SDCARD;
|
||||
}
|
||||
#if 0
|
||||
if (app->m_ftp_show_content_sd0.Get()) {
|
||||
g_ftpsrv_mount_flags |= VfsNxMountFlag_CONTENT_SDCARD0;
|
||||
}
|
||||
if (app->m_ftp_show_custom_system.Get()) {
|
||||
g_ftpsrv_mount_flags |= VfsNxMountFlag_CUSTOM_SYSTEM;
|
||||
}
|
||||
if (app->m_ftp_show_custom_sd.Get()) {
|
||||
g_ftpsrv_mount_flags |= VfsNxMountFlag_CUSTOM_SDCARD;
|
||||
}
|
||||
#endif
|
||||
if (app->m_ftp_show_switch.Get()) {
|
||||
g_ftpsrv_mount_flags |= VfsNxMountFlag_SWITCH;
|
||||
}
|
||||
|
||||
if (app->m_ftp_show_install.Get()) {
|
||||
g_custom_vfs.push_back({
|
||||
.name = "install",
|
||||
.user = &g_shared_data,
|
||||
.func = &g_vfs_install,
|
||||
});
|
||||
}
|
||||
|
||||
if (app->m_ftp_show_games.Get()) {
|
||||
g_custom_vfs.push_back({
|
||||
.name = "games",
|
||||
.user = NULL,
|
||||
.func = &g_vfs_stdio,
|
||||
});
|
||||
}
|
||||
|
||||
if (app->m_ftp_show_mounts.Get()) {
|
||||
g_custom_vfs.push_back({
|
||||
.name = "mounts",
|
||||
.user = NULL,
|
||||
.func = &g_vfs_stdio,
|
||||
});
|
||||
}
|
||||
|
||||
g_ftpsrv_config.timeout = 0;
|
||||
|
||||
if (!g_ftpsrv_config.port) {
|
||||
g_ftpsrv_config.port = 5000;
|
||||
log_write("[FTP] no port config, defaulting to 5000\n");
|
||||
}
|
||||
|
||||
// keep compat with older sphaira
|
||||
if (!std::strlen(g_ftpsrv_config.user) && !std::strlen(g_ftpsrv_config.pass)) {
|
||||
g_ftpsrv_config.anon = true;
|
||||
log_write("[FTP] no user pass, defaulting to anon\n");
|
||||
}
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED(rc = utils::CreateThread(&g_thread, loop, nullptr))) {
|
||||
@@ -602,7 +617,9 @@ void Exit() {
|
||||
threadWaitForExit(&g_thread);
|
||||
threadClose(&g_thread);
|
||||
|
||||
memset(&g_ftpsrv_config, 0, sizeof(g_ftpsrv_config));
|
||||
std::memset(&g_ftpsrv_config, 0, sizeof(g_ftpsrv_config));
|
||||
g_custom_vfs.clear();
|
||||
g_ftpsrv_mount_flags = 0;
|
||||
|
||||
log_write("[FTP] exitied\n");
|
||||
}
|
||||
|
||||
@@ -669,12 +669,40 @@ bool Init() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add default mount of the sd card.
|
||||
g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsNativeSd>(), "", "microSD card"));
|
||||
|
||||
if (App::GetApp()->m_mtp_show_album.Get()) {
|
||||
g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsNativeImage>(FsImageDirectoryId_Sd), "Album", "Album (Image SD)"));
|
||||
}
|
||||
|
||||
if (App::GetApp()->m_mtp_show_content_sd.Get()) {
|
||||
g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsNativeContentStorage>(FsContentStorageId_SdCard), "ContentsM", "Contents (microSD card)"));
|
||||
}
|
||||
|
||||
if (App::GetApp()->m_mtp_show_content_system.Get()) {
|
||||
g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsNativeContentStorage>(FsContentStorageId_System), "ContentsS", "Contents (System)"));
|
||||
}
|
||||
|
||||
if (App::GetApp()->m_mtp_show_content_user.Get()) {
|
||||
g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsNativeContentStorage>(FsContentStorageId_User), "ContentsU", "Contents (User)"));
|
||||
}
|
||||
|
||||
if (App::GetApp()->m_mtp_show_games.Get()) {
|
||||
g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsStdio>(true, "games:/"), "Games", "Games"));
|
||||
g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsStdio>(true, "mounts:/"), "Mounts", "Mounts"));
|
||||
g_fs_entries.emplace_back(std::make_shared<FsDevNullProxy>("DevNull", "DevNull (Speed Test)"));
|
||||
}
|
||||
|
||||
if (App::GetApp()->m_mtp_show_install.Get()) {
|
||||
g_fs_entries.emplace_back(std::make_shared<FsInstallProxy>("install", "Install (NSP, XCI, NSZ, XCZ)"));
|
||||
}
|
||||
|
||||
if (App::GetApp()->m_mtp_show_mounts.Get()) {
|
||||
g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsStdio>(true, "mounts:/"), "Mounts", "Mounts"));
|
||||
}
|
||||
|
||||
if (App::GetApp()->m_mtp_show_speedtest.Get()) {
|
||||
g_fs_entries.emplace_back(std::make_shared<FsDevNullProxy>("DevNull", "DevNull (Speed Test)"));
|
||||
}
|
||||
|
||||
g_should_exit = false;
|
||||
if (!haze::Initialize(haze_callback, g_fs_entries, App::GetApp()->m_mtp_vid.Get(), App::GetApp()->m_mtp_pid.Get())) {
|
||||
|
||||
@@ -337,15 +337,19 @@ MainMenu::MainMenu() {
|
||||
});
|
||||
}
|
||||
|
||||
options->Add<SidebarEntryBool>("FTP"_i18n, App::GetFtpEnable(), [](bool& enable){
|
||||
App::SetFtpEnable(enable);
|
||||
}, "Enable FTP server to run in the background.\n\n"
|
||||
"The default port is 5000 with no user/pass set. "
|
||||
"You can change this behaviour in /config/ftpsrv/config.ini"_i18n);
|
||||
options->Add<SidebarEntryCallback>("FTP"_i18n, [](){ App::DisplayFtpOptions(); },
|
||||
"Enable / modify the FTP server settings such as port, user/pass and the folders that are shown.\n\n"
|
||||
"NOTE: Changing any of the options will automatically restart the FTP server when exiting the options menu."_i18n
|
||||
);
|
||||
|
||||
options->Add<SidebarEntryBool>("MTP"_i18n, App::GetMtpEnable(), [](bool& enable){
|
||||
App::SetMtpEnable(enable);
|
||||
}, "Enable MTP server to run in the background."_i18n);
|
||||
options->Add<SidebarEntryCallback>("MTP"_i18n, [](){ App::DisplayMtpOptions(); },
|
||||
"Enable / modify the MTP responder settings such as the folders that are shown.\n\n"
|
||||
"NOTE: Changing any of the options will automatically restart the MTP server when exiting the options menu."_i18n
|
||||
);
|
||||
|
||||
options->Add<SidebarEntryCallback>("HDD"_i18n, [](){
|
||||
App::DisplayHddOptions();
|
||||
}, "Enable / modify the HDD mount options."_i18n);
|
||||
|
||||
options->Add<SidebarEntryBool>("NXlink"_i18n, App::GetNxlinkEnable(), [](bool& enable){
|
||||
App::SetNxlinkEnable(enable);
|
||||
@@ -353,14 +357,6 @@ MainMenu::MainMenu() {
|
||||
"NXlink is used to send .nro's from PC to the switch\n\n"
|
||||
"If you are not a developer, you can disable this option."_i18n);
|
||||
|
||||
options->Add<SidebarEntryBool>("HDD"_i18n, App::GetHddEnable(), [](bool& enable){
|
||||
App::SetHddEnable(enable);
|
||||
}, "Enable mounting of connected USB/HDD devices. "
|
||||
"Connected devices can be used in the FileBrowser, as well as a backup location when dumping games and saves."_i18n);
|
||||
|
||||
options->Add<SidebarEntryBool>("HDD write protect"_i18n, App::GetWriteProtect(), [](bool& enable){
|
||||
App::SetWriteProtect(enable);
|
||||
}, "Makes the connected HDD read-only."_i18n);
|
||||
}, "Toggle FTP, MTP, HDD and NXlink\n\n"
|
||||
"If Sphaira has a update available, you can download it from this menu"_i18n);
|
||||
|
||||
|
||||
@@ -124,24 +124,27 @@ SidebarEntryBool::SidebarEntryBool(const std::string& title, bool option, const
|
||||
} else {
|
||||
m_option ^= 1;
|
||||
m_callback(m_option);
|
||||
SetDirty();
|
||||
} }
|
||||
});
|
||||
}
|
||||
|
||||
SidebarEntryBool::SidebarEntryBool(const std::string& title, bool& option, const std::string& info, const std::string& true_str, const std::string& false_str)
|
||||
: SidebarEntryBool{title, option, Callback{}, info, true_str, false_str} {
|
||||
m_callback = [&option](bool&){
|
||||
m_callback = [this, &option](bool&){
|
||||
option ^= 1;
|
||||
SetDirty();
|
||||
};
|
||||
}
|
||||
|
||||
SidebarEntryBool::SidebarEntryBool(const std::string& title, option::OptionBool& option, const Callback& cb, const std::string& info, const std::string& true_str, const std::string& false_str)
|
||||
: SidebarEntryBool{title, option.Get(), Callback{}, info, true_str, false_str} {
|
||||
m_callback = [&option, cb](bool& v_out){
|
||||
m_callback = [this, &option, cb](bool& v_out){
|
||||
if (cb) {
|
||||
cb(v_out);
|
||||
}
|
||||
option.Set(v_out);
|
||||
SetDirty();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -166,6 +169,7 @@ SidebarEntrySlider::SidebarEntrySlider(const std::string& title, float value, fl
|
||||
DependsClick();
|
||||
} else {
|
||||
m_value = std::clamp(m_value - m_inc, m_min, m_max);
|
||||
SetDirty();
|
||||
// m_callback(m_option);
|
||||
} }
|
||||
});
|
||||
@@ -174,6 +178,7 @@ SidebarEntrySlider::SidebarEntrySlider(const std::string& title, float value, fl
|
||||
DependsClick();
|
||||
} else {
|
||||
m_value = std::clamp(m_value + m_inc, m_min, m_max);
|
||||
SetDirty();
|
||||
// m_callback(m_option);
|
||||
} }
|
||||
});
|
||||
@@ -240,6 +245,8 @@ SidebarEntryArray::SidebarEntryArray(const std::string& title, const Items& item
|
||||
App::Push<PopupList>(
|
||||
m_title, m_items, index, m_index
|
||||
);
|
||||
|
||||
SetDirty();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -275,6 +282,7 @@ SidebarEntryArray::SidebarEntryArray(const std::string& title, const Items& item
|
||||
} else {
|
||||
// m_callback(m_index);
|
||||
m_list_callback();
|
||||
SetDirty();
|
||||
}}
|
||||
});
|
||||
}
|
||||
@@ -291,6 +299,7 @@ SidebarEntryTextBase::SidebarEntryTextBase(const std::string& title, const std::
|
||||
SetAction(Button::A, Action{"OK"_i18n, [this](){
|
||||
if (m_callback) {
|
||||
m_callback();
|
||||
SetDirty();
|
||||
}
|
||||
}});
|
||||
}
|
||||
@@ -300,26 +309,35 @@ 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)
|
||||
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)
|
||||
: SidebarEntryTextBase{title, value, {}, info}
|
||||
, m_guide{guide}
|
||||
, m_len_min{len_min}
|
||||
, m_len_max{len_max} {
|
||||
, 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))) {
|
||||
SetValue(out);
|
||||
|
||||
if (m_callback) {
|
||||
m_callback(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SidebarEntryTextInput::SidebarEntryTextInput(const std::string& title, s64 value, const std::string& guide, s64 len_min, s64 len_max, const std::string& info)
|
||||
: SidebarEntryTextInput{title, std::to_string(value), guide, len_min, len_max, info} {
|
||||
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} {
|
||||
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))) {
|
||||
SetValue(std::to_string(out));
|
||||
|
||||
if (m_callback) {
|
||||
m_callback(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -331,6 +349,7 @@ SidebarEntryFilePicker::SidebarEntryFilePicker(const std::string& title, const s
|
||||
App::Push<menu::filebrowser::picker::Menu>(
|
||||
[this](const fs::FsPath& path) {
|
||||
SetValue(path);
|
||||
SetDirty();
|
||||
return true;
|
||||
},
|
||||
m_filter
|
||||
@@ -370,6 +389,17 @@ Sidebar::Sidebar(const std::string& title, const std::string& sub, Side side, fl
|
||||
m_list->SetScrollBarPos(GetX() + GetW() - 20, m_base_pos.y - 10, pos.h - m_base_pos.y + 48);
|
||||
}
|
||||
|
||||
Sidebar::~Sidebar() {
|
||||
if (m_on_exit_when_changed) {
|
||||
for (const auto& item : m_items) {
|
||||
if (item->IsDirty()) {
|
||||
m_on_exit_when_changed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Sidebar::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||
Widget::Update(controller, touch);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user