Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/UI/AdhocServerScreen.cpp
10520 views
1
#include <algorithm>
2
#include "ppsspp_config.h"
3
4
#undef new
5
#include "ext/rapidjson/include/rapidjson/document.h"
6
#include "Common/DbgNew.h"
7
8
#include "AdhocServerScreen.h"
9
#include "Core/Util/GameDB.h"
10
11
#include "Common/Net/Resolve.h"
12
#include "Common/UI/Root.h"
13
#include "Common/UI/PopupScreens.h"
14
#include "Common/StringUtils.h"
15
#include "Common/Net/HTTPClient.h"
16
#include "Core/HLE/sceNetAdhoc.h"
17
#include "UI/MiscViews.h"
18
19
static void UpgradeGameName(std::string *str) {
20
if (str->size() == 9) { // TODO: Make a better heuristic, we might make some failed lookup into the DB.
21
// It's probably a game ID. Convert it to a name using the database.
22
std::vector<GameDBInfo> infos;
23
if (g_gameDB.GetGameInfos(*str, &infos)) {
24
*str = infos[0].title;
25
}
26
}
27
}
28
29
std::vector<AdhocGame> ParseDataJson(std::string_view json) {
30
rapidjson::Document d;
31
d.Parse(json.data(), json.size());
32
33
std::vector<AdhocGame> gameList;
34
35
if (d.HasParseError() || !d.HasMember("games")) return gameList;
36
37
const auto& gamesArray = d["games"];
38
for (auto& g : gamesArray.GetArray()) {
39
AdhocGame game;
40
game.name = g["name"].GetString();
41
UpgradeGameName(&game.name);
42
43
// Handle string-to-int conversion for usercount
44
game.usercount = std::stoi(g["usercount"].GetString());
45
46
if (g.HasMember("groups")) {
47
for (auto& grp : g["groups"].GetArray()) {
48
AdhocGroup group;
49
group.name = grp["name"].GetString();
50
group.usercount = std::stoi(grp["usercount"].GetString());
51
52
if (grp.HasMember("users")) {
53
for (auto& u : grp["users"].GetArray()) {
54
AdhocUser user;
55
user.name = u["name"].GetString();
56
57
for (auto& p : u["pdp_ports"].GetArray())
58
user.pdp_ports.push_back(p.GetInt());
59
60
for (auto& p : u["ptp_ports"].GetArray())
61
user.ptp_ports.push_back(p.GetInt());
62
63
group.users.push_back(user);
64
}
65
}
66
game.groups.push_back(group);
67
}
68
}
69
gameList.push_back(game);
70
}
71
return gameList;
72
}
73
74
class AdhocAddServerPopupScreen : public UI::PopupScreen {
75
public:
76
AdhocAddServerPopupScreen(std::string *outEditValue) : PopupScreen(T(I18NCat::NETWORKING, "Add server"), T(I18NCat::DIALOG, "Add"), T(I18NCat::DIALOG, "Cancel")), outEditValue_(outEditValue) {
77
}
78
79
void CreatePopupContents(UI::ViewGroup *parent) override {
80
using namespace UI;
81
auto ni = GetI18NCategory(I18NCat::NETWORKING);
82
83
PopupTextInputChoice *textInputChoice = parent->Add(new PopupTextInputChoice(GetRequesterToken(), &editValue_, ni->T("Hostname or IP"), "", 450, screenManager()));
84
textInputChoice->SetShadowText(ni->T("Hostname or IP"));
85
parent->Add(new CheckBox(&hasRelay_, ni->T("Relay server mode")));
86
}
87
88
virtual void OnCompleted(DialogResult result) override {
89
if (result == DialogResult::DR_OK) {
90
std::vector<AdhocServerListEntry> servers = AdhocGetServerList(AdhocLoadListMode::CacheOnlySync);
91
bool preset = false;
92
for (auto &iter : servers) {
93
if (equalsNoCase(editValue_, iter.host)) {
94
// We have this predefined.
95
preset = true;
96
}
97
}
98
if (!preset) {
99
if (hasRelay_) {
100
// Insert at the start of the vector.
101
if (!ContainsNoCase(g_Config.vCustomAdhocServerListWithRelay, editValue_) &&
102
!ContainsNoCase(g_Config.vCustomAdhocServerList, editValue_)) {
103
g_Config.vCustomAdhocServerListWithRelay.insert(g_Config.vCustomAdhocServerListWithRelay.begin(), editValue_);
104
}
105
} else {
106
if (!ContainsNoCase(g_Config.vCustomAdhocServerList, editValue_) &&
107
!ContainsNoCase(g_Config.vCustomAdhocServerListWithRelay, editValue_)) {
108
g_Config.vCustomAdhocServerList.insert(g_Config.vCustomAdhocServerList.begin(), editValue_);
109
}
110
}
111
}
112
*outEditValue_ = editValue_;
113
}
114
}
115
virtual bool CanComplete(DialogResult result) override { return result == DR_OK ? !editValue_.empty() : true; }
116
117
const char *tag() const override { return "AdhocAddServerPopup"; }
118
119
private:
120
std::string editValue_;
121
std::string *outEditValue_;
122
bool hasRelay_ = false;
123
};
124
125
class AdhocServerCompactInfo : public UI::LinearLayout {
126
public:
127
AdhocServerCompactInfo(const AdhocServerListEntry &entry, UI::LayoutParams *layoutParams = nullptr);
128
void Draw(UIContext &dc) override {
129
UI::LinearLayout::Draw(dc);
130
// Underline
131
dc.Draw()->DrawImageCenterTexel(dc.GetTheme().whiteImage, bounds_.x, bounds_.y2() - 2, bounds_.x2(), bounds_.y2(), dc.GetTheme().popupTitleStyle.fgColor);
132
}
133
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override {
134
w = 500; h = 100;
135
}
136
private:
137
AdhocServerListEntry entry_;
138
};
139
140
AdhocServerCompactInfo::AdhocServerCompactInfo(const AdhocServerListEntry &entry, UI::LayoutParams *layoutParams)
141
: UI::LinearLayout(ORIENT_HORIZONTAL, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::WRAP_CONTENT, UI::Margins(5.0f, 0.0f))), entry_(entry) {
142
using namespace UI;
143
144
SetSpacing(5.0f);
145
146
LinearLayout *lines = Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(Margins(5, 5))));
147
lines->SetSpacing(0.0f);
148
TextView *name = lines->Add(new TextView(entry.name));
149
150
std::string secondLine = entry.host;
151
auto n = GetI18NCategory(I18NCat::NETWORKING);
152
if (!entry.location.empty()) {
153
secondLine += ": " + entry.location;
154
}
155
156
lines->Add(new TextView(secondLine))->SetTextSize(TextSize::Small)->SetWordWrap();
157
158
Add(new Spacer(0.0f, new LinearLayoutParams(1.0f, Margins(0.0f, 5.0f))));
159
160
if (entry.mode == AdhocDataMode::AemuPostoffice) {
161
TextView *relay = Add(new TextView(n->T("Relay"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity::G_VCENTER, Margins(10.0))));
162
}
163
164
Add(new Choice(ImageID("I_FILE_COPY"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)))->OnClick.Add([host = entry_.host](UI::EventParams &) {
165
System_CopyStringToClipboard(host);
166
});
167
}
168
169
static UI::View *CreateInfoItemWithButton(std::string_view text, ImageID buttonImage, std::function<void(UI::EventParams &)> onClick) {
170
using namespace UI;
171
LinearLayout *line = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(12, 0)));
172
line->Add(new TextView(text, new LinearLayoutParams(0.0f, Gravity::G_VCENTER)));
173
line->Add(new Spacer(0, new LinearLayoutParams(1.0f)));
174
line->Add(new Choice(buttonImage, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)))->OnClick.Add(onClick);
175
return line;
176
}
177
178
static UI::View *CreateLinkButton(std::string url, std::string_view title = "") {
179
using namespace UI;
180
181
// steal strings from all over the place
182
auto cr = GetI18NCategory(I18NCat::PSPCREDITS);
183
auto st = GetI18NCategory(I18NCat::STORE);
184
185
ImageID icon = ImageID("I_LINK_OUT_QUESTION");
186
if (startsWith(url, "https://discord")) {
187
icon = ImageID("I_LOGO_DISCORD");
188
if (title.empty())
189
title = cr->T("Discord");
190
} else {
191
icon = ImageID("I_LINK_OUT");
192
if (title.empty())
193
title = st->T("Website");
194
}
195
196
Choice *choice = new Choice(title, icon, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT));
197
choice->OnClick.Add([url](UI::EventParams &) {
198
System_LaunchUrl(LaunchUrlType::BROWSER_URL, url);
199
});
200
return choice;
201
}
202
203
// Later, this might also show games-in-progress.
204
// For now, it's just a simple metadata viewer.
205
AdhocServerInfoScreen::AdhocServerInfoScreen(const AdhocServerListEntry &entry)
206
: UI::PopupScreen("", T(I18NCat::DIALOG, "Back")), entry_(entry) {
207
208
if (!entry.dataJsonUrl.empty()) {
209
statusRequest_ = g_DownloadManager.StartDownload(entry.dataJsonUrl, Path(), http::RequestFlags::KeepInMemory, nullptr, "status");
210
}
211
}
212
213
void AdhocServerInfoScreen::CreatePopupContents(UI::ViewGroup *parent) {
214
using namespace UI;
215
auto pa = GetI18NCategory(I18NCat::PAUSE);
216
auto di = GetI18NCategory(I18NCat::DIALOG);
217
auto ni = GetI18NCategory(I18NCat::NETWORKING);
218
219
Margins contentMargins(12, 0);
220
221
parent->Add(new AdhocServerCompactInfo(entry_, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, contentMargins)));
222
parent->Add(new Spacer(5.0f));
223
224
ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f));
225
LinearLayout *content = new LinearLayout(ORIENT_VERTICAL);
226
content->SetSpacing(6.0f);
227
if (!entry_.ip.empty()) {
228
content->Add(new InfoItem(entry_.ip, ""));
229
}
230
TextView *desc = content->Add(new TextView(entry_.description, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, contentMargins)));
231
desc->SetTextSize(TextSize::Small);
232
desc->SetWordWrap();
233
234
LinearLayout *buttonStrip = content->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, contentMargins)));
235
buttonStrip->SetSpacing(8);
236
if (!entry_.web.empty() || !entry_.discord.empty()) {
237
if (!entry_.web.empty()) {
238
buttonStrip->Add(CreateLinkButton(entry_.web));
239
}
240
if (!entry_.discord.empty()) {
241
buttonStrip->Add(CreateLinkButton(entry_.discord));
242
}
243
}
244
245
if (entry_.dataJsonUrl.empty()) {
246
content->Add(CreateInfoItemWithButton(ni->T("This server has no data.json status page"), ImageID("I_LINK_OUT_QUESTION"), [](UI::EventParams &e) {
247
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/docs/multiplayer/adhoc-server-status/");
248
}));
249
if (!entry_.statusWebUrl.empty()) {
250
buttonStrip->Add(CreateLinkButton(entry_.statusWebUrl, ni->T("Status")));
251
}
252
if (!entry_.statusXmlUrl.empty()) {
253
buttonStrip->Add(CreateLinkButton(entry_.statusXmlUrl, ni->T("Status")));
254
}
255
} else {
256
if (games_.empty()) {
257
if (statusRequest_) {
258
// Still loading. TODO: Show a spinner or something.
259
} else {
260
content->Add(new TextView(ni->T("No games in progress on this server")));
261
}
262
} else {
263
for (const AdhocGame &game : games_) {
264
std::string title = game.name + " - " + ApplySafeSubstitutions(ni->T("players: %1"), game.usercount) + " " + ApplySafeSubstitutions(ni->T("groups: %1"), (int)game.groups.size());
265
CollapsibleSection *gameSection = content->Add(new CollapsibleSection(title));
266
for (const AdhocGroup &group : game.groups) {
267
if (group.usercount == 1 && group.name == "Groupless") {
268
gameSection->Add(new TextView(" " + ApplySafeSubstitutions(ni->T("Players waiting: %1"), group.usercount)))->SetTextSize(TextSize::Small);
269
continue;
270
}
271
gameSection->Add(new TextView(" " + group.name + " - " + ApplySafeSubstitutions(ni->T("players: %1"), group.usercount)))->SetTextSize(TextSize::Small);
272
for (const AdhocUser &user : group.users) {
273
std::string portInfo;
274
if (!user.pdp_ports.empty()) {
275
portInfo += "PDP: ";
276
for (int port : user.pdp_ports) {
277
portInfo += std::to_string(port) + " ";
278
}
279
}
280
if (!user.ptp_ports.empty()) {
281
portInfo += "PTP: ";
282
for (int port : user.ptp_ports) {
283
portInfo += std::to_string(port) + " ";
284
}
285
}
286
gameSection->Add(new TextView(" " + user.name + " " + portInfo))->SetTextSize(TextSize::Tiny);
287
}
288
}
289
gameSection->SetOpen(false); // NOTE: Must be last!
290
}
291
}
292
}
293
294
scroll->Add(content);
295
parent->Add(scroll);
296
}
297
298
void AdhocServerInfoScreen::update() {
299
UI::PopupScreen::update();
300
if (statusRequest_ && statusRequest_->Done()) {
301
std::string json;
302
statusRequest_->buffer().TakeAll(&json);
303
games_ = ParseDataJson(json);
304
statusRequest_.reset();
305
RecreateViews();
306
}
307
}
308
309
void AddDeleteButton(std::string *editValue, ScreenManager *screenManager, UI::ViewGroup *viewGroup, const AdhocServerListEntry &entry) {
310
using namespace UI;
311
Choice *deleteButton = viewGroup->Add(new Choice(ImageID("I_TRASHCAN"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity::G_VCENTER, Margins(0, 0, 10, 0))));
312
deleteButton->OnClick.Add([host = entry.host, screenManager, editValue](UI::EventParams &e) {
313
auto di = GetI18NCategory(I18NCat::DIALOG);
314
const std::string quotedHost = "\"" + host + "\"";
315
const std::string message = ApplySafeSubstitutions(di->T("Are you sure you want to delete %1?"), quotedHost);
316
screenManager->push(new UI::MessagePopupScreen(di->T("Delete"), message, di->T("Delete"), di->T("Cancel"), [host, editValue](bool confirmed) {
317
if (confirmed) {
318
RemoveNoCase(g_Config.vCustomAdhocServerList, host);
319
RemoveNoCase(g_Config.vCustomAdhocServerListWithRelay, host);
320
if (*editValue == host) {
321
// Reset to socom.cc, which will always be in a list.
322
*editValue = DefaultProAdhocServer();
323
}
324
}
325
}));
326
});
327
}
328
329
class AdhocServerRow : public UI::LinearLayout {
330
public:
331
AdhocServerRow(std::string *value, const AdhocServerListEntry &entry, bool showDeleteButton, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr);
332
333
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override {
334
w = 500; h = 90;
335
}
336
337
void Draw(UIContext &dc) override {
338
dc.FillRect(dc.GetTheme().itemStyle.background, bounds_);
339
if (*value_ == entry_.host) {
340
// TODO: Make this highlight themable
341
dc.FillRect(UI::Drawable(0x48FFFFFF), GetBounds());
342
}
343
LinearLayout::Draw(dc);
344
}
345
346
bool Touch(const TouchInput &input) override {
347
using namespace UI;
348
if (UI::LinearLayout::Touch(input)) {
349
return true;
350
}
351
if (input.flags & TouchInputFlags::DOWN) {
352
if (bounds_.Contains(input.x, input.y)) {
353
dragging_ = true;
354
return true;
355
}
356
}
357
if (dragging_ && (input.flags & TouchInputFlags::UP)) {
358
dragging_ = false;
359
if (!(input.flags & TouchInputFlags::CANCEL) && bounds_.Contains(input.x, input.y)) {
360
EventParams e;
361
e.v = this;
362
OnSelected.Trigger(e);
363
return true;
364
}
365
}
366
return false;
367
}
368
369
UI::Event OnSelected;
370
371
private:
372
bool dragging_ = false;
373
std::string *value_;
374
AdhocServerListEntry entry_;
375
};
376
377
AdhocServerRow::AdhocServerRow(std::string *editValue, const AdhocServerListEntry &entry, bool showDeleteButton, ScreenManager *screenManager, UI::LayoutParams *layoutParams)
378
: UI::LinearLayout(ORIENT_HORIZONTAL, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::WRAP_CONTENT, UI::Margins(5.0f, 0.0f))), value_(editValue), entry_(entry) {
379
using namespace UI;
380
381
SetSpacing(5.0f);
382
// Show as radio button to make it really clear that selection actually is the choice.
383
int number = 0;
384
Add(new ImageView([editValue, host = entry.host]() { return host == *editValue ? ImageID("I_RADIO_SELECTED") : ImageID("I_RADIO_EMPTY"); },
385
new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity::G_VCENTER, Margins(5, 0, 0, 0))));
386
387
LinearLayout *lines = Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(Margins(5, 5))));
388
lines->SetSpacing(0.0f);
389
ClickableTextView *name = lines->Add(new ClickableTextView(entry.name));
390
name->SetFocusable(true);
391
name->OnClick.Add([this](UI::EventParams &e) {
392
EventParams e2;
393
e2.v = this;
394
OnSelected.Trigger(e2);
395
});
396
397
std::string secondLine = entry.host;
398
auto n = GetI18NCategory(I18NCat::NETWORKING);
399
if (entry.host == "localhost") {
400
// Special case this to add a hint.
401
secondLine = n->T("Ad hoc server address hint");
402
}
403
if (!entry.location.empty()) {
404
secondLine += ": " + entry.location;
405
}
406
407
lines->Add(new TextView(secondLine))->SetTextSize(TextSize::Small)->SetWordWrap();
408
409
Add(new Spacer(0.0f, new LinearLayoutParams(1.0f, Margins(0.0f, 5.0f))));
410
411
if (entry.mode == AdhocDataMode::AemuPostoffice) {
412
TextView *relay = Add(new TextView(n->T("Relay"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity::G_VCENTER, Margins(10.0))));
413
}
414
if (showDeleteButton) {
415
AddDeleteButton(editValue, screenManager, this, entry);
416
}
417
418
if (!entry.description.empty()) {
419
Choice *infoButton = Add(new Choice(ImageID("I_INFO"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity::G_VCENTER, Margins(0, 0, 10, 0))));
420
AdhocServerListEntry copy = entry;
421
infoButton->OnClick.Add([this, copy, screenManager](UI::EventParams &e) {
422
e.v = this;
423
screenManager->push(new AdhocServerInfoScreen(copy));
424
});
425
}
426
}
427
428
AdhocServerScreen::AdhocServerScreen(std::string *value, std::string_view title)
429
: UI::PopupScreen(title, T(I18NCat::DIALOG, "OK"), T(I18NCat::DIALOG, "Cancel")), value_(value) {
430
resolver_ = std::thread([](AdhocServerScreen *thiz) {
431
thiz->ResolverThread();
432
}, this);
433
editValue_ = *value;
434
AdhocLoadServerList(AdhocLoadListMode::AllSourcesAsync);
435
}
436
437
AdhocServerScreen::~AdhocServerScreen() {
438
{
439
std::unique_lock<std::mutex> guard(resolverLock_);
440
resolverState_ = ResolverState::QUIT;
441
resolverCond_.notify_one();
442
}
443
resolver_.join();
444
}
445
446
void AdhocServerScreen::sendMessage(UIMessage message, const char *value) {
447
if (message == UIMessage::ADHOC_SERVER_LIST_CHANGED) {
448
RecreateViews();
449
}
450
}
451
452
void AdhocServerScreen::CreatePopupContents(UI::ViewGroup *parent) {
453
using namespace UI;
454
auto sy = GetI18NCategory(I18NCat::SYSTEM);
455
auto di = GetI18NCategory(I18NCat::DIALOG);
456
auto n = GetI18NCategory(I18NCat::NETWORKING);
457
458
// We already kicked off loading the server list in the constructor, so by now it should be either loaded or in the process of loading.
459
// We can get the cached list immediately, and then update it when the async load finishes.
460
std::vector<AdhocServerListEntry> listItems = AdhocGetServerList(AdhocLoadListMode::CacheOnlySync);
461
462
Choice *addServer = parent->Add(new Choice(n->T("Add server"), ImageID("I_PLUS")));
463
addServer->OnClick.Add([this](UI::EventParams &e) {
464
AdhocAddServerPopupScreen *addScreen = new AdhocAddServerPopupScreen(&editValue_);
465
screenManager()->push(addScreen);
466
});
467
468
parent->Add(new Spacer(5.0f));
469
470
// editValue_ has the currently selected server. On closing the dialog, we copy that to settings.
471
472
// Start with the downloaded list.
473
std::vector<AdhocServerListEntry> entries = listItems;
474
std::vector<AdhocServerListEntry> localEntries;
475
std::vector<AdhocServerListEntry> customEntries;
476
477
bool currentServerFound = false; // If the current server is not found, we'll have to add it to one of the lists.
478
479
for (const auto &iter : entries) {
480
if (iter.host == editValue_) {
481
currentServerFound = true;
482
}
483
}
484
485
// Add localhost and local IPs, since those are common ones to connect to.
486
{
487
AdhocServerListEntry localhostEntry;
488
localhostEntry.name = "localhost";
489
localhostEntry.host = "localhost";
490
localEntries.push_back(localhostEntry);
491
492
std::vector<std::string> listIP;
493
net::GetLocalIP4List(listIP);
494
495
for (const auto &ipAddress : listIP) {
496
if (startsWith(ipAddress, "127.") || startsWith(ipAddress, "169.254.") || startsWith(ipAddress, "0.")) {
497
continue;
498
}
499
AdhocServerListEntry entry;
500
entry.name = ipAddress;
501
entry.host = ipAddress;
502
localEntries.push_back(entry);
503
504
if (ipAddress == editValue_) {
505
currentServerFound = true;
506
}
507
}
508
}
509
510
auto hostInEntries = [entries](const std::string &host) {
511
for (const auto &entry : entries) {
512
if (entry.host == host) {
513
return true;
514
}
515
}
516
return false;
517
};
518
519
for (auto iter = g_Config.vCustomAdhocServerListWithRelay.begin(); iter != g_Config.vCustomAdhocServerListWithRelay.end();) {
520
// If the host is already in the public list, skip it. We don't want duplicates.
521
if (hostInEntries(*iter) || iter->empty()) {
522
// Remove things that duplicate the public list, or that are empty (probably added by mistake).
523
iter = g_Config.vCustomAdhocServerListWithRelay.erase(iter);
524
recreateParent_ = true;
525
continue;
526
}
527
AdhocServerListEntry entry;
528
entry.name = *iter;
529
entry.host = *iter;
530
entry.mode = AdhocDataMode::AemuPostoffice;
531
customEntries.push_back(entry);
532
533
if (*iter == editValue_) {
534
currentServerFound = true;
535
}
536
iter++;
537
}
538
539
for (auto iter = g_Config.vCustomAdhocServerList.begin(); iter != g_Config.vCustomAdhocServerList.end();) {
540
// If the host is already in the public list, skip it. We don't want duplicates.
541
if (hostInEntries(*iter) || iter->empty()) {
542
// Remove things that duplicate the public list, or that are empty (probably added by mistake).
543
iter = g_Config.vCustomAdhocServerList.erase(iter);
544
recreateParent_ = true;
545
continue;
546
}
547
AdhocServerListEntry entry;
548
entry.name = *iter;
549
entry.host = *iter;
550
entry.mode = AdhocDataMode::P2P;
551
customEntries.push_back(entry);
552
553
if (*iter == editValue_) {
554
currentServerFound = true;
555
}
556
iter++;
557
}
558
559
ScrollView *scrollView = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0f));
560
LinearLayout *innerView = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
561
innerView->SetSpacing(5.0f);
562
563
auto AddButtonFromEntry = [this](UI::ViewGroup *parent, const AdhocServerListEntry &entry, bool showDeleteButton) {
564
AdhocServerRow *row = new AdhocServerRow(&editValue_, entry, showDeleteButton, screenManager());
565
parent->Add(row);
566
row->OnSelected.Add([this](UI::EventParams &e) {
567
std::string value = e.v->Tag();
568
if (!value.empty()) {
569
editValue_ = value;
570
}
571
});
572
row->SetTag(entry.host);
573
};
574
575
if (!customEntries.empty()) {
576
CollapsibleSection *customSection = innerView->Add(new CollapsibleSection(n->T("Custom server list"), new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
577
578
if (!currentServerFound) {
579
// Add the entry to one of the lists.
580
AdhocServerListEntry entry;
581
entry.name = editValue_;
582
entry.host = editValue_;
583
// Let's do a heuristic, we don't have a good value here..
584
entry.mode = (AdhocServerRelayMode)g_Config.iAdhocServerRelayMode == AdhocServerRelayMode::AlwaysOn ? AdhocDataMode::AemuPostoffice : AdhocDataMode::P2P;
585
if (entry.mode == AdhocDataMode::AemuPostoffice) {
586
g_Config.vCustomAdhocServerListWithRelay.insert(g_Config.vCustomAdhocServerListWithRelay.begin(), editValue_);
587
} else {
588
g_Config.vCustomAdhocServerList.insert(g_Config.vCustomAdhocServerList.begin(), editValue_);
589
}
590
recreateParent_ = true;
591
AddButtonFromEntry(customSection, entry, true);
592
}
593
594
for (const auto &entry : customEntries) {
595
AddButtonFromEntry(customSection, entry, true);
596
}
597
}
598
599
CollapsibleSection *publicSection = innerView->Add(new CollapsibleSection(n->T("Public server list"), new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
600
for (const auto &entry : entries) {
601
AddButtonFromEntry(publicSection, entry, false);
602
}
603
604
CollapsibleSection *localSection = innerView->Add(new CollapsibleSection(n->T("Local network addresses"), new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
605
for (const auto &entry : localEntries) {
606
AddButtonFromEntry(localSection, entry, false);
607
}
608
609
scrollView->Add(innerView);
610
parent->Add(scrollView);
611
612
progressView_ = parent->Add(new NoticeView(NoticeLevel::INFO, n->T("Validating address..."), "", new LinearLayoutParams(Margins(0, 5, 0, 0))));
613
progressView_->SetVisibility(UI::V_GONE);
614
}
615
616
void AdhocServerScreen::ResolverThread() {
617
std::unique_lock<std::mutex> guard(resolverLock_);
618
619
while (resolverState_ != ResolverState::QUIT) {
620
resolverCond_.wait(guard);
621
622
if (resolverState_ == ResolverState::QUEUED) {
623
resolverState_ = ResolverState::PROGRESS;
624
625
addrinfo *resolved = nullptr;
626
std::string err;
627
toResolveResult_ = net::DNSResolve(toResolve_, "80", &resolved, err);
628
if (resolved)
629
net::DNSResolveFree(resolved);
630
631
resolverState_ = ResolverState::READY;
632
}
633
}
634
}
635
636
bool AdhocServerScreen::CanComplete(DialogResult result) {
637
auto n = GetI18NCategory(I18NCat::NETWORKING);
638
639
if (result != DR_OK)
640
return true;
641
642
std::string value = editValue_;
643
if (lastResolved_ == value) {
644
return true;
645
}
646
647
// Currently running.
648
if (resolverState_ == ResolverState::PROGRESS)
649
return false;
650
651
std::lock_guard<std::mutex> guard(resolverLock_);
652
switch (resolverState_) {
653
case ResolverState::PROGRESS:
654
case ResolverState::QUIT:
655
return false;
656
657
case ResolverState::QUEUED:
658
case ResolverState::WAITING:
659
break;
660
661
case ResolverState::READY:
662
if (toResolve_ == value) {
663
// Reset the state, nothing there now.
664
resolverState_ = ResolverState::WAITING;
665
toResolve_.clear();
666
lastResolved_ = value;
667
lastResolvedResult_ = toResolveResult_;
668
669
if (lastResolvedResult_) {
670
progressView_->SetVisibility(UI::V_GONE);
671
} else {
672
progressView_->SetText(n->T("Invalid IP or hostname"));
673
progressView_->SetLevel(NoticeLevel::ERROR);
674
progressView_->SetVisibility(UI::V_VISIBLE);
675
}
676
return true;
677
}
678
679
// Throw away that last result, it was for a different value.
680
break;
681
}
682
683
resolverState_ = ResolverState::QUEUED;
684
toResolve_ = value;
685
resolverCond_.notify_one();
686
687
progressView_->SetText(n->T("Validating address..."));
688
progressView_->SetLevel(NoticeLevel::INFO);
689
progressView_->SetVisibility(UI::V_VISIBLE);
690
691
return false;
692
}
693
694
void AdhocServerScreen::OnCompleted(DialogResult result) {
695
if (result == DR_OK) {
696
*value_ = StripSpaces(editValue_);
697
}
698
}
699
700
bool AdhocServerNameIsCustom() {
701
for (auto iter : g_Config.vCustomAdhocServerList) {
702
if (iter == g_Config.sProAdhocServer) {
703
return true;
704
}
705
}
706
for (auto iter : g_Config.vCustomAdhocServerListWithRelay) {
707
if (iter == g_Config.sProAdhocServer) {
708
return true;
709
}
710
}
711
return false;
712
}
713
714
static void EditServerName(std::string_view newServerName) {
715
newServerName = StripSpaces(newServerName);
716
if (newServerName != g_Config.sProAdhocServer) {
717
for (auto &name : g_Config.vCustomAdhocServerList) {
718
if (name == g_Config.sProAdhocServer) {
719
name = newServerName;
720
}
721
}
722
for (auto &name : g_Config.vCustomAdhocServerListWithRelay) {
723
if (name == g_Config.sProAdhocServer) {
724
name = newServerName;
725
}
726
}
727
g_Config.sProAdhocServer = newServerName;
728
}
729
}
730
731
void AskToEditCurrentServer(int requestToken, ScreenManager *screenManager) {
732
using namespace UI;
733
auto n = GetI18NCategory(I18NCat::NETWORKING);
734
735
// Choose method depending on platform capabilities.
736
if (System_GetPropertyBool(SYSPROP_HAS_TEXT_INPUT_DIALOG)) {
737
System_InputBoxGetString(requestToken, n->T("Ad hoc server address"), g_Config.sProAdhocServer, false, [](const std::string &enteredValue, int) {
738
EditServerName(enteredValue);
739
});
740
return;
741
}
742
static std::string editText = g_Config.sProAdhocServer;
743
TextEditPopupScreen *popupScreen = new TextEditPopupScreen(&editText, editText, n->T("Ad hoc server address"), 256);
744
if (System_GetPropertyBool(SYSPROP_KEYBOARD_IS_SOFT)) {
745
popupScreen->SetAlignTop(true);
746
}
747
popupScreen->OnChange.Add([](EventParams &e) {
748
EditServerName(editText);
749
});
750
screenManager->push(popupScreen);
751
}
752
753