#include "AdhocServerScreen.h" #include "Common/Net/Resolve.h" #include "Common/UI/Root.h" #include "Common/StringUtils.h" #include "Core/HLE/sceNetAdhoc.h" AdhocServerScreen::AdhocServerScreen(std::string *value, std::string_view title) : UI::PopupScreen(title, T(I18NCat::DIALOG, "OK"), T(I18NCat::DIALOG, "Cancel")), value_(value) { resolver_ = std::thread([](AdhocServerScreen *thiz) { thiz->ResolverThread(); }, this); editValue_ = *value; AdhocLoadServerList(); } AdhocServerScreen::~AdhocServerScreen() { { std::unique_lock guard(resolverLock_); resolverState_ = ResolverState::QUIT; resolverCond_.notify_one(); } resolver_.join(); } void AdhocServerScreen::sendMessage(UIMessage message, const char *value) { if (message == UIMessage::ADHOC_SERVER_LIST_CHANGED) { RecreateViews(); } } void AdhocServerScreen::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; auto sy = GetI18NCategory(I18NCat::SYSTEM); auto di = GetI18NCategory(I18NCat::DIALOG); auto n = GetI18NCategory(I18NCat::NETWORKING); auto listItems = AdhocGetServerList(); PopupTextInputChoice *textInputChoice = parent->Add(new PopupTextInputChoice(GetRequesterToken(), &editValue_, n->T("Hostname"), "", 256, screenManager())); parent->Add(new Spacer(5.0f)); // editValue_ has the currently selected server. On closing the dialog, we copy that to settings. // Start with the downloaded list. std::vector entries = listItems; std::vector localEntries; std::vector customEntries; // Add localhost and local IPs, since those are common ones to connect to. { AdhocServerListEntry localhostEntry; localhostEntry.name = "localhost"; localhostEntry.host = "localhost"; localEntries.push_back(localhostEntry); std::vector listIP; net::GetLocalIP4List(listIP); for (const auto &ipAddress : listIP) { if (startsWith(ipAddress, "127.") || startsWith(ipAddress, "169.254.") || startsWith(ipAddress, "0.")) { continue; } AdhocServerListEntry entry; entry.name = ipAddress; entry.host = ipAddress; localEntries.push_back(entry); } } auto hostInEntries = [entries](const std::string &host) { for (const auto &entry : entries) { if (entry.host == host) { return true; } } return false; }; for (const auto &host : g_Config.vCustomAdhocServerListWithRelay) { // If the host is already in the public list, skip it. We don't want duplicates. if (hostInEntries(host)) { continue; } AdhocServerListEntry entry; entry.name = host; entry.host = host; entry.mode = AdhocDataMode::AemuPostoffice; customEntries.push_back(entry); } for (const auto &host : g_Config.vCustomAdhocServerList) { // If the host is already in the public list, skip it. We don't want duplicates. if (hostInEntries(host)) { continue; } AdhocServerListEntry entry; entry.name = host; entry.host = host; entry.mode = AdhocDataMode::P2P; customEntries.push_back(entry); } ScrollView *scrollView = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0f)); LinearLayout *innerView = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); innerView->SetSpacing(5.0f); auto AddButtonFromEntry = [this](UI::ViewGroup *parent, const AdhocServerListEntry &entry) { // Filter out IP prefixed with "127." and "169.254." also "0." since they can be redundant or unusable auto button = parent->Add(new Button(entry.host, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); button->OnClick.Add([this](UI::EventParams &e) { std::string value = e.v->Tag(); if (!value.empty()) { editValue_ = value; // TODO: Let's change this to an actual button later. System_CopyStringToClipboard(value); } }); button->SetTag(entry.host); }; if (!customEntries.empty()) { CollapsibleSection *customSection = innerView->Add(new CollapsibleSection(n->T("Custom server list"), new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); for (const auto &entry : customEntries) { AddButtonFromEntry(customSection, entry); } } CollapsibleSection *publicSection = innerView->Add(new CollapsibleSection(n->T("Public server list"), new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); for (const auto &entry : entries) { AddButtonFromEntry(publicSection, entry); } CollapsibleSection *localSection = innerView->Add(new CollapsibleSection(n->T("Local network addresses"), new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); for (const auto &entry : localEntries) { AddButtonFromEntry(localSection, entry); } scrollView->Add(innerView); parent->Add(scrollView); progressView_ = parent->Add(new NoticeView(NoticeLevel::INFO, n->T("Validating address..."), "", new LinearLayoutParams(Margins(0, 5, 0, 0)))); progressView_->SetVisibility(UI::V_GONE); } void AdhocServerScreen::ResolverThread() { std::unique_lock guard(resolverLock_); while (resolverState_ != ResolverState::QUIT) { resolverCond_.wait(guard); if (resolverState_ == ResolverState::QUEUED) { resolverState_ = ResolverState::PROGRESS; addrinfo *resolved = nullptr; std::string err; toResolveResult_ = net::DNSResolve(toResolve_, "80", &resolved, err); if (resolved) net::DNSResolveFree(resolved); resolverState_ = ResolverState::READY; } } } bool AdhocServerScreen::CanComplete(DialogResult result) { auto n = GetI18NCategory(I18NCat::NETWORKING); if (result != DR_OK) return true; std::string value = editValue_; if (lastResolved_ == value) { return true; } // Currently running. if (resolverState_ == ResolverState::PROGRESS) return false; std::lock_guard guard(resolverLock_); switch (resolverState_) { case ResolverState::PROGRESS: case ResolverState::QUIT: return false; case ResolverState::QUEUED: case ResolverState::WAITING: break; case ResolverState::READY: if (toResolve_ == value) { // Reset the state, nothing there now. resolverState_ = ResolverState::WAITING; toResolve_.clear(); lastResolved_ = value; lastResolvedResult_ = toResolveResult_; if (lastResolvedResult_) { progressView_->SetVisibility(UI::V_GONE); } else { progressView_->SetText(n->T("Invalid IP or hostname")); progressView_->SetLevel(NoticeLevel::ERROR); progressView_->SetVisibility(UI::V_VISIBLE); } return true; } // Throw away that last result, it was for a different value. break; } resolverState_ = ResolverState::QUEUED; toResolve_ = value; resolverCond_.notify_one(); progressView_->SetText(n->T("Validating address...")); progressView_->SetLevel(NoticeLevel::INFO); progressView_->SetVisibility(UI::V_VISIBLE); return false; } void AdhocServerScreen::OnCompleted(DialogResult result) { if (result == DR_OK) { *value_ = StripSpaces(editValue_); } }