vcl/unx/gtk4/a11y.cxx | 147 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-)
New commits: commit 2186fd52d1ef909d4398e84408f76c8357718e1e Author: Michael Weghorn <m.wegh...@posteo.de> AuthorDate: Fri Oct 27 11:13:55 2023 +0200 Commit: Michael Weghorn <m.wegh...@posteo.de> CommitDate: Fri Oct 27 16:45:17 2023 +0200 gtk4 a11y: Implement GtkAccessibleRange iface Implement the `GtkAccessibleRange` range interface [1] added in gtk 4.10, which is bridged to the AT-SPI Value interface on Linux inside gtk. Also set the related properties to their values retrieved from the `XAccessibleValue` interface. (Those already existed before gtk 4.10.) The GType handling for implementing the interface only if the `XAccessibleContext` of the underlying LO a11y object implements `XAccessibleValue` is mostly copied over from the gtk3 a11y implementation (in vcl/unx/gtk3/a11y/atkwrapper.cxx) and adapted a bit. Let `lo_accessible_range_set_current_value` always return true independent of whether the given value was actually set, since LibreOffice (together with Accerciser) otherwise crashes due to no proper DBus reply being sent, when trying to set a new value via AT-SPI API, s.a. [2] and [3]. When setting a new value, take the type of the current value into account, as done for gtk3 in commit a0b7b47e3ec843d8012a7526c1e8e72d67cc41b1 Author: Michael Weghorn <m.wegh...@posteo.de> Date: Tue Oct 24 12:29:54 2023 +0200 gtk3 a11y: Take current type into account when setting new value With this change in place, the AT-SPI Value interface is now shown as available for a checkbox form control in a Writer doc when selecting the corresponding a11y object in Accerciser's tree view of the LO a11y hierarchy. When changing the value via Accerciser, the checkbox in LibreOffice gets (un)checked as expected. (Setting the value to 1.0 causes the checkbox to be checked, 0.0 to become unchecked.). The initial values (current value, min and max value) are set properly. However, when querying the current value after changing it, the initial value is still returned. This likely needs to be addressed by bridging the corresponding `AccessibleEventId::VALUE_CHANGED` event to Gtk/AT-SPI by calling `gtk_accessible_update_property` with the new value. However, there's currently no implementation for handling a11y events in the gtk4 VCL plugin yet, so leave that for later. More interfaces to expose more functionality to the a11y layer will likely be implemented in upcoming gtk versions (like an interface for Text [4]) and bridging those should presumably be possible in a similar way. [1] https://docs.gtk.org/gtk4/iface.AccessibleRange.html [2] https://gitlab.gnome.org/GNOME/gtk/-/issues/6150 [3] https://gitlab.gnome.org/GNOME/gtk/-/commit/0dbd2bd09eff8c9233e45338a05daf2a835529ab [4] https://gitlab.gnome.org/GNOME/gtk/-/issues/5912 Change-Id: I84136fd80361d21cf4f79ab17118bb14079ab785 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158556 Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com> Tested-by: Jenkins Reviewed-by: Michael Weghorn <m.wegh...@posteo.de> diff --git a/vcl/unx/gtk4/a11y.cxx b/vcl/unx/gtk4/a11y.cxx index cbc2f853feaf..bf149f2ac083 100644 --- a/vcl/unx/gtk4/a11y.cxx +++ b/vcl/unx/gtk4/a11y.cxx @@ -11,6 +11,7 @@ #include <com/sun/star/accessibility/AccessibleStateType.hpp> #include <com/sun/star/accessibility/XAccessibleComponent.hpp> #include <com/sun/star/accessibility/XAccessibleText.hpp> +#include <com/sun/star/accessibility/XAccessibleValue.hpp> #include <unx/gtk/gtkframe.hxx> #include <gtk/gtk.h> @@ -183,6 +184,80 @@ struct LoAccessibleClass GObjectClass parent_class; }; +#if GTK_CHECK_VERSION(4, 10, 0) +static void lo_accessible_range_init(GtkAccessibleRangeInterface* iface); +static gboolean lo_accessible_range_set_current_value(GtkAccessibleRange* self, double fNewValue); +#endif + +extern "C" { +typedef GType (*GetGIfaceType)(); +} +const struct +{ + const char* name; + GInterfaceInitFunc const aInit; + GetGIfaceType const aGetGIfaceType; + const css::uno::Type& (*aGetUnoType)(); +} TYPE_TABLE[] = { +#if GTK_CHECK_VERSION(4, 10, 0) + { "Value", reinterpret_cast<GInterfaceInitFunc>(lo_accessible_range_init), + gtk_accessible_range_get_type, cppu::UnoType<css::accessibility::XAccessibleValue>::get } +#endif +}; + +static bool isOfType(css::uno::XInterface* xInterface, const css::uno::Type& rType) +{ + if (!xInterface) + return false; + + try + { + css::uno::Any aRet = xInterface->queryInterface(rType); + const bool bIs = (typelib_TypeClass_INTERFACE == aRet.pType->eTypeClass) + && (aRet.pReserved != nullptr); + return bIs; + } + catch (const css::uno::Exception&) + { + return false; + } +} + +static GType ensureTypeFor(css::uno::XInterface* xAccessible) +{ + OStringBuffer aTypeNameBuf("OOoGtkAccessibleObj"); + std::vector<bool> bTypes(std::size(TYPE_TABLE), false); + for (size_t i = 0; i < std::size(TYPE_TABLE); i++) + { + if (isOfType(xAccessible, TYPE_TABLE[i].aGetUnoType())) + { + aTypeNameBuf.append(TYPE_TABLE[i].name); + bTypes[i] = true; + } + } + + const OString aTypeName = aTypeNameBuf.makeStringAndClear(); + GType nType = g_type_from_name(aTypeName.getStr()); + if (nType != G_TYPE_INVALID) + return nType; + + GTypeInfo aTypeInfo = { sizeof(LoAccessibleClass), nullptr, nullptr, nullptr, nullptr, nullptr, + sizeof(LoAccessible), 0, nullptr, nullptr }; + nType + = g_type_register_static(LO_TYPE_ACCESSIBLE, aTypeName.getStr(), &aTypeInfo, GTypeFlags(0)); + + for (size_t i = 0; i < std::size(TYPE_TABLE); i++) + { + if (bTypes[i]) + { + GInterfaceInfo aIfaceInfo = { nullptr, nullptr, nullptr }; + aIfaceInfo.interface_init = TYPE_TABLE[i].aInit; + g_type_add_interface_static(nType, TYPE_TABLE[i].aGetGIfaceType(), &aIfaceInfo); + } + } + return nType; +} + enum { CHILD_PROP_0, @@ -285,6 +360,13 @@ static void lo_accessible_accessible_init(GtkAccessibleInterface* iface) iface->get_platform_state = lo_accessible_get_platform_state; } +#if GTK_CHECK_VERSION(4, 10, 0) +static void lo_accessible_range_init(GtkAccessibleRangeInterface* iface) +{ + iface->set_current_value = lo_accessible_range_set_current_value; +} +#endif + G_DEFINE_TYPE_WITH_CODE(LoAccessible, lo_accessible, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE(GTK_TYPE_ACCESSIBLE, lo_accessible_accessible_init)) @@ -306,10 +388,30 @@ static LoAccessible* lo_accessible_new(GdkDisplay* pDisplay, GtkAccessible* pParent, const css::uno::Reference<css::accessibility::XAccessible>& rAccessible) { - LoAccessible* ret = LO_ACCESSIBLE(g_object_new(LO_TYPE_ACCESSIBLE, nullptr)); + assert(rAccessible.is()); + + GType nType = ensureTypeFor(rAccessible.get()); + LoAccessible* ret = LO_ACCESSIBLE(g_object_new(nType, nullptr)); ret->display = pDisplay; ret->parent = pParent; ret->uno_accessible = rAccessible; + + // set values from XAccessibleValue interface if that's implemented + css::uno::Reference<css::accessibility::XAccessibleContext> xContext( + ret->uno_accessible->getAccessibleContext()); + css::uno::Reference<css::accessibility::XAccessibleValue> xAccessibleValue(xContext, + css::uno::UNO_QUERY); + if (xAccessibleValue.is()) + { + double fCurrentValue = 0, fMinValue = 0, fMaxValue = 0; + xAccessibleValue->getCurrentValue() >>= fCurrentValue; + xAccessibleValue->getMinimumValue() >>= fMinValue; + xAccessibleValue->getMaximumValue() >>= fMaxValue; + gtk_accessible_update_property(GTK_ACCESSIBLE(ret), GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, + fCurrentValue, GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, fMinValue, + GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, fMaxValue, -1); + } + return ret; } @@ -409,6 +511,49 @@ static gboolean lo_accessible_get_platform_state(GtkAccessible* self, return false; } +#if GTK_CHECK_VERSION(4, 10, 0) +static gboolean lo_accessible_range_set_current_value(GtkAccessibleRange* self, double fNewValue) +{ + // return 'true' in any case, since otherwise no proper AT-SPI DBus reply gets sent + // and the app crashes, s. + // https://gitlab.gnome.org/GNOME/gtk/-/issues/6150 + // https://gitlab.gnome.org/GNOME/gtk/-/commit/0dbd2bd09eff8c9233e45338a05daf2a835529ab + + LoAccessible* pAccessible = LO_ACCESSIBLE(self); + if (!pAccessible->uno_accessible) + return true; + + css::uno::Reference<css::accessibility::XAccessibleContext> xContext( + pAccessible->uno_accessible->getAccessibleContext()); + + css::uno::Reference<css::accessibility::XAccessibleValue> xValue(xContext, css::uno::UNO_QUERY); + if (!xValue.is()) + return true; + + // Different types of numerical values for XAccessibleValue are possible. + // If current value has an integer type, also use that for the new value, to make + // sure underlying implementations expecting that can handle the value properly. + const css::uno::Any aCurrentValue = xValue->getCurrentValue(); + if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_LONG) + { + const sal_Int32 nValue = std::round<sal_Int32>(fNewValue); + xValue->setCurrentValue(css::uno::Any(nValue)); + return true; + } + else if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_HYPER) + { + const sal_Int64 nValue = std::round<sal_Int64>(fNewValue); + xValue->setCurrentValue(css::uno::Any(nValue)); + return true; + } + + css::uno::Any aValue; + aValue <<= fNewValue; + xValue->setCurrentValue(aValue); + return true; +} +#endif + static void lo_accessible_init(LoAccessible* /*iface*/) {} static GtkATContext* get_at_context(GtkAccessible* self)