, int> = 1> \
P cast(T* pInstance) \
{ \
return G_TYPE_CHECK_INSTANCE_CAST(pInstance, GType, std::remove_pointer_t); \
}
DEFINE_GOBJECT_CAST(AtspiStateSet, ATSPI_TYPE_STATE_SET)
DEFINE_GOBJECT_CAST(AtspiRelation, ATSPI_TYPE_RELATION)
DEFINE_GOBJECT_CAST(AtspiAccessible, ATSPI_TYPE_ACCESSIBLE)
DEFINE_GOBJECT_CAST(AtspiComponent, ATSPI_TYPE_COMPONENT)
DEFINE_GOBJECT_CAST(AtspiText, ATSPI_TYPE_TEXT)
#undef DEFINE_GOBJECT_CAST
// --- end GObject cast wrappers
class GLibEnumBase
{
protected:
/**
* @brief Retrieves the string representation of an enumeration value
* @param gt The @c GType for the enumeration
* @param value The enumeration value for which to get the name for
* @param fallback Fallback value in case @p values falls outside the enumeration
* @returns A string representing @p value
*/
static std::string glibEnumValueName(GType gt, gint value,
std::string_view fallback = "unknown")
{
auto klass = static_cast(g_type_class_ref(gt));
auto enum_value = g_enum_get_value(klass, value);
std::string ret(enum_value ? enum_value->value_name : fallback);
g_type_class_unref(klass);
return ret;
}
};
class Role : private GLibEnumBase
{
public:
static std::string getName(AtspiRole role)
{
return glibEnumValueName(atspi_role_get_type(), role);
}
};
class State : private GLibEnumBase
{
public:
static std::string getName(AtspiStateType state)
{
return glibEnumValueName(atspi_state_type_get_type(), state);
}
};
class TextGranularity : private GLibEnumBase
{
public:
static std::string getName(AtspiTextGranularity granularity)
{
return glibEnumValueName(atspi_text_granularity_get_type(), granularity);
}
};
class TextBoundaryType : private GLibEnumBase
{
public:
static std::string getName(AtspiTextBoundaryType boundaryType)
{
return glibEnumValueName(atspi_text_boundary_type_get_type(), boundaryType);
}
};
/**
* @brief Base class for GObject wrappers
*
* This leverages std::shared_ptr as a cheap way of wrapping a raw pointer, and its deleter as a
* mean of using g_object_unref() to cleanup. This is sub-optimal as it maintains a separate
* refcount to the GObject one, but it's easy.
*/
template class GObjectWrapperBase : public std::shared_ptr
{
public:
/* this is the boundary of C++ type safety, so we can have inheritance working
* properly with the C types. This should still be safe as it uses cast() which should be
* defined for each using type with DEFINE_GOBJECT_CAST(), which uses GType validation */
template P get() const { return cast(std::shared_ptr::get()); }
protected:
/**
* @brief Calls the C function @p f on the C object wrapped by @p this
* @param f The C function to call
* @param args Additional arguments to @p f
* @returns The return value from @p f
*
* Calls the C function @p f similar to f(get(), args...). Care is taken of
* transforming @c get() to the type actually expected as the first argument of @p f, using
* @c get(), which performs a runtime verification of the conversion.
*
* @note The type verification on whether @p f actually takes what get() returns is performed
* at runtime, so there will be no compilation error or warning if trying to use an
* incompatible C function. A check will however be performed at runtime, at least
* helping diagnose a possible invalid conversion.
*/
template inline auto invoke(F f, Ts... args) const
{
using FT = std::remove_pointer_t;
const auto p = get::arg1_type>();
return f(p, args...);
}
private:
static void deleter(T* p)
{
if (p)
g_object_unref(p);
}
public:
/**
* @param pObj The raw GObject to wrap
* @param takeRef Whether to take ownership of the object or not. If set to @c false, it will
* call @c g_object_ref(pAcc) to acquire a new reference to the GObject.
*/
GObjectWrapperBase(T* pObj = nullptr, bool takeRef = true)
: std::shared_ptr(pObj, deleter)
{
if (pObj && !takeRef)
g_object_ref(pObj);
}
};
/** @brief AtspiStateSet C++ wrapper */
class StateSet : public GObjectWrapperBase
{
public:
using GObjectWrapperBase::GObjectWrapperBase;
void add(const AtspiStateType t) { invoke(atspi_state_set_add, t); }
StateSet compare(const StateSet& other) const
{
return StateSet(invoke(atspi_state_set_compare, other.get()));
}
bool contains(const AtspiStateType t) const { return invoke(atspi_state_set_contains, t); }
bool operator==(const StateSet& other) const
{
return invoke(atspi_state_set_equals, other.get());
}
std::vector getStates() const
{
auto garray = gmem::unique_garray(invoke(atspi_state_set_get_states));
std::vector states;
for (auto i = decltype(garray->len){ 0 }; i < garray->len; i++)
{
states.push_back(g_array_index(garray, decltype(states)::value_type, i));
}
return states;
}
bool empty() const { return invoke(atspi_state_set_is_empty); }
void remove(AtspiStateType t) { invoke(atspi_state_set_remove, t); }
void setByName(const std::string_view name, bool enable)
{
invoke(atspi_state_set_set_by_name, name.data(), enable);
}
};
class Accessible;
/** @brief AtspiRelation C++ wrapper */
class Relation : public GObjectWrapperBase
{
public:
using GObjectWrapperBase::GObjectWrapperBase;
AtspiRelationType getRelationType() const { return invoke(atspi_relation_get_relation_type); }
int getNTargets() const { return invoke(atspi_relation_get_n_targets); }
Accessible getTarget(int i) const;
};
/* intermediate base just for splitting out the *invoke* helpers implementations, so the actual
* user-targeted class can hold only the actual API */
template class AtspiWrapperBase : public GObjectWrapperBase
{
protected:
using GObjectWrapperBase::invoke;
/**
* @brief Calls the throwing C function @p f on the C object wrapped by @p this
* @param f The C function to call
* @param args Additional arguments to @p f
* @returns The raw return value from @p f
* @throws css::uno::RuntimeException Exception @c GError are translated to
*
* This wrapper calls @p f with parameters @p args and an additional @c GError parameter to
* catch C exception, transforming them into C++ exceptions of type
* @c css::uno::RuntimeException.
*
* @see invoke()
*/
template inline auto invokeThrow(F f, Ts... args) const
{
GError* err = nullptr;
auto ret = invoke(f, args..., &err);
if (err)
{
throw css::uno::RuntimeException(OUString::fromUtf8(err->message));
}
return ret;
}
/**
* @brief Calls the throwing C function @p f on the C object wrapped by @p this and returns a string
* @param f The C function to call
* @param args Additional arguments to @p f
* @tparam E the type of exception to throw if @p f returns @c null, defaults to
* @c css::uno::RuntimeException
* @returns A string holding the return value from @p f
* @throws css::uno::RuntimeException See invokeThrow()
* @throws E Exception to use if @p f returns null with no other error
*
* Just like @c invokeThrow(), but wraps the return value in an @c std::string and manages the
* lifetime of the C function return value.
*
* As @c std::string cannot represent a @c null value, if @p f returned such a value without
* throwing an exception, this method will throw an exception of type @p E
*
* @see invokeThrow()
*/
template
inline std::string strInvoke(F f, Ts... args) const
{
auto r = invokeThrow(f, args...);
/* if the API returned NULL without throwing, use the specified exception because a nullptr
* std::string is not valid, and std::logic_error isn't gonna be very useful to the caller */
if (!r)
throw E();
return gmem::unique_gmem(r).get();
}
/**
* @brief Calls the throwing C function @p f on the C object wrapped by @p this and returns a vector
* @param f The C function to call
* @param args Additional arguments to @p f
* @tparam Vi The type of the members of the @c GArray @p f returns
* @tparam Vo The type of the members of the returned vector
* @returns A vector holding the return value from @p f
* @throws css::uno::RuntimeException See invokeThrow()
*
* Just like @c invokeThrow(), but wraps the return in an @c std::vector and manages the
* lifetime of the C function return value.
*
* @p Vi has to be implicitly convertible to @p Vo. A typical usage could be
* garrayInvoke(...), which would transform a @c GArray
* of @c AtspiAccessible* to an @c std::vector of @c Atspi::Accessible.
*
* @warning You have to get @p Vi right, there is no way to validate this type is correct or
* not, so you won't get a compilation error nor even a warning if you give the wrong
* type here.
*
* @see invokeThrow()
*/
template
inline std::vector garrayInvoke(F f, Ts... args) const
{
auto garray = gmem::unique_garray(invokeThrow(f, args...));
std::vector vec;
for (auto i = decltype(garray->len){ 0 }; i < garray->len; i++)
vec.push_back(g_array_index(garray, Vi, i));
return vec;
}
/**
* @brief Wraps an AT-SPI call returning a GHashTable
* @tparam Ki Type of the keys in the wrapped hash table
* @tparam Vi Type of the values in the wrapped hash table
* @tparam Ko Type of the keys in the wrapper map (this must be convertible from Ki)
* @tparam Vo Type of the values in the wrapper map (this must be convertible from Kv)
* @param f The function to call
* @param args Arguments to pass to @p f
* @returns A @c std::unordered_map holding the data returned by @p f.
*
* @see invokeThrow()
* @see strHashMapInvoke()
*/
template
inline std::unordered_map hashMapInvoke(F f, Ts... args) const
{
auto ghash = gmem::unique_ghashtable(invokeThrow(f, args...));
std::unordered_map map;
GHashTableIter iter;
g_hash_table_iter_init(&iter, ghash.get());
gpointer key, value;
while (g_hash_table_iter_next(&iter, &key, &value))
{
map.emplace(static_cast(key), static_cast(value));
}
return map;
}
/**
* @brief Just like @c hashMapInvoke() but already specialized for strings
* @param f The C function to call
* @param args Arguments to @p f
* @returns A @c std::unordered_map holding the data returned by @p f
*
* This is exactly the same as
* hashMapInvoke(f, args...)
*/
template inline auto strHashMapInvoke(F f, Ts... args) const
{
return hashMapInvoke(f, args...);
}
public:
using GObjectWrapperBase::GObjectWrapperBase;
};
class Component;
class Text;
/**
* @brief AtspiAccessible C++ wrapper
*
* This is a wrapper for the AtspiAccessible GObject class to make it a bit nicer to use in C++,
* including a proper class with methods, regular exceptions, easy memory management, and an
* iterator to enumerate children.
*
* As this class actually inherits from std::shared_ptr, you can easily use @c get() to retrieve
* the wrapped pointer in case you need to, e.g. if some specific API is missing. However, take
* care of memory management on that object not to have it destroyed early (e.g. don't let anyone
* call @c g_object_unref() on it if they didn't call g_object_ref() first).
*
* To use it, just wrap an initial AtspiAccessible using the class constructor.
* @code
* Atspi::Accessible desktop(atspi_get_desktop(0));
* for (auto&& app: desktop) {
* std::cout << app->getName() << std::endl;
* }
* @endcode
*
* For details on the specific methods, see the C Atspi documentation.
*/
class Accessible : public AtspiWrapperBase
{
public:
using AtspiWrapperBase::AtspiWrapperBase;
void setCacheMask(AtspiCache mask) const { invoke(atspi_accessible_set_cache_mask, mask); }
void clearCache() const { invoke(atspi_accessible_clear_cache); }
AtspiRole getRole() const { return invokeThrow(atspi_accessible_get_role); }
std::string getRoleName() const { return strInvoke(atspi_accessible_get_role_name); }
std::string getName() const { return strInvoke(atspi_accessible_get_name); }
std::string getDescription() const { return strInvoke(atspi_accessible_get_description); }
int getChildCount() const { return invokeThrow(atspi_accessible_get_child_count); }
Accessible getChildAtIndex(int idx) const
{
return Accessible(invokeThrow(atspi_accessible_get_child_at_index, idx));
}
int getIndexInParent() const { return invokeThrow(atspi_accessible_get_index_in_parent); }
Accessible getParent() const { return Accessible(invokeThrow(atspi_accessible_get_parent)); }
StateSet getStateSet() const { return StateSet(invoke(atspi_accessible_get_state_set)); }
std::unordered_map getAttributes() const
{
return strHashMapInvoke(atspi_accessible_get_attributes);
}
std::vector getRelationSet() const
{
return garrayInvoke(atspi_accessible_get_relation_set);
}
private:
template I queryInterface(F f) const
{
auto pIface = invoke(f);
if (pIface)
return I(pIface);
throw css::uno::RuntimeException("Not implemented");
}
public:
Component queryComponent() const;
Text queryText() const;
// convenience extensions
class iterator
{
public:
using iterator_category = std::input_iterator_tag;
using value_type = Accessible;
using difference_type = std::ptrdiff_t;
using reference = value_type&;
using pointer = value_type*;
private:
const Accessible* m_pAccessible;
int m_idx;
public:
explicit iterator(const Accessible* pAccessible, int idx = 0)
: m_pAccessible(pAccessible)
, m_idx(idx)
{
}
iterator(iterator const& other)
: m_pAccessible(other.m_pAccessible)
, m_idx(other.m_idx)
{
}
iterator& operator++()
{
m_idx++;
return *this;
}
iterator operator++(int)
{
iterator other = *this;
++(*this);
return other;
}
bool operator==(iterator other) const
{
return m_idx == other.m_idx && m_pAccessible == other.m_pAccessible;
}
bool operator!=(iterator other) const { return !(*this == other); }
value_type operator*() const
{
assert(m_idx < m_pAccessible->getChildCount());
return m_pAccessible->getChildAtIndex(m_idx);
}
};
iterator begin() const { return iterator(this); }
iterator end() const
{
if (!get())
return iterator(this);
return iterator(this, std::max(0, getChildCount()));
}
};
// we just use ATSPI's own structure here, but pass it by value
using Rect = AtspiRect;
using Point = AtspiPoint;
/** @brief AtspiComponent C++ wrapper */
class Component : public Accessible
{
public:
Component(AtspiComponent* pObj = nullptr, bool takeRef = true)
: Accessible(cast(pObj), takeRef)
{
}
bool contains(int x, int y, AtspiCoordType coordType) const
{
return invokeThrow(atspi_component_contains, x, y, coordType);
}
Accessible getAccessibleAtPoint(int x, int y, AtspiCoordType coordType) const
{
return invokeThrow(atspi_component_get_accessible_at_point, x, y, coordType);
}
Rect getExtents(AtspiCoordType coordType) const
{
return *gmem::unique_gmem(invokeThrow(atspi_component_get_extents, coordType));
}
Point getPosition(AtspiCoordType coordType) const
{
return *gmem::unique_gmem(invokeThrow(atspi_component_get_position, coordType));
}
Point getSize() const { return *gmem::unique_gmem(invokeThrow(atspi_component_get_size)); }
AtspiComponentLayer getLayer() const { return invokeThrow(atspi_component_get_layer); }
short getMdiZOrder() const { return invokeThrow(atspi_component_get_mdi_z_order); }
bool grabFocus() const { return invokeThrow(atspi_component_grab_focus); }
double getAlpha() const { return invokeThrow(atspi_component_get_alpha); }
#if HAVE_ATSPI2_SCROLL_TO
bool scrollTo(AtspiScrollType scrollType) const
{
return invokeThrow(atspi_component_scroll_to, scrollType);
}
bool scrollToPoint(AtspiCoordType coordType, int x, int y) const
{
return invokeThrow(atspi_component_scroll_to_point, coordType, x, y);
}
#endif // HAVE_ATSPI2_SCROLL_TO
};
/** @brief AtspiText C++ wrapper */
class Text : public Accessible
{
public:
Text(AtspiText* pObj = nullptr, bool takeRef = true)
: Accessible(cast(pObj), takeRef)
{
}
/** Wrapper for AtspiRange
*
* This is not actually required, but helps make this more C++-y (by allowing TextRange to
* inherit it) and more LibreOffice-y (by having camelCase names) */
struct Range
{
int startOffset;
int endOffset;
Range(int startOffset_, int endOffset_)
: startOffset(startOffset_)
, endOffset(endOffset_)
{
}
Range(const AtspiRange* r)
: startOffset(r->start_offset)
, endOffset(r->end_offset)
{
}
};
/** Wrapper for AtspiTextRange */
struct TextRange : Range
{
std::string content;
TextRange(int startOffset_, int endOffset_, std::string_view content_)
: Range(startOffset_, endOffset_)
, content(content_)
{
}
TextRange(const AtspiTextRange* r)
: Range(r->start_offset, r->end_offset)
, content(r->content)
{
}
};
int getCharacterCount() const { return invokeThrow(atspi_text_get_character_count); }
std::string getText(int startOffset, int endOffset) const
{
return strInvoke(atspi_text_get_text, startOffset, endOffset);
}
int getCaretOffset() const { return invokeThrow(atspi_text_get_caret_offset); }
std::unordered_map getTextAttributes(int offset, int* startOffset,
int* endOffset) const
{
return strHashMapInvoke(atspi_text_get_text_attributes, offset, startOffset, endOffset);
}
std::unordered_map
getAttributeRun(int offset, bool includeDefaults, int* startOffset, int* endOffset) const
{
return strHashMapInvoke(atspi_text_get_attribute_run, offset, includeDefaults, startOffset,
endOffset);
}
std::unordered_map getDefaultAttributes() const
{
return strHashMapInvoke(atspi_text_get_default_attributes);
}
std::string getTextAttributeValue(int offset, std::string_view name) const
{
return strInvoke(
atspi_text_get_text_attribute_value, offset, const_cast(name.data()));
}
protected:
/** Like @c invokeThrow() on C calls returning an @c AtspiTextRange */
template inline TextRange invokeTextRange(F f, Ts... args) const
{
struct deleter
{
void operator()(AtspiTextRange* ptr)
{
g_free(ptr->content);
g_free(ptr);
}
};
std::unique_ptr r(invokeThrow(f, args...));
return r.get();
}
public:
TextRange getStringAtOffset(int offset, AtspiTextGranularity granularity) const
{
return invokeTextRange(atspi_text_get_string_at_offset, offset, granularity);
}
/* the next 3 are deprecated, but LO doesn't implement getStringAtOffset() itself so it's a lot
* trickier to test for */
TextRange getTextBeforeOffset(int offset, AtspiTextBoundaryType boundary) const
{
return invokeTextRange(atspi_text_get_text_before_offset, offset, boundary);
}
TextRange getTextAtOffset(int offset, AtspiTextBoundaryType boundary) const
{
return invokeTextRange(atspi_text_get_text_at_offset, offset, boundary);
}
TextRange getTextAfterOffset(int offset, AtspiTextBoundaryType boundary) const
{
return invokeTextRange(atspi_text_get_text_after_offset, offset, boundary);
}
sal_Int32 getCharacterAtOffset(int offset) const
{
return invokeThrow(atspi_text_get_character_at_offset, offset);
}
Rect getCharacterExtents(int offset, AtspiCoordType type) const
{
return *gmem::unique_gmem(invokeThrow(atspi_text_get_character_extents, offset, type));
}
int getOffsetAtPoint(int x, int y, AtspiCoordType type) const
{
return invokeThrow(atspi_text_get_offset_at_point, x, y, type);
}
Rect getRangeExtents(int startOffset, int endOffset, AtspiCoordType type) const
{
return *gmem::unique_gmem(
invokeThrow(atspi_text_get_range_extents, startOffset, endOffset, type));
}
// getBoundedRanges() ?
int getNSelections() const { return invokeThrow(atspi_text_get_n_selections); }
Range getSelection(gint selectionNum) const
{
return gmem::unique_gmem(invokeThrow(atspi_text_get_selection, selectionNum)).get();
}
bool addSelection(int startOffset, int endOffset) const
{
return invokeThrow(atspi_text_add_selection, startOffset, endOffset);
}
bool removeSelection(int selectionNum) const
{
return invokeThrow(atspi_text_remove_selection, selectionNum);
}
bool setSelection(int selectionNum, int startOffset, int endOffset) const
{
return invokeThrow(atspi_text_set_selection, selectionNum, startOffset, endOffset);
}
#if HAVE_ATSPI2_SCROLL_TO
bool scrollSubstringTo(int startOffset, int endOffset, AtspiScrollType type) const
{
return invokeThrow(atspi_text_scroll_substring_to, startOffset, endOffset, type);
}
bool scrollSubstringToPoint(int startOffset, int endOffset, AtspiCoordType coords, gint x,
gint y) const
{
return invokeThrow(atspi_text_scroll_substring_to_point, startOffset, endOffset, coords, x,
y);
}
#endif // HAVE_ATSPI2_SCROLL_TO
};
}
// CppUnit integration
CPPUNIT_NS_BEGIN
template <> struct assertion_traits
{
static bool equal(const AtspiRole a, const AtspiRole b) { return a == b; }
static std::string toString(const AtspiRole role) { return Atspi::Role::getName(role); }
};
CPPUNIT_NS_END
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */