mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-07-11 01:25:07 +02:00
Add prototype Upload screen and a couple new icons. Some refactoring.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -433,6 +433,8 @@ bool System_GetPropertyBool(SystemProperty prop) {
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
case SYSPROP_HAS_ACCELEROMETER:
|
||||
return true; // for debugging
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 |
Reference in New Issue
Block a user