Add prototype Upload screen and a couple new icons. Some refactoring.

This commit is contained in:
Henrik Rydgård
2025-10-25 10:25:46 +02:00
parent f83d6ac4a9
commit c686d48268
24 changed files with 270 additions and 68 deletions
+2
View File
@@ -1605,6 +1605,8 @@ list(APPEND NativeAppSource
UI/SystemInfoScreen.cpp
UI/Store.h
UI/Store.cpp
UI/UploadScreen.h
UI/UploadScreen.cpp
UI/CwCheatScreen.h
UI/CwCheatScreen.cpp
UI/InstallZipScreen.h
+11 -3
View File
@@ -75,7 +75,8 @@ std::string GetLocalIP(int sock) {
} server_addr;
memset(&server_addr, 0, sizeof(server_addr));
socklen_t len = sizeof(server_addr);
if (getsockname(sock, (struct sockaddr *)&server_addr, &len) == 0) {
int retval = getsockname(sock, (struct sockaddr *)&server_addr, &len);
if (retval == 0) {
char temp[64]{};
// We clear the port below for WSAAddressToStringA.
@@ -94,17 +95,24 @@ std::string GetLocalIP(int sock) {
wchar_t wtemp[sizeof(temp)];
DWORD len = (DWORD)sizeof(temp);
// Windows XP doesn't support inet_ntop.
if (WSAAddressToStringW((struct sockaddr *)&server_addr, sizeof(server_addr), nullptr, wtemp, &len) == 0) {
HRESULT result = WSAAddressToStringW((struct sockaddr *)&server_addr, sizeof(server_addr), nullptr, wtemp, &len);
if (result == 0) {
return ConvertWStringToUTF8(wtemp);
} else {
return "";
}
#else
const char *result = inet_ntop(server_addr.sa.sa_family, addr, temp, sizeof(temp));
if (result) {
return result;
} else {
return "";
}
#endif
} else {
WARN_LOG(Log::IO, "GetLocalIP: getsockname failed with error %d (%s)", retval, strerror(retval));
return "";
}
return "";
}
} // fd_util
+4
View File
@@ -47,6 +47,10 @@ const char *DNSTypeAsString(DNSType type) {
}
}
std::string Connection::GetLocalIpAsString() const {
return fd_util::GetLocalIP(this->sock());
}
bool Connection::Resolve(const char *host, int port, DNSType type) {
if ((intptr_t)sock_ != -1) {
ERROR_LOG(Log::IO, "Resolve: Already have a socket");
+2
View File
@@ -30,6 +30,8 @@ public:
// Only to be used for bring-up and debugging.
uintptr_t sock() const { return sock_; }
std::string GetLocalIpAsString() const;
protected:
// Store the remote host here, so we can send it along through HTTP/1.1 requests.
// TODO: Move to http::client?
+37 -35
View File
@@ -66,7 +66,7 @@ ServerRequest::ServerRequest(int fd)
header_.ParseHeaders(in_);
if (header_.ok) {
VERBOSE_LOG(Log::IO, "The request carried with it %i bytes", (int)header_.content_length);
VERBOSE_LOG(Log::HTTP, "The request carried with it %i bytes", (int)header_.content_length);
} else {
Close();
}
@@ -76,11 +76,11 @@ ServerRequest::~ServerRequest() {
Close();
if (!in_->Empty()) {
ERROR_LOG(Log::IO, "Input not empty - invalid request?");
ERROR_LOG(Log::HTTP, "Input not empty - invalid request?");
}
delete in_;
if (!out_->Empty()) {
WARN_LOG(Log::IO, "Output not empty - connection abort? (%s) (%d bytes)", this->header_.resource, (int)out_->BytesRemaining());
WARN_LOG(Log::HTTP, "Output not empty - connection abort? (%s) (%d bytes)", this->header_.resource, (int)out_->BytesRemaining());
}
delete out_;
}
@@ -170,8 +170,8 @@ bool Server::Listen(int port, const char *reason, net::DNSType type) {
}
bool Server::Listen4(int port, const char *reason) {
listener_ = socket(AF_INET, SOCK_STREAM, 0);
if (listener_ < 0)
listenerSock_ = socket(AF_INET, SOCK_STREAM, 0);
if (listenerSock_ < 0)
return false;
struct sockaddr_in server_addr;
@@ -182,42 +182,43 @@ bool Server::Listen4(int port, const char *reason) {
int opt = 1;
// Enable re-binding to avoid the pain when restarting the server quickly.
setsockopt(listener_, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
setsockopt(listenerSock_, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
if (bind(listener_, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
if (bind(listenerSock_, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
#if PPSSPP_PLATFORM(WINDOWS)
int err = WSAGetLastError();
#else
int err = errno;
#endif
ERROR_LOG(Log::IO, "%s: Failed to bind to port %d, error=%d - Bailing (ipv4)", reason, port, err);
closesocket(listener_);
ERROR_LOG(Log::HTTP, "%s: Failed to bind to port %d, error=%d - Bailing (ipv4)", reason, port, err);
closesocket(listenerSock_);
return false;
}
fd_util::SetNonBlocking(listener_, true);
fd_util::SetNonBlocking(listenerSock_, true);
// 1024 is the max number of queued requests.
if (listen(listener_, 1024) < 0) {
closesocket(listener_);
if (listen(listenerSock_, 1024) < 0) {
closesocket(listenerSock_);
return false;
}
socklen_t len = sizeof(server_addr);
if (getsockname(listener_, (struct sockaddr *)&server_addr, &len) == 0) {
if (getsockname(listenerSock_, (struct sockaddr *)&server_addr, &len) == 0) {
port = ntohs(server_addr.sin_port);
}
INFO_LOG(Log::IO, "HTTP server started on port %d: %s", port, reason);
port_ = port;
localAddress_ = fd_util::GetLocalIP(listenerSock_);
INFO_LOG(Log::HTTP, "HTTP IPv4 server started on port %d: %s (ip: %s)", port, reason, localAddress_.c_str());
port_ = port;
return true;
}
bool Server::Listen6(int port, bool ipv6_only, const char *reason) {
#if !PPSSPP_PLATFORM(SWITCH)
listener_ = socket(AF_INET6, SOCK_STREAM, 0);
if (listener_ < 0)
listenerSock_ = socket(AF_INET6, SOCK_STREAM, 0);
if (listenerSock_ < 0)
return false;
struct sockaddr_in6 server_addr;
@@ -228,54 +229,55 @@ bool Server::Listen6(int port, bool ipv6_only, const char *reason) {
int opt = 1;
// Enable re-binding to avoid the pain when restarting the server quickly.
setsockopt(listener_, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
setsockopt(listenerSock_, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
// Enable listening on IPv6 and IPv4?
opt = ipv6_only ? 1 : 0;
setsockopt(listener_, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&opt, sizeof(opt));
setsockopt(listenerSock_, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&opt, sizeof(opt));
if (bind(listener_, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
if (bind(listenerSock_, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
#if PPSSPP_PLATFORM(WINDOWS)
int err = WSAGetLastError();
#else
int err = errno;
#endif
ERROR_LOG(Log::IO, "%s: Failed to bind to port %d, error=%d - Bailing (ipv6)", reason, port, err);
closesocket(listener_);
ERROR_LOG(Log::HTTP, "%s: Failed to bind to port %d, error=%d - Bailing (ipv6)", reason, port, err);
closesocket(listenerSock_);
return false;
}
fd_util::SetNonBlocking(listener_, true);
fd_util::SetNonBlocking(listenerSock_, true);
// 1024 is the max number of queued requests.
if (listen(listener_, 1024) < 0) {
closesocket(listener_);
if (listen(listenerSock_, 1024) < 0) {
closesocket(listenerSock_);
return false;
}
socklen_t len = sizeof(server_addr);
if (getsockname(listener_, (struct sockaddr *)&server_addr, &len) == 0) {
if (getsockname(listenerSock_, (struct sockaddr *)&server_addr, &len) == 0) {
port = ntohs(server_addr.sin6_port);
}
INFO_LOG(Log::IO, "HTTP server started on port %d: %s", port, reason);
localAddress_ = fd_util::GetLocalIP(listenerSock_);
INFO_LOG(Log::HTTP, "HTTP IPv6 server started on port %d: %s (ip: %s)", port, reason, localAddress_.c_str());
port_ = port;
return true;
#else
ERROR_LOG(Log::HTTP, "IPv6 not supported on this platform.");
return false;
#endif
}
bool Server::RunSlice(double timeout) {
if (listener_ < 0 || port_ == 0) {
if (listenerSock_ < 0 || port_ == 0) {
return false;
}
if (timeout <= 0.0) {
timeout = 86400.0;
}
if (!fd_util::WaitUntilReady(listener_, timeout, false)) {
if (!fd_util::WaitUntilReady(listenerSock_, timeout, false)) {
return false;
}
@@ -287,13 +289,13 @@ bool Server::RunSlice(double timeout) {
#endif
} client_addr;
socklen_t client_addr_size = sizeof(client_addr);
int conn_fd = accept(listener_, &client_addr.sa, &client_addr_size);
int conn_fd = accept(listenerSock_, &client_addr.sa, &client_addr_size);
if (conn_fd >= 0) {
executor_->Run(std::bind(&Server::HandleConnection, this, conn_fd));
return true;
}
else {
ERROR_LOG(Log::IO, "socket accept failed: %i", conn_fd);
ERROR_LOG(Log::HTTP, "socket accept failed: %i", conn_fd);
return false;
}
}
@@ -312,13 +314,13 @@ bool Server::Run(int port) {
}
void Server::Stop() {
closesocket(listener_);
closesocket(listenerSock_);
}
void Server::HandleConnection(int conn_fd) {
ServerRequest request(conn_fd);
if (!request.IsOK()) {
WARN_LOG(Log::IO, "Bad request, ignoring.");
WARN_LOG(Log::HTTP, "Bad request, ignoring.");
return;
}
HandleRequest(request);
@@ -350,7 +352,7 @@ void Server::HandleRequestDefault(const ServerRequest &request) {
}
void Server::Handle404(const ServerRequest &request) {
INFO_LOG(Log::IO, "No handler for '%.*s', falling back to 404.", (int)request.resource().size(), request.resource().data());
INFO_LOG(Log::HTTP, "No handler for '%.*s', falling back to 404.", (int)request.resource().size(), request.resource().data());
const char *payload = "<html><body>404 not found</body></html>\r\n";
request.WriteHttpResponseHeader("1.0", 404, strlen(payload));
request.Out()->Push(payload);
+9 -2
View File
@@ -96,9 +96,15 @@ public:
// if they don't recognize the url.
virtual void HandleRequest(const ServerRequest &request);
int Port() {
int ListenerSocket() const {
return listenerSock_;
}
int Port() const {
return port_;
}
const std::string &LocalAddress() const {
return localAddress_;
}
private:
bool Listen6(int port, bool ipv6_only, const char *reason);
@@ -113,8 +119,9 @@ private:
void HandleListing(const ServerRequest &request);
void Handle404(const ServerRequest &request);
int listener_;
int listenerSock_;
int port_ = 0;
std::string localAddress_;
UrlHandlerMap handlers_;
UrlHandlerFunc fallback_;
+7 -7
View File
@@ -113,7 +113,7 @@ void DNSResolveFree(addrinfo *res)
freeaddrinfo(res);
}
bool GetIPList(std::vector<std::string> &IP4s) {
bool GetLocalIP4List(std::vector<std::string> &IP4s) {
char ipstr[INET6_ADDRSTRLEN]; // We use IPv6 length since it's longer than IPv4
// getifaddrs first appeared in glibc 2.3, On Android officially supported since __ANDROID_API__ >= 24
#if defined(_IFADDRS_H_) || (__GLIBC__ > 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3) || (__ANDROID_API__ >= 24)
@@ -145,7 +145,7 @@ bool GetIPList(std::vector<std::string> &IP4s) {
return true;
}
#elif defined(SIOCGIFCONF) // Better detection on Linux/UNIX/MacOS/some Android
INFO_LOG(Log::sceNet, "GetIPList from SIOCGIFCONF");
INFO_LOG(Log::IO, "GetIPList from SIOCGIFCONF");
static struct ifreq ifreqs[32];
struct ifconf ifc{};
ifc.ifc_req = ifreqs;
@@ -153,13 +153,13 @@ bool GetIPList(std::vector<std::string> &IP4s) {
int sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
ERROR_LOG(Log::sceNet, "GetIPList failed to create socket (result = %i, errno = %i)", sd, socket_errno);
ERROR_LOG(Log::IO, "GetIPList failed to create socket (result = %i, errno = %i)", sd, socket_errno);
return false;
}
int r = ioctl(sd, SIOCGIFCONF, (char*)&ifc);
if (r != 0) {
ERROR_LOG(Log::sceNet, "GetIPList failed ioctl/SIOCGIFCONF (result = %i, errno = %i)", r, socket_errno);
ERROR_LOG(Log::IO, "GetIPList failed ioctl/SIOCGIFCONF (result = %i, errno = %i)", r, socket_errno);
return false;
}
@@ -175,7 +175,7 @@ bool GetIPList(std::vector<std::string> &IP4s) {
r = ioctl(sd, SIOCGIFADDR, item);
if (r != 0)
{
ERROR_LOG(Log::sceNet, "GetIPList failed ioctl/SIOCGIFADDR (i = %i, result = %i, errno = %i)", i, r, socket_errno);
ERROR_LOG(Log::IO, "GetIPList failed ioctl/SIOCGIFADDR (i = %i, result = %i, errno = %i)", i, r, socket_errno);
}
if (ifreqs[i].ifr_addr.sa_family == AF_INET) {
@@ -194,8 +194,8 @@ bool GetIPList(std::vector<std::string> &IP4s) {
close(sd);
return true;
#else // Fallback to POSIX/Cross-platform way but may not works well on Linux (ie. only shows 127.0.0.1)
INFO_LOG(Log::sceNet, "GetIPList from Fallback");
#else // Fallback to POSIX/Cross-platform way but may not work well on Linux (ie. only shows 127.0.0.1)
DEBUG_LOG(Log::IO, "GetIPList from fallback method");
struct addrinfo hints, * res, * p;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
+1 -1
View File
@@ -20,7 +20,7 @@ enum class DNSType {
bool DNSResolve(const std::string &host, const std::string &service, addrinfo **res, std::string &error, DNSType type = DNSType::ANY);
void DNSResolveFree(addrinfo *res);
bool GetIPList(std::vector<std::string>& IP4s);
bool GetLocalIP4List(std::vector<std::string>& IP4s);
int inet_pton(int af, const char* src, void* dst);
+38 -5
View File
@@ -73,7 +73,7 @@ static ServerStatus RetrieveStatus() {
}
// This reports the local IP address to report.ppsspp.org, which can then
// relay that address to a mobile device searching for the server.
// relay that address to a mobile device on the same wifi/LAN searching for the server.
static bool RegisterServer(int port) {
bool success = false;
http::Client http(nullptr);
@@ -86,7 +86,7 @@ static bool RegisterServer(int port) {
char resource4[1024]{};
if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT, net::DNSType::IPV4)) {
if (http.Connect()) {
std::string ip = fd_util::GetLocalIP(http.sock());
std::string ip = http.GetLocalIpAsString();
snprintf(resource4, sizeof(resource4) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port);
if (http.GET(http::RequestParams(resource4), &theVoid, &progress) > 0)
@@ -111,7 +111,7 @@ static bool RegisterServer(int port) {
// Currently, we're not using keepalive, so gotta reconnect...
if (http.Connect(timeout)) {
char resource6[1024] = {};
std::string ip = fd_util::GetLocalIP(http.sock());
std::string ip = http.GetLocalIpAsString();
snprintf(resource6, sizeof(resource6) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port);
if (http.GET(http::RequestParams(resource6), &theVoid, &progress) > 0)
@@ -388,6 +388,7 @@ static void HandleFallback(const http::ServerRequest &request) {
static void ForwardDebuggerRequest(const http::ServerRequest &request) {
SetCurrentThreadName("ForwardDebuggerRequest");
// Hm, is this needed?
AndroidJNIThreadContext jniContext;
if (serverFlags & (int)WebServerFlags::DEBUGGER) {
@@ -408,7 +409,26 @@ static void ForwardDebuggerRequest(const http::ServerRequest &request) {
}
}
static void ExecuteWebServer() {
static void HandleUploadUI(const http::ServerRequest &request) {
// Read the file from VFS.
AndroidJNIThreadContext jniContext;
request.WriteHttpResponseHeader("1.0", 200, -1, "text/html");
request.Out()->Push(
"<html><head><title>PPSSPP Remote ISO Upload</title></head><body>"
"<h1>Upload ISO File</h1>"
"<form method=\"POST\" enctype=\"multipart/form-data\">"
"<input type=\"file\" name=\"isofile\" accept=\".iso,.cso,.pbp,.chd\" required>"
"<input type=\"submit\" value=\"Upload\">"
"</form>"
"</body></html>");
}
static void HandleUploadPost(const http::ServerRequest &request) {
AndroidJNIThreadContext jniContext;
}
static void WebServerThread() {
SetCurrentThreadName("HTTPServer");
AndroidJNIThreadContext context; // Destructor detaches.
@@ -418,6 +438,8 @@ static void ExecuteWebServer() {
// This lists all the (current) recent ISOs. It also handles the debugger, which is very ugly.
http->SetFallbackHandler(&HandleFallback);
http->RegisterHandler("/debugger", &ForwardDebuggerRequest);
http->RegisterHandler("/upload", &HandleUploadUI);
http->RegisterHandler("/upload_post", &HandleUploadPost);
if (!http->Listen(g_Config.iRemoteISOPort, "debugger-webserver")) {
if (!http->Listen(0, "debugger-webserver")) {
@@ -426,11 +448,14 @@ static void ExecuteWebServer() {
return;
}
}
UpdateStatus(ServerStatus::RUNNING);
g_Config.iRemoteISOPort = http->Port();
RegisterServer(http->Port());
double lastRegister = time_now_d();
INFO_LOG(Log::HTTP, "Entering web server loop. Listening on port %d", g_Config.iRemoteISOPort);
while (RetrieveStatus() == ServerStatus::RUNNING) {
constexpr double webServerSliceSeconds = 0.2f;
http->RunSlice(webServerSliceSeconds);
@@ -440,6 +465,7 @@ static void ExecuteWebServer() {
lastRegister = now;
}
}
INFO_LOG(Log::HTTP, "Leaving web server loop.");
http->Stop();
StopAllDebuggers();
@@ -448,11 +474,13 @@ static void ExecuteWebServer() {
UpdateStatus(ServerStatus::FINISHED);
}
// Only adds flags.
bool StartWebServer(WebServerFlags flags) {
std::lock_guard<std::mutex> guard(serverStatusLock);
switch (serverStatus) {
case ServerStatus::RUNNING:
if ((serverFlags & (int)flags) == (int)flags) {
// Already running with these flags.
return false;
}
serverFlags |= (int)flags;
@@ -465,7 +493,7 @@ bool StartWebServer(WebServerFlags flags) {
case ServerStatus::STOPPED:
serverStatus = ServerStatus::STARTING;
serverFlags = (int)flags;
serverThread = std::thread(&ExecuteWebServer);
serverThread = std::thread(&WebServerThread);
return true;
default:
@@ -473,6 +501,7 @@ bool StartWebServer(WebServerFlags flags) {
}
}
// Only removes flags.
bool StopWebServer(WebServerFlags flags) {
std::lock_guard<std::mutex> guard(serverStatusLock);
if (serverStatus != ServerStatus::RUNNING) {
@@ -510,3 +539,7 @@ void ShutdownWebServer() {
bool WebServerRunning(WebServerFlags flags) {
return RetrieveStatus() == ServerStatus::RUNNING && (serverFlags & (int)flags) != 0;
}
int WebServerPort() {
return g_Config.iRemoteISOPort;
}
+3 -1
View File
@@ -23,8 +23,9 @@ enum class WebServerFlags {
NONE = 0,
DISCS = 1,
DEBUGGER = 2,
FILE_UPLOAD = 4,
ALL = 1 | 2,
ALL = 1 | 2 | 4,
};
ENUM_CLASS_BITOPS(WebServerFlags);
@@ -36,3 +37,4 @@ bool WebServerRunning(WebServerFlags flags);
void ShutdownWebServer();
bool RemoteISOFileSupported(const std::string &filename);
int WebServerPort();
+3
View File
@@ -496,6 +496,9 @@ void ShaderViewScreen::CreateViews() {
LinearLayout *topbar = new LinearLayout(ORIENT_HORIZONTAL);
topbar->Add(new Choice(ImageID("I_NAVIGATE_BACK"), new LinearLayoutParams()))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
topbar->Add(new Choice(ImageID("I_FILE_COPY"), new LinearLayoutParams()))->OnClick.Add([this](UI::EventParams &e) {
System_CopyStringToClipboard(gpu->DebugGetShaderString(id_, type_, SHADER_STRING_SHORT_DESC));
});
topbar->Add(new TextView(gpu->DebugGetShaderString(id_, type_, SHADER_STRING_SHORT_DESC), FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT, false));
layout->Add(topbar);
+1 -1
View File
@@ -1874,7 +1874,7 @@ void HostnameSelectScreen::CreatePopupContents(UI::ViewGroup *parent) {
}
// Add non-editable items
listIP.push_back("localhost");
net::GetIPList(listIP);
net::GetLocalIP4List(listIP);
ipRows_ = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0));
ScrollView* scrollView = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
+7
View File
@@ -62,6 +62,7 @@
#include "UI/DisplayLayoutScreen.h"
#include "UI/SavedataScreen.h"
#include "UI/Store.h"
#include "UI/UploadScreen.h"
#include "UI/InstallZipScreen.h"
#include "Core/Config.h"
#include "Core/Loaders.h"
@@ -815,6 +816,12 @@ void GameBrowser::Refresh() {
topBar->Add(new Choice(mm->T("PPSSPP Homebrew Store"), new UI::LinearLayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::OnHomebrewStore);
}
if (browseFlags_ & BrowseFlags::UPLOAD_BUTTON) {
topBar->Add(new Choice(ImageID("I_FOLDER_UPLOAD"), new UI::LinearLayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Add([this](UI::EventParams &e) {
screenManager_->push(new UploadScreen());
});
}
ChoiceStrip *layoutChoice = topBar->Add(new ChoiceStrip(ORIENT_HORIZONTAL));
layoutChoice->AddChoice(ImageID("I_GRID"));
layoutChoice->AddChoice(ImageID("I_LINES"));
+2 -1
View File
@@ -38,7 +38,8 @@ enum class BrowseFlags {
ARCHIVES = 4,
PIN = 8,
HOMEBREW_STORE = 16,
STANDARD = 1 | 2 | 4 | 8,
UPLOAD_BUTTON = 32,
STANDARD = 1 | 2 | 4 | 8 | 32,
};
ENUM_CLASS_BITOPS(BrowseFlags);
+2
View File
@@ -75,6 +75,7 @@
<ClCompile Include="InstallZipScreen.cpp" />
<ClCompile Include="Theme.cpp" />
<ClCompile Include="UIAtlas.cpp" />
<ClCompile Include="UploadScreen.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="AudioCommon.h" />
@@ -124,6 +125,7 @@
<ClInclude Include="InstallZipScreen.h" />
<ClInclude Include="Theme.h" />
<ClInclude Include="UIAtlas.h" />
<ClInclude Include="UploadScreen.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.vcxproj">
+6
View File
@@ -129,6 +129,9 @@
<ClCompile Include="ImDebugger\ImJitViewer.cpp">
<Filter>ImDebugger</Filter>
</ClCompile>
<ClCompile Include="UploadScreen.cpp">
<Filter>Screens</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="GameInfoCache.h" />
@@ -258,6 +261,9 @@
<ClInclude Include="ImDebugger\ImJitViewer.h">
<Filter>ImDebugger</Filter>
</ClInclude>
<ClInclude Include="UploadScreen.h">
<Filter>Screens</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Screens">
+3
View File
@@ -138,6 +138,9 @@ static const ImageMeta imageIDs[] = {
{"I_SETTINGS_DISPLAY", false},
{"I_NAVIGATE_BACK", false},
{"I_NAVIGATE_FORWARD", false},
{"I_FOLDER_UPLOAD", false},
{"I_FILE", false},
{"I_FILE_COPY", false},
};
static std::string PNGNameFromID(std::string_view id) {
+52
View File
@@ -0,0 +1,52 @@
#include "Common/Net/Resolve.h"
#include "Common/System/Request.h"
#include "UI/UploadScreen.h"
#include "Common/StringUtils.h"
#include "Core/WebServer.h"
UploadScreen::UploadScreen() {
net::GetLocalIP4List(localIPs_);
StartWebServer(WebServerFlags::FILE_UPLOAD);
}
UploadScreen::~UploadScreen() {
StopWebServer(WebServerFlags::FILE_UPLOAD);
}
void UploadScreen::CreateViews() {
using namespace UI;
auto co = GetI18NCategory(I18NCat::NETWORKING);
root_ = new LinearLayout(ORIENT_VERTICAL);
LinearLayout *topBar = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
topBar->Add(new Choice(ImageID("I_NAVIGATE_BACK"), new LinearLayoutParams()))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
root_->Add(topBar);
if (prevRunning_) {
root_->Add(new TextView(co->T("On your other device, connect to the same network, then go to this URL:")));
for (const auto &ip : localIPs_) {
std::string url = StringFromFormat("http://%s:%d/upload", ip.c_str(), WebServerPort());
root_->Add(new TextView(url));
root_->Add(new Choice(ImageID("I_FILE_COPY")))->OnClick.Add([url](UI::EventParams &) {
System_CopyStringToClipboard(url);
});
}
} else {
root_->Add(new TextView(co->T("Connecting...")));
}
//infoText->SetTextAlignment(TEXT_ALIGN_CENTER);
//verticalLayout->AddView(infoText);
//AddStandardBack(verticalLayout);
//root_->AddView(verticalLayout);
}
void UploadScreen::update() {
UIScreen::update();
bool running = WebServerRunning(WebServerFlags::FILE_UPLOAD);
if (prevRunning_ != running) {
prevRunning_ = running;
RecreateViews();
}
}
+47
View File
@@ -0,0 +1,47 @@
#pragma once
// Copyright (c) 2013- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include <functional>
#include <string>
#include <vector>
#include "Common/UI/UIScreen.h"
#include "Common/UI/ViewGroup.h"
#include "UI/MiscScreens.h"
// Upload screen: Shows the user an ip address to go to in a web browser on the same network,
// in order to upload game files to the current directory.
class UploadScreen : public UIDialogScreenWithBackground {
public:
UploadScreen();
~UploadScreen();
void CreateViews() override;
protected:
void update() override;
const char *tag() const override { return "Upload"; }
private:
bool prevRunning_ = false;
std::vector<std::string> localIPs_;
};
+2
View File
@@ -132,6 +132,7 @@
<ClInclude Include="..\..\UI\TouchControlLayoutScreen.h" />
<ClInclude Include="..\..\UI\TouchControlVisibilityScreen.h" />
<ClInclude Include="..\..\UI\UIAtlas.h" />
<ClInclude Include="..\..\UI\UploadScreen.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
@@ -184,6 +185,7 @@
<ClCompile Include="..\..\UI\TouchControlLayoutScreen.cpp" />
<ClCompile Include="..\..\UI\TouchControlVisibilityScreen.cpp" />
<ClCompile Include="..\..\UI\UIAtlas.cpp" />
<ClCompile Include="..\..\UI\UploadScreen.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
+6
View File
@@ -124,6 +124,9 @@
<ClCompile Include="..\..\UI\TouchControlVisibilityScreen.cpp">
<Filter>Screens</Filter>
</ClCompile>
<ClCompile Include="..\..\UI\UploadScreen.cpp">
<Filter>Screens</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -249,6 +252,9 @@
<ClInclude Include="..\..\UI\TouchControlVisibilityScreen.h">
<Filter>Screens</Filter>
</ClInclude>
<ClInclude Include="..\..\UI\UploadScreen.h">
<Filter>Screens</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="ImDebugger">
+2
View File
@@ -433,6 +433,8 @@ bool System_GetPropertyBool(SystemProperty prop) {
#else
return false;
#endif
case SYSPROP_HAS_ACCELEROMETER:
return true; // for debugging
default:
return false;
}
+1
View File
@@ -922,6 +922,7 @@ LOCAL_SRC_FILES := \
$(SRC)/UI/JoystickHistoryView.cpp \
$(SRC)/UI/GameInfoCache.cpp \
$(SRC)/UI/GameScreen.cpp \
$(SRC)/UI/UploadScreen.cpp \
$(SRC)/UI/ControlMappingScreen.cpp \
$(SRC)/UI/GameSettingsScreen.cpp \
$(SRC)/UI/DeveloperToolsScreen.cpp \
+22 -12
View File
@@ -24,15 +24,15 @@
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="2.8284272"
inkscape:cx="322.97101"
inkscape:cy="372.46849"
inkscape:zoom="8.0000002"
inkscape:cx="297.18749"
inkscape:cy="545.18749"
inkscape:window-width="3379"
inkscape:window-height="1941"
inkscape:window-x="-9"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg1"
inkscape:current-layer="layer1"
showgrid="false" /><defs
id="defs1"><inkscape:path-effect
effect="fillet_chamfer"
@@ -3083,7 +3083,7 @@
id="I_HOME"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
transform="matrix(0.26458333,0,0,0.26458333,5.0473301,79.356161)"><path
transform="matrix(0.26458333,0,0,0.26458333,-5.9908997,79.356161)"><path
style="color:#000000;fill:#ffffff;-inkscape-stroke:none"
d="m 154.41016,251.21289 h -3.10743 v 16.09766 h -19.04687 v -16.09766 h -2.42969 c 0,0 11.30045,-11.07641 12.1072,-11.88316 z m -12.47434,-15.43751 -18.13699,17.93751 h 5.95703 v 16.09766 h 24.04687 v -16.09766 h 6.21094 z"
id="path5204"
@@ -3187,7 +3187,7 @@
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" /></g><g
id="I_SPEAKER_MAX"
transform="matrix(0.44009307,0,0,0.44009307,-62.491119,1.9213872)"
transform="matrix(0.44009307,0,0,0.44009307,-76.896945,1.9213872)"
inkscape:export-xdpi="135"
inkscape:export-ydpi="135"><g
transform="matrix(1.1742907,0,0,1.1742907,225.4342,162.52437)"
@@ -3242,14 +3242,14 @@
inkscape:export-xdpi="135"
inkscape:export-filename="speaker.png"
id="I_SPEAKER"
transform="matrix(0.51679723,0,0,0.51679723,64.418875,73.447233)"><path
transform="matrix(0.51679723,0,0,0.51679723,50.013049,73.447233)"><path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;fill-opacity:0.670213;fill-rule:nonzero;stroke:#ffffff;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
d="m 40.140311,135.92727 h 4.054542 l 3.801958,-2.99163 -0.04448,15.5982 -3.757475,-3.39096 h -4.054542 z"
id="path2639"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" /></g><g
id="I_SPEAKER_OFF"
transform="matrix(0.44009307,0,0,0.44009307,-63.536334,1.7642172)"
transform="matrix(0.44009307,0,0,0.44009307,-77.94216,1.7642172)"
inkscape:export-xdpi="135"
inkscape:export-ydpi="135"><g
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
@@ -3267,7 +3267,7 @@
id="rect2643"
d="m 319.32217,324.08362 -0.31055,0.31055 3.69727,3.69726 -3.61719,3.61719 0.31055,0.31055 3.61718,-3.61719 3.61524,3.61719 0.31055,-0.31055 -3.61524,-3.61719 3.69727,-3.69726 -0.31055,-0.31055 -3.69727,3.69727 z"
style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.35233;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none" /></g><g
transform="matrix(0.39414007,0,0,0.39414007,-45.596736,29.857907)"
transform="matrix(0.39414007,0,0,0.39414007,-51.396484,29.857907)"
id="I_WARNING"
inkscape:export-xdpi="135"
inkscape:export-ydpi="135"><path
@@ -3292,7 +3292,7 @@
id="I_TRASHCAN"
inkscape:export-xdpi="135"
inkscape:export-ydpi="135"
transform="matrix(0.39414007,0,0,0.39414007,-38.179785,14.443237)"><rect
transform="matrix(0.39414007,0,0,0.39414007,-46.785863,14.443237)"><rect
style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
id="rect5029"
width="1.6071385"
@@ -3317,7 +3317,7 @@
transform="matrix(0.93415882,0,0,0.87040224,11.368845,44.781213)"
sodipodi:nodetypes="ccccccccccccccccccccccc" /></g><g
id="I_THREE_DOTS"
transform="matrix(0.42098793,0,0,0.42098793,-52.570448,27.877317)"
transform="matrix(0.42098793,0,0,0.42098793,-65.292476,27.877317)"
inkscape:export-xdpi="135"
inkscape:export-ydpi="135"><path
inkscape:export-ydpi="135"
@@ -4197,7 +4197,17 @@
style="color:#000000;fill:#ececec;stroke-width:0.788011;stroke-miterlimit:4.8;-inkscape-stroke:none"
d="m 77.896652,80.722753 2.657871,2.704643 -2.552633,2.645661 -0.727756,-0.714513 1.41025,-1.409734 h -3.050491 c -1.344769,0 -2.424142,1.079372 -2.424142,2.424142 v 0.304891 c 0,1.27712 1.024032,2.300634 2.301153,2.300634 h 3.379638 v 1.042314 h -3.379638 c -1.840515,0 -3.342949,-1.502434 -3.342949,-3.342948 v -0.304891 c 0,-1.908164 1.557775,-3.467489 3.465938,-3.467489 h 3.049974 l -1.444812,-1.491584 z"
sodipodi:nodetypes="cccccssssccssssccc" />
</g><path
<path
id="I_FOLDER_UPLOAD"
style="baseline-shift:baseline;display:inline;overflow:visible;opacity:1;vector-effect:none;fill:#ffffff;marker:none;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 86.729062,77.137918 -1.122929,2.205033 v 7.861758 h 3.798734 v -0.586011 h -3.21324 v -7.139322 l 0.894519,-1.757515 h 3.847827 l 0.834057,1.704805 h 4.701005 l 0.101802,0.06821 v 7.123819 h -3.166731 v 0.586011 h 3.750675 v -8.016271 l -0.503845,-0.347782 h -4.517553 l -0.836125,-1.702738 z m 4.603853,3.505211 -2.634981,2.80293 2.04587,0.01912 v 4.252363 h 1.193208 v -4.252363 l 2.031917,-0.01912 z"
sodipodi:nodetypes="ccccccccccccccccccccccccccccc" /><path
id="I_FILE"
style="color:#000000;fill:#ffffff;stroke-miterlimit:4.8;-inkscape-stroke:none"
d="m 81.915403,142.76852 v 10.86136 h 8.347293 v -7.85741 l -2.925402,-3.00395 z m 0.740007,0.74208 h 4.109309 v 2.89284 h 2.757971 v 6.48643 h -6.86728 z m 4.849832,0.49299 1.615922,1.65778 h -1.615922 z m -3.549137,3.50521 -0.0036,0.74208 4.325834,0.0238 0.0041,-0.74259 z m 0,1.63659 -0.0036,0.7426 4.325834,0.0232 0.0041,-0.74207 z m 0,1.70739 -0.0057,0.74208 3.087666,0.0232 0.0062,-0.74207 z" /><path
id="I_FILE_COPY"
style="color:#000000;fill:#ffffff;stroke-width:0.898414;stroke-miterlimit:4.8;-inkscape-stroke:none"
d="M 96.308838 142.38664 L 96.308838 144.03563 L 94.392159 144.03563 L 94.392159 153.79369 L 101.89144 153.79369 L 101.89144 152.1447 L 103.80812 152.1447 L 103.80812 145.08518 L 101.17986 142.38664 L 96.308838 142.38664 z M 96.973914 143.05327 L 100.66568 143.05327 L 100.66568 145.65207 L 103.14356 145.65207 L 103.14356 151.47962 L 96.973914 151.47962 L 96.973914 143.05327 z M 101.33075 143.49613 L 102.78286 144.98545 L 101.33075 144.98545 L 101.33075 143.49613 z M 95.057235 144.70226 L 96.308838 144.70226 L 96.308838 152.1447 L 101.22688 152.1447 L 101.22688 153.12862 L 95.057235 153.12862 L 95.057235 144.70226 z M 98.142318 146.64529 L 98.1387 147.31192 L 102.02528 147.33311 L 102.02942 146.66596 L 98.142318 146.64529 z M 98.142318 148.11549 L 98.1387 148.78263 L 102.02528 148.80382 L 102.02942 148.13719 L 98.142318 148.11549 z M 98.142318 149.64976 L 98.13715 150.31639 L 100.91114 150.33706 L 100.91579 149.67043 L 98.142318 149.64976 z " /></g><path
id="I_GEAR"
style="fill:#ececec;fill-opacity:1;stroke-width:0.019487"
class="st0"

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 204 KiB