Fully fix menu logic and UI issues

This commit is contained in:
XorTroll 2019-10-24 19:24:00 +02:00
parent 79de9de4a1
commit bb0f03fbc8
16 changed files with 334 additions and 252 deletions

View file

@ -24,4 +24,5 @@ namespace am
Result ApplicationSetForeground(); Result ApplicationSetForeground();
Result ApplicationSend(void *data, size_t size, AppletLaunchParameterKind kind = AppletLaunchParameterKind_UserChannel); Result ApplicationSend(void *data, size_t size, AppletLaunchParameterKind kind = AppletLaunchParameterKind_UserChannel);
u64 ApplicationGetId(); u64 ApplicationGetId();
Result ApplicationGetResult();
} }

View file

@ -106,6 +106,7 @@ namespace cfg
void SaveConfig(Config &cfg); void SaveConfig(Config &cfg);
void SaveRecord(TitleRecord &record); void SaveRecord(TitleRecord &record);
void RemoveRecord(TitleRecord &record);
bool MoveRecordTo(TitleList &list, TitleRecord record, std::string folder); bool MoveRecordTo(TitleList &list, TitleRecord record, std::string folder);
TitleFolder &FindFolderByName(TitleList &list, std::string name); TitleFolder &FindFolderByName(TitleList &list, std::string name);
bool ExistsRecord(TitleList &list, TitleRecord record); bool ExistsRecord(TitleList &list, TitleRecord record);

View file

@ -6,4 +6,5 @@ namespace os
{ {
u32 GetBatteryLevel(); u32 GetBatteryLevel();
bool IsConsoleCharging(); bool IsConsoleCharging();
std::string GetFirmwareVersion();
} }

View file

@ -50,9 +50,10 @@ static constexpr size_t RawRGBAScreenBufferSize = 1280 * 720 * 4;
} \ } \
}) })
#define STLITER_FINDWITHCONDITION(stl_item, var_name, cond) std::find_if(stl_item.begin(), stl_item.end(), [&](auto &var_name){ return (cond); }); #define STL_FIND_IF(stl_item, var_name, cond) std::find_if(stl_item.begin(), stl_item.end(), [&](const auto &var_name){ return (cond); });
#define STLITER_ISFOUND(stl_item, find_ret) (find_ret != stl_item.end()) #define STL_FOUND(stl_item, find_ret) (find_ret != stl_item.end())
#define STLITER_UNWRAP(find_ret) (*find_ret) #define STL_UNWRAP(find_ret) (*find_ret)
#define STL_REMOVE_IF(stl_item, var_name, cond) stl_item.erase(std::remove_if(stl_item.begin(), stl_item.end(), [&](const auto &var_name){ return (cond); }), stl_item.end());
template<typename ...Args> template<typename ...Args>
using ResultWith = std::tuple<Result, Args...>; using ResultWith = std::tuple<Result, Args...>;

View file

@ -257,7 +257,10 @@ namespace cfg
if(record.json_name.empty()) record.json_name = jsonname; if(record.json_name.empty()) record.json_name = jsonname;
json += "/" + jsonname; json += "/" + jsonname;
entry["nro_path"] = record.nro_target.nro_path; entry["nro_path"] = record.nro_target.nro_path;
if(strcmp(record.nro_target.nro_path, record.nro_target.argv) != 0) entry["nro_argv"] = record.nro_target.argv; if(strlen(record.nro_target.argv))
{
if(strcmp(record.nro_target.nro_path, record.nro_target.argv) != 0) entry["nro_argv"] = record.nro_target.argv;
}
if(!record.icon.empty()) entry["icon"] = record.icon; if(!record.icon.empty()) entry["icon"] = record.icon;
} }
else if((TitleType)record.title_type == TitleType::Installed) else if((TitleType)record.title_type == TitleType::Installed)
@ -277,6 +280,28 @@ namespace cfg
ofs.close(); ofs.close();
} }
void RemoveRecord(TitleRecord &record)
{
// Prepare JSON path
std::string basepath = Q_ENTRIES_PATH;
std::string json = basepath;
if((TitleType)record.title_type == TitleType::Homebrew)
{
auto jsonname = std::to_string(fs::GetFileSize(record.nro_target.nro_path)) + ".json";
if(record.json_name.empty()) record.json_name = jsonname;
json += "/" + jsonname;
}
else if((TitleType)record.title_type == TitleType::Installed)
{
auto strappid = util::FormatApplicationId(record.app_id);
auto jsonname = strappid + ".json";
if(record.json_name.empty()) record.json_name = jsonname;
json += "/" + jsonname;
}
if(!record.json_name.empty()) json = basepath + "/" + record.json_name;
fs::DeleteFile(json);
}
bool MoveTitleToDirectory(TitleList &list, u64 app_id, std::string folder) bool MoveTitleToDirectory(TitleList &list, u64 app_id, std::string folder)
{ {
bool title_found = false; bool title_found = false;
@ -284,11 +309,11 @@ namespace cfg
std::string recjson; std::string recjson;
// Search in root first // Search in root first
auto find = STLITER_FINDWITHCONDITION(list.root.titles, tit, (tit.app_id == app_id)); auto find = STL_FIND_IF(list.root.titles, tit, (tit.app_id == app_id));
if(STLITER_ISFOUND(list.root.titles, find)) if(STL_FOUND(list.root.titles, find))
{ {
if(folder.empty()) return true; // It is already on root...? if(folder.empty()) return true; // It is already on root...?
recjson = STLITER_UNWRAP(find).json_name; recjson = STL_UNWRAP(find).json_name;
list.root.titles.erase(find); list.root.titles.erase(find);
title_found = true; title_found = true;
@ -298,11 +323,11 @@ namespace cfg
{ {
for(auto &fld: list.folders) for(auto &fld: list.folders)
{ {
auto find = STLITER_FINDWITHCONDITION(fld.titles, entry, (entry.app_id == app_id)); auto find = STL_FIND_IF(fld.titles, entry, (entry.app_id == app_id));
if(STLITER_ISFOUND(fld.titles, find)) if(STL_FOUND(fld.titles, find))
{ {
if(fld.name == folder) return true; // It is already on that folder...? if(fld.name == folder) return true; // It is already on that folder...?
recjson = STLITER_UNWRAP(find).json_name; recjson = STL_UNWRAP(find).json_name;
fld.titles.erase(find); fld.titles.erase(find);
title_found = true; title_found = true;
@ -325,10 +350,10 @@ namespace cfg
} }
else // Add it to the new folder else // Add it to the new folder
{ {
auto find2 = STLITER_FINDWITHCONDITION(list.folders, fld, (fld.name == folder)); auto find2 = STL_FIND_IF(list.folders, fld, (fld.name == folder));
if(STLITER_ISFOUND(list.folders, find2)) if(STL_FOUND(list.folders, find2))
{ {
STLITER_UNWRAP(find2).titles.push_back(title); STL_UNWRAP(find2).titles.push_back(title);
} }
else else
{ {
@ -354,11 +379,11 @@ namespace cfg
// Search in root first // Search in root first
if((TitleType)record.title_type == TitleType::Installed) if((TitleType)record.title_type == TitleType::Installed)
{ {
auto find = STLITER_FINDWITHCONDITION(list.root.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id)); auto find = STL_FIND_IF(list.root.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id));
if(STLITER_ISFOUND(list.root.titles, find)) if(STL_FOUND(list.root.titles, find))
{ {
if(folder.empty()) return true; // It is already on root...? if(folder.empty()) return true; // It is already on root...?
recjson = STLITER_UNWRAP(find).json_name; recjson = STL_UNWRAP(find).json_name;
list.root.titles.erase(find); list.root.titles.erase(find);
title_found = true; title_found = true;
@ -366,11 +391,11 @@ namespace cfg
} }
else else
{ {
auto find = STLITER_FINDWITHCONDITION(list.root.titles, tit, (tit.title_type == record.title_type) && (strcmp(tit.nro_target.nro_path, record.nro_target.nro_path) == 0)); auto find = STL_FIND_IF(list.root.titles, tit, (tit.title_type == record.title_type) && (strcmp(tit.nro_target.nro_path, record.nro_target.nro_path) == 0));
if(STLITER_ISFOUND(list.root.titles, find)) if(STL_FOUND(list.root.titles, find))
{ {
if(folder.empty()) return true; // It is already on root...? if(folder.empty()) return true; // It is already on root...?
recjson = STLITER_UNWRAP(find).json_name; recjson = STL_UNWRAP(find).json_name;
list.root.titles.erase(find); list.root.titles.erase(find);
title_found = true; title_found = true;
@ -383,11 +408,11 @@ namespace cfg
{ {
if((TitleType)record.title_type == TitleType::Installed) if((TitleType)record.title_type == TitleType::Installed)
{ {
auto find = STLITER_FINDWITHCONDITION(fld.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id)); auto find = STL_FIND_IF(fld.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id));
if(STLITER_ISFOUND(fld.titles, find)) if(STL_FOUND(fld.titles, find))
{ {
if(fld.name == folder) return true; // It is already on that folder...? if(fld.name == folder) return true; // It is already on that folder...?
recjson = STLITER_UNWRAP(find).json_name; recjson = STL_UNWRAP(find).json_name;
fld.titles.erase(find); fld.titles.erase(find);
title_found = true; title_found = true;
@ -396,11 +421,11 @@ namespace cfg
} }
else else
{ {
auto find = STLITER_FINDWITHCONDITION(fld.titles, tit, (tit.title_type == record.title_type) && (strcmp(tit.nro_target.nro_path, record.nro_target.nro_path) == 0)); auto find = STL_FIND_IF(fld.titles, tit, (tit.title_type == record.title_type) && (strcmp(tit.nro_target.nro_path, record.nro_target.nro_path) == 0));
if(STLITER_ISFOUND(fld.titles, find)) if(STL_FOUND(fld.titles, find))
{ {
if(fld.name == folder) return true; // It is already on that folder...? if(fld.name == folder) return true; // It is already on that folder...?
recjson = STLITER_UNWRAP(find).json_name; recjson = STL_UNWRAP(find).json_name;
fld.titles.erase(find); fld.titles.erase(find);
title_found = true; title_found = true;
@ -422,10 +447,10 @@ namespace cfg
} }
else // Add it to the new folder else // Add it to the new folder
{ {
auto find2 = STLITER_FINDWITHCONDITION(list.folders, fld, (fld.name == folder)); auto find2 = STL_FIND_IF(list.folders, fld, (fld.name == folder));
if(STLITER_ISFOUND(list.folders, find2)) if(STL_FOUND(list.folders, find2))
{ {
STLITER_UNWRAP(find2).titles.push_back(title); STL_UNWRAP(find2).titles.push_back(title);
} }
else else
{ {
@ -446,10 +471,10 @@ namespace cfg
{ {
if(!name.empty()) if(!name.empty())
{ {
auto f = STLITER_FINDWITHCONDITION(list.folders, fld, (fld.name == name)); auto f = STL_FIND_IF(list.folders, fld, (fld.name == name));
if(STLITER_ISFOUND(list.folders, f)) if(STL_FOUND(list.folders, f))
{ {
return STLITER_UNWRAP(f); return STL_UNWRAP(f);
} }
} }
return list.root; return list.root;
@ -464,18 +489,18 @@ namespace cfg
// Search in root first // Search in root first
if((TitleType)record.title_type == TitleType::Installed) if((TitleType)record.title_type == TitleType::Installed)
{ {
auto find = STLITER_FINDWITHCONDITION(list.root.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id)); auto find = STL_FIND_IF(list.root.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id));
if(STLITER_ISFOUND(list.root.titles, find)) if(STL_FOUND(list.root.titles, find))
{ {
if(!STLITER_UNWRAP(find).json_name.empty()) title_found = true; if(!STL_UNWRAP(find).json_name.empty()) title_found = true;
} }
} }
else else
{ {
auto find = STLITER_FINDWITHCONDITION(list.root.titles, tit, (tit.title_type == record.title_type) && (strcmp(tit.nro_target.nro_path, record.nro_target.nro_path) == 0)); auto find = STL_FIND_IF(list.root.titles, tit, (tit.title_type == record.title_type) && (strcmp(tit.nro_target.nro_path, record.nro_target.nro_path) == 0));
if(STLITER_ISFOUND(list.root.titles, find)) if(STL_FOUND(list.root.titles, find))
{ {
if(!STLITER_UNWRAP(find).json_name.empty()) title_found = true; if(!STL_UNWRAP(find).json_name.empty()) title_found = true;
} }
} }
@ -485,10 +510,10 @@ namespace cfg
{ {
if((TitleType)record.title_type == TitleType::Installed) if((TitleType)record.title_type == TitleType::Installed)
{ {
auto find = STLITER_FINDWITHCONDITION(fld.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id)); auto find = STL_FIND_IF(fld.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id));
if(STLITER_ISFOUND(fld.titles, find)) if(STL_FOUND(fld.titles, find))
{ {
if(!STLITER_UNWRAP(find).json_name.empty()) if(!STL_UNWRAP(find).json_name.empty())
{ {
title_found = true; title_found = true;
break; break;
@ -497,10 +522,10 @@ namespace cfg
} }
else else
{ {
auto find = STLITER_FINDWITHCONDITION(fld.titles, tit, (tit.title_type == record.title_type) && (strcmp(tit.nro_target.nro_path, record.nro_target.nro_path) == 0)); auto find = STL_FIND_IF(fld.titles, tit, (tit.title_type == record.title_type) && (strcmp(tit.nro_target.nro_path, record.nro_target.nro_path) == 0));
if(STLITER_ISFOUND(fld.titles, find)) if(STL_FOUND(fld.titles, find))
{ {
if(!STLITER_UNWRAP(find).json_name.empty()) if(!STL_UNWRAP(find).json_name.empty())
{ {
title_found = true; title_found = true;
break; break;
@ -550,16 +575,16 @@ namespace cfg
rec.app_id = appid; rec.app_id = appid;
rec.title_type = (u32)TitleType::Installed; rec.title_type = (u32)TitleType::Installed;
auto find = STLITER_FINDWITHCONDITION(list.root.titles, tit, (tit.app_id == appid)); auto find = STL_FIND_IF(list.root.titles, tit, (tit.app_id == appid));
if(STLITER_ISFOUND(list.root.titles, find)) if(STL_FOUND(list.root.titles, find))
{ {
list.root.titles.erase(find); list.root.titles.erase(find);
} }
auto find2 = STLITER_FINDWITHCONDITION(list.folders, fld, (fld.name == folder)); auto find2 = STL_FIND_IF(list.folders, fld, (fld.name == folder));
if(STLITER_ISFOUND(list.folders, find2)) if(STL_FOUND(list.folders, find2))
{ {
STLITER_UNWRAP(find2).titles.push_back(rec); STL_UNWRAP(find2).titles.push_back(rec);
} }
else else
{ {
@ -590,8 +615,8 @@ namespace cfg
if(folder.empty()) list.root.titles.push_back(rec); if(folder.empty()) list.root.titles.push_back(rec);
else else
{ {
auto find = STLITER_FINDWITHCONDITION(list.folders, fld, (fld.name == folder)); auto find = STL_FIND_IF(list.folders, fld, (fld.name == folder));
if(STLITER_ISFOUND(list.folders, find)) STLITER_UNWRAP(find).titles.push_back(rec); if(STL_FOUND(list.folders, find)) STL_UNWRAP(find).titles.push_back(rec);
else else
{ {
TitleFolder fld = {}; TitleFolder fld = {};

View file

@ -15,4 +15,11 @@ namespace os
psmGetChargerType(&cht); psmGetChargerType(&cht);
return (cht > ChargerType_None); return (cht > ChargerType_None);
} }
std::string GetFirmwareVersion()
{
SetSysFirmwareVersion fwver;
setsysGetFirmwareVersion(&fwver);
return std::string(fwver.display_version);
}
} }

View file

@ -61,8 +61,6 @@ namespace ui
bool warnshown; bool warnshown;
bool homebrew_mode; bool homebrew_mode;
u8 minalpha; u8 minalpha;
u32 root_idx;
u32 root_baseidx;
u32 mode; u32 mode;
s32 rawalpha; s32 rawalpha;
pu::audio::Sfx sfxTitleLaunch; pu::audio::Sfx sfxTitleLaunch;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -14,20 +14,25 @@ extern "C"
size_t __nx_heap_size = 0x10000000; // 256MB heap - now we can use as much as we want from the applet pool ;) size_t __nx_heap_size = 0x10000000; // 256MB heap - now we can use as much as we want from the applet pool ;)
} }
// Some global vars
ui::QMenuApplication::Ref qapp; ui::QMenuApplication::Ref qapp;
cfg::TitleList list; cfg::TitleList list;
std::vector<cfg::TitleRecord> homebrew; std::vector<cfg::TitleRecord> homebrew;
cfg::Config config; cfg::Config config;
cfg::ProcessedTheme theme; cfg::ProcessedTheme theme;
u8 *app_buf;
namespace qmenu namespace qmenu
{ {
void Initialize(bool cache_homebrew) void Initialize()
{ {
accountInitialize(); accountInitialize();
nsInitialize(); nsInitialize();
nifmInitialize(); nifmInitialize();
psmInitialize(); psmInitialize();
setsysInitialize();
db::Mount(); db::Mount();
fs::CreateDirectory(Q_BASE_DB_DIR); fs::CreateDirectory(Q_BASE_DB_DIR);
fs::CreateDirectory(Q_BASE_SD_DIR); fs::CreateDirectory(Q_BASE_SD_DIR);
@ -37,10 +42,11 @@ namespace qmenu
fs::CreateDirectory(Q_BASE_SD_DIR "/user"); fs::CreateDirectory(Q_BASE_SD_DIR "/user");
fs::CreateDirectory(Q_BASE_SD_DIR "/nro"); fs::CreateDirectory(Q_BASE_SD_DIR "/nro");
db::Commit(); db::Commit();
am::QMenu_InitializeDaemonService(); am::QMenu_InitializeDaemonService();
// Cache all homebrew (is this too slow...?) // Cache all homebrew (is this too slow...?)
homebrew = cfg::QueryAllHomebrew(cache_homebrew); homebrew = cfg::QueryAllHomebrew(true);
// Load menu config // Load menu config
config = cfg::EnsureConfig(); config = cfg::EnsureConfig();
@ -53,7 +59,10 @@ namespace qmenu
void Exit() void Exit()
{ {
am::QMenu_FinalizeDaemonService(); am::QMenu_FinalizeDaemonService();
db::Unmount(); db::Unmount();
setsysExit();
psmExit(); psmExit();
nifmExit(); nifmExit();
nsExit(); nsExit();
@ -61,15 +70,13 @@ namespace qmenu
} }
} }
u8 *app_buf;
int main() int main()
{ {
auto [rc, smode] = am::QMenu_ProcessInput(); auto [rc, smode] = am::QMenu_ProcessInput();
if(R_SUCCEEDED(rc)) if(R_SUCCEEDED(rc))
{ {
app_buf = new u8[RawRGBAScreenBufferSize](); app_buf = new u8[RawRGBAScreenBufferSize]();
qmenu::Initialize(smode == am::QMenuStartMode::StartupScreen); // Cache homebrew only on first launch qmenu::Initialize();
auto [_rc, menulist] = cfg::LoadTitleList(true); auto [_rc, menulist] = cfg::LoadTitleList(true);
list = menulist; list = menulist;

View file

@ -20,8 +20,6 @@ namespace ui
this->susptr = raw; this->susptr = raw;
this->mode = 0; this->mode = 0;
this->rawalpha = 255; this->rawalpha = 255;
this->root_idx = 0;
this->root_baseidx = 0;
this->last_hasconn = false; this->last_hasconn = false;
this->last_batterylvl = 0; this->last_batterylvl = 0;
this->last_charge = false; this->last_charge = false;
@ -111,131 +109,166 @@ namespace ui
void MenuLayout::menu_Click(u64 down, u32 index) void MenuLayout::menu_Click(u64 down, u32 index)
{ {
if(index == 0) if((down & KEY_A) || (down & KEY_X) || (down & KEY_Y))
{ {
if(down & KEY_A) if(index == 0)
{ {
if(this->homebrew_mode) if(down & KEY_A)
{ {
am::QMenuCommandWriter writer(am::QDaemonMessage::LaunchHomebrewLibApplet); if(this->homebrew_mode)
hb::TargetInput ipt = {};
strcpy(ipt.nro_path, "sdmc:/hbmenu.nro"); // Launch normal hbmenu
strcpy(ipt.argv, "sdmc:/hbmenu.nro");
writer.Write<hb::TargetInput>(ipt);
writer.FinishWrite();
pu::audio::Play(this->sfxTitleLaunch);
qapp->StopPlayBGM();
qapp->CloseWithFadeOut();
return;
}
else
{
qapp->CreateShowDialog("All", "All titles...", {"Ok"}, true);
}
}
}
else
{
u32 realidx = index - 1;
if(this->homebrew_mode)
{
auto hb = homebrew[realidx];
if(down & KEY_A) this->HandleHomebrewLaunch(hb);
else if(down & KEY_X)
{
auto sopt = qapp->CreateShowDialog("Add to menu", "Would you like to add this homebrew to the main menu?", { "Yes", "Cancel" }, true);
if(sopt == 0)
{ {
if(cfg::ExistsRecord(list, hb)) qapp->CreateShowDialog("Add to menu", "The homebrew is alredy in the main menu.\nNothing was added nor removed.", { "Ok" }, true); am::QMenuCommandWriter writer(am::QDaemonMessage::LaunchHomebrewLibApplet);
else hb::TargetInput ipt = {};
{ strcpy(ipt.nro_path, "sdmc:/hbmenu.nro"); // Launch normal hbmenu
cfg::SaveRecord(hb); strcpy(ipt.argv, "sdmc:/hbmenu.nro");
list.root.titles.push_back(hb); writer.Write<hb::TargetInput>(ipt);
qapp->CreateShowDialog("Add to menu", "The homebrew was successfully added to the main menu.", { "Ok" }, true); writer.FinishWrite();
}
pu::audio::Play(this->sfxTitleLaunch);
qapp->StopPlayBGM();
qapp->CloseWithFadeOut();
return;
}
else
{
qapp->CreateShowDialog("All", "All titles...", {"Ok"}, true);
} }
} }
} }
else else
{ {
auto &folder = cfg::FindFolderByName(list, this->curfolder); u32 realidx = index - 1;
if(realidx < folder.titles.size()) if(this->homebrew_mode)
{ {
auto title = folder.titles[realidx]; auto hb = homebrew[realidx];
if(down & KEY_A) if(down & KEY_A) this->HandleHomebrewLaunch(hb);
{
if(!qapp->IsSuspended())
{
if((cfg::TitleType)title.title_type == cfg::TitleType::Homebrew) this->HandleHomebrewLaunch(title);
else
{
am::QMenuCommandWriter writer(am::QDaemonMessage::LaunchApplication);
writer.Write<u64>(title.app_id);
writer.FinishWrite();
am::QMenuCommandResultReader reader;
if(reader && R_SUCCEEDED(reader.GetReadResult()))
{
pu::audio::Play(this->sfxTitleLaunch);
qapp->StopPlayBGM();
qapp->CloseWithFadeOut();
return;
}
else
{
auto rc = reader.GetReadResult();
qapp->CreateShowDialog("Title launch", "An error ocurred attempting to launch the title:\n" + util::FormatResultDisplay(rc) + " (" + util::FormatResultHex(rc) + ")", { "Ok" }, true);
}
reader.FinishRead();
}
}
else
{
if((cfg::TitleType)title.title_type == cfg::TitleType::Homebrew)
{
if(std::string(title.nro_target.nro_path) == qapp->GetSuspendedHomebrewPath())
{
if(this->mode == 1) this->mode = 2;
}
}
else
{
if(title.app_id == qapp->GetSuspendedApplicationId())
{
if(this->mode == 1) this->mode = 2;
}
}
}
}
else if(down & KEY_X) else if(down & KEY_X)
{ {
if(qapp->IsSuspended()) if(qapp->IsSuspended())
{ {
if((cfg::TitleType)title.title_type == cfg::TitleType::Homebrew) if(std::string(hb.nro_target.nro_path) == qapp->GetSuspendedHomebrewPath()) this->HandleCloseSuspended();
{
if(std::string(title.nro_target.nro_path) == qapp->GetSuspendedHomebrewPath()) this->HandleCloseSuspended();
}
else
{
if(title.app_id == qapp->GetSuspendedApplicationId()) this->HandleCloseSuspended();
}
} }
} }
else if(down & KEY_Y) else if(down & KEY_Y)
{ {
if(this->HandleFolderChange(title)) auto sopt = qapp->CreateShowDialog("Add to menu", "Would you like to add this homebrew to the main menu?", { "Yes", "Cancel" }, true);
if(sopt == 0)
{ {
this->MoveFolder(this->curfolder, true); if(cfg::ExistsRecord(list, hb)) qapp->CreateShowDialog("Add to menu", "The homebrew is already in the main menu.\nNothing was added nor removed.\n\nYou can remove it from main menu itself.", { "Ok" }, true);
else
{
cfg::SaveRecord(hb);
list.root.titles.push_back(hb);
qapp->CreateShowDialog("Add to menu", "The homebrew was successfully added to the main menu.", { "Ok" }, true);
}
} }
} }
} }
else else
{ {
auto foldr = list.folders[realidx - folder.titles.size()]; auto &folder = cfg::FindFolderByName(list, this->curfolder);
if(down & KEY_A) if(realidx < folder.titles.size())
{ {
this->MoveFolder(foldr.name, true); auto title = folder.titles[realidx];
if(down & KEY_A)
{
if(!qapp->IsSuspended())
{
if((cfg::TitleType)title.title_type == cfg::TitleType::Homebrew) this->HandleHomebrewLaunch(title);
else
{
am::QMenuCommandWriter writer(am::QDaemonMessage::LaunchApplication);
writer.Write<u64>(title.app_id);
writer.FinishWrite();
am::QMenuCommandResultReader reader;
if(reader && R_SUCCEEDED(reader.GetReadResult()))
{
pu::audio::Play(this->sfxTitleLaunch);
qapp->StopPlayBGM();
qapp->CloseWithFadeOut();
return;
}
else
{
auto rc = reader.GetReadResult();
qapp->CreateShowDialog("Title launch", "An error ocurred attempting to launch the title:\n" + util::FormatResultDisplay(rc) + " (" + util::FormatResultHex(rc) + ")", { "Ok" }, true);
}
reader.FinishRead();
}
}
else
{
if((cfg::TitleType)title.title_type == cfg::TitleType::Homebrew)
{
if(std::string(title.nro_target.nro_path) == qapp->GetSuspendedHomebrewPath())
{
if(this->mode == 1) this->mode = 2;
}
}
else
{
if(title.app_id == qapp->GetSuspendedApplicationId())
{
if(this->mode == 1) this->mode = 2;
}
}
}
}
else if(down & KEY_X)
{
if(qapp->IsSuspended())
{
if((cfg::TitleType)title.title_type == cfg::TitleType::Homebrew)
{
if(std::string(title.nro_target.nro_path) == qapp->GetSuspendedHomebrewPath()) this->HandleCloseSuspended();
}
else
{
if(title.app_id == qapp->GetSuspendedApplicationId()) this->HandleCloseSuspended();
}
}
}
else if(down & KEY_Y)
{
if((cfg::TitleType)title.title_type == cfg::TitleType::Homebrew)
{
auto sopt = qapp->CreateShowDialog("Entry options", "What would you like to do with the selected entry?", { "Move to/from folder", "Remove", "Cancel" }, true);
if(sopt == 0)
{
if(this->HandleFolderChange(title))
{
this->MoveFolder(this->curfolder, true);
}
}
else if(sopt == 1)
{
auto sopt2 = qapp->CreateShowDialog("Remove entry", "Would you like to remove this entry from main menu?\nThis homebrew will still be launchable from the homebrew menu.", { "Yes", "No" }, true);
if(sopt2 == 0)
{
cfg::RemoveRecord(title);
folder.titles.erase(folder.titles.begin() + realidx);
qapp->CreateShowDialog("Remove entry", "The entry was successfully removed.", { "Ok" }, true);
this->MoveFolder(this->curfolder, true);
}
}
}
else
{
if(this->HandleFolderChange(title))
{
this->MoveFolder(this->curfolder, true);
}
}
}
}
else
{
auto foldr = list.folders[realidx - folder.titles.size()];
if(down & KEY_A)
{
this->MoveFolder(foldr.name, true);
}
} }
} }
} }
@ -326,62 +359,45 @@ namespace ui
{ {
if(fade) qapp->FadeOut(); if(fade) qapp->FadeOut();
if(this->homebrew_mode) auto itm_list = homebrew;
if(!this->homebrew_mode)
{ {
this->itemsMenu->ClearItems();
this->itemsMenu->AddItem(cfg::ProcessedThemeResource(theme, "ui/Hbmenu.png"));
for(auto itm: homebrew)
{
this->itemsMenu->AddItem(cfg::GetRecordIconPath(itm));
}
}
else
{
if(this->curfolder.empty())
{
// Moving from root to a folder, let's save the indexes we were on
this->root_idx = itemsMenu->GetSelectedItem();
this->root_baseidx = itemsMenu->GetBaseItemIndex();
}
auto &folder = cfg::FindFolderByName(list, name); auto &folder = cfg::FindFolderByName(list, name);
this->itemsMenu->ClearItems(); itm_list = folder.titles;
}
// Add first item for all titles menu this->itemsMenu->ClearItems();
this->itemsMenu->AddItem(cfg::ProcessedThemeResource(theme, "ui/AllTitles.png")); auto initial_itm = cfg::ProcessedThemeResource(theme, "ui/AllTitles.png");
u32 tmpidx = 0; if(this->homebrew_mode) initial_itm = cfg::ProcessedThemeResource(theme, "ui/Hbmenu.png");
for(auto itm: folder.titles) this->itemsMenu->AddItem(initial_itm);
u32 tmpidx = 0;
for(auto itm: itm_list)
{
if((cfg::TitleType)itm.title_type == cfg::TitleType::Installed)
{ {
if((cfg::TitleType)itm.title_type == cfg::TitleType::Installed) if(qapp->IsTitleSuspended()) if(qapp->GetSuspendedApplicationId() == itm.app_id) this->itemsMenu->SetSuspendedItem(tmpidx + 1); // Skip initial item
{
if(qapp->IsTitleSuspended()) if(qapp->GetSuspendedApplicationId() == itm.app_id) this->itemsMenu->SetSuspendedItem(tmpidx + 1); // 1st item is always "all titles"!
}
else
{
if(qapp->IsHomebrewSuspended()) if(qapp->GetSuspendedHomebrewPath() == std::string(itm.nro_target.nro_path)) this->itemsMenu->SetSuspendedItem(tmpidx + 1); // 1st item is always "all titles"!
}
this->itemsMenu->AddItem(cfg::GetRecordIconPath(itm));
tmpidx++;
} }
else
{
if(qapp->IsHomebrewSuspended()) if(qapp->GetSuspendedHomebrewPath() == std::string(itm.nro_target.nro_path)) this->itemsMenu->SetSuspendedItem(tmpidx + 1); // Skip initial item
}
this->itemsMenu->AddItem(cfg::GetRecordIconPath(itm));
tmpidx++;
}
if(!this->homebrew_mode) // Only normal menu has folders
{
if(name.empty()) if(name.empty())
{ {
std::vector<cfg::TitleFolder> folders; STL_REMOVE_IF(list.folders, fldr, (fldr.titles.empty())) // Remove empty folders
for(auto folder: list.folders) for(auto folder: list.folders) this->itemsMenu->AddItem(cfg::ProcessedThemeResource(theme, "ui/Folder.png"));
{
if(!folder.titles.empty())
{
folders.push_back(folder);
this->itemsMenu->AddItem(cfg::ProcessedThemeResource(theme, "ui/Folder.png"));
}
}
list.folders = folders;
this->itemsMenu->SetBasePositions(this->root_idx, this->root_baseidx);
} }
this->itemsMenu->UpdateBorderIcons();
this->curfolder = name;
} }
this->itemsMenu->UpdateBorderIcons();
if(!this->homebrew_mode) this->curfolder = name;
if(fade) qapp->FadeIn(); if(fade) qapp->FadeIn();
} }
@ -568,6 +584,7 @@ namespace ui
swkbdConfigSetHeaderText(&swkbd, "Enter directory name"); swkbdConfigSetHeaderText(&swkbd, "Enter directory name");
char dir[500] = {0}; char dir[500] = {0};
auto rc = swkbdShow(&swkbd, dir, 500); auto rc = swkbdShow(&swkbd, dir, 500);
swkbdClose(&swkbd);
if(R_SUCCEEDED(rc)) if(R_SUCCEEDED(rc))
{ {
changedone = cfg::MoveRecordTo(list, rec, std::string(dir)); changedone = cfg::MoveRecordTo(list, rec, std::string(dir));
@ -575,7 +592,7 @@ namespace ui
} }
else else
{ {
auto sopt = qapp->CreateShowDialog("Title move", "Would you like to move this entry outside the folder?", { "Yes", "Cancel" }, true); auto sopt = qapp->CreateShowDialog("Entry move", "Would you like to move this entry outside the folder?", { "Yes", "Cancel" }, true);
if(sopt == 0) if(sopt == 0)
{ {
changedone = cfg::MoveRecordTo(list, rec, ""); changedone = cfg::MoveRecordTo(list, rec, "");
@ -652,6 +669,7 @@ namespace ui
swkbdConfigSetHeaderText(&swkbd, "Enter web page URL"); swkbdConfigSetHeaderText(&swkbd, "Enter web page URL");
char url[500] = {0}; char url[500] = {0};
auto rc = swkbdShow(&swkbd, url, 500); auto rc = swkbdShow(&swkbd, url, 500);
swkbdClose(&swkbd);
if(R_SUCCEEDED(rc)) if(R_SUCCEEDED(rc))
{ {
WebCommonConfig web = {}; WebCommonConfig web = {};

View file

@ -139,7 +139,10 @@ namespace ui
void SideMenu::ClearItems() void SideMenu::ClearItems()
{ {
this->icons.clear(); this->icons.clear();
for(auto &ricon: this->ricons) pu::ui::render::DeleteTexture(ricon); for(auto ricon: this->ricons)
{
if(ricon != NULL) pu::ui::render::DeleteTexture(ricon);
}
this->ricons.clear(); this->ricons.clear();
this->selitm = 0; this->selitm = 0;
this->baseiconidx = 0; this->baseiconidx = 0;

View file

@ -85,13 +85,13 @@ namespace ui
swkbdConfigSetHeaderText(&swkbd, "Input user password"); swkbdConfigSetHeaderText(&swkbd, "Input user password");
char inpass[0x10] = {0}; char inpass[0x10] = {0};
auto rc = swkbdShow(&swkbd, inpass, 0x10); auto rc = swkbdShow(&swkbd, inpass, 0x10);
swkbdClose(&swkbd);
if(R_SUCCEEDED(rc)) if(R_SUCCEEDED(rc))
{ {
auto rc = db::TryLogUser(uid, std::string(inpass)); auto rc = db::TryLogUser(uid, std::string(inpass));
if(R_FAILED(rc)) qapp->CreateShowDialog("Login", "Invalid password. Please try again.", {"Ok"}, true); if(R_FAILED(rc)) qapp->CreateShowDialog("Login", "Invalid password. Please try again.", {"Ok"}, true);
else login_ok = true; else login_ok = true;
} }
swkbdClose(&swkbd);
} }
else login_ok = true; else login_ok = true;
if(login_ok) if(login_ok)

View file

@ -1,7 +1,7 @@
export Q_VERSION := dev export Q_VERSION := dev
.PHONY: all clean .PHONY: all dev clean
all: all:
@$(MAKE) -C SystemAppletQDaemon/ @$(MAKE) -C SystemAppletQDaemon/
@ -15,6 +15,14 @@ all:
@cp -r $(CURDIR)/LibraryAppletQHbTarget/Out $(CURDIR)/SdOut/titles/0100000000001009 @cp -r $(CURDIR)/LibraryAppletQHbTarget/Out $(CURDIR)/SdOut/titles/0100000000001009
@cp -r $(CURDIR)/SystemApplicationQHbTarget/Out $(CURDIR)/SdOut/titles/01008BB00013C000 @cp -r $(CURDIR)/SystemApplicationQHbTarget/Out $(CURDIR)/SdOut/titles/01008BB00013C000
setdev:
$(eval export Q_DEV := 1)
@echo
@echo IMPORTANT! Building in development mode - do not treat this build as release...
@echo
dev: setdev all
clean: clean:
@rm -rf $(CURDIR)/SdOut @rm -rf $(CURDIR)/SdOut
@$(MAKE) clean -C SystemAppletQDaemon/ @$(MAKE) clean -C SystemAppletQDaemon/

View file

@ -4,6 +4,8 @@
uLaunch is a very ambicious project, consisting on two custom library applets, a custom system application and a custom system applet, in order to replace the console's **HOME menu** with a custom, homebrew-orienteed one. uLaunch is a very ambicious project, consisting on two custom library applets, a custom system application and a custom system applet, in order to replace the console's **HOME menu** with a custom, homebrew-orienteed one.
No, this isn't any kind of HOME menu extension, injection, patch, etc. uLaunch is a **complete** reimplementation, 100% open-source, which also takes over eShop and Mii applets and flog system title, for its extended functionality.
## **NOTE:** the project is still a work-in-progress. Check what's left to do before release [here](TODO.md)! ## **NOTE:** the project is still a work-in-progress. Check what's left to do before release [here](TODO.md)!
- The project is licensed as **GPLv2**. - The project is licensed as **GPLv2**.

View file

@ -34,6 +34,10 @@ CFLAGS := -g -Wall -O2 -ffunction-sections \
CFLAGS += $(INCLUDE) -D__SWITCH__ -DQ_VERSION=\"$(Q_VERSION)\" CFLAGS += $(INCLUDE) -D__SWITCH__ -DQ_VERSION=\"$(Q_VERSION)\"
ifeq ($(Q_DEV),1)
CFLAGS += -DQ_DEV
endif
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17 CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
ASFLAGS := -g $(ARCH) ASFLAGS := -g $(ARCH)

View file

@ -13,7 +13,11 @@
extern "C" extern "C"
{ {
u32 __nx_applet_type = AppletType_SystemApplet; u32 __nx_applet_type = AppletType_SystemApplet;
size_t __nx_heap_size = 0x3000000;//0x1000000; #ifdef Q_DEV
size_t __nx_heap_size = 0x3000000; // Dev uses 3x heap (48MB, still pretty low) for debug console
#else
size_t __nx_heap_size = 0x1000000;
#endif
} }
u8 *app_buf; u8 *app_buf;
@ -286,52 +290,54 @@ namespace qdaemon
app_buf = new u8[RawRGBAScreenBufferSize](); app_buf = new u8[RawRGBAScreenBufferSize]();
fs::CreateDirectory(Q_BASE_SD_DIR); fs::CreateDirectory(Q_BASE_SD_DIR);
// Debug testing mode #ifdef Q_DEV
consoleInit(NULL); // Debug testing mode
CONSOLE_FMT("Welcome to QDaemon's debug mode!") consoleInit(NULL);
CONSOLE_FMT("") CONSOLE_FMT("Welcome to QDaemon's debug mode!")
CONSOLE_FMT("(A) -> Dump system save data to sd:/<q>/save_dump") CONSOLE_FMT("")
CONSOLE_FMT("(B) -> Delete everything in save data (except official HOME menu's content)") CONSOLE_FMT("(A) -> Dump system save data to sd:/<q>/save_dump")
CONSOLE_FMT("(X) -> Reboot system") CONSOLE_FMT("(B) -> Delete everything in save data (except official HOME menu's content)")
CONSOLE_FMT("(Y) -> Continue to QMenu (proceed launch)") CONSOLE_FMT("(X) -> Reboot system")
CONSOLE_FMT("") CONSOLE_FMT("(Y) -> Continue to QMenu (proceed launch)")
CONSOLE_FMT("")
while(true) while(true)
{
hidScanInput();
auto k = hidKeysDown(CONTROLLER_P1_AUTO);
if(k & KEY_A)
{ {
db::Mount(); hidScanInput();
fs::CopyDirectory(Q_DB_MOUNT_NAME ":/", Q_BASE_SD_DIR "/save_dump"); auto k = hidKeysDown(CONTROLLER_P1_AUTO);
db::Unmount(); if(k & KEY_A)
CONSOLE_FMT(" - Dump done.") {
db::Mount();
fs::CopyDirectory(Q_DB_MOUNT_NAME ":/", Q_BASE_SD_DIR "/save_dump");
db::Unmount();
CONSOLE_FMT(" - Dump done.")
}
else if(k & KEY_B)
{
db::Mount();
fs::DeleteDirectory(Q_BASE_DB_DIR);
fs::CreateDirectory(Q_BASE_DB_DIR);
db::Commit();
db::Unmount();
CONSOLE_FMT(" - Cleanup done.")
}
else if(k & KEY_X)
{
CONSOLE_FMT(" - Rebooting...")
svcSleepThread(200'000'000);
appletStartRebootSequence();
}
else if(k & KEY_Y)
{
CONSOLE_FMT(" - Proceeding with launch...")
svcSleepThread(500'000'000);
break;
}
svcSleepThread(10'000'000);
} }
else if(k & KEY_B)
{
db::Mount();
fs::DeleteDirectory(Q_BASE_DB_DIR);
fs::CreateDirectory(Q_BASE_DB_DIR);
db::Commit();
db::Unmount();
CONSOLE_FMT(" - Cleanup done.")
}
else if(k & KEY_X)
{
CONSOLE_FMT(" - Rebooting...")
svcSleepThread(200'000'000);
appletStartRebootSequence();
}
else if(k & KEY_Y)
{
CONSOLE_FMT(" - Proceeding with launch...")
svcSleepThread(500'000'000);
break;
}
svcSleepThread(10'000'000);
}
consoleExit(NULL); consoleExit(NULL);
#endif
svcSleepThread(100'000'000); // Wait for proper moment svcSleepThread(100'000'000); // Wait for proper moment
} }