#ifndef GI_CALLBACK_HPP #define GI_CALLBACK_HPP #include "base.hpp" #include "exception.hpp" #include "wrap.hpp" #include #include namespace gi { namespace detail { inline std::string exception_desc(const std::exception &e) { auto desc = e.what(); return desc ? desc : typeid(e).name(); } inline std::string exception_desc(...) { return "[unknown]"; } #if GI_CONFIG_EXCEPTIONS inline ::GError ** find_gerror(bool &has_gerror) { has_gerror = false; return nullptr; } inline ::GError ** find_gerror(bool &has_gerror, ::GError **error) { has_gerror = true; return error; } template inline ::GError ** find_gerror(bool &has_gerror, CT /*arg*/, CType... args) { return find_gerror(has_gerror, args...); } inline ::GError * exception_error(const repository::GLib::Error &exc) { return g_error_copy(exc.gobj_()); } inline ::GError * exception_error(const repository::GLib::Error_Ref &exc) { return g_error_copy(exc.gobj_()); } template inline ::GError * exception_error(const E &exc) { static auto quark = g_quark_from_static_string("gi-error-quark"); return g_error_new(quark, 0, "%s", exception_desc(exc).c_str()); } template void report_exception(const E &exc, CType... args) { // see if we can really report error somewhere bool has_gerror = false; GError **error = find_gerror(has_gerror, args...); if (has_gerror) { g_return_if_fail(error == NULL || *error == NULL); if (error) *error = exception_error(exc); // if caller does not need/want error, exception disappears here } else { // simply report the hard and simple way // otherwise catch internally if something else/more is desired if (!SILENT) { auto msg = std::string("handler exception; ") + exception_desc(exc); g_critical("%s", msg.c_str()); } } } #endif // (re)float only applies to GObject template::value>::type * = nullptr> auto unwrap_maybe_float(CppType &&v, const Transfer &t) { return unwrap(std::forward(v), t); } // (re) float only for transfer none/floating template::value>::type * = nullptr> inline typename traits::ctype::type unwrap_maybe_float(CppType &&v, const transfer_full_t &t) { return unwrap(std::forward(v), t); } template::value>::type * = nullptr> inline typename traits::ctype::type unwrap_maybe_float(CppType &&v, const transfer_none_t &) { // expected called with r-value static_assert(!std::is_reference::value, ""); // steal/take from wrapper auto result = (typename traits::ctype::type)v.release_(); // the following is essentially a bit of a hack as mentioned in // https://bugzilla.gnome.org/show_bug.cgi?id=693393) // that is, we are about to return an object to C (from binding/callback) // and this should be done with none transfer // this none may actually mean floating (e.g. a factory-like callback) // but no way to know from annotations // so if at runtime the wrapper actually holds the only reference, // then it is about to be destroyed (when wrapper goes away) // *before* the object can make it back to caller // so turn that ref into a floating one (as only that makes sense) // if it is not the only ref, it is kept alive elsewhere // (as typically so for a "getter" callback) // so it is really treated as none auto obj = (GObject *)result; // theoretically not MT safe, but if == 1, only 1 thread should be involved if (obj->ref_count == 1) { g_object_force_floating(obj); } else { // otherwise unref as wrapper would have g_object_unref(obj); } return result; } template constexpr T unconst(T t) { return t; } inline char * unconst(const char *t) { return (char *)t; } // helper types to provide additional argument info beyond Transfer template struct args_index { static constexpr auto value = std::make_tuple(index...); using value_type = decltype(value); }; template> struct arg_info { using transfer_type = Transfer; static constexpr bool inout = _inout; // tuple of index; used to selects the C arguments (to assemble C++ argument) using args_type = ArgsIndex; // additional info as used by corresponding cb_arg_handler using custom_type = CustomTraits; }; // access above info by forwarding types template struct arg_traits { using transfer_type = typename T::transfer_type; static constexpr bool inout = T::inout; using args_type = typename T::args_type; using custom_type = typename T::custom_type; }; // legacy case; only transfer type template struct arg_traits::value>::type> { using transfer_type = T; static constexpr bool inout = false; using args_type = args_index<>; using custom_type = void; }; // IndexTuple is essentially an args_index<...> template decltype(auto) apply_with_args(std::index_sequence, F &&f, ArgTuple &&args) { return f(std::get(IndexTuple::value)>(args)...); } template decltype(auto) apply_with_args(F &&f, Args &&...args) { return apply_with_args( std::make_index_sequence< std::tuple_size::value>(), std::forward(f), std::forward_as_tuple(args...)); } // a simple callback has no (need for) args_index template struct is_simple_cb : public std::true_type {}; template struct is_simple_cb> { static constexpr bool value = std::tuple_size< typename arg_traits::args_type::value_type>::value == 0 || (sizeof...(Transfers) > 0 && is_simple_cb>::value); }; template struct map_cpp_function; // handles all calls C -> C++ (callbacks, virtual method calls) // restrictions though on types supported (enforced by code generation) template::type, bool SIMPLE = is_simple_cb::value> struct transform_caller; // helper used below that provides pre-call and post-call steps // to handle each argument's conversion to and from C++ // in so-called (most) simple cases, there is one-to-one mapping between // C and C++ arguments and C++ argument type that allows to deduce context // (in particular, no callbacks or sized array) // as such, proper steps can be obtained by specialization on Cpp argument type template struct cb_arg_handler; // in simple cases, C signature can be derived from C++ signature template struct map_cpp_function { using type = CT; }; template struct map_cpp_function { using type = typename traits::ctype::type( typename cb_arg_handler::c_type...); }; // signature used in virtual method handling template struct map_cpp_function { using type = typename traits::ctype::type( typename cb_arg_handler::c_type...); }; // NOTE function type R(const A) is identical to R(A) // so no deduced Args below will retain const (if such) // in simple cases, *Transfer* is simply a transfer type // but it may also be a more elaborate argument trait template struct transform_caller, CR(CArgs...), SIMPLE> { static_assert(sizeof...(Args) == sizeof...(Transfers), ""); static_assert(!SIMPLE || sizeof...(Args) == sizeof...(CArgs), ""); typedef transform_caller self_type; typedef R (*caller_type)(Args &&..., void *d); private: static R do_call(Args &&...args, caller_type func, void *d) { return func(std::forward(args)..., d); } // helper that provides context for pack expansion below static void dummy_call(...){}; template static auto _tt(const T &) { return typename arg_traits::transfer_type(); } // non-void return template::value>::type * = nullptr> static CR _wrapper( CArgs... args, caller_type func, void *d, std::index_sequence) { std::tuple...> handlers; auto ret = do_call( std::get(handlers).arg(args, _tt(Transfers()), Transfers())..., func, d); dummy_call((std::get(handlers).post(args, _tt(Transfers())), 0)...); return unconst(unwrap_maybe_float(std::move(ret), RetTransfer())); } // void return template::value>::type * = nullptr> static CR _wrapper( CArgs... args, caller_type func, void *d, std::index_sequence) { std::tuple...> handlers; do_call( std::get(handlers).arg(args, _tt(Transfers()), Transfers())..., func, d); dummy_call((std::get(handlers).post(args, _tt(Transfers())), 0)...); } // complex; non-void return template::value>::type * = nullptr> static CR _wrapper( CArgs... args, caller_type func, void *d, std::index_sequence) { std::tuple...> handlers; auto ret = do_call( apply_with_args::args_type>( [&handlers](auto... selargs) mutable -> decltype(auto) { return std::get(handlers).arg(selargs..., typename arg_traits::transfer_type(), Transfers()); }, args...)..., func, d); dummy_call((apply_with_args::args_type>( [&handlers](auto... selargs) mutable -> decltype(auto) { return std::get(handlers).post(selargs..., typename arg_traits::transfer_type()); }, args...), 0)...); return unconst(unwrap_maybe_float(std::move(ret), RetTransfer())); } // complex; void return template::value>::type * = nullptr> static CR _wrapper( CArgs... args, caller_type func, void *d, std::index_sequence) { std::tuple...> handlers; do_call(apply_with_args::args_type>( [&handlers](auto... selargs) mutable -> decltype(auto) { return std::get(handlers).arg(selargs..., typename arg_traits::transfer_type(), Transfers()); }, args...)..., func, d); dummy_call((apply_with_args::args_type>( [&handlers](auto... selargs) mutable -> decltype(auto) { return std::get(handlers).post(selargs..., typename arg_traits::transfer_type()); }, args...), 0)...); } public: static CR wrapper(CArgs... args, caller_type func, void *d) { // exceptions should not escape into plain C #if GI_CONFIG_EXCEPTIONS try { #endif return self_type::template _wrapper( args..., func, d, std::make_index_sequence()); #if GI_CONFIG_EXCEPTIONS } catch (const repository::GLib::Error &exc) { report_exception(exc, args...); } catch (const repository::GLib::Error_Ref &exc) { report_exception(exc, args...); } catch (const std::exception &exc) { report_exception(exc, args...); } catch (...) { report_exception(nullptr, args...); } return typename traits::ctype::type(); #endif } }; // minor helper traits used below namespace _traits { template struct remove_all_pointers { using type = T; }; template struct remove_all_pointers { using type = typename remove_all_pointers::type; }; template struct remove_all_pointers { using type = typename remove_all_pointers::type; }; template using is_basic_or_void = typename std::conditional::value || std::is_void::value, std::true_type, std::false_type>::type; template using is_basic_argument = typename std::conditional< is_basic_or_void::type>::type>::value, std::true_type, std::false_type>::type; template using is_const_lvalue = typename std::conditional< std::is_lvalue_reference::value && std::is_const::type>::value, std::true_type, std::false_type>::type; } // namespace _traits // generic fallback case; assume input parameter // (also covers boxed callerallocates, which pretty much is/becomes input) template struct cb_arg_handler { using c_type = typename traits::ctype::type; // use generic CType to handle const differences wrt c_type template typename std::decay::type arg( CType arg, const Transfer &t, const ArgTrait &) noexcept { // wrap to normalized destination target (no const &) // the destination type here is really only relevant for collection cases // (since that depends on the contained element) // otherwise all the info is pretty much in c_type type return wrap_to::type>(arg, t); } // minor variation; dynamic sized array input // also accepts length input template typename std::decay::type arg( CType arg, int length, const Transfer &t, const ArgTrait &) noexcept { // destination also really needed here return wrap_to::type>(arg, length, t); } void post(...){}; }; // passthrough on (pointers to) basic values; // handles input/output of basic values, as well as arrays of such template struct cb_arg_handler::value>::type> { using c_type = CppArg; template CType arg(CType arg, const Transfer &, const ArgTrait &) noexcept { return arg; } void post(...){}; // array size case; inout C int* to in Cpp int // (only enable if so tagged by non-default custom trait type) template typename std::enable_if< !std::is_void::custom_type>::value, CType>::type arg(CType *arg, const Transfer &, const ArgTrait &) noexcept { return *arg; } }; // handle "complex" (in)out argument // (though also handles e.g. int& case, which might be optimized, but anyways) // these are recognized/assumed to be a pointer or reference to non-basic type template struct cb_arg_handler::value && (std::is_pointer::value || (std::is_lvalue_reference::value && !_traits::is_const_lvalue::value))>::type> { using BaseCppType = typename std::decay::type>::type; // no more pointer expected here // (other than for void* cases, which do not introspect well, but anyways) static_assert(!std::is_pointer::value || std::is_same::value || std::is_same::value, ""); using c_type = typename traits::ctype::type *; // intermediate helper storage BaseCppType var_{}; // pointer case CppArg rv(std::true_type) { return &var_; } // ref case CppArg rv(std::false_type) { return var_; } // handle const variations (e.g. const char**) template static void assign(T *&t, X val) { t = const_cast(val); } template static void assign(T &t, X val) { t = unconst(val); } template CppArg arg(c_type arg, const Transfer &t, const ArgTrait &) { bool inout = arg_traits::inout; // a plain type has no special needs // so we can always take any bogus value as-is // (which is then less sensitive to incorrect annotation) if (arg && (inout || traits::is_plain::value)) var_ = wrap_to(*arg, t); return rv(std::is_pointer()); } // overall function call happens following arg call above // post invoked after function call template void post(c_type arg, const Transfer &t) { if (arg) assign(*arg, unwrap_maybe_float(std::move(var_), t)); } // sized array variants; arguments (data, size) // latter could be input (int) or (in)out (int*) template CppArg arg(c_type arg, Int size, const Transfer &t, const ArgTrait &) { bool inout = arg_traits::inout; if (arg && inout) var_ = wrap_to(*arg, size, t); return rv(std::is_pointer()); } template CppArg arg(c_type arg, Int *size, const Transfer &t, const ArgTrait &) { bool inout = arg_traits::inout; if (arg && size && inout) var_ = wrap_to(*arg, *size, t); return rv(std::is_pointer()); } template void post(c_type arg, Int, const Transfer &t) { post(arg, nullptr, t); } template void post(c_type arg, Int *size, const Transfer &t) { if (arg) assign(*arg, unwrap_maybe_float(std::move(var_), t)); if (size) *size = var_.size(); } }; #define GI_CB_ARG_CALLBACK_CUSTOM(Type, CF_CType, CF_handler) \ struct Type \ { \ using handler_cb_type = CF_CType; \ static constexpr auto handler = CF_handler; \ } // handles callback argument // (as argument in anther callback, most likely a virtual method) template struct cb_arg_handler::value>::type> { template CppArg arg(CType cb, gpointer userdata, ::GDestroyNotify destroy, const Transfer &, const ArgTrait &) noexcept { using ct = typename arg_traits::custom_type; // setup shared pointer to userdata with destroy as Deleter auto sp = destroy ? std::shared_ptr(userdata, destroy) : nullptr; // keep userdata/destroy alive as long as lambda handler auto h = [cb, userdata, destroy = std::move(destroy)]( auto &&...args) -> decltype(auto) { // avoid unused warning (void)destroy; // original callback type CType should match deduced handler_cb_tpe // but let's make sure as usual return ct::handler(std::forward(args)..., typename ct::handler_cb_type(cb), userdata); }; return h; } template CppArg arg(CType cb, gpointer userdata, const Transfer &t, const ArgTrait &tt) noexcept { return arg(cb, userdata, nullptr, t, tt); } void post(...){}; }; class connection_status { public: struct data { bool connected{false}; }; bool connected() const { auto sp = data_.lock(); return sp && sp->connected; } protected: std::weak_ptr data_; }; // callback handling template class connectable; template class callback_wrapper; template class connectable { friend class callback_wrapper; struct data : public connection_status::data { template> data(T &&t) : callable(std::forward(t)) {} std::function callable; }; struct connection_status_factory : public connection_status { connection_status_factory(std::shared_ptr p) { data_ = std::weak_ptr(p); } }; public: // avoid copy constructor mishaps template> connectable(T &&t) : data_(std::make_shared(std::forward(t))) {} connection_status connection() const { return connection_status_factory(data_); } R operator()(Args... args) const { return data_->callable(std::forward(args)...); } explicit operator bool() const { return data_ && data_->callable; } private: // state management by wrapper void connected(bool conn) { data_->connected = conn; } void disconnect() { data_->connected = false; } private: std::shared_ptr data_; }; template class callback_wrapper { typedef callback_wrapper self_type; public: template> callback_wrapper(T &&t) : _callback(std::forward(t)) { // mark connected now that it is wrapped _callback.connected(true); } // (only) used by manual callback workaround static R wrapper(Args... args, gpointer user_data) { auto t = reinterpret_cast(user_data); std::unique_ptr wt(AUTODESTROY ? t : nullptr); return t->_callback(args...); } static void destroy(gpointer user_data) { auto t = reinterpret_cast(user_data); delete t; } // (async scope) wrapper may have to take ownership of additional data // (other callback wrapper) template void take_data(std::shared_ptr d) { auto cb = std::move(_callback.data_->callable); auto newcb = [d, cb](Args &&...args) { return cb(std::forward(args)...); }; _callback.data_->callable = std::move(newcb); } template void take_data(T *d) { take_data(std::shared_ptr(d)); } ~callback_wrapper() { // other shared ptr to data might be around (unlikely though) // but regardless disconnect now as requested (as wrapper is going away) _callback.disconnect(); } connectable _callback; }; template::type> struct transform_callback_wrapper; template struct transform_callback_wrapper { // transfers of arguments template class with_transfer : public callback_wrapper { typedef callback_wrapper super_type; typedef with_transfer self_type; public: template explicit with_transfer(T &&t) : super_type(std::forward(t)) {} private: static R caller(Args &&...args, void *d) { auto this_ = (self_type *)d; return this_->_callback(std::forward(args)...); } public: static CR wrapper(CArgs... args, gpointer user_data) { auto t = reinterpret_cast(user_data); std::unique_ptr wt(AUTODESTROY ? t : nullptr); return transform_caller, CR(CArgs...)>::wrapper(args..., caller, t); } }; }; // used early in declarations, so avoid using unknown types in CSigOrVoid template, typename CSigOrVoid = void> class callback; template class callback, CSigOrVoid> : public connectable { typedef connectable super_type; public: typedef CppSig function_type; typedef typename map_cpp_function::type CSig; template using wrapper_type = typename transform_callback_wrapper::template with_transfer; typedef typename std::add_pointer::wrapper)>::type cfunction_type; using super_type::super_type; }; // signal handling; // transfer none for arguments // transfer full for return template struct transform_signal_wrapper; template struct signal_arg_transfer { typedef transfer_none_t type; }; template struct transform_signal_wrapper : public transform_callback_wrapper::template with_transfer::type...> { private: typedef typename transform_callback_wrapper::template with_transfer::type...> super_; public: template transform_signal_wrapper(T &&t) : super_(std::forward(t)) {} }; // connection helpers class connection_impl { public: connection_impl(gulong id, connection_status s) : id_(id), status_(s) {} bool connected() const { return status_.connected(); } gulong id() const { return id_; } protected: gulong id_; connection_status status_; }; template class connection { typedef Connection impl; public: connection() = default; template explicit connection(Args... arg) : conn_(std::make_shared(std::forward(arg)...)) { // this is to be expected at this time if (!connected()) { g_warning("creating non-connected connection"); } } // implicit copy/move bool connected() const { return conn_ && conn_->connected(); } void disconnect() { if (connected()) conn_->disconnect(); } protected: std::shared_ptr conn_; }; template class scoped_connection : public ConnectionBase { typedef ConnectionBase connection; public: scoped_connection() : connection() {} ~scoped_connection() { this->disconnect(); } void release() { this->conn_.reset(); } // ensure default movable scoped_connection(scoped_connection &&other) = default; scoped_connection &operator=(scoped_connection &&other) = default; // not copyable; to avoid inadvertent disconnect scoped_connection(const scoped_connection &other) = delete; scoped_connection &operator=(const scoped_connection &other) = delete; // but convert/assign from base class scoped_connection(const connection &other) : connection(other) {} scoped_connection &operator=(const connection &other) { (*(connection *)(this)) = other; return *this; } scoped_connection &operator=(const connection &&other) { (*(connection *)(this)) = std::move(other); return *this; } }; } // namespace detail // function bind helpers template inline std::function mem_fun(R (T::*pm)(Args...), Tp object) { return [object, pm](Args... args) { return (object->*pm)(std::forward(args)...); }; } template inline std::function mem_fun(R (T::*pm)(Args...) const, Tp object) { return [object, pm](Args... args) { return (object->*pm)(std::forward(args)...); }; } // expose for use in fallback scenarios using detail::callback_wrapper; } // namespace gi #endif // CALLBACK_HPP