diff --git a/Source/hoc-clk/overlay/src/ui/gui/info_gui.cpp b/Source/hoc-clk/overlay/src/ui/gui/info_gui.cpp index 6a2b2383..3a339d5b 100644 --- a/Source/hoc-clk/overlay/src/ui/gui/info_gui.cpp +++ b/Source/hoc-clk/overlay/src/ui/gui/info_gui.cpp @@ -84,22 +84,3 @@ void InfoGui::listUI() { this->listElement->addItem(gap, PARA_GAP); } } - -void addWrappedTextItems(tsl::elm::List *list, const std::vector ¶graphs) { - const s32 maxWidth = tsl::cfg::FramebufferWidth - MARGIN_L - MARGIN_R; - - for (const auto ¶ : paragraphs) { - for (const auto &lineText : wrapText(para, maxWidth)) { - auto *d = new FocusableDrawer([lineText](tsl::gfx::Renderer *r, s32 x, s32 y, s32 w, s32 h) { - r->drawString((lineText + "\n").c_str(), false, x + MARGIN_L, y + LINE_H - 5, TEXT_SIZE, tsl::style::color::ColorText); - }); - d->setBoundaries(0, 0, tsl::cfg::FramebufferWidth, LINE_H); - list->addItem(d, LINE_H); - } - - // paragraph gap - auto *gap = new tsl::elm::CustomDrawer([](tsl::gfx::Renderer *, s32, s32, s32, s32) {}); - gap->setBoundaries(0, 0, tsl::cfg::FramebufferWidth, PARA_GAP); - list->addItem(gap, PARA_GAP); - } -} \ No newline at end of file diff --git a/Source/hoc-clk/overlay/src/ui/gui/info_gui.h b/Source/hoc-clk/overlay/src/ui/gui/info_gui.h index 4850eae6..0721060d 100644 --- a/Source/hoc-clk/overlay/src/ui/gui/info_gui.h +++ b/Source/hoc-clk/overlay/src/ui/gui/info_gui.h @@ -21,8 +21,6 @@ #include "base_menu_gui.h" -void addWrappedTextItems(tsl::elm::List *list, const std::vector ¶graphs); - class InfoGui : public BaseMenuGui { public: InfoGui(std::string title, std::vector strings); diff --git a/Source/hoc-clk/overlay/src/ui/gui/main_gui.cpp b/Source/hoc-clk/overlay/src/ui/gui/main_gui.cpp index b1b14ca2..5ab0a053 100644 --- a/Source/hoc-clk/overlay/src/ui/gui/main_gui.cpp +++ b/Source/hoc-clk/overlay/src/ui/gui/main_gui.cpp @@ -32,7 +32,6 @@ #include "main_gui.h" #include "misc_gui.h" #include "ult_ext.h" -#include "update_gui.h" tsl::elm::Element *MainGui::baseUI() { auto *list = new BoxClippedList(); @@ -114,17 +113,6 @@ void MainGui::listUI() { }); this->listElement->addItem(aboutItem); - tsl::elm::ListItem *updateItem = new tsl::elm::ListItem("Updates"); - updateItem->setClickListener([this](u64 keys) { - if ((keys & HidNpadButton_A) == HidNpadButton_A && this->context) { - tsl::swapTo(); - return true; - } - - return false; - }); - this->listElement->addItem(updateItem); - if (!lastItemName.empty()) { this->listElement->jumpToItem(lastItemName); } diff --git a/Source/hoc-clk/overlay/src/ui/gui/update_confirm_gui.cpp b/Source/hoc-clk/overlay/src/ui/gui/update_confirm_gui.cpp deleted file mode 100644 index 8341bd99..00000000 --- a/Source/hoc-clk/overlay/src/ui/gui/update_confirm_gui.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "update_confirm_gui.h" -#include "info_gui.h" -#include "update_gui.h" - -#include - -UpdateConfirmGui::UpdateConfirmGui(UpdateGui *parent, int packageIndex, std::string packageName, - std::string tag, std::vector changelog) - : m_parent(parent), m_packageIndex(packageIndex), m_packageName(std::move(packageName)), - m_tag(std::move(tag)), m_changelog(std::move(changelog)) { -} - -void UpdateConfirmGui::listUI() { - std::string header = m_packageName; - if (!m_tag.empty()) - header += " " + m_tag; - this->listElement->addItem(new tsl::elm::CategoryHeader(header)); - - addWrappedTextItems(this->listElement, m_changelog); - - this->listElement->addItem(new tsl::elm::CategoryHeader("Confirm")); - - auto *installItem = new tsl::elm::ListItem("Install Update"); - installItem->setValue(""); - installItem->setClickListener([this](u64 keys) -> bool { - if ((keys & HidNpadButton_A) == HidNpadButton_A) { - m_parent->startJob(m_packageIndex, false); - tsl::goBack(); - return true; - } - return false; - }); - this->listElement->addItem(installItem); -} - -bool UpdateConfirmGui::handleInput(u64, u64, const HidTouchState &, - HidAnalogStickState, HidAnalogStickState) { - return false; -} diff --git a/Source/hoc-clk/overlay/src/ui/gui/update_confirm_gui.h b/Source/hoc-clk/overlay/src/ui/gui/update_confirm_gui.h deleted file mode 100644 index 3439f0fc..00000000 --- a/Source/hoc-clk/overlay/src/ui/gui/update_confirm_gui.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -#include -#include - -#include "base_menu_gui.h" - -class UpdateGui; - -class UpdateConfirmGui : public BaseMenuGui { -public: - UpdateConfirmGui(UpdateGui *parent, int packageIndex, std::string packageName, - std::string tag, std::vector changelog); - ~UpdateConfirmGui() = default; - - void listUI() override; - bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, - HidAnalogStickState leftJoy, HidAnalogStickState rightJoy) override; - -private: - UpdateGui *m_parent; - int m_packageIndex; - std::string m_packageName; - std::string m_tag; - std::vector m_changelog; -}; diff --git a/Source/hoc-clk/overlay/src/ui/gui/update_gui.cpp b/Source/hoc-clk/overlay/src/ui/gui/update_gui.cpp deleted file mode 100644 index c56c8d5c..00000000 --- a/Source/hoc-clk/overlay/src/ui/gui/update_gui.cpp +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "update_gui.h" -#include "ult_ext.h" -#include "base_menu_gui.h" -#include "update_confirm_gui.h" -#include "../format.h" -#include "../style.h" - -#include -#include -#include -#include -#include - - -static constexpr u16 PANEL_H = 80; - -class UpdateStatusPanel : public tsl::elm::Element { -public: - void setState(int stage, int progress, - const std::string &pkg, const std::string &msg) { - m_stage = stage; - m_progress = progress; - m_pkg = pkg; - m_msg = msg; - } - - void draw(tsl::gfx::Renderer *renderer) override { - if (this->getHeight() == 0) return; - - const s32 PAD = 20; - const s32 x = this->getX() + PAD; - const s32 y = this->getY(); - const s32 w = this->getWidth() - PAD * 2; - - if (m_stage == 0) { - return; - } - - if (m_stage == 1 || m_stage == 2) { - const char *opLabel = (m_stage == 1) ? "Downloading" : "Extracting"; - tsl::Color accent = (m_stage == 1) - ? tsl::Color{0x4, 0x8, 0xF, 0xF} // blue - : tsl::Color{0x3, 0xA, 0x8, 0xF}; // teal - - renderer->drawString(opLabel, false, x, y + 20, 15, accent); - renderer->drawString(m_pkg.c_str(), false, x + 104, y + 20, 15, - tsl::sectionTextColor); - - const s32 barY = y + 48; - renderer->drawRect(x, barY, w, 7, tsl::Color{0x3, 0x3, 0x3, 0xF}); - - int pct = (m_progress < 0) ? 0 : (m_progress > 100 ? 100 : m_progress); - s32 fillW = (s32)((long)w * pct / 100); - if (fillW > 0) - renderer->drawRect(x, barY, fillW, 7, accent); - - char buf[8]; - if (m_progress < 0) - std::snprintf(buf, sizeof(buf), "..."); - else - std::snprintf(buf, sizeof(buf), "%d%%", pct); - - u32 tw = renderer->getTextDimensions(buf, false, 13).first; - renderer->drawString(buf, false, - x + w - (s32)tw, barY - 14, 13, tsl::infoTextColor); - - } else { - tsl::Color col; - switch (m_stage) { - case 3: col = {0x3, 0xB, 0x5, 0xF}; break; // Done → green - case 4: col = {0xE, 0x3, 0x3, 0xF}; break; // Failed → red - default: col = {0x7, 0x7, 0x7, 0xF}; break; // Cancel → grey - } - renderer->drawString(m_msg.c_str(), false, x, y + 30, 14, col); - } - } - - void layout(u16, u16, u16, u16) override { - this->setBoundaries(this->getX(), this->getY(), - this->getWidth(), PANEL_H); - } - - Element *requestFocus(Element *, tsl::FocusDirection) override { - return nullptr; - } - -private: - int m_stage = 0; - int m_progress = 0; - std::string m_pkg; - std::string m_msg; -}; - -UpdateGui::UpdateGui() = default; -UpdateGui::~UpdateGui() { reapThread(); } - -void UpdateGui::startJob(int packageIndex, bool extractOnly) { - if (isBusy()) return; - - m_activePackage = packageIndex; - m_extractOnly = extractOnly; - m_resultMessage.clear(); - - ult::abortDownload.store(false, std::memory_order_release); - ult::abortUnzip.store(false, std::memory_order_release); - - m_stage.store(UpdateStage::Downloading, std::memory_order_release); - - if (R_SUCCEEDED(threadCreate(&m_thread, jobEntry, this, - nullptr, 0x8000, 0x20, -2))) { - m_threadActive = true; - threadStart(&m_thread); - } else { - m_resultMessage = "Failed to create thread"; - m_stage.store(UpdateStage::Failed, std::memory_order_release); - m_threadActive = false; - } -} - -void UpdateGui::startChangelogFetch(int packageIndex) { - if (isBusy()) return; - - m_activePackage = packageIndex; - m_resultMessage.clear(); - m_changelogTag.clear(); - m_changelogLines.clear(); - - ult::abortDownload.store(false, std::memory_order_release); - - m_stage.store(UpdateStage::FetchingChangelog, std::memory_order_release); - - if (R_SUCCEEDED(threadCreate(&m_thread, fetchChangelogEntry, this, - nullptr, 0x8000, 0x20, -2))) { - m_threadActive = true; - threadStart(&m_thread); - } else { - m_resultMessage = "Failed to create thread"; - m_stage.store(UpdateStage::Failed, std::memory_order_release); - m_threadActive = false; - } -} - -void UpdateGui::requestCancel() { - ult::abortDownload.store(true, std::memory_order_release); - ult::abortUnzip.store(true, std::memory_order_release); -} - -void UpdateGui::reapThread() { - if (m_threadActive) { - threadWaitForExit(&m_thread); - threadClose(&m_thread); - m_threadActive = false; - } -} - -/* static */ void UpdateGui::jobEntry(void *arg) { - static_cast(arg)->jobBody(); -} - -void UpdateGui::jobBody() { - const PackageInfo &pkg = kPackages[m_activePackage]; - - if (!m_extractOnly) { - m_stage.store(UpdateStage::Downloading, std::memory_order_release); - ult::createDirectory("sdmc:/config/horizon-oc/"); - - bool ok = ult::downloadFile(pkg.url, pkg.zipPath, false, false); - if (!ok || ult::abortDownload.load(std::memory_order_acquire)) { - m_resultMessage = ult::abortDownload.load(std::memory_order_acquire) - ? "Download cancelled" - : "Download failed -- check network"; - m_stage.store( - ult::abortDownload.load() ? UpdateStage::Cancelled : UpdateStage::Failed, - std::memory_order_release); - return; - } - } - - m_stage.store(UpdateStage::Extracting, std::memory_order_release); - - bool ok = ult::unzipFile(pkg.zipPath, "sdmc:/"); - if (!ok || ult::abortUnzip.load(std::memory_order_acquire)) { - m_resultMessage = ult::abortUnzip.load(std::memory_order_acquire) - ? "Extraction cancelled" - : "Extraction failed -- archive may be corrupt"; - m_stage.store( - ult::abortUnzip.load() ? UpdateStage::Cancelled : UpdateStage::Failed, - std::memory_order_release); - return; - } - - std::remove(pkg.zipPath); - - m_resultMessage = "Update complete! Restart required."; - m_stage.store(UpdateStage::Done, std::memory_order_release); -} - -/* static */ void UpdateGui::fetchChangelogEntry(void *arg) { - static_cast(arg)->fetchChangelogBody(); -} - -void UpdateGui::fetchChangelogBody() { - ult::createDirectory("sdmc:/config/horizon-oc/"); - - bool ok = ult::downloadFile(kReleaseApiUrl, kReleaseInfoPath, false, false); - if (!ok || ult::abortDownload.load(std::memory_order_acquire)) { - m_resultMessage = ult::abortDownload.load(std::memory_order_acquire) - ? "Changelog fetch cancelled" - : "Could not fetch changelog -- check network"; - m_stage.store( - ult::abortDownload.load() ? UpdateStage::Cancelled : UpdateStage::Failed, - std::memory_order_release); - return; - } - - std::unique_ptr root(ult::readJsonFromFile(kReleaseInfoPath)); - std::remove(kReleaseInfoPath); - - if (!root) { - m_resultMessage = "Could not parse changelog"; - m_stage.store(UpdateStage::Failed, std::memory_order_release); - return; - } - - m_changelogTag = ult::getStringFromJson(root.get(), "tag_name"); - const std::string body = ult::getStringFromJson(root.get(), "body"); - - m_changelogLines.clear(); - size_t pos = 0; - while (pos <= body.size()) { - size_t nl = body.find('\n', pos); - std::string line = (nl == std::string::npos) ? body.substr(pos) : body.substr(pos, nl - pos); - if (!line.empty() && line.back() == '\r') - line.pop_back(); - if (!line.empty()) - m_changelogLines.push_back(line); - if (nl == std::string::npos) - break; - pos = nl + 1; - } - if (m_changelogLines.empty()) - m_changelogLines.push_back("No changelog available for this release."); - - m_stage.store(UpdateStage::ChangelogReady, std::memory_order_release); -} - - -void UpdateGui::listUI() { - BaseMenuGui::refresh(); - if (!this->context) return; - - this->listElement->addItem(new tsl::elm::CategoryHeader("Releases")); - - for (int i = 0; i < 2; ++i) { - auto *item = new tsl::elm::ListItem(kPackages[i].displayName); - item->setValue("\uE0E0"); - - const int idx = i; - item->setClickListener([this, idx](u64 keys) -> bool { - if (isBusy()) return false; - if (keys & HidNpadButton_A) { - if (ult::limitedMemory) { - m_status->setState(4, 100, kPackages[idx].displayName, - "Updates cannot be performed on a 4MB overlay heap"); - return true; - } - startChangelogFetch(idx); - return true; - } - return false; - }); - - m_items[i] = item; - this->listElement->addItem(item); - } - - this->listElement->addItem(new tsl::elm::CategoryHeader("Status")); - - auto *panel = new UpdateStatusPanel(); - panel->setState(0, 0, "", ""); - m_status = panel; - this->listElement->addItem(panel); -} - -void UpdateGui::pollJob() { - auto stage = m_stage.load(std::memory_order_acquire); - if (stage == UpdateStage::Idle || !m_status) return; - - const char *pkgName = (m_activePackage >= 0 && m_activePackage < 2) - ? kPackages[m_activePackage].displayName : ""; - - if (stage == UpdateStage::Downloading) { - int pct = ult::downloadPercentage.load(std::memory_order_relaxed); - m_status->setState(1, pct, pkgName, ""); - if (m_items[m_activePackage]) - m_items[m_activePackage]->setValue("..."); - return; - } - - if (stage == UpdateStage::Extracting) { - int pct = ult::unzipPercentage.load(std::memory_order_relaxed); - m_status->setState(2, pct, pkgName, ""); - if (m_items[m_activePackage]) - m_items[m_activePackage]->setValue("..."); - return; - } - - if (stage == UpdateStage::FetchingChangelog) { - m_status->setState(6, 0, pkgName, "Fetching changelog..."); - if (m_items[m_activePackage]) - m_items[m_activePackage]->setValue("..."); - return; - } - - if (stage == UpdateStage::ChangelogReady) { - reapThread(); - m_stage.store(UpdateStage::Idle, std::memory_order_release); - m_status->setState(0, 0, "", ""); - if (m_items[m_activePackage]) - m_items[m_activePackage]->setValue(""); - - m_pendingChangelogConfirm = true; - return; - } - - int panelStage = (stage == UpdateStage::Done) ? 3 - : (stage == UpdateStage::Failed) ? 4 : 5; - - m_status->setState(panelStage, 100, pkgName, m_resultMessage); - - for (int i = 0; i < 2; ++i) - if (m_items[i]) m_items[i]->setValue("\uE0E0"); - - reapThread(); - m_stage.store(UpdateStage::Idle, std::memory_order_release); -} - -void UpdateGui::update() { - BaseMenuGui::update(); - pollJob(); -} - -bool UpdateGui::handleInput(u64 keysDown, u64 keysHeld, - const HidTouchState &touchPos, - HidAnalogStickState leftJoy, - HidAnalogStickState rightJoy) { - if (m_pendingChangelogConfirm) { - m_pendingChangelogConfirm = false; - tsl::changeTo(this, m_activePackage, kPackages[m_activePackage].displayName, - m_changelogTag, m_changelogLines); - return true; - } - - if ((keysDown & HidNpadButton_R) && isBusy()) { - requestCancel(); - return true; - } - - return BaseMenuGui::handleInput(keysDown, keysHeld, touchPos, leftJoy, rightJoy); -} - -std::string UpdateGui::getJumpToItemName() { - return "Updates"; -} diff --git a/Source/hoc-clk/overlay/src/ui/gui/update_gui.h b/Source/hoc-clk/overlay/src/ui/gui/update_gui.h deleted file mode 100644 index 17b5dd04..00000000 --- a/Source/hoc-clk/overlay/src/ui/gui/update_gui.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -#include "../../ipc.h" - -#include -#include -#include - -#include "base_menu_gui.h" - -class UpdateStatusPanel; - -class UpdateGui : public BaseMenuGui { - -public: - UpdateGui(); - ~UpdateGui(); - - void listUI() override; - void update() override; - bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, - HidAnalogStickState leftJoy, HidAnalogStickState rightJoy) override; - - void startJob(int packageIndex, bool extractOnly); - -private: - enum class UpdateStage { - Idle, - Downloading, - Extracting, - Done, - Failed, - Cancelled, - FetchingChangelog, - ChangelogReady, - }; - - struct PackageInfo { - const char *displayName; - const char *url; - const char *zipPath; - }; - - static constexpr PackageInfo kPackages[2] = { - {"Horizon OC", "https://github.com/Horizon-OC/Horizon-OC/releases/latest/download/dist.zip", "sdmc:/config/horizon-oc/dist.zip"}, - {"Horizon OC + Extensions", "https://github.com/Horizon-OC/Horizon-OC/releases/latest/download/dist_ext.zip", "sdmc:/config/horizon-oc/dist_ext.zip"}, - }; - - static constexpr const char *kReleaseApiUrl = "https://api.github.com/repos/Horizon-OC/Horizon-OC/releases/latest"; - static constexpr const char *kReleaseInfoPath = "sdmc:/config/horizon-oc/release_info.json"; - - bool isBusy() const { - return m_stage.load(std::memory_order_acquire) != UpdateStage::Idle; - } - - void startChangelogFetch(int packageIndex); - void requestCancel(); - void reapThread(); - void pollJob(); - - static void jobEntry(void *arg); - void jobBody(); - - static void fetchChangelogEntry(void *arg); - void fetchChangelogBody(); - - std::atomic m_stage{UpdateStage::Idle}; - int m_activePackage = -1; - bool m_extractOnly = false; - bool m_threadActive = false; - Thread m_thread{}; - std::string m_resultMessage; - - std::string m_changelogTag; - std::vector m_changelogLines; - - bool m_pendingChangelogConfirm = false; - - tsl::elm::ListItem *m_items[2] = {nullptr, nullptr}; - UpdateStatusPanel *m_status = nullptr; - -protected: - std::string getJumpToItemName() override; -};