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 ApplicationSend(void *data, size_t size, AppletLaunchParameterKind kind = AppletLaunchParameterKind_UserChannel);
u64 ApplicationGetId();
Result ApplicationGetResult();
}

View file

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

View file

@ -6,4 +6,5 @@ namespace os
{
u32 GetBatteryLevel();
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 STLITER_ISFOUND(stl_item, find_ret) (find_ret != stl_item.end())
#define STLITER_UNWRAP(find_ret) (*find_ret)
#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 STL_FOUND(stl_item, find_ret) (find_ret != stl_item.end())
#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>
using ResultWith = std::tuple<Result, Args...>;

View file

@ -257,7 +257,10 @@ namespace cfg
if(record.json_name.empty()) record.json_name = jsonname;
json += "/" + jsonname;
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;
}
else if((TitleType)record.title_type == TitleType::Installed)
@ -277,6 +280,28 @@ namespace cfg
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 title_found = false;
@ -284,11 +309,11 @@ namespace cfg
std::string recjson;
// Search in root first
auto find = STLITER_FINDWITHCONDITION(list.root.titles, tit, (tit.app_id == app_id));
if(STLITER_ISFOUND(list.root.titles, find))
auto find = STL_FIND_IF(list.root.titles, tit, (tit.app_id == app_id));
if(STL_FOUND(list.root.titles, find))
{
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);
title_found = true;
@ -298,11 +323,11 @@ namespace cfg
{
for(auto &fld: list.folders)
{
auto find = STLITER_FINDWITHCONDITION(fld.titles, entry, (entry.app_id == app_id));
if(STLITER_ISFOUND(fld.titles, find))
auto find = STL_FIND_IF(fld.titles, entry, (entry.app_id == app_id));
if(STL_FOUND(fld.titles, find))
{
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);
title_found = true;
@ -325,10 +350,10 @@ namespace cfg
}
else // Add it to the new folder
{
auto find2 = STLITER_FINDWITHCONDITION(list.folders, fld, (fld.name == folder));
if(STLITER_ISFOUND(list.folders, find2))
auto find2 = STL_FIND_IF(list.folders, fld, (fld.name == folder));
if(STL_FOUND(list.folders, find2))
{
STLITER_UNWRAP(find2).titles.push_back(title);
STL_UNWRAP(find2).titles.push_back(title);
}
else
{
@ -354,11 +379,11 @@ namespace cfg
// Search in root first
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));
if(STLITER_ISFOUND(list.root.titles, find))
auto find = STL_FIND_IF(list.root.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id));
if(STL_FOUND(list.root.titles, find))
{
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);
title_found = true;
@ -366,11 +391,11 @@ namespace cfg
}
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));
if(STLITER_ISFOUND(list.root.titles, find))
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(STL_FOUND(list.root.titles, find))
{
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);
title_found = true;
@ -383,11 +408,11 @@ namespace cfg
{
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));
if(STLITER_ISFOUND(fld.titles, find))
auto find = STL_FIND_IF(fld.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id));
if(STL_FOUND(fld.titles, find))
{
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);
title_found = true;
@ -396,11 +421,11 @@ namespace cfg
}
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));
if(STLITER_ISFOUND(fld.titles, find))
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(STL_FOUND(fld.titles, find))
{
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);
title_found = true;
@ -422,10 +447,10 @@ namespace cfg
}
else // Add it to the new folder
{
auto find2 = STLITER_FINDWITHCONDITION(list.folders, fld, (fld.name == folder));
if(STLITER_ISFOUND(list.folders, find2))
auto find2 = STL_FIND_IF(list.folders, fld, (fld.name == folder));
if(STL_FOUND(list.folders, find2))
{
STLITER_UNWRAP(find2).titles.push_back(title);
STL_UNWRAP(find2).titles.push_back(title);
}
else
{
@ -446,10 +471,10 @@ namespace cfg
{
if(!name.empty())
{
auto f = STLITER_FINDWITHCONDITION(list.folders, fld, (fld.name == name));
if(STLITER_ISFOUND(list.folders, f))
auto f = STL_FIND_IF(list.folders, fld, (fld.name == name));
if(STL_FOUND(list.folders, f))
{
return STLITER_UNWRAP(f);
return STL_UNWRAP(f);
}
}
return list.root;
@ -464,18 +489,18 @@ namespace cfg
// Search in root first
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));
if(STLITER_ISFOUND(list.root.titles, find))
auto find = STL_FIND_IF(list.root.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id));
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
{
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));
if(STLITER_ISFOUND(list.root.titles, find))
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(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)
{
auto find = STLITER_FINDWITHCONDITION(fld.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id));
if(STLITER_ISFOUND(fld.titles, find))
auto find = STL_FIND_IF(fld.titles, tit, (tit.title_type == record.title_type) && (tit.app_id == record.app_id));
if(STL_FOUND(fld.titles, find))
{
if(!STLITER_UNWRAP(find).json_name.empty())
if(!STL_UNWRAP(find).json_name.empty())
{
title_found = true;
break;
@ -497,10 +522,10 @@ namespace cfg
}
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));
if(STLITER_ISFOUND(fld.titles, find))
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(STL_FOUND(fld.titles, find))
{
if(!STLITER_UNWRAP(find).json_name.empty())
if(!STL_UNWRAP(find).json_name.empty())
{
title_found = true;
break;
@ -550,16 +575,16 @@ namespace cfg
rec.app_id = appid;
rec.title_type = (u32)TitleType::Installed;
auto find = STLITER_FINDWITHCONDITION(list.root.titles, tit, (tit.app_id == appid));
if(STLITER_ISFOUND(list.root.titles, find))
auto find = STL_FIND_IF(list.root.titles, tit, (tit.app_id == appid));
if(STL_FOUND(list.root.titles, find))
{
list.root.titles.erase(find);
}
auto find2 = STLITER_FINDWITHCONDITION(list.folders, fld, (fld.name == folder));
if(STLITER_ISFOUND(list.folders, find2))
auto find2 = STL_FIND_IF(list.folders, fld, (fld.name == folder));
if(STL_FOUND(list.folders, find2))
{
STLITER_UNWRAP(find2).titles.push_back(rec);
STL_UNWRAP(find2).titles.push_back(rec);
}
else
{
@ -590,8 +615,8 @@ namespace cfg
if(folder.empty()) list.root.titles.push_back(rec);
else
{
auto find = STLITER_FINDWITHCONDITION(list.folders, fld, (fld.name == folder));
if(STLITER_ISFOUND(list.folders, find)) STLITER_UNWRAP(find).titles.push_back(rec);
auto find = STL_FIND_IF(list.folders, fld, (fld.name == folder));
if(STL_FOUND(list.folders, find)) STL_UNWRAP(find).titles.push_back(rec);
else
{
TitleFolder fld = {};

View file

@ -15,4 +15,11 @@ namespace os
psmGetChargerType(&cht);
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 homebrew_mode;
u8 minalpha;
u32 root_idx;
u32 root_baseidx;
u32 mode;
s32 rawalpha;
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 ;)
}
// Some global vars
ui::QMenuApplication::Ref qapp;
cfg::TitleList list;
std::vector<cfg::TitleRecord> homebrew;
cfg::Config config;
cfg::ProcessedTheme theme;
u8 *app_buf;
namespace qmenu
{
void Initialize(bool cache_homebrew)
void Initialize()
{
accountInitialize();
nsInitialize();
nifmInitialize();
psmInitialize();
setsysInitialize();
db::Mount();
fs::CreateDirectory(Q_BASE_DB_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 "/nro");
db::Commit();
am::QMenu_InitializeDaemonService();
// Cache all homebrew (is this too slow...?)
homebrew = cfg::QueryAllHomebrew(cache_homebrew);
homebrew = cfg::QueryAllHomebrew(true);
// Load menu config
config = cfg::EnsureConfig();
@ -53,7 +59,10 @@ namespace qmenu
void Exit()
{
am::QMenu_FinalizeDaemonService();
db::Unmount();
setsysExit();
psmExit();
nifmExit();
nsExit();
@ -61,15 +70,13 @@ namespace qmenu
}
}
u8 *app_buf;
int main()
{
auto [rc, smode] = am::QMenu_ProcessInput();
if(R_SUCCEEDED(rc))
{
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);
list = menulist;

View file

@ -20,8 +20,6 @@ namespace ui
this->susptr = raw;
this->mode = 0;
this->rawalpha = 255;
this->root_idx = 0;
this->root_baseidx = 0;
this->last_hasconn = false;
this->last_batterylvl = 0;
this->last_charge = false;
@ -111,131 +109,166 @@ namespace ui
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);
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(this->homebrew_mode)
{
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);
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);
}
am::QMenuCommandWriter writer(am::QDaemonMessage::LaunchHomebrewLibApplet);
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
{
auto &folder = cfg::FindFolderByName(list, this->curfolder);
if(realidx < folder.titles.size())
u32 realidx = index - 1;
if(this->homebrew_mode)
{
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;
}
}
}
}
auto hb = homebrew[realidx];
if(down & KEY_A) this->HandleHomebrewLaunch(hb);
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();
}
if(std::string(hb.nro_target.nro_path) == qapp->GetSuspendedHomebrewPath()) this->HandleCloseSuspended();
}
}
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
{
auto foldr = list.folders[realidx - folder.titles.size()];
if(down & KEY_A)
auto &folder = cfg::FindFolderByName(list, this->curfolder);
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(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);
this->itemsMenu->ClearItems();
itm_list = folder.titles;
}
// Add first item for all titles menu
this->itemsMenu->AddItem(cfg::ProcessedThemeResource(theme, "ui/AllTitles.png"));
u32 tmpidx = 0;
for(auto itm: folder.titles)
this->itemsMenu->ClearItems();
auto initial_itm = cfg::ProcessedThemeResource(theme, "ui/AllTitles.png");
if(this->homebrew_mode) initial_itm = cfg::ProcessedThemeResource(theme, "ui/Hbmenu.png");
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); // 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++;
if(qapp->IsTitleSuspended()) if(qapp->GetSuspendedApplicationId() == itm.app_id) this->itemsMenu->SetSuspendedItem(tmpidx + 1); // Skip initial item
}
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())
{
std::vector<cfg::TitleFolder> folders;
for(auto folder: list.folders)
{
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);
STL_REMOVE_IF(list.folders, fldr, (fldr.titles.empty())) // Remove empty folders
for(auto folder: list.folders) this->itemsMenu->AddItem(cfg::ProcessedThemeResource(theme, "ui/Folder.png"));
}
this->itemsMenu->UpdateBorderIcons();
this->curfolder = name;
}
this->itemsMenu->UpdateBorderIcons();
if(!this->homebrew_mode) this->curfolder = name;
if(fade) qapp->FadeIn();
}
@ -568,6 +584,7 @@ namespace ui
swkbdConfigSetHeaderText(&swkbd, "Enter directory name");
char dir[500] = {0};
auto rc = swkbdShow(&swkbd, dir, 500);
swkbdClose(&swkbd);
if(R_SUCCEEDED(rc))
{
changedone = cfg::MoveRecordTo(list, rec, std::string(dir));
@ -575,7 +592,7 @@ namespace ui
}
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)
{
changedone = cfg::MoveRecordTo(list, rec, "");
@ -652,6 +669,7 @@ namespace ui
swkbdConfigSetHeaderText(&swkbd, "Enter web page URL");
char url[500] = {0};
auto rc = swkbdShow(&swkbd, url, 500);
swkbdClose(&swkbd);
if(R_SUCCEEDED(rc))
{
WebCommonConfig web = {};

View file

@ -139,7 +139,10 @@ namespace ui
void SideMenu::ClearItems()
{
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->selitm = 0;
this->baseiconidx = 0;

View file

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

View file

@ -1,7 +1,7 @@
export Q_VERSION := dev
.PHONY: all clean
.PHONY: all dev clean
all:
@$(MAKE) -C SystemAppletQDaemon/
@ -15,6 +15,14 @@ all:
@cp -r $(CURDIR)/LibraryAppletQHbTarget/Out $(CURDIR)/SdOut/titles/0100000000001009
@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:
@rm -rf $(CURDIR)/SdOut
@$(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.
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)!
- 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)\"
ifeq ($(Q_DEV),1)
CFLAGS += -DQ_DEV
endif
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
ASFLAGS := -g $(ARCH)

View file

@ -13,7 +13,11 @@
extern "C"
{
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;
@ -286,52 +290,54 @@ namespace qdaemon
app_buf = new u8[RawRGBAScreenBufferSize]();
fs::CreateDirectory(Q_BASE_SD_DIR);
// Debug testing mode
consoleInit(NULL);
CONSOLE_FMT("Welcome to QDaemon's debug mode!")
CONSOLE_FMT("")
CONSOLE_FMT("(A) -> Dump system save data to sd:/<q>/save_dump")
CONSOLE_FMT("(B) -> Delete everything in save data (except official HOME menu's content)")
CONSOLE_FMT("(X) -> Reboot system")
CONSOLE_FMT("(Y) -> Continue to QMenu (proceed launch)")
CONSOLE_FMT("")
#ifdef Q_DEV
// Debug testing mode
consoleInit(NULL);
CONSOLE_FMT("Welcome to QDaemon's debug mode!")
CONSOLE_FMT("")
CONSOLE_FMT("(A) -> Dump system save data to sd:/<q>/save_dump")
CONSOLE_FMT("(B) -> Delete everything in save data (except official HOME menu's content)")
CONSOLE_FMT("(X) -> Reboot system")
CONSOLE_FMT("(Y) -> Continue to QMenu (proceed launch)")
CONSOLE_FMT("")
while(true)
{
hidScanInput();
auto k = hidKeysDown(CONTROLLER_P1_AUTO);
if(k & KEY_A)
while(true)
{
db::Mount();
fs::CopyDirectory(Q_DB_MOUNT_NAME ":/", Q_BASE_SD_DIR "/save_dump");
db::Unmount();
CONSOLE_FMT(" - Dump done.")
hidScanInput();
auto k = hidKeysDown(CONTROLLER_P1_AUTO);
if(k & KEY_A)
{
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
}