diff --git a/nui/src/nui/backend/gobject.hpp b/nui/src/nui/backend/gobject.hpp new file mode 100644 index 0000000..5ae0316 --- /dev/null +++ b/nui/src/nui/backend/gobject.hpp @@ -0,0 +1,207 @@ +#pragma once + +namespace Nui +{ + namespace Detail + { + template + T* referenceGObject(T* ptr) + { + if (ptr) + g_object_ref_sink(ptr); + return ptr; + } + + template + void unreferenceGObject(T* ptr) + { + if (ptr) + g_object_unref(ptr); + } + } + + template + class GObjectReference + { + public: + struct AdoptFlag + {}; + + using value_type = T; + using pointer_type = value_type*; + + GObjectReference() + : ptr_{nullptr} + {} + + explicit GObjectReference(pointer_type ptr) + : ptr_{ptr} + { + if (ptr_) + Detail::referenceGObject(ptr_); + } + + GObjectReference(GObjectReference const& other) + : ptr_{other.ptr_} + { + if (ptr_) + Detail::referenceGObject(ptr_); + } + + template + GObjectReference(GObjectReference const& other) + : ptr_{other.get()} + { + if (ptr_) + Detail::referenceGObject(ptr_); + } + + GObjectReference(GObjectReference&& other) + : ptr_{other.release()} + {} + + ~GObjectReference() + { + Detail::unreferenceGObject(ptr_); + } + + void clear() + { + using std::swap; + T* ptr = nullptr; + swap(ptr_, ptr); + if (ptr) + derefGPtr(ptr); + } + + pointer_type release() + { + pointer_type ptr = nullptr; + using std::swap; + swap(ptr_, ptr); + return ptr; + } + + T* get() const + { + return ptr_; + } + T& operator*() const + { + return *ptr_; + } + T* operator->() const + { + return ptr_; + } + + explicit operator bool() const + { + return ptr_ != nullptr; + } + bool operator!() const + { + return ptr_ == nullptr; + } + + GObjectReference& operator=(GObjectReference const& other) + { + pointer_type optr = other.get(); + if (optr) + Detail::referenceGObject(optr); + pointer_type ptr = ptr_; + ptr_ = optr; + if (ptr) + Detail::unreferenceGObject(ptr); + return *this; + } + GObjectReference& operator=(GObjectReference&& other) + { + GObjectReference(std::move(other)).swap(*this); + return *this; + } + GObjectReference& operator=(pointer_type optr) + { + pointer_type ptr = ptr_; + if (optr) + Detail::referenceGObject(optr); + ptr_ = optr; + if (ptr) + Detail::unreferenceGObject(ptr); + return *this; + } + template + GObjectReference& operator=(GObjectReference const& other) + { + GObjectReference(other).swap(*this); + return *this; + } + + void swap(GObjectReference& other) + { + using std::swap; + swap(ptr_, other.ptr_); + } + static GObjectReference adoptReference(T* ptr) + { + return GObjectReference(ptr, AdoptFlag{}); + } + + private: + GObjectReference(pointer_type ptr, AdoptFlag) + : ptr_{ptr} + {} + + private: + T* ptr_; + }; + + template + void swap(GObjectReference& lhs, GObjectReference& rhs) + { + lhs.swap(rhs); + } + + template + bool operator==(GObjectReference const& lhs, GObjectReference const& rhs) + { + return lhs.get() == rhs.get(); + } + template + bool operator==(GObjectReference const& lhs, T* rhs) + { + return lhs.get() == rhs; + } + template + bool operator==(T* lhs, GObjectReference const& rhs) + { + return lhs == rhs.get(); + } + + template + bool operator!=(GObjectReference const& lhs, GObjectReference const& rhs) + { + return lhs.get() != rhs.get(); + } + template + bool operator!=(GObjectReference const& lhs, T* rhs) + { + return lhs.get() != rhs; + } + template + bool operator!=(T* lhs, GObjectReference const& rhs) + { + return lhs != rhs.get(); + } + + template + GObjectReference static_pointer_cast(GObjectReference const& ptr) + { + return GObjectReference(static_cast(ptr.get())); + } + template + GObjectReference dynamic_pointer_cast(GObjectReference const& ptr) + { + return GObjectReference(dynamic_cast(ptr.get())); + } +} \ No newline at end of file diff --git a/nui/src/nui/backend/window_impl_linux.ipp b/nui/src/nui/backend/window_impl_linux.ipp index 7c407da..c87654b 100644 --- a/nui/src/nui/backend/window_impl_linux.ipp +++ b/nui/src/nui/backend/window_impl_linux.ipp @@ -1,10 +1,39 @@ +#include "gobject.hpp" + namespace Nui::Impl::Linux { + struct AsyncResponse + { + GObjectReference stream; + GObjectReference response; + std::string data; + }; + struct SchemeContext { std::size_t id; std::weak_ptr impl; CustomScheme schemeInfo; + std::mutex asyncResponsesGuard; + std::map asyncResponses; + int asyncResponseCounter = 0; + + void gcResponses() + { + std::lock_guard asyncResponsesGuard{this->asyncResponsesGuard}; + std::vector removals{}; + for (auto it = asyncResponses.begin(); it != asyncResponses.end(); ++it) + { + GInputStream* stream = it->second.stream.get(); + if (g_input_stream_is_closed(stream)) + { + removals.push_back(it->first); + break; + } + } + for (auto const& removal : removals) + asyncResponses.erase(removal); + } }; } @@ -19,7 +48,10 @@ std::size_t strlenLimited(char const* str, std::size_t limit) extern "C" { void uriSchemeRequestCallback(WebKitURISchemeRequest* request, gpointer userData) { + using namespace std::string_literals; + auto* schemeContext = static_cast(userData); + schemeContext->gcResponses(); // const auto path = std::string_view{webkit_uri_scheme_request_get_path(request)}; // const auto scheme = std::string_view{webkit_uri_scheme_request_get_scheme(request)}; @@ -102,50 +134,59 @@ extern "C" { .method = std::string{cmethod}, }); - GInputStream* stream; - stream = g_memory_input_stream_new_from_data( - responseObj.body.c_str(), static_cast(responseObj.body.size()), nullptr); - auto deleteStream = Nui::ScopeExit{[stream] { - g_object_unref(stream); - }}; - - WebKitURISchemeResponse* response; - response = webkit_uri_scheme_response_new(stream, static_cast(responseObj.body.size())); - - std::string contentType; - if (responseObj.headers.find("Content-Type") != responseObj.headers.end()) - { - auto range = responseObj.headers.equal_range("Content-Type"); - for (auto it = range.first; it != range.second; ++it) - contentType += it->second + "; "; - } - else - contentType = "application/octet-stream"; - - webkit_uri_scheme_response_set_content_type(response, contentType.c_str()); - webkit_uri_scheme_response_set_status(response, static_cast(responseObj.statusCode), nullptr); - - auto* responseHeaders = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); - auto deleteResponseHeaders = Nui::ScopeExit{[responseHeaders] { - g_object_unref(responseHeaders); - }}; - for (auto const& [key, value] : responseObj.headers) - soup_message_headers_append(responseHeaders, key.c_str(), value.c_str()); - - if (responseObj.headers.find("Access-Control-Allow-Origin") == responseObj.headers.end() && - !schemeInfo.allowedOrigins.empty()) - { - auto const& front = schemeInfo.allowedOrigins.front(); - soup_message_headers_append(responseHeaders, "Access-Control-Allow-Origin", front.c_str()); - } - - webkit_uri_scheme_response_set_http_headers(response, responseHeaders); - webkit_uri_scheme_request_finish_with_response(request, response); + using Nui::GObjectReference; + + std::lock_guard asyncResponsesGuard{schemeContext->asyncResponsesGuard}; + ++schemeContext->asyncResponseCounter; + schemeContext->asyncResponses[schemeContext->asyncResponseCounter] = Nui::Impl::Linux::AsyncResponse{}; + auto& asyncResponse = schemeContext->asyncResponses[schemeContext->asyncResponseCounter]; + asyncResponse.data = std::move(responseObj.body); + + asyncResponse.stream = Nui::GObjectReference::adoptReference(g_memory_input_stream_new_from_data( + asyncResponse.data.data(), static_cast(asyncResponse.data.size()), nullptr)); + + asyncResponse.response = Nui::GObjectReference::adoptReference( + webkit_uri_scheme_response_new(asyncResponse.stream.get(), static_cast(asyncResponse.data.size()))); + + const std::string contentType = [&]() { + if (responseObj.headers.find("Content-Type") != responseObj.headers.end()) + { + std::string contentType; + auto range = responseObj.headers.equal_range("Content-Type"); + for (auto it = range.first; it != range.second; ++it) + contentType += it->second + "; "; + contentType.pop_back(); + contentType.pop_back(); + return contentType; + } + return "application/octet-stream"s; + }(); + + webkit_uri_scheme_response_set_content_type(asyncResponse.response.get(), contentType.c_str()); + webkit_uri_scheme_response_set_status( + asyncResponse.response.get(), static_cast(responseObj.statusCode), nullptr); + + auto setHeaders = [&]() { + auto* responseHeaders = soup_message_headers_new(SOUP_MESSAGE_HEADERS_RESPONSE); + for (auto const& [key, value] : responseObj.headers) + soup_message_headers_append(responseHeaders, key.c_str(), value.c_str()); + + if (responseObj.headers.find("Access-Control-Allow-Origin") == responseObj.headers.end() && + !schemeInfo.allowedOrigins.empty()) + { + auto const& front = schemeInfo.allowedOrigins.front(); + soup_message_headers_append(responseHeaders, "Access-Control-Allow-Origin", front.c_str()); + } + webkit_uri_scheme_response_set_http_headers(asyncResponse.response.get(), responseHeaders); + }; + + setHeaders(); + webkit_uri_scheme_request_finish_with_response(request, asyncResponse.response.get()); } - void uriSchemeDestroyNotify(void*) + void uriSchemeDestroyNotify(void* userData) { - // Happens when everything else is already dead. + // Useless, because called when everything is already destroyed } }