#ifndef GI_OBJECT_HPP #define GI_OBJECT_HPP #include "callback.hpp" #include "container.hpp" #include "exception.hpp" #include "objectbase.hpp" #include "paramspec.hpp" #include "value.hpp" namespace gi { namespace detail { // helper // signal argument connect/emit helper; // turn (C++) argument into a GType or GValue // most arguments are inputs (with specific GType), // but some arguments are used as output with G_TYPE_POINTER (e.g. int*) // which are mapped to (e.g.) int* or int& in C++ signature template struct signal_arg { static GType get_type() { return traits::gtype::get_type(); } static detail::Value make(Arg arg) { return detail::Value(std::forward(arg)); } }; // re-route e.g. const std::string& cases template struct signal_arg : public signal_arg {}; template struct signal_arg { static GType get_type() { return G_TYPE_POINTER; } static detail::Value make(Arg &arg) { return signal_arg::make(&arg); } }; template struct signal_arg { static GType get_type() { return G_TYPE_POINTER; } static detail::Value make(Arg *arg) { // (size of) wrapper argument should match wrappee static_assert(sizeof(typename traits::ctype::type) == sizeof(Arg), ""); // the above should suffice for proper handling // however, in these output cases, transfer should also be considered, // which is not (yet) available here // (but could be passed along similar to callback argument info) // so, restrict to plain cases for now static_assert(traits::is_plain::value, ""); return detail::Value(gpointer(arg)); } }; // returns -size if signed numeric, +size if unsigned numeric, otherwise 0 inline int get_number_size_signed(GType type) { // note; these are generally lower (absolute) bounds // at least it works in the context where it is used below #define GI_HANDLE_TYPE_SWITCH(cpptype, g_type, factor) \ case g_type: \ return factor * int(sizeof(cpptype)); switch (type) { GI_HANDLE_TYPE_SWITCH(gchar, G_TYPE_CHAR, -1) GI_HANDLE_TYPE_SWITCH(guchar, G_TYPE_UCHAR, 1) GI_HANDLE_TYPE_SWITCH(gint, G_TYPE_INT, -1) GI_HANDLE_TYPE_SWITCH(guint, G_TYPE_UINT, 1) GI_HANDLE_TYPE_SWITCH(glong, G_TYPE_LONG, -1) GI_HANDLE_TYPE_SWITCH(gulong, G_TYPE_ULONG, 1) GI_HANDLE_TYPE_SWITCH(gint64, G_TYPE_INT64, -1) GI_HANDLE_TYPE_SWITCH(guint64, G_TYPE_UINT64, 1) } #undef GI_HANDLE_TYPE_SWITCH return 0; } // glib type systems treats G_TYPE_INT64 as distinct from the other types // in practice, however, quite likely C gint64 == long inline bool compatible_type(GType expected, GType actual) { if (expected == G_TYPE_BOOLEAN) return std::abs(get_number_size_signed(actual)) == sizeof(gboolean); auto ssa_e = get_number_size_signed(expected); auto ssa_a = get_number_size_signed(actual); return ssa_e == ssa_a; } inline void check_signal_type(GType tp, const gi::cstring_v name, GType return_type, GType *param_types, guint n_params) { const char *errmsg("expected "); auto check_types = [tp, &name, &errmsg](const std::string &desc, GType expected, GType actual) { // normalize expected &= ~G_SIGNAL_TYPE_STATIC_SCOPE; actual &= ~G_SIGNAL_TYPE_STATIC_SCOPE; if (expected == actual || compatible_type(expected, actual) || g_type_is_a(expected, actual)) return; std::string msg = errmsg; msg += desc + " type "; msg += detail::make_string(g_type_name(expected)) + " != "; msg += detail::make_string(g_type_name(actual)); detail::try_throw(invalid_signal_callback_error(tp, name, msg)); }; // determine signal (detail) guint id; GQuark detail; if (!g_signal_parse_name(name.c_str(), tp, &id, &detail, false) || (id == 0)) detail::try_throw(unknown_signal_error(tp, name)); // get signal info GSignalQuery query; g_signal_query(id, &query); // check if (n_params != query.n_params + 1) { auto msg = std::string(errmsg) + "argument count "; msg += std::to_string(query.n_params); msg += " != " + std::to_string(n_params); detail::try_throw(invalid_signal_callback_error(tp, name, msg)); } check_types("return", query.return_type, return_type); check_types("instance", query.itype, param_types[0]); const std::string arg("argument "); for (guint i = 0; i < query.n_params; ++i) check_types( arg + std::to_string(i + 1), query.param_types[i], param_types[i + 1]); } template struct signal_type; template struct signal_type { static void check(GType tp, const gi::cstring_v name) { // capture type info and delegate const int argcount = sizeof...(Args); GType ti[] = {signal_arg::get_type()...}; check_signal_type(tp, name, traits::gtype::get_type(), ti, argcount); } }; // like GParameter, but with extra Value trimming struct Parameter { const char *name; detail::Value value; }; #ifdef GI_OBJECT_NEWV GI_DISABLE_DEPRECATED_WARN_BEGIN static_assert(sizeof(Parameter) == sizeof(GParameter), ""); GI_DISABLE_DEPRECATED_WARN_END #endif inline void fill_parameters(Parameter *) { // no-op } template inline void fill_parameters(Parameter *param, const char *name, Arg &&arg, Args &&...args) { param->name = name; param->value.init::type>(); set_value(¶m->value, std::forward(arg)); fill_parameters(param + 1, std::forward(args)...); } } // namespace detail #if GLIB_CHECK_VERSION(2, 54, 0) #define GI_GOBJECT_PROPERTY_VALUE 1 #endif namespace repository { /* if you have arrived here due to an ambiguous GObject reference * (both the C typedef GObject and this namespace) * then that can be worked-around by: * + using _GObject (struct name instead) * + adjust 'using namespace' style imports e.g. alias * namespace GObject_ = gi::GObject; * or simply do not mention GObject at all and simply use the wrappers ;-) */ namespace GObject { typedef std::vector construct_params; template construct_params make_construct_params(Args &&...args) { const int nparams = sizeof...(Args) / 2; construct_params parameters; parameters.resize(nparams); detail::fill_parameters(parameters.data(), std::forward(args)...); return parameters; } class Object : public detail::ObjectBase { typedef Object self; typedef detail::ObjectBase super_type; public: typedef ::GObject BaseObjectType; Object(std::nullptr_t = nullptr) : super_type() {} BaseObjectType *gobj_() { return (BaseObjectType *)super_type::gobj_(); } const BaseObjectType *gobj_() const { return (const BaseObjectType *)super_type::gobj_(); } BaseObjectType *gobj_copy_() const { return (BaseObjectType *)super_type::gobj_copy_(); } // class type static GType get_type_() { return G_TYPE_OBJECT; } // instance type GType gobj_type_() const { return G_OBJECT_TYPE(gobj_()); } // type-erased generic object creation // transfer full return static gpointer new_(GType gtype, const construct_params ¶ms) { #ifdef GI_OBJECT_NEWV GI_DISABLE_DEPRECATED_WARN_BEGIN auto result = g_object_newv(gtype, params.size(), (GParameter *)params.data()); GI_DISABLE_DEPRECATED_WARN_END #else std::vector names; std::vector values; names.reserve(params.size()); values.reserve(params.size()); // ownership remains in params for (auto &&p : params) { names.push_back(p.name); values.emplace_back(p.value); } auto result = g_object_new_with_properties( gtype, params.size(), names.data(), values.data()); #endif // GIR says transfer full, but let's be careful and really make it so // if likely still floating, then we assume ownership // but if it is no longer, then it has already been stolen (e.g. // GtkWindow), and we need to add one here if (g_type_is_a(gtype, G_TYPE_INITIALLY_UNOWNED)) g_object_ref_sink(result); return result; } // type-based generic object creation template static TYPE new_(Args &&...args) { auto parameters = make_construct_params(std::forward(args)...); typename TYPE::BaseObjectType *result = (typename TYPE::BaseObjectType *)new_(TYPE::get_type_(), parameters); return gi::wrap(result, transfer_full); } // property stuff // generic type unsafe template self &set_property(ParamSpec _pspec, V &&val) { // additional checks // allows for basic conversion between arithmetic types // without worrying about those details auto pspec = _pspec.gobj_(); detail::Value v(std::forward(val)); detail::Value dest; GValue *p = &v; if (G_VALUE_TYPE(&v) != pspec->value_type) { g_value_init(&dest, pspec->value_type); if (!g_value_transform(&v, &dest)) detail::try_throw( detail::transform_error(pspec->value_type, pspec->name)); p = &dest; } g_object_set_property(gobj_(), pspec->name, p); return *this; } template self &set_property(const gi::cstring_v propname, V &&val) { return set_property(find_property(propname, true), std::forward(val)); } template self &set_properties(const gi::cstring_v propname, V &&val) { return set_property(propname, std::forward(val)); } // set a number of props template self &set_properties(const gi::cstring_v propname, V &&val, Args... args) { g_object_freeze_notify(gobj_()); #if GI_CONFIG_EXCEPTIONS try { #endif set_property(propname, std::forward(val)); set_properties(std::forward(args)...); #if GI_CONFIG_EXCEPTIONS } catch (...) { g_object_thaw_notify(gobj_()); throw; } #endif g_object_thaw_notify(gobj_()); return *this; } #ifdef GI_GOBJECT_PROPERTY_VALUE self &set_property(const gi::cstring_v propname, Value val) { g_object_set_property(gobj_(), propname.c_str(), val.gobj_()); return *this; } #endif template V get_property(const char *propname) const { // this would return a ref to what is owned by stack-local v below static_assert(!traits::is_reftype::value, "dangling ref"); detail::Value v; v.init(); // the _get_ already tries to transform // also close enough to const g_object_get_property(const_cast<::GObject *>(gobj_()), propname, &v); return detail::get_value(&v); } template V get_property(const gi::cstring_v propname) const { return get_property(propname.c_str()); } #ifdef GI_GOBJECT_PROPERTY_VALUE Value get_property(const gi::cstring_v propname) const { Value result; const gchar *name = propname.c_str(); GValue *val = result.gobj_(); g_object_getv(const_cast<::GObject *>(gobj_()), 1, &name, val); return result; } #endif ParamSpec find_property( const gi::cstring_v propname, bool _throw = false) const { GParamSpec *spec; if (g_type_is_a(gobj_type_(), G_TYPE_INTERFACE)) { // interface should be loaded if we have an instance here auto vtable = g_type_default_interface_peek(gobj_type_()); spec = g_object_interface_find_property(vtable, propname.c_str()); } else { spec = g_object_class_find_property( G_OBJECT_GET_CLASS(gobj_()), propname.c_str()); } if (_throw && !spec) detail::try_throw( detail::unknown_property_error(gobj_type_(), propname.c_str())); return gi::wrap(spec, transfer_none); } gi::Collection list_properties() const { GParamSpec **specs; guint nspecs = 0; if (g_type_is_a(gobj_type_(), G_TYPE_INTERFACE)) { // interface should be loaded if we have an instance here auto vtable = g_type_default_interface_peek(gobj_type_()); specs = g_object_interface_list_properties(vtable, &nspecs); } else { specs = g_object_class_list_properties(G_OBJECT_GET_CLASS(gobj_()), &nspecs); } return wrap_to< gi::Collection>( specs, nspecs, transfer_container); } // signal stuff private: template gulong connect_data( const gi::cstring_v signal, Functor &&f, GConnectFlags flags) { // runtime signature check detail::signal_type::check(gobj_type_(), signal); auto w = new detail::transform_signal_wrapper(std::forward(f)); // mind gcc's -Wcast-function-type return g_signal_connect_data(gobj_(), signal.c_str(), (GCallback)&w->wrapper, w, (GClosureNotify)(GCallback)&w->destroy, flags); } public: template gulong connect(const gi::cstring_v signal, Functor &&f) { return connect_data( signal, std::forward(f), (GConnectFlags)0); } template gulong connect_after(const gi::cstring_v signal, Functor &&f) { return connect_data( signal, std::forward(f), G_CONNECT_AFTER); } // TODO the object variants ?? // in case of unsupported signal signature // connect using a plain C signature without check/transform (wrap/unwrap) template gulong connect_unchecked( const gi::cstring_v signal, Functor &&f, GConnectFlags flags = {}) { auto w = new detail::callback_wrapper(std::forward(f)); // mind gcc's -Wcast-function-type return g_signal_connect_data(gobj_(), signal.c_str(), (GCallback)&w->wrapper, w, (GClosureNotify)(GCallback)&w->destroy, flags); } void disconnect(gulong id) { g_signal_handler_disconnect(gobj_(), id); } // Args... may be explicitly specified or deduced // if deduced; arrange to decay/strip reference below // if not deduced; may need to considere specified type as-is template R emit(const gi::cstring_v signal, Args &&...args) { // static constexpr bool DECAY = true; guint id; GQuark detail; if (!g_signal_parse_name(signal.c_str(), gobj_type_(), &id, &detail, true)) detail::try_throw(std::out_of_range(std::string("unknown signal name: ") + detail::make_string(signal.c_str()))); detail::Value values[] = {detail::Value(*this), detail::signal_arg::make(std::forward(args))...}; detail::Value retv; retv.init(); g_signal_emitv(values, id, detail, &retv); return detail::get_value(&retv); } void handler_block(gulong handler_id) { g_signal_handler_block(gobj_(), handler_id); } void handler_unblock(gulong handler_id) { g_signal_handler_unblock(gobj_(), handler_id); } bool handler_is_connected(gulong handler_id) { return g_signal_handler_is_connected(gobj_(), handler_id); } void stop_emission(guint id, GQuark detail) { g_signal_stop_emission(gobj_(), id, detail); } void stop_emission_by_name(const gi::cstring_v signal) { g_signal_stop_emission_by_name(gobj_(), signal.c_str()); } }; } // namespace GObject template<> struct declare_cpptype_of<::GObject> { typedef repository::GObject::Object type; }; namespace GLib { // predefined typedef detail::callback DestroyNotify; } // namespace GLib } // namespace repository // type safe signal connection template class signal_proxy; template class signal_proxy { protected: typedef R(CppSig)(Instance, Args...); Base object_; gi::cstring name_; public: typedef CppSig function_type; typedef detail::connectable slot_type; signal_proxy(Base owner, gi::cstring name) : object_(owner), name_(std::move(name)) {} template gulong connect(Functor &&f) { return object_.template connect(name_, std::forward(f)); } template gulong connect_after(Functor &&f) { return object_.template connect_after( name_, std::forward(f)); } R emit(Args... args) { return object_.template emit( name_, std::forward(args)...); } template slot_type slot(Functor &&f) { return slot_type(std::forward(f)); } }; // type safe property setting template class property_proxy { typedef property_proxy self; typedef repository::GObject::ParamSpec ParamSpec; protected: Base object_; ParamSpec pspec_; public: property_proxy(Base owner, ParamSpec pspec) : object_(owner), pspec_(pspec) {} property_proxy(Base owner, const gi::cstring_v name) : property_proxy(owner, owner.find_property(name, true)) {} void set(T v) { object_.set_property(pspec_, std::move(v)); } self &operator=(T v) { set(v); return *this; } T get() const { return object_.template get_property(pspec_.gobj_()->name); } ParamSpec param_spec() const { return pspec_; } signal_proxy signal_notify() const { return signal_proxy( object_, gi::cstring_v("notify::") + gi::cstring_v(pspec_.name_())); } }; template class property_proxy_read : private property_proxy { typedef property_proxy super; public: using super::get; using super::property_proxy; }; template class property_proxy_write : private property_proxy { typedef property_proxy super; public: using super::property_proxy; using super::set; using super::operator=; }; // interface (ptr) is wrapped the same way, // as it is essentially a ptr to implementing object // TODO use other intermediate base ?? using InterfaceBase = repository::GObject::Object; namespace repository { namespace GObject { // connection helpers namespace internal { class SignalConnection : public detail::connection_impl { public: SignalConnection(gulong id, detail::connection_status s, Object object) : connection_impl(id, s), object_(object) {} void disconnect() { object_.disconnect(id_); } private: Object object_; }; } // namespace internal using SignalConnection = detail::connection; using SignalScopedConnection = detail::scoped_connection; } // namespace GObject } // namespace repository // connection callback type template using slot = detail::connectable; template inline repository::GObject::SignalConnection make_connection( gulong id, const gi::slot &s, repository::GObject::Object object) { using repository::GObject::SignalConnection; return SignalConnection(id, s.connection(), object); } } // namespace gi #endif // GI_OBJECT_HPP