Skip to content

Commit

Permalink
Merge pull request #103 from NuiCpp/devel
Browse files Browse the repository at this point in the history
Linux Custom Scheme Handler Fix
  • Loading branch information
5cript authored Jan 10, 2024
2 parents f58becc + 73485d7 commit 4cb4cbc
Show file tree
Hide file tree
Showing 2 changed files with 289 additions and 41 deletions.
207 changes: 207 additions & 0 deletions nui/src/nui/backend/gobject.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#pragma once

namespace Nui
{
namespace Detail
{
template <typename T>
T* referenceGObject(T* ptr)
{
if (ptr)
g_object_ref_sink(ptr);
return ptr;
}

template <typename T>
void unreferenceGObject(T* ptr)
{
if (ptr)
g_object_unref(ptr);
}
}

template <typename T>
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 <typename U>
GObjectReference(GObjectReference<U> 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 <typename U>
GObjectReference& operator=(GObjectReference<U> const& other)
{
GObjectReference(other).swap(*this);
return *this;
}

void swap(GObjectReference& other)
{
using std::swap;
swap(ptr_, other.ptr_);
}
static GObjectReference<T> adoptReference(T* ptr)
{
return GObjectReference<T>(ptr, AdoptFlag{});
}

private:
GObjectReference(pointer_type ptr, AdoptFlag)
: ptr_{ptr}
{}

private:
T* ptr_;
};

template <typename T>
void swap(GObjectReference<T>& lhs, GObjectReference<T>& rhs)
{
lhs.swap(rhs);
}

template <typename T, typename U>
bool operator==(GObjectReference<T> const& lhs, GObjectReference<U> const& rhs)
{
return lhs.get() == rhs.get();
}
template <typename T, typename U>
bool operator==(GObjectReference<T> const& lhs, T* rhs)
{
return lhs.get() == rhs;
}
template <typename T, typename U>
bool operator==(T* lhs, GObjectReference<U> const& rhs)
{
return lhs == rhs.get();
}

template <typename T, typename U>
bool operator!=(GObjectReference<T> const& lhs, GObjectReference<U> const& rhs)
{
return lhs.get() != rhs.get();
}
template <typename T, typename U>
bool operator!=(GObjectReference<T> const& lhs, T* rhs)
{
return lhs.get() != rhs;
}
template <typename T, typename U>
bool operator!=(T* lhs, GObjectReference<U> const& rhs)
{
return lhs != rhs.get();
}

template <typename T, typename U>
GObjectReference<T> static_pointer_cast(GObjectReference<U> const& ptr)
{
return GObjectReference<T>(static_cast<T*>(ptr.get()));
}
template <typename T, typename U>
GObjectReference<T> dynamic_pointer_cast(GObjectReference<U> const& ptr)
{
return GObjectReference<T>(dynamic_cast<T*>(ptr.get()));
}
}
123 changes: 82 additions & 41 deletions nui/src/nui/backend/window_impl_linux.ipp
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
#include "gobject.hpp"

namespace Nui::Impl::Linux
{
struct AsyncResponse
{
GObjectReference<GInputStream> stream;
GObjectReference<WebKitURISchemeResponse> response;
std::string data;
};

struct SchemeContext
{
std::size_t id;
std::weak_ptr<Window::LinuxImplementation> impl;
CustomScheme schemeInfo;
std::mutex asyncResponsesGuard;
std::map<int, AsyncResponse> asyncResponses;
int asyncResponseCounter = 0;

void gcResponses()
{
std::lock_guard<std::mutex> asyncResponsesGuard{this->asyncResponsesGuard};
std::vector<int> 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);
}
};
}

Expand All @@ -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<Nui::Impl::Linux::SchemeContext*>(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)};
Expand Down Expand Up @@ -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<gssize>(responseObj.body.size()), nullptr);
auto deleteStream = Nui::ScopeExit{[stream] {
g_object_unref(stream);
}};

WebKitURISchemeResponse* response;
response = webkit_uri_scheme_response_new(stream, static_cast<gint64>(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<guint>(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<std::mutex> 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<GInputStream>::adoptReference(g_memory_input_stream_new_from_data(
asyncResponse.data.data(), static_cast<gssize>(asyncResponse.data.size()), nullptr));

asyncResponse.response = Nui::GObjectReference<WebKitURISchemeResponse>::adoptReference(
webkit_uri_scheme_response_new(asyncResponse.stream.get(), static_cast<gint64>(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<guint>(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
}
}

Expand Down

0 comments on commit 4cb4cbc

Please sign in to comment.