Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore

# macOS Finder metadata
.DS_Store

# User-specific files
*.suo
*.user
Expand Down
4 changes: 2 additions & 2 deletions SwitchThemesNX/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ ROMFS := romfs

APP_TITLE := NXThemes Installer
APP_AUTHOR := exelix
APP_VERSION := 3.0.1
APP_VERSION := 3.0.2

GITVER ?= Unknown
#---------------------------------------------------------------------------------
Expand Down Expand Up @@ -246,4 +246,4 @@ $(OFILES_SRC) : $(HFILES_BIN)

#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------
3 changes: 2 additions & 1 deletion SwitchThemesNX/SwitchThemesNX.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
<ClInclude Include="source\Pages\RemoteInstall\Detail.hpp" />
<ClInclude Include="source\Pages\RemoteInstall\List.hpp" />
<ClInclude Include="source\Pages\RemoteInstall\RemoteInstall.hpp" />
<ClInclude Include="source\Pages\RemoteInstall\RemoteTarget.hpp" />
<ClInclude Include="source\Pages\RemoteInstall\Worker.hpp" />
<ClInclude Include="source\Pages\SettingsPage.hpp" />
<ClInclude Include="source\Pages\TextPage.hpp" />
Expand Down Expand Up @@ -551,4 +552,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>
5 changes: 4 additions & 1 deletion SwitchThemesNX/SwitchThemesNX.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@
<ClInclude Include="source\Pages\RemoteInstall\RemoteInstall.hpp">
<Filter>Pages\RemoteInstall</Filter>
</ClInclude>
<ClInclude Include="source\Pages\RemoteInstall\RemoteTarget.hpp">
<Filter>Pages\RemoteInstall</Filter>
</ClInclude>
<ClInclude Include="source\Platform\PlatformFs.hpp">
<Filter>Platform</Filter>
</ClInclude>
Expand Down Expand Up @@ -933,4 +936,4 @@
<Filter>Libs\mbedtls</Filter>
</Text>
</ItemGroup>
</Project>
</Project>
6 changes: 5 additions & 1 deletion SwitchThemesNX/source/Pages/RemoteInstall/API.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,12 @@ namespace RemoteInstall::API
The name should be a short name describing the theme, layout info and author name are already part of the NXTheme file and not needed there.
When saving on the sd card the installer will normalize and, if needed, shorten the name obtained from the NXTheme manifest.

Valid falues for the `target` field are the internal nxtheme target strings, currently these are the following:
Valid values for the `target` field are the internal nxtheme target strings, currently these are the following:
"home", "lock", "user", "apps", "set", "news" and "psl".
The special target "__image" is used for raw image files rather than
nxtheme archives. The installer validates the downloaded bytes, saves them
with their real image extension, and opens the standard image installation
dialog so the user can choose a theme wallpaper or boot-screen destination.

The entry must have at least one preview image between `preview` and `thumbnail`, having both is ideal but not needed.
`preview` is downloaded for full screen previewing, `thumbnail` for lists.
Expand Down
48 changes: 38 additions & 10 deletions SwitchThemesNX/source/Pages/RemoteInstall/Detail.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
#include "Detail.hpp"
#include "Worker.hpp"
#include "RemoteTarget.hpp"
#include "../../fs.hpp"
#include "../../ViewFunctions.hpp"
#include "../../SwitchThemesCommon/Common.hpp"
#include "../ThemeEntry/ThemeEntry.hpp"
#include "../ThemeEntry/ImageEntry.hpp"
#include "../ImagePreview.hpp"
#include "../ThemePage.hpp"
#include "../../SwitchThemesCommon/Bntx/ImageConversion.hpp"

#include <utility>

RemoteInstall::DetailPage::DetailPage(const RemoteInstall::API::Entry& entry, ImageRef i) : entry(entry), img(i)
{
auto info = ThemeTargetInfo::Find(entry.Target);
PartName = info ? info->PartName : "Unknown part name";
PartName = RemoteInstall::TargetLabel(entry.Target);
}

void RemoteInstall::DetailPage::Update() {}
Expand Down Expand Up @@ -55,12 +58,29 @@ void RemoteInstall::DetailPage::Render(int X, int Y)

void RemoteInstall::DetailPage::UserDownload(Action action)
{
PushFunction([this, action]() {
const bool isImage = RemoteInstall::IsImage(entry.Target);
PushFunction([this, action, isImage]() {
auto theme = DownloadData();
if (theme.size() == 0) return;

auto entry = ThemeEntry::FromMemory(theme);
if (!entry->CanInstall())

std::string imageError;
const auto imageExtension = isImage ?
ImageConversion::GetSupportedImageExtension(theme, imageError) : std::string_view{};
if (isImage && imageExtension.empty())
{
DialogBlocking("The downloaded image is not valid: " + imageError);
return;
}

std::unique_ptr<ThemeEntry> themeEntry;
if (isImage)
{
themeEntry = std::make_unique<ImageEntry>(this->entry.Name + std::string(imageExtension), std::move(theme));
}
else
themeEntry = ThemeEntry::FromMemory(theme);

if (!themeEntry->CanInstall())
{
DialogBlocking("This theme is not valid");
return;
Expand All @@ -69,22 +89,30 @@ void RemoteInstall::DetailPage::UserDownload(Action action)
if ((int)action & (int)Action::Download)
{
fs::EnsureDownloadsFolderExists();
std::string name = fs::path::DownloadsFolder + fs::SanitizeName(this->entry.Name) + ".nxtheme";
const auto extension = isImage ? imageExtension : std::string_view(".nxtheme");
std::string name = fs::path::DownloadsFolder + fs::SanitizeName(this->entry.Name) + std::string(extension);
if (fs::Exists(name) && !YesNoPage::Ask("A file called " + name + " already exists on the sd card, do you want to replace it ?"))
{
if (action == Action::Download) // If the user asked to download the theme don't close the page, otherwise just install it
return;
}
else
{
fs::WriteFile(name, theme);
fs::WriteFile(name, isImage ? DownloadedTheme : theme);
fs::theme::RequestThemeListRefresh();
ThemesPage::Instance->SelectElementOnRescan(name);
}
}

if ((int)action & (int)Action::Install)
entry->Install(true);
{
if (!themeEntry->Install(true) && isImage)
{
if (!themeEntry->CanInstall() && !themeEntry->CannotInstallReason.empty())
DialogBlocking(themeEntry->CannotInstallReason);
return;
}
}

PopPage(this);
});
Expand Down
49 changes: 29 additions & 20 deletions SwitchThemesNX/source/Pages/RemoteInstall/List.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
#include <sstream>
#include <filesystem>
#include <utility>
#include "List.hpp"
#include "Worker.hpp"
#include "RemoteTarget.hpp"
#include "../ImagePreview.hpp"
#include "../ThemePage.hpp"
#include "../../fs.hpp"
#include "../../ViewFunctions.hpp"
#include "../../UI/UI.hpp"
#include "../../SwitchThemesCommon/Common.hpp"
#include "../../SwitchThemesCommon/Bntx/ImageConversion.hpp"

const ImVec2 ImageSize = { 398, 224 };

Expand Down Expand Up @@ -114,14 +116,6 @@ bool RemoteInstall::ListPage::IsSelected(size_t i)
return Selection[i];
}

std::vector<std::string> RemoteInstall::ListPage::GetSelectedUrls()
{
std::vector<std::string> Urls;
for (size_t i = 0; i < response.Entries.size(); i++)
if (IsSelected(i)) Urls.push_back(response.Entries[i].Url);
return Urls;
}

void RemoteInstall::ListPage::SelectionChanged()
{
std::stringstream ss;
Expand Down Expand Up @@ -173,14 +167,29 @@ void RemoteInstall::ListPage::DownloadClicked()

folderName += '/';

auto urls = GetSelectedUrls();
std::vector<std::string> urls;
std::vector<bool> imageEntries;
for (size_t i = 0; i < response.Entries.size(); i++)
{
if (!IsSelected(i))
continue;

urls.push_back(response.Entries[i].Url);
imageEntries.push_back(RemoteInstall::IsImage(response.Entries[i].Target));
}

size_t numFailed;
std::string OutFirstFilaName = "";

auto worker = new Worker::ActionOnItemFinish(urls, numFailed, [&folderName, &OutFirstFilaName](std::vector<u8>&& _invec, uintptr_t index) -> bool {
std::vector<u8> vec = _invec;
std::string name = folderName + std::to_string(index) + ".nxtheme";
auto worker = new Worker::ActionOnItemFinish(urls, numFailed, [&folderName, &OutFirstFilaName, imageEntries = std::move(imageEntries)](std::vector<u8>&& vec, uintptr_t index) -> bool {
std::string imageError;
const auto imageExtension = imageEntries[index] ?
ImageConversion::GetSupportedImageExtension(vec, imageError) : std::string_view{};
if (imageEntries[index] && imageExtension.empty())
return false;

const auto extension = imageEntries[index] ? imageExtension : std::string_view(".nxtheme");
std::string name = folderName + std::to_string(index) + std::string(extension);

try {
fs::WriteFile(name, vec);
Expand Down Expand Up @@ -230,8 +239,7 @@ RemoteInstall::ListPage::Result RemoteInstall::ListPage::RenderWidget(size_t ind
const bool selected = IsSelected(index);
const std::string& Name = response.Entries[index].Name;

auto targetInfo = ThemeTargetInfo::Find(response.Entries[index].Target);
const char* Target = targetInfo ? targetInfo->PartName.c_str() : "Unknown part name";
const auto Target = RemoteInstall::TargetLabel(response.Entries[index].Target);

const auto& img = images.List[index];

Expand All @@ -244,12 +252,13 @@ RemoteInstall::ListPage::Result RemoteInstall::ListPage::RenderWidget(size_t ind
const ImGuiID id = window->GetID(ScrollIDs[index].c_str());

const ImVec2 name_size = ImGui::CalcTextSize(Name.c_str(), NULL, false, ImageSize.x - 6);
const ImVec2 target_size = ImGui::CalcTextSize(Target, NULL, false, ImageSize.x - 6);
const ImVec2 target_size = ImGui::CalcTextSize(Target.data(), NULL, false, ImageSize.x - 6);

ImVec2 pos = window->DC.CursorPos;
ImVec2 sz = { ImageSize.x, ImageSize.y + 6 + name_size.y };

if (Target) sz += {0, target_size.y + 6};
if (!Target.empty())
sz += {0, target_size.y + 6};

const ImRect imageBox(pos, pos + ImageSize);

Expand Down Expand Up @@ -284,11 +293,11 @@ RemoteInstall::ListPage::Result RemoteInstall::ListPage::RenderWidget(size_t ind

ImGui::PushFont(font25);
ImGui::RenderTextWrapped({ pos.x + 3, pos.y + ImageSize.y + 3 }, Name.c_str(), 0, ImageSize.x - 6);
if (Target)
ImGui::RenderTextWrapped({ pos.x + 3, pos.y + ImageSize.y + name_size.y + 6 }, Target, 0, ImageSize.x - 6);
if (!Target.empty())
ImGui::RenderTextWrapped({ pos.x + 3, pos.y + ImageSize.y + name_size.y + 6 }, Target.data(), 0, ImageSize.x - 6);
ImGui::PopFont();

IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);

return result;
}
}
3 changes: 1 addition & 2 deletions SwitchThemesNX/source/Pages/RemoteInstall/List.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ namespace RemoteInstall
void ApplySelection(bool all);
void ToggleSelected(size_t i);
bool IsSelected(size_t i);
std::vector<std::string> GetSelectedUrls();

std::string DownloadBtnText = "Download";
void SelectionChanged();
Expand All @@ -40,4 +39,4 @@ namespace RemoteInstall
void PopulateScrollIDs();
std::vector<std::string> ScrollIDs;
};
}
}
25 changes: 25 additions & 0 deletions SwitchThemesNX/source/Pages/RemoteInstall/RemoteTarget.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <string>
#include <string_view>

#include "../../SwitchThemesCommon/Common.hpp"

namespace RemoteInstall
{
inline constexpr std::string_view ImageTarget = "__image";

inline bool IsImage(std::string_view target) noexcept
{
return target == ImageTarget;
}

inline std::string TargetLabel(std::string_view target)
{
if (IsImage(target))
return "Plain Image";

auto info = ThemeTargetInfo::Find(std::string(target));
return info ? info->PartName : "Unknown part name";
}
}
7 changes: 5 additions & 2 deletions SwitchThemesNX/source/Pages/RemoteInstall/Worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ void RemoteInstall::Worker::BaseWorker::Update()
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &index);
curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &httpCode);

if (msg->data.result != CURLE_OK || !OnFinished(index, httpCode))
const bool httpSuccess = httpCode >= 200 && httpCode < 300;
if (msg->data.result != CURLE_OK || !httpSuccess || !OnFinished(index, httpCode))
{
if (appendUrlToError) {
if (index < urls.size())
Expand All @@ -66,6 +67,8 @@ void RemoteInstall::Worker::BaseWorker::Update()

if (msg->data.result != CURLE_OK)
Errors << " failed: " << curl_easy_strerror(msg->data.result) << "(" << msg->data.result << ")" << std::endl;
else if (!httpSuccess)
Errors << " failed with HTTP status " << httpCode << std::endl;
else
Errors << " failed due to handler error" << std::endl;

Expand Down Expand Up @@ -170,4 +173,4 @@ void RemoteInstall::Worker::DownloadSingle::OnComplete()
DialogBlocking(str);
}
else OutBuffer = std::move(Results[0]);
}
}
4 changes: 2 additions & 2 deletions SwitchThemesNX/source/Pages/ThemeEntry/BaseEntry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ unique_ptr<ThemeEntry> ThemeEntry::FromFile(const std::string& fileName)
return make_unique<LegacyEntry>(fileName, move(data));
if (StrEndsWith(fileName, ".nxtheme") || StrEndsWith(fileName, ".zip"))
return make_unique<NxEntry>(fileName, move(data));
if (StrEndsWith(fileName, ".jpg") || StrEndsWith(fileName, ".jpeg") || StrEndsWith(fileName, ".png"))
if (StrEndsWith(fileName, ".jpg") || StrEndsWith(fileName, ".jpeg") || StrEndsWith(fileName, ".png") || StrEndsWith(fileName, ".bmp"))
return make_unique<ImageEntry>(fileName, move(data));
}
catch (std::exception &ex)
Expand Down Expand Up @@ -241,4 +241,4 @@ ThemeEntry::UserAction ThemeEntry::Render(bool OverrideColor)
return pressed && Utils::ItemNotDragging() ? UserAction::Enter : UserAction::None;
}

const std::vector<u8> ThemeEntry::_emtptyVec = {};
const std::vector<u8> ThemeEntry::_emtptyVec = {};
Loading
Loading