Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fb5319da3 | ||
|
|
6970fec554 | ||
|
|
36be56647f | ||
|
|
cca6326314 | ||
|
|
9176c6780a | ||
|
|
b1a6b12cf3 | ||
|
|
c7cc11cc98 | ||
|
|
ec4b96b95d | ||
|
|
a2e343daa7 | ||
|
|
b811c9e3cd |
@@ -66,7 +66,7 @@
|
||||
"Select Theme": "选择主题",
|
||||
"Shuffle": "随机播放",
|
||||
"Music": "音乐",
|
||||
"12 Hour Time": "",
|
||||
"12 Hour Time": "12小时制时间",
|
||||
"Network": "网络",
|
||||
"Network Options": "网络选项",
|
||||
"Ftp": "FTP",
|
||||
@@ -170,7 +170,7 @@
|
||||
"Controller": "控制器",
|
||||
"Pad ": "手柄 ",
|
||||
" (Available)": " (可用的)",
|
||||
" (Unsupported)": "",
|
||||
" (Unsupported)": " (不支持的)",
|
||||
" (Unconnected)": " (未连接)",
|
||||
"HandHeld": "掌机模式",
|
||||
"Rotation": "旋转",
|
||||
@@ -239,7 +239,7 @@
|
||||
"Update avaliable: ": "有可用更新!",
|
||||
"Download update: ": "下载更新:",
|
||||
"Updated to ": "更新至 ",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Press OK to restart Sphaira": "按OK键以重启shphaira菜单",
|
||||
"Restart Sphaira?": "重启 Sphaira?",
|
||||
"Failed to download update": "更新下载失败",
|
||||
"Restore hbmenu?": "恢复 hbmenu?",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(sphaira_VERSION 0.8.1)
|
||||
set(sphaira_VERSION 0.8.2)
|
||||
|
||||
project(sphaira
|
||||
VERSION ${sphaira_VERSION}
|
||||
@@ -41,6 +41,7 @@ add_executable(sphaira
|
||||
source/ui/menus/file_viewer.cpp
|
||||
source/ui/menus/filebrowser.cpp
|
||||
source/ui/menus/homebrew.cpp
|
||||
source/ui/menus/irs_menu.cpp
|
||||
source/ui/menus/main_menu.cpp
|
||||
source/ui/menus/menu_base.cpp
|
||||
source/ui/menus/themezer.cpp
|
||||
|
||||
@@ -137,6 +137,13 @@ private:
|
||||
void InstallForwarder();
|
||||
void InstallFile(const FileEntry& target);
|
||||
void InstallFiles(const std::vector<FileEntry>& targets);
|
||||
|
||||
void UnzipFile(const fs::FsPath& folder, const FileEntry& target);
|
||||
void UnzipFiles(fs::FsPath folder, const std::vector<FileEntry>& targets);
|
||||
|
||||
void ZipFile(const fs::FsPath& zip_path, const FileEntry& target);
|
||||
void ZipFiles(fs::FsPath zip_path, const std::vector<FileEntry>& targets);
|
||||
|
||||
auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result;
|
||||
|
||||
void LoadAssocEntriesPath(const fs::FsPath& path);
|
||||
|
||||
69
sphaira/include/ui/menus/irs_menu.hpp
Normal file
69
sphaira/include/ui/menus/irs_menu.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::ui::menu::irs {
|
||||
|
||||
enum Rotation {
|
||||
Rotation_0,
|
||||
Rotation_90,
|
||||
Rotation_180,
|
||||
Rotation_270,
|
||||
};
|
||||
|
||||
enum Colour {
|
||||
Colour_Grey,
|
||||
Colour_Ironbow,
|
||||
Colour_Green,
|
||||
Colour_Red,
|
||||
Colour_Blue,
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
IrsIrCameraHandle m_handle{};
|
||||
IrsIrCameraStatus status{};
|
||||
bool m_update_needed{};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu();
|
||||
~Menu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
private:
|
||||
void PollCameraStatus(bool statup = false);
|
||||
void LoadDefaultConfig();
|
||||
void UpdateConfig(const IrsImageTransferProcessorExConfig* config);
|
||||
void ResetImage();
|
||||
void UpdateImage();
|
||||
void updateColourArray();
|
||||
auto GetEntryName(s64 i) -> std::string;
|
||||
|
||||
private:
|
||||
Result m_init_rc{};
|
||||
|
||||
IrsImageTransferProcessorExConfig m_config{};
|
||||
IrsMomentProcessorConfig m_moment_config{};
|
||||
IrsClusteringProcessorConfig m_clustering_config{};
|
||||
IrsTeraPluginProcessorConfig m_tera_config{};
|
||||
IrsIrLedProcessorConfig m_led_config{};
|
||||
IrsAdaptiveClusteringProcessorConfig m_adaptive_config{};
|
||||
IrsHandAnalysisConfig m_hand_config{};
|
||||
|
||||
Entry m_entries[IRS_MAX_CAMERAS]{};
|
||||
u32 m_irs_width{};
|
||||
u32 m_irs_height{};
|
||||
std::vector<u32> m_rgba{};
|
||||
std::vector<u8> m_irs_buffer{};
|
||||
IrsImageTransferProcessorState m_prev_state{};
|
||||
Rotation m_rotation{Rotation_90};
|
||||
Colour m_colour{Colour_Grey};
|
||||
int m_image{};
|
||||
s64 m_index{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::irs
|
||||
@@ -36,6 +36,7 @@ private:
|
||||
Items m_items{};
|
||||
Callback m_callback{};
|
||||
s64 m_index{}; // index in list array
|
||||
s64 m_starting_index{};
|
||||
|
||||
std::unique_ptr<List> m_list{};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "ui/error_box.hpp"
|
||||
|
||||
#include "ui/menus/main_menu.hpp"
|
||||
#include "ui/menus/irs_menu.hpp"
|
||||
#include "ui/menus/themezer.hpp"
|
||||
#include "ui/menus/ghdl.hpp"
|
||||
#include "ui/menus/usb_menu.hpp"
|
||||
@@ -1540,6 +1541,10 @@ void App::DisplayMiscOptions(bool left_side) {
|
||||
App::Push(std::make_shared<ui::menu::gc::Menu>());
|
||||
}));
|
||||
}
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Irs (Infrared Joycon Camera)"_i18n, [](){
|
||||
App::Push(std::make_shared<ui::menu::irs::Menu>());
|
||||
}));
|
||||
}
|
||||
|
||||
void App::DisplayAdvancedOptions(bool left_side) {
|
||||
|
||||
@@ -195,6 +195,8 @@ private:
|
||||
} else {
|
||||
const auto update_entry = [this, &hash_key](const char* tag, const std::string& value) {
|
||||
if (value.empty()) {
|
||||
// workaround for appstore accepting etags but not returning them.
|
||||
yyjson_mut_obj_remove_str(hash_key, tag);
|
||||
return true;
|
||||
} else {
|
||||
auto key = yyjson_mut_obj_get(hash_key, tag);
|
||||
@@ -471,7 +473,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (e.GetFlags() & Flag_Cache) {
|
||||
// only add etag if the dst file still exists.
|
||||
if ((e.GetFlags() & Flag_Cache) && fs::FileExists(&fs.m_fs, e.GetPath())) {
|
||||
g_cache.get(e.GetPath(), header_in);
|
||||
}
|
||||
}
|
||||
@@ -490,6 +493,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_BUFFERSIZE, 1024*512);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_HEADERFUNCTION, header_callback);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_HEADERDATA, &header_out);
|
||||
// enable all forms of compression supported by libcurl.
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_ACCEPT_ENCODING, "");
|
||||
|
||||
if (has_post) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_POSTFIELDS, e.GetFields().c_str());
|
||||
@@ -558,6 +563,15 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
g_cache.set(e.GetPath(), header_out);
|
||||
}
|
||||
|
||||
// enable to log received headers.
|
||||
#if 0
|
||||
log_write("\n\nLOGGING HEADER\n");
|
||||
for (auto [a, b] : header_out.m_map) {
|
||||
log_write("\t%s: %s\n", a.c_str(), b.c_str());
|
||||
}
|
||||
log_write("\n\n");
|
||||
#endif
|
||||
|
||||
fs.DeleteFile(e.GetPath());
|
||||
fs.CreateDirectoryRecursivelyWithPath(e.GetPath());
|
||||
if (R_FAILED(fs.RenameFile(tmp_buf, e.GetPath()))) {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "yati/source/file.hpp"
|
||||
|
||||
#include <minIni.h>
|
||||
#include <minizip/zip.h>
|
||||
#include <minizip/unzip.h>
|
||||
#include <dirent.h>
|
||||
#include <cstring>
|
||||
@@ -57,6 +58,14 @@ constexpr std::string_view IMAGE_EXTENSIONS[] = {
|
||||
constexpr std::string_view INSTALL_EXTENSIONS[] = {
|
||||
"nsp", "xci", "nsz", "xcz",
|
||||
};
|
||||
// these are files that are already compressed or encrypted and should
|
||||
// be stored raw in a zip file.
|
||||
constexpr std::string_view COMPRESSED_EXTENSIONS[] = {
|
||||
"zip", "xz", "7z", "rar", "tar", "nca", "nsp", "xci", "nsz", "xcz"
|
||||
};
|
||||
constexpr std::string_view ZIP_EXTENSIONS[] = {
|
||||
"zip",
|
||||
};
|
||||
|
||||
|
||||
struct RomDatabaseEntry {
|
||||
@@ -267,6 +276,25 @@ auto GetRomIcon(fs::FsNative* fs, ProgressBox* pbox, std::string filename, const
|
||||
|
||||
Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i18n}, m_nro_entries{nro_entries} {
|
||||
this->SetActions(
|
||||
std::make_pair(Button::L2, Action{[this](){
|
||||
if (!m_selected_files.empty()) {
|
||||
ResetSelection();
|
||||
}
|
||||
|
||||
const auto set = m_selected_count != m_entries_current.size();
|
||||
|
||||
for (const auto& i : m_entries_current) {
|
||||
auto& e = GetEntry(i);
|
||||
if (e.selected != set) {
|
||||
e.selected = set;
|
||||
if (set) {
|
||||
m_selected_count++;
|
||||
} else {
|
||||
m_selected_count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::R2, Action{[this](){
|
||||
if (!m_selected_files.empty()) {
|
||||
ResetSelection();
|
||||
@@ -475,22 +503,24 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
}));
|
||||
}
|
||||
|
||||
// if install is enabled, check if all currently selected files are installable.
|
||||
if (m_entries_current.size() && App::GetInstallEnable()) {
|
||||
bool should_install = true;
|
||||
// returns true if all entries match the ext array.
|
||||
const auto check_all_ext = [this](auto& exts){
|
||||
if (!m_selected_count) {
|
||||
should_install = IsExtension(GetEntry().GetExtension(), INSTALL_EXTENSIONS);
|
||||
return IsExtension(GetEntry().GetExtension(), exts);
|
||||
} else {
|
||||
const auto entries = GetSelectedEntries();
|
||||
for (auto&e : entries) {
|
||||
if (!IsExtension(e.GetExtension(), INSTALL_EXTENSIONS)) {
|
||||
should_install = false;
|
||||
break;
|
||||
if (!IsExtension(e.GetExtension(), exts)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (should_install) {
|
||||
// if install is enabled, check if all currently selected files are installable.
|
||||
if (m_entries_current.size() && App::GetInstallEnable()) {
|
||||
if (check_all_ext(INSTALL_EXTENSIONS)) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Install"_i18n, [this](){
|
||||
if (!m_selected_count) {
|
||||
InstallFile(GetEntry());
|
||||
@@ -501,6 +531,79 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
}
|
||||
}
|
||||
|
||||
if (m_fs_type == FsType::Sd && m_entries_current.size()) {
|
||||
if (App::GetInstallEnable() && HasTypeInSelectedEntries(FsDirEntryType_File) && !m_selected_count && (GetEntry().GetExtension() == "nro" || !FindFileAssocFor().empty())) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Install Forwarder"_i18n, [this](){;
|
||||
if (App::GetInstallPrompt()) {
|
||||
App::Push(std::make_shared<OptionBox>(
|
||||
"WARNING: Installing forwarders will lead to a ban!"_i18n,
|
||||
"Back"_i18n, "Install"_i18n, 0, [this](auto op_index){
|
||||
if (op_index && *op_index) {
|
||||
InstallForwarder();
|
||||
}
|
||||
}
|
||||
));
|
||||
} else {
|
||||
InstallForwarder();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (m_entries_current.size()) {
|
||||
if (check_all_ext(ZIP_EXTENSIONS)) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Extract"_i18n, [this](){
|
||||
auto options = std::make_shared<Sidebar>("Extract Options"_i18n, Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Extract here"_i18n, [this](){
|
||||
if (!m_selected_count) {
|
||||
UnzipFile("", GetEntry());
|
||||
} else {
|
||||
UnzipFiles("", GetSelectedEntries());
|
||||
}
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Extract to..."_i18n, [this](){
|
||||
std::string out;
|
||||
if (R_SUCCEEDED(swkbd::ShowText(out, "Enter the path to the folder to extract into", fs::AppendPath(m_path, ""))) && !out.empty()) {
|
||||
if (!m_selected_count) {
|
||||
UnzipFile(out, GetEntry());
|
||||
} else {
|
||||
UnzipFiles(out, GetSelectedEntries());
|
||||
}
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
if (!check_all_ext(ZIP_EXTENSIONS) || m_selected_count) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Compress"_i18n, [this](){
|
||||
auto options = std::make_shared<Sidebar>("Compress Options"_i18n, Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Compress"_i18n, [this](){
|
||||
if (!m_selected_count) {
|
||||
ZipFile("", GetEntry());
|
||||
} else {
|
||||
ZipFiles("", GetSelectedEntries());
|
||||
}
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Compress to..."_i18n, [this](){
|
||||
std::string out;
|
||||
if (R_SUCCEEDED(swkbd::ShowText(out, "Enter the path to the folder to extract into", m_path)) && !out.empty()) {
|
||||
if (!m_selected_count) {
|
||||
ZipFile(out, GetEntry());
|
||||
} else {
|
||||
ZipFiles(out, GetSelectedEntries());
|
||||
}
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Advanced"_i18n, [this](){
|
||||
auto options = std::make_shared<Sidebar>("Advanced Options"_i18n, Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
@@ -529,7 +632,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Create Folder"_i18n, [this](){
|
||||
std::string out;
|
||||
if (R_SUCCEEDED(swkbd::ShowText(out, "Set Folder Name"_i18n.c_str())) && !out.empty()) {
|
||||
if (R_SUCCEEDED(swkbd::ShowText(out, "Set Folder Name"_i18n.c_str(), fs::AppendPath(m_path, ""))) && !out.empty()) {
|
||||
App::PopToMenu();
|
||||
|
||||
fs::FsPath full_path;
|
||||
@@ -554,25 +657,6 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
}));
|
||||
}
|
||||
|
||||
if (m_fs_type == FsType::Sd && m_entries_current.size()) {
|
||||
if (App::GetInstallEnable() && HasTypeInSelectedEntries(FsDirEntryType_File) && !m_selected_count && (GetEntry().GetExtension() == "nro" || !FindFileAssocFor().empty())) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Install Forwarder"_i18n, [this](){;
|
||||
if (App::GetInstallPrompt()) {
|
||||
App::Push(std::make_shared<OptionBox>(
|
||||
"WARNING: Installing forwarders will lead to a ban!"_i18n,
|
||||
"Back"_i18n, "Install"_i18n, 0, [this](auto op_index){
|
||||
if (op_index && *op_index) {
|
||||
InstallForwarder();
|
||||
}
|
||||
}
|
||||
));
|
||||
} else {
|
||||
InstallForwarder();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("Ignore read only"_i18n, m_ignore_read_only.Get(), [this](bool& v_out){
|
||||
m_ignore_read_only.Set(v_out);
|
||||
m_fs->SetIgnoreReadOnly(v_out);
|
||||
@@ -673,7 +757,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
} else if (IsExtension(ext, INSTALL_EXTENSIONS)) {
|
||||
// todo: maybe replace this icon with something else?
|
||||
icon = ThemeEntryID_ICON_NRO;
|
||||
} else if (IsExtension(ext, "zip")) {
|
||||
} else if (IsExtension(ext, ZIP_EXTENSIONS)) {
|
||||
icon = ThemeEntryID_ICON_ZIP;
|
||||
} else if (IsExtension(ext, "nro")) {
|
||||
icon = ThemeEntryID_ICON_NRO;
|
||||
@@ -848,6 +932,272 @@ void Menu::InstallFiles(const std::vector<FileEntry>& targets) {
|
||||
}));
|
||||
}
|
||||
|
||||
void Menu::UnzipFile(const fs::FsPath& dir_path, const FileEntry& target) {
|
||||
std::vector<FileEntry> targets{target};
|
||||
UnzipFiles(dir_path, targets);
|
||||
}
|
||||
|
||||
void Menu::UnzipFiles(fs::FsPath dir_path, const std::vector<FileEntry>& targets) {
|
||||
// set to current path.
|
||||
if (dir_path.empty()) {
|
||||
dir_path = m_path;
|
||||
}
|
||||
|
||||
App::Push(std::make_shared<ui::ProgressBox>(0, "Extracting "_i18n, "", [this, dir_path, targets](auto pbox) mutable -> bool {
|
||||
constexpr auto chunk_size = 1024 * 512; // 512KiB
|
||||
auto& fs = *m_fs.get();
|
||||
|
||||
for (auto& e : targets) {
|
||||
pbox->SetTitle(e.GetName());
|
||||
|
||||
const auto zip_out = GetNewPath(e);
|
||||
auto zfile = unzOpen64(zip_out);
|
||||
if (!zfile) {
|
||||
log_write("failed to open zip: %s\n", zip_out.s);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(unzClose(zfile));
|
||||
|
||||
unz_global_info64 pglobal_info;
|
||||
if (UNZ_OK != unzGetGlobalInfo64(zfile, &pglobal_info)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < pglobal_info.number_entry; i++) {
|
||||
if (i > 0) {
|
||||
if (UNZ_OK != unzGoToNextFile(zfile)) {
|
||||
log_write("failed to unzGoToNextFile\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (UNZ_OK != unzOpenCurrentFile(zfile)) {
|
||||
log_write("failed to open current file\n");
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(unzCloseCurrentFile(zfile));
|
||||
|
||||
unz_file_info64 info;
|
||||
char name[512];
|
||||
if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &info, name, sizeof(name), 0, 0, 0, 0)) {
|
||||
log_write("failed to get current info\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto file_path = fs::AppendPath(dir_path, name);
|
||||
pbox->NewTransfer(name);
|
||||
|
||||
// create directories
|
||||
fs.CreateDirectoryRecursivelyWithPath(file_path);
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
FsFile f;
|
||||
if (R_FAILED(rc = fs.OpenFile(file_path, FsOpenMode_Write, &f))) {
|
||||
log_write("failed to open file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(fsFileClose(&f));
|
||||
|
||||
if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) {
|
||||
log_write("failed to set file size: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<char> buf(chunk_size);
|
||||
s64 offset{};
|
||||
while (offset < info.uncompressed_size) {
|
||||
if (pbox->ShouldExit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size());
|
||||
if (bytes_read <= 0) {
|
||||
log_write("failed to read zip file: %s\n", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fsFileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None))) {
|
||||
log_write("failed to write file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
pbox->UpdateTransfer(offset, info.uncompressed_size);
|
||||
offset += bytes_read;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [this](bool success){
|
||||
if (success) {
|
||||
App::Notify("Extract success!");
|
||||
} else {
|
||||
App::Notify("Extract failed!");
|
||||
}
|
||||
Scan(m_path);
|
||||
log_write("did extract\n");
|
||||
}));
|
||||
}
|
||||
|
||||
void Menu::ZipFile(const fs::FsPath& zip_path, const FileEntry& target) {
|
||||
std::vector<FileEntry> targets{target};
|
||||
ZipFiles(zip_path, targets);
|
||||
}
|
||||
|
||||
void Menu::ZipFiles(fs::FsPath zip_out, const std::vector<FileEntry>& targets) {
|
||||
// set to current path.
|
||||
if (zip_out.empty()) {
|
||||
if (std::size(targets) == 1) {
|
||||
const auto name = targets[0].name;
|
||||
const auto ext = std::strrchr(targets[0].name, '.');
|
||||
fs::FsPath file_path;
|
||||
if (!ext) {
|
||||
std::snprintf(file_path, sizeof(file_path), "%s.zip", name);
|
||||
} else {
|
||||
std::snprintf(file_path, sizeof(file_path), "%.*s.zip", (int)(ext - name), name);
|
||||
}
|
||||
zip_out = fs::AppendPath(m_path, file_path);
|
||||
log_write("zip out: %s name: %s file_path: %s\n", zip_out.s, name, file_path.s);
|
||||
} else {
|
||||
// loop until we find an unused file name.
|
||||
for (u64 i = 0; ; i++) {
|
||||
fs::FsPath file_path = "Archive.zip";
|
||||
if (i) {
|
||||
std::snprintf(file_path, sizeof(file_path), "Archive (%zu).zip", i);
|
||||
}
|
||||
|
||||
zip_out = fs::AppendPath(m_path, file_path);
|
||||
if (!fs::FileExists(&m_fs->m_fs, zip_out)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!std::string_view(zip_out).ends_with(".zip")) {
|
||||
zip_out += ".zip";
|
||||
}
|
||||
}
|
||||
|
||||
App::Push(std::make_shared<ui::ProgressBox>(0, "Compressing "_i18n, "", [this, zip_out, targets](auto pbox) mutable -> bool {
|
||||
constexpr auto chunk_size = 1024 * 512; // 512KiB
|
||||
auto& fs = *m_fs.get();
|
||||
|
||||
const auto t = std::time(NULL);
|
||||
const auto tm = std::localtime(&t);
|
||||
|
||||
// pre-calculate the time rather than calculate it in the loop.
|
||||
zip_fileinfo zip_info{};
|
||||
zip_info.tmz_date.tm_sec = tm->tm_sec;
|
||||
zip_info.tmz_date.tm_min = tm->tm_min;
|
||||
zip_info.tmz_date.tm_hour = tm->tm_hour;
|
||||
zip_info.tmz_date.tm_mday = tm->tm_mday;
|
||||
zip_info.tmz_date.tm_mon = tm->tm_mon;
|
||||
zip_info.tmz_date.tm_year = tm->tm_year;
|
||||
|
||||
auto zfile = zipOpen(zip_out, APPEND_STATUS_CREATE);
|
||||
if (!zfile) {
|
||||
log_write("failed to open zip: %s\n", zip_out.s);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(zipClose(zfile, "sphaira v" APP_VERSION_HASH));
|
||||
|
||||
const auto zip_add = [&](const fs::FsPath& file_path){
|
||||
// the file name needs to be relative to the current directory.
|
||||
const char* file_name_in_zip = file_path.s + std::strlen(m_path);
|
||||
|
||||
// root paths are banned in zips, they will warn when extracting otherwise.
|
||||
if (file_name_in_zip[0] == '/') {
|
||||
file_name_in_zip++;
|
||||
}
|
||||
|
||||
pbox->NewTransfer(file_name_in_zip);
|
||||
|
||||
const auto ext = std::strrchr(file_name_in_zip, '.');
|
||||
const auto raw = ext && IsExtension(ext + 1, COMPRESSED_EXTENSIONS);
|
||||
|
||||
if (ZIP_OK != zipOpenNewFileInZip2(zfile, file_name_in_zip, &zip_info, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION, raw)) {
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(zipCloseFileInZip(zfile));
|
||||
|
||||
FsFile f;
|
||||
Result rc;
|
||||
if (R_FAILED(rc = fs.OpenFile(file_path, FsOpenMode_Read, &f))) {
|
||||
log_write("failed to open file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(fsFileClose(&f));
|
||||
|
||||
s64 file_size;
|
||||
if (R_FAILED(rc = fsFileGetSize(&f, &file_size))) {
|
||||
log_write("failed to get file size: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<char> buf(chunk_size);
|
||||
s64 offset{};
|
||||
while (offset < file_size) {
|
||||
if (pbox->ShouldExit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 bytes_read;
|
||||
if (R_FAILED(rc = fsFileRead(&f, offset, buf.data(), buf.size(), FsReadOption_None, &bytes_read))) {
|
||||
log_write("failed to write file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ZIP_OK != zipWriteInFileInZip(zfile, buf.data(), bytes_read)) {
|
||||
log_write("failed to write zip file: %s\n", file_path.s);
|
||||
return false;
|
||||
}
|
||||
|
||||
pbox->UpdateTransfer(offset, file_size);
|
||||
offset += bytes_read;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
for (auto& e : targets) {
|
||||
pbox->SetTitle(e.GetName());
|
||||
if (e.IsFile()) {
|
||||
const auto file_path = GetNewPath(e);
|
||||
if (!zip_add(file_path)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
FsDirCollections collections;
|
||||
get_collections(GetNewPath(e), e.name, collections);
|
||||
|
||||
for (const auto& collection : collections) {
|
||||
for (const auto& file : collection.files) {
|
||||
const auto file_path = fs::AppendPath(collection.path, file.name);
|
||||
if (!zip_add(file_path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [this](bool success){
|
||||
if (success) {
|
||||
App::Notify("Compress success!");
|
||||
} else {
|
||||
App::Notify("Compress failed!");
|
||||
}
|
||||
Scan(m_path);
|
||||
log_write("did compress\n");
|
||||
}));
|
||||
}
|
||||
|
||||
auto Menu::Scan(const fs::FsPath& new_path, bool is_walk_up) -> Result {
|
||||
log_write("new scan path: %s\n", new_path.s);
|
||||
if (!is_walk_up && !m_path.empty() && !m_entries_current.empty()) {
|
||||
|
||||
549
sphaira/source/ui/menus/irs_menu.cpp
Normal file
549
sphaira/source/ui/menus/irs_menu.cpp
Normal file
@@ -0,0 +1,549 @@
|
||||
#include "ui/menus/irs_menu.hpp"
|
||||
#include "ui/sidebar.hpp"
|
||||
#include "ui/popup_list.hpp"
|
||||
#include "app.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "log.hpp"
|
||||
#include "ui/nvg_util.hpp"
|
||||
#include "i18n.hpp"
|
||||
#include <cstring>
|
||||
#include <array>
|
||||
|
||||
namespace sphaira::ui::menu::irs {
|
||||
namespace {
|
||||
|
||||
// from trial and error
|
||||
constexpr u32 GAIN_MIN = 1;
|
||||
constexpr u32 GAIN_MAX = 16;
|
||||
|
||||
consteval auto generte_iron_palette_table() {
|
||||
std::array<u32, 256> array{};
|
||||
|
||||
const u32 iron_palette[] = {
|
||||
0xff000014, 0xff000025, 0xff00002a, 0xff000032, 0xff000036, 0xff00003e, 0xff000042, 0xff00004f,
|
||||
0xff010055, 0xff010057, 0xff02005c, 0xff03005e, 0xff040063, 0xff050065, 0xff070069, 0xff0a0070,
|
||||
0xff0b0073, 0xff0d0075, 0xff0d0076, 0xff100078, 0xff120079, 0xff15007c, 0xff17007d, 0xff1c0081,
|
||||
0xff200084, 0xff220085, 0xff260087, 0xff280089, 0xff2c008a, 0xff2e008b, 0xff32008d, 0xff38008f,
|
||||
0xff390090, 0xff3c0092, 0xff3e0093, 0xff410094, 0xff420095, 0xff450096, 0xff470096, 0xff4c0097,
|
||||
0xff4f0097, 0xff510097, 0xff540098, 0xff560098, 0xff5a0099, 0xff5c0099, 0xff5f009a, 0xff64009b,
|
||||
0xff66009b, 0xff6a009b, 0xff6c009c, 0xff6f009c, 0xff70009c, 0xff73009d, 0xff75009d, 0xff7a009d,
|
||||
0xff7e009d, 0xff7f009d, 0xff83009d, 0xff84009d, 0xff87009d, 0xff89009d, 0xff8b009d, 0xff91009c,
|
||||
0xff93009c, 0xff96009b, 0xff98009b, 0xff9b009b, 0xff9c009b, 0xff9f009b, 0xffa0009b, 0xffa4009b,
|
||||
0xffa7009a, 0xffa8009a, 0xffaa0099, 0xffab0099, 0xffae0198, 0xffaf0198, 0xffb00198, 0xffb30196,
|
||||
0xffb40296, 0xffb60295, 0xffb70395, 0xffb90495, 0xffba0495, 0xffbb0593, 0xffbc0593, 0xffbf0692,
|
||||
0xffc00791, 0xffc00791, 0xffc10990, 0xffc20a8f, 0xffc30b8e, 0xffc40c8d, 0xffc60d8b, 0xffc81088,
|
||||
0xffc91187, 0xffca1385, 0xffcb1385, 0xffcc1582, 0xffcd1681, 0xffce187e, 0xffcf187c, 0xffd11b78,
|
||||
0xffd21c75, 0xffd21d74, 0xffd32071, 0xffd4216f, 0xffd5236b, 0xffd52469, 0xffd72665, 0xffd92a60,
|
||||
0xffda2b5e, 0xffdb2e5a, 0xffdb2f57, 0xffdd3051, 0xffdd314e, 0xffde3347, 0xffdf3444, 0xffe0373a,
|
||||
0xffe03933, 0xffe13a30, 0xffe23c2a, 0xffe33d26, 0xffe43f20, 0xffe4411d, 0xffe5431b, 0xffe64616,
|
||||
0xffe74715, 0xffe74913, 0xffe84a12, 0xffe84c0f, 0xffe94d0e, 0xffea4e0c, 0xffea4f0c, 0xffeb520a,
|
||||
0xffec5409, 0xffec5608, 0xffec5808, 0xffed5907, 0xffed5b06, 0xffee5c06, 0xffee5d05, 0xffef6004,
|
||||
0xffef6104, 0xfff06303, 0xfff06403, 0xfff16603, 0xfff16603, 0xfff16803, 0xfff16902, 0xfff16b02,
|
||||
0xfff26d01, 0xfff26e01, 0xfff37001, 0xfff37101, 0xfff47300, 0xfff47400, 0xfff47600, 0xfff47a00,
|
||||
0xfff57b00, 0xfff57e00, 0xfff57f00, 0xfff68100, 0xfff68200, 0xfff78400, 0xfff78500, 0xfff88800,
|
||||
0xfff88900, 0xfff88a00, 0xfff88c00, 0xfff98d00, 0xfff98e00, 0xfff98f00, 0xfff99100, 0xfffa9400,
|
||||
0xfffa9500, 0xfffb9800, 0xfffb9900, 0xfffb9c00, 0xfffc9d00, 0xfffca000, 0xfffca100, 0xfffda400,
|
||||
0xfffda700, 0xfffda800, 0xfffdab00, 0xfffdac00, 0xfffdae00, 0xfffeaf00, 0xfffeb100, 0xfffeb400,
|
||||
0xfffeb500, 0xfffeb800, 0xfffeb900, 0xfffeba00, 0xfffebb00, 0xfffebd00, 0xfffebe00, 0xfffec200,
|
||||
0xfffec400, 0xfffec500, 0xfffec700, 0xfffec800, 0xfffeca01, 0xfffeca01, 0xfffecc02, 0xfffecf04,
|
||||
0xfffecf04, 0xfffed106, 0xfffed308, 0xfffed50a, 0xfffed60a, 0xfffed80c, 0xfffed90d, 0xffffdb10,
|
||||
0xffffdc14, 0xffffdd16, 0xffffde1b, 0xffffdf1e, 0xffffe122, 0xffffe224, 0xffffe328, 0xffffe531,
|
||||
0xffffe635, 0xffffe73c, 0xffffe83f, 0xffffea46, 0xffffeb49, 0xffffec50, 0xffffed54, 0xffffee5f,
|
||||
0xffffef67, 0xfffff06a, 0xfffff172, 0xfffff177, 0xfffff280, 0xfffff285, 0xfffff38e, 0xfffff49a,
|
||||
0xfffff59e, 0xfffff5a6, 0xfffff6aa, 0xfffff7b3, 0xfffff7b6, 0xfffff8bd, 0xfffff8c1, 0xfffff9ca,
|
||||
0xfffffad1, 0xfffffad4, 0xfffffcdb, 0xfffffcdf, 0xfffffde5, 0xfffffde8, 0xfffffeee, 0xfffffff6
|
||||
};
|
||||
|
||||
for (u32 i = 0; i < 256; i++) {
|
||||
const auto c = iron_palette[i];
|
||||
array[i] = RGBA8_MAXALPHA((c >> 16) & 0xFF, (c >> 8) & 0xFF, (c >> 0) & 0xFF);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
// ARGB Ironbow palette
|
||||
constexpr auto iron_palette = generte_iron_palette_table();
|
||||
|
||||
void irsConvertConfigExToNormal(const IrsImageTransferProcessorExConfig* ex, IrsImageTransferProcessorConfig* nor) {
|
||||
std::memcpy(nor, ex, sizeof(*nor));
|
||||
}
|
||||
|
||||
void irsConvertConfigNormalToEx(const IrsImageTransferProcessorConfig* nor, IrsImageTransferProcessorExConfig* ex) {
|
||||
std::memcpy(ex, nor, sizeof(*nor));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Menu::Menu() : MenuBase{"Irs"_i18n} {
|
||||
SetAction(Button::B, Action{"Back"_i18n, [this](){
|
||||
SetPop();
|
||||
}});
|
||||
|
||||
SetAction(Button::X, Action{"Options"_i18n, [this](){
|
||||
auto options = std::make_shared<Sidebar>("Options"_i18n, Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
SidebarEntryArray::Items controller_str;
|
||||
for (u32 i = 0; i < IRS_MAX_CAMERAS; i++) {
|
||||
controller_str.emplace_back(GetEntryName(i));
|
||||
}
|
||||
|
||||
SidebarEntryArray::Items rotation_str;
|
||||
rotation_str.emplace_back("0 (Sideways)"_i18n);
|
||||
rotation_str.emplace_back("90 (Flat)"_i18n);
|
||||
rotation_str.emplace_back("180 (-Sideways)"_i18n);
|
||||
rotation_str.emplace_back("270 (Upside down)"_i18n);
|
||||
|
||||
SidebarEntryArray::Items colour_str;
|
||||
colour_str.emplace_back("Grey"_i18n);
|
||||
colour_str.emplace_back("Ironbow"_i18n);
|
||||
colour_str.emplace_back("Green"_i18n);
|
||||
colour_str.emplace_back("Red"_i18n);
|
||||
colour_str.emplace_back("Blue"_i18n);
|
||||
|
||||
SidebarEntryArray::Items light_target_str;
|
||||
light_target_str.emplace_back("All leds"_i18n);
|
||||
light_target_str.emplace_back("Bright group"_i18n);
|
||||
light_target_str.emplace_back("Dim group"_i18n);
|
||||
light_target_str.emplace_back("None"_i18n);
|
||||
|
||||
SidebarEntryArray::Items gain_str;
|
||||
for (u32 i = GAIN_MIN; i <= GAIN_MAX; i++) {
|
||||
gain_str.emplace_back(std::to_string(i));
|
||||
}
|
||||
|
||||
SidebarEntryArray::Items is_negative_image_used_str;
|
||||
is_negative_image_used_str.emplace_back("Normal image"_i18n);
|
||||
is_negative_image_used_str.emplace_back("Negative image"_i18n);
|
||||
|
||||
SidebarEntryArray::Items format_str;
|
||||
format_str.emplace_back("320x240"_i18n);
|
||||
format_str.emplace_back("160x120"_i18n);
|
||||
format_str.emplace_back("80x60"_i18n);
|
||||
if (hosversionAtLeast(4,0,0)) {
|
||||
format_str.emplace_back("40x30"_i18n);
|
||||
format_str.emplace_back("20x15"_i18n);
|
||||
}
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Controller"_i18n, controller_str, [this](s64& index){
|
||||
irsStopImageProcessor(m_entries[m_index].m_handle);
|
||||
m_index = index;
|
||||
UpdateConfig(&m_config);
|
||||
}, m_index));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Rotation"_i18n, rotation_str, [this](s64& index){
|
||||
m_rotation = (Rotation)index;
|
||||
}, m_rotation));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Colour"_i18n, colour_str, [this](s64& index){
|
||||
m_colour = (Colour)index;
|
||||
updateColourArray();
|
||||
}, m_colour));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Light Target"_i18n, light_target_str, [this](s64& index){
|
||||
m_config.light_target = index;
|
||||
UpdateConfig(&m_config);
|
||||
}, m_config.light_target));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Gain"_i18n, gain_str, [this](s64& index){
|
||||
m_config.gain = GAIN_MIN + index;
|
||||
UpdateConfig(&m_config);
|
||||
}, m_config.gain - GAIN_MIN));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Negative Image"_i18n, is_negative_image_used_str, [this](s64& index){
|
||||
m_config.is_negative_image_used = index;
|
||||
UpdateConfig(&m_config);
|
||||
}, m_config.is_negative_image_used));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Format"_i18n, format_str, [this](s64& index){
|
||||
m_config.orig_format = index;
|
||||
m_config.trimming_format = index;
|
||||
UpdateConfig(&m_config);
|
||||
}, m_config.orig_format));
|
||||
|
||||
if (hosversionAtLeast(4,0,0)) {
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Trimming Format"_i18n, format_str, [this](s64& index){
|
||||
// you cannot set trim a larger region than the source
|
||||
if (index < m_config.orig_format) {
|
||||
index = m_config.orig_format;
|
||||
} else {
|
||||
m_config.trimming_format = index;
|
||||
UpdateConfig(&m_config);
|
||||
}
|
||||
}, m_config.orig_format));
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryBool>("External Light Filter"_i18n, m_config.is_external_light_filter_enabled, [this](bool& enable){
|
||||
m_config.is_external_light_filter_enabled = enable;
|
||||
UpdateConfig(&m_config);
|
||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
||||
}
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Load Default"_i18n, [this](){
|
||||
LoadDefaultConfig();
|
||||
}, true));
|
||||
}});
|
||||
|
||||
if (R_FAILED(m_init_rc = irsInitialize())) {
|
||||
return;
|
||||
}
|
||||
|
||||
static_assert(IRS_MAX_CAMERAS >= 9, "max camaeras has gotten smaller!");
|
||||
|
||||
// open all handles
|
||||
irsGetIrCameraHandle(&m_entries[0].m_handle, HidNpadIdType_No1);
|
||||
irsGetIrCameraHandle(&m_entries[1].m_handle, HidNpadIdType_No2);
|
||||
irsGetIrCameraHandle(&m_entries[2].m_handle, HidNpadIdType_No3);
|
||||
irsGetIrCameraHandle(&m_entries[3].m_handle, HidNpadIdType_No4);
|
||||
irsGetIrCameraHandle(&m_entries[4].m_handle, HidNpadIdType_No5);
|
||||
irsGetIrCameraHandle(&m_entries[5].m_handle, HidNpadIdType_No6);
|
||||
irsGetIrCameraHandle(&m_entries[6].m_handle, HidNpadIdType_No7);
|
||||
irsGetIrCameraHandle(&m_entries[7].m_handle, HidNpadIdType_No8);
|
||||
irsGetIrCameraHandle(&m_entries[8].m_handle, HidNpadIdType_Handheld);
|
||||
// get status of all handles
|
||||
PollCameraStatus(true);
|
||||
// load default config
|
||||
LoadDefaultConfig();
|
||||
// poll to get first available handle.
|
||||
PollCameraStatus(false);
|
||||
|
||||
// find the first available entry and connect to that.
|
||||
for (s64 i = 0; i < std::size(m_entries); i++) {
|
||||
if (m_entries[i].status == IrsIrCameraStatus_Available) {
|
||||
m_index = i;
|
||||
UpdateConfig(&m_config);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu::~Menu() {
|
||||
ResetImage();
|
||||
|
||||
for (auto& e : m_entries) {
|
||||
irsStopImageProcessor(e.m_handle);
|
||||
}
|
||||
|
||||
// this closes all handles
|
||||
irsExit();
|
||||
}
|
||||
|
||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||
MenuBase::Update(controller, touch);
|
||||
PollCameraStatus();
|
||||
SetTitleSubHeading(GetEntryName(m_index));
|
||||
}
|
||||
|
||||
void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
MenuBase::Draw(vg, theme);
|
||||
|
||||
IrsImageTransferProcessorState state;
|
||||
const auto rc = irsGetImageTransferProcessorState(m_entries[m_index].m_handle, m_irs_buffer.data(), m_irs_buffer.size(), &state);
|
||||
if (R_SUCCEEDED(rc) && state.sampling_number != m_prev_state.sampling_number) {
|
||||
m_prev_state = state;
|
||||
SetSubHeading("Ambient Noise Level: "_i18n + std::to_string(m_prev_state.ambient_noise_level));
|
||||
updateColourArray();
|
||||
}
|
||||
|
||||
if (m_image) {
|
||||
float cx{}, cy{};
|
||||
float w{}, h{};
|
||||
float angle{};
|
||||
|
||||
switch (m_rotation) {
|
||||
case Rotation_0: {
|
||||
const auto scale_x = m_pos.w / float(m_irs_width);
|
||||
const auto scale_y = m_pos.h / float(m_irs_height);
|
||||
const auto scale = std::min(scale_x, scale_y);
|
||||
w = m_irs_width * scale;
|
||||
h = m_irs_height * scale;
|
||||
cx = (m_pos.x + m_pos.w / 2.F) - w / 2.F;
|
||||
cy = (m_pos.y + m_pos.h / 2.F) - h / 2.F;
|
||||
angle = 0;
|
||||
} break;
|
||||
case Rotation_90: {
|
||||
const auto scale_x = m_pos.w / float(m_irs_height);
|
||||
const auto scale_y = m_pos.h / float(m_irs_width);
|
||||
const auto scale = std::min(scale_x, scale_y);
|
||||
w = m_irs_width * scale;
|
||||
h = m_irs_height * scale;
|
||||
cx = (m_pos.x + m_pos.w / 2.F) + h / 2.F;
|
||||
cy = (m_pos.y + m_pos.h / 2.F) - w / 2.F;
|
||||
angle = 90;
|
||||
} break;
|
||||
case Rotation_180: {
|
||||
const auto scale_x = m_pos.w / float(m_irs_width);
|
||||
const auto scale_y = m_pos.h / float(m_irs_height);
|
||||
const auto scale = std::min(scale_x, scale_y);
|
||||
w = m_irs_width * scale;
|
||||
h = m_irs_height * scale;
|
||||
cx = (m_pos.x + m_pos.w / 2.F) + w / 2.F;
|
||||
cy = (m_pos.y + m_pos.h / 2.F) + h / 2.F;
|
||||
angle = 180;
|
||||
} break;
|
||||
case Rotation_270: {
|
||||
const auto scale_x = m_pos.w / float(m_irs_height);
|
||||
const auto scale_y = m_pos.h / float(m_irs_width);
|
||||
const auto scale = std::min(scale_x, scale_y);
|
||||
w = m_irs_width * scale;
|
||||
h = m_irs_height * scale;
|
||||
cx = (m_pos.x + m_pos.w / 2.F) - h / 2.F;
|
||||
cy = (m_pos.y + m_pos.h / 2.F) + w / 2.F;
|
||||
angle = 270;
|
||||
} break;
|
||||
}
|
||||
|
||||
nvgSave(vg);
|
||||
nvgTranslate(vg, cx, cy);
|
||||
const auto paint = nvgImagePattern(vg, 0, 0, w, h, 0, m_image, 1.f);
|
||||
nvgRotate(vg, nvgDegToRad(angle));
|
||||
nvgBeginPath(vg);
|
||||
nvgRect(vg, 0, 0, w, h);
|
||||
nvgFillPaint(vg, paint);
|
||||
nvgFill(vg);
|
||||
nvgRestore(vg);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::OnFocusGained() {
|
||||
MenuBase::OnFocusGained();
|
||||
}
|
||||
|
||||
void Menu::PollCameraStatus(bool statup) {
|
||||
int index = 0;
|
||||
for (auto& e : m_entries) {
|
||||
IrsIrCameraStatus status;
|
||||
if (R_FAILED(irsGetIrCameraStatus(e.m_handle, &status))) {
|
||||
log_write("failed to get ir status\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.status != status || statup) {
|
||||
e.status = status;
|
||||
e.m_update_needed = false;
|
||||
|
||||
log_write("status changed\n");
|
||||
switch (e.status) {
|
||||
case IrsIrCameraStatus_Available:
|
||||
if (hosversionAtLeast(4,0,0)) {
|
||||
// calling this breaks the handle, kinda
|
||||
#if 0
|
||||
if (R_FAILED(irsCheckFirmwareUpdateNecessity(e.m_handle, &e.m_update_needed))) {
|
||||
log_write("failed to check if update needed: %u\n", e.m_update_needed);
|
||||
} else {
|
||||
if (e.m_update_needed) {
|
||||
log_write("update needed\n");
|
||||
} else {
|
||||
log_write("no update needed\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
log_write("irs index: %d status: IrsIrCameraStatus_Available\n", index);
|
||||
break;
|
||||
case IrsIrCameraStatus_Unsupported:
|
||||
log_write("irs index: %d status: IrsIrCameraStatus_Unsupported\n", index);
|
||||
break;
|
||||
case IrsIrCameraStatus_Unconnected:
|
||||
log_write("irs index: %d status: IrsIrCameraStatus_Unconnected\n", index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::ResetImage() {
|
||||
if (m_image) {
|
||||
nvgDeleteImage(App::GetVg(), m_image);
|
||||
m_image = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::UpdateImage() {
|
||||
ResetImage();
|
||||
m_image = nvgCreateImageRGBA(App::GetVg(), m_irs_width, m_irs_height, NVG_IMAGE_NEAREST, (const unsigned char*)m_rgba.data());
|
||||
}
|
||||
|
||||
void Menu::LoadDefaultConfig() {
|
||||
IrsImageTransferProcessorExConfig ex_config;
|
||||
|
||||
if (hosversionAtLeast(4,0,0)) {
|
||||
irsGetDefaultImageTransferProcessorExConfig(&ex_config);
|
||||
} else {
|
||||
IrsImageTransferProcessorConfig nor_config;
|
||||
irsGetDefaultImageTransferProcessorConfig(&nor_config);
|
||||
irsConvertConfigNormalToEx(&nor_config, &ex_config);
|
||||
}
|
||||
|
||||
irsGetMomentProcessorDefaultConfig(&m_moment_config);
|
||||
irsGetClusteringProcessorDefaultConfig(&m_clustering_config);
|
||||
irsGetIrLedProcessorDefaultConfig(&m_led_config);
|
||||
|
||||
m_tera_config = {};
|
||||
m_adaptive_config = {};
|
||||
m_hand_config = {};
|
||||
|
||||
UpdateConfig(&ex_config);
|
||||
}
|
||||
|
||||
void Menu::UpdateConfig(const IrsImageTransferProcessorExConfig* config) {
|
||||
m_config = *config;
|
||||
irsStopImageProcessor(m_entries[m_index].m_handle);
|
||||
|
||||
if (R_FAILED(irsRunMomentProcessor(m_entries[m_index].m_handle, &m_moment_config))) {
|
||||
log_write("failed to irsRunMomentProcessor\n");
|
||||
} else {
|
||||
log_write("did irsRunMomentProcessor\n");
|
||||
}
|
||||
|
||||
if (R_FAILED(irsRunClusteringProcessor(m_entries[m_index].m_handle, &m_clustering_config))) {
|
||||
log_write("failed to irsRunClusteringProcessor\n");
|
||||
} else {
|
||||
log_write("did irsRunClusteringProcessor\n");
|
||||
}
|
||||
|
||||
if (R_FAILED(irsRunPointingProcessor(m_entries[m_index].m_handle))) {
|
||||
log_write("failed to irsRunPointingProcessor\n");
|
||||
} else {
|
||||
log_write("did irsRunPointingProcessor\n");
|
||||
}
|
||||
|
||||
if (R_FAILED(irsRunTeraPluginProcessor(m_entries[m_index].m_handle, &m_tera_config))) {
|
||||
log_write("failed to irsRunTeraPluginProcessor\n");
|
||||
} else {
|
||||
log_write("did irsRunTeraPluginProcessor\n");
|
||||
}
|
||||
|
||||
if (R_FAILED(irsRunIrLedProcessor(m_entries[m_index].m_handle, &m_led_config))) {
|
||||
log_write("failed to irsRunIrLedProcessor\n");
|
||||
} else {
|
||||
log_write("did irsRunIrLedProcessor\n");
|
||||
}
|
||||
|
||||
if (R_FAILED(irsRunAdaptiveClusteringProcessor(m_entries[m_index].m_handle, &m_adaptive_config))) {
|
||||
log_write("failed to irsRunAdaptiveClusteringProcessor\n");
|
||||
} else {
|
||||
log_write("did irsRunAdaptiveClusteringProcessor\n");
|
||||
}
|
||||
|
||||
if (R_FAILED(irsRunHandAnalysis(m_entries[m_index].m_handle, &m_hand_config))) {
|
||||
log_write("failed to irsRunHandAnalysis\n");
|
||||
} else {
|
||||
log_write("did irsRunHandAnalysis\n");
|
||||
}
|
||||
|
||||
if (hosversionAtLeast(4,0,0)) {
|
||||
m_init_rc = irsRunImageTransferExProcessor(m_entries[m_index].m_handle, &m_config, 0x10000000);
|
||||
} else {
|
||||
IrsImageTransferProcessorConfig nor;
|
||||
irsConvertConfigExToNormal(&m_config, &nor);
|
||||
m_init_rc = irsRunImageTransferProcessor(m_entries[m_index].m_handle, &nor, 0x10000000);
|
||||
}
|
||||
|
||||
if (R_FAILED(m_init_rc)) {
|
||||
log_write("irs failed to set config!\n");
|
||||
}
|
||||
|
||||
auto format = m_config.orig_format;
|
||||
log_write("IRS CONFIG\n");
|
||||
log_write("\texposure_time: %lu\n", m_config.exposure_time);
|
||||
log_write("\tlight_target: %u\n", m_config.light_target);
|
||||
log_write("\tgain: %u\n", m_config.gain);
|
||||
log_write("\tis_negative_image_used: %u\n", m_config.is_negative_image_used);
|
||||
log_write("\tlight_target: %u\n", m_config.light_target);
|
||||
if (hosversionAtLeast(4,0,0)) {
|
||||
format = m_config.trimming_format;
|
||||
log_write("\ttrimming_format: %u\n", m_config.trimming_format);
|
||||
log_write("\ttrimming_start_x: %u\n", m_config.trimming_start_x);
|
||||
log_write("\ttrimming_start_y: %u\n", m_config.trimming_start_y);
|
||||
log_write("\tis_external_light_filter_enabled: %u\n", m_config.is_external_light_filter_enabled);
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case IrsImageTransferProcessorFormat_320x240:
|
||||
log_write("\tsetting format: %s\n", "IrsImageTransferProcessorFormat_320x240");
|
||||
m_irs_width = 320;
|
||||
m_irs_height = 240;
|
||||
break;
|
||||
case IrsImageTransferProcessorFormat_160x120:
|
||||
log_write("\tsetting format: %s\n", "IrsImageTransferProcessorFormat_160x120");
|
||||
m_irs_width = 160;
|
||||
m_irs_height = 120;
|
||||
break;
|
||||
case IrsImageTransferProcessorFormat_80x60:
|
||||
log_write("\tsetting format: %s\n", "IrsImageTransferProcessorFormat_80x60");
|
||||
m_irs_width = 80;
|
||||
m_irs_height = 60;
|
||||
break;
|
||||
case IrsImageTransferProcessorFormat_40x30:
|
||||
log_write("\tsetting format: %s\n", "IrsImageTransferProcessorFormat_40x30");
|
||||
m_irs_width = 40;
|
||||
m_irs_height = 30;
|
||||
break;
|
||||
case IrsImageTransferProcessorFormat_20x15:
|
||||
log_write("\tsetting format: %s\n", "IrsImageTransferProcessorFormat_20x15");
|
||||
m_irs_width = 20;
|
||||
m_irs_height = 15;
|
||||
break;
|
||||
}
|
||||
|
||||
m_rgba.resize(m_irs_width * m_irs_height);
|
||||
m_irs_buffer.resize(m_irs_width * m_irs_height);
|
||||
m_prev_state.sampling_number = UINT64_MAX;
|
||||
std::fill(m_irs_buffer.begin(), m_irs_buffer.end(), 0);
|
||||
updateColourArray();
|
||||
}
|
||||
|
||||
void Menu::updateColourArray() {
|
||||
const auto ir_width = m_irs_width;
|
||||
const auto ir_height = m_irs_height;
|
||||
const auto colour = m_colour;
|
||||
|
||||
for (u32 y = 0; y < ir_height; y++) {
|
||||
for (u32 x = 0; x < ir_width; x++) {
|
||||
const u32 pos = y * ir_width + x;
|
||||
const u32 pos2 = y * ir_width + x;
|
||||
|
||||
switch (colour) {
|
||||
case Colour_Grey:
|
||||
m_rgba[pos] = RGBA8_MAXALPHA(m_irs_buffer[pos2], m_irs_buffer[pos2], m_irs_buffer[pos2]);
|
||||
break;
|
||||
case Colour_Ironbow:
|
||||
m_rgba[pos] = iron_palette[m_irs_buffer[pos2]];
|
||||
break;
|
||||
case Colour_Green:
|
||||
m_rgba[pos] = RGBA8_MAXALPHA(0, m_irs_buffer[pos2], 0);
|
||||
break;
|
||||
case Colour_Red:
|
||||
m_rgba[pos] = RGBA8_MAXALPHA(m_irs_buffer[pos2], 0, 0);
|
||||
break;
|
||||
case Colour_Blue:
|
||||
m_rgba[pos] = RGBA8_MAXALPHA(0, 0, m_irs_buffer[pos2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateImage();
|
||||
}
|
||||
|
||||
auto Menu::GetEntryName(s64 i) -> std::string {
|
||||
const auto& e = m_entries[i];
|
||||
std::string text = "Pad "_i18n + (i == 8 ? "HandHeld"_i18n : std::to_string(i));
|
||||
switch (e.status) {
|
||||
case IrsIrCameraStatus_Available:
|
||||
text += " (Available)"_i18n;
|
||||
break;
|
||||
case IrsIrCameraStatus_Unsupported:
|
||||
text += " (Unsupported)"_i18n;
|
||||
break;
|
||||
case IrsIrCameraStatus_Unconnected:
|
||||
text += " (Unconnected)"_i18n;
|
||||
break;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui::menu::irs
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "download.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "i18n.hpp"
|
||||
#include "ui/menus/usb_menu.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <minizip/unzip.h>
|
||||
|
||||
@@ -306,6 +306,7 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
|
||||
}
|
||||
|
||||
const auto file_path = fs::AppendPath(dir_path, name);
|
||||
pbox->NewTransfer(name);
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
|
||||
|
||||
@@ -73,6 +73,8 @@ PopupList::PopupList(std::string title, Items items, Callback cb, s64 index)
|
||||
}})
|
||||
);
|
||||
|
||||
m_starting_index = m_index;
|
||||
|
||||
m_pos.w = 1280.f;
|
||||
const float a = std::min(370.f, (60.f * static_cast<float>(m_items.size())));
|
||||
m_pos.h = 80.f + 140.f + a;
|
||||
@@ -110,15 +112,21 @@ auto PopupList::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
|
||||
m_list->Draw(vg, theme, m_items.size(), [this](auto* vg, auto* theme, auto v, auto i) {
|
||||
const auto& [x, y, w, h] = v;
|
||||
auto colour = ThemeEntryID_TEXT;
|
||||
if (m_index == i) {
|
||||
gfx::drawRectOutline(vg, theme, 4.f, v);
|
||||
gfx::drawText(vg, x + m_text_xoffset, y + (h / 2.f), 20.f, m_items[i].c_str(), NULL, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_SELECTED));
|
||||
} else {
|
||||
if (i != m_items.size() - 1) {
|
||||
gfx::drawRect(vg, x, y + h, w, 1.f, theme->GetColour(ThemeEntryID_LINE_SEPARATOR));
|
||||
}
|
||||
gfx::drawText(vg, x + m_text_xoffset, y + (h / 2.f), 20.f, m_items[i].c_str(), NULL, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT));
|
||||
}
|
||||
|
||||
if (m_starting_index == i) {
|
||||
colour = ThemeEntryID_TEXT_SELECTED;
|
||||
gfx::drawText(vg, x + w - m_text_xoffset, y + (h / 2.f), 20.f, "\uE14B", NULL, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE, theme->GetColour(colour));
|
||||
}
|
||||
|
||||
gfx::drawText(vg, x + m_text_xoffset, y + (h / 2.f), 20.f, m_items[i].c_str(), NULL, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(colour));
|
||||
});
|
||||
|
||||
Widget::Draw(vg, theme);
|
||||
|
||||
Reference in New Issue
Block a user