aboutsummaryrefslogtreecommitdiff
path: root/libs/plugin_python/src/pybind11-utils/include/pybind11_utils
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
commit7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch)
tree27fb39be241fdb5ac2734c574de678977d1856d0 /libs/plugin_python/src/pybind11-utils/include/pybind11_utils
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/plugin_python/src/pybind11-utils/include/pybind11_utils')
-rw-r--r--libs/plugin_python/src/pybind11-utils/include/pybind11_utils/functional.h155
-rw-r--r--libs/plugin_python/src/pybind11-utils/include/pybind11_utils/generator.h57
-rw-r--r--libs/plugin_python/src/pybind11-utils/include/pybind11_utils/shared_cpp_owner.h90
-rw-r--r--libs/plugin_python/src/pybind11-utils/include/pybind11_utils/smart_variant.h53
-rw-r--r--libs/plugin_python/src/pybind11-utils/include/pybind11_utils/smart_variant_wrapper.h180
5 files changed, 535 insertions, 0 deletions
diff --git a/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/functional.h b/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/functional.h
new file mode 100644
index 0000000..8662d82
--- /dev/null
+++ b/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/functional.h
@@ -0,0 +1,155 @@
+#ifndef PYTHON_PYBIND11_FUNCTIONAL_H
+#define PYTHON_PYBIND11_FUNCTIONAL_H
+
+#include <pybind11/pybind11.h>
+
+namespace mo2::python::detail {
+
+ // check if the given function is valid for a C++ function with the
+ // given arity
+ //
+ bool has_compatible_arity(pybind11::function handle, std::size_t arity);
+
+} // namespace mo2::python::detail
+
+namespace pybind11::detail {
+
+ // custom type_caster for std::function<>
+ //
+ // most of this is from pybind11 except that we also check arity of the function to
+ // allow overloaded function based on arity of argument
+ //
+ template <typename Return, typename... Args>
+ struct type_caster<std::function<Return(Args...)>> {
+ using type = std::function<Return(Args...)>;
+ using retval_type =
+ conditional_t<std::is_same<Return, void>::value, void_type, Return>;
+ using function_type = Return (*)(Args...);
+
+ public:
+ bool load(handle src, bool convert)
+ {
+ if (src.is_none()) {
+ // Defer accepting None to other overloads (if we aren't in convert
+ // mode):
+ if (!convert) {
+ return false;
+ }
+ return true;
+ }
+
+ if (!isinstance<function>(src)) {
+ return false;
+ }
+
+ auto func = reinterpret_borrow<function>(src);
+
+ /*
+ When passing a C++ function as an argument to another C++
+ function via Python, every function call would normally involve
+ a full C++ -> Python -> C++ roundtrip, which can be prohibitive.
+ Here, we try to at least detect the case where the function is
+ stateless (i.e. function pointer or lambda function without
+ captured variables), in which case the roundtrip can be avoided.
+ */
+ if (auto cfunc = func.cpp_function()) {
+ auto* cfunc_self = PyCFunction_GET_SELF(cfunc.ptr());
+ if (isinstance<capsule>(cfunc_self)) {
+ auto c = reinterpret_borrow<capsule>(cfunc_self);
+ auto* rec = (function_record*)c;
+
+ while (rec != nullptr) {
+ if (rec->is_stateless &&
+ same_type(typeid(function_type),
+ *reinterpret_cast<const std::type_info*>(
+ rec->data[1]))) {
+ struct capture {
+ function_type f;
+ };
+ value = ((capture*)&rec->data)->f;
+ return true;
+ }
+ rec = rec->next;
+ }
+ }
+ // PYPY segfaults here when passing builtin function like sum.
+ // Raising an fail exception here works to prevent the segfault, but
+ // only on gcc. See PR #1413 for full details
+ }
+
+ // !MO2! - check arity
+
+ if (!mo2::python::detail::has_compatible_arity(func, sizeof...(Args))) {
+ return false;
+ }
+
+ // !MO2! - everything below is copy/paste from pybind11
+
+ // ensure GIL is held during functor destruction
+ struct func_handle {
+ function f;
+#if !(defined(_MSC_VER) && _MSC_VER == 1916 && defined(PYBIND11_CPP17))
+ // This triggers a syntax error under very special conditions (very
+ // weird indeed).
+ explicit
+#endif
+ func_handle(function&& f_) noexcept
+ : f(std::move(f_))
+ {
+ }
+ func_handle(const func_handle& f_) { operator=(f_); }
+ func_handle& operator=(const func_handle& f_)
+ {
+ gil_scoped_acquire acq;
+ f = f_.f;
+ return *this;
+ }
+ ~func_handle()
+ {
+ gil_scoped_acquire acq;
+ function kill_f(std::move(f));
+ }
+ };
+
+ // to emulate 'move initialization capture' in C++11
+ struct func_wrapper {
+ func_handle hfunc;
+ explicit func_wrapper(func_handle&& hf) noexcept : hfunc(std::move(hf))
+ {
+ }
+ Return operator()(Args... args) const
+ {
+ gil_scoped_acquire acq;
+ object retval(hfunc.f(std::forward<Args>(args)...));
+ return retval.template cast<Return>();
+ }
+ };
+
+ value = func_wrapper(func_handle(std::move(func)));
+ return true;
+ }
+
+ template <typename Func>
+ static handle cast(Func&& f_, return_value_policy policy, handle /* parent */)
+ {
+ if (!f_) {
+ return none().inc_ref();
+ }
+
+ auto result = f_.template target<function_type>();
+ if (result) {
+ return cpp_function(*result, policy).release();
+ }
+ return cpp_function(std::forward<Func>(f_), policy).release();
+ }
+
+ PYBIND11_TYPE_CASTER(type, const_name("Callable[[") +
+ concat(make_caster<Args>::name...) +
+ const_name("], ") +
+ make_caster<retval_type>::name +
+ const_name("]"));
+ };
+
+} // namespace pybind11::detail
+
+#endif
diff --git a/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/generator.h b/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/generator.h
new file mode 100644
index 0000000..cbf9d18
--- /dev/null
+++ b/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/generator.h
@@ -0,0 +1,57 @@
+#ifndef PYTHON_PYBIND11_GENERATOR_H
+#define PYTHON_PYBIND11_GENERATOR_H
+
+#include <generator>
+
+#include <pybind11/pybind11.h>
+
+namespace mo2::python {
+
+ // the code here is mostly taken from pybind11 itself, and relies on some pybind11
+ // internals so might be subject to change when upgrading pybind11 versions
+
+ namespace detail {
+ template <typename T>
+ struct generator_state {
+ std::generator<T> g;
+ decltype(g.begin()) it;
+
+ generator_state(std::generator<T> gen) : g(std::move(gen)), it(g.begin()) {}
+ };
+ } // namespace detail
+
+ // create a Python generator from a C++ generator
+ //
+ template <typename T, typename... Args>
+ auto make_generator(std::generator<T> g, Args&&... args)
+ {
+ using state = detail::generator_state<T>;
+
+ namespace py = pybind11;
+ if (!py::detail::get_type_info(typeid(state), false)) {
+ py::class_<state>(py::handle(), "iterator", pybind11::module_local())
+ .def("__iter__",
+ [](state& s) -> state& {
+ return s;
+ })
+ .def(
+ "__next__",
+ [](state& s) -> T {
+ if (s.it != s.g.end()) {
+ T v = *s.it;
+ s.it++;
+ return v;
+ }
+ else {
+ throw py::stop_iteration();
+ }
+ },
+ std::forward<Args>(args)...);
+ }
+
+ return py::cast(state{std::move(g)});
+ }
+
+} // namespace mo2::python
+
+#endif
diff --git a/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/shared_cpp_owner.h b/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/shared_cpp_owner.h
new file mode 100644
index 0000000..02c2111
--- /dev/null
+++ b/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/shared_cpp_owner.h
@@ -0,0 +1,90 @@
+#ifndef PYTHON_PYBIND11_SHARED_CPP_OWNER_H
+#define PYTHON_PYBIND11_SHARED_CPP_OWNER_H
+
+#include <pybind11/pybind11.h>
+
+// pybind11 has some issues when a Python classes extend a C++ wrapper since the Python
+// object is not kept alive alongside the returned object
+//
+// there is a pybind11 branch called "smart_holder" that tries to solve this in a very
+// complicated way (with many other features)
+//
+// here, we simply use a custom type_caster<> for the classes we need - see the actual
+// definition in mo2::python::detail below
+//
+// IMPORTANT: this only works for classes that are managed by shared_ptr on the C++
+// side, not Qt object (see pybind11-qt holder for that)
+//
+
+namespace mo2::python::detail {
+
+ template <class Type, class SharedType>
+ struct shared_cpp_owner_caster
+ : pybind11::detail::copyable_holder_caster<Type, SharedType> {
+
+ // note that the actual holder type might be different in term of constness
+ using type = Type;
+ using holder_type = SharedType;
+
+ using base = pybind11::detail::copyable_holder_caster<Type, SharedType>;
+ using base::holder;
+ using base::value;
+
+ // in load, we use the default type_caster<> to extract the shared pointer, then
+ // we replace it by a custom one
+ //
+ // the custom shared_ptr<> holds the py::object BUT does not really manage the
+ // C++ object because it will ref-count but not delete it
+ //
+ // this should work because here it's how it works:
+ // - the Python object holds a standard shared_ptr<> for the C++ object -> the
+ // C++ object remains alive as long as the Python one remains alive
+ // - the C++ object holds a shared_ptr<> that manages the python object -> the
+ // Python object remains alive as-long as there is a shared_ptr<> on the C++
+ // side
+ //
+ bool load(pybind11::handle src, bool convert)
+ {
+ namespace py = pybind11;
+
+ if (!base::load(src, convert)) {
+ return false;
+ }
+
+ holder.reset(holder.get(), [pyobj = py::reinterpret_borrow<py::object>(
+ src)](auto*) mutable {
+ py::gil_scoped_acquire s;
+ pyobj = py::object();
+
+ // we do NOT delete the object here - if this was the last reference to
+ // the Python object, the Python object will delete it
+ });
+
+ return true;
+ }
+
+ // cast simply forward to the original type_caster<>
+ //
+ static pybind11::handle cast(const holder_type& src,
+ pybind11::return_value_policy policy,
+ pybind11::handle parent)
+ {
+ return base::cast(src, policy, parent);
+ }
+ };
+
+} // namespace mo2::python::detail
+
+#define MO2_PYBIND11_SHARED_CPP_HOLDER(Type) \
+ namespace pybind11::detail { \
+ template <> \
+ struct type_caster<std::shared_ptr<Type>> \
+ : mo2::python::detail::shared_cpp_owner_caster<Type, \
+ std::shared_ptr<Type>> {}; \
+ template <> \
+ struct type_caster<std::shared_ptr<const Type>> \
+ : mo2::python::detail::shared_cpp_owner_caster< \
+ Type, std::shared_ptr<const Type>> {}; \
+ }
+
+#endif
diff --git a/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/smart_variant.h b/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/smart_variant.h
new file mode 100644
index 0000000..bd7bd9b
--- /dev/null
+++ b/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/smart_variant.h
@@ -0,0 +1,53 @@
+#ifndef PYTHON_PYBIND11_UTILS_SMART_VARIANT_H
+#define PYTHON_PYBIND11_UTILS_SMART_VARIANT_H
+
+#include <variant>
+
+namespace mo2::python {
+
+ namespace detail {
+
+ // simple template class that should be specialized to expose proper fromXXX
+ // methods
+ //
+ template <class T>
+ struct smart_variant_converter {
+ template <class U>
+ static T from(U&& u)
+ {
+ return T{std::forward<U>(u)};
+ }
+ };
+
+ } // namespace detail
+
+ // a smart_variant is a std::variant that can be automatically converted to any of
+ // its type via custom operator T()
+ //
+ // user should specialize detail::smart_variant_converter to provide proper
+ // conversions
+ //
+ template <class... Args>
+ struct smart_variant : std::variant<Args...> {
+ using std::variant<Args...>::variant;
+
+ template <class T, std::enable_if_t<
+ std::disjunction_v<std::is_same<T, Args>...>, int> = 0>
+ operator T() const
+ {
+ return std::visit(
+ [](auto const& t) -> T {
+ if constexpr (std::is_same_v<std::decay_t<decltype(t)>, T>) {
+ return t;
+ }
+ else {
+ return detail::smart_variant_converter<T>::from(t);
+ }
+ },
+ *this);
+ }
+ };
+
+} // namespace mo2::python
+
+#endif
diff --git a/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/smart_variant_wrapper.h b/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/smart_variant_wrapper.h
new file mode 100644
index 0000000..123d95c
--- /dev/null
+++ b/libs/plugin_python/src/pybind11-utils/include/pybind11_utils/smart_variant_wrapper.h
@@ -0,0 +1,180 @@
+#ifndef PYTHON_PYBIND11_UTILS_SMART_VARIANT_WRAPPER_H
+#define PYTHON_PYBIND11_UTILS_SMART_VARIANT_WRAPPER_H
+
+#include <functional>
+#include <type_traits>
+
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+#include "smart_variant.h"
+
+namespace mo2::python {
+
+ namespace detail {
+
+ // simple helper class that expose a ::type attribute which is U is I is in Is,
+ // V otherwise
+ template <class U, class V, std::size_t I, class Is>
+ struct wrap_arg;
+
+ template <class U, class V, std::size_t I, std::size_t... Is>
+ struct wrap_arg<U, V, I, std::index_sequence<Is...>> {
+ using type =
+ std::conditional_t<std::disjunction_v<std::bool_constant<I == Is>...>,
+ U, V>;
+ };
+
+ // helper type for wrap_arg
+ template <class U, class A, std::size_t I, class Is>
+ using wrap_arg_t = typename wrap_arg<U, A, I, Is>::type;
+
+ template <class T, std::size_t... Is, std::size_t... AIs, class Fn, class R,
+ class... Args>
+ auto wrap_arguments_impl(std::index_sequence<Is...>, Fn&& fn, R (*)(Args...),
+ std::index_sequence<AIs...>)
+ {
+ return [fn = std::forward<Fn>(fn)](
+ wrap_arg_t<T, Args, AIs, std::index_sequence<Is...>>... args) {
+ return std::invoke(fn, std::forward<decltype(args)>(args)...);
+ };
+ }
+
+ template <class T, class... Args, std::size_t... Is>
+ auto make_convertible_index_sequence(std::index_sequence<Is...>)
+ {
+ return std::index_sequence<(std::is_convertible_v<T, Args> ? Is : -1)...>{};
+ }
+
+ template <class T, std::size_t... Is, class Fn, class R, class... Args>
+ auto wrap_arguments_impl(Fn&& fn, R (*sg)(Args...))
+ {
+ if constexpr (sizeof...(Is) == 0) {
+ return wrap_arguments_impl<T>(
+ make_convertible_index_sequence<T, Args...>(
+ std::make_index_sequence<sizeof...(Args)>{}),
+ std::forward<Fn>(fn), sg,
+ std::make_index_sequence<sizeof...(Args)>{});
+ }
+ else {
+ return wrap_arguments_impl<T>(
+ std::index_sequence<Is...>{}, std::forward<Fn>(fn), sg,
+ std::make_index_sequence<sizeof...(Args)>{});
+ }
+ }
+
+ template <class T, class Fn, class R, class... Args>
+ auto wrap_return_impl(Fn&& fn, R (*)(Args...))
+ {
+ return [fn = std::forward<Fn>(fn)](Args... args) {
+ return T{std::invoke(fn, std::forward<decltype(args)>(args)...)};
+ };
+ }
+
+ // make_python_function_signature: return a null-pointer with the proper type
+ // for the given function
+
+ template <class Fn>
+ struct function_signature {
+ using type =
+ pybind11::detail::function_signature_t<std::remove_reference_t<Fn>>;
+ };
+
+ template <class R, class... Args>
+ struct function_signature<R (*)(Args...)> {
+ using type = R(Args...);
+ };
+
+ template <class R, class C, class... Args>
+ struct function_signature<R (C::*)(Args...)> {
+ using type = R(C*, Args...);
+ };
+
+ template <class R, class C, class... Args>
+ struct function_signature<R (C::*)(Args...) &> {
+ using type = R(C*, Args...);
+ };
+
+ template <class R, class C, class... Args>
+ struct function_signature<R (C::*)(Args...) const> {
+ using type = R(const C*, Args...);
+ };
+
+ template <class R, class C, class... Args>
+ struct function_signature<R (C::*)(Args...) const&> {
+ using type = R(const C*, Args...);
+ };
+
+ template <class Fn>
+ using function_signature_t = typename function_signature<Fn>::type;
+
+ template <class Type, class... WrappedTypes>
+ class wrap_type_caster {
+ using variant_type = std::variant<WrappedTypes...>;
+ using variant_caster = pybind11::detail::make_caster<variant_type>;
+
+ public:
+ PYBIND11_TYPE_CASTER(Type, variant_caster::name);
+
+ bool load(pybind11::handle src, bool convert)
+ {
+ variant_caster caster;
+
+ if (!caster.load(src, convert)) {
+ return false;
+ }
+
+ value = std::visit(
+ [](auto const& fn) {
+ return Type(fn);
+ },
+ static_cast<variant_type>(caster));
+ return true;
+ }
+
+ static pybind11::handle cast(const Type& src,
+ pybind11::return_value_policy policy,
+ pybind11::handle parent)
+ {
+ return variant_caster::cast(variant_type(std::in_place_index<0>, src),
+ policy, parent);
+ }
+ };
+
+ } // namespace detail
+
+ // wrap the given function-like object to accept T instead of the specified
+ // arguments at the specified positions
+ //
+ // if the list of positions is empty, replace all arguments that can be converted to
+ // T
+ //
+ template <class T, std::size_t... Is, class Fn>
+ auto wrap_arguments(Fn&& fn)
+ {
+ return detail::wrap_arguments_impl<T, Is...>(
+ std::forward<Fn>(fn),
+ (mo2::python::detail::function_signature_t<Fn>*)nullptr);
+ }
+
+ // wrap the given function-like object to return T instead of the specified type
+ //
+ template <class T, class Fn>
+ auto wrap_return(Fn&& fn)
+ {
+ return detail::wrap_return_impl<T>(
+ std::forward<Fn>(fn),
+ (mo2::python::detail::function_signature_t<Fn>*)nullptr);
+ }
+
+} // namespace mo2::python
+
+namespace pybind11::detail {
+
+ template <class... Args>
+ struct type_caster<::mo2::python::smart_variant<Args...>>
+ : variant_caster<::mo2::python::smart_variant<Args...>> {};
+
+} // namespace pybind11::detail
+
+#endif