diff --git a/code_templates/nsp_dumper_stor.c b/code_templates/nsp_dumper_stor.c index d4337ec..c7be27e 100644 --- a/code_templates/nsp_dumper_stor.c +++ b/code_templates/nsp_dumper_stor.c @@ -521,7 +521,7 @@ static void nspDump(TitleInfo *title_info, u64 free_space) utilsCreateDirectoryTree(path, false); - if (nsp_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(path)) + if (!ums_device && nsp_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(path)) { consolePrint("create concatenation file failed\n"); goto end; @@ -742,7 +742,7 @@ end: if (fd) { fclose(fd); - if (!success) utilsRemoveConcatenationFile(path); + if (!ums_device && !success) utilsRemoveConcatenationFile(path); utilsCommitSdCardFileSystemChanges(); } diff --git a/include/root_view.hpp b/include/root_view.hpp index d325ec1..e5b8d5d 100644 --- a/include/root_view.hpp +++ b/include/root_view.hpp @@ -38,6 +38,17 @@ namespace nxdt::views nxdt::tasks::TitleTask *title_task = nullptr; nxdt::tasks::UmsTask *ums_task = nullptr; nxdt::tasks::UsbHostTask *usb_host_task = nullptr; + + brls::VoidEvent::Subscription status_info_task_sub; + + brls::Label *applet_mode_lbl = nullptr; + brls::Label *time_lbl = nullptr; + brls::Label *battery_icon = nullptr, *battery_percentage = nullptr; + brls::Label *connection_icon = nullptr, *connection_status_lbl = nullptr; + + protected: + void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) override; + void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override; public: RootView(void); diff --git a/include/tasks.hpp b/include/tasks.hpp index 41e27fe..85f3a47 100644 --- a/include/tasks.hpp +++ b/include/tasks.hpp @@ -58,6 +58,7 @@ namespace nxdt::tasks NifmInternetConnectionType connection_type = (NifmInternetConnectionType)0; u32 signal_strength = 0; NifmInternetConnectionStatus connection_status = (NifmInternetConnectionStatus)0; + char *ip_addr = NULL; protected: void run(retro_time_t current_time) override; @@ -68,7 +69,7 @@ namespace nxdt::tasks std::string GetCurrentTimeString(void); void GetBatteryStats(u32 *out_charge_percentage, PsmChargerType *out_charger_type); - void GetNetworkStats(NifmInternetConnectionType *out_connection_type, u32 *out_signal_strength, NifmInternetConnectionStatus *out_connection_status); + void GetNetworkStats(NifmInternetConnectionType *out_connection_type, u32 *out_signal_strength, NifmInternetConnectionStatus *out_connection_status, char **out_ip_addr); ALWAYS_INLINE VoidEvent::Subscription RegisterListener(VoidEvent::Callback cb) { diff --git a/romfs/i18n/en-US/about_tab.json b/romfs/i18n/en-US/about_tab.json index ea3cd41..72687be 100644 --- a/romfs/i18n/en-US/about_tab.json +++ b/romfs/i18n/en-US/about_tab.json @@ -23,7 +23,7 @@ "line_07": "\uE016 The folks from NSWDB.COM and No-Intro.org, for being kind enough to put up public APIs to perform online checksum lookups.", "line_08": "\uE016 The folks at the nxdumptool Discord server.", "line_09": "\uE016 The Comfy Boyes, for always being awesome and supportive. You know who you are.", - "line_10": "\uE016 My girlfriend, for always being by my side and motivating me to keep working on all my projects. I love you. \u2665", + "line_10": "\uE016 My girlfriend, for always being by my side and motivating me to keep working on all my projects. I love you. \uE87D", "line_11": "\uE016 And, at last but not least, you! Thank you for using my work!" }, diff --git a/romfs/i18n/en-US/root_view.json b/romfs/i18n/en-US/root_view.json index 2c3b0a1..e99bba0 100644 --- a/romfs/i18n/en-US/root_view.json +++ b/romfs/i18n/en-US/root_view.json @@ -1,4 +1,10 @@ { + "applet_mode": "\uE8B2 Applet Mode \uE8B2", + + "date": "{1:02d}/{2:02d}/{0} {3:02d}:{4:02d}:{5:02d} {6}", + + "not_connected": "Not connected", + "tabs": { "gamecard": "Gamecard", "user_titles": "User titles", diff --git a/source/gamecard_tab.cpp b/source/gamecard_tab.cpp index 2fa55de..6351009 100644 --- a/source/gamecard_tab.cpp +++ b/source/gamecard_tab.cpp @@ -102,7 +102,7 @@ namespace nxdt::views this->addLayerWrapper(this->list); - /* Setup gamecard status task. */ + /* Subscribe to gamecard status event. */ this->gc_status_task_sub = this->gc_status_task->RegisterListener([this](GameCardStatus gc_status) { if (gc_status < GameCardStatus_InsertedAndInfoLoaded) this->changeLayerWrapper(this->error_frame); diff --git a/source/root_view.cpp b/source/root_view.cpp index 968185a..4396fbf 100644 --- a/source/root_view.cpp +++ b/source/root_view.cpp @@ -36,6 +36,28 @@ namespace nxdt::views /* Check if we're running under applet mode. */ this->applet_mode = utilsAppletModeCheck(); + /* Create labels. */ + this->applet_mode_lbl = new brls::Label(brls::LabelStyle::HINT, "root_view/applet_mode"_i18n); + this->applet_mode_lbl->setColor(nvgRGB(255, 0, 0)); + this->applet_mode_lbl->setParent(this); + + this->time_lbl = new brls::Label(brls::LabelStyle::SMALL, ""); + this->time_lbl->setParent(this); + + this->battery_icon = new brls::Label(brls::LabelStyle::SMALL, ""); + this->battery_icon->setFont(brls::Application::getFontStash()->material); + this->battery_icon->setParent(this); + + this->battery_percentage = new brls::Label(brls::LabelStyle::SMALL, ""); + this->battery_percentage->setParent(this); + + this->connection_icon = new brls::Label(brls::LabelStyle::SMALL, ""); + this->connection_icon->setFont(brls::Application::getFontStash()->material); + this->connection_icon->setParent(this); + + this->connection_status_lbl = new brls::Label(brls::LabelStyle::SMALL, ""); + this->connection_status_lbl->setParent(this); + /* Start background tasks. */ this->status_info_task = new nxdt::tasks::StatusInfoTask(); this->gc_status_task = new nxdt::tasks::GameCardTask(); @@ -57,14 +79,133 @@ namespace nxdt::views this->addTab("root_view/tabs/options"_i18n, new brls::Rectangle(nvgRGB(255, 255, 0))); this->addSeparator(); this->addTab("root_view/tabs/about"_i18n, new AboutTab()); + + /* Subscribe to status info event. */ + this->status_info_task_sub = this->status_info_task->RegisterListener([this](void) { + u32 charge_percentage = 0; + PsmChargerType charger_type = PsmChargerType_Unconnected; + + NifmInternetConnectionType connection_type = (NifmInternetConnectionType)0; + u32 signal_strength = 0; + NifmInternetConnectionStatus connection_status = (NifmInternetConnectionStatus)0; + char *ip_addr = NULL; + + /* Update time label. */ + this->time_lbl->setText(this->status_info_task->GetCurrentTimeString()); + + /* Update battery labels. */ + this->status_info_task->GetBatteryStats(&charge_percentage, &charger_type); + + this->battery_icon->setText(charger_type != PsmChargerType_Unconnected ? "\uE1A3" : (charge_percentage <= 15 ? "\uE19C" : "\uE1A4")); + this->battery_icon->setColor(charger_type != PsmChargerType_Unconnected ? nvgRGB(0, 255, 0) : (charge_percentage <= 15 ? nvgRGB(255, 0, 0) : brls::Application::getTheme()->textColor)); + + this->battery_percentage->setText(fmt::format("{}%", charge_percentage)); + + /* Update network label. */ + this->status_info_task->GetNetworkStats(&connection_type, &signal_strength, &connection_status, &ip_addr); + + this->connection_icon->setText(!connection_type ? "\uE195" : (connection_type == NifmInternetConnectionType_WiFi ? "\uE63E" : "\uE8BE")); + this->connection_status_lbl->setText(ip_addr ? std::string(ip_addr) : "root_view/not_connected"_i18n); + + /* Update layout. */ + this->invalidate(true); + }); } RootView::~RootView(void) { + /* Unregister status info task listener. */ + this->status_info_task->UnregisterListener(this->status_info_task_sub); + + /* Destroy labels. */ + delete this->applet_mode_lbl; + delete this->time_lbl; + delete this->battery_icon; + delete this->battery_percentage; + delete this->connection_icon; + delete this->connection_status_lbl; + /* Stop background tasks. */ this->gc_status_task->stop(); this->title_task->stop(); this->ums_task->stop(); this->usb_host_task->stop(); } + + void RootView::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) + { + if (this->applet_mode) this->applet_mode_lbl->frame(ctx); + + this->time_lbl->frame(ctx); + + this->battery_icon->frame(ctx); + this->battery_percentage->frame(ctx); + + this->connection_icon->frame(ctx); + this->connection_status_lbl->frame(ctx); + + brls::AppletFrame::draw(vg, x, y, width, height, style, ctx); + } + + void RootView::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) + { + int y_pos = 0; + + brls::AppletFrame::layout(vg, style, stash); + + if (this->applet_mode) + { + /* Applet mode label. */ + this->applet_mode_lbl->invalidate(true); + this->applet_mode_lbl->setBoundaries( + this->x + (this->width / 4) - (this->applet_mode_lbl->getWidth() / 2), + this->y + this->height - (style->AppletFrame.footerHeight / 2), + this->applet_mode_lbl->getWidth(), + this->applet_mode_lbl->getHeight()); + } + + /* Time label. */ + this->time_lbl->invalidate(true); + y_pos += this->y + 25 + this->time_lbl->getHeight(); + + this->time_lbl->setBoundaries( + this->x + this->width - (style->AppletFrame.separatorSpacing * 2) - this->time_lbl->getWidth(), + y_pos, + this->time_lbl->getWidth(), + this->time_lbl->getHeight()); + + /* Battery stats labels. */ + this->battery_icon->invalidate(true); + this->battery_percentage->invalidate(true); + y_pos += (20 + this->battery_icon->getHeight()); + + this->battery_icon->setBoundaries( + this->x + this->width - (style->AppletFrame.separatorSpacing * 2) - this->battery_percentage->getWidth() - 5 - this->battery_icon->getWidth(), + y_pos, + this->battery_icon->getWidth(), + this->battery_icon->getHeight()); + + this->battery_percentage->setBoundaries( + this->x + this->width - (style->AppletFrame.separatorSpacing * 2) - this->battery_percentage->getWidth(), + y_pos, + this->battery_percentage->getWidth(), + this->battery_percentage->getHeight()); + + /* Network connection labels. */ + this->connection_icon->invalidate(true); + this->connection_status_lbl->invalidate(true); + y_pos += (20 + this->connection_icon->getHeight()); + + this->connection_icon->setBoundaries( + this->x + this->width - (style->AppletFrame.separatorSpacing * 2) - this->connection_status_lbl->getWidth() - 5 - this->connection_icon->getWidth(), + y_pos, + this->connection_icon->getWidth(), + this->connection_icon->getHeight()); + + this->connection_status_lbl->setBoundaries( + this->x + this->width - (style->AppletFrame.separatorSpacing * 2) - this->connection_status_lbl->getWidth(), + y_pos, + this->connection_status_lbl->getWidth(), + this->connection_status_lbl->getHeight()); + } } diff --git a/source/tasks.cpp b/source/tasks.cpp index 1c191ae..5205468 100644 --- a/source/tasks.cpp +++ b/source/tasks.cpp @@ -21,14 +21,18 @@ #include #include +#include #define NXDT_TASK_INTERVAL 100 /* 100 ms. */ +namespace i18n = brls::i18n; /* For getStr(). */ +using namespace i18n::literals; /* For _i18n. */ + namespace nxdt::tasks { /* Status info task. */ - StatusInfoTask::StatusInfoTask(void) : brls::RepeatingTask(NXDT_TASK_INTERVAL * 10) + StatusInfoTask::StatusInfoTask(void) : brls::RepeatingTask(1000) { brls::RepeatingTask::start(); brls::Logger::debug("Status info task started."); @@ -61,8 +65,11 @@ namespace nxdt::tasks timeinfo->tm_hour = 12; } + timeinfo->tm_mon++; + timeinfo->tm_year += 1900; + this->cur_time.clear(); - fmt::format_to(std::back_inserter(this->cur_time), "{:02d}:{:02d}:{:02d} {}", timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, is_am ? "AM" : "PM"); + this->cur_time = i18n::getStr("root_view/date"_i18n, timeinfo->tm_year, timeinfo->tm_mon, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, is_am ? "AM" : "PM"); /* Get battery stats. */ psmGetBatteryChargePercentage(&(this->charge_percentage)); @@ -70,11 +77,21 @@ namespace nxdt::tasks /* Get network connection status. */ Result rc = nifmGetInternetConnectionStatus(&(this->connection_type), &(this->signal_strength), &(this->connection_status)); - if (R_FAILED(rc)) + if (R_SUCCEEDED(rc)) { + if (this->connection_type && this->connection_status == NifmInternetConnectionStatus_Connected) + { + struct in_addr addr = { .s_addr = 0 }; + nifmGetCurrentIpAddress(&(addr.s_addr)); + this->ip_addr = inet_ntoa(addr); + } else { + this->ip_addr = NULL; + } + } else { this->connection_type = (NifmInternetConnectionType)0; this->signal_strength = 0; this->connection_status = (NifmInternetConnectionStatus)0; + this->ip_addr = NULL; } this->status_info_event.fire(); @@ -92,12 +109,13 @@ namespace nxdt::tasks *out_charger_type = this->charger_type; } - void StatusInfoTask::GetNetworkStats(NifmInternetConnectionType *out_connection_type, u32 *out_signal_strength, NifmInternetConnectionStatus *out_connection_status) + void StatusInfoTask::GetNetworkStats(NifmInternetConnectionType *out_connection_type, u32 *out_signal_strength, NifmInternetConnectionStatus *out_connection_status, char **out_ip_addr) { - if (!out_connection_type || !out_signal_strength || !out_connection_status) return; + if (!out_connection_type || !out_signal_strength || !out_connection_status || !out_ip_addr) return; *out_connection_type = this->connection_type; *out_signal_strength = this->signal_strength; *out_connection_status = this->connection_status; + *out_ip_addr = this->ip_addr; } /* Gamecard task. */