From a2e874de855bf6f9576322078e8a210d1b241d8c Mon Sep 17 00:00:00 2001 From: Nikolai Fedorov Date: Sat, 25 Apr 2026 10:34:50 +0300 Subject: [PATCH] ui improve --- include/TitlesLayout.hpp | 3 + include/TransferOverlay.hpp | 15 ++-- include/TransferState.hpp | 10 ++- include/client.hpp | 2 + source/Main.cpp | 1 + source/TitlesLayout.cpp | 67 +++++++++++----- source/client.cpp | 149 ++++++++++++++++++++---------------- source/server.cpp | 8 ++ source/util.cpp | 15 ---- 9 files changed, 162 insertions(+), 108 deletions(-) diff --git a/include/TitlesLayout.hpp b/include/TitlesLayout.hpp index 038b53b..7443a23 100644 --- a/include/TitlesLayout.hpp +++ b/include/TitlesLayout.hpp @@ -7,10 +7,13 @@ namespace ui { private: pu::ui::elm::Menu::Ref titlesMenu; + bool m_inputLocked = false; public: void InitTitles(); + void LockInput() { m_inputLocked = true; } + void UnlockInput() { m_inputLocked = false; } void onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos); diff --git a/include/TransferOverlay.hpp b/include/TransferOverlay.hpp index ea0c6fa..ffa2c38 100644 --- a/include/TransferOverlay.hpp +++ b/include/TransferOverlay.hpp @@ -19,17 +19,17 @@ namespace ui { TransferOverlay(const std::string &title) : Overlay(OvlX, OvlY, OvlW, OvlH, pu::ui::Color(30, 30, 30, 220)) { - titleText = pu::ui::elm::TextBlock::New(OvlX + 40, OvlY + 30, title); + titleText = pu::ui::elm::TextBlock::New(40, 30, title); titleText->SetColor(pu::ui::Color(255, 255, 255, 255)); - statusText = pu::ui::elm::TextBlock::New(OvlX + 40, OvlY + 90, ""); + statusText = pu::ui::elm::TextBlock::New(40, 90, ""); statusText->SetColor(pu::ui::Color(180, 180, 180, 255)); - progressBar = pu::ui::elm::ProgressBar::New(OvlX + 40, OvlY + 140, OvlW - 80, 20, 100.0); + progressBar = pu::ui::elm::ProgressBar::New(40, 140, OvlW - 80, 20, 100.0); progressBar->SetProgressColor(pu::ui::Color(100, 180, 255, 255)); progressBar->SetBackgroundColor(pu::ui::Color(70, 70, 70, 255)); - hintText = pu::ui::elm::TextBlock::New(OvlX + 40, OvlY + 195, "Press B to cancel"); + hintText = pu::ui::elm::TextBlock::New(40, 195, "Press B to cancel"); hintText->SetColor(pu::ui::Color(130, 130, 130, 255)); this->Add(titleText); @@ -40,7 +40,12 @@ namespace ui { PU_SMART_CTOR(TransferOverlay) void SetStatus(const std::string &status) { - statusText->SetText(status); + static constexpr size_t MaxChars = 48; + if (status.size() > MaxChars) { + statusText->SetText(status.substr(0, MaxChars - 3) + "..."); + } else { + statusText->SetText(status); + } } void SetProgress(double val) { diff --git a/include/TransferState.hpp b/include/TransferState.hpp index 36a71bc..e7753c4 100644 --- a/include/TransferState.hpp +++ b/include/TransferState.hpp @@ -6,6 +6,7 @@ struct TransferState { std::atomic done{false}; std::atomic cancelled{false}; + std::atomic connection_failed{false}; std::atomic bytes_done{0}; std::atomic bytes_total{0}; @@ -13,10 +14,11 @@ struct TransferState { mutable std::mutex status_mutex; void reset() { - done = false; - cancelled = false; - bytes_done = 0; - bytes_total = 0; + done = false; + cancelled = false; + connection_failed = false; + bytes_done = 0; + bytes_total = 0; std::lock_guard lock(status_mutex); status.clear(); } diff --git a/include/client.hpp b/include/client.hpp index e0b83bf..f62c79b 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -5,6 +5,8 @@ using path = fs::path; int transfer_files(path directory); bool isClientTransferDone(); +bool isClientTransferCancelled(); +bool isClientConnectionFailed(); void cancelClientTransfer(); double getClientProgress(); std::string getClientStatusText(); diff --git a/source/Main.cpp b/source/Main.cpp index 60393c3..2904fcd 100644 --- a/source/Main.cpp +++ b/source/Main.cpp @@ -60,5 +60,6 @@ int main() { main->Show(); + servicesExit(); return 0; } diff --git a/source/TitlesLayout.cpp b/source/TitlesLayout.cpp index 19f6795..47cc672 100644 --- a/source/TitlesLayout.cpp +++ b/source/TitlesLayout.cpp @@ -26,7 +26,11 @@ namespace ui { } void TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) { + if (m_inputLocked) return; + if (Down & HidNpadButton_Plus) { + cancelClientTransfer(); + cancelServerTransfer(); mainApp->Close(); return; } @@ -42,14 +46,24 @@ namespace ui { switch (opt) { case 0: { // Transfer selected - auto result = io::backup(index, g_currentUId); - if (std::get<0>(result)) { - printf("path is %s\n", std::get<2>(result).c_str()); - std::string path = std::get<2>(result); - auto directory = std::filesystem::path(path); - transfer_files(directory); + auto backupResult = io::backup(index, g_currentUId); + if (!std::get<0>(backupResult)) { + mainApp->CreateShowDialog("Transfer", "Failed to create backup:\n" + std::get<2>(backupResult), {"OK"}, true); + break; + } + auto directory = std::filesystem::path(std::get<2>(backupResult)); + { auto ovl = TransferOverlay::New("Transferring save data..."); + this->titlesMenu->SetVisible(false); mainApp->StartOverlay(ovl); + this->LockInput(); + if (transfer_files(directory) != 0) { + mainApp->EndOverlay(); + this->titlesMenu->SetVisible(true); + this->UnlockInput(); + mainApp->CreateShowDialog("Transfer", "Failed to start transfer.", {"OK"}, true); + break; + } while (!isClientTransferDone()) { ovl->SetStatus(getClientStatusText()); ovl->SetProgress(getClientProgress()); @@ -60,17 +74,29 @@ namespace ui { svcSleepThread(16666666LL); } mainApp->EndOverlay(); + this->titlesMenu->SetVisible(true); + this->UnlockInput(); + } + if (isClientConnectionFailed()) { + mainApp->CreateShowDialog("Transfer", "No receiver found.\nMake sure the other Switch is in Receive mode.", {"OK"}, true); + } else if (isClientTransferCancelled()) { + mainApp->CreateShowDialog("Transfer", "Transfer cancelled.", {"OK"}, true); + } else { + mainApp->CreateShowDialog("Transfer", "Save data sent successfully!", {"OK"}, true); } break; } case 1: { // Receive selected - printf("startSendingThread\n"); - int result = startSendingThread(); - printf("result is %i\n", result); - if (result == 0) { + if (startSendingThread() != 0) { + mainApp->CreateShowDialog("Receive", "Failed to start receiver.\nCheck network connection.", {"OK"}, true); + break; + } + { auto ovl = TransferOverlay::New("Receiving save data..."); + this->titlesMenu->SetVisible(false); mainApp->StartOverlay(ovl); + this->LockInput(); while (!isServerTransferDone()) { ovl->SetStatus(getServerStatusText()); ovl->SetProgress(getServerProgress()); @@ -81,16 +107,19 @@ namespace ui { svcSleepThread(16666666LL); } mainApp->EndOverlay(); - if (!isServerTransferCancelled()) { - auto restoreResult = io::restore(index, g_currentUId, 0, title.name()); - if (std::get<0>(restoreResult)) { - printf("%s\n", std::get<2>(restoreResult).c_str()); - } else { - printf("Failed to restore with error %s\n", std::get<2>(restoreResult).c_str()); - } - } + this->titlesMenu->SetVisible(true); + this->UnlockInput(); + } + if (isServerTransferCancelled()) { + mainApp->CreateShowDialog("Receive", "Transfer cancelled.", {"OK"}, true); + break; + } + auto restoreResult = io::restore(index, g_currentUId, 0, title.name()); + if (std::get<0>(restoreResult)) { + mainApp->CreateShowDialog("Receive", "Save data received and restored successfully!", {"OK"}, true); + } else { + mainApp->CreateShowDialog("Receive", "Restore failed:\n" + std::get<2>(restoreResult), {"OK"}, true); } - break; } } diff --git a/source/client.cpp b/source/client.cpp index d737685..3b5d872 100644 --- a/source/client.cpp +++ b/source/client.cpp @@ -1,6 +1,8 @@ #include +#include #include #include +#include #include #include #include @@ -24,15 +26,12 @@ using path = fs::path; static TransferState g_client_state; -bool isClientTransferDone() { return g_client_state.done.load(); } -void cancelClientTransfer() { g_client_state.cancelled.store(true); } -double getClientProgress() { return g_client_state.progress(); } -std::string getClientStatusText() { return g_client_state.getStatus(); } - -struct ThreadArgs { - int sock; - fs::path directory; -}; +bool isClientTransferDone() { return g_client_state.done.load(); } +bool isClientTransferCancelled() { return g_client_state.cancelled.load(); } +bool isClientConnectionFailed() { return g_client_state.connection_failed.load(); } +void cancelClientTransfer() { g_client_state.cancelled.store(true); } +double getClientProgress() { return g_client_state.progress(); } +std::string getClientStatusText() { return g_client_state.getStatus(); } static bool send_all(int sock, const void* buf, size_t len) { size_t sent = 0; @@ -79,37 +78,64 @@ static bool sendFile(int sock, const fs::path& filepath) { return true; } -static void* send_files_thread(void* arg) { - ThreadArgs* targs = static_cast(arg); - int sock = targs->sock; - fs::path cwd = targs->directory; - delete targs; +static int find_server(char* server_ip); - for (const auto& entry : fs::recursive_directory_iterator(cwd)) { +static void fail_connect() { + g_client_state.connection_failed.store(true); + g_client_state.done.store(true); +} + +static void* discovery_and_send_thread(void* arg) { + fs::path directory = *static_cast(arg); + delete static_cast(arg); + + char server_ip[INET_ADDRSTRLEN]; + if (find_server(server_ip) != 0) { + if (!g_client_state.cancelled.load()) fail_connect(); + g_client_state.done.store(true); + return nullptr; + } + if (g_client_state.cancelled.load()) { g_client_state.done.store(true); return nullptr; } + + g_client_state.setStatus("Connecting..."); + Socket tcp(socket(AF_INET, SOCK_STREAM, 0)); + if (!tcp.valid()) { fail_connect(); return nullptr; } + + sockaddr_in serv{}; + serv.sin_family = AF_INET; + serv.sin_port = htons(proto::TCP_PORT); + if (inet_pton(AF_INET, server_ip, &serv.sin_addr) <= 0 || + connect(tcp, (sockaddr*)&serv, sizeof(serv)) < 0) { + fail_connect(); + return nullptr; + } + + uint64_t total = 0; + for (const auto& entry : fs::recursive_directory_iterator(directory)) + if (fs::is_regular_file(entry.path())) + total += fs::file_size(entry.path()); + g_client_state.bytes_total.store(total); + + for (const auto& entry : fs::recursive_directory_iterator(directory)) { if (g_client_state.cancelled.load()) break; const path& p = entry.path(); if (fs::is_regular_file(p)) { g_client_state.setStatus(p.filename().string()); - if (!sendFile(sock, p)) break; + if (!sendFile(tcp.fd, p)) break; } } - // EOF sentinel — server reads this and exits its receive loop cleanly uint32_t sentinel = proto::EOF_SENTINEL; - send_all(sock, &sentinel, sizeof(sentinel)); + send_all(tcp.fd, &sentinel, sizeof(sentinel)); g_client_state.setStatus(""); - close(sock); g_client_state.done.store(true); return nullptr; } static int find_server(char* server_ip) { - Socket udp(socket(AF_INET, SOCK_DGRAM, 0)); - if (!udp.valid()) { - std::cerr << "find_server: socket failed" << std::endl; - return -1; - } + int udp_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (udp_fd < 0) return -1; sockaddr_in addr{}; addr.sin_family = AF_INET; @@ -117,57 +143,50 @@ static int find_server(char* server_ip) { addr.sin_addr.s_addr = inet_addr(proto::MULTICAST_GROUP); const char* msg = "DISCOVER_SERVER"; - if (sendto(udp, msg, strlen(msg), 0, (sockaddr*)&addr, sizeof(addr)) < 0) { - std::cerr << "find_server: sendto failed" << std::endl; + if (sendto(udp_fd, msg, strlen(msg), 0, (sockaddr*)&addr, sizeof(addr)) < 0) { + close(udp_fd); return -1; } - sockaddr_in from{}; - socklen_t fromlen = sizeof(from); - char buf[256]; - ssize_t n = recvfrom(udp, buf, sizeof(buf) - 1, 0, (sockaddr*)&from, &fromlen); - if (n < 0) { - std::cerr << "find_server: recvfrom failed" << std::endl; - return -1; + // Poll in 100ms slices so we can react to cancel within 100ms + auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3); + while (std::chrono::steady_clock::now() < deadline) { + if (g_client_state.cancelled.load()) { + close(udp_fd); + return -1; + } + struct timeval tv{0, 100000}; + fd_set fds; + FD_ZERO(&fds); + FD_SET(udp_fd, &fds); + if (select(udp_fd + 1, &fds, nullptr, nullptr, &tv) > 0) { + sockaddr_in from{}; + socklen_t fromlen = sizeof(from); + char buf[256]; + ssize_t n = recvfrom(udp_fd, buf, sizeof(buf) - 1, 0, (sockaddr*)&from, &fromlen); + if (n > 0) { + buf[n] = '\0'; + if (strcmp(buf, "SERVER_HERE") == 0) { + inet_ntop(AF_INET, &from.sin_addr, server_ip, INET_ADDRSTRLEN); + close(udp_fd); + return 0; + } + } + } } - buf[n] = '\0'; - if (strcmp(buf, "SERVER_HERE") != 0) { - std::cerr << "find_server: unexpected response: " << buf << std::endl; - return -1; - } - inet_ntop(AF_INET, &from.sin_addr, server_ip, INET_ADDRSTRLEN); - return 0; + + close(udp_fd); + return -1; } int transfer_files(fs::path directory) { - char server_ip[INET_ADDRSTRLEN]; - if (find_server(server_ip) != 0) return -1; - - Socket tcp(socket(AF_INET, SOCK_STREAM, 0)); - if (!tcp.valid()) return -1; - - sockaddr_in serv{}; - serv.sin_family = AF_INET; - serv.sin_port = htons(proto::TCP_PORT); - if (inet_pton(AF_INET, server_ip, &serv.sin_addr) <= 0) return -1; - if (connect(tcp, (sockaddr*)&serv, sizeof(serv)) < 0) return -1; - - uint64_t total = 0; - for (const auto& entry : fs::recursive_directory_iterator(directory)) { - if (fs::is_regular_file(entry.path())) - total += fs::file_size(entry.path()); - } - g_client_state.reset(); - g_client_state.bytes_total.store(total); - g_client_state.setStatus("Connecting..."); - - ThreadArgs* args = new ThreadArgs{tcp.fd, directory}; - tcp.release(); // ownership transferred to thread + g_client_state.setStatus("Searching for receiver..."); + fs::path* arg = new fs::path(directory); pthread_t thread; - if (pthread_create(&thread, nullptr, send_files_thread, args) != 0) { - delete args; + if (pthread_create(&thread, nullptr, discovery_and_send_thread, arg) != 0) { + delete arg; return -1; } pthread_detach(thread); diff --git a/source/server.cpp b/source/server.cpp index 1f7acd5..b282077 100644 --- a/source/server.cpp +++ b/source/server.cpp @@ -24,6 +24,7 @@ static TransferState g_server_state; static std::atomic g_server_client_sock{-1}; +static std::atomic g_server_listen_sock{-1}; static std::atomic g_broadcast_sock{-1}; bool isServerTransferDone() { return g_server_state.done.load(); } @@ -35,6 +36,8 @@ void cancelServerTransfer() { g_server_state.cancelled.store(true); int sock = g_server_client_sock.load(); if (sock >= 0) shutdown(sock, SHUT_RDWR); + int lsock = g_server_listen_sock.load(); + if (lsock >= 0) shutdown(lsock, SHUT_RDWR); int bsock = g_broadcast_sock.load(); if (bsock >= 0) shutdown(bsock, SHUT_RDWR); } @@ -173,9 +176,11 @@ static void* accept_and_handle(void* arg) { int server_fd = static_cast(arg)->server_fd; delete static_cast(arg); + g_server_listen_sock.store(server_fd); sockaddr_in client_addr{}; socklen_t client_len = sizeof(client_addr); int client_socket = accept(server_fd, (sockaddr*)&client_addr, &client_len); + g_server_listen_sock.store(-1); close(server_fd); if (client_socket >= 0) { @@ -256,6 +261,9 @@ int startSendingThread() { return 1; } + int yes = 1; + setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; diff --git a/source/util.cpp b/source/util.cpp index f65e0ad..f6a1198 100644 --- a/source/util.cpp +++ b/source/util.cpp @@ -32,10 +32,7 @@ void servicesExit(void) { Logger::getInstance().flush(); - pdmqryExit(); - socketExit(); Account::exit(); - nsExit(); plExit(); romfsExit(); } @@ -79,18 +76,6 @@ Result servicesInit(void) return res; } - if (R_FAILED(res = nsInitialize())) { - Logger::getInstance().log(Logger::ERROR, "nsInitialize failed. Result code 0x%08lX.", res); - return res; - } - - // Configuration::getInstance(); - - if (R_SUCCEEDED(res = pdmqryInitialize())) {} - else { - Logger::getInstance().log(Logger::WARN, "pdmqryInitialize failed with result 0x%08lX.", res); - } - Logger::getInstance().log(Logger::INFO, "NXST loading completed!"); return 0;