Files
tdesktop/cmake/external/glib/cppgir/gi/object.hpp
allhaileris afb81b8278
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
init
2026-02-16 15:50:16 +03:00

695 lines
19 KiB
C++

#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"
GI_MODULE_EXPORT
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<typename Arg, bool DECAY>
struct signal_arg
{
static GType get_type() { return traits::gtype<Arg>::get_type(); }
static detail::Value make(Arg arg)
{
return detail::Value(std::forward<Arg>(arg));
}
};
// re-route e.g. const std::string& cases
template<typename Arg>
struct signal_arg<const Arg &, false> : public signal_arg<const Arg &, true>
{};
template<typename Arg>
struct signal_arg<Arg &, false>
{
static GType get_type() { return G_TYPE_POINTER; }
static detail::Value make(Arg &arg)
{
return signal_arg<Arg *, false>::make(&arg);
}
};
template<typename Arg>
struct signal_arg<Arg *, false>
{
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<Arg>::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<Arg>::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<typename G>
struct signal_type;
template<typename R, typename... Args>
struct signal_type<R(Args...)>
{
static void check(GType tp, const gi::cstring_v name)
{
// capture type info and delegate
const int argcount = sizeof...(Args);
GType ti[] = {signal_arg<Args, false>::get_type()...};
check_signal_type(tp, name, traits::gtype<R>::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<typename Arg, typename... Args>
inline void
fill_parameters(Parameter *param, const char *name, Arg &&arg, Args &&...args)
{
param->name = name;
param->value.init<typename std::remove_reference<Arg>::type>();
set_value(&param->value, std::forward<Arg>(arg));
fill_parameters(param + 1, std::forward<Args>(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<detail::Parameter> construct_params;
template<typename... Args>
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>(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 &params)
{
#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<const char *> names;
std::vector<GValue> 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<typename CTYPE, typename... Args>
static auto new_(GType gtype, Args &&...args)
{
auto parameters = make_construct_params(std::forward<Args>(args)...);
auto *result = CTYPE(new_(gtype, parameters));
return gi::wrap(result, transfer_full);
}
// type-based generic object creation
// Args are a sequence of name, value
template<typename TYPE, typename... Args>
static TYPE new_(Args &&...args)
{
return new_<typename TYPE::BaseObjectType *>(
TYPE::get_type_(), std::forward<Args>(args)...);
}
// property stuff
// generic type unsafe
template<typename V>
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<V>(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<typename V>
self &set_property(const gi::cstring_v propname, V &&val)
{
return set_property<V>(find_property(propname, true), std::forward<V>(val));
}
template<typename V>
self &set_properties(const gi::cstring_v propname, V &&val)
{
return set_property<V>(propname, std::forward<V>(val));
}
// set a number of props
template<typename V, typename... Args>
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<V>(val));
set_properties(std::forward<Args>(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<typename V>
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<V>::value, "dangling ref");
detail::Value v;
v.init<V>();
// 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>(&v);
}
template<typename V>
V get_property(const gi::cstring_v propname) const
{
return get_property<V>(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
static ParamSpec find_property(
GType gtype, const gi::cstring_v propname, bool _throw = false)
{
GParamSpec *spec;
if (g_type_is_a(gtype, G_TYPE_INTERFACE)) {
// interface should be loaded if we have an instance here
auto vtable = g_type_default_interface_peek(gtype);
spec = g_object_interface_find_property(vtable, propname.c_str());
} else {
spec = g_object_class_find_property(
(GObjectClass *)g_type_class_peek(gtype), propname.c_str());
}
if (_throw && !spec)
detail::try_throw(
detail::unknown_property_error(gtype, propname.c_str()));
return gi::wrap(spec, transfer_none);
}
ParamSpec find_property(
const gi::cstring_v propname, bool _throw = false) const
{
return find_property(gobj_type_(), propname, _throw);
}
gi::Collection<gi::DSpan, GParamSpec *, gi::transfer_container_t>
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<gi::DSpan, GParamSpec *, gi::transfer_container_t>>(
specs, nspecs, transfer_container);
}
// signal stuff
private:
template<typename F, typename Functor>
gulong connect_data(
const gi::cstring_v signal, Functor &&f, GConnectFlags flags)
{
// runtime signature check
detail::signal_type<F>::check(gobj_type_(), signal);
auto w = new detail::transform_signal_wrapper<F>(std::forward<Functor>(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<typename F, typename Functor>
gulong connect(const gi::cstring_v signal, Functor &&f)
{
return connect_data<F, Functor>(
signal, std::forward<Functor>(f), (GConnectFlags)0);
}
template<typename F, typename Functor>
gulong connect_after(const gi::cstring_v signal, Functor &&f)
{
return connect_data<F, Functor>(
signal, std::forward<Functor>(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<typename F, typename Functor>
gulong connect_unchecked(
const gi::cstring_v signal, Functor &&f, GConnectFlags flags = {})
{
auto w = new detail::callback_wrapper<F>(std::forward<Functor>(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<typename R, bool DECAY = true, typename... Args>
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<Args, DECAY>::make(std::forward<Args>(args))...};
detail::Value retv;
retv.init<R>();
g_signal_emitv(values, id, detail, &retv);
return detail::get_value<R>(&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<void(), gi::transfer_none_t> DestroyNotify;
} // namespace GLib
} // namespace repository
// type safe signal connection
template<typename T, typename Base = repository::GObject::Object>
class signal_proxy;
template<typename R, typename Instance, typename... Args, typename Base>
class signal_proxy<R(Instance, Args...), Base>
{
protected:
typedef R(CppSig)(Instance, Args...);
Base object_;
gi::cstring name_;
public:
typedef CppSig function_type;
typedef detail::connectable<function_type> slot_type;
signal_proxy(Base owner, gi::cstring name)
: object_(owner), name_(std::move(name))
{}
template<typename Functor>
gulong connect(Functor &&f)
{
return object_.template connect<CppSig>(name_, std::forward<Functor>(f));
}
template<typename Functor>
gulong connect_after(Functor &&f)
{
return object_.template connect_after<CppSig>(
name_, std::forward<Functor>(f));
}
R emit(Args... args)
{
return object_.template emit<R, false, Args...>(
name_, std::forward<Args>(args)...);
}
template<typename Functor>
slot_type slot(Functor &&f)
{
return slot_type(std::forward<Functor>(f));
}
};
// type safe property setting
template<typename T, typename Base = repository::GObject::Object>
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<T>(pspec_.gobj_()->name);
}
ParamSpec param_spec() const { return pspec_; }
signal_proxy<void(Base, ParamSpec)> signal_notify() const
{
return signal_proxy<void(Base, ParamSpec)>(
object_, gi::cstring_v("notify::") + gi::cstring_v(pspec_.name_()));
}
};
template<typename T, typename Base = repository::GObject::Object>
class property_proxy_read : private property_proxy<T, Base>
{
typedef property_proxy<T, Base> super;
public:
using super::get;
using super::property_proxy;
};
template<typename T, typename Base = repository::GObject::Object>
class property_proxy_write : private property_proxy<T, Base>
{
typedef property_proxy<T, Base> 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<internal::SignalConnection>;
using SignalScopedConnection = detail::scoped_connection<SignalConnection>;
} // namespace GObject
} // namespace repository
// connection callback type
template<typename G>
using slot = detail::connectable<G>;
template<typename G>
inline repository::GObject::SignalConnection
make_connection(
gulong id, const gi::slot<G> &s, repository::GObject::Object object)
{
using repository::GObject::SignalConnection;
return SignalConnection(id, s.connection(), object);
}
} // namespace gi
#endif // GI_OBJECT_HPP