// Copyright (c) 2025, Cifro Codes LLC // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "wallet.h" #include #include #include #include #include #include "decorate/overlay.h" #include "events.h" #include "lwcli_config.h" #include "translate.h" #include "util.h" #include "views/history.h" namespace lwcli { namespace view { namespace { bool set_proxy(Monero::Wallet& wal, const std::string& proxy) { wal.setProxy(proxy); return true; } bool set_url(Monero::Wallet& wal, const std::string& url) { const bool is_ssl = bool(from_string(wal.getCacheAttribute(std::string{config::server::ssl}))); wal.init(url, 0, "", "", is_ssl, true, wal.getCacheAttribute(std::string{config::server::proxy})); return true; } bool set_refresh(Monero::Wallet& wal, const std::string& interval) { const auto number = from_string(interval); if (!number) return false; wal.setAutoRefreshInterval(std::chrono::milliseconds{std::chrono::seconds{*number}}.count()); return true; } bool set_ssl(Monero::Wallet& wal, const std::string& ssl) { const auto is_ssl = from_string(ssl); if (!is_ssl) return false; // no way to forcibly select ssl without another init call. hopefully doesn't break things wal.init(wal.getCacheAttribute(std::string{config::server::url}), 0, "", "", bool(*is_ssl), true, wal.getCacheAttribute(std::string{config::server::proxy})); return true; } bool set_major_lookahead(Monero::Wallet& wal, const std::string& major) { const auto major_integer = from_string(major); if (!major_integer) return false; auto minor = from_string(wal.getCacheAttribute(std::string{config::minor_lookahead})); if (!minor) minor = config::default_minor_lookahead; wal.setSubaddressLookahead(*major_integer, *minor); return true; } bool set_minor_lookahead(Monero::Wallet& wal, const std::string& minor) { const auto minor_integer = from_string(minor); if (!minor_integer) return false; auto major = from_string(wal.getCacheAttribute(std::string{config::major_lookahead})); if (!major) major = config::default_major_lookahead; wal.setSubaddressLookahead(*major, *minor_integer); return true; } struct option { using updater = bool(Monero::Wallet&, const std::string&); const std::string_view path; const std::string_view description; updater* const update; }; const std::array options{{ {config::server::url, _("API Server"), set_url}, {config::server::refresh_interval, _("Refresh Interval (seconds)"), set_refresh}, {config::server::ssl, _("TLS/SSL Cert Check"), set_ssl}, {config::server::proxy, _("Proxy"), set_proxy}, {config::major_lookahead, _("Subaddress Major Lookahead"), set_major_lookahead}, {config::minor_lookahead, _("Subaddress Minor Lookahead"), set_minor_lookahead} }}; ftxui::Component last_input(std::string* str) { auto opt = ftxui::InputOption::Default(); opt.cursor_position = str->size(); opt.multiline = false; return ftxui::Input(str, std::move(opt)); } ftxui::ButtonOption ascii() { return ftxui::ButtonOption::Ascii(); } struct option_state { ftxui::Element description; std::string original; std::string value; ftxui::Component ui; }; struct configuration { std::array states; configuration(const Monero::Wallet& wal) : states() { for (std::size_t i = 0; i < options.size(); ++i) { states[i].description = ftxui::text(std::string{options[i].description} + ": "); states[i].original = wal.getCacheAttribute(std::string{options[i].path}); states[i].value = states[i].original; states[i].ui = last_input(&states[i].value); } } void store(Monero::Wallet& wal, std::string& error) { for (std::size_t i = 0; i < options.size(); ++i) { if (states[i].original != states[i].value) { if (!options[i].update || options[i].update(wal, states[i].value)) wal.setCacheAttribute(std::string{options[i].path}, states[i].value); else { error = std::string{options[i].description} + " is invalid"; break; } } } } }; class settings_ final : public ftxui::ComponentBase { const std::shared_ptr wal_; const ftxui::Element title_; const std::unique_ptr config_; std::string error_; ftxui::Component buttons_; ftxui::Component ui_; bool Focusable() const override final { return true; } ftxui::Component ActiveChild() override final { return ui_; } public: explicit settings_(std::shared_ptr&& wal) : ftxui::ComponentBase(), wal_(std::move(wal)), title_(ftxui::text(_("Settings"))), config_(std::make_unique(*wal_)), error_(), buttons_(), ui_() { buttons_ = ftxui::Container::Horizontal({ ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ascii()), ftxui::Button(_("Save"), [this] () { error_.clear(); config_->store(*wal_, error_); if (error_.empty()) throw event::close{}; }, ascii()) }); ftxui::Components ui; ui.reserve(config_->states.size() + 1); ui.push_back(buttons_); for (const auto& opt : config_->states) ui.push_back(opt.ui); ui_ = ftxui::Container::Vertical(std::move(ui)); Add(ui_); } bool OnEvent(ftxui::Event event) override final { if (event == ftxui::Event::CtrlQ) throw event::close{}; ui_->OnEvent(std::move(event)); return true; } ftxui::Element OnRender() override final { const auto min_size = ftxui::size(ftxui::WIDTH, ftxui::GREATER_THAN, 5); std::vector grid; grid.reserve(config_->states.size()); for (const auto& opt : config_->states) grid.push_back({opt.description, min_size(opt.ui->Render())}); ftxui::Element highlighted; if (error_.empty()) highlighted = ftxui::separator(); else highlighted = ftxui::inverted(ftxui::text(error_)); return ftxui::window(title_, ftxui::vbox({ ftxui::hcenter(buttons_->Render()), highlighted, ftxui::gridbox(std::move(grid)) })); } }; } ftxui::Component settings(std::shared_ptr wal) { return std::make_shared(std::move(wal)); } }} // lwcli //