diff --git a/CMakeLists.txt b/CMakeLists.txt index 7efddc6b3b..3b95915f1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/Common/File/FileDescriptor.cpp b/Common/File/FileDescriptor.cpp index 0da3b9e09f..61fa4a73e8 100644 --- a/Common/File/FileDescriptor.cpp +++ b/Common/File/FileDescriptor.cpp @@ -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 diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index e17565f2c0..a9aa042bb6 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -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"); diff --git a/Common/Net/HTTPClient.h b/Common/Net/HTTPClient.h index c8a147db56..2ecc1d1d16 100644 --- a/Common/Net/HTTPClient.h +++ b/Common/Net/HTTPClient.h @@ -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? diff --git a/Common/Net/HTTPServer.cpp b/Common/Net/HTTPServer.cpp index 34bc87a7a0..c69aae0125 100644 --- a/Common/Net/HTTPServer.cpp +++ b/Common/Net/HTTPServer.cpp @@ -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 = "404 not found\r\n"; request.WriteHttpResponseHeader("1.0", 404, strlen(payload)); request.Out()->Push(payload); diff --git a/Common/Net/HTTPServer.h b/Common/Net/HTTPServer.h index 4f117069ae..bc2b2bc6e7 100644 --- a/Common/Net/HTTPServer.h +++ b/Common/Net/HTTPServer.h @@ -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_; diff --git a/Common/Net/Resolve.cpp b/Common/Net/Resolve.cpp index eb3727136d..23d3bf989d 100644 --- a/Common/Net/Resolve.cpp +++ b/Common/Net/Resolve.cpp @@ -113,7 +113,7 @@ void DNSResolveFree(addrinfo *res) freeaddrinfo(res); } -bool GetIPList(std::vector &IP4s) { +bool GetLocalIP4List(std::vector &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 &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 &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 &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 &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 diff --git a/Common/Net/Resolve.h b/Common/Net/Resolve.h index 5c7958d654..09c2ab168c 100644 --- a/Common/Net/Resolve.h +++ b/Common/Net/Resolve.h @@ -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& IP4s); +bool GetLocalIP4List(std::vector& IP4s); int inet_pton(int af, const char* src, void* dst); diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index 710479c98e..0fde0fe922 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -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( + "PPSSPP Remote ISO Upload" + "

Upload ISO File

" + "
" + "" + "" + "
" + ""); +} + +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 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 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; +} diff --git a/Core/WebServer.h b/Core/WebServer.h index cd0d2cc3ec..26d2f64c71 100644 --- a/Core/WebServer.h +++ b/Core/WebServer.h @@ -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(); diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 19c5737f72..956916330e 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -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(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); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index fbb440029b..dc79bd3d4c 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -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)); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 39d155c46d..e4d0da1bac 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -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")); diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 40da0b1945..358c60fca2 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -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); diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index 42ad2df168..551778b5e7 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -75,6 +75,7 @@ + @@ -124,6 +125,7 @@ + diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index 3d8693dd36..ecbd604bcb 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -129,6 +129,9 @@ ImDebugger + + Screens + @@ -258,6 +261,9 @@ ImDebugger + + Screens + diff --git a/UI/UIAtlas.cpp b/UI/UIAtlas.cpp index 5da10ea751..673a084939 100644 --- a/UI/UIAtlas.cpp +++ b/UI/UIAtlas.cpp @@ -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) { diff --git a/UI/UploadScreen.cpp b/UI/UploadScreen.cpp new file mode 100644 index 0000000000..581e1ebe45 --- /dev/null +++ b/UI/UploadScreen.cpp @@ -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(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(); + } +} diff --git a/UI/UploadScreen.h b/UI/UploadScreen.h new file mode 100644 index 0000000000..4f4d0bb1cd --- /dev/null +++ b/UI/UploadScreen.h @@ -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 +#include +#include + +#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 localIPs_; +}; diff --git a/UWP/UI_UWP/UI_UWP.vcxproj b/UWP/UI_UWP/UI_UWP.vcxproj index 4d14b3fa83..a4f785d754 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj +++ b/UWP/UI_UWP/UI_UWP.vcxproj @@ -132,6 +132,7 @@ + @@ -184,6 +185,7 @@ + Create diff --git a/UWP/UI_UWP/UI_UWP.vcxproj.filters b/UWP/UI_UWP/UI_UWP.vcxproj.filters index f064528ae2..addb46f19c 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj.filters +++ b/UWP/UI_UWP/UI_UWP.vcxproj.filters @@ -124,6 +124,9 @@ Screens + + Screens + @@ -249,6 +252,9 @@ Screens + + Screens + diff --git a/Windows/main.cpp b/Windows/main.cpp index cf5ab99e4d..db8050dcea 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -433,6 +433,8 @@ bool System_GetPropertyBool(SystemProperty prop) { #else return false; #endif + case SYSPROP_HAS_ACCELEROMETER: + return true; // for debugging default: return false; } diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 519895951a..23c1f27f77 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -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 \ diff --git a/assets/ui_images/images.svg b/assets/ui_images/images.svg index 9675533ad5..1c0450333c 100644 --- a/assets/ui_images/images.svg +++ b/assets/ui_images/images.svg @@ -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" /> -