summaryrefslogtreecommitdiff
path: root/gtk/a11y
diff options
context:
space:
mode:
Diffstat (limited to 'gtk/a11y')
-rw-r--r--gtk/a11y/atspi/Accessibility.xml22
-rw-r--r--gtk/a11y/atspi/Accessible.xml71
-rw-r--r--gtk/a11y/atspi/Action.xml38
-rw-r--r--gtk/a11y/atspi/Application.xml26
-rw-r--r--gtk/a11y/atspi/Cache.xml21
-rw-r--r--gtk/a11y/atspi/Collection.xml48
-rw-r--r--gtk/a11y/atspi/Component.xml86
-rw-r--r--gtk/a11y/atspi/DeviceEventController.xml64
-rw-r--r--gtk/a11y/atspi/DeviceEventListener.xml12
-rw-r--r--gtk/a11y/atspi/Document.xml24
-rw-r--r--gtk/a11y/atspi/EditableText.xml40
-rw-r--r--gtk/a11y/atspi/Event.xml193
-rw-r--r--gtk/a11y/atspi/Hyperlink.xml27
-rw-r--r--gtk/a11y/atspi/Hypertext.xml21
-rw-r--r--gtk/a11y/atspi/Image.xml27
-rw-r--r--gtk/a11y/atspi/Registry.xml30
-rw-r--r--gtk/a11y/atspi/Selection.xml42
-rw-r--r--gtk/a11y/atspi/Socket.xml27
-rw-r--r--gtk/a11y/atspi/Table.xml135
-rw-r--r--gtk/a11y/atspi/TableCell.xml22
-rw-r--r--gtk/a11y/atspi/Text.xml170
-rw-r--r--gtk/a11y/atspi/Value.xml14
-rw-r--r--gtk/a11y/atspi/meson.build65
-rw-r--r--gtk/a11y/gtkatspicache.c218
-rw-r--r--gtk/a11y/gtkatspicacheprivate.h44
-rw-r--r--gtk/a11y/gtkatspicontext.c1416
-rw-r--r--gtk/a11y/gtkatspicontextprivate.h42
-rw-r--r--gtk/a11y/gtkatspieditabletext.c356
-rw-r--r--gtk/a11y/gtkatspieditabletextprivate.h30
-rw-r--r--gtk/a11y/gtkatspipango.c1257
-rw-r--r--gtk/a11y/gtkatspipangoprivate.h62
-rw-r--r--gtk/a11y/gtkatspiprivate.h249
-rw-r--r--gtk/a11y/gtkatspiroot.c603
-rw-r--r--gtk/a11y/gtkatspirootprivate.h45
-rw-r--r--gtk/a11y/gtkatspiselection.c1060
-rw-r--r--gtk/a11y/gtkatspiselectionprivate.h39
-rw-r--r--gtk/a11y/gtkatspitext.c1452
-rw-r--r--gtk/a11y/gtkatspitextbuffer.c984
-rw-r--r--gtk/a11y/gtkatspitextbufferprivate.h55
-rw-r--r--gtk/a11y/gtkatspitextprivate.h45
-rw-r--r--gtk/a11y/gtkatspiutils.c307
-rw-r--r--gtk/a11y/gtkatspiutilsprivate.h34
-rw-r--r--gtk/a11y/gtkatspivalue.c129
-rw-r--r--gtk/a11y/gtkatspivalueprivate.h30
-rw-r--r--gtk/a11y/meson.build23
45 files changed, 9705 insertions, 0 deletions
diff --git a/gtk/a11y/atspi/Accessibility.xml b/gtk/a11y/atspi/Accessibility.xml
new file mode 100644
index 0000000000..a454aeb55b
--- /dev/null
+++ b/gtk/a11y/atspi/Accessibility.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<spec xmlns:xi="http://www.w3.org/2001/XInclude">
+ <xi:include href="Accessible.xml" parse="xml"/>
+ <xi:include href="Action.xml" parse="xml"/>
+ <xi:include href="Application.xml" parse="xml"/>
+ <xi:include href="Collection.xml" parse="xml"/>
+ <xi:include href="Component.xml" parse="xml"/>
+ <xi:include href="Document.xml" parse="xml"/>
+ <xi:include href="Hypertext.xml" parse="xml"/>
+ <xi:include href="Hyperlink.xml" parse="xml"/>
+ <xi:include href="Image.xml" parse="xml"/>
+ <xi:include href="Selection.xml" parse="xml"/>
+ <xi:include href="Table.xml" parse="xml"/>
+ <xi:include href="TableCell.xml" parse="xml"/>
+ <xi:include href="Text.xml" parse="xml"/>
+ <xi:include href="EditableText.xml" parse="xml"/>
+ <xi:include href="Cache.xml" parse="xml"/>
+ <xi:include href="Value.xml" parse="xml"/>
+ <xi:include href="Registry.xml" parse="xml"/>
+ <xi:include href="DeviceEventController.xml" parse="xml"/>
+ <xi:include href="DeviceEventListener.xml" parse="xml"/>
+</spec>
diff --git a/gtk/a11y/atspi/Accessible.xml b/gtk/a11y/atspi/Accessible.xml
new file mode 100644
index 0000000000..a274cdb902
--- /dev/null
+++ b/gtk/a11y/atspi/Accessible.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Accessible">
+
+ <property name="Name" type="s" access="read"/>
+
+ <property name="Description" type="s" access="read"/>
+
+ <property name="Parent" type="(so)" access="read">
+ <annotation name="com.trolltech.QtDBus.QtTypeName" value="QSpiObjectReference"/>
+ </property>
+
+ <property name="ChildCount" type="i" access="read"/>
+
+ <property name="Locale" type="s" access="read"/>
+
+ <property name="AccessibleId" type="s" access="read"/>
+
+ <method name="GetChildAtIndex">
+ <arg direction="in" name="index" type="i"/>
+ <arg direction="out" type="(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
+ </method>
+
+ <method name="GetChildren">
+ <arg direction="out" type="a(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReferenceArray"/>
+ </method>
+
+ <method name="GetIndexInParent">
+ <arg direction="out" type="i"/>
+ </method>
+
+ <method name="GetRelationSet">
+ <arg direction="out" type="a(ua(so))"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiRelationArray"/>
+ </method>
+
+ <method name="GetRole">
+ <arg direction="out" type="u"/>
+ </method>
+
+ <method name="GetRoleName">
+ <arg direction="out" type="s"/>
+ </method>
+
+ <method name="GetLocalizedRoleName">
+ <arg direction="out" type="s"/>
+ </method>
+
+ <method name="GetState">
+ <arg direction="out" type="au"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiIntList"/>
+ </method>
+
+ <method name="GetAttributes">
+ <arg direction="out" type="a{ss}"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiAttributeSet"/>
+ </method>
+
+ <method name="GetApplication">
+ <arg direction="out" type="(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
+ </method>
+
+ <method name="GetInterfaces">
+ <arg direction="out" type="as"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Action.xml b/gtk/a11y/atspi/Action.xml
new file mode 100644
index 0000000000..0f5aa850bd
--- /dev/null
+++ b/gtk/a11y/atspi/Action.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Action">
+
+ <property name="NActions" type="i" access="read"/>
+
+ <method name="GetDescription">
+ <arg type="i" name="index" direction="in"/>
+ <arg type="s" direction="out"/>
+ </method>
+
+ <method name="GetName">
+ <arg type="i" name="index" direction="in"/>
+ <arg type="s" direction="out"/>
+ </method>
+
+ <method name="GetLocalizedName">
+ <arg type="i" name="index" direction="in"/>
+ <arg type="s" direction="out"/>
+ </method>
+
+ <method name="GetKeyBinding">
+ <arg type="i" name="index" direction="in"/>
+ <arg type="s" direction="out"/>
+ </method>
+
+ <method name="GetActions">
+ <arg direction="out" type="a(sss)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiActionArray"/>
+ </method>
+
+ <method name="DoAction">
+ <arg direction="in" name="index" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Application.xml b/gtk/a11y/atspi/Application.xml
new file mode 100644
index 0000000000..2191f08858
--- /dev/null
+++ b/gtk/a11y/atspi/Application.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Application">
+
+ <property name="ToolkitName" type="s" access="read"/>
+
+ <property name="Version" type="s" access="read"/>
+
+ <property name="AtspiVersion" type="s" access="read"/>
+ <property name="Id" type="i" access="readwrite"/>
+
+ <method name="GetLocale">
+ <arg direction="in" name="lctype" type="u"/>
+ <arg direction="out" type="s"/>
+ </method>
+
+ <method name="RegisterEventListener">
+ <arg direction="in" name="event" type="s"/>
+ </method>
+
+ <method name="DeregisterEventListener">
+ <arg direction="in" name="event" type="s"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Cache.xml b/gtk/a11y/atspi/Cache.xml
new file mode 100644
index 0000000000..e693b54cdb
--- /dev/null
+++ b/gtk/a11y/atspi/Cache.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Cache">
+
+ <method name="GetItems">
+ <arg name="nodes" type="a((so)(so)iiassusau)" direction="out"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiAccessibleCacheArray"/>
+ </method>
+
+ <signal name="AddAccessible">
+ <arg name="nodeAdded" type="((so)(so)iiassusau)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiAccessibleCacheItem"/>
+ </signal>
+
+ <signal name="RemoveAccessible">
+ <arg name="nodeRemoved" type="(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/>
+ </signal>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Collection.xml b/gtk/a11y/atspi/Collection.xml
new file mode 100644
index 0000000000..7b39776a0a
--- /dev/null
+++ b/gtk/a11y/atspi/Collection.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Collection">
+
+ <method name="GetMatches">
+ <arg direction="in" name="rule" type="(auuasuauusub)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QSpiMatchRule"/>
+ <arg direction="in" name="sortby" type="u"/>
+ <arg direction="in" name="count" type="i"/>
+ <arg direction="in" name="traverse" type="b"/>
+ <arg direction="out" type="a(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiReferenceSet"/>
+ </method>
+
+ <method name="GetMatchesTo">
+ <arg direction="in" name="current_object" type="o"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/>
+ <arg direction="in" name="rule" type="(auuasuauusub)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QSpiMatchRule"/>
+ <arg direction="in" name="sortby" type="u"/>
+ <arg direction="in" name="tree" type="u"/>
+ <arg direction="in" name="limit_scope" type="b"/>
+ <arg direction="in" name="count" type="i"/>
+ <arg direction="in" name="traverse" type="b"/>
+ <arg direction="out" type="a(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiReferenceSet"/>
+ </method>
+
+ <method name="GetMatchesFrom">
+ <arg direction="in" name="current_object" type="o"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/>
+ <arg direction="in" name="rule" type="(auuasuauusub)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QSpiMatchRule"/>
+ <arg direction="in" name="sortby" type="u"/>
+ <arg direction="in" name="tree" type="u"/>
+ <arg direction="in" name="count" type="i"/>
+ <arg direction="in" name="traverse" type="b"/>
+ <arg direction="out" type="a(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiReferenceSet"/>
+ </method>
+
+ <method name="GetActiveDescendant">
+ <arg direction="out" type="(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiReferenceSet"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Component.xml b/gtk/a11y/atspi/Component.xml
new file mode 100644
index 0000000000..c1258d27c6
--- /dev/null
+++ b/gtk/a11y/atspi/Component.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Component">
+
+ <method name="Contains">
+ <arg direction="in" name="x" type="i"/>
+ <arg direction="in" name="y" type="i"/>
+ <arg direction="in" name="coord_type" type="u"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="GetAccessibleAtPoint">
+ <arg direction="in" name="x" type="i"/>
+ <arg direction="in" name="y" type="i"/>
+ <arg direction="in" name="coord_type" type="u"/>
+ <arg direction="out" type="(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
+ </method>
+
+ <method name="GetExtents">
+ <arg direction="in" name="coord_type" type="u"/>
+ <arg direction="out" type="(iiii)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiRect"/>
+ </method>
+
+ <method name="GetPosition">
+ <arg direction="in" name="coord_type" type="u"/>
+ <arg direction="out" name="x" type="i"/>
+ <arg direction="out" name="y" type="i"/>
+ </method>
+
+ <method name="GetSize">
+ <arg direction="out" name="width" type="i"/>
+ <arg direction="out" name="height" type="i"/>
+ </method>
+
+ <method name="GetLayer">
+ <arg direction="out" type="u"/>
+ </method>
+
+ <method name="GetMDIZOrder">
+ <arg direction="out" type="n"/>
+ </method>
+
+ <method name="GrabFocus">
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="GetAlpha">
+ <arg direction="out" type="d"/>
+ </method>
+
+ <method name="SetExtents">
+ <arg direction="in" name="x" type="i"/>
+ <arg direction="in" name="y" type="i"/>
+ <arg direction="in" name="width" type="i"/>
+ <arg direction="in" name="height" type="i"/>
+ <arg direction="in" name="coord_type" type="u"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="SetPosition">
+ <arg direction="in" name="x" type="i"/>
+ <arg direction="in" name="y" type="i"/>
+ <arg direction="in" name="coord_type" type="u"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="SetSize">
+ <arg direction="in" name="width" type="i"/>
+ <arg direction="in" name="height" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="ScrollTo">
+ <arg direction="in" name="type" type="u"/>
+ </method>
+
+ <method name="ScrollToPoint">
+ <arg direction="in" name="type" type="u"/>
+ <arg direction="in" name="x" type="i"/>
+ <arg direction="in" name="y" type="i"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/DeviceEventController.xml b/gtk/a11y/atspi/DeviceEventController.xml
new file mode 100644
index 0000000000..0af9aac27f
--- /dev/null
+++ b/gtk/a11y/atspi/DeviceEventController.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.DeviceEventController">
+
+ <method name="RegisterKeystrokeListener">
+ <arg direction="in" name="listener" type="o"/>
+ <arg direction="in" name="keys" type="a(iisi)">
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QSpiKeyTypeArray"/>
+ </arg>
+ <arg direction="in" name="mask" type="u"/>
+ <arg direction="in" name="type" type="au">
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In3" value="QSpiEventTypeArray"/>
+ </arg>
+ <arg direction="in" name="mode" type="(bbb)">
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In4" value="QSpiEventMode"/>
+ </arg>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="DeregisterKeystrokeListener">
+ <arg direction="in" name="listener" type="o"/>
+ <arg direction="in" name="keys" type="a(iisi)">
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QSpiKeyTypeArray"/>
+ </arg>
+ <arg direction="in" name="mask" type="u"/>
+ <arg direction="in" name="type" type="u"/>
+ </method>
+
+ <method name="RegisterDeviceEventListener">
+ <arg direction="in" name="listener" type="o"/>
+ <arg direction="in" name="types" type="u"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="DeregisterDeviceEventListener">
+ <arg direction="in" name="listener" type="o"/>
+ <arg direction="in" name="types" type="u"/>
+ </method>
+
+ <method name="GenerateKeyboardEvent">
+ <arg direction="in" name="keycode" type="i"/>
+ <arg direction="in" name="keystring" type="s"/>
+ <arg direction="in" name="type" type="u"/>
+ </method>
+
+ <method name="GenerateMouseEvent">
+ <arg direction="in" name="x" type="i"/>
+ <arg direction="in" name="y" type="i"/>
+ <arg direction="in" name="eventName" type="s"/>
+ </method>
+
+ <method name="NotifyListenersSync">
+ <arg direction="in" name="event" type="(uiuuisb)"/>
+ <arg direction="out" type="b"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiDeviceEvent"/>
+ </method>
+
+ <method name="NotifyListenersAsync">
+ <arg direction="in" name="event" type="(uiuuisb)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiDeviceEvent"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/DeviceEventListener.xml b/gtk/a11y/atspi/DeviceEventListener.xml
new file mode 100644
index 0000000000..a6dd3b60c8
--- /dev/null
+++ b/gtk/a11y/atspi/DeviceEventListener.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.DeviceEventListener">
+
+ <method name="NotifyEvent">
+ <arg direction="in" name="event" type="(uiuuisb)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiDeviceEvent"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Document.xml b/gtk/a11y/atspi/Document.xml
new file mode 100644
index 0000000000..d12a3060ca
--- /dev/null
+++ b/gtk/a11y/atspi/Document.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Document">
+
+ <property name="CurrentPageNumber" type="i" access="read"/>
+
+ <property name="PageCount" type="i" access="read"/>
+
+ <method name="GetLocale">
+ <arg direction="out" type="s"/>
+ </method>
+
+ <method name="GetAttributeValue">
+ <arg direction="in" name="attributename" type="s"/>
+ <arg direction="out" type="s"/>
+ </method>
+
+ <method name="GetAttributes">
+ <arg direction="out" type="{ss}"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiAttrubutes"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/EditableText.xml b/gtk/a11y/atspi/EditableText.xml
new file mode 100644
index 0000000000..09f62f5aa7
--- /dev/null
+++ b/gtk/a11y/atspi/EditableText.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.EditableText">
+
+ <method name="SetTextContents">
+ <arg direction="in" name="newContents" type="s"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="InsertText">
+ <arg direction="in" name="position" type="i"/>
+ <arg direction="in" name="text" type="s"/>
+ <arg direction="in" name="length" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="CopyText">
+ <arg direction="in" name="startPos" type="i"/>
+ <arg direction="in" name="endPos" type="i"/>
+ </method>
+
+ <method name="CutText">
+ <arg direction="in" name="startPos" type="i"/>
+ <arg direction="in" name="endPos" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="DeleteText">
+ <arg direction="in" name="startPos" type="i"/>
+ <arg direction="in" name="endPos" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="PasteText">
+ <arg direction="in" name="position" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Event.xml b/gtk/a11y/atspi/Event.xml
new file mode 100644
index 0000000000..fac67deb05
--- /dev/null
+++ b/gtk/a11y/atspi/Event.xml
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+
+<interface name="org.a11y.atspi.Event.Object">
+ <signal name="PropertyChange"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="BoundsChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="LinkSelected"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="StateChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="ChildrenChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="VisibleDataChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="SelectionChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="ModelChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="ActiveDescendantChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="RowInserted"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="RowReordered"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="RowDeleted"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="ColumnInserted"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="ColumnReordered"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="ColumnDeleted"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="TextBoundsChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="TextSelectionChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="TextChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="TextAttributesChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="TextCaretMoved"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="AttributesChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+</interface>
+
+<interface name="org.a11y.atspi.Event.Window">
+ <signal name="PropertyChange"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Minimize"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Maximize"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Restore"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Close"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Create"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Reparent"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="DesktopCreate"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="DesktopDestroy"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Destroy"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Activate"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Deactivate"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Raise"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Lower"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Move"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Resize"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Shade"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="uUshade"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Restyle"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+</interface>
+
+<interface name="org.a11y.atspi.Event.Mouse">
+ <signal name="Abs"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Rel"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Button"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+</interface>
+
+<interface name="org.a11y.atspi.Event.Keyboard">
+ <signal name="Modifiers"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+</interface>
+
+<interface name="org.a11y.atspi.Event.Terminal">
+ <signal name="LineChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="ColumncountChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="LinecountChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="ApplicationChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="CharwidthChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+</interface>
+
+<interface name="org.a11y.atspi.Event.Document">
+ <signal name="LoadComplete"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="Reload"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="LoadStopped"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="ContentChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="AttributesChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+ <signal name="PageChanged"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+</interface>
+
+<interface name="org.a11y.atspi.Event.Focus">
+ <signal name="Focus"><arg direction="in" type="(suuv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiEvent"/>
+ </signal>
+</interface>
+
+</node>
diff --git a/gtk/a11y/atspi/Hyperlink.xml b/gtk/a11y/atspi/Hyperlink.xml
new file mode 100644
index 0000000000..75f809931f
--- /dev/null
+++ b/gtk/a11y/atspi/Hyperlink.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Hyperlink">
+
+ <property name="NAnchors" type="n" access="read"/>
+
+ <property name="StartIndex" type="i" access="read"/>
+
+ <property name="EndIndex" type="i" access="read"/>
+
+ <method name="GetObject">
+ <arg direction="in" name="i" type="i"/>
+ <arg direction="out" type="(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
+ </method>
+
+ <method name="GetURI">
+ <arg direction="in" name="i" type="i"/>
+ <arg direction="out" type="s"/>
+ </method>
+
+ <method name="IsValid">
+ <arg direction="out" type="b"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Hypertext.xml b/gtk/a11y/atspi/Hypertext.xml
new file mode 100644
index 0000000000..18a3b342a8
--- /dev/null
+++ b/gtk/a11y/atspi/Hypertext.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Hypertext">
+
+ <method name="GetNLinks">
+ <arg direction="out" type="i"/>
+ </method>
+
+ <method name="GetLink">
+ <arg direction="in" name="linkIndex" type="i"/>
+ <arg direction="out" type="(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
+ </method>
+
+ <method name="GetLinkIndex">
+ <arg direction="in" name="characterIndex" type="i"/>
+ <arg direction="out" type="i"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Image.xml b/gtk/a11y/atspi/Image.xml
new file mode 100644
index 0000000000..43536ee956
--- /dev/null
+++ b/gtk/a11y/atspi/Image.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Image">
+
+ <property name="ImageDescription" type="s" access="read"/>
+
+ <property name="ImageLocale" type="s" access="read"/>
+
+ <method name="GetImageExtents">
+ <arg direction="in" name="coordType" type="u"/>
+ <arg direction="out" type="(iiii)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiRect"/>
+ </method>
+
+ <method name="GetImagePosition">
+ <arg direction="out" name="x" type="i"/>
+ <arg direction="out" name="y" type="i"/>
+ <arg direction="in" name="coordType" type="u"/>
+ </method>
+
+ <method name="GetImageSize">
+ <arg direction="out" name="width" type="i"/>
+ <arg direction="out" name="height" type="i"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Registry.xml b/gtk/a11y/atspi/Registry.xml
new file mode 100644
index 0000000000..a3ab93c3c2
--- /dev/null
+++ b/gtk/a11y/atspi/Registry.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Registry">
+
+ <method name="RegisterEvent">
+ <arg direction="in" name="event" type="s">
+ </arg>
+ </method>
+
+ <method name="DeregisterEvent">
+ <arg direction="in" name="event" type="s">
+ </arg>
+ </method>
+
+ <method name="GetRegisteredEvents">
+ <arg direction="out" name="events" type="a(ss)">
+ </arg>
+ </method>
+
+ <signal name="EventListenerRegistered">
+ <arg direction="out" name="bus" type="s"/>
+ <arg direction="out" name="path" type="s"/>
+ </signal>
+
+ <signal name="EventListenerDeregistered">
+ <arg direction="out" name="bus" type="s"/>
+ <arg direction="out" name="path" type="s"/>
+ </signal>
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Selection.xml b/gtk/a11y/atspi/Selection.xml
new file mode 100644
index 0000000000..2e1918757a
--- /dev/null
+++ b/gtk/a11y/atspi/Selection.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Selection">
+
+ <property name="NSelectedChildren" type="i" access="read"/>
+
+ <method name="GetSelectedChild">
+ <arg direction="in" name="selectedChildIndex" type="i"/>
+ <arg direction="out" type="(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
+ </method>
+
+ <method name="SelectChild">
+ <arg direction="in" name="childIndex" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="DeselectSelectedChild">
+ <arg direction="in" name="selectedChildIndex" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="IsChildSelected">
+ <arg direction="in" name="childIndex" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="SelectAll">
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="ClearSelection">
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="DeselectChild">
+ <arg direction="in" name="childIndex" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Socket.xml b/gtk/a11y/atspi/Socket.xml
new file mode 100644
index 0000000000..8da9948833
--- /dev/null
+++ b/gtk/a11y/atspi/Socket.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Socket">
+
+ <method name="Embed">
+ <arg direction="in" name="plug" type="(so)">
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/>
+ </arg>
+ <arg direction="out" name="socket" type="(so)">
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
+ </arg>
+ </method>
+
+ <method name="Unembed">
+ <arg direction="in" name="plug" type="(so)">
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/>
+ </arg>
+ </method>
+
+ <signal name="Available">
+ <arg direction="in" name="socket" type="(so)">
+ <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/>
+ </arg>
+ </signal>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Table.xml b/gtk/a11y/atspi/Table.xml
new file mode 100644
index 0000000000..181acaa6af
--- /dev/null
+++ b/gtk/a11y/atspi/Table.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Table">
+
+ <property name="NRows" type="i" access="read"/>
+
+ <property name="NColumns" type="i" access="read"/>
+
+ <property name="Caption" type="(so)" access="read">
+ <annotation name="com.trolltech.QtDBus.QtTypeName" value="QSpiObjectReference"/>
+ </property>
+
+ <property name="Summary" type="(so)" access="read">
+ <annotation name="com.trolltech.QtDBus.QtTypeName" value="QSpiObjectReference"/>
+ </property>
+
+ <property name="NSelectedRows" type="i" access="read"/>
+
+ <property name="NSelectedColumns" type="i" access="read"/>
+
+ <method name="GetAccessibleAt">
+ <arg direction="in" name="row" type="i"/>
+ <arg direction="in" name="column" type="i"/>
+ <arg direction="out" type="(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
+ </method>
+
+ <method name="GetIndexAt">
+ <arg direction="in" name="row" type="i"/>
+ <arg direction="in" name="column" type="i"/>
+ <arg direction="out" type="i"/>
+ </method>
+
+ <method name="GetRowAtIndex">
+ <arg direction="in" name="index" type="i"/>
+ <arg direction="out" type="i"/>
+ </method>
+
+ <method name="GetColumnAtIndex">
+ <arg direction="in" name="index" type="i"/>
+ <arg direction="out" type="i"/>
+ </method>
+
+ <method name="GetRowDescription">
+ <arg direction="in" name="row" type="i"/>
+ <arg direction="out" type="s"/>
+ </method>
+
+ <method name="GetColumnDescription">
+ <arg direction="in" name="column" type="i"/>
+ <arg direction="out" type="s"/>
+ </method>
+
+ <method name="GetRowExtentAt">
+ <arg direction="in" name="row" type="i"/>
+ <arg direction="in" name="column" type="i"/>
+ <arg direction="out" type="i"/>
+ </method>
+
+ <method name="GetColumnExtentAt">
+ <arg direction="in" name="row" type="i"/>
+ <arg direction="in" name="column" type="i"/>
+ <arg direction="out" type="i"/>
+ </method>
+
+ <method name="GetRowHeader">
+ <arg direction="in" name="row" type="i"/>
+ <arg direction="out" type="(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
+ </method>
+
+ <method name="GetColumnHeader">
+ <arg direction="in" name="column" type="i"/>
+ <arg direction="out" type="(so)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiObjectReference"/>
+ </method>
+
+ <method name="GetSelectedRows">
+ <arg direction="out" type="ai"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiIntList"/>
+ </method>
+
+ <method name="GetSelectedColumns">
+ <arg direction="out" type="ai"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiIntList"/>
+ </method>
+
+ <method name="IsRowSelected">
+ <arg direction="in" name="row" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="IsColumnSelected">
+ <arg direction="in" name="column" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="IsSelected">
+ <arg direction="in" name="row" type="i"/>
+ <arg direction="in" name="column" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="AddRowSelection">
+ <arg direction="in" name="row" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="AddColumnSelection">
+ <arg direction="in" name="column" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="RemoveRowSelection">
+ <arg direction="in" name="row" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="RemoveColumnSelection">
+ <arg direction="in" name="column" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="GetRowColumnExtentsAtIndex">
+ <arg direction="in" name="index" type="i"/>
+ <arg direction="out" type="b"/>
+ <arg direction="out" name="row" type="i"/>
+ <arg direction="out" name="col" type="i"/>
+ <arg direction="out" name="row_extents" type="i"/>
+ <arg direction="out" name="col_extents" type="i"/>
+ <arg direction="out" name="is_selected" type="b"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/TableCell.xml b/gtk/a11y/atspi/TableCell.xml
new file mode 100644
index 0000000000..c60a074fd4
--- /dev/null
+++ b/gtk/a11y/atspi/TableCell.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.TableCell">
+
+ <property access="read" name="ColumnSpan" type="i" />
+
+ <property access="read" name="Position" type="(ii)" />
+
+ <property access="read" name="RowSpan" type="i" />
+
+ <property access="read" name="Table" type="(so)" />
+
+ <method name="GetRowColumnSpan">
+ <arg direction="out" type="b" />
+ <arg direction="out" name="row" type="i" />
+ <arg direction="out" name="col" type="i" />
+ <arg direction="out" name="row_extents" type="i" />
+ <arg direction="out" name="col_extents" type="i" />
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Text.xml b/gtk/a11y/atspi/Text.xml
new file mode 100644
index 0000000000..313131e6f8
--- /dev/null
+++ b/gtk/a11y/atspi/Text.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Text">
+
+ <property name="CharacterCount" type="i" access="read"/>
+
+ <property name="CaretOffset" type="i" access="read"/>
+
+ <method name="GetStringAtOffset">
+ <arg direction="in" name="offset" type="i"/>
+ <arg direction="in" name="granularity" type="u"/>
+ <arg direction="out" type="s"/>
+ <arg direction="out" name="startOffset" type="i"/>
+ <arg direction="out" name="endOffset" type="i"/>
+ </method>
+
+ <method name="GetText">
+ <arg direction="in" name="startOffset" type="i"/>
+ <arg direction="in" name="endOffset" type="i"/>
+ <arg direction="out" type="s"/>
+ </method>
+
+ <method name="SetCaretOffset">
+ <arg direction="in" name="offset" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="GetTextBeforeOffset">
+ <arg direction="in" name="offset" type="i"/>
+ <arg direction="in" name="type" type="u"/>
+ <arg direction="out" type="s"/>
+ <arg direction="out" name="startOffset" type="i"/>
+ <arg direction="out" name="endOffset" type="i"/>
+ </method>
+
+ <method name="GetTextAtOffset">
+ <arg direction="in" name="offset" type="i"/>
+ <arg direction="in" name="type" type="u"/>
+ <arg direction="out" type="s"/>
+ <arg direction="out" name="startOffset" type="i"/>
+ <arg direction="out" name="endOffset" type="i"/>
+ </method>
+
+ <method name="GetTextAfterOffset">
+ <arg direction="in" name="offset" type="i"/>
+ <arg direction="in" name="type" type="u"/>
+ <arg direction="out" type="s"/>
+ <arg direction="out" name="startOffset" type="i"/>
+ <arg direction="out" name="endOffset" type="i"/>
+ </method>
+
+ <method name="GetCharacterAtOffset">
+ <arg name="offset" type="i" direction="in"/>
+ <arg type="i" direction="out"/>
+ </method>
+
+ <method name="GetAttributeValue">
+ <arg direction="in" name="offset" type="i"/>
+ <arg direction="in" name="attributeName" type="s"/>
+ <arg direction="out" type="s"/>
+ </method>
+
+ <method name="GetAttributes">
+ <arg direction="in" name="offset" type="i"/>
+ <arg direction="out" type="a{ss}"/>
+ <arg direction="out" name="startOffset" type="i"/>
+ <arg direction="out" name="endOffset" type="i"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiAttributeSet"/>
+ </method>
+
+ <method name="GetDefaultAttributes">
+ <arg direction="out" type="a{ss}"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiAttributeSet"/>
+ </method>
+
+ <method name="GetCharacterExtents">
+ <arg direction="in" name="offset" type="i"/>
+ <arg direction="out" name="x" type="i"/>
+ <arg direction="out" name="y" type="i"/>
+ <arg direction="out" name="width" type="i"/>
+ <arg direction="out" name="height" type="i"/>
+ <arg direction="in" name="coordType" type="u"/>
+ </method>
+
+ <method name="GetOffsetAtPoint">
+ <arg direction="in" name="x" type="i"/>
+ <arg direction="in" name="y" type="i"/>
+ <arg direction="in" name="coordType" type="u"/>
+ <arg direction="out" type="i"/>
+ </method>
+
+ <method name="GetNSelections">
+ <arg direction="out" type="i"/>
+ </method>
+
+ <method name="GetSelection">
+ <arg direction="in" name="selectionNum" type="i"/>
+ <arg direction="out" name="startOffset" type="i"/>
+ <arg direction="out" name="endOffset" type="i"/>
+ </method>
+
+ <method name="AddSelection">
+ <arg direction="in" name="startOffset" type="i"/>
+ <arg direction="in" name="endOffset" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="RemoveSelection">
+ <arg direction="in" name="selectionNum" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="SetSelection">
+ <arg direction="in" name="selectionNum" type="i"/>
+ <arg direction="in" name="startOffset" type="i"/>
+ <arg direction="in" name="endOffset" type="i"/>
+ <arg direction="out" type="b"/>
+ </method>
+
+ <method name="GetRangeExtents">
+ <arg direction="in" name="startOffset" type="i"/>
+ <arg direction="in" name="endOffset" type="i"/>
+ <arg direction="out" name="x" type="i"/>
+ <arg direction="out" name="y" type="i"/>
+ <arg direction="out" name="width" type="i"/>
+ <arg direction="out" name="height" type="i"/>
+ <arg direction="in" name="coordType" type="u"/>
+ </method>
+
+ <method name="GetBoundedRanges">
+ <arg direction="in" name="x" type="i"/>
+ <arg direction="in" name="y" type="i"/>
+ <arg direction="in" name="width" type="i"/>
+ <arg direction="in" name="height" type="i"/>
+ <arg direction="in" name="coordType" type="u"/>
+ <arg direction="in" name="xClipType" type="u"/>
+ <arg direction="in" name="yClipType" type="u"/>
+ <arg direction="out" type="a(iisv)"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiRangeList"/>
+ </method>
+
+ <method name="GetAttributeRun">
+ <arg direction="in" name="offset" type="i"/>
+ <arg direction="in" name="includeDefaults" type="b"/>
+ <arg direction="out" type="a{ss}"/>
+ <arg direction="out" name="startOffset" type="i"/>
+ <arg direction="out" name="endOffset" type="i"/>
+ <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QSpiAttributeSet"/>
+ </method>
+
+ <method name="GetDefaultAttributeSet">
+ <arg direction="out" type="a{ss}"/>
+ </method>
+
+ <method name="ScrollSubstringTo">
+ <arg direction="in" name="startOffset" type="i"/>
+ <arg direction="in" name="endOffset" type="i"/>
+ <arg direction="in" name="type" type="u"/>
+ </method>
+
+ <method name="ScrollSubstringToPoint">
+ <arg direction="in" name="startOffset" type="i"/>
+ <arg direction="in" name="endOffset" type="i"/>
+ <arg direction="in" name="type" type="u"/>
+ <arg direction="in" name="x" type="i"/>
+ <arg direction="in" name="y" type="i"/>
+ </method>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/Value.xml b/gtk/a11y/atspi/Value.xml
new file mode 100644
index 0000000000..ccd6c7aa59
--- /dev/null
+++ b/gtk/a11y/atspi/Value.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node name="/node">
+<interface name="org.a11y.atspi.Value">
+
+ <property name="MinimumValue" type="d" access="read"/>
+
+ <property name="MaximumValue" type="d" access="read"/>
+
+ <property name="MinimumIncrement" type="d" access="read"/>
+
+ <property name="CurrentValue" type="d" access="readwrite"/>
+
+</interface>
+</node>
diff --git a/gtk/a11y/atspi/meson.build b/gtk/a11y/atspi/meson.build
new file mode 100644
index 0000000000..05be90c2e7
--- /dev/null
+++ b/gtk/a11y/atspi/meson.build
@@ -0,0 +1,65 @@
+atspi_xml = [
+ 'Accessible.xml',
+ 'Action.xml',
+ 'Application.xml',
+ 'Cache.xml',
+ 'Collection.xml',
+ 'Component.xml',
+ 'DeviceEventController.xml',
+ 'DeviceEventListener.xml',
+ 'Document.xml',
+ 'EditableText.xml',
+ 'Event.xml',
+ 'Hyperlink.xml',
+ 'Hypertext.xml',
+ 'Image.xml',
+ 'Registry.xml',
+ 'Selection.xml',
+ 'Socket.xml',
+ 'Table.xml',
+ 'TableCell.xml',
+ 'Text.xml',
+ 'Value.xml',
+]
+
+gdbus_codegen = find_program('gdbus-codegen')
+atspi_src = []
+foreach xml: atspi_xml
+ obj_name = xml.split('.').get(0)
+
+ # We cannot use gnome.gdbus_codegen() directly because we only care about
+ # the interface definitions, not the whole GTypeInterface/GObject proxy
+ # classes
+ gen_hdr = custom_target('atspi-' + obj_name.to_lower() + '-hdr',
+ input: xml,
+ output: 'atspi-' + obj_name.to_lower() + '.h',
+ command: [
+ gdbus_codegen,
+ '--interface-prefix=org.a11y.atspi',
+ '--c-namespace=Atspi',
+ '--pragma-once',
+ '--interface-info-header',
+ '--output=@OUTPUT@',
+ '@INPUT@',
+ ],
+ build_by_default: true,
+ )
+
+ gen_src = custom_target('atspi-' + obj_name.to_lower() + '-src',
+ input: xml,
+ output: 'atspi-' + obj_name.to_lower() + '.c',
+ command: [
+ gdbus_codegen,
+ '--interface-prefix=org.a11y.atspi',
+ '--c-namespace=Atspi',
+ '--interface-info-body',
+ '--output=@OUTPUT@',
+ '@INPUT@',
+ ],
+ build_by_default: true,
+ )
+
+ atspi_src += [gen_src, gen_hdr]
+endforeach
+
+gtk_a11y_src += atspi_src
diff --git a/gtk/a11y/gtkatspicache.c b/gtk/a11y/gtkatspicache.c
new file mode 100644
index 0000000000..9681fd613e
--- /dev/null
+++ b/gtk/a11y/gtkatspicache.c
@@ -0,0 +1,218 @@
+/* gtkatspicache.c: AT-SPI object cache
+ *
+ * Copyright 2020 holder
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkatspicacheprivate.h"
+
+#include "gtkdebug.h"
+
+#include "a11y/atspi/atspi-cache.h"
+
+struct _GtkAtSpiCache
+{
+ GObject parent_instance;
+
+ char *cache_path;
+ GDBusConnection *connection;
+
+ GHashTable *contexts;
+};
+
+enum
+{
+ PROP_CACHE_PATH = 1,
+ PROP_CONNECTION,
+
+ N_PROPS
+};
+
+static GParamSpec *obj_props[N_PROPS];
+
+G_DEFINE_TYPE (GtkAtSpiCache, gtk_at_spi_cache, G_TYPE_OBJECT)
+
+static void
+gtk_at_spi_cache_finalize (GObject *gobject)
+{
+ GtkAtSpiCache *self = GTK_AT_SPI_CACHE (gobject);
+
+ g_clear_pointer (&self->contexts, g_hash_table_unref);
+ g_clear_object (&self->connection);
+ g_free (self->cache_path);
+
+ G_OBJECT_CLASS (gtk_at_spi_cache_parent_class)->finalize (gobject);
+}
+
+static void
+gtk_at_spi_cache_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAtSpiCache *self = GTK_AT_SPI_CACHE (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_CACHE_PATH:
+ g_free (self->cache_path);
+ self->cache_path = g_value_dup_string (value);
+ break;
+
+ case PROP_CONNECTION:
+ g_clear_object (&self->connection);
+ self->connection = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+handle_cache_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ g_printerr ("[Cache] Method '%s' on interface '%s' for object '%s' from '%s'\n",
+ method_name, interface_name, object_path, sender);
+
+}
+
+static GVariant *
+handle_cache_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GVariant *res = NULL;
+
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Unknown property '%s'", property_name);
+
+ return res;
+}
+
+
+static const GDBusInterfaceVTable cache_vtable = {
+ handle_cache_method,
+ handle_cache_get_property,
+ NULL,
+};
+
+static void
+gtk_at_spi_cache_constructed (GObject *gobject)
+{
+ GtkAtSpiCache *self = GTK_AT_SPI_CACHE (gobject);
+
+ g_assert (self->connection);
+ g_assert (self->cache_path);
+
+ g_dbus_connection_register_object (self->connection,
+ self->cache_path,
+ (GDBusInterfaceInfo *) &atspi_cache_interface,
+ &cache_vtable,
+ self,
+ NULL,
+ NULL);
+
+ GTK_NOTE (A11Y, g_message ("Cache registered at %s", self->cache_path));
+
+ G_OBJECT_CLASS (gtk_at_spi_cache_parent_class)->constructed (gobject);
+}
+
+static void
+gtk_at_spi_cache_class_init (GtkAtSpiCacheClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->constructed = gtk_at_spi_cache_constructed;
+ gobject_class->set_property = gtk_at_spi_cache_set_property;
+ gobject_class->finalize = gtk_at_spi_cache_finalize;
+
+ obj_props[PROP_CACHE_PATH] =
+ g_param_spec_string ("cache-path", NULL, NULL,
+ NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ obj_props[PROP_CONNECTION] =
+ g_param_spec_object ("connection", NULL, NULL,
+ G_TYPE_DBUS_CONNECTION,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, obj_props);
+}
+
+static void
+gtk_at_spi_cache_init (GtkAtSpiCache *self)
+{
+ self->contexts = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free,
+ NULL);
+}
+
+GtkAtSpiCache *
+gtk_at_spi_cache_new (GDBusConnection *connection,
+ const char *cache_path)
+{
+ g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+ g_return_val_if_fail (cache_path != NULL, NULL);
+
+ return g_object_new (GTK_TYPE_AT_SPI_CACHE,
+ "connection", connection,
+ "cache-path", cache_path,
+ NULL);
+}
+
+void
+gtk_at_spi_cache_add_context (GtkAtSpiCache *self,
+ const char *path,
+ GtkATContext *context)
+{
+ g_return_if_fail (GTK_IS_AT_SPI_CACHE (self));
+ g_return_if_fail (path != NULL);
+ g_return_if_fail (GTK_IS_AT_CONTEXT (context));
+
+ if (g_hash_table_contains (self->contexts, path))
+ return;
+
+ g_hash_table_insert (self->contexts, g_strdup (path), context);
+}
+
+GtkATContext *
+gtk_at_spi_cache_get_context (GtkAtSpiCache *self,
+ const char *path)
+{
+ g_return_val_if_fail (GTK_IS_AT_SPI_CACHE (self), NULL);
+ g_return_val_if_fail (path != NULL, NULL);
+
+ return g_hash_table_lookup (self->contexts, path);
+}
diff --git a/gtk/a11y/gtkatspicacheprivate.h b/gtk/a11y/gtkatspicacheprivate.h
new file mode 100644
index 0000000000..12952a3c87
--- /dev/null
+++ b/gtk/a11y/gtkatspicacheprivate.h
@@ -0,0 +1,44 @@
+/* gtkatspicacheprivate.h: AT-SPI object cache
+ *
+ * Copyright 2020 GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "gtkatcontextprivate.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_AT_SPI_CACHE (gtk_at_spi_cache_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkAtSpiCache, gtk_at_spi_cache, GTK, AT_SPI_CACHE, GObject)
+
+GtkAtSpiCache *
+gtk_at_spi_cache_new (GDBusConnection *connection,
+ const char *cache_path);
+
+void
+gtk_at_spi_cache_add_context (GtkAtSpiCache *self,
+ const char *path,
+ GtkATContext *context);
+
+GtkATContext *
+gtk_at_spi_cache_get_context (GtkAtSpiCache *self,
+ const char *path);
+
+G_END_DECLS
diff --git a/gtk/a11y/gtkatspicontext.c b/gtk/a11y/gtkatspicontext.c
new file mode 100644
index 0000000000..36f4636862
--- /dev/null
+++ b/gtk/a11y/gtkatspicontext.c
@@ -0,0 +1,1416 @@
+/* gtkatspicontext.c: AT-SPI GtkATContext implementation
+ *
+ * Copyright 2020 GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkatspicontextprivate.h"
+
+#include "gtkaccessibleprivate.h"
+
+#include "gtkatspicacheprivate.h"
+#include "gtkatspirootprivate.h"
+#include "gtkatspiprivate.h"
+#include "gtkatspiutilsprivate.h"
+#include "gtkatspitextprivate.h"
+#include "gtkatspieditabletextprivate.h"
+#include "gtkatspivalueprivate.h"
+#include "gtkatspiselectionprivate.h"
+
+#include "a11y/atspi/atspi-accessible.h"
+#include "a11y/atspi/atspi-text.h"
+#include "a11y/atspi/atspi-editabletext.h"
+#include "a11y/atspi/atspi-value.h"
+#include "a11y/atspi/atspi-selection.h"
+
+#include "gtkdebug.h"
+#include "gtkeditable.h"
+#include "gtkentryprivate.h"
+#include "gtkroot.h"
+#include "gtktextview.h"
+#include "gtkwindow.h"
+#include "gtkstack.h"
+
+#include <gio/gio.h>
+
+#include <locale.h>
+
+#if defined(GDK_WINDOWING_WAYLAND)
+# include <gdk/wayland/gdkwaylanddisplay.h>
+#endif
+#if defined(GDK_WINDOWING_X11)
+# include <gdk/x11/gdkx11display.h>
+# include <gdk/x11/gdkx11property.h>
+#endif
+
+/* We create a GtkAtSpiContext object for (almost) every widget.
+ *
+ * Each context implements a number of Atspi interfaces on a D-Bus
+ * object. The context objects are connected into a tree by the
+ * Parent property and GetChildAtIndex method of the Accessible
+ * interface.
+ *
+ * The tree is an almost perfect mirror image of the widget tree,
+ * with a few notable exceptions:
+ *
+ * - We don't create contexts for the GtkText widgets inside
+ * entry wrappers, since the Text functionality is represented
+ * on the entry contexts.
+ *
+ * - We insert non-widget backed context objects for each page
+ * of a stack. The main purpose of these extra context is to
+ * hold the TAB_PANEL role and be the target of the CONTROLS
+ * relation with their corresponding tabs (in the stack
+ * switcher or notebook).
+ */
+
+struct _GtkAtSpiContext
+{
+ GtkATContext parent_instance;
+
+ /* The root object, used as a entry point */
+ GtkAtSpiRoot *root;
+
+ /* The cache object, used to retrieve ATContexts */
+ GtkAtSpiCache *cache;
+
+ /* The address for the ATSPI accessibility bus */
+ char *bus_address;
+
+ /* The object path of the ATContext on the bus */
+ char *context_path;
+
+ /* Just a pointer; the connection is owned by the GtkAtSpiRoot
+ * associated to the GtkATContext
+ */
+ GDBusConnection *connection;
+
+ /* Accerciser refuses to work unless we implement a GetInterface
+ * call that returns a list of all implemented interfaces. We
+ * collect the answer here.
+ */
+ GVariant *interfaces;
+
+ guint registration_ids[20];
+ guint n_registered_objects;
+};
+
+enum
+{
+ PROP_BUS_ADDRESS = 1,
+
+ N_PROPS
+};
+
+static GParamSpec *obj_props[N_PROPS];
+
+G_DEFINE_TYPE (GtkAtSpiContext, gtk_at_spi_context, GTK_TYPE_AT_CONTEXT)
+
+static void
+set_atspi_state (guint64 *states,
+ AtspiStateType state)
+{
+ *states |= (G_GUINT64_CONSTANT (1) << state);
+}
+
+static void
+unset_atspi_state (guint64 *states,
+ AtspiStateType state)
+{
+ *states &= ~(G_GUINT64_CONSTANT (1) << state);
+}
+
+static void
+collect_states (GtkAtSpiContext *self,
+ GVariantBuilder *builder)
+{
+ GtkATContext *ctx = GTK_AT_CONTEXT (self);
+ GtkAccessibleValue *value;
+ GtkAccessible *accessible;
+ guint64 states = 0;
+
+ accessible = gtk_at_context_get_accessible (ctx);
+
+ set_atspi_state (&states, ATSPI_STATE_VISIBLE);
+
+ if (ctx->accessible_role == GTK_ACCESSIBLE_ROLE_TEXT_BOX ||
+ ctx->accessible_role == GTK_ACCESSIBLE_ROLE_SEARCH_BOX ||
+ ctx->accessible_role == GTK_ACCESSIBLE_ROLE_SPIN_BUTTON)
+ set_atspi_state (&states, ATSPI_STATE_EDITABLE);
+
+ if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_READ_ONLY))
+ {
+ value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_READ_ONLY);
+ if (gtk_boolean_accessible_value_get (value))
+ {
+ set_atspi_state (&states, ATSPI_STATE_READ_ONLY);
+ unset_atspi_state (&states, ATSPI_STATE_EDITABLE);
+ }
+ }
+
+ if (gtk_accessible_get_platform_state (accessible, GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE))
+ set_atspi_state (&states, ATSPI_STATE_FOCUSABLE);
+
+ if (gtk_accessible_get_platform_state (accessible, GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED))
+ set_atspi_state (&states, ATSPI_STATE_FOCUSED);
+
+ if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_ORIENTATION))
+ {
+ value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_ORIENTATION);
+ if (gtk_orientation_accessible_value_get (value) == GTK_ORIENTATION_HORIZONTAL)
+ set_atspi_state (&states, ATSPI_STATE_HORIZONTAL);
+ else
+ set_atspi_state (&states, ATSPI_STATE_VERTICAL);
+ }
+
+ if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MODAL))
+ {
+ value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MODAL);
+ if (gtk_boolean_accessible_value_get (value))
+ set_atspi_state (&states, ATSPI_STATE_MODAL);
+ }
+
+ if (gtk_at_context_has_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MULTI_LINE))
+ {
+ value = gtk_at_context_get_accessible_property (ctx, GTK_ACCESSIBLE_PROPERTY_MULTI_LINE);
+ if (gtk_boolean_accessible_value_get (value))
+ set_atspi_state (&states, ATSPI_STATE_MULTI_LINE);
+ }
+
+ if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_BUSY))
+ {
+ value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_BUSY);
+ if (gtk_boolean_accessible_value_get (value))
+ set_atspi_state (&states, ATSPI_STATE_BUSY);
+ }
+
+ if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_CHECKED))
+ {
+ value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_CHECKED);
+ switch (gtk_tristate_accessible_value_get (value))
+ {
+ case GTK_ACCESSIBLE_TRISTATE_TRUE:
+ set_atspi_state (&states, ATSPI_STATE_CHECKED);
+ break;
+ case GTK_ACCESSIBLE_TRISTATE_MIXED:
+ set_atspi_state (&states, ATSPI_STATE_INDETERMINATE);
+ break;
+ case GTK_ACCESSIBLE_TRISTATE_FALSE:
+ default:
+ break;
+ }
+ }
+
+ if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_DISABLED))
+ {
+ value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_DISABLED);
+ if (!gtk_boolean_accessible_value_get (value))
+ set_atspi_state (&states, ATSPI_STATE_SENSITIVE);
+ }
+ else
+ set_atspi_state (&states, ATSPI_STATE_SENSITIVE);
+
+ if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_EXPANDED))
+ {
+ value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_EXPANDED);
+ if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN)
+ {
+ set_atspi_state (&states, ATSPI_STATE_EXPANDABLE);
+ if (gtk_boolean_accessible_value_get (value))
+ set_atspi_state (&states, ATSPI_STATE_EXPANDED);
+ }
+ }
+
+ if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_INVALID))
+ {
+ value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_INVALID);
+ switch (gtk_invalid_accessible_value_get (value))
+ {
+ case GTK_ACCESSIBLE_INVALID_TRUE:
+ case GTK_ACCESSIBLE_INVALID_GRAMMAR:
+ case GTK_ACCESSIBLE_INVALID_SPELLING:
+ set_atspi_state (&states, ATSPI_STATE_INVALID);
+ break;
+ case GTK_ACCESSIBLE_INVALID_FALSE:
+ default:
+ break;
+ }
+ }
+
+ if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_PRESSED))
+ {
+ value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_PRESSED);
+ switch (gtk_tristate_accessible_value_get (value))
+ {
+ case GTK_ACCESSIBLE_TRISTATE_TRUE:
+ set_atspi_state (&states, ATSPI_STATE_PRESSED);
+ break;
+ case GTK_ACCESSIBLE_TRISTATE_MIXED:
+ set_atspi_state (&states, ATSPI_STATE_INDETERMINATE);
+ break;
+ case GTK_ACCESSIBLE_TRISTATE_FALSE:
+ default:
+ break;
+ }
+ }
+
+ if (gtk_at_context_has_accessible_state (ctx, GTK_ACCESSIBLE_STATE_SELECTED))
+ {
+ value = gtk_at_context_get_accessible_state (ctx, GTK_ACCESSIBLE_STATE_SELECTED);
+ if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN)
+ {
+ set_atspi_state (&states, ATSPI_STATE_SELECTABLE);
+ if (gtk_boolean_accessible_value_get (value))
+ set_atspi_state (&states, ATSPI_STATE_SELECTED);
+ }
+ }
+
+ g_variant_builder_add (builder, "u", (guint32) (states & 0xffffffff));
+ g_variant_builder_add (builder, "u", (guint32) (states >> 32));
+}
+
+static void
+collect_relations (GtkAtSpiContext *self,
+ GVariantBuilder *builder)
+{
+ GtkATContext *ctx = GTK_AT_CONTEXT (self);
+ struct {
+ GtkAccessibleRelation r;
+ AtspiRelationType s;
+ } map[] = {
+ { GTK_ACCESSIBLE_RELATION_LABELLED_BY, ATSPI_RELATION_LABELLED_BY },
+ { GTK_ACCESSIBLE_RELATION_CONTROLS, ATSPI_RELATION_CONTROLLER_FOR },
+ { GTK_ACCESSIBLE_RELATION_DESCRIBED_BY, ATSPI_RELATION_DESCRIBED_BY },
+ { GTK_ACCESSIBLE_RELATION_FLOW_TO, ATSPI_RELATION_FLOWS_TO},
+ };
+ GtkAccessibleValue *value;
+ GList *list, *l;
+ GtkATContext *target_ctx;
+ const char *unique_name;
+ int i;
+
+ unique_name = g_dbus_connection_get_unique_name (self->connection);
+
+ for (i = 0; i < G_N_ELEMENTS (map); i++)
+ {
+ if (!gtk_at_context_has_accessible_relation (ctx, map[i].r))
+ continue;
+
+ GVariantBuilder b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)"));
+
+ value = gtk_at_context_get_accessible_relation (ctx, map[i].r);
+ list = gtk_reference_list_accessible_value_get (value);
+
+ for (l = list; l; l = l->next)
+ {
+ target_ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (l->data));
+ g_variant_builder_add (&b, "(so)",
+ unique_name,
+ GTK_AT_SPI_CONTEXT (target_ctx)->context_path);
+ }
+
+ g_variant_builder_add (builder, "(ua(so))", map[i].s, &b);
+ }
+}
+
+static int
+get_index_in_parent (GtkWidget *widget)
+{
+ GtkWidget *parent = gtk_widget_get_parent (widget);
+ GtkWidget *child;
+ int idx;
+
+ idx = 0;
+ for (child = gtk_widget_get_first_child (parent);
+ child;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child)))
+ continue;
+
+ if (child == widget)
+ return idx;
+
+ idx++;
+ }
+
+ return -1;
+}
+
+static int
+get_index_in_toplevels (GtkWidget *widget)
+{
+ GListModel *toplevels = gtk_window_get_toplevels ();
+ guint n_toplevels = g_list_model_get_n_items (toplevels);
+ GtkWidget *window;
+ int idx;
+
+ idx = 0;
+ for (guint i = 0; i < n_toplevels; i++)
+ {
+ window = g_list_model_get_item (toplevels, i);
+
+ g_object_unref (window);
+
+ if (!gtk_widget_get_visible (window))
+ continue;
+
+ if (window == widget)
+ return idx;
+
+ idx += 1;
+ }
+
+ return -1;
+}
+
+static void
+handle_accessible_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkAtSpiContext *self = user_data;
+
+ if (g_strcmp0 (method_name, "GetRole") == 0)
+ {
+ guint atspi_role = gtk_atspi_role_for_context (GTK_AT_CONTEXT (self));
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", atspi_role));
+ }
+ else if (g_strcmp0 (method_name, "GetRoleName") == 0)
+ {
+ GtkAccessibleRole role = gtk_at_context_get_accessible_role (GTK_AT_CONTEXT (self));
+ const char *name = gtk_accessible_role_to_name (role, NULL);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", name));
+ }
+ else if (g_strcmp0 (method_name, "GetLocalizedRoleName") == 0)
+ {
+ GtkAccessibleRole role = gtk_at_context_get_accessible_role (GTK_AT_CONTEXT (self));
+ const char *name = gtk_accessible_role_to_name (role, GETTEXT_PACKAGE);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", name));
+ }
+ else if (g_strcmp0 (method_name, "GetState") == 0)
+ {
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(au)"));
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("au"));
+ collect_states (self, &builder);
+ g_variant_builder_close (&builder);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder));
+ }
+ else if (g_strcmp0 (method_name, "GetAttributes") == 0)
+ {
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(a{ss})"));
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{ss}"));
+ g_variant_builder_add (&builder, "{ss}", "toolkit", "GTK");
+
+ if (gtk_at_context_has_accessible_property (GTK_AT_CONTEXT (self), GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER))
+ {
+ GtkAccessibleValue *value;
+
+ value = gtk_at_context_get_accessible_property (GTK_AT_CONTEXT (self), GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER);
+
+ g_variant_builder_add (&builder, "{ss}",
+ "placeholder-text", gtk_string_accessible_value_get (value));
+ }
+
+ g_variant_builder_close (&builder);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder));
+ }
+ else if (g_strcmp0 (method_name, "GetApplication") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(@(so))", gtk_at_spi_root_to_ref (self->root)));
+ }
+ else if (g_strcmp0 (method_name, "GetChildAtIndex") == 0)
+ {
+ GtkWidget *child = NULL;
+ int idx, real_idx = 0;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
+ if (GTK_IS_WIDGET (accessible))
+ {
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ real_idx = 0;
+ for (child = gtk_widget_get_first_child (widget);
+ child;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child)))
+ continue;
+
+ if (real_idx == idx)
+ break;
+
+ real_idx += 1;
+ }
+ }
+ else if (GTK_IS_STACK_PAGE (accessible))
+ {
+ if (idx == 0)
+ {
+ child = gtk_stack_page_get_child (GTK_STACK_PAGE (accessible));
+ if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child)))
+ child = NULL;
+ }
+ }
+
+ if (child == NULL)
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "No child with index %d", idx);
+ return;
+ }
+
+ GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child));
+
+ const char *name = g_dbus_connection_get_unique_name (self->connection);
+ const char *path = gtk_at_spi_context_get_context_path (GTK_AT_SPI_CONTEXT (context));
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("((so))", name, path));
+ }
+ else if (g_strcmp0 (method_name, "GetChildren") == 0)
+ {
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)"));
+
+ GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
+ if (GTK_IS_WIDGET (accessible))
+ {
+ GtkWidget *widget = GTK_WIDGET (accessible);
+ GtkWidget *child;
+
+ for (child = gtk_widget_get_first_child (widget);
+ child;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child)))
+ continue;
+
+ GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child));
+
+ const char *name = g_dbus_connection_get_unique_name (self->connection);
+ const char *path = gtk_at_spi_context_get_context_path (GTK_AT_SPI_CONTEXT (context));
+
+ g_variant_builder_add (&builder, "(so)", name, path);
+ }
+ }
+ else if (GTK_IS_STACK_PAGE (accessible))
+ {
+ GtkWidget *child = gtk_stack_page_get_child (GTK_STACK_PAGE (accessible));
+
+ if (gtk_accessible_should_present (GTK_ACCESSIBLE (child)))
+ {
+ GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child));
+ const char *name = g_dbus_connection_get_unique_name (self->connection);
+ const char *path = gtk_at_spi_context_get_context_path (GTK_AT_SPI_CONTEXT (context));
+ g_variant_builder_add (&builder, "(so)", name, path);
+ }
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(so))", &builder));
+ }
+ else if (g_strcmp0 (method_name, "GetIndexInParent") == 0)
+ {
+ GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
+ int idx;
+
+ if (GTK_IS_ROOT (accessible))
+ idx = get_index_in_toplevels (GTK_WIDGET (accessible));
+ else if (GTK_IS_STACK_PAGE (accessible))
+ idx = get_index_in_parent (gtk_stack_page_get_child (GTK_STACK_PAGE (accessible)));
+ else if (GTK_IS_STACK (gtk_widget_get_parent (GTK_WIDGET (accessible))))
+ idx = 1;
+ else
+ idx = get_index_in_parent (GTK_WIDGET (accessible));
+
+ if (idx == -1)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Not found");
+ else
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", idx));
+ }
+ else if (g_strcmp0 (method_name, "GetRelationSet") == 0)
+ {
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(ua(so))"));
+ collect_relations (self, &builder);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(ua(so)))", &builder));
+ }
+ else if (g_strcmp0 (method_name, "GetInterfaces") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@as)", self->interfaces));
+ }
+
+}
+
+static GVariant *
+handle_accessible_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkAtSpiContext *self = user_data;
+ GVariant *res = NULL;
+
+ GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
+
+ if (g_strcmp0 (property_name, "Name") == 0)
+ {
+ if (GTK_IS_WIDGET (accessible))
+ res = g_variant_new_string (gtk_widget_get_name (GTK_WIDGET (accessible)));
+ else if (GTK_IS_STACK_PAGE (accessible))
+ {
+ const char *name = gtk_stack_page_get_name (GTK_STACK_PAGE (accessible));
+ if (name == NULL)
+ name = G_OBJECT_TYPE_NAME (accessible);
+ res = g_variant_new_string (name);
+ }
+ else
+ res = g_variant_new_string (G_OBJECT_TYPE_NAME (accessible));
+ }
+ else if (g_strcmp0 (property_name, "Description") == 0)
+ {
+ char *label = gtk_at_context_get_label (GTK_AT_CONTEXT (self));
+ res = g_variant_new_string (label);
+ g_free (label);
+ }
+ else if (g_strcmp0 (property_name, "Locale") == 0)
+ res = g_variant_new_string (setlocale (LC_MESSAGES, NULL));
+ else if (g_strcmp0 (property_name, "AccessibleId") == 0)
+ res = g_variant_new_string ("");
+ else if (g_strcmp0 (property_name, "Parent") == 0)
+ {
+ if (GTK_IS_WIDGET (accessible))
+ {
+ GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (accessible));
+
+ if (parent == NULL)
+ {
+ res = gtk_at_spi_root_to_ref (self->root);
+ }
+ else if (GTK_IS_STACK (parent))
+ {
+ GtkStackPage *page =
+ gtk_stack_get_page (GTK_STACK (parent), GTK_WIDGET (accessible));
+ GtkATContext *parent_context =
+ gtk_accessible_get_at_context (GTK_ACCESSIBLE (page));
+
+ if (parent_context != NULL)
+ res = g_variant_new ("(so)",
+ g_dbus_connection_get_unique_name (self->connection),
+ GTK_AT_SPI_CONTEXT (parent_context)->context_path);
+ }
+ else
+ {
+ GtkATContext *parent_context =
+ gtk_accessible_get_at_context (GTK_ACCESSIBLE (parent));
+
+ if (parent_context != NULL)
+ res = g_variant_new ("(so)",
+ g_dbus_connection_get_unique_name (self->connection),
+ GTK_AT_SPI_CONTEXT (parent_context)->context_path);
+ }
+ }
+ else if (GTK_IS_STACK_PAGE (accessible))
+ {
+ GtkWidget *parent = gtk_widget_get_parent (gtk_stack_page_get_child (GTK_STACK_PAGE (accessible)));
+ GtkATContext *parent_context =
+ gtk_accessible_get_at_context (GTK_ACCESSIBLE (parent));
+
+ if (parent_context != NULL)
+ res = g_variant_new ("(so)",
+ g_dbus_connection_get_unique_name (self->connection),
+ GTK_AT_SPI_CONTEXT (parent_context)->context_path);
+ }
+
+ if (res == NULL)
+ res = gtk_at_spi_null_ref ();
+ }
+ else if (g_strcmp0 (property_name, "ChildCount") == 0)
+ {
+ int n_children = 0;
+
+ if (GTK_IS_WIDGET (accessible))
+ {
+ GtkWidget *child;
+
+ for (child = gtk_widget_get_first_child (GTK_WIDGET (accessible));
+ child;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ if (!gtk_accessible_should_present (GTK_ACCESSIBLE (child)))
+ continue;
+
+ n_children++;
+ }
+ }
+ else if (GTK_IS_STACK_PAGE (accessible))
+ {
+ n_children = 1;
+ }
+
+ res = g_variant_new_int32 (n_children);
+ }
+ else
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Unknown property '%s'", property_name);
+
+ return res;
+}
+
+static const GDBusInterfaceVTable accessible_vtable = {
+ handle_accessible_method,
+ handle_accessible_get_property,
+ NULL,
+};
+
+static void
+gtk_at_spi_context_register_object (GtkAtSpiContext *self)
+{
+ GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
+ GVariantBuilder interfaces = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_STRING_ARRAY);
+ const GDBusInterfaceVTable *vtable;
+
+ g_variant_builder_add (&interfaces, "s", atspi_accessible_interface.name);
+ self->registration_ids[self->n_registered_objects] =
+ g_dbus_connection_register_object (self->connection,
+ self->context_path,
+ (GDBusInterfaceInfo *) &atspi_accessible_interface,
+ &accessible_vtable,
+ self,
+ NULL,
+ NULL);
+ self->n_registered_objects++;
+
+ vtable = gtk_atspi_get_text_vtable (accessible);
+ if (vtable)
+ {
+ g_variant_builder_add (&interfaces, "s", atspi_text_interface.name);
+ self->registration_ids[self->n_registered_objects] =
+ g_dbus_connection_register_object (self->connection,
+ self->context_path,
+ (GDBusInterfaceInfo *) &atspi_text_interface,
+ vtable,
+ self,
+ NULL,
+ NULL);
+ self->n_registered_objects++;
+ }
+
+ vtable = gtk_atspi_get_editable_text_vtable (accessible);
+ if (vtable)
+ {
+ g_variant_builder_add (&interfaces, "s", atspi_editable_text_interface.name);
+ self->registration_ids[self->n_registered_objects] =
+ g_dbus_connection_register_object (self->connection,
+ self->context_path,
+ (GDBusInterfaceInfo *) &atspi_editable_text_interface,
+ vtable,
+ self,
+ NULL,
+ NULL);
+ self->n_registered_objects++;
+ }
+ vtable = gtk_atspi_get_value_vtable (accessible);
+ if (vtable)
+ {
+ g_variant_builder_add (&interfaces, "s", atspi_value_interface.name);
+ self->registration_ids[self->n_registered_objects] =
+ g_dbus_connection_register_object (self->connection,
+ self->context_path,
+ (GDBusInterfaceInfo *) &atspi_value_interface,
+ vtable,
+ self,
+ NULL,
+ NULL);
+ self->n_registered_objects++;
+ }
+
+ /* Calling gtk_accessible_get_accessible_role() in here will recurse,
+ * so pass the role in explicitly.
+ */
+ vtable = gtk_atspi_get_selection_vtable (accessible,
+ GTK_AT_CONTEXT (self)->accessible_role);
+ if (vtable)
+ {
+ g_variant_builder_add (&interfaces, "s", atspi_selection_interface.name);
+ self->registration_ids[self->n_registered_objects] =
+ g_dbus_connection_register_object (self->connection,
+ self->context_path,
+ (GDBusInterfaceInfo *) &atspi_selection_interface,
+ vtable,
+ self,
+ NULL,
+ NULL);
+ self->n_registered_objects++;
+ }
+
+ self->interfaces = g_variant_ref_sink (g_variant_builder_end (&interfaces));
+}
+
+static void
+gtk_at_spi_context_unregister_object (GtkAtSpiContext *self)
+{
+ while (self->n_registered_objects > 0)
+ {
+ self->n_registered_objects--;
+ g_dbus_connection_unregister_object (self->connection,
+ self->registration_ids[self->n_registered_objects]);
+ self->registration_ids[self->n_registered_objects] = 0;
+ }
+}
+
+static void
+emit_text_changed (GtkAtSpiContext *self,
+ const char *kind,
+ int start,
+ int end,
+ const char *text)
+{
+ g_dbus_connection_emit_signal (self->connection,
+ NULL,
+ self->context_path,
+ "org.a11y.atspi.Event.Object",
+ "TextChanged",
+ g_variant_new ("(siiva{sv})",
+ kind, start, end, g_variant_new_string (text), NULL),
+ NULL);
+}
+
+static void
+emit_text_selection_changed (GtkAtSpiContext *self,
+ const char *kind,
+ int cursor_position)
+{
+ g_dbus_connection_emit_signal (self->connection,
+ NULL,
+ self->context_path,
+ "org.a11y.atspi.Event.Object",
+ "TextChanged",
+ g_variant_new ("(siiva{sv})",
+ kind, cursor_position, 0, g_variant_new_string (""), NULL),
+ NULL);
+}
+
+static void
+emit_selection_changed (GtkAtSpiContext *self,
+ const char *kind)
+{
+ g_dbus_connection_emit_signal (self->connection,
+ NULL,
+ self->context_path,
+ "org.a11y.atspi.Event.Object",
+ "SelectionChanged",
+ g_variant_new ("(siiva{sv})",
+ "", 0, 0, g_variant_new_string (""), NULL),
+ NULL);
+}
+
+static void
+emit_state_changed (GtkAtSpiContext *self,
+ const char *name,
+ gboolean enabled)
+{
+ g_dbus_connection_emit_signal (self->connection,
+ NULL,
+ self->context_path,
+ "org.a11y.atspi.Event.Object",
+ "StateChanged",
+ g_variant_new ("(siiva{sv})",
+ name, enabled, 0, g_variant_new_string ("0"), NULL),
+ NULL);
+}
+
+static void
+emit_property_changed (GtkAtSpiContext *self,
+ const char *name,
+ GVariant *value)
+{
+ g_dbus_connection_emit_signal (self->connection,
+ NULL,
+ self->context_path,
+ "org.a11y.atspi.Event.Object",
+ "PropertyChange",
+ g_variant_new ("(siiva{sv})",
+ name, 0, 0, value, NULL),
+ NULL);
+}
+
+static void
+gtk_at_spi_context_state_change (GtkATContext *ctx,
+ GtkAccessibleStateChange changed_states,
+ GtkAccessiblePropertyChange changed_properties,
+ GtkAccessibleRelationChange changed_relations,
+ GtkAccessiblePlatformChange changed_platform,
+ GtkAccessibleAttributeSet *states,
+ GtkAccessibleAttributeSet *properties,
+ GtkAccessibleAttributeSet *relations)
+{
+ GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (ctx);
+ GtkWidget *widget = GTK_WIDGET (gtk_at_context_get_accessible (ctx));
+ GtkAccessibleValue *value;
+
+ if (!gtk_widget_get_realized (widget))
+ return;
+
+ if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_BUSY)
+ {
+ value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_BUSY);
+ emit_state_changed (self, "busy", gtk_boolean_accessible_value_get (value));
+ }
+
+ if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_CHECKED)
+ {
+ value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_CHECKED);
+
+ switch (gtk_tristate_accessible_value_get (value))
+ {
+ case GTK_ACCESSIBLE_TRISTATE_TRUE:
+ emit_state_changed (self, "checked", TRUE);
+ emit_state_changed (self, "indeterminate", FALSE);
+ break;
+ case GTK_ACCESSIBLE_TRISTATE_MIXED:
+ emit_state_changed (self, "checked", FALSE);
+ emit_state_changed (self, "indeterminate", TRUE);
+ break;
+ case GTK_ACCESSIBLE_TRISTATE_FALSE:
+ emit_state_changed (self, "checked", FALSE);
+ emit_state_changed (self, "indeterminate", FALSE);
+ default:
+ break;
+ }
+ }
+
+ if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_DISABLED)
+ {
+ value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_DISABLED);
+ emit_state_changed (self, "sensitive", !gtk_boolean_accessible_value_get (value));
+ }
+
+ if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_EXPANDED)
+ {
+ value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_EXPANDED);
+ if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN)
+ {
+ emit_state_changed (self, "expandable", TRUE);
+ emit_state_changed (self, "expanded",gtk_boolean_accessible_value_get (value));
+ }
+ else
+ emit_state_changed (self, "expandable", FALSE);
+ }
+
+ if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_INVALID)
+ {
+ value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_INVALID);
+ switch (gtk_invalid_accessible_value_get (value))
+ {
+ case GTK_ACCESSIBLE_INVALID_TRUE:
+ case GTK_ACCESSIBLE_INVALID_GRAMMAR:
+ case GTK_ACCESSIBLE_INVALID_SPELLING:
+ emit_state_changed (self, "invalid", TRUE);
+ break;
+ case GTK_ACCESSIBLE_INVALID_FALSE:
+ emit_state_changed (self, "invalid", FALSE);
+ default:
+ break;
+ }
+ }
+
+ if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_PRESSED)
+ {
+ value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_PRESSED);
+ switch (gtk_tristate_accessible_value_get (value))
+ {
+ case GTK_ACCESSIBLE_TRISTATE_TRUE:
+ emit_state_changed (self, "pressed", TRUE);
+ emit_state_changed (self, "indeterminate", FALSE);
+ break;
+ case GTK_ACCESSIBLE_TRISTATE_MIXED:
+ emit_state_changed (self, "pressed", FALSE);
+ emit_state_changed (self, "indeterminate", TRUE);
+ break;
+ case GTK_ACCESSIBLE_TRISTATE_FALSE:
+ emit_state_changed (self, "pressed", FALSE);
+ emit_state_changed (self, "indeterminate", FALSE);
+ default:
+ break;
+ }
+ }
+
+ if (changed_states & GTK_ACCESSIBLE_STATE_CHANGE_SELECTED)
+ {
+ value = gtk_accessible_attribute_set_get_value (states, GTK_ACCESSIBLE_STATE_SELECTED);
+ if (value->value_class->type == GTK_ACCESSIBLE_VALUE_TYPE_BOOLEAN)
+ {
+ emit_state_changed (self, "selectable", TRUE);
+ emit_state_changed (self, "selected",gtk_boolean_accessible_value_get (value));
+ }
+ else
+ emit_state_changed (self, "selectable", FALSE);
+ }
+
+ if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_READ_ONLY)
+ {
+ gboolean readonly;
+
+ value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_READ_ONLY);
+ readonly = gtk_boolean_accessible_value_get (value);
+
+ emit_state_changed (self, "read-only", readonly);
+ if (ctx->accessible_role == GTK_ACCESSIBLE_ROLE_TEXT_BOX)
+ emit_state_changed (self, "editable", !readonly);
+ }
+
+ if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_ORIENTATION)
+ {
+ value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_ORIENTATION);
+ if (gtk_orientation_accessible_value_get (value) == GTK_ORIENTATION_HORIZONTAL)
+ {
+ emit_state_changed (self, "horizontal", TRUE);
+ emit_state_changed (self, "vertical", FALSE);
+ }
+ else
+ {
+ emit_state_changed (self, "horizontal", FALSE);
+ emit_state_changed (self, "vertical", TRUE);
+ }
+ }
+
+ if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_MODAL)
+ {
+ value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_MODAL);
+ emit_state_changed (self, "modal", gtk_boolean_accessible_value_get (value));
+ }
+
+ if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_MULTI_LINE)
+ {
+ value = gtk_accessible_attribute_set_get_value (properties, GTK_ACCESSIBLE_PROPERTY_MULTI_LINE);
+ emit_state_changed (self, "multi-line", gtk_boolean_accessible_value_get (value));
+ }
+
+ if (changed_properties & GTK_ACCESSIBLE_PROPERTY_CHANGE_LABEL)
+ {
+ char *label = gtk_at_context_get_label (GTK_AT_CONTEXT (self));
+ GVariant *v = g_variant_new_take_string (label);
+ emit_property_changed (self, "accessible-description", v);
+ }
+
+ if (changed_platform & GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSABLE)
+ {
+ gboolean state = gtk_accessible_get_platform_state (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE);
+ emit_state_changed (self, "focusable", state);
+ }
+
+ if (changed_platform & GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSED)
+ {
+ gboolean state = gtk_accessible_get_platform_state (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED);
+ emit_state_changed (self, "focused", state);
+ }
+}
+
+static void
+gtk_at_spi_context_dispose (GObject *gobject)
+{
+ GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+ GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
+
+ gtk_at_spi_context_unregister_object (self);
+ gtk_atspi_disconnect_text_signals (accessible);
+ gtk_atspi_disconnect_selection_signals (accessible);
+
+ G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->dispose (gobject);
+}
+
+static void
+gtk_at_spi_context_finalize (GObject *gobject)
+{
+ GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+
+ g_free (self->bus_address);
+ g_free (self->context_path);
+ g_clear_pointer (&self->interfaces, g_variant_unref);
+
+ G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->finalize (gobject);
+}
+
+static void
+gtk_at_spi_context_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_BUS_ADDRESS:
+ self->bus_address = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+gtk_at_spi_context_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_BUS_ADDRESS:
+ g_value_set_string (value, self->bus_address);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+gtk_at_spi_context_constructed (GObject *gobject)
+{
+ GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+ GdkDisplay *display;
+
+ g_assert (self->bus_address);
+
+ /* Every GTK application has a single root AT-SPI object, which
+ * handles all the global state, including the cache of accessible
+ * objects. We use the GdkDisplay to store it, so it's guaranteed
+ * to be unique per-display connection
+ */
+ display = gtk_at_context_get_display (GTK_AT_CONTEXT (self));
+ self->root =
+ g_object_get_data (G_OBJECT (display), "-gtk-atspi-root");
+
+ if (self->root == NULL)
+ {
+ self->root = gtk_at_spi_root_new (self->bus_address);
+ g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-root",
+ self->root,
+ g_object_unref);
+ }
+
+ self->connection = gtk_at_spi_root_get_connection (self->root);
+
+ /* We use the application's object path to build the path of each
+ * accessible object exposed on the accessibility bus; the path is
+ * also used to access the object cache
+ */
+ GApplication *application = g_application_get_default ();
+ char *base_path = NULL;
+
+ if (application != NULL)
+ {
+ const char *app_path = g_application_get_dbus_object_path (application);
+ base_path = g_strconcat (app_path, "/a11y", NULL);
+ }
+ else
+ {
+ char *uuid = g_uuid_string_random ();
+ base_path = g_strconcat ("/org/gtk/application/", uuid, "/a11y", NULL);
+ g_free (uuid);
+ }
+
+ /* We use a unique id to ensure that we don't have conflicting
+ * objects on the bus
+ */
+ char *uuid = g_uuid_string_random ();
+
+ self->context_path = g_strconcat (base_path, "/", uuid, NULL);
+
+ /* UUIDs use '-' as the separator, but that's not a valid character
+ * for a DBus object path
+ */
+ size_t path_len = strlen (self->context_path);
+ for (size_t i = 0; i < path_len; i++)
+ {
+ if (self->context_path[i] == '-')
+ self->context_path[i] = '_';
+ }
+
+ g_free (base_path);
+ g_free (uuid);
+
+ GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
+ gtk_atspi_connect_text_signals (accessible,
+ (GtkAtspiTextChangedCallback *)emit_text_changed,
+ (GtkAtspiTextSelectionCallback *)emit_text_selection_changed,
+ self);
+ gtk_atspi_connect_selection_signals (accessible,
+ (GtkAtspiSelectionCallback *)emit_selection_changed,
+ self);
+ gtk_at_spi_context_register_object (self);
+
+ G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->constructed (gobject);
+}
+
+static void
+gtk_at_spi_context_class_init (GtkAtSpiContextClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkATContextClass *context_class = GTK_AT_CONTEXT_CLASS (klass);
+
+ gobject_class->constructed = gtk_at_spi_context_constructed;
+ gobject_class->set_property = gtk_at_spi_context_set_property;
+ gobject_class->get_property = gtk_at_spi_context_get_property;
+ gobject_class->finalize = gtk_at_spi_context_finalize;
+ gobject_class->dispose = gtk_at_spi_context_dispose;
+
+ context_class->state_change = gtk_at_spi_context_state_change;
+
+ obj_props[PROP_BUS_ADDRESS] =
+ g_param_spec_string ("bus-address", NULL, NULL,
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, obj_props);
+}
+
+static void
+gtk_at_spi_context_init (GtkAtSpiContext *self)
+{
+}
+
+#ifdef GDK_WINDOWING_X11
+static char *
+get_bus_address_x11 (GdkDisplay *display)
+{
+ GTK_NOTE (A11Y, g_message ("Acquiring a11y bus via X11..."));
+
+ Display *xdisplay = gdk_x11_display_get_xdisplay (display);
+ Atom type_return;
+ int format_return;
+ gulong nitems_return;
+ gulong bytes_after_return;
+ guchar *data = NULL;
+ char *address = NULL;
+
+ gdk_x11_display_error_trap_push (display);
+ XGetWindowProperty (xdisplay, DefaultRootWindow (xdisplay),
+ gdk_x11_get_xatom_by_name_for_display (display, "AT_SPI_BUS"),
+ 0L, BUFSIZ, False,
+ (Atom) 31,
+ &type_return, &format_return, &nitems_return,
+ &bytes_after_return, &data);
+ gdk_x11_display_error_trap_pop_ignored (display);
+
+ address = g_strdup ((char *) data);
+
+ XFree (data);
+
+ return address;
+}
+#endif
+
+#if defined(GDK_WINDOWING_WAYLAND) || defined(GDK_WINDOWING_X11)
+static char *
+get_bus_address_dbus (GdkDisplay *display)
+{
+ GTK_NOTE (A11Y, g_message ("Acquiring a11y bus via DBus..."));
+
+ GError *error = NULL;
+ GDBusConnection *connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (error != NULL)
+ {
+ g_critical ("Unable to acquire session bus: %s", error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ GVariant *res =
+ g_dbus_connection_call_sync (connection, "org.a11y.Bus",
+ "/org/a11y/bus",
+ "org.a11y.Bus",
+ "GetAddress",
+ NULL, NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (error != NULL)
+ {
+ g_critical ("Unable to acquire the address of the accessibility bus: %s",
+ error->message);
+ g_error_free (error);
+ }
+
+ char *address = NULL;
+ if (res != NULL)
+ {
+ g_variant_get (res, "(s)", &address);
+ g_variant_unref (res);
+ }
+
+ g_object_unref (connection);
+
+ return address;
+}
+#endif
+
+static const char *
+get_bus_address (GdkDisplay *display)
+{
+ const char *bus_address;
+
+ bus_address = g_object_get_data (G_OBJECT (display), "-gtk-atspi-bus-address");
+ if (bus_address != NULL)
+ return bus_address;
+
+ /* The bus address environment variable takes precedence; this is the
+ * mechanism used by Flatpak to handle the accessibility bus portal
+ * between the sandbox and the outside world
+ */
+ bus_address = g_getenv ("AT_SPI_BUS_ADDRESS");
+ if (bus_address != NULL && *bus_address != '\0')
+ {
+ GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from environment: %s", bus_address));
+ g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address",
+ g_strdup (bus_address),
+ g_free);
+ goto out;
+ }
+
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (bus_address == NULL)
+ {
+ if (GDK_IS_WAYLAND_DISPLAY (display))
+ {
+ char *addr = get_bus_address_dbus (display);
+
+ GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from D-Bus: %s", addr));
+ g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address",
+ addr,
+ g_free);
+
+ bus_address = addr;
+ }
+ }
+#endif
+#if defined(GDK_WINDOWING_X11)
+ if (bus_address == NULL)
+ {
+ if (GDK_IS_X11_DISPLAY (display))
+ {
+ char *addr = get_bus_address_dbus (display);
+
+ if (addr == NULL)
+ {
+ addr = get_bus_address_x11 (display);
+ GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from X11: %s", addr));
+ }
+ else
+ {
+ GTK_NOTE (A11Y, g_message ("Using ATSPI bus address from D-Bus: %s", addr));
+ }
+
+ g_object_set_data_full (G_OBJECT (display), "-gtk-atspi-bus-address",
+ addr,
+ g_free);
+
+ bus_address = addr;
+ }
+ }
+#endif
+
+out:
+ return bus_address;
+}
+
+GtkATContext *
+gtk_at_spi_create_context (GtkAccessibleRole accessible_role,
+ GtkAccessible *accessible,
+ GdkDisplay *display)
+{
+ g_return_val_if_fail (GTK_IS_ACCESSIBLE (accessible), NULL);
+ g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);
+
+ const char *bus_address = get_bus_address (display);
+
+ if (bus_address == NULL)
+ return NULL;
+
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (GDK_IS_WAYLAND_DISPLAY (display))
+ return g_object_new (GTK_TYPE_AT_SPI_CONTEXT,
+ "accessible-role", accessible_role,
+ "accessible", accessible,
+ "display", display,
+ "bus-address", bus_address,
+ NULL);
+#endif
+#if defined(GDK_WINDOWING_X11)
+ if (GDK_IS_X11_DISPLAY (display))
+ return g_object_new (GTK_TYPE_AT_SPI_CONTEXT,
+ "accessible-role", accessible_role,
+ "accessible", accessible,
+ "display", display,
+ "bus-address", bus_address,
+ NULL);
+#endif
+
+ return NULL;
+}
+
+const char *
+gtk_at_spi_context_get_context_path (GtkAtSpiContext *self)
+{
+ g_return_val_if_fail (GTK_IS_AT_SPI_CONTEXT (self), NULL);
+
+ return self->context_path;
+}
+
+/*< private >
+ * gtk_at_spi_context_to_ref:
+ * @self: a #GtkAtSpiContext
+ *
+ * Returns an ATSPI object reference for the #GtkAtSpiContext.
+ *
+ * Returns: (transfer floating): a #GVariant with the reference
+ */
+GVariant *
+gtk_at_spi_context_to_ref (GtkAtSpiContext *self)
+{
+ const char *name = g_dbus_connection_get_unique_name (self->connection);
+ return g_variant_new ("(so)", name, self->context_path);
+}
diff --git a/gtk/a11y/gtkatspicontextprivate.h b/gtk/a11y/gtkatspicontextprivate.h
new file mode 100644
index 0000000000..b0cd407579
--- /dev/null
+++ b/gtk/a11y/gtkatspicontextprivate.h
@@ -0,0 +1,42 @@
+/* gtkatspicontextprivate.h: AT-SPI GtkATContext implementation
+ *
+ * Copyright 2020 GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "gtkatcontextprivate.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_AT_SPI_CONTEXT (gtk_at_spi_context_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkAtSpiContext, gtk_at_spi_context, GTK, AT_SPI_CONTEXT, GtkATContext)
+
+GtkATContext *
+gtk_at_spi_create_context (GtkAccessibleRole accessible_role,
+ GtkAccessible *accessible,
+ GdkDisplay *display);
+
+const char *
+gtk_at_spi_context_get_context_path (GtkAtSpiContext *self);
+
+GVariant *
+gtk_at_spi_context_to_ref (GtkAtSpiContext *self);
+
+G_END_DECLS
diff --git a/gtk/a11y/gtkatspieditabletext.c b/gtk/a11y/gtkatspieditabletext.c
new file mode 100644
index 0000000000..a904ef916f
--- /dev/null
+++ b/gtk/a11y/gtkatspieditabletext.c
@@ -0,0 +1,356 @@
+/* gtkatspieditabletext.c: EditableText interface for GtkAtspiContext
+ *
+ * Copyright 2020 Red Hat, Inc
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkatspieditabletextprivate.h"
+#include "gtkatcontextprivate.h"
+
+#include "a11y/atspi/atspi-editabletext.h"
+
+#include "gtkeditable.h"
+#include "gtkentry.h"
+#include "gtksearchentry.h"
+#include "gtkpasswordentry.h"
+#include "gtkspinbutton.h"
+#include "gtktextview.h"
+
+#include <gio/gio.h>
+
+typedef struct
+{
+ GtkWidget *widget;
+ int position;
+} PasteData;
+
+static void
+text_received (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GdkClipboard *clipboard = GDK_CLIPBOARD (source);
+ PasteData *pdata = data;
+ char *text;
+
+ text = gdk_clipboard_read_text_finish (clipboard, result, NULL);
+ if (text)
+ gtk_editable_insert_text (GTK_EDITABLE (pdata->widget), text, -1, &pdata->position);
+ g_free (text);
+ g_free (pdata);
+}
+
+static void
+entry_handle_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (method_name, "SetTextContents") == 0)
+ {
+ char *text;
+ gboolean ret = FALSE;
+
+ g_variant_get (parameters, "(&s)", &text);
+
+ if (gtk_editable_get_editable (GTK_EDITABLE (widget)))
+ {
+ gtk_editable_set_text (GTK_EDITABLE (widget), text);
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "InsertText") == 0)
+ {
+ int position;
+ char *text;
+ int len;
+ gboolean ret = FALSE;
+
+ g_variant_get (parameters, "(i&si)", &position, &text, &len);
+
+ if (gtk_editable_get_editable (GTK_EDITABLE (widget)))
+ {
+ gtk_editable_insert_text (GTK_EDITABLE (widget), text, -1, &position);
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "CopyText") == 0)
+ {
+ int start, end;
+ char *str;
+
+ g_variant_get (parameters, "(ii)", &start, &end);
+
+ str = gtk_editable_get_chars (GTK_EDITABLE (widget), start, end);
+ gdk_clipboard_set_text (gtk_widget_get_clipboard (widget), str);
+ g_free (str);
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+ else if (g_strcmp0 (method_name, "CutText") == 0)
+ {
+ int start, end;
+ gboolean ret = FALSE;
+
+ g_variant_get (parameters, "(ii)", &start, &end);
+
+ if (gtk_editable_get_editable (GTK_EDITABLE (widget)))
+ {
+ char *str;
+
+ str = gtk_editable_get_chars (GTK_EDITABLE (widget), start, end);
+ gdk_clipboard_set_text (gtk_widget_get_clipboard (widget), str);
+ g_free (str);
+ gtk_editable_delete_text (GTK_EDITABLE (widget), start, end);
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "DeleteText") == 0)
+ {
+ int start, end;
+ gboolean ret = FALSE;
+
+ g_variant_get (parameters, "(ii)", &start, &end);
+
+ if (gtk_editable_get_editable (GTK_EDITABLE (widget)))
+ {
+ gtk_editable_delete_text (GTK_EDITABLE (widget), start, end);
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "PasteText") == 0)
+ {
+ int position;
+ gboolean ret = FALSE;
+
+ g_variant_get (parameters, "(i)", &position);
+
+ if (gtk_editable_get_editable (GTK_EDITABLE (widget)))
+ {
+ PasteData *data;
+
+ data = g_new (PasteData, 1);
+ data->widget = widget;
+ data->position = position;
+
+ gdk_clipboard_read_text_async (gtk_widget_get_clipboard (widget), NULL, text_received, data);
+
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+}
+
+static const GDBusInterfaceVTable entry_vtable = {
+ entry_handle_method,
+ NULL,
+};
+
+
+static void
+text_view_received (GObject *source,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GdkClipboard *clipboard = GDK_CLIPBOARD (source);
+ PasteData *pdata = data;
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (pdata->widget));
+ GtkTextIter iter;
+ char *text;
+
+ text = gdk_clipboard_read_text_finish (clipboard, result, NULL);
+ if (text)
+ {
+ gtk_text_buffer_get_iter_at_offset (buffer, &iter, pdata->position);
+ gtk_text_buffer_insert (buffer, &iter, text, -1);
+ }
+
+ g_free (text);
+ g_free (pdata);
+}
+
+static void
+text_view_handle_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (method_name, "SetTextContents") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ char *text;
+ gboolean ret = FALSE;
+
+ g_variant_get (parameters, "(&s)", &text);
+
+ if (gtk_text_view_get_editable (GTK_TEXT_VIEW (widget)))
+ {
+ gtk_text_buffer_set_text (buffer, text, -1);
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "InsertText") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GtkTextIter iter;
+ int position;
+ char *text;
+ int len;
+ gboolean ret = FALSE;
+
+ g_variant_get (parameters, "(i&si)", &position, &text, &len);
+
+ if (gtk_text_view_get_editable (GTK_TEXT_VIEW (widget)))
+ {
+ gtk_text_buffer_get_iter_at_offset (buffer, &iter, position);
+ gtk_text_buffer_insert (buffer, &iter, text, len);
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "CopyText") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GtkTextIter start_iter, end_iter;
+ int start, end;
+ char *str;
+
+ g_variant_get (parameters, "(ii)", &start, &end);
+
+ gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+ gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+ str = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE);
+ gdk_clipboard_set_text (gtk_widget_get_clipboard (widget), str);
+ g_free (str);
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+ else if (g_strcmp0 (method_name, "CutText") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GtkTextIter start_iter, end_iter;
+ int start, end;
+ gboolean ret = FALSE;
+
+ g_variant_get (parameters, "(ii)", &start, &end);
+
+ if (gtk_text_view_get_editable (GTK_TEXT_VIEW (widget)))
+ {
+ char *str;
+
+ gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+ gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+ str = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE);
+ gdk_clipboard_set_text (gtk_widget_get_clipboard (widget), str);
+ g_free (str);
+ gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "DeleteText") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GtkTextIter start_iter, end_iter;
+ int start, end;
+ gboolean ret = FALSE;
+
+ g_variant_get (parameters, "(ii)", &start, &end);
+
+ if (gtk_text_view_get_editable (GTK_TEXT_VIEW (widget)))
+ {
+ gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+ gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+ gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "PasteText") == 0)
+ {
+ int position;
+ gboolean ret = FALSE;
+
+ g_variant_get (parameters, "(i)", &position);
+
+ if (gtk_text_view_get_editable (GTK_TEXT_VIEW (widget)))
+ {
+ PasteData *data;
+
+ data = g_new (PasteData, 1);
+ data->widget = widget;
+ data->position = position;
+
+ gdk_clipboard_read_text_async (gtk_widget_get_clipboard (widget), NULL, text_view_received, data);
+
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+}
+
+static const GDBusInterfaceVTable text_view_vtable = {
+ text_view_handle_method,
+ NULL,
+};
+
+
+const GDBusInterfaceVTable *
+gtk_atspi_get_editable_text_vtable (GtkAccessible *accessible)
+{
+ if (GTK_IS_ENTRY (accessible) ||
+ GTK_IS_SEARCH_ENTRY (accessible) ||
+ GTK_IS_PASSWORD_ENTRY (accessible) ||
+ GTK_IS_SPIN_BUTTON (accessible))
+ return &entry_vtable;
+ else if (GTK_IS_TEXT_VIEW (accessible))
+ return &text_view_vtable;
+
+ return NULL;
+}
+
diff --git a/gtk/a11y/gtkatspieditabletextprivate.h b/gtk/a11y/gtkatspieditabletextprivate.h
new file mode 100644
index 0000000000..d673384371
--- /dev/null
+++ b/gtk/a11y/gtkatspieditabletextprivate.h
@@ -0,0 +1,30 @@
+/* gtkatspieditabletextprivate.h: AT-SPI EditableText implementation
+ *
+ * Copyright 2020 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include "gtkaccessible.h"
+
+G_BEGIN_DECLS
+
+const GDBusInterfaceVTable *gtk_atspi_get_editable_text_vtable (GtkAccessible *accessible);
+
+G_END_DECLS
diff --git a/gtk/a11y/gtkatspipango.c b/gtk/a11y/gtkatspipango.c
new file mode 100644
index 0000000000..1171c8758b
--- /dev/null
+++ b/gtk/a11y/gtkatspipango.c
@@ -0,0 +1,1257 @@
+/* gtkatspipango.c - pango-related utilities for AT-SPI
+ *
+ * Copyright (c) 2010 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.Free
+ */
+
+#include "config.h"
+#include "gtkatspipangoprivate.h"
+
+const char *
+pango_style_to_string (PangoStyle style)
+{
+ switch (style)
+ {
+ case PANGO_STYLE_NORMAL:
+ return "normal";
+ case PANGO_STYLE_OBLIQUE:
+ return "oblique";
+ case PANGO_STYLE_ITALIC:
+ return "italic";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+const char *
+pango_variant_to_string (PangoVariant variant)
+{
+ switch (variant)
+ {
+ case PANGO_VARIANT_NORMAL:
+ return "normal";
+ case PANGO_VARIANT_SMALL_CAPS:
+ return "small_caps";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+const char *
+pango_stretch_to_string (PangoStretch stretch)
+{
+ switch (stretch)
+ {
+ case PANGO_STRETCH_ULTRA_CONDENSED:
+ return "ultra_condensed";
+ case PANGO_STRETCH_EXTRA_CONDENSED:
+ return "extra_condensed";
+ case PANGO_STRETCH_CONDENSED:
+ return "condensed";
+ case PANGO_STRETCH_SEMI_CONDENSED:
+ return "semi_condensed";
+ case PANGO_STRETCH_NORMAL:
+ return "normal";
+ case PANGO_STRETCH_SEMI_EXPANDED:
+ return "semi_expanded";
+ case PANGO_STRETCH_EXPANDED:
+ return "expanded";
+ case PANGO_STRETCH_EXTRA_EXPANDED:
+ return "extra_expanded";
+ case PANGO_STRETCH_ULTRA_EXPANDED:
+ return "ultra_expanded";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+const char *
+pango_underline_to_string (PangoUnderline value)
+{
+ switch (value)
+ {
+ case PANGO_UNDERLINE_NONE:
+ return "none";
+ case PANGO_UNDERLINE_SINGLE:
+ case PANGO_UNDERLINE_SINGLE_LINE:
+ return "single";
+ case PANGO_UNDERLINE_DOUBLE:
+ case PANGO_UNDERLINE_DOUBLE_LINE:
+ return "double";
+ case PANGO_UNDERLINE_LOW:
+ return "low";
+ case PANGO_UNDERLINE_ERROR:
+ case PANGO_UNDERLINE_ERROR_LINE:
+ return "error";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+const char *
+pango_wrap_mode_to_string (PangoWrapMode mode)
+{
+ switch (mode)
+ {
+ case PANGO_WRAP_WORD:
+ return "word";
+ case PANGO_WRAP_CHAR:
+ return "char";
+ case PANGO_WRAP_WORD_CHAR:
+ return "word-char";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+void
+gtk_pango_get_font_attributes (PangoFontDescription *font,
+ GVariantBuilder *builder)
+{
+ char buf[60];
+
+ g_variant_builder_add (builder, "{ss}", "style",
+ pango_style_to_string (pango_font_description_get_style (font)));
+ g_variant_builder_add (builder, "{ss}", "variant",
+ pango_variant_to_string (pango_font_description_get_variant (font)));
+ g_variant_builder_add (builder, "{ss}", "stretch",
+ pango_stretch_to_string (pango_font_description_get_stretch (font)));
+ g_variant_builder_add (builder, "{ss}", "family-name",
+ pango_font_description_get_family (font));
+
+ g_snprintf (buf, 60, "%d", pango_font_description_get_weight (font));
+ g_variant_builder_add (builder, "{ss}", "weight", buf);
+ g_snprintf (buf, 60, "%i", pango_font_description_get_size (font) / PANGO_SCALE);
+ g_variant_builder_add (builder, "{ss}", "size", buf);
+}
+
+/*
+ * gtk_pango_get_default_attributes:
+ * @attributes: a #AtkAttributeSet to add the attributes to
+ * @layout: the #PangoLayout from which to get attributes
+ *
+ * Adds the default text attributes from @layout to @attributes,
+ * after translating them from Pango attributes to atspi attributes.
+ *
+ * This is a convenience function that can be used to implement
+ * support for the #AtkText interface in widgets using Pango
+ * layouts.
+ *
+ * Returns: the modified @attributes
+ */
+void
+gtk_pango_get_default_attributes (PangoLayout *layout,
+ GVariantBuilder *builder)
+{
+ PangoContext *context;
+ const char *val;
+ PangoAlignment align;
+ PangoWrapMode mode;
+
+ context = pango_layout_get_context (layout);
+ if (context)
+ {
+ PangoLanguage *language;
+ PangoFontDescription *font;
+
+ language = pango_context_get_language (context);
+ if (language)
+ g_variant_builder_add (builder, "{ss}", "language",
+ pango_language_to_string (language));
+
+ font = pango_context_get_font_description (context);
+ if (font)
+ gtk_pango_get_font_attributes (font, builder);
+ }
+ if (pango_layout_get_justify (layout))
+ {
+ val = "fill";
+ }
+ else
+ {
+ align = pango_layout_get_alignment (layout);
+ if (align == PANGO_ALIGN_LEFT)
+ val = "left";
+ else if (align == PANGO_ALIGN_CENTER)
+ val = "center";
+ else
+ val = "right";
+ }
+ g_variant_builder_add (builder, "{ss}", "justification", val);
+
+ mode = pango_layout_get_wrap (layout);
+ g_variant_builder_add (builder, "{ss}", "wrap-mode",
+ pango_wrap_mode_to_string (mode));
+
+ g_variant_builder_add (builder, "{ss}", "strikethrough", "false");
+ g_variant_builder_add (builder, "{ss}", "underline", "false");
+ g_variant_builder_add (builder, "{ss}", "rise", "0");
+ g_variant_builder_add (builder, "{ss}", "scale", "1");
+ g_variant_builder_add (builder, "{ss}", "bg-full-height", "0");
+ g_variant_builder_add (builder, "{ss}", "pixels-inside-wrap", "0");
+ g_variant_builder_add (builder, "{ss}", "pixels-below-lines", "0");
+ g_variant_builder_add (builder, "{ss}", "pixels-above-lines", "0");
+ g_variant_builder_add (builder, "{ss}", "editable", "false");
+ g_variant_builder_add (builder, "{ss}", "invisible", "false");
+ g_variant_builder_add (builder, "{ss}", "indent", "0");
+ g_variant_builder_add (builder, "{ss}", "right-margin", "0");
+ g_variant_builder_add (builder, "{ss}", "left-margin", "0");
+}
+
+/*
+ * gtk_pango_get_run_attributes:
+ * @layout: the #PangoLayout to get the attributes from
+ * @builder: GVariantBuilder to add to
+ * @offset: the offset at which the attributes are wanted
+ * @start_offset: return location for the starting offset
+ * of the current run
+ * @end_offset: return location for the ending offset of the
+ * current run
+ *
+ * Finds the “run” around index (i.e. the maximal range of characters
+ * where the set of applicable attributes remains constant) and
+ * returns the starting and ending offsets for it.
+ *
+ * The attributes for the run are added to @attributes, after
+ * translating them from Pango attributes to atspi attributes.
+ *
+ * This is a convenience function that can be used to implement
+ * support for the #AtkText interface in widgets using Pango
+ * layouts.
+ */
+void
+gtk_pango_get_run_attributes (PangoLayout *layout,
+ GVariantBuilder *builder,
+ int offset,
+ int *start_offset,
+ int *end_offset)
+{
+ PangoAttrIterator *iter;
+ PangoAttrList *attr;
+ PangoAttrString *pango_string;
+ PangoAttrInt *pango_int;
+ PangoAttrColor *pango_color;
+ PangoAttrLanguage *pango_lang;
+ PangoAttrFloat *pango_float;
+ int index, start_index, end_index;
+ gboolean is_next;
+ glong len;
+ const char *text;
+ char *value;
+ const char *val;
+
+ text = pango_layout_get_text (layout);
+ len = g_utf8_strlen (text, -1);
+
+ /* Grab the attributes of the PangoLayout, if any */
+ attr = pango_layout_get_attributes (layout);
+
+ if (attr == NULL)
+ {
+ *start_offset = 0;
+ *end_offset = len;
+ return;
+ }
+
+ iter = pango_attr_list_get_iterator (attr);
+ /* Get invariant range offsets */
+ /* If offset out of range, set offset in range */
+ if (offset > len)
+ offset = len;
+ else if (offset < 0)
+ offset = 0;
+
+ index = g_utf8_offset_to_pointer (text, offset) - text;
+ pango_attr_iterator_range (iter, &start_index, &end_index);
+ is_next = TRUE;
+ while (is_next)
+ {
+ if (index >= start_index && index < end_index)
+ {
+ *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
+ if (end_index == G_MAXINT) /* Last iterator */
+ end_index = len;
+
+ *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
+ break;
+ }
+ is_next = pango_attr_iterator_next (iter);
+ pango_attr_iterator_range (iter, &start_index, &end_index);
+ }
+
+ /* Get attributes */
+ pango_string = (PangoAttrString *) pango_attr_iterator_get (iter, PANGO_ATTR_FAMILY);
+ if (pango_string != NULL)
+ {
+ value = g_strdup_printf ("%s", pango_string->value);
+ g_variant_builder_add (builder, "{ss}", "family-name", value);
+ g_free (value);
+ }
+
+ pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_STYLE);
+ if (pango_int != NULL)
+ g_variant_builder_add (builder, "{ss}", "style", pango_style_to_string (pango_int->value));
+
+ pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_WEIGHT);
+ if (pango_int != NULL)
+ {
+ value = g_strdup_printf ("%i", pango_int->value);
+ g_variant_builder_add (builder, "{ss}", "weight", value);
+ g_free (value);
+ }
+
+ pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_VARIANT);
+ if (pango_int != NULL)
+ g_variant_builder_add (builder, "{ss}", "variant",
+ pango_variant_to_string (pango_int->value));
+
+ pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_STRETCH);
+ if (pango_int != NULL)
+ g_variant_builder_add (builder, "{ss}", "stretch",
+ pango_stretch_to_string (pango_int->value));
+
+ pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_SIZE);
+ if (pango_int != NULL)
+ {
+ value = g_strdup_printf ("%i", pango_int->value / PANGO_SCALE);
+ g_variant_builder_add (builder, "{ss}", "size", value);
+ g_free (value);
+ }
+
+ pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE);
+ if (pango_int != NULL)
+ g_variant_builder_add (builder, "{ss}", "underline",
+ pango_underline_to_string (pango_int->value));
+
+ pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_STRIKETHROUGH);
+ if (pango_int != NULL)
+ {
+ if (pango_int->value)
+ val = "true";
+ else
+ val = "false";
+ g_variant_builder_add (builder, "{ss}", "strikethrough", val);
+ }
+
+ pango_int = (PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_RISE);
+ if (pango_int != NULL)
+ {
+ value = g_strdup_printf ("%i", pango_int->value);
+ g_variant_builder_add (builder, "{ss}", "rise", value);
+ g_free (value);
+ }
+
+ pango_lang = (PangoAttrLanguage *) pango_attr_iterator_get (iter, PANGO_ATTR_LANGUAGE);
+ if (pango_lang != NULL)
+ {
+ g_variant_builder_add (builder, "{ss}", "language",
+ pango_language_to_string (pango_lang->value));
+ }
+
+ pango_float = (PangoAttrFloat *) pango_attr_iterator_get (iter, PANGO_ATTR_SCALE);
+ if (pango_float != NULL)
+ {
+ value = g_strdup_printf ("%g", pango_float->value);
+ g_variant_builder_add (builder, "{ss}", "scale", value);
+ g_free (value);
+ }
+
+ pango_color = (PangoAttrColor *) pango_attr_iterator_get (iter, PANGO_ATTR_FOREGROUND);
+ if (pango_color != NULL)
+ {
+ value = g_strdup_printf ("%u,%u,%u",
+ pango_color->color.red,
+ pango_color->color.green,
+ pango_color->color.blue);
+ g_variant_builder_add (builder, "{ss}", "fg-color", value);
+ g_free (value);
+ }
+
+ pango_color = (PangoAttrColor *) pango_attr_iterator_get (iter, PANGO_ATTR_BACKGROUND);
+ if (pango_color != NULL)
+ {
+ value = g_strdup_printf ("%u,%u,%u",
+ pango_color->color.red,
+ pango_color->color.green,
+ pango_color->color.blue);
+ g_variant_builder_add (builder, "{ss}", "bg-color", value);
+ g_free (value);
+ }
+ pango_attr_iterator_destroy (iter);
+}
+
+/*
+ * gtk_pango_move_chars:
+ * @layout: a #PangoLayout
+ * @offset: a character offset in @layout
+ * @count: the number of characters to move from @offset
+ *
+ * Returns the position that is @count characters from the
+ * given @offset. @count may be positive or negative.
+ *
+ * For the purpose of this function, characters are defined
+ * by what Pango considers cursor positions.
+ *
+ * Returns: the new position
+ */
+static int
+gtk_pango_move_chars (PangoLayout *layout,
+ int offset,
+ int count)
+{
+ const PangoLogAttr *attrs;
+ int n_attrs;
+
+ attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
+
+ while (count > 0 && offset < n_attrs - 1)
+ {
+ do
+ offset++;
+ while (offset < n_attrs - 1 && !attrs[offset].is_cursor_position);
+
+ count--;
+ }
+ while (count < 0 && offset > 0)
+ {
+ do
+ offset--;
+ while (offset > 0 && !attrs[offset].is_cursor_position);
+
+ count++;
+ }
+
+ return offset;
+}
+
+/*
+ * gtk_pango_move_words:
+ * @layout: a #PangoLayout
+ * @offset: a character offset in @layout
+ * @count: the number of words to move from @offset
+ *
+ * Returns the position that is @count words from the
+ * given @offset. @count may be positive or negative.
+ *
+ * If @count is positive, the returned position will
+ * be a word end, otherwise it will be a word start.
+ * See the Pango documentation for details on how
+ * word starts and ends are defined.
+ *
+ * Returns: the new position
+ */
+static int
+gtk_pango_move_words (PangoLayout *layout,
+ int offset,
+ int count)
+{
+ const PangoLogAttr *attrs;
+ int n_attrs;
+
+ attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
+
+ while (count > 0 && offset < n_attrs - 1)
+ {
+ do
+ offset++;
+ while (offset < n_attrs - 1 && !attrs[offset].is_word_end);
+
+ count--;
+ }
+ while (count < 0 && offset > 0)
+ {
+ do
+ offset--;
+ while (offset > 0 && !attrs[offset].is_word_start);
+
+ count++;
+ }
+
+ return offset;
+}
+
+/*
+ * gtk_pango_move_sentences:
+ * @layout: a #PangoLayout
+ * @offset: a character offset in @layout
+ * @count: the number of sentences to move from @offset
+ *
+ * Returns the position that is @count sentences from the
+ * given @offset. @count may be positive or negative.
+ *
+ * If @count is positive, the returned position will
+ * be a sentence end, otherwise it will be a sentence start.
+ * See the Pango documentation for details on how
+ * sentence starts and ends are defined.
+ *
+ * Returns: the new position
+ */
+static int
+gtk_pango_move_sentences (PangoLayout *layout,
+ int offset,
+ int count)
+{
+ const PangoLogAttr *attrs;
+ int n_attrs;
+
+ attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
+
+ while (count > 0 && offset < n_attrs - 1)
+ {
+ do
+ offset++;
+ while (offset < n_attrs - 1 && !attrs[offset].is_sentence_end);
+
+ count--;
+ }
+ while (count < 0 && offset > 0)
+ {
+ do
+ offset--;
+ while (offset > 0 && !attrs[offset].is_sentence_start);
+
+ count++;
+ }
+
+ return offset;
+}
+
+#if 0
+/*
+ * gtk_pango_move_lines:
+ * @layout: a #PangoLayout
+ * @offset: a character offset in @layout
+ * @count: the number of lines to move from @offset
+ *
+ * Returns the position that is @count lines from the
+ * given @offset. @count may be positive or negative.
+ *
+ * If @count is negative, the returned position will
+ * be the start of a line, else it will be the end of
+ * line.
+ *
+ * Returns: the new position
+ */
+static int
+gtk_pango_move_lines (PangoLayout *layout,
+ int offset,
+ int count)
+{
+ GSList *lines, *l;
+ PangoLayoutLine *line;
+ int num;
+ const char *text;
+ int pos, line_pos;
+ int index;
+ int len;
+
+ text = pango_layout_get_text (layout);
+ index = g_utf8_offset_to_pointer (text, offset) - text;
+ lines = pango_layout_get_lines (layout);
+ line = NULL;
+
+ num = 0;
+ for (l = lines; l; l = l->next)
+ {
+ line = l->data;
+ if (index < line->start_index + line->length)
+ break;
+ num++;
+ }
+
+ if (count < 0)
+ {
+ num += count;
+ if (num < 0)
+ num = 0;
+
+ line = g_slist_nth_data (lines, num);
+
+ return g_utf8_pointer_to_offset (text, text + line->start_index);
+ }
+ else
+ {
+ line_pos = index - line->start_index;
+
+ len = g_slist_length (lines);
+ num += count;
+ if (num >= len || (count == 0 && num == len - 1))
+ return g_utf8_strlen (text, -1) - 1;
+
+ line = l->data;
+ pos = line->start_index + line_pos;
+ if (pos >= line->start_index + line->length)
+ pos = line->start_index + line->length - 1;
+
+ return g_utf8_pointer_to_offset (text, text + pos);
+ }
+}
+#endif
+
+/*
+ * gtk_pango_is_inside_word:
+ * @layout: a #PangoLayout
+ * @offset: a character offset in @layout
+ *
+ * Returns whether the given position is inside
+ * a word.
+ *
+ * Returns: %TRUE if @offset is inside a word
+ */
+static gboolean
+gtk_pango_is_inside_word (PangoLayout *layout,
+ int offset)
+{
+ const PangoLogAttr *attrs;
+ int n_attrs;
+
+ attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
+
+ while (offset >= 0 &&
+ !(attrs[offset].is_word_start || attrs[offset].is_word_end))
+ offset--;
+
+ if (offset >= 0)
+ return attrs[offset].is_word_start;
+
+ return FALSE;
+}
+
+/*
+ * gtk_pango_is_inside_sentence:
+ * @layout: a #PangoLayout
+ * @offset: a character offset in @layout
+ *
+ * Returns whether the given position is inside
+ * a sentence.
+ *
+ * Returns: %TRUE if @offset is inside a sentence
+ */
+static gboolean
+gtk_pango_is_inside_sentence (PangoLayout *layout,
+ int offset)
+{
+ const PangoLogAttr *attrs;
+ int n_attrs;
+
+ attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
+
+ while (offset >= 0 &&
+ !(attrs[offset].is_sentence_start || attrs[offset].is_sentence_end))
+ offset--;
+
+ if (offset >= 0)
+ return attrs[offset].is_sentence_start;
+
+ return FALSE;
+}
+
+static void
+pango_layout_get_line_before (PangoLayout *layout,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset)
+{
+ PangoLayoutIter *iter;
+ PangoLayoutLine *line, *prev_line = NULL, *prev_prev_line = NULL;
+ int index, start_index, end_index;
+ const char *text;
+ gboolean found = FALSE;
+
+ text = pango_layout_get_text (layout);
+ index = g_utf8_offset_to_pointer (text, offset) - text;
+ iter = pango_layout_get_iter (layout);
+ do
+ {
+ line = pango_layout_iter_get_line (iter);
+ start_index = line->start_index;
+ end_index = start_index + line->length;
+
+ if (index >= start_index && index <= end_index)
+ {
+ /* Found line for offset */
+ if (prev_line)
+ {
+ switch (boundary_type)
+ {
+ case ATSPI_TEXT_BOUNDARY_LINE_START:
+ end_index = start_index;
+ start_index = prev_line->start_index;
+ break;
+ case ATSPI_TEXT_BOUNDARY_LINE_END:
+ if (prev_prev_line)
+ start_index = prev_prev_line->start_index + prev_prev_line->length;
+ else
+ start_index = 0;
+ end_index = prev_line->start_index + prev_line->length;
+ break;
+ case ATSPI_TEXT_BOUNDARY_CHAR:
+ case ATSPI_TEXT_BOUNDARY_WORD_START:
+ case ATSPI_TEXT_BOUNDARY_WORD_END:
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_START:
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_END:
+ default:
+ g_assert_not_reached();
+ }
+ }
+ else
+ start_index = end_index = 0;
+
+ found = TRUE;
+ break;
+ }
+
+ prev_prev_line = prev_line;
+ prev_line = line;
+ }
+ while (pango_layout_iter_next_line (iter));
+
+ if (!found)
+ {
+ start_index = prev_line->start_index + prev_line->length;
+ end_index = start_index;
+ }
+ pango_layout_iter_free (iter);
+
+ *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
+ *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
+}
+
+static void
+pango_layout_get_line_at (PangoLayout *layout,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset)
+{
+ PangoLayoutIter *iter;
+ PangoLayoutLine *line, *prev_line = NULL;
+ int index, start_index, end_index;
+ const char *text;
+ gboolean found = FALSE;
+
+ text = pango_layout_get_text (layout);
+ index = g_utf8_offset_to_pointer (text, offset) - text;
+ iter = pango_layout_get_iter (layout);
+ do
+ {
+ line = pango_layout_iter_get_line (iter);
+ start_index = line->start_index;
+ end_index = start_index + line->length;
+
+ if (index >= start_index && index <= end_index)
+ {
+ /* Found line for offset */
+ switch (boundary_type)
+ {
+ case ATSPI_TEXT_BOUNDARY_LINE_START:
+ if (pango_layout_iter_next_line (iter))
+ end_index = pango_layout_iter_get_line (iter)->start_index;
+ break;
+ case ATSPI_TEXT_BOUNDARY_LINE_END:
+ if (prev_line)
+ start_index = prev_line->start_index + prev_line->length;
+ break;
+ case ATSPI_TEXT_BOUNDARY_CHAR:
+ case ATSPI_TEXT_BOUNDARY_WORD_START:
+ case ATSPI_TEXT_BOUNDARY_WORD_END:
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_START:
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_END:
+ default:
+ g_assert_not_reached();
+ }
+
+ found = TRUE;
+ break;
+ }
+
+ prev_line = line;
+ }
+ while (pango_layout_iter_next_line (iter));
+
+ if (!found)
+ {
+ start_index = prev_line->start_index + prev_line->length;
+ end_index = start_index;
+ }
+ pango_layout_iter_free (iter);
+
+ *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
+ *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
+}
+
+static void
+pango_layout_get_line_after (PangoLayout *layout,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset)
+{
+ PangoLayoutIter *iter;
+ PangoLayoutLine *line, *prev_line = NULL;
+ int index, start_index, end_index;
+ const char *text;
+ gboolean found = FALSE;
+
+ text = pango_layout_get_text (layout);
+ index = g_utf8_offset_to_pointer (text, offset) - text;
+ iter = pango_layout_get_iter (layout);
+ do
+ {
+ line = pango_layout_iter_get_line (iter);
+ start_index = line->start_index;
+ end_index = start_index + line->length;
+
+ if (index >= start_index && index <= end_index)
+ {
+ /* Found line for offset */
+ if (pango_layout_iter_next_line (iter))
+ {
+ line = pango_layout_iter_get_line (iter);
+ switch (boundary_type)
+ {
+ case ATSPI_TEXT_BOUNDARY_LINE_START:
+ start_index = line->start_index;
+ if (pango_layout_iter_next_line (iter))
+ end_index = pango_layout_iter_get_line (iter)->start_index;
+ else
+ end_index = start_index + line->length;
+ break;
+ case ATSPI_TEXT_BOUNDARY_LINE_END:
+ start_index = end_index;
+ end_index = line->start_index + line->length;
+ break;
+ case ATSPI_TEXT_BOUNDARY_CHAR:
+ case ATSPI_TEXT_BOUNDARY_WORD_START:
+ case ATSPI_TEXT_BOUNDARY_WORD_END:
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_START:
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_END:
+ default:
+ g_assert_not_reached();
+ }
+ }
+ else
+ start_index = end_index;
+
+ found = TRUE;
+ break;
+ }
+
+ prev_line = line;
+ }
+ while (pango_layout_iter_next_line (iter));
+
+ if (!found)
+ {
+ start_index = prev_line->start_index + prev_line->length;
+ end_index = start_index;
+ }
+ pango_layout_iter_free (iter);
+
+ *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
+ *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
+}
+
+/*
+ * gtk_pango_get_text_before:
+ * @layout: a #PangoLayout
+ * @offset: a character offset in @layout
+ * @boundary_type: a #AtspiTextBoundaryType
+ * @start_offset: return location for the start of the returned text
+ * @end_offset: return location for the end of the return text
+ *
+ * Gets a slice of the text from @layout before @offset.
+ *
+ * The @boundary_type determines the size of the returned slice of
+ * text. For the exact semantics of this function, see
+ * atk_text_get_text_before_offset().
+ *
+ * Returns: a newly allocated string containing a slice of text
+ * from layout. Free with g_free().
+ */
+char *
+gtk_pango_get_text_before (PangoLayout *layout,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset)
+{
+ const char *text;
+ int start, end;
+ const PangoLogAttr *attrs;
+ int n_attrs;
+
+ text = pango_layout_get_text (layout);
+
+ if (text[0] == 0)
+ {
+ *start_offset = 0;
+ *end_offset = 0;
+ return g_strdup ("");
+ }
+
+ attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
+
+ start = offset;
+ end = start;
+
+ switch (boundary_type)
+ {
+ case ATSPI_TEXT_BOUNDARY_CHAR:
+ start = gtk_pango_move_chars (layout, start, -1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_WORD_START:
+ if (!attrs[start].is_word_start)
+ start = gtk_pango_move_words (layout, start, -1);
+ end = start;
+ start = gtk_pango_move_words (layout, start, -1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_WORD_END:
+ if (gtk_pango_is_inside_word (layout, start) &&
+ !attrs[start].is_word_start)
+ start = gtk_pango_move_words (layout, start, -1);
+ while (!attrs[start].is_word_end && start > 0)
+ start = gtk_pango_move_chars (layout, start, -1);
+ end = start;
+ start = gtk_pango_move_words (layout, start, -1);
+ while (!attrs[start].is_word_end && start > 0)
+ start = gtk_pango_move_chars (layout, start, -1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_START:
+ if (!attrs[start].is_sentence_start)
+ start = gtk_pango_move_sentences (layout, start, -1);
+ end = start;
+ start = gtk_pango_move_sentences (layout, start, -1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_END:
+ if (gtk_pango_is_inside_sentence (layout, start) &&
+ !attrs[start].is_sentence_start)
+ start = gtk_pango_move_sentences (layout, start, -1);
+ while (!attrs[start].is_sentence_end && start > 0)
+ start = gtk_pango_move_chars (layout, start, -1);
+ end = start;
+ start = gtk_pango_move_sentences (layout, start, -1);
+ while (!attrs[start].is_sentence_end && start > 0)
+ start = gtk_pango_move_chars (layout, start, -1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_LINE_START:
+ case ATSPI_TEXT_BOUNDARY_LINE_END:
+ pango_layout_get_line_before (layout, offset, boundary_type, &start, &end);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ *start_offset = start;
+ *end_offset = end;
+
+ g_assert (start <= end);
+
+ return g_utf8_substring (text, start, end);
+}
+
+/*
+ * gtk_pango_get_text_after:
+ * @layout: a #PangoLayout
+ * @offset: a character offset in @layout
+ * @boundary_type: a #AtspiTextBoundaryType
+ * @start_offset: return location for the start of the returned text
+ * @end_offset: return location for the end of the return text
+ *
+ * Gets a slice of the text from @layout after @offset.
+ *
+ * The @boundary_type determines the size of the returned slice of
+ * text. For the exact semantics of this function, see
+ * atk_text_get_text_after_offset().
+ *
+ * Returns: a newly allocated string containing a slice of text
+ * from layout. Free with g_free().
+ */
+char *
+gtk_pango_get_text_after (PangoLayout *layout,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset)
+{
+ const char *text;
+ int start, end;
+ const PangoLogAttr *attrs;
+ int n_attrs;
+
+ text = pango_layout_get_text (layout);
+
+ if (text[0] == 0)
+ {
+ *start_offset = 0;
+ *end_offset = 0;
+ return g_strdup ("");
+ }
+
+ attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
+
+ start = offset;
+ end = start;
+
+ switch (boundary_type)
+ {
+ case ATSPI_TEXT_BOUNDARY_CHAR:
+ start = gtk_pango_move_chars (layout, start, 1);
+ end = start;
+ end = gtk_pango_move_chars (layout, end, 1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_WORD_START:
+ if (gtk_pango_is_inside_word (layout, end))
+ end = gtk_pango_move_words (layout, end, 1);
+ while (!attrs[end].is_word_start && end < n_attrs - 1)
+ end = gtk_pango_move_chars (layout, end, 1);
+ start = end;
+ if (end < n_attrs - 1)
+ {
+ end = gtk_pango_move_words (layout, end, 1);
+ while (!attrs[end].is_word_start && end < n_attrs - 1)
+ end = gtk_pango_move_chars (layout, end, 1);
+ }
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_WORD_END:
+ end = gtk_pango_move_words (layout, end, 1);
+ start = end;
+ if (end < n_attrs - 1)
+ end = gtk_pango_move_words (layout, end, 1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_START:
+ if (gtk_pango_is_inside_sentence (layout, end))
+ end = gtk_pango_move_sentences (layout, end, 1);
+ while (!attrs[end].is_sentence_start && end < n_attrs - 1)
+ end = gtk_pango_move_chars (layout, end, 1);
+ start = end;
+ if (end < n_attrs - 1)
+ {
+ end = gtk_pango_move_sentences (layout, end, 1);
+ while (!attrs[end].is_sentence_start && end < n_attrs - 1)
+ end = gtk_pango_move_chars (layout, end, 1);
+ }
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_END:
+ end = gtk_pango_move_sentences (layout, end, 1);
+ start = end;
+ if (end < n_attrs - 1)
+ end = gtk_pango_move_sentences (layout, end, 1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_LINE_START:
+ case ATSPI_TEXT_BOUNDARY_LINE_END:
+ pango_layout_get_line_after (layout, offset, boundary_type, &start, &end);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ *start_offset = start;
+ *end_offset = end;
+
+ g_assert (start <= end);
+
+ return g_utf8_substring (text, start, end);
+}
+
+/*
+ * gtk_pango_get_text_at:
+ * @layout: a #PangoLayout
+ * @offset: a character offset in @layout
+ * @boundary_type: a #AtspiTextBoundaryType
+ * @start_offset: return location for the start of the returned text
+ * @end_offset: return location for the end of the return text
+ *
+ * Gets a slice of the text from @layout at @offset.
+ *
+ * The @boundary_type determines the size of the returned slice of
+ * text. For the exact semantics of this function, see
+ * atk_text_get_text_after_offset().
+ *
+ * Returns: a newly allocated string containing a slice of text
+ * from layout. Free with g_free().
+ */
+char *
+gtk_pango_get_text_at (PangoLayout *layout,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset)
+{
+ const char *text;
+ int start, end;
+ const PangoLogAttr *attrs;
+ int n_attrs;
+
+ text = pango_layout_get_text (layout);
+
+ if (text[0] == 0)
+ {
+ *start_offset = 0;
+ *end_offset = 0;
+ return g_strdup ("");
+ }
+
+ attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
+
+ start = offset;
+ end = start;
+
+ switch (boundary_type)
+ {
+ case ATSPI_TEXT_BOUNDARY_CHAR:
+ end = gtk_pango_move_chars (layout, end, 1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_WORD_START:
+ if (!attrs[start].is_word_start)
+ start = gtk_pango_move_words (layout, start, -1);
+ if (gtk_pango_is_inside_word (layout, end))
+ end = gtk_pango_move_words (layout, end, 1);
+ while (!attrs[end].is_word_start && end < n_attrs - 1)
+ end = gtk_pango_move_chars (layout, end, 1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_WORD_END:
+ if (gtk_pango_is_inside_word (layout, start) &&
+ !attrs[start].is_word_start)
+ start = gtk_pango_move_words (layout, start, -1);
+ while (!attrs[start].is_word_end && start > 0)
+ start = gtk_pango_move_chars (layout, start, -1);
+ end = gtk_pango_move_words (layout, end, 1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_START:
+ if (!attrs[start].is_sentence_start)
+ start = gtk_pango_move_sentences (layout, start, -1);
+ if (gtk_pango_is_inside_sentence (layout, end))
+ end = gtk_pango_move_sentences (layout, end, 1);
+ while (!attrs[end].is_sentence_start && end < n_attrs - 1)
+ end = gtk_pango_move_chars (layout, end, 1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_END:
+ if (gtk_pango_is_inside_sentence (layout, start) &&
+ !attrs[start].is_sentence_start)
+ start = gtk_pango_move_sentences (layout, start, -1);
+ while (!attrs[start].is_sentence_end && start > 0)
+ start = gtk_pango_move_chars (layout, start, -1);
+ end = gtk_pango_move_sentences (layout, end, 1);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_LINE_START:
+ case ATSPI_TEXT_BOUNDARY_LINE_END:
+ pango_layout_get_line_at (layout, offset, boundary_type, &start, &end);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ *start_offset = start;
+ *end_offset = end;
+
+ g_assert (start <= end);
+
+ return g_utf8_substring (text, start, end);
+}
+
+char *gtk_pango_get_string_at (PangoLayout *layout,
+ int offset,
+ AtspiTextGranularity granularity,
+ int *start_offset,
+ int *end_offset)
+{
+ const char *text;
+ int start, end;
+ const PangoLogAttr *attrs;
+ int n_attrs;
+
+ text = pango_layout_get_text (layout);
+
+ if (text[0] == 0)
+ {
+ *start_offset = 0;
+ *end_offset = 0;
+ return g_strdup ("");
+ }
+
+ attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
+
+ start = offset;
+ end = start;
+
+ switch (granularity)
+ {
+ case ATSPI_TEXT_GRANULARITY_CHAR:
+ end = gtk_pango_move_chars (layout, end, 1);
+ break;
+
+ case ATSPI_TEXT_GRANULARITY_WORD:
+ if (!attrs[start].is_word_start)
+ start = gtk_pango_move_words (layout, start, -1);
+ if (gtk_pango_is_inside_word (layout, end))
+ end = gtk_pango_move_words (layout, end, 1);
+ while (!attrs[end].is_word_start && end < n_attrs - 1)
+ end = gtk_pango_move_chars (layout, end, 1);
+ break;
+
+ case ATSPI_TEXT_GRANULARITY_SENTENCE:
+ if (!attrs[start].is_sentence_start)
+ start = gtk_pango_move_sentences (layout, start, -1);
+ if (gtk_pango_is_inside_sentence (layout, end))
+ end = gtk_pango_move_sentences (layout, end, 1);
+ while (!attrs[end].is_sentence_start && end < n_attrs - 1)
+ end = gtk_pango_move_chars (layout, end, 1);
+ break;
+
+ case ATSPI_TEXT_GRANULARITY_LINE:
+ pango_layout_get_line_at (layout, offset, ATSPI_TEXT_BOUNDARY_LINE_START, &start, &end);
+ break;
+
+ case ATSPI_TEXT_GRANULARITY_PARAGRAPH:
+ /* FIXME: In theory, a layout can hold more than one paragraph */
+ start = 0;
+ end = g_utf8_strlen (text, -1);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ *start_offset = start;
+ *end_offset = end;
+
+ g_assert (start <= end);
+
+ return g_utf8_substring (text, start, end);
+}
diff --git a/gtk/a11y/gtkatspipangoprivate.h b/gtk/a11y/gtkatspipangoprivate.h
new file mode 100644
index 0000000000..e9e91d292f
--- /dev/null
+++ b/gtk/a11y/gtkatspipangoprivate.h
@@ -0,0 +1,62 @@
+/* gtkatspipangoprivate.h: Utilities for pango and AT-SPI
+ * Copyright 2020 Red Hat, Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango/pangocairo.h>
+#include "gtkatspiprivate.h"
+
+G_BEGIN_DECLS
+
+const char *pango_wrap_mode_to_string (PangoWrapMode mode);
+const char *pango_underline_to_string (PangoUnderline underline);
+const char *pango_stretch_to_string (PangoStretch stretch);
+const char *pango_style_to_string (PangoStyle style);
+const char *pango_variant_to_string (PangoVariant variant);
+
+void gtk_pango_get_font_attributes (PangoFontDescription *font,
+ GVariantBuilder *builder);
+void gtk_pango_get_default_attributes (PangoLayout *layout,
+ GVariantBuilder *builder);
+void gtk_pango_get_run_attributes (PangoLayout *layout,
+ GVariantBuilder *builder,
+ int offset,
+ int *start_offset,
+ int *end_offset);
+
+char *gtk_pango_get_text_before (PangoLayout *layout,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset);
+char *gtk_pango_get_text_at (PangoLayout *layout,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset);
+char *gtk_pango_get_text_after (PangoLayout *layout,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset);
+char *gtk_pango_get_string_at (PangoLayout *layout,
+ int offset,
+ AtspiTextGranularity granularity,
+ int *start_offset,
+ int *end_offset);
+
+G_END_DECLS
diff --git a/gtk/a11y/gtkatspiprivate.h b/gtk/a11y/gtkatspiprivate.h
new file mode 100644
index 0000000000..c3fa225df8
--- /dev/null
+++ b/gtk/a11y/gtkatspiprivate.h
@@ -0,0 +1,249 @@
+/* gtkatspiprivate.h: AT-SPI shared types
+ *
+ * Copyright 2020 GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ ATSPI_ROLE_INVALID,
+ ATSPI_ROLE_ACCELERATOR_LABEL,
+ ATSPI_ROLE_ALERT,
+ ATSPI_ROLE_ANIMATION,
+ ATSPI_ROLE_ARROW,
+ ATSPI_ROLE_CALENDAR,
+ ATSPI_ROLE_CANVAS,
+ ATSPI_ROLE_CHECK_BOX,
+ ATSPI_ROLE_CHECK_MENU_ITEM,
+ ATSPI_ROLE_COLOR_CHOOSER,
+ ATSPI_ROLE_COLUMN_HEADER,
+ ATSPI_ROLE_COMBO_BOX,
+ ATSPI_ROLE_DATE_EDITOR,
+ ATSPI_ROLE_DESKTOP_ICON,
+ ATSPI_ROLE_DESKTOP_FRAME,
+ ATSPI_ROLE_DIAL,
+ ATSPI_ROLE_DIALOG,
+ ATSPI_ROLE_DIRECTORY_PANE,
+ ATSPI_ROLE_DRAWING_AREA,
+ ATSPI_ROLE_FILE_CHOOSER,
+ ATSPI_ROLE_FILLER,
+ ATSPI_ROLE_FOCUS_TRAVERSABLE,
+ ATSPI_ROLE_FONT_CHOOSER,
+ ATSPI_ROLE_FRAME,
+ ATSPI_ROLE_GLASS_PANE,
+ ATSPI_ROLE_HTML_CONTAINER,
+ ATSPI_ROLE_ICON,
+ ATSPI_ROLE_IMAGE,
+ ATSPI_ROLE_INTERNAL_FRAME,
+ ATSPI_ROLE_LABEL,
+ ATSPI_ROLE_LAYERED_PANE,
+ ATSPI_ROLE_LIST,
+ ATSPI_ROLE_LIST_ITEM,
+ ATSPI_ROLE_MENU,
+ ATSPI_ROLE_MENU_BAR,
+ ATSPI_ROLE_MENU_ITEM,
+ ATSPI_ROLE_OPTION_PANE,
+ ATSPI_ROLE_PAGE_TAB,
+ ATSPI_ROLE_PAGE_TAB_LIST,
+ ATSPI_ROLE_PANEL,
+ ATSPI_ROLE_PASSWORD_TEXT,
+ ATSPI_ROLE_POPUP_MENU,
+ ATSPI_ROLE_PROGRESS_BAR,
+ ATSPI_ROLE_PUSH_BUTTON,
+ ATSPI_ROLE_RADIO_BUTTON,
+ ATSPI_ROLE_RADIO_MENU_ITEM,
+ ATSPI_ROLE_ROOT_PANE,
+ ATSPI_ROLE_ROW_HEADER,
+ ATSPI_ROLE_SCROLL_BAR,
+ ATSPI_ROLE_SCROLL_PANE,
+ ATSPI_ROLE_SEPARATOR,
+ ATSPI_ROLE_SLIDER,
+ ATSPI_ROLE_SPIN_BUTTON,
+ ATSPI_ROLE_SPLIT_PANE,
+ ATSPI_ROLE_STATUS_BAR,
+ ATSPI_ROLE_TABLE,
+ ATSPI_ROLE_TABLE_CELL,
+ ATSPI_ROLE_TABLE_COLUMN_HEADER,
+ ATSPI_ROLE_TABLE_ROW_HEADER,
+ ATSPI_ROLE_TEAROFF_MENU_ITEM,
+ ATSPI_ROLE_TERMINAL,
+ ATSPI_ROLE_TEXT,
+ ATSPI_ROLE_TOGGLE_BUTTON,
+ ATSPI_ROLE_TOOL_BAR,
+ ATSPI_ROLE_TOOL_TIP,
+ ATSPI_ROLE_TREE,
+ ATSPI_ROLE_TREE_TABLE,
+ ATSPI_ROLE_UNKNOWN,
+ ATSPI_ROLE_VIEWPORT,
+ ATSPI_ROLE_WINDOW,
+ ATSPI_ROLE_EXTENDED,
+ ATSPI_ROLE_HEADER,
+ ATSPI_ROLE_FOOTER,
+ ATSPI_ROLE_PARAGRAPH,
+ ATSPI_ROLE_RULER,
+ ATSPI_ROLE_APPLICATION,
+ ATSPI_ROLE_AUTOCOMPLETE,
+ ATSPI_ROLE_EDITBAR,
+ ATSPI_ROLE_EMBEDDED,
+ ATSPI_ROLE_ENTRY,
+ ATSPI_ROLE_CHART,
+ ATSPI_ROLE_CAPTION,
+ ATSPI_ROLE_DOCUMENT_FRAME,
+ ATSPI_ROLE_HEADING,
+ ATSPI_ROLE_PAGE,
+ ATSPI_ROLE_SECTION,
+ ATSPI_ROLE_REDUNDANT_OBJECT,
+ ATSPI_ROLE_FORM,
+ ATSPI_ROLE_LINK,
+ ATSPI_ROLE_INPUT_METHOD_WINDOW,
+ ATSPI_ROLE_TABLE_ROW,
+ ATSPI_ROLE_TREE_ITEM,
+ ATSPI_ROLE_DOCUMENT_SPREADSHEET,
+ ATSPI_ROLE_DOCUMENT_PRESENTATION,
+ ATSPI_ROLE_DOCUMENT_TEXT,
+ ATSPI_ROLE_DOCUMENT_WEB,
+ ATSPI_ROLE_DOCUMENT_EMAIL,
+ ATSPI_ROLE_COMMENT,
+ ATSPI_ROLE_LIST_BOX,
+ ATSPI_ROLE_GROUPING,
+ ATSPI_ROLE_IMAGE_MAP,
+ ATSPI_ROLE_NOTIFICATION,
+ ATSPI_ROLE_INFO_BAR,
+ ATSPI_ROLE_LEVEL_BAR,
+ ATSPI_ROLE_TITLE_BAR,
+ ATSPI_ROLE_BLOCK_QUOTE,
+ ATSPI_ROLE_AUDIO,
+ ATSPI_ROLE_VIDEO,
+ ATSPI_ROLE_DEFINITION,
+ ATSPI_ROLE_ARTICLE,
+ ATSPI_ROLE_LANDMARK,
+ ATSPI_ROLE_LOG,
+ ATSPI_ROLE_MARQUEE,
+ ATSPI_ROLE_MATH,
+ ATSPI_ROLE_RATING,
+ ATSPI_ROLE_TIMER,
+ ATSPI_ROLE_STATIC,
+ ATSPI_ROLE_MATH_FRACTION,
+ ATSPI_ROLE_MATH_ROOT,
+ ATSPI_ROLE_SUBSCRIPT,
+ ATSPI_ROLE_SUPERSCRIPT,
+ ATSPI_ROLE_DESCRIPTION_LIST,
+ ATSPI_ROLE_DESCRIPTION_TERM,
+ ATSPI_ROLE_DESCRIPTION_VALUE,
+ ATSPI_ROLE_FOOTNOTE,
+ ATSPI_ROLE_CONTENT_DELETION,
+ ATSPI_ROLE_CONTENT_INSERTION,
+ ATSPI_ROLE_MARK,
+ ATSPI_ROLE_SUGGESTION,
+ ATSPI_ROLE_LAST_DEFINED,
+} AtspiRole;
+
+typedef enum {
+ ATSPI_STATE_INVALID,
+ ATSPI_STATE_ACTIVE,
+ ATSPI_STATE_ARMED,
+ ATSPI_STATE_BUSY,
+ ATSPI_STATE_CHECKED,
+ ATSPI_STATE_COLLAPSED,
+ ATSPI_STATE_DEFUNCT,
+ ATSPI_STATE_EDITABLE,
+ ATSPI_STATE_ENABLED,
+ ATSPI_STATE_EXPANDABLE,
+ ATSPI_STATE_EXPANDED,
+ ATSPI_STATE_FOCUSABLE,
+ ATSPI_STATE_FOCUSED,
+ ATSPI_STATE_HAS_TOOLTIP,
+ ATSPI_STATE_HORIZONTAL,
+ ATSPI_STATE_ICONIFIED,
+ ATSPI_STATE_MODAL,
+ ATSPI_STATE_MULTI_LINE,
+ ATSPI_STATE_MULTISELECTABLE,
+ ATSPI_STATE_OPAQUE,
+ ATSPI_STATE_PRESSED,
+ ATSPI_STATE_RESIZABLE,
+ ATSPI_STATE_SELECTABLE,
+ ATSPI_STATE_SELECTED,
+ ATSPI_STATE_SENSITIVE,
+ ATSPI_STATE_SHOWING,
+ ATSPI_STATE_SINGLE_LINE,
+ ATSPI_STATE_STALE,
+ ATSPI_STATE_TRANSIENT,
+ ATSPI_STATE_VERTICAL,
+ ATSPI_STATE_VISIBLE,
+ ATSPI_STATE_MANAGES_DESCENDANTS,
+ ATSPI_STATE_INDETERMINATE,
+ ATSPI_STATE_REQUIRED,
+ ATSPI_STATE_TRUNCATED,
+ ATSPI_STATE_ANIMATED,
+ ATSPI_STATE_INVALID_ENTRY,
+ ATSPI_STATE_SUPPORTS_AUTOCOMPLETION,
+ ATSPI_STATE_SELECTABLE_TEXT,
+ ATSPI_STATE_IS_DEFAULT,
+ ATSPI_STATE_VISITED,
+ ATSPI_STATE_CHECKABLE,
+ ATSPI_STATE_HAS_POPUP,
+ ATSPI_STATE_READ_ONLY,
+ ATSPI_STATE_LAST_DEFINED,
+} AtspiStateType;
+
+typedef enum {
+ ATSPI_RELATION_NULL,
+ ATSPI_RELATION_LABEL_FOR,
+ ATSPI_RELATION_LABELLED_BY,
+ ATSPI_RELATION_CONTROLLER_FOR,
+ ATSPI_RELATION_CONTROLLED_BY,
+ ATSPI_RELATION_MEMBER_OF,
+ ATSPI_RELATION_TOOLTIP_FOR,
+ ATSPI_RELATION_NODE_CHILD_OF,
+ ATSPI_RELATION_NODE_PARENT_OF,
+ ATSPI_RELATION_EXTENDED,
+ ATSPI_RELATION_FLOWS_TO,
+ ATSPI_RELATION_FLOWS_FROM,
+ ATSPI_RELATION_SUBWINDOW_OF,
+ ATSPI_RELATION_EMBEDS,
+ ATSPI_RELATION_EMBEDDED_BY,
+ ATSPI_RELATION_POPUP_FOR,
+ ATSPI_RELATION_PARENT_WINDOW_OF,
+ ATSPI_RELATION_DESCRIPTION_FOR,
+ ATSPI_RELATION_DESCRIBED_BY,
+ ATSPI_RELATION_LAST_DEFINED,
+} AtspiRelationType;
+
+typedef enum {
+ ATSPI_TEXT_BOUNDARY_CHAR,
+ ATSPI_TEXT_BOUNDARY_WORD_START,
+ ATSPI_TEXT_BOUNDARY_WORD_END,
+ ATSPI_TEXT_BOUNDARY_SENTENCE_START,
+ ATSPI_TEXT_BOUNDARY_SENTENCE_END,
+ ATSPI_TEXT_BOUNDARY_LINE_START,
+ ATSPI_TEXT_BOUNDARY_LINE_END,
+} AtspiTextBoundaryType;
+
+typedef enum {
+ ATSPI_TEXT_GRANULARITY_CHAR,
+ ATSPI_TEXT_GRANULARITY_WORD,
+ ATSPI_TEXT_GRANULARITY_SENTENCE,
+ ATSPI_TEXT_GRANULARITY_LINE,
+ ATSPI_TEXT_GRANULARITY_PARAGRAPH
+} AtspiTextGranularity;
+
+G_END_DECLS
diff --git a/gtk/a11y/gtkatspiroot.c b/gtk/a11y/gtkatspiroot.c
new file mode 100644
index 0000000000..75290623ea
--- /dev/null
+++ b/gtk/a11y/gtkatspiroot.c
@@ -0,0 +1,603 @@
+/* gtkatspiroot.c: AT-SPI root object
+ *
+ * Copyright 2020 GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkatspirootprivate.h"
+
+#include "gtkatspicontextprivate.h"
+#include "gtkaccessibleprivate.h"
+#include "gtkatspiprivate.h"
+#include "gtkatspiutilsprivate.h"
+
+#include "gtkdebug.h"
+#include "gtkwindow.h"
+
+#include "a11y/atspi/atspi-accessible.h"
+#include "a11y/atspi/atspi-application.h"
+
+#include <locale.h>
+
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+#define ATSPI_VERSION "2.1"
+
+#define ATSPI_PATH_PREFIX "/org/a11y/atspi"
+#define ATSPI_ROOT_PATH ATSPI_PATH_PREFIX "/accessible/root"
+#define ATSPI_CACHE_PATH ATSPI_PATH_PREFIX "/cache"
+
+struct _GtkAtSpiRoot
+{
+ GObject parent_instance;
+
+ char *bus_address;
+ GDBusConnection *connection;
+
+ const char *root_path;
+
+ const char *toolkit_name;
+ const char *version;
+ const char *atspi_version;
+
+ char *desktop_name;
+ char *desktop_path;
+
+ gint32 application_id;
+
+ GtkAtSpiCache *cache;
+
+ GListModel *toplevels;
+};
+
+enum
+{
+ PROP_BUS_ADDRESS = 1,
+
+ N_PROPS
+};
+
+static GParamSpec *obj_props[N_PROPS];
+
+G_DEFINE_TYPE (GtkAtSpiRoot, gtk_at_spi_root, G_TYPE_OBJECT)
+
+static void
+gtk_at_spi_root_finalize (GObject *gobject)
+{
+ GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+ g_free (self->bus_address);
+ g_free (self->desktop_name);
+ g_free (self->desktop_path);
+
+ G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->dispose (gobject);
+}
+
+static void
+gtk_at_spi_root_dispose (GObject *gobject)
+{
+ GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+ g_clear_object (&self->cache);
+ g_clear_object (&self->connection);
+
+ G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->dispose (gobject);
+}
+
+static void
+gtk_at_spi_root_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_BUS_ADDRESS:
+ self->bus_address = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+gtk_at_spi_root_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_BUS_ADDRESS:
+ g_value_set_string (value, self->bus_address);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+handle_application_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ if (g_strcmp0 (method_name, "GetLocale") == 0)
+ {
+ guint lctype;
+ const char *locale;
+
+ int types[] = {
+ LC_MESSAGES, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME
+ };
+
+ g_variant_get (parameters, "(u)", &lctype);
+ if (lctype >= G_N_ELEMENTS (types))
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "Not a known locale facet: %u", lctype);
+ return;
+ }
+
+ locale = setlocale (types[lctype], NULL);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", locale));
+ }
+}
+
+static GVariant *
+handle_application_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkAtSpiRoot *self = user_data;
+ GVariant *res = NULL;
+
+ if (g_strcmp0 (property_name, "Id") == 0)
+ res = g_variant_new_int32 (self->application_id);
+ else if (g_strcmp0 (property_name, "ToolkitName") == 0)
+ res = g_variant_new_string (self->toolkit_name);
+ else if (g_strcmp0 (property_name, "Version") == 0)
+ res = g_variant_new_string (self->version);
+ else if (g_strcmp0 (property_name, "AtspiVersion") == 0)
+ res = g_variant_new_string (self->atspi_version);
+ else
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Unknown property '%s'", property_name);
+
+ return res;
+}
+
+static gboolean
+handle_application_set_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GVariant *value,
+ GError **error,
+ gpointer user_data)
+{
+ GtkAtSpiRoot *self = user_data;
+
+ if (g_strcmp0 (property_name, "Id") == 0)
+ {
+ g_variant_get (value, "i", &(self->application_id));
+ }
+ else
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Invalid property '%s'", property_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+handle_accessible_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkAtSpiRoot *self = user_data;
+
+ if (g_strcmp0 (method_name, "GetRole") == 0)
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", ATSPI_ROLE_APPLICATION));
+ else if (g_strcmp0 (method_name, "GetRoleName") == 0)
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "application"));
+ else if (g_strcmp0 (method_name, "GetLocalizedRoleName") == 0)
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", C_("accessibility", "application")));
+ else if (g_strcmp0 (method_name, "GetState") == 0)
+ {
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(au)"));
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("au"));
+ g_variant_builder_add (&builder, "u", 0);
+ g_variant_builder_add (&builder, "u", 0);
+ g_variant_builder_close (&builder);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder));
+ }
+ else if (g_strcmp0 (method_name, "GetAttributes") == 0)
+ {
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(a{ss})"));
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{ss}"));
+ g_variant_builder_add (&builder, "{ss}", "toolkit", "GTK");
+ g_variant_builder_close (&builder);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_builder_end (&builder));
+ }
+ else if (g_strcmp0 (method_name, "GetApplication") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("((so))",
+ self->desktop_name,
+ self->desktop_path));
+ }
+ else if (g_strcmp0 (method_name, "GetChildAtIndex") == 0)
+ {
+ int idx, real_idx = 0;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ GtkWidget *window = NULL;
+ guint n_toplevels = g_list_model_get_n_items (self->toplevels);
+ for (guint i = 0; i < n_toplevels; i++)
+ {
+ window = g_list_model_get_item (self->toplevels, i);
+
+ g_object_unref (window);
+
+ if (!gtk_widget_get_visible (window))
+ continue;
+
+ if (real_idx == idx)
+ break;
+
+ real_idx += 1;
+ }
+
+ if (window == NULL)
+ return;
+
+ GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (window));
+
+ const char *name = g_dbus_connection_get_unique_name (self->connection);
+ const char *path = gtk_at_spi_context_get_context_path (GTK_AT_SPI_CONTEXT (context));
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("((so))", name, path));
+ }
+ else if (g_strcmp0 (method_name, "GetChildren") == 0)
+ {
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(so)"));
+
+ guint n_toplevels = g_list_model_get_n_items (self->toplevels);
+ for (guint i = 0; i < n_toplevels; i++)
+ {
+ GtkWidget *window = g_list_model_get_item (self->toplevels, i);
+
+ g_object_unref (window);
+
+ if (!gtk_widget_get_visible (window))
+ continue;
+
+ GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (window));
+ const char *name = g_dbus_connection_get_unique_name (self->connection);
+ const char *path = gtk_at_spi_context_get_context_path (GTK_AT_SPI_CONTEXT (context));
+
+ g_variant_builder_add (&builder, "(so)", name, path);
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(so))", &builder));
+ }
+ else if (g_strcmp0 (method_name, "GetIndexInParent") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", -1));
+ }
+ else if (g_strcmp0 (method_name, "GetRelationSet") == 0)
+ {
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(ua(so))"));
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a(ua(so)))", &builder));
+ }
+ else if (g_strcmp0 (method_name, "GetInterfaces") == 0)
+ {
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("as"));
+
+ g_variant_builder_add (&builder, "s", "org.a11y.atspi.Accessible");
+ g_variant_builder_add (&builder, "s", "org.a11y.atspi.Application");
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(as)", &builder));
+ }
+}
+
+static GVariant *
+handle_accessible_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkAtSpiRoot *self = user_data;
+ GVariant *res = NULL;
+
+ if (g_strcmp0 (property_name, "Name") == 0)
+ res = g_variant_new_string (g_get_prgname () ? g_get_prgname () : "Unnamed");
+ else if (g_strcmp0 (property_name, "Description") == 0)
+ res = g_variant_new_string (g_get_application_name () ? g_get_application_name () : "No description");
+ else if (g_strcmp0 (property_name, "Locale") == 0)
+ res = g_variant_new_string (setlocale (LC_MESSAGES, NULL));
+ else if (g_strcmp0 (property_name, "AccessibleId") == 0)
+ res = g_variant_new_string ("");
+ else if (g_strcmp0 (property_name, "Parent") == 0)
+ res = g_variant_new ("(so)", self->desktop_name, self->desktop_path);
+ else if (g_strcmp0 (property_name, "ChildCount") == 0)
+ {
+ guint n_toplevels = g_list_model_get_n_items (self->toplevels);
+ int n_children = 0;
+
+ for (guint i = 0; i < n_toplevels; i++)
+ {
+ GtkWidget *window = g_list_model_get_item (self->toplevels, i);
+
+ if (gtk_widget_get_visible (window))
+ n_children += 1;
+
+ g_object_unref (window);
+ }
+
+ res = g_variant_new_int32 (n_children);
+ }
+ else
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Unknown property '%s'", property_name);
+
+ return res;
+}
+
+static const GDBusInterfaceVTable root_application_vtable = {
+ handle_application_method,
+ handle_application_get_property,
+ handle_application_set_property,
+};
+
+static const GDBusInterfaceVTable root_accessible_vtable = {
+ handle_accessible_method,
+ handle_accessible_get_property,
+ NULL,
+};
+
+static void
+on_registration_reply (GObject *gobject,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GtkAtSpiRoot *self = user_data;
+
+ GError *error = NULL;
+ GVariant *reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (gobject), result, &error);
+
+ if (error != NULL)
+ {
+ g_critical ("Unable to register the application: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ if (reply != NULL)
+ {
+ g_variant_get (reply, "((so))",
+ &self->desktop_name,
+ &self->desktop_path);
+ g_variant_unref (reply);
+
+ GTK_NOTE (A11Y, g_message ("Connected to the a11y registry at (%s, %s)",
+ self->desktop_name,
+ self->desktop_path));
+ }
+
+ /* Register the cache object */
+ self->cache = gtk_at_spi_cache_new (self->connection, ATSPI_CACHE_PATH);
+
+ /* Monitor the top levels */
+ self->toplevels = gtk_window_get_toplevels ();
+}
+
+static void
+gtk_at_spi_root_register (GtkAtSpiRoot *self)
+{
+ /* Register the root element; every application has a single root, so we only
+ * need to do this once.
+ *
+ * The root element is used to advertise our existence on the accessibility
+ * bus, and it's the entry point to the accessible objects tree.
+ *
+ * The announcement is split into two phases:
+ *
+ * 1. we register the org.a11y.atspi.Application and org.a11y.atspi.Accessible
+ * interfaces at the well-known object path
+ * 2. we invoke the org.a11y.atspi.Socket.Embed method with the connection's
+ * unique name and the object path
+ * 3. the ATSPI registry daemon will set the org.a11y.atspi.Application.Id
+ * property on the given object path
+ * 4. the registration concludes when the Embed method returns us the desktop
+ * name and object path
+ */
+ self->toolkit_name = "GTK";
+ self->version = PACKAGE_VERSION;
+ self->atspi_version = ATSPI_VERSION;
+ self->root_path = ATSPI_ROOT_PATH;
+
+ g_dbus_connection_register_object (self->connection,
+ self->root_path,
+ (GDBusInterfaceInfo *) &atspi_application_interface,
+ &root_application_vtable,
+ self,
+ NULL,
+ NULL);
+ g_dbus_connection_register_object (self->connection,
+ self->root_path,
+ (GDBusInterfaceInfo *) &atspi_accessible_interface,
+ &root_accessible_vtable,
+ self,
+ NULL,
+ NULL);
+
+ GTK_NOTE (A11Y, g_message ("Registering (%s, %s) on the a11y bus",
+ g_dbus_connection_get_unique_name (self->connection),
+ self->root_path));
+
+ g_dbus_connection_call (self->connection,
+ "org.a11y.atspi.Registry",
+ ATSPI_ROOT_PATH,
+ "org.a11y.atspi.Socket",
+ "Embed",
+ g_variant_new ("((so))",
+ g_dbus_connection_get_unique_name (self->connection),
+ self->root_path
+ ),
+ G_VARIANT_TYPE ("((so))"),
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ NULL,
+ on_registration_reply,
+ self);
+}
+
+static void
+gtk_at_spi_root_constructed (GObject *gobject)
+{
+ GtkAtSpiRoot *self = GTK_AT_SPI_ROOT (gobject);
+
+ GError *error = NULL;
+
+ /* The accessibility bus is a fully managed bus */
+ self->connection =
+ g_dbus_connection_new_for_address_sync (self->bus_address,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+ NULL, NULL,
+ &error);
+
+ if (error != NULL)
+ {
+ g_critical ("Unable to connect to the accessibility bus at '%s': %s",
+ self->bus_address,
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ gtk_at_spi_root_register (self);
+
+out:
+ G_OBJECT_CLASS (gtk_at_spi_root_parent_class)->constructed (gobject);
+}
+
+static void
+gtk_at_spi_root_class_init (GtkAtSpiRootClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->constructed = gtk_at_spi_root_constructed;
+ gobject_class->set_property = gtk_at_spi_root_set_property;
+ gobject_class->get_property = gtk_at_spi_root_get_property;
+ gobject_class->dispose = gtk_at_spi_root_dispose;
+ gobject_class->finalize = gtk_at_spi_root_finalize;
+
+ obj_props[PROP_BUS_ADDRESS] =
+ g_param_spec_string ("bus-address", NULL, NULL,
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, obj_props);
+}
+
+static void
+gtk_at_spi_root_init (GtkAtSpiRoot *self)
+{
+}
+
+GtkAtSpiRoot *
+gtk_at_spi_root_new (const char *bus_address)
+{
+ g_return_val_if_fail (bus_address != NULL, NULL);
+
+ return g_object_new (GTK_TYPE_AT_SPI_ROOT,
+ "bus-address", bus_address,
+ NULL);
+}
+
+GDBusConnection *
+gtk_at_spi_root_get_connection (GtkAtSpiRoot *self)
+{
+ g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL);
+
+ return self->connection;
+}
+
+GtkAtSpiCache *
+gtk_at_spi_root_get_cache (GtkAtSpiRoot *self)
+{
+ g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL);
+
+ return self->cache;
+}
+
+/*< private >
+ * gtk_at_spi_root_to_ref:
+ * @self: a #GtkAtSpiRoot
+ *
+ * Returns an ATSPI object reference for the #GtkAtSpiRoot node.
+ *
+ * Returns: (transfer floating): a #GVariant with the root reference
+ */
+GVariant *
+gtk_at_spi_root_to_ref (GtkAtSpiRoot *self)
+{
+ g_return_val_if_fail (GTK_IS_AT_SPI_ROOT (self), NULL);
+
+ if (self->desktop_path == NULL)
+ return gtk_at_spi_null_ref ();
+
+ return g_variant_new ("(so)", self->desktop_name, self->desktop_path);
+}
diff --git a/gtk/a11y/gtkatspirootprivate.h b/gtk/a11y/gtkatspirootprivate.h
new file mode 100644
index 0000000000..0ce5e6b693
--- /dev/null
+++ b/gtk/a11y/gtkatspirootprivate.h
@@ -0,0 +1,45 @@
+/* gtkatspirootprivate.h: AT-SPI root object
+ *
+ * Copyright 2020 GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+#include "gtkatspicacheprivate.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_AT_SPI_ROOT (gtk_at_spi_root_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkAtSpiRoot, gtk_at_spi_root, GTK, AT_SPI_ROOT, GObject)
+
+GtkAtSpiRoot *
+gtk_at_spi_root_new (const char *bus_address);
+
+GDBusConnection *
+gtk_at_spi_root_get_connection (GtkAtSpiRoot *self);
+
+GtkAtSpiCache *
+gtk_at_spi_root_get_cache (GtkAtSpiRoot *self);
+
+GVariant *
+gtk_at_spi_root_to_ref (GtkAtSpiRoot *self);
+
+G_END_DECLS
diff --git a/gtk/a11y/gtkatspiselection.c b/gtk/a11y/gtkatspiselection.c
new file mode 100644
index 0000000000..0260e5d56e
--- /dev/null
+++ b/gtk/a11y/gtkatspiselection.c
@@ -0,0 +1,1060 @@
+/* gtkatspiselection.c: AT-SPI Selection implementation
+ *
+ * Copyright 2020 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkatspiselectionprivate.h"
+
+#include "a11y/atspi/atspi-selection.h"
+
+#include "gtkatcontextprivate.h"
+#include "gtkatspicontextprivate.h"
+#include "gtkaccessibleprivate.h"
+#include "gtkdebug.h"
+#include "gtklistbase.h"
+#include "gtklistbox.h"
+#include "gtkflowbox.h"
+#include "gtkcombobox.h"
+#include "gtkstackswitcher.h"
+#include "gtknotebook.h"
+#include "gtklistview.h"
+#include "gtkgridview.h"
+#include "gtklistitem.h"
+#include "gtkbitset.h"
+#include "gtklistbaseprivate.h"
+#include "gtklistitemwidgetprivate.h"
+
+#include <gio/gio.h>
+
+typedef struct {
+ int n;
+ GtkWidget *child;
+} Counter;
+
+static void
+find_nth (GtkWidget *box,
+ GtkWidget *child,
+ gpointer data)
+{
+ Counter *counter = data;
+
+ if (counter->n == 0)
+ counter->child = child;
+
+ counter->n--;
+}
+
+static void
+listbox_handle_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (method_name, "GetSelectedChild") == 0)
+ {
+ Counter counter;
+ int idx;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ counter.n = idx;
+ counter.child = NULL;
+
+ gtk_list_box_selected_foreach (GTK_LIST_BOX (widget), (GtkListBoxForeachFunc)find_nth, &counter);
+
+ if (counter.child == NULL)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx);
+ else
+ {
+ GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (counter.child));
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx))));
+ }
+ }
+ else if (g_strcmp0 (method_name, "SelectChild") == 0)
+ {
+ int idx;
+ GtkListBoxRow *row;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (widget), idx);
+ if (!row)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx);
+ else
+ {
+ gboolean ret;
+
+ gtk_list_box_select_row (GTK_LIST_BOX (widget), row);
+ ret = gtk_list_box_row_is_selected (row);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ }
+ else if (g_strcmp0 (method_name, "DeselectChild") == 0)
+ {
+ int idx;
+ GtkListBoxRow *row;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (widget), idx);
+ if (!row)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx);
+ else
+ {
+ gboolean ret;
+
+ gtk_list_box_unselect_row (GTK_LIST_BOX (widget), row);
+ ret = !gtk_list_box_row_is_selected (row);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ }
+ else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0)
+ {
+ Counter counter;
+ int idx;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ counter.n = idx;
+ counter.child = NULL;
+
+ gtk_list_box_selected_foreach (GTK_LIST_BOX (widget), (GtkListBoxForeachFunc)find_nth, &counter);
+
+ if (counter.child == NULL)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx);
+ else
+ {
+ gboolean ret;
+
+ gtk_list_box_unselect_row (GTK_LIST_BOX (widget), GTK_LIST_BOX_ROW (counter.child));
+ ret = !gtk_list_box_row_is_selected (GTK_LIST_BOX_ROW (counter.child));
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ }
+ else if (g_strcmp0 (method_name, "IsChildSelected") == 0)
+ {
+ int idx;
+ GtkListBoxRow *row;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (widget), idx);
+ if (!row)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx);
+ else
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", gtk_list_box_row_is_selected (row)));
+ }
+ else if (g_strcmp0 (method_name, "SelectAll") == 0)
+ {
+ gtk_list_box_select_all (GTK_LIST_BOX (widget));
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
+ }
+ else if (g_strcmp0 (method_name, "ClearSelection") == 0)
+ {
+ gtk_list_box_unselect_all (GTK_LIST_BOX (widget));
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
+ }
+}
+
+static void
+count_selected (GtkWidget *box,
+ GtkWidget *child,
+ gpointer data)
+{
+ *(int *)data += 1;
+}
+
+static GVariant *
+listbox_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkATContext *self = GTK_AT_CONTEXT (user_data);
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (property_name, "NSelectedChildren") == 0)
+ {
+ int count = 0;
+
+ gtk_list_box_selected_foreach (GTK_LIST_BOX (widget), (GtkListBoxForeachFunc)count_selected, &count);
+
+ return g_variant_new_int32 (count);
+ }
+
+ return NULL;
+}
+
+static const GDBusInterfaceVTable listbox_vtable = {
+ listbox_handle_method,
+ listbox_get_property,
+ NULL
+};
+
+
+static void
+listview_handle_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+ GtkSelectionModel *model = gtk_list_base_get_model (GTK_LIST_BASE (widget));
+
+ g_print ("list item %s %s\n", interface_name, method_name);
+
+ if (g_strcmp0 (method_name, "GetSelectedChild") == 0)
+ {
+ int idx;
+ guint pos;
+ GtkBitset *set;
+ GtkWidget *child;
+ GtkListItem *item;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ set = gtk_selection_model_get_selection (model);
+ pos = gtk_bitset_get_nth (set, idx);
+ gtk_bitset_unref (set);
+
+ for (child = gtk_widget_get_first_child (widget);
+ child;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ item = gtk_list_item_widget_get_list_item (GTK_LIST_ITEM_WIDGET (child));
+ if (pos == gtk_list_item_get_position (item))
+ break;
+ }
+
+ if (child == NULL)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx);
+ else
+ {
+ GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child));
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx))));
+ }
+ }
+ else if (g_strcmp0 (method_name, "SelectChild") == 0)
+ {
+ int idx;
+ gboolean ret;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ ret = gtk_selection_model_select_item (model, idx, FALSE);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "DeselectChild") == 0)
+ {
+ int idx;
+ gboolean ret;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ ret = gtk_selection_model_select_item (model, idx, FALSE);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0)
+ {
+ int idx;
+ guint pos;
+ GtkBitset *set;
+ gboolean ret;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ set = gtk_selection_model_get_selection (model);
+ pos = gtk_bitset_get_nth (set, idx);
+ gtk_bitset_unref (set);
+
+ ret = gtk_selection_model_unselect_item (model, pos);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "IsChildSelected") == 0)
+ {
+ int idx;
+ gboolean ret;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ ret = gtk_selection_model_is_selected (model, idx);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "SelectAll") == 0)
+ {
+ gboolean ret;
+
+ ret = gtk_selection_model_select_all (model);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "ClearSelection") == 0)
+ {
+ gboolean ret;
+
+ ret = gtk_selection_model_unselect_all (model);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+}
+
+static GVariant *
+listview_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkATContext *self = GTK_AT_CONTEXT (user_data);
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+ GtkSelectionModel *model = gtk_list_base_get_model (GTK_LIST_BASE (widget));
+
+ if (g_strcmp0 (property_name, "NSelectedChildren") == 0)
+ {
+ int count = 0;
+ GtkBitset *set;
+
+ set = gtk_selection_model_get_selection (model);
+ count = gtk_bitset_get_size (set);
+ gtk_bitset_unref (set);
+
+ return g_variant_new_int32 (count);
+ }
+
+ return NULL;
+}
+
+static const GDBusInterfaceVTable listview_vtable = {
+ listview_handle_method,
+ listview_get_property,
+ NULL
+};
+
+
+static void
+flowbox_handle_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (method_name, "GetSelectedChild") == 0)
+ {
+ Counter counter;
+ int idx;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ counter.n = idx;
+ counter.child = NULL;
+
+ gtk_flow_box_selected_foreach (GTK_FLOW_BOX (widget), (GtkFlowBoxForeachFunc)find_nth, &counter);
+
+ if (counter.child == NULL)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx);
+ else
+ {
+ GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (counter.child));
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx))));
+ }
+ }
+ else if (g_strcmp0 (method_name, "SelectChild") == 0)
+ {
+ int idx;
+ GtkFlowBoxChild *child;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (widget), idx);
+ if (!child)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx);
+ else
+ {
+ gboolean ret;
+
+ gtk_flow_box_select_child (GTK_FLOW_BOX (widget), child);
+ ret = gtk_flow_box_child_is_selected (child);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ }
+ else if (g_strcmp0 (method_name, "DeselectChild") == 0)
+ {
+ int idx;
+ GtkFlowBoxChild *child;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (widget), idx);
+ if (!child)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx);
+ else
+ {
+ gboolean ret;
+
+ gtk_flow_box_unselect_child (GTK_FLOW_BOX (widget), child);
+ ret = !gtk_flow_box_child_is_selected (child);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ }
+ else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0)
+ {
+ Counter counter;
+ int idx;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ counter.n = idx;
+ counter.child = NULL;
+
+ gtk_flow_box_selected_foreach (GTK_FLOW_BOX (widget), (GtkFlowBoxForeachFunc)find_nth, &counter);
+
+ if (counter.child == NULL)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx);
+ else
+ {
+ gboolean ret;
+
+ gtk_flow_box_unselect_child (GTK_FLOW_BOX (widget), GTK_FLOW_BOX_CHILD (counter.child));
+ ret = !gtk_flow_box_child_is_selected (GTK_FLOW_BOX_CHILD (counter.child));
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ }
+ else if (g_strcmp0 (method_name, "IsChildSelected") == 0)
+ {
+ int idx;
+ GtkFlowBoxChild *child;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (widget), idx);
+ if (!child)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx);
+ else
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", gtk_flow_box_child_is_selected (child)));
+ }
+ else if (g_strcmp0 (method_name, "SelectAll") == 0)
+ {
+ gtk_flow_box_select_all (GTK_FLOW_BOX (widget));
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
+ }
+ else if (g_strcmp0 (method_name, "ClearSelection") == 0)
+ {
+ gtk_flow_box_unselect_all (GTK_FLOW_BOX (widget));
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
+ }
+}
+
+static GVariant *
+flowbox_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkATContext *self = GTK_AT_CONTEXT (user_data);
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (property_name, "NSelectedChildren") == 0)
+ {
+ int count = 0;
+
+ gtk_flow_box_selected_foreach (GTK_FLOW_BOX (widget), (GtkFlowBoxForeachFunc)count_selected, &count);
+
+ return g_variant_new_int32 (count);
+ }
+
+ return NULL;
+}
+
+static const GDBusInterfaceVTable flowbox_vtable = {
+ flowbox_handle_method,
+ flowbox_get_property,
+ NULL
+};
+
+
+static void
+combobox_handle_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (method_name, "GetSelectedChild") == 0)
+ {
+ /* Need to figure out what to do here */
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "SelectChild") == 0)
+ {
+ int idx;
+
+ g_variant_get (parameters, "(i)", &idx);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), idx);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
+ }
+ else if (g_strcmp0 (method_name, "DeselectChild") == 0)
+ {
+ int idx;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), -1);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
+ }
+ else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0)
+ {
+ int idx;
+
+ g_variant_get (parameters, "(i)", &idx);
+ if (idx == 0)
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), -1);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", idx == 0));
+ }
+ else if (g_strcmp0 (method_name, "IsChildSelected") == 0)
+ {
+ int idx;
+ gboolean active;
+
+ g_variant_get (parameters, "(i)", &idx);
+ active = idx = gtk_combo_box_get_active (GTK_COMBO_BOX (widget));
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", active));
+ }
+ else if (g_strcmp0 (method_name, "SelectAll") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
+ }
+ else if (g_strcmp0 (method_name, "ClearSelection") == 0)
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), -1);
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
+ }
+}
+
+static GVariant *
+combobox_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkATContext *self = GTK_AT_CONTEXT (user_data);
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (property_name, "NSelectedChildren") == 0)
+ {
+ if (gtk_combo_box_get_active (GTK_COMBO_BOX (widget)))
+ return g_variant_new_int32 (1);
+ else
+ return g_variant_new_int32 (0);
+ }
+
+ return NULL;
+}
+
+static const GDBusInterfaceVTable combobox_vtable = {
+ combobox_handle_method,
+ combobox_get_property,
+ NULL
+};
+
+
+static void
+stackswitcher_handle_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+ GtkStack *stack = gtk_stack_switcher_get_stack (GTK_STACK_SWITCHER (widget));
+
+ if (g_strcmp0 (method_name, "GetSelectedChild") == 0)
+ {
+ guint i, n;
+ GtkSelectionModel *pages;
+ GtkWidget *child;
+
+ pages = gtk_stack_get_pages (stack);
+ n = g_list_model_get_n_items (G_LIST_MODEL (pages));
+ for (i = 0, child = gtk_widget_get_first_child (widget);
+ i < n && child;
+ i++, child = gtk_widget_get_next_sibling (child))
+ {
+ if (gtk_selection_model_is_selected (pages, i))
+ break;
+ }
+ g_object_unref (pages);
+
+ if (child == NULL)
+ g_dbus_method_invocation_return_error_literal (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "No selected child");
+ else
+ {
+ GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child));
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx))));
+ }
+ }
+ else if (g_strcmp0 (method_name, "SelectChild") == 0)
+ {
+ int idx;
+ GtkSelectionModel *pages;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ pages = gtk_stack_get_pages (stack);
+ gtk_selection_model_select_item (pages, idx, TRUE);
+ g_object_unref (pages);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
+ }
+ else if (g_strcmp0 (method_name, "DeselectChild") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
+ }
+ else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
+ }
+ else if (g_strcmp0 (method_name, "IsChildSelected") == 0)
+ {
+ int idx;
+ GtkSelectionModel *pages;
+ gboolean active;
+
+ g_variant_get (parameters, "(i)", &idx);
+
+ pages = gtk_stack_get_pages (stack);
+ active = gtk_selection_model_is_selected (pages, idx);
+ g_object_unref (pages);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", active));
+ }
+ else if (g_strcmp0 (method_name, "SelectAll") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
+ }
+ else if (g_strcmp0 (method_name, "ClearSelection") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
+ }
+}
+
+static GVariant *
+stackswitcher_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkATContext *self = GTK_AT_CONTEXT (user_data);
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (property_name, "NSelectedChildren") == 0)
+ {
+ GtkStack *stack = gtk_stack_switcher_get_stack (GTK_STACK_SWITCHER (widget));
+
+ if (stack == NULL || gtk_stack_get_visible_child (stack) == NULL)
+ return g_variant_new_int32 (0);
+ else
+ return g_variant_new_int32 (1);
+ }
+
+ return NULL;
+}
+
+static const GDBusInterfaceVTable stackswitcher_vtable = {
+ stackswitcher_handle_method,
+ stackswitcher_get_property,
+ NULL
+};
+
+
+static void
+notebook_handle_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+ GtkWidget *notebook = gtk_widget_get_parent (gtk_widget_get_parent (widget));
+
+ g_print ("notebook %s %s\n", interface_name, method_name);
+
+ if (g_strcmp0 (method_name, "GetSelectedChild") == 0)
+ {
+ int i;
+ GtkWidget *child;
+
+ i = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
+
+ for (child = gtk_widget_get_first_child (widget);
+ child;
+ child = gtk_widget_get_next_sibling (widget))
+ {
+ /* skip actions */
+ if (gtk_accessible_get_accessible_role (GTK_ACCESSIBLE (child)) != GTK_ACCESSIBLE_ROLE_TAB)
+ continue;
+
+ if (i == 0)
+ break;
+
+ i--;
+ }
+
+ if (child == NULL)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "No selected child");
+ }
+ else
+ {
+ GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child));
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx))));
+ }
+ }
+ else if (g_strcmp0 (method_name, "SelectChild") == 0)
+ {
+ int i;
+ GtkWidget *child;
+
+ g_variant_get (parameters, "(i)", &i);
+
+ /* skip an action widget */
+ child = gtk_widget_get_first_child (widget);
+ if (gtk_accessible_get_accessible_role (GTK_ACCESSIBLE (child)) != GTK_ACCESSIBLE_ROLE_TAB)
+ i--;
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
+ }
+ else if (g_strcmp0 (method_name, "DeselectChild") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
+ }
+ else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
+ }
+ else if (g_strcmp0 (method_name, "IsChildSelected") == 0)
+ {
+ int i;
+ gboolean active;
+ GtkWidget *child;
+
+ g_variant_get (parameters, "(i)", &i);
+
+ /* skip an action widget */
+ child = gtk_widget_get_first_child (widget);
+ if (gtk_accessible_get_accessible_role (GTK_ACCESSIBLE (child)) != GTK_ACCESSIBLE_ROLE_TAB)
+ i--;
+
+ active = i == gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", active));
+ }
+ else if (g_strcmp0 (method_name, "SelectAll") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
+ }
+ else if (g_strcmp0 (method_name, "ClearSelection") == 0)
+ {
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
+ }
+}
+
+static GVariant *
+notebook_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ if (g_strcmp0 (property_name, "NSelectedChildren") == 0)
+ {
+ return g_variant_new_int32 (1);
+ }
+
+ return NULL;
+}
+
+static const GDBusInterfaceVTable notebook_vtable = {
+ notebook_handle_method,
+ notebook_get_property,
+ NULL
+};
+
+#define IS_NOTEBOOK_TAB_LIST(s,r) \
+ ((r == GTK_ACCESSIBLE_ROLE_TAB_LIST) && \
+ (gtk_widget_get_parent (GTK_WIDGET (s)) != NULL) && \
+ GTK_IS_NOTEBOOK (gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (s)))))
+
+const GDBusInterfaceVTable *
+gtk_atspi_get_selection_vtable (GtkAccessible *accessible,
+ GtkAccessibleRole role)
+{
+ if (GTK_IS_LIST_BOX (accessible))
+ return &listbox_vtable;
+ else if (GTK_IS_LIST_VIEW (accessible) ||
+ GTK_IS_GRID_VIEW (accessible))
+ {
+ g_print ("using listview vtable\n");
+ return &listview_vtable;
+ }
+ else if (GTK_IS_FLOW_BOX (accessible))
+ return &flowbox_vtable;
+ else if (GTK_IS_COMBO_BOX (accessible))
+ return &combobox_vtable;
+ else if (GTK_IS_STACK_SWITCHER (accessible))
+ return &stackswitcher_vtable;
+ else if (IS_NOTEBOOK_TAB_LIST (accessible, role))
+ return &notebook_vtable;
+
+ return NULL;
+}
+
+typedef struct {
+ GtkAtspiSelectionCallback *changed;
+ gpointer data;
+} SelectionChanged;
+
+typedef struct {
+ GtkSelectionModel *model;
+ GtkAtspiSelectionCallback *changed;
+ gpointer data;
+} ListViewData;
+
+static void
+update_model (ListViewData *data,
+ GtkSelectionModel *model)
+{
+ if (data->model)
+ g_signal_handlers_disconnect_by_func (data->model, data->changed, data->data);
+
+ g_set_object (&data->model, model);
+
+ if (data->model)
+ g_signal_connect_swapped (data->model, "selection-changed", G_CALLBACK (data->changed), data->data);
+}
+
+static void
+list_view_data_free (gpointer user_data)
+{
+ ListViewData *data = user_data;
+ update_model (data, NULL);
+ g_free (data);
+}
+
+static void
+model_changed (GtkListBase *list,
+ GParamSpec *pspec,
+ gpointer unused)
+{
+ ListViewData *data;
+
+ data = (ListViewData *)g_object_get_data (G_OBJECT (list), "accessible-selection-data");
+ update_model (data, gtk_list_base_get_model (list));
+}
+
+void
+gtk_atspi_connect_selection_signals (GtkAccessible *accessible,
+ GtkAtspiSelectionCallback selection_changed,
+ gpointer data)
+{
+ if (GTK_IS_LIST_BOX (accessible))
+ {
+ SelectionChanged *changed;
+
+ changed = g_new (SelectionChanged, 1);
+ changed->changed = selection_changed;
+ changed->data = data;
+
+ g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free);
+
+ g_signal_connect_swapped (accessible, "selected-rows-changed", G_CALLBACK (selection_changed), data);
+ }
+ else if (GTK_IS_FLOW_BOX (accessible))
+ {
+ SelectionChanged *changed;
+
+ changed = g_new (SelectionChanged, 1);
+ changed->changed = selection_changed;
+ changed->data = data;
+
+ g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free);
+
+ g_signal_connect_swapped (accessible, "selected-children-changed", G_CALLBACK (selection_changed), data);
+ }
+ else if (GTK_IS_COMBO_BOX (accessible))
+ {
+ SelectionChanged *changed;
+
+ changed = g_new (SelectionChanged, 1);
+ changed->changed = selection_changed;
+ changed->data = data;
+
+ g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free);
+
+ g_signal_connect_swapped (accessible, "changed", G_CALLBACK (selection_changed), data);
+ }
+ else if (GTK_IS_STACK_SWITCHER (accessible))
+ {
+ SelectionChanged *changed;
+
+ changed = g_new (SelectionChanged, 1);
+ changed->changed = selection_changed;
+ changed->data = data;
+
+ g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free);
+
+ g_signal_connect_swapped (accessible, "notify::visible-child", G_CALLBACK (selection_changed), data);
+ }
+ else if (IS_NOTEBOOK_TAB_LIST (accessible, GTK_AT_CONTEXT (data)->accessible_role))
+ {
+ GtkWidget *notebook = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (accessible)));
+ SelectionChanged *changed;
+
+ changed = g_new (SelectionChanged, 1);
+ changed->changed = selection_changed;
+ changed->data = data;
+
+ g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free);
+
+ g_signal_connect_swapped (notebook, "notify::page", G_CALLBACK (selection_changed), data);
+ }
+ else if (GTK_IS_LIST_VIEW (accessible) ||
+ GTK_IS_GRID_VIEW (accessible))
+ {
+ ListViewData *changed;
+
+ changed = g_new0 (ListViewData, 1);
+ changed->changed = selection_changed;
+ changed->data = data;
+
+ g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, list_view_data_free);
+
+ g_signal_connect (accessible, "notify::model", G_CALLBACK (model_changed), NULL);
+ model_changed (GTK_LIST_BASE (accessible), NULL, changed);
+ }
+}
+
+void
+gtk_atspi_disconnect_selection_signals (GtkAccessible *accessible)
+{
+ if (GTK_IS_LIST_BOX (accessible) ||
+ GTK_IS_FLOW_BOX (accessible) ||
+ GTK_IS_COMBO_BOX (accessible) ||
+ GTK_IS_STACK_SWITCHER (accessible))
+ {
+ SelectionChanged *changed;
+
+ changed = g_object_get_data (G_OBJECT (accessible), "accessible-selection-data");
+
+ g_signal_handlers_disconnect_by_func (accessible, changed->changed, changed->data);
+
+ g_object_set_data (G_OBJECT (accessible), "accessible-selection-data", NULL);
+ }
+ else if (IS_NOTEBOOK_TAB_LIST (accessible, gtk_accessible_get_accessible_role (accessible)))
+ {
+ GtkWidget *notebook = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (accessible)));
+ SelectionChanged *changed;
+
+ changed = g_object_get_data (G_OBJECT (accessible), "accessible-selection-data");
+
+ g_signal_handlers_disconnect_by_func (notebook, changed->changed, changed->data);
+
+ g_object_set_data (G_OBJECT (accessible), "accessible-selection-data", NULL);
+ }
+ else if (GTK_IS_LIST_VIEW (accessible) ||
+ GTK_IS_GRID_VIEW (accessible))
+ {
+ g_signal_handlers_disconnect_by_func (accessible, model_changed, NULL);
+
+ g_object_set_data (G_OBJECT (accessible), "accessible-selection-data", NULL);
+ }
+}
diff --git a/gtk/a11y/gtkatspiselectionprivate.h b/gtk/a11y/gtkatspiselectionprivate.h
new file mode 100644
index 0000000000..2a9da6738e
--- /dev/null
+++ b/gtk/a11y/gtkatspiselectionprivate.h
@@ -0,0 +1,39 @@
+/* gtkatspiselectionprivate.h: AT-SPI Selection implementation
+ *
+ * Copyright 2020 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include "gtkaccessible.h"
+
+G_BEGIN_DECLS
+
+const GDBusInterfaceVTable *gtk_atspi_get_selection_vtable (GtkAccessible *accessible,
+ GtkAccessibleRole role);
+
+typedef void (GtkAtspiSelectionCallback) (gpointer data);
+
+void gtk_atspi_connect_selection_signals (GtkAccessible *accessible,
+ GtkAtspiSelectionCallback selection_changed,
+ gpointer data);
+void gtk_atspi_disconnect_selection_signals (GtkAccessible *accessible);
+
+
+G_END_DECLS
diff --git a/gtk/a11y/gtkatspitext.c b/gtk/a11y/gtkatspitext.c
new file mode 100644
index 0000000000..fe7741a39b
--- /dev/null
+++ b/gtk/a11y/gtkatspitext.c
@@ -0,0 +1,1452 @@
+/* gtkatspitext.c: Text interface for GtkAtspiContext
+ *
+ * Copyright 2020 Red Hat, Inc
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkatspitextprivate.h"
+
+#include "gtkatspiprivate.h"
+#include "gtkatspiutilsprivate.h"
+#include "gtkatspipangoprivate.h"
+#include "gtkatspitextbufferprivate.h"
+
+#include "a11y/atspi/atspi-text.h"
+
+#include "gtkatcontextprivate.h"
+#include "gtkdebug.h"
+#include "gtkeditable.h"
+#include "gtklabelprivate.h"
+#include "gtkentryprivate.h"
+#include "gtksearchentryprivate.h"
+#include "gtkpasswordentryprivate.h"
+#include "gtkspinbuttonprivate.h"
+#include "gtktextview.h"
+
+#include <gio/gio.h>
+
+static void
+label_handle_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (method_name, "GetCaretOffset") == 0)
+ {
+ int offset;
+
+ offset = _gtk_label_get_cursor_position (GTK_LABEL (widget));
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", offset));
+ }
+ else if (g_strcmp0 (method_name, "SetCaretOffset") == 0)
+ {
+ int offset;
+ gboolean ret;
+
+ g_variant_get (parameters, "(i)", &offset);
+
+ ret = gtk_label_get_selectable (GTK_LABEL (widget));
+ if (ret)
+ gtk_label_select_region (GTK_LABEL (widget), offset, offset);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "GetText") == 0)
+ {
+ int start, end;
+ const char *text;
+ int len;
+ char *string;
+
+ g_variant_get (parameters, "(ii)", &start, &end);
+
+ text = gtk_label_get_text (GTK_LABEL (widget));
+ len = g_utf8_strlen (text, -1);
+
+ start = CLAMP (start, 0, len);
+ end = CLAMP (end, 0, len);
+
+ if (end <= start)
+ string = g_strdup ("");
+ else
+ {
+ const char *p, *q;
+ p = g_utf8_offset_to_pointer (text, start);
+ q = g_utf8_offset_to_pointer (text, end);
+ string = g_strndup (p, q - p);
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", string));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetTextBeforeOffset") == 0)
+ {
+ PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget));
+ int offset;
+ AtspiTextBoundaryType boundary_type;
+ char *string;
+ int start, end;
+
+ g_variant_get (parameters, "(iu)", &offset, &boundary_type);
+
+ string = gtk_pango_get_text_before (layout, offset, boundary_type, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetTextAtOffset") == 0)
+ {
+ PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget));
+ int offset;
+ AtspiTextBoundaryType boundary_type;
+ char *string;
+ int start, end;
+
+ g_variant_get (parameters, "(iu)", &offset, &boundary_type);
+
+ string = gtk_pango_get_text_at (layout, offset, boundary_type, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetTextAfterOffset") == 0)
+ {
+ PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget));
+ int offset;
+ AtspiTextBoundaryType boundary_type;
+ char *string;
+ int start, end;
+
+ g_variant_get (parameters, "(iu)", &offset, &boundary_type);
+
+ string = gtk_pango_get_text_after (layout, offset, boundary_type, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetCharacterAtOffset") == 0)
+ {
+ int offset;
+ const char *text;
+ gunichar ch = 0;
+
+ g_variant_get (parameters, "(i)", &offset);
+
+ text = gtk_label_get_text (GTK_LABEL (widget));
+
+ if (0 <= offset && offset < g_utf8_strlen (text, -1))
+ ch = g_utf8_get_char (g_utf8_offset_to_pointer (text, offset));
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", ch));
+ }
+ else if (g_strcmp0 (method_name, "GetStringAtOffset") == 0)
+ {
+ PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget));
+ int offset;
+ AtspiTextGranularity granularity;
+ char *string;
+ int start, end;
+
+ g_variant_get (parameters, "(iu)", &offset, &granularity);
+
+ string = gtk_pango_get_string_at (layout, offset, granularity, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetAttributes") == 0)
+ {
+ PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget));
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
+ int offset;
+ int start, end;
+
+ g_variant_get (parameters, "(i)", &offset);
+
+ gtk_pango_get_run_attributes (layout, &builder, offset, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss}ii)", &builder, start, end));
+ }
+ else if (g_strcmp0 (method_name, "GetAttributeValue") == 0)
+ {
+ PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget));
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
+ int offset;
+ const char *name;
+ int start, end;
+ GVariant *attrs;
+ const char *val;
+
+ g_variant_get (parameters, "(i&s)", &offset, &name);
+
+ gtk_pango_get_run_attributes (layout, &builder, offset, &start, &end);
+
+ attrs = g_variant_builder_end (&builder);
+ if (!g_variant_lookup (attrs, name, "&s", &val))
+ val = "";
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", val));
+ g_variant_unref (attrs);
+ }
+ else if (g_strcmp0 (method_name, "GetAttributeRun") == 0)
+ {
+ PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget));
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
+ int offset;
+ gboolean include_defaults;
+ int start, end;
+
+ g_variant_get (parameters, "(ib)", &offset, &include_defaults);
+
+ if (include_defaults)
+ gtk_pango_get_default_attributes (layout, &builder);
+
+ gtk_pango_get_run_attributes (layout, &builder, offset, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss}ii)", &builder, start, end));
+ }
+ else if (g_strcmp0 (method_name, "GetDefaultAttributes") == 0 ||
+ g_strcmp0 (method_name, "GetDefaultAttributeSet") == 0)
+ {
+ PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget));
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
+
+ gtk_pango_get_default_attributes (layout, &builder);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss})", &builder));
+ }
+ else if (g_strcmp0 (method_name, "GetNSelections") == 0)
+ {
+ int n = 0;
+
+ if (gtk_label_get_selection_bounds (GTK_LABEL (widget), NULL, NULL))
+ n = 1;
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", n));
+ }
+ else if (g_strcmp0 (method_name, "GetSelection") == 0)
+ {
+ int num;
+ int start, end;
+ gboolean ret = TRUE;
+
+ g_variant_get (parameters, "(i)", &num);
+
+ if (num != 0)
+ ret = FALSE;
+ else
+ {
+ if (!gtk_label_get_selection_bounds (GTK_LABEL (widget), &start, &end))
+ ret = FALSE;
+ }
+
+ if (!ret)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Not a valid selection: %d", num);
+ else
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(ii)", start, end));
+ }
+ else if (g_strcmp0 (method_name, "AddSelection") == 0)
+ {
+ int start, end;
+ gboolean ret;
+
+ g_variant_get (parameters, "(ii)", &start, &end);
+
+ if (!gtk_label_get_selectable (GTK_LABEL (widget)) ||
+ gtk_label_get_selection_bounds (GTK_LABEL (widget), NULL, NULL))
+ {
+ ret = FALSE;
+ }
+ else
+ {
+ gtk_label_select_region (GTK_LABEL (widget), start, end);
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "RemoveSelection") == 0)
+ {
+ int num;
+ int start, end;
+ gboolean ret;
+
+ g_variant_get (parameters, "(i)", &num);
+
+ if (num != 0)
+ ret = FALSE;
+ else
+ {
+ if (!gtk_label_get_selectable (GTK_LABEL (widget)) ||
+ !gtk_label_get_selection_bounds (GTK_LABEL (widget), &start, &end))
+ {
+ ret = FALSE;
+ }
+ else
+ {
+ gtk_label_select_region (GTK_LABEL (widget), end, end);
+ ret = TRUE;
+ }
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "SetSelection") == 0)
+ {
+ int num;
+ int start, end;
+ gboolean ret;
+
+ g_variant_get (parameters, "(iii)", &num, &start, &end);
+
+ if (num != 0)
+ ret = FALSE;
+ else
+ {
+ if (!gtk_label_get_selectable (GTK_LABEL (widget)) ||
+ !gtk_label_get_selection_bounds (GTK_LABEL (widget), NULL, NULL))
+ {
+ ret = FALSE;
+ }
+ else
+ {
+ gtk_label_select_region (GTK_LABEL (widget), start, end);
+ ret = TRUE;
+ }
+ }
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "GetCharacterExtents") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "GetRangeExtents") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "GetBoundedRanges") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "ScrollSubstringTo") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "ScrollSubstringToPoint") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+}
+
+static GVariant *
+label_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (property_name, "CharacterCount") == 0)
+ {
+ const char *text;
+ int len;
+
+ text = gtk_label_get_text (GTK_LABEL (widget));
+ len = g_utf8_strlen (text, -1);
+
+ return g_variant_new_int32 (len);
+ }
+ else if (g_strcmp0 (property_name, "CaretOffset") == 0)
+ {
+ int offset;
+
+ offset = _gtk_label_get_cursor_position (GTK_LABEL (widget));
+
+ return g_variant_new_int32 (offset);
+ }
+
+ return NULL;
+}
+
+static const GDBusInterfaceVTable label_vtable = {
+ label_handle_method,
+ label_get_property,
+ NULL,
+};
+
+static GtkText *
+gtk_editable_get_text_widget (GtkWidget *widget)
+{
+ if (GTK_IS_ENTRY (widget))
+ return gtk_entry_get_text_widget (GTK_ENTRY (widget));
+ else if (GTK_IS_SEARCH_ENTRY (widget))
+ return gtk_search_entry_get_text_widget (GTK_SEARCH_ENTRY (widget));
+ else if (GTK_IS_PASSWORD_ENTRY (widget))
+ return gtk_password_entry_get_text_widget (GTK_PASSWORD_ENTRY (widget));
+ else if (GTK_IS_SPIN_BUTTON (widget))
+ return gtk_spin_button_get_text_widget (GTK_SPIN_BUTTON (widget));
+
+ return NULL;
+}
+
+static void
+entry_handle_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+ GtkText *text_widget = gtk_editable_get_text_widget (widget);
+
+ if (g_strcmp0 (method_name, "GetCaretOffset") == 0)
+ {
+ int offset;
+
+ offset = gtk_editable_get_position (GTK_EDITABLE (widget));
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", offset));
+ }
+ else if (g_strcmp0 (method_name, "SetCaretOffset") == 0)
+ {
+ int offset;
+ gboolean ret;
+
+ g_variant_get (parameters, "(i)", &offset);
+
+ gtk_editable_set_position (GTK_EDITABLE (widget), offset);
+ ret = TRUE;
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "GetText") == 0)
+ {
+ int start, end;
+ const char *text;
+ int len;
+ char *string;
+
+ g_variant_get (parameters, "(ii)", &start, &end);
+
+ text = gtk_editable_get_text (GTK_EDITABLE (widget));
+ len = g_utf8_strlen (text, -1);
+
+ start = CLAMP (start, 0, len);
+ end = CLAMP (end, 0, len);
+
+ if (end <= start)
+ string = g_strdup ("");
+ else
+ {
+ const char *p, *q;
+ p = g_utf8_offset_to_pointer (text, start);
+ q = g_utf8_offset_to_pointer (text, end);
+ string = g_strndup (p, q - p);
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", string));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetTextBeforeOffset") == 0)
+ {
+ PangoLayout *layout = gtk_text_get_layout (text_widget);
+ int offset;
+ AtspiTextBoundaryType boundary_type;
+ char *string;
+ int start, end;
+
+ g_variant_get (parameters, "(iu)", &offset, &boundary_type);
+
+ string = gtk_pango_get_text_before (layout, offset, boundary_type, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetTextAtOffset") == 0)
+ {
+ PangoLayout *layout = gtk_text_get_layout (text_widget);
+ int offset;
+ AtspiTextBoundaryType boundary_type;
+ char *string;
+ int start, end;
+
+ g_variant_get (parameters, "(iu)", &offset, &boundary_type);
+
+ string = gtk_pango_get_text_at (layout, offset, boundary_type, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetTextAfterOffset") == 0)
+ {
+ PangoLayout *layout = gtk_text_get_layout (text_widget);
+ int offset;
+ AtspiTextBoundaryType boundary_type;
+ char *string;
+ int start, end;
+
+ g_variant_get (parameters, "(iu)", &offset, &boundary_type);
+
+ string = gtk_pango_get_text_after (layout, offset, boundary_type, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetCharacterAtOffset") == 0)
+ {
+ int offset;
+ const char *text;
+ gunichar ch = 0;
+
+ g_variant_get (parameters, "(i)", &offset);
+
+ text = gtk_editable_get_text (GTK_EDITABLE (widget));
+ if (0 <= offset && offset < g_utf8_strlen (text, -1))
+ ch = g_utf8_get_char (g_utf8_offset_to_pointer (text, offset));
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", ch));
+ }
+ else if (g_strcmp0 (method_name, "GetStringAtOffset") == 0)
+ {
+ PangoLayout *layout = gtk_text_get_layout (text_widget);
+ int offset;
+ AtspiTextGranularity granularity;
+ char *string;
+ int start, end;
+
+ g_variant_get (parameters, "(iu)", &offset, &granularity);
+
+ string = gtk_pango_get_string_at (layout, offset, granularity, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetAttributes") == 0)
+ {
+ PangoLayout *layout = gtk_text_get_layout (text_widget);
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
+ int offset;
+ int start, end;
+
+ g_variant_get (parameters, "(i)", &offset);
+
+ gtk_pango_get_run_attributes (layout, &builder, offset, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss}ii)", &builder, start, end));
+ }
+ else if (g_strcmp0 (method_name, "GetAttributeValue") == 0)
+ {
+ PangoLayout *layout = gtk_text_get_layout (text_widget);
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
+ int offset;
+ const char *name;
+ int start, end;
+ GVariant *attrs;
+ const char *val;
+
+ g_variant_get (parameters, "(i&s)", &offset, &name);
+
+ gtk_pango_get_run_attributes (layout, &builder, offset, &start, &end);
+ attrs = g_variant_builder_end (&builder);
+ if (!g_variant_lookup (attrs, name, "&s", &val))
+ val = "";
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", val));
+ g_variant_unref (attrs);
+ }
+ else if (g_strcmp0 (method_name, "GetAttributeRun") == 0)
+ {
+ PangoLayout *layout = gtk_text_get_layout (text_widget);
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
+ int offset;
+ gboolean include_defaults;
+ int start, end;
+
+ g_variant_get (parameters, "(ib)", &offset, &include_defaults);
+
+ if (include_defaults)
+ gtk_pango_get_default_attributes (layout, &builder);
+
+ gtk_pango_get_run_attributes (layout, &builder, offset, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss}ii)", &builder, start, end));
+ }
+ else if (g_strcmp0 (method_name, "GetDefaultAttributes") == 0 ||
+ g_strcmp0 (method_name, "GetDefaultAttributeSet") == 0)
+ {
+ PangoLayout *layout = gtk_text_get_layout (text_widget);
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
+
+ gtk_pango_get_default_attributes (layout, &builder);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss})", &builder));
+ }
+ else if (g_strcmp0 (method_name, "GetNSelections") == 0)
+ {
+ int n = 0;
+
+ if (gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), NULL, NULL))
+ n = 1;
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", n));
+ }
+ else if (g_strcmp0 (method_name, "GetSelection") == 0)
+ {
+ int num;
+ int start, end;
+ gboolean ret = TRUE;
+
+ g_variant_get (parameters, "(i)", &num);
+
+ if (num != 0)
+ ret = FALSE;
+ else
+ {
+ if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), &start, &end))
+ ret = FALSE;
+ }
+
+ if (!ret)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Not a valid selection: %d", num);
+ else
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(ii)", start, end));
+ }
+ else if (g_strcmp0 (method_name, "AddSelection") == 0)
+ {
+ int start, end;
+ gboolean ret;
+
+ g_variant_get (parameters, "(ii)", &start, &end);
+
+ if (gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), NULL, NULL))
+ {
+ ret = FALSE;
+ }
+ else
+ {
+ gtk_editable_select_region (GTK_EDITABLE (widget), start, end);
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "RemoveSelection") == 0)
+ {
+ int num;
+ int start, end;
+ gboolean ret;
+
+ g_variant_get (parameters, "(i)", &num);
+
+ if (num != 0)
+ ret = FALSE;
+ else
+ {
+ if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), &start, &end))
+ {
+ ret = FALSE;
+ }
+ else
+ {
+ gtk_editable_select_region (GTK_EDITABLE (widget), end, end);
+ ret = TRUE;
+ }
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "SetSelection") == 0)
+ {
+ int num;
+ int start, end;
+ gboolean ret;
+
+ g_variant_get (parameters, "(iii)", &num, &start, &end);
+
+ if (num != 0)
+ ret = FALSE;
+ else
+ {
+ if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), NULL, NULL))
+ {
+ ret = FALSE;
+ }
+ else
+ {
+ gtk_editable_select_region (GTK_EDITABLE (widget), start, end);
+ ret = TRUE;
+ }
+ }
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "GetCharacterExtents") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "GetRangeExtents") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "GetBoundedRanges") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "ScrollSubstringTo") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "ScrollSubstringToPoint") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+}
+
+static GVariant *
+entry_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (property_name, "CharacterCount") == 0)
+ {
+ const char *text;
+ int len;
+
+ text = gtk_editable_get_text (GTK_EDITABLE (widget));
+ len = g_utf8_strlen (text, -1);
+
+ return g_variant_new_int32 (len);
+ }
+ else if (g_strcmp0 (property_name, "CaretOffset") == 0)
+ {
+ int offset;
+
+ offset = gtk_editable_get_position (GTK_EDITABLE (widget));
+
+ return g_variant_new_int32 (offset);
+ }
+
+ return NULL;
+}
+
+static const GDBusInterfaceVTable entry_vtable = {
+ entry_handle_method,
+ entry_get_property,
+ NULL,
+};
+
+
+static void
+text_view_handle_method (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (method_name, "GetCaretOffset") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GtkTextMark *insert;
+ GtkTextIter iter;
+ int offset;
+
+ insert = gtk_text_buffer_get_insert (buffer);
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+ offset = gtk_text_iter_get_offset (&iter);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", offset));
+ }
+ else if (g_strcmp0 (method_name, "SetCaretOffset") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GtkTextIter iter;
+ int offset;
+
+ g_variant_get (parameters, "(i)", &offset);
+
+ gtk_text_buffer_get_iter_at_offset (buffer, &iter, offset);
+ gtk_text_buffer_place_cursor (buffer, &iter);
+ gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (widget), &iter, 0, FALSE, 0, 0);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
+ }
+ else if (g_strcmp0 (method_name, "GetText") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GtkTextIter start_iter, end_iter;
+ int start, end;
+ char *string;
+
+ g_variant_get (parameters, "(ii)", &start, &end);
+
+ gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+ gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+
+ string = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", string));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetTextBeforeOffset") == 0)
+ {
+ int offset;
+ AtspiTextBoundaryType boundary_type;
+ char *string;
+ int start, end;
+
+ g_variant_get (parameters, "(iu)", &offset, &boundary_type);
+
+ string = gtk_text_view_get_text_before (GTK_TEXT_VIEW (widget), offset, boundary_type, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetTextAtOffset") == 0)
+ {
+ int offset;
+ AtspiTextBoundaryType boundary_type;
+ char *string;
+ int start, end;
+
+ g_variant_get (parameters, "(iu)", &offset, &boundary_type);
+
+ string = gtk_text_view_get_text_at (GTK_TEXT_VIEW (widget), offset, boundary_type, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetTextAfterOffset") == 0)
+ {
+ int offset;
+ AtspiTextBoundaryType boundary_type;
+ char *string;
+ int start, end;
+
+ g_variant_get (parameters, "(iu)", &offset, &boundary_type);
+
+ string = gtk_text_view_get_text_after (GTK_TEXT_VIEW (widget), offset, boundary_type, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetCharacterAtOffset") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ int offset;
+ gunichar ch = 0;
+
+ g_variant_get (parameters, "(i)", &offset);
+
+ if (offset >= 0 && offset < gtk_text_buffer_get_char_count (buffer))
+ {
+ GtkTextIter start, end;
+ char *string;
+ gtk_text_buffer_get_iter_at_offset (buffer, &start, offset);
+ end = start;
+ gtk_text_iter_forward_char (&end);
+ string = gtk_text_buffer_get_slice (buffer, &start, &end, FALSE);
+ ch = g_utf8_get_char (string);
+ g_free (string);
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", ch));
+ }
+ else if (g_strcmp0 (method_name, "GetStringAtOffset") == 0)
+ {
+ int offset;
+ AtspiTextGranularity granularity;
+ char *string;
+ int start, end;
+
+ g_variant_get (parameters, "(iu)", &offset, &granularity);
+
+ string = gtk_text_view_get_string_at (GTK_TEXT_VIEW (widget), offset, granularity, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(sii)", string, start, end));
+ g_free (string);
+ }
+ else if (g_strcmp0 (method_name, "GetAttributes") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
+ int offset;
+ int start, end;
+
+ g_variant_get (parameters, "(i)", &offset);
+
+ gtk_text_buffer_get_run_attributes (buffer, &builder, offset, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss}ii)", &builder, start, end));
+ }
+ else if (g_strcmp0 (method_name, "GetAttributeValue") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
+ int offset;
+ const char *name;
+ int start, end;
+ GVariant *attrs;
+ const char *val;
+
+ g_variant_get (parameters, "(i&s)", &offset, &name);
+
+ gtk_text_buffer_get_run_attributes (buffer, &builder, offset, &start, &end);
+
+ attrs = g_variant_builder_end (&builder);
+ if (!g_variant_lookup (attrs, name, "&s", &val))
+ val = "";
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", val));
+ g_variant_unref (attrs);
+ }
+ else if (g_strcmp0 (method_name, "GetAttributeRun") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
+ int offset;
+ gboolean include_defaults;
+ int start, end;
+
+ g_variant_get (parameters, "(ib)", &offset, &include_defaults);
+
+ if (include_defaults)
+ gtk_text_view_add_default_attributes (GTK_TEXT_VIEW (widget), &builder);
+
+ gtk_text_buffer_get_run_attributes (buffer, &builder, offset, &start, &end);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss}ii)", &builder, start, end));
+ }
+ else if (g_strcmp0 (method_name, "GetDefaultAttributes") == 0 ||
+ g_strcmp0 (method_name, "GetDefaultAttributeSet") == 0)
+ {
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
+
+ gtk_text_view_add_default_attributes (GTK_TEXT_VIEW (widget), &builder);
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(a{ss})", &builder));
+ }
+ else if (g_strcmp0 (method_name, "GetNSelections") == 0)
+ {
+ int n = 0;
+
+ if (gtk_text_buffer_get_selection_bounds (gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)), NULL, NULL))
+ n = 1;
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", n));
+ }
+ else if (g_strcmp0 (method_name, "GetSelection") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GtkTextIter start_iter, end_iter;
+ int num;
+ int start, end;
+ gboolean ret = TRUE;
+
+ g_variant_get (parameters, "(i)", &num);
+
+ if (num != 0)
+ ret = FALSE;
+ else
+ {
+ if (!gtk_text_buffer_get_selection_bounds (buffer, &start_iter, &end_iter))
+ ret = FALSE;
+ else
+ {
+ start = gtk_text_iter_get_offset (&start_iter);
+ end = gtk_text_iter_get_offset (&end_iter);
+ }
+ }
+
+ if (!ret)
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Not a valid selection: %d", num);
+ else
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(ii)", start, end));
+ }
+ else if (g_strcmp0 (method_name, "AddSelection") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GtkTextIter start_iter, end_iter;
+ int start, end;
+ gboolean ret;
+
+ g_variant_get (parameters, "(ii)", &start, &end);
+
+ if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL))
+ {
+ ret = FALSE;
+ }
+ else
+ {
+ gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+ gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+ gtk_text_buffer_select_range (buffer, &start_iter, &end_iter);
+ ret = TRUE;
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(i)", ret));
+ }
+ else if (g_strcmp0 (method_name, "RemoveSelection") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GtkTextIter start_iter, end_iter;
+ int num;
+ gboolean ret;
+
+ g_variant_get (parameters, "(i)", &num);
+
+ if (num != 0)
+ ret = FALSE;
+ else
+ {
+ if (!gtk_text_buffer_get_selection_bounds (buffer, &start_iter, &end_iter))
+ {
+ ret = FALSE;
+ }
+ else
+ {
+ gtk_text_buffer_select_range (buffer, &end_iter, &end_iter);
+ ret = TRUE;
+ }
+ }
+
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "SetSelection") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GtkTextIter start_iter, end_iter;
+ int num;
+ int start, end;
+ gboolean ret;
+
+ g_variant_get (parameters, "(iii)", &num, &start, &end);
+
+ if (num != 0)
+ ret = FALSE;
+ else
+ {
+ if (!gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL))
+ {
+ ret = FALSE;
+ }
+ else
+ {
+ gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+ gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+ gtk_text_buffer_select_range (buffer, &end_iter, &end_iter);
+ ret = TRUE;
+ }
+ }
+ g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
+ }
+ else if (g_strcmp0 (method_name, "GetCharacterExtents") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "GetRangeExtents") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "GetBoundedRanges") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "ScrollSubstringTo") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+ else if (g_strcmp0 (method_name, "ScrollSubstringToPoint") == 0)
+ {
+ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ }
+}
+
+static GVariant *
+text_view_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkAccessible *accessible = gtk_at_context_get_accessible (self);
+ GtkWidget *widget = GTK_WIDGET (accessible);
+
+ if (g_strcmp0 (property_name, "CharacterCount") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ int len;
+
+ len = gtk_text_buffer_get_char_count (buffer);
+
+ return g_variant_new_int32 (len);
+ }
+ else if (g_strcmp0 (property_name, "CaretOffset") == 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ GtkTextMark *insert;
+ GtkTextIter iter;
+ int offset;
+
+ insert = gtk_text_buffer_get_insert (buffer);
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+ offset = gtk_text_iter_get_offset (&iter);
+
+ return g_variant_new_int32 (offset);
+ }
+
+ return NULL;
+}
+
+static const GDBusInterfaceVTable text_view_vtable = {
+ text_view_handle_method,
+ text_view_get_property,
+ NULL,
+};
+
+const GDBusInterfaceVTable *
+gtk_atspi_get_text_vtable (GtkAccessible *accessible)
+{
+ if (GTK_IS_LABEL (accessible))
+ return &label_vtable;
+ else if (GTK_IS_ENTRY (accessible) ||
+ GTK_IS_SEARCH_ENTRY (accessible) ||
+ GTK_IS_PASSWORD_ENTRY (accessible) ||
+ GTK_IS_SPIN_BUTTON (accessible))
+ return &entry_vtable;
+ else if (GTK_IS_TEXT_VIEW (accessible))
+ return &text_view_vtable;
+
+ return NULL;
+}
+
+typedef struct {
+ void (* text_changed) (gpointer data,
+ const char *kind,
+ int start,
+ int end,
+ const char *text);
+ void (* selection_changed) (gpointer data,
+ const char *kind,
+ int cursor_position);
+
+ gpointer data;
+ GtkTextBuffer *buffer;
+ int cursor_position;
+ int selection_bound;
+} TextChanged;
+
+static void
+insert_text_cb (GtkEditable *editable,
+ char *new_text,
+ int new_text_length,
+ int *position,
+ TextChanged *changed)
+{
+ int length;
+
+ if (new_text_length == 0)
+ return;
+
+ length = g_utf8_strlen (new_text, new_text_length);
+ changed->text_changed (changed->data, "insert", *position - length, length, new_text);
+}
+
+static void
+delete_text_cb (GtkEditable *editable,
+ int start,
+ int end,
+ TextChanged *changed)
+{
+ char *text;
+
+ if (start == end)
+ return;
+
+ text = gtk_editable_get_chars (editable, start, end);
+ changed->text_changed (changed->data, "delete", start, end - start, text);
+ g_free (text);
+}
+
+static void
+update_selection (TextChanged *changed,
+ int cursor_position,
+ int selection_bound)
+{
+ gboolean caret_moved, bound_moved;
+
+ caret_moved = cursor_position != changed->cursor_position;
+ bound_moved = selection_bound != changed->selection_bound;
+
+ if (!caret_moved && !bound_moved)
+ return;
+
+ changed->cursor_position = cursor_position;
+ changed->selection_bound = selection_bound;
+
+ if (caret_moved)
+ changed->selection_changed (changed->data, "text-caret-moved", changed->cursor_position);
+
+ if (caret_moved || bound_moved)
+ changed->selection_changed (changed->data, "text-selection-changed", 0);
+}
+
+static void
+notify_cb (GObject *object,
+ GParamSpec *pspec,
+ TextChanged *changed)
+{
+ if (g_strcmp0 (pspec->name, "cursor-position") == 0 ||
+ g_strcmp0 (pspec->name, "selection-bound") == 0)
+ {
+ int cursor_position, selection_bound;
+
+ gtk_editable_get_selection_bounds (GTK_EDITABLE (object), &cursor_position, &selection_bound);
+ update_selection (changed, cursor_position, selection_bound);
+ }
+}
+
+static void
+update_cursor (GtkTextBuffer *buffer,
+ TextChanged *changed)
+{
+ GtkTextIter iter;
+ int cursor_position, selection_bound;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer));
+ cursor_position = gtk_text_iter_get_offset (&iter);
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_selection_bound (buffer));
+
+ selection_bound = gtk_text_iter_get_offset (&iter);
+
+ update_selection (changed, cursor_position, selection_bound);
+}
+
+static void
+insert_range_cb (GtkTextBuffer *buffer,
+ GtkTextIter *iter,
+ char *text,
+ int len,
+ TextChanged *changed)
+{
+ int position;
+ int length;
+
+ position = gtk_text_iter_get_offset (iter);
+ length = g_utf8_strlen (text, len);
+
+ changed->text_changed (changed->data, "insert", position - length, length, text);
+
+ update_cursor (buffer, changed);
+}
+
+static void
+delete_range_cb (GtkTextBuffer *buffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ TextChanged *changed)
+{
+ int offset, length;
+ char *text;
+
+ text = gtk_text_buffer_get_slice (buffer, start, end, FALSE);
+
+ offset = gtk_text_iter_get_offset (start);
+ length = gtk_text_iter_get_offset (end) - offset;
+
+ changed->text_changed (changed->data, "delete", offset, length, text);
+
+ g_free (text);
+}
+
+static void
+delete_range_after_cb (GtkTextBuffer *buffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ TextChanged *changed)
+{
+ update_cursor (buffer, changed);
+}
+
+static void
+mark_set_cb (GtkTextBuffer *buffer,
+ GtkTextIter *location,
+ GtkTextMark *mark,
+ TextChanged *changed)
+{
+ if (mark == gtk_text_buffer_get_insert (buffer) ||
+ mark == gtk_text_buffer_get_selection_bound (buffer))
+ update_cursor (buffer, changed);
+}
+
+static void
+buffer_changed (GtkWidget *widget,
+ GParamSpec *pspec,
+ TextChanged *changed)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+ char *text;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+
+ if (changed->buffer)
+ {
+ g_signal_handlers_disconnect_by_func (changed->buffer, insert_range_cb, changed);
+ g_signal_handlers_disconnect_by_func (changed->buffer, delete_range_cb, changed);
+ g_signal_handlers_disconnect_by_func (changed->buffer, delete_range_after_cb, changed);
+ g_signal_handlers_disconnect_by_func (changed->buffer, mark_set_cb, changed);
+
+ gtk_text_buffer_get_bounds (changed->buffer, &start, &end);
+ text = gtk_text_buffer_get_slice (changed->buffer, &start, &end, FALSE);
+ changed->text_changed (changed->data, "delete", 0, gtk_text_buffer_get_char_count (changed->buffer), text);
+ g_free (text);
+
+ update_selection (changed, 0, 0);
+
+ g_clear_object (&changed->buffer);
+ }
+
+ changed->buffer = buffer;
+
+ if (changed->buffer)
+ {
+ g_object_ref (changed->buffer);
+ g_signal_connect (changed->buffer, "insert-text", G_CALLBACK (insert_range_cb), changed);
+ g_signal_connect (changed->buffer, "delete-range", G_CALLBACK (delete_range_cb), changed);
+ g_signal_connect_after (changed->buffer, "delete-range", G_CALLBACK (delete_range_after_cb), changed);
+ g_signal_connect_after (changed->buffer, "mark-set", G_CALLBACK (mark_set_cb), changed);
+
+ gtk_text_buffer_get_bounds (changed->buffer, &start, &end);
+ text = gtk_text_buffer_get_slice (changed->buffer, &start, &end, FALSE);
+ changed->text_changed (changed->data, "insert", 0, gtk_text_buffer_get_char_count (changed->buffer), text);
+ g_free (text);
+
+ update_cursor (changed->buffer, changed);
+ }
+}
+
+void
+gtk_atspi_connect_text_signals (GtkAccessible *accessible,
+ GtkAtspiTextChangedCallback text_changed,
+ GtkAtspiTextSelectionCallback selection_changed,
+ gpointer data)
+{
+ TextChanged *changed;
+
+ if (!GTK_IS_EDITABLE (accessible) &&
+ !GTK_IS_TEXT_VIEW (accessible))
+ return;
+
+ changed = g_new0 (TextChanged, 1);
+ changed->text_changed = text_changed;
+ changed->selection_changed = selection_changed;
+ changed->data = data;
+
+ g_object_set_data_full (G_OBJECT (accessible), "accessible-text-data", changed, g_free);
+
+ if (GTK_IS_EDITABLE (accessible))
+ {
+ GtkText *text = gtk_editable_get_text_widget (GTK_WIDGET (accessible));
+
+ if (text)
+ {
+ g_signal_connect_after (text, "insert-text", G_CALLBACK (insert_text_cb), changed);
+ g_signal_connect (text, "delete-text", G_CALLBACK (delete_text_cb), changed);
+ g_signal_connect (text, "notify", G_CALLBACK (notify_cb), changed);
+
+ gtk_editable_get_selection_bounds (GTK_EDITABLE (text), &changed->cursor_position, &changed->selection_bound);
+ }
+ }
+ else if (GTK_IS_TEXT_VIEW (accessible))
+ {
+ g_signal_connect (accessible, "notify::buffer", G_CALLBACK (buffer_changed), changed);
+ buffer_changed (GTK_WIDGET (accessible), NULL, changed);
+ }
+}
+
+void
+gtk_atspi_disconnect_text_signals (GtkAccessible *accessible)
+{
+ TextChanged *changed;
+
+ changed = g_object_get_data (G_OBJECT (accessible), "accessible-text-data");
+
+ if (GTK_IS_EDITABLE (accessible))
+ {
+ GtkText *text = gtk_editable_get_text_widget (GTK_WIDGET (accessible));
+
+ if (text)
+ {
+ g_signal_handlers_disconnect_by_func (text, insert_text_cb, changed);
+ g_signal_handlers_disconnect_by_func (text, delete_text_cb, changed);
+ g_signal_handlers_disconnect_by_func (text, notify_cb, changed);
+ }
+ }
+ else if (GTK_IS_TEXT_VIEW (accessible))
+ {
+ g_signal_handlers_disconnect_by_func (accessible, buffer_changed, changed);
+ if (changed->buffer)
+ {
+ g_signal_handlers_disconnect_by_func (changed->buffer, insert_range_cb, changed);
+ g_signal_handlers_disconnect_by_func (changed->buffer, delete_range_cb, changed);
+ g_signal_handlers_disconnect_by_func (changed->buffer, delete_range_after_cb, changed);
+ g_signal_handlers_disconnect_by_func (changed->buffer, mark_set_cb, changed);
+ }
+ g_clear_object (&changed->buffer);
+ }
+
+ g_object_set_data (G_OBJECT (accessible), "accessible-text-data", NULL);
+}
diff --git a/gtk/a11y/gtkatspitextbuffer.c b/gtk/a11y/gtkatspitextbuffer.c
new file mode 100644
index 0000000000..73380997bc
--- /dev/null
+++ b/gtk/a11y/gtkatspitextbuffer.c
@@ -0,0 +1,984 @@
+/* gtkatspitextbuffer.c - GtkTextBuffer-related utilities for AT-SPI
+ *
+ * Copyright (c) 2020 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.Free
+ */
+
+#include "config.h"
+#include "gtkatspitextbufferprivate.h"
+#include "gtkatspipangoprivate.h"
+#include "gtktextviewprivate.h"
+
+static const char *
+gtk_justification_to_string (GtkJustification just)
+{
+ switch (just)
+ {
+ case GTK_JUSTIFY_LEFT:
+ return "left";
+ case GTK_JUSTIFY_RIGHT:
+ return "right";
+ case GTK_JUSTIFY_CENTER:
+ return "center";
+ case GTK_JUSTIFY_FILL:
+ return "fill";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static const char *
+gtk_text_direction_to_string (GtkTextDirection direction)
+{
+ switch (direction)
+ {
+ case GTK_TEXT_DIR_NONE:
+ return "none";
+ case GTK_TEXT_DIR_LTR:
+ return "ltr";
+ case GTK_TEXT_DIR_RTL:
+ return "rtl";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+void
+gtk_text_view_add_default_attributes (GtkTextView *view,
+ GVariantBuilder *builder)
+{
+ GtkTextAttributes *text_attrs;
+ PangoFontDescription *font;
+ char *value;
+
+ text_attrs = gtk_text_view_get_default_attributes (view);
+
+ font = text_attrs->font;
+
+ if (font)
+ gtk_pango_get_font_attributes (font, builder);
+
+ g_variant_builder_add (builder, "{ss}", "justification",
+ gtk_justification_to_string (text_attrs->justification));
+ g_variant_builder_add (builder, "{ss}", "direction",
+ gtk_text_direction_to_string (text_attrs->direction));
+ g_variant_builder_add (builder, "{ss}", "wrap-mode",
+ pango_wrap_mode_to_string (text_attrs->wrap_mode));
+ g_variant_builder_add (builder, "{ss}", "editable",
+ text_attrs->editable ? "true" : "false");
+ g_variant_builder_add (builder, "{ss}", "invisible",
+ text_attrs->invisible ? "true" : "false");
+ g_variant_builder_add (builder, "{ss}", "bg-full-height",
+ text_attrs->bg_full_height ? "true" : "false");
+ g_variant_builder_add (builder, "{ss}", "strikethrough",
+ text_attrs->appearance.strikethrough ? "true" : "false");
+ g_variant_builder_add (builder, "{ss}", "underline",
+ pango_underline_to_string (text_attrs->appearance.underline));
+
+ value = g_strdup_printf ("%u,%u,%u",
+ (guint)(text_attrs->appearance.bg_rgba->red * 65535),
+ (guint)(text_attrs->appearance.bg_rgba->green * 65535),
+ (guint)(text_attrs->appearance.bg_rgba->blue * 65535));
+ g_variant_builder_add (builder, "{ss}", "bg-color", value);
+ g_free (value);
+
+ value = g_strdup_printf ("%u,%u,%u",
+ (guint)(text_attrs->appearance.fg_rgba->red * 65535),
+ (guint)(text_attrs->appearance.fg_rgba->green * 65535),
+ (guint)(text_attrs->appearance.fg_rgba->blue * 65535));
+ g_variant_builder_add (builder, "{ss}", "bg-color", value);
+ g_free (value);
+
+ value = g_strdup_printf ("%g", text_attrs->font_scale);
+ g_variant_builder_add (builder, "{ss}", "scale", value);
+ g_free (value);
+
+ value = g_strdup ((gchar *)(text_attrs->language));
+ g_variant_builder_add (builder, "{ss}", "language", value);
+ g_free (value);
+
+ value = g_strdup_printf ("%i", text_attrs->appearance.rise);
+ g_variant_builder_add (builder, "{ss}", "rise", value);
+ g_free (value);
+
+ value = g_strdup_printf ("%i", text_attrs->pixels_inside_wrap);
+ g_variant_builder_add (builder, "{ss}", "pixels-inside-wrap", value);
+ g_free (value);
+
+ value = g_strdup_printf ("%i", text_attrs->pixels_below_lines);
+ g_variant_builder_add (builder, "{ss}", "pixels-below-lines", value);
+ g_free (value);
+
+ value = g_strdup_printf ("%i", text_attrs->pixels_above_lines);
+ g_variant_builder_add (builder, "{ss}", "pixels-above-lines", value);
+ g_free (value);
+
+ value = g_strdup_printf ("%i", text_attrs->indent);
+ g_variant_builder_add (builder, "{ss}", "indent", value);
+ g_free (value);
+
+ value = g_strdup_printf ("%i", text_attrs->left_margin);
+ g_variant_builder_add (builder, "{ss}", "left-margin", value);
+ g_free (value);
+
+ value = g_strdup_printf ("%i", text_attrs->right_margin);
+ g_variant_builder_add (builder, "{ss}", "right-margin", value);
+ g_free (value);
+
+ gtk_text_attributes_unref (text_attrs);
+}
+
+void
+gtk_text_buffer_get_run_attributes (GtkTextBuffer *buffer,
+ GVariantBuilder *builder,
+ int offset,
+ int *start_offset,
+ int *end_offset)
+{
+ GtkTextIter iter;
+ GSList *tags, *temp_tags;
+ gdouble scale = 1;
+ gboolean val_set = FALSE;
+
+ gtk_text_buffer_get_iter_at_offset (buffer, &iter, offset);
+
+ gtk_text_iter_forward_to_tag_toggle (&iter, NULL);
+ *end_offset = gtk_text_iter_get_offset (&iter);
+
+ gtk_text_iter_backward_to_tag_toggle (&iter, NULL);
+ *start_offset = gtk_text_iter_get_offset (&iter);
+
+ gtk_text_buffer_get_iter_at_offset (buffer, &iter, offset);
+
+ tags = gtk_text_iter_get_tags (&iter);
+ tags = g_slist_reverse (tags);
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ PangoStyle style;
+
+ g_object_get (tag,
+ "style-set", &val_set,
+ "style", &style,
+ NULL);
+ if (val_set)
+ g_variant_builder_add (builder, "{ss}", "style", pango_style_to_string (style));
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ PangoVariant variant;
+
+ g_object_get (tag,
+ "variant-set", &val_set,
+ "variant", &variant,
+ NULL);
+ if (val_set)
+ g_variant_builder_add (builder, "{ss}", "variant", pango_variant_to_string (variant));
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ PangoStretch stretch;
+
+ g_object_get (tag,
+ "stretch-set", &val_set,
+ "stretch", &stretch,
+ NULL);
+ if (val_set)
+ g_variant_builder_add (builder, "{ss}", "stretch", pango_stretch_to_string (stretch));
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ GtkJustification justification;
+
+ g_object_get (tag,
+ "justification-set", &val_set,
+ "justification", &justification,
+ NULL);
+ if (val_set)
+ g_variant_builder_add (builder, "{ss}", "justification", gtk_justification_to_string (justification));
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ GtkTextDirection direction;
+
+ g_object_get (tag, "direction", &direction, NULL);
+ if (direction != GTK_TEXT_DIR_NONE)
+ {
+ val_set = TRUE;
+ g_variant_builder_add (builder, "{ss}", "direction", gtk_text_direction_to_string (direction));
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ GtkWrapMode wrap_mode;
+
+ g_object_get (tag,
+ "wrap-mode-set", &val_set,
+ "wrap-mode", &wrap_mode,
+ NULL);
+ if (val_set)
+ g_variant_builder_add (builder, "{ss}", "wrap-mode", pango_wrap_mode_to_string (wrap_mode));
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+
+ g_object_get (tag, "foreground-set", &val_set, NULL);
+ if (val_set)
+ {
+ GdkRGBA *rgba;
+ char *value;
+
+ g_object_get (tag, "foreground", &rgba, NULL);
+ value = g_strdup_printf ("%u,%u,%u",
+ (guint) rgba->red * 65535,
+ (guint) rgba->green * 65535,
+ (guint) rgba->blue * 65535);
+ gdk_rgba_free (rgba);
+ g_variant_builder_add (builder, "{ss}", "fg-color", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+
+ g_object_get (tag, "background-set", &val_set, NULL);
+ if (val_set)
+ {
+ GdkRGBA *rgba;
+ char *value;
+
+ g_object_get (tag, "background-rgba", &rgba, NULL);
+ value = g_strdup_printf ("%u,%u,%u",
+ (guint) rgba->red * 65535,
+ (guint) rgba->green * 65535,
+ (guint) rgba->blue * 65535);
+ gdk_rgba_free (rgba);
+ g_variant_builder_add (builder, "{ss}", "bg-color", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+
+ g_object_get (tag, "family-set", &val_set, NULL);
+
+ if (val_set)
+ {
+ char *value;
+ g_object_get (tag, "family", &value, NULL);
+ g_variant_builder_add (builder, "{ss}", "family-name", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+
+ g_object_get (tag, "language-set", &val_set, NULL);
+
+ if (val_set)
+ {
+ char *value;
+ g_object_get (tag, "language", &value, NULL);
+ g_variant_builder_add (builder, "{ss}", "language", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ int weight;
+
+ g_object_get (tag,
+ "weight-set", &val_set,
+ "weight", &weight,
+ NULL);
+
+ if (val_set)
+ {
+ char *value;
+ value = g_strdup_printf ("%d", weight);
+ g_variant_builder_add (builder, "{ss}", "weight", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ /* scale is special as the effective value is the product
+ * of all specified values
+ */
+ temp_tags = tags;
+ while (temp_tags)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ gboolean scale_set;
+
+ g_object_get (tag, "scale-set", &scale_set, NULL);
+ if (scale_set)
+ {
+ double font_scale;
+ g_object_get (tag, "scale", &font_scale, NULL);
+ val_set = TRUE;
+ scale *= font_scale;
+ }
+ temp_tags = temp_tags->next;
+ }
+ if (val_set)
+ {
+ char *value = g_strdup_printf ("%g", scale);
+ g_variant_builder_add (builder, "{ss}", "scale", value);
+ g_free (value);
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ int size;
+
+ g_object_get (tag,
+ "size-set", &val_set,
+ "size", &size,
+ NULL);
+ if (val_set)
+ {
+ char *value = g_strdup_printf ("%i", size);
+ g_variant_builder_add (builder, "{ss}", "size", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ gboolean strikethrough;
+
+ g_object_get (tag,
+ "strikethrough-set", &val_set,
+ "strikethrough", &strikethrough,
+ NULL);
+ if (val_set)
+ g_variant_builder_add (builder, "{ss}", "strikethrough", strikethrough ? "true" : "false");
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ PangoUnderline underline;
+
+ g_object_get (tag,
+ "underline-set", &val_set,
+ "underline", &underline,
+ NULL);
+ if (val_set)
+ g_variant_builder_add (builder, "{ss}", "underline",
+ pango_underline_to_string (underline));
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ int rise;
+
+ g_object_get (tag,
+ "rise-set", &val_set,
+ "rise", &rise,
+ NULL);
+ if (val_set)
+ {
+ char *value = g_strdup_printf ("%i", rise);
+ g_variant_builder_add (builder, "{ss}", "rise", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ gboolean bg_full_height;
+
+ g_object_get (tag,
+ "background-full-height-set", &val_set,
+ "background-full-height", &bg_full_height,
+ NULL);
+ if (val_set)
+ g_variant_builder_add (builder, "{ss}", "bg-full-height", bg_full_height ? "true" : "false");
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ int pixels;
+
+ g_object_get (tag,
+ "pixels-inside-wrap-set", &val_set,
+ "pixels-inside-wrap", &pixels,
+ NULL);
+ if (val_set)
+ {
+ char *value = g_strdup_printf ("%i", pixels);
+ g_variant_builder_add (builder, "{ss}", "pixels-inside-wrap", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ int pixels;
+
+ g_object_get (tag,
+ "pixels-below-lines-set", &val_set,
+ "pixels-below-lines", &pixels,
+ NULL);
+ if (val_set)
+ {
+ char *value = g_strdup_printf ("%i", pixels);
+ g_variant_builder_add (builder, "{ss}", "pixels-below-lines", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ int pixels;
+
+ g_object_get (tag,
+ "pixels-above-lines-set", &val_set,
+ "pixels-above-lines", &pixels,
+ NULL);
+ if (val_set)
+ {
+ char *value = g_strdup_printf ("%i", pixels);
+ g_variant_builder_add (builder, "{ss}", "pixels-above-lines", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ gboolean editable;
+
+ g_object_get (tag,
+ "editable-set", &val_set,
+ "editable", &editable,
+ NULL);
+ if (val_set)
+ g_variant_builder_add (builder, "{ss}", "editable", editable ? "true" : "false");
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ gboolean invisible;
+
+ g_object_get (tag,
+ "invisible-set", &val_set,
+ "invisible", &invisible,
+ NULL);
+ if (val_set)
+ g_variant_builder_add (builder, "{ss}", "invisible", invisible ? "true" : "false");
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ int indent;
+
+ g_object_get (tag,
+ "indent-set", &val_set,
+ "indent", &indent,
+ NULL);
+ if (val_set)
+ {
+ char *value = g_strdup_printf ("%i", indent);
+ g_variant_builder_add (builder, "{ss}", "indent", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ int margin;
+
+ g_object_get (tag,
+ "right-margin-set", &val_set,
+ "right-margin", &margin,
+ NULL);
+ if (val_set)
+ {
+ char *value = g_strdup_printf ("%i", margin);
+ g_variant_builder_add (builder, "{ss}", "right-margin", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ temp_tags = tags;
+ while (temp_tags && !val_set)
+ {
+ GtkTextTag *tag = GTK_TEXT_TAG (temp_tags->data);
+ int margin;
+
+ g_object_get (tag,
+ "left-margin-set", &val_set,
+ "left-margin", &margin,
+ NULL);
+ if (val_set)
+ {
+ char *value = g_strdup_printf ("%i", margin);
+ g_variant_builder_add (builder, "{ss}", "left-margin", value);
+ g_free (value);
+ }
+ temp_tags = temp_tags->next;
+ }
+ val_set = FALSE;
+
+ g_slist_free (tags);
+}
+
+char *
+gtk_text_view_get_text_before (GtkTextView *view,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter pos, start, end;
+
+ buffer = gtk_text_view_get_buffer (view);
+ gtk_text_buffer_get_iter_at_offset (buffer, &pos, offset);
+ start = end = pos;
+
+ switch (boundary_type)
+ {
+ case ATSPI_TEXT_BOUNDARY_CHAR:
+ gtk_text_iter_backward_char (&start);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_WORD_START:
+ if (!gtk_text_iter_starts_word (&start))
+ gtk_text_iter_backward_word_start (&start);
+ end = start;
+ gtk_text_iter_backward_word_start (&start);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_WORD_END:
+ if (gtk_text_iter_inside_word (&start) &&
+ !gtk_text_iter_starts_word (&start))
+ gtk_text_iter_backward_word_start (&start);
+ while (!gtk_text_iter_ends_word (&start))
+ {
+ if (!gtk_text_iter_backward_char (&start))
+ break;
+ }
+ end = start;
+ gtk_text_iter_backward_word_start (&start);
+ while (!gtk_text_iter_ends_word (&start))
+ {
+ if (!gtk_text_iter_backward_char (&start))
+ break;
+ }
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_START:
+ if (!gtk_text_iter_starts_sentence (&start))
+ gtk_text_iter_backward_sentence_start (&start);
+ end = start;
+ gtk_text_iter_backward_sentence_start (&start);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_END:
+ if (gtk_text_iter_inside_sentence (&start) &&
+ !gtk_text_iter_starts_sentence (&start))
+ gtk_text_iter_backward_sentence_start (&start);
+ while (!gtk_text_iter_ends_sentence (&start))
+ {
+ if (!gtk_text_iter_backward_char (&start))
+ break;
+ }
+ end = start;
+ gtk_text_iter_backward_sentence_start (&start);
+ while (!gtk_text_iter_ends_sentence (&start))
+ {
+ if (!gtk_text_iter_backward_char (&start))
+ break;
+ }
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_LINE_START:
+ gtk_text_view_backward_display_line_start (view, &start);
+ end = start;
+ gtk_text_view_backward_display_line (view, &start);
+ gtk_text_view_backward_display_line_start (view, &start);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_LINE_END:
+ gtk_text_view_backward_display_line_start (view, &start);
+ if (!gtk_text_iter_is_start (&start))
+ {
+ gtk_text_view_backward_display_line (view, &start);
+ end = start;
+ gtk_text_view_forward_display_line_end (view, &end);
+ if (!gtk_text_iter_is_start (&start))
+ {
+ if (gtk_text_view_backward_display_line (view, &start))
+ gtk_text_view_forward_display_line_end (view, &start);
+ else
+ gtk_text_iter_set_offset (&start, 0);
+ }
+ }
+ else
+ end = start;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ *start_offset = gtk_text_iter_get_offset (&start);
+ *end_offset = gtk_text_iter_get_offset (&end);
+
+ return gtk_text_buffer_get_slice (buffer, &start, &end, FALSE);
+}
+
+char *
+gtk_text_view_get_text_at (GtkTextView *view,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter pos, start, end;
+
+ buffer = gtk_text_view_get_buffer (view);
+ gtk_text_buffer_get_iter_at_offset (buffer, &pos, offset);
+ start = end = pos;
+
+ switch (boundary_type)
+ {
+ case ATSPI_TEXT_BOUNDARY_CHAR:
+ gtk_text_iter_forward_char (&end);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_WORD_START:
+ if (!gtk_text_iter_starts_word (&start))
+ gtk_text_iter_backward_word_start (&start);
+ if (gtk_text_iter_inside_word (&end))
+ gtk_text_iter_forward_word_end (&end);
+ while (!gtk_text_iter_starts_word (&end))
+ {
+ if (!gtk_text_iter_forward_char (&end))
+ break;
+ }
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_WORD_END:
+ if (gtk_text_iter_inside_word (&start) &&
+ !gtk_text_iter_starts_word (&start))
+ gtk_text_iter_backward_word_start (&start);
+ while (!gtk_text_iter_ends_word (&start))
+ {
+ if (!gtk_text_iter_backward_char (&start))
+ break;
+ }
+ gtk_text_iter_forward_word_end (&end);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_START:
+ if (!gtk_text_iter_starts_sentence (&start))
+ gtk_text_iter_backward_sentence_start (&start);
+ if (gtk_text_iter_inside_sentence (&end))
+ gtk_text_iter_forward_sentence_end (&end);
+ while (!gtk_text_iter_starts_sentence (&end))
+ {
+ if (!gtk_text_iter_forward_char (&end))
+ break;
+ }
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_END:
+ if (gtk_text_iter_inside_sentence (&start) &&
+ !gtk_text_iter_starts_sentence (&start))
+ gtk_text_iter_backward_sentence_start (&start);
+ while (!gtk_text_iter_ends_sentence (&start))
+ {
+ if (!gtk_text_iter_backward_char (&start))
+ break;
+ }
+ gtk_text_iter_forward_sentence_end (&end);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_LINE_START:
+ gtk_text_view_backward_display_line_start (view, &start);
+ gtk_text_view_forward_display_line (view, &end);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_LINE_END:
+ gtk_text_view_backward_display_line_start (view, &start);
+ if (!gtk_text_iter_is_start (&start))
+ {
+ gtk_text_view_backward_display_line (view, &start);
+ gtk_text_view_forward_display_line_end (view, &start);
+ }
+ gtk_text_view_forward_display_line_end (view, &end);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ *start_offset = gtk_text_iter_get_offset (&start);
+ *end_offset = gtk_text_iter_get_offset (&end);
+
+ return gtk_text_buffer_get_slice (buffer, &start, &end, FALSE);
+}
+
+char *
+gtk_text_view_get_text_after (GtkTextView *view,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter pos, start, end;
+
+ buffer = gtk_text_view_get_buffer (view);
+ gtk_text_buffer_get_iter_at_offset (buffer, &pos, offset);
+ start = end = pos;
+
+ switch (boundary_type)
+ {
+ case ATSPI_TEXT_BOUNDARY_CHAR:
+ gtk_text_iter_forward_char (&start);
+ gtk_text_iter_forward_chars (&end, 2);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_WORD_START:
+ if (gtk_text_iter_inside_word (&end))
+ gtk_text_iter_forward_word_end (&end);
+ while (!gtk_text_iter_starts_word (&end))
+ {
+ if (!gtk_text_iter_forward_char (&end))
+ break;
+ }
+ start = end;
+ if (!gtk_text_iter_is_end (&end))
+ {
+ gtk_text_iter_forward_word_end (&end);
+ while (!gtk_text_iter_starts_word (&end))
+ {
+ if (!gtk_text_iter_forward_char (&end))
+ break;
+ }
+ }
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_WORD_END:
+ gtk_text_iter_forward_word_end (&end);
+ start = end;
+ if (!gtk_text_iter_is_end (&end))
+ gtk_text_iter_forward_word_end (&end);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_START:
+ if (gtk_text_iter_inside_sentence (&end))
+ gtk_text_iter_forward_sentence_end (&end);
+ while (!gtk_text_iter_starts_sentence (&end))
+ {
+ if (!gtk_text_iter_forward_char (&end))
+ break;
+ }
+ start = end;
+ if (!gtk_text_iter_is_end (&end))
+ {
+ gtk_text_iter_forward_sentence_end (&end);
+ while (!gtk_text_iter_starts_sentence (&end))
+ {
+ if (!gtk_text_iter_forward_char (&end))
+ break;
+ }
+ }
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_SENTENCE_END:
+ gtk_text_iter_forward_sentence_end (&end);
+ start = end;
+ if (!gtk_text_iter_is_end (&end))
+ gtk_text_iter_forward_sentence_end (&end);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_LINE_START:
+ gtk_text_view_forward_display_line (view, &end);
+ start = end;
+ gtk_text_view_forward_display_line (view, &end);
+ break;
+
+ case ATSPI_TEXT_BOUNDARY_LINE_END:
+ gtk_text_view_forward_display_line_end (view, &end);
+ start = end;
+ gtk_text_view_forward_display_line (view, &end);
+ gtk_text_view_forward_display_line_end (view, &end);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ *start_offset = gtk_text_iter_get_offset (&start);
+ *end_offset = gtk_text_iter_get_offset (&end);
+
+ return gtk_text_buffer_get_slice (buffer, &start, &end, FALSE);
+}
+
+char *
+gtk_text_view_get_string_at (GtkTextView *view,
+ int offset,
+ AtspiTextGranularity granularity,
+ int *start_offset,
+ int *end_offset)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter pos, start, end;
+
+ buffer = gtk_text_view_get_buffer (view);
+ gtk_text_buffer_get_iter_at_offset (buffer, &pos, offset);
+ start = end = pos;
+
+ if (granularity == ATSPI_TEXT_GRANULARITY_CHAR)
+ {
+ gtk_text_iter_forward_char (&end);
+ }
+ else if (granularity == ATSPI_TEXT_GRANULARITY_WORD)
+ {
+ if (!gtk_text_iter_starts_word (&start))
+ gtk_text_iter_backward_word_start (&start);
+ gtk_text_iter_forward_word_end (&end);
+ }
+ else if (granularity == ATSPI_TEXT_GRANULARITY_SENTENCE)
+ {
+ if (!gtk_text_iter_starts_sentence (&start))
+ gtk_text_iter_backward_sentence_start (&start);
+ gtk_text_iter_forward_sentence_end (&end);
+ }
+ else if (granularity == ATSPI_TEXT_GRANULARITY_LINE)
+ {
+ if (!gtk_text_view_starts_display_line (view, &start))
+ gtk_text_view_backward_display_line (view, &start);
+ gtk_text_view_forward_display_line_end (view, &end);
+ }
+ else if (granularity == ATSPI_TEXT_GRANULARITY_PARAGRAPH)
+ {
+ gtk_text_iter_set_line_offset (&start, 0);
+ gtk_text_iter_forward_to_line_end (&end);
+ }
+
+ *start_offset = gtk_text_iter_get_offset (&start);
+ *end_offset = gtk_text_iter_get_offset (&end);
+
+ return gtk_text_buffer_get_slice (buffer, &start, &end, FALSE);
+}
diff --git a/gtk/a11y/gtkatspitextbufferprivate.h b/gtk/a11y/gtkatspitextbufferprivate.h
new file mode 100644
index 0000000000..fb97a94641
--- /dev/null
+++ b/gtk/a11y/gtkatspitextbufferprivate.h
@@ -0,0 +1,55 @@
+/* gtkatspitextbufferprivate.h: Utilities for GtkTextBuffer and AT-SPI
+ *
+ * Copyright 2020 Red Hat, Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "gtkatspiprivate.h"
+#include "gtktextview.h"
+
+G_BEGIN_DECLS
+
+void gtk_text_view_add_default_attributes (GtkTextView *view,
+ GVariantBuilder *builder);
+void gtk_text_buffer_get_run_attributes (GtkTextBuffer *buffer,
+ GVariantBuilder *builder,
+ int offset,
+ int *start_offset,
+ int *end_offset);
+
+char *gtk_text_view_get_text_before (GtkTextView *view,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset);
+char *gtk_text_view_get_text_at (GtkTextView *view,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset);
+char *gtk_text_view_get_text_after (GtkTextView *view,
+ int offset,
+ AtspiTextBoundaryType boundary_type,
+ int *start_offset,
+ int *end_offset);
+char *gtk_text_view_get_string_at (GtkTextView *view,
+ int offset,
+ AtspiTextGranularity granularity,
+ int *start_offset,
+ int *end_offset);
+
+G_END_DECLS
diff --git a/gtk/a11y/gtkatspitextprivate.h b/gtk/a11y/gtkatspitextprivate.h
new file mode 100644
index 0000000000..34ba37b065
--- /dev/null
+++ b/gtk/a11y/gtkatspitextprivate.h
@@ -0,0 +1,45 @@
+/* gtkatspitextprivate.h: AT-SPI Text implementation
+ *
+ * Copyright 2020 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include "gtkaccessible.h"
+
+G_BEGIN_DECLS
+
+const GDBusInterfaceVTable *gtk_atspi_get_text_vtable (GtkAccessible *accessible);
+
+typedef void (GtkAtspiTextChangedCallback) (gpointer data,
+ const char *kind,
+ int start,
+ int end,
+ const char *text);
+typedef void (GtkAtspiTextSelectionCallback) (gpointer data,
+ const char *kind,
+ int position);
+
+void gtk_atspi_connect_text_signals (GtkAccessible *accessible,
+ GtkAtspiTextChangedCallback text_changed,
+ GtkAtspiTextSelectionCallback selection_changed,
+ gpointer data);
+void gtk_atspi_disconnect_text_signals (GtkAccessible *accessible);
+
+G_END_DECLS
diff --git a/gtk/a11y/gtkatspiutils.c b/gtk/a11y/gtkatspiutils.c
new file mode 100644
index 0000000000..1fef628144
--- /dev/null
+++ b/gtk/a11y/gtkatspiutils.c
@@ -0,0 +1,307 @@
+/* gtkatspiutils.c: Shared utilities for ATSPI
+ *
+ * Copyright 2020 GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkatspiutilsprivate.h"
+#include "gtkenums.h"
+#include "gtkpasswordentry.h"
+
+/*< private >
+ * gtk_accessible_role_to_atspi_role:
+ * @role: a #GtkAccessibleRole
+ *
+ * Converts a #GtkAccessibleRole value to the equivalent ATSPI role.
+ *
+ * Returns: an #AtspiRole
+ */
+static AtspiRole
+gtk_accessible_role_to_atspi_role (GtkAccessibleRole role)
+{
+ switch (role)
+ {
+ case GTK_ACCESSIBLE_ROLE_ALERT:
+ return ATSPI_ROLE_ALERT;
+
+ case GTK_ACCESSIBLE_ROLE_ALERT_DIALOG:
+ return ATSPI_ROLE_DIALOG;
+
+ case GTK_ACCESSIBLE_ROLE_BANNER:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_BUTTON:
+ return ATSPI_ROLE_PUSH_BUTTON;
+
+ case GTK_ACCESSIBLE_ROLE_CAPTION:
+ return ATSPI_ROLE_CAPTION;
+
+ case GTK_ACCESSIBLE_ROLE_CELL:
+ return ATSPI_ROLE_TABLE_CELL;
+
+ case GTK_ACCESSIBLE_ROLE_CHECKBOX:
+ return ATSPI_ROLE_CHECK_BOX;
+
+ case GTK_ACCESSIBLE_ROLE_COLUMN_HEADER:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_COMBO_BOX:
+ return ATSPI_ROLE_COMBO_BOX;
+
+ case GTK_ACCESSIBLE_ROLE_COMMAND:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_COMPOSITE:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_DIALOG:
+ return ATSPI_ROLE_DIALOG;
+
+ case GTK_ACCESSIBLE_ROLE_DOCUMENT:
+ return ATSPI_ROLE_DOCUMENT_TEXT;
+
+ case GTK_ACCESSIBLE_ROLE_FEED:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_FORM:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_GENERIC:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_GRID:
+ return ATSPI_ROLE_TABLE;
+
+ case GTK_ACCESSIBLE_ROLE_GRID_CELL:
+ return ATSPI_ROLE_TABLE_CELL;
+
+ case GTK_ACCESSIBLE_ROLE_GROUP:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_HEADING:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_IMG:
+ return ATSPI_ROLE_IMAGE;
+
+ case GTK_ACCESSIBLE_ROLE_INPUT:
+ return ATSPI_ROLE_ENTRY;
+
+ case GTK_ACCESSIBLE_ROLE_LABEL:
+ return ATSPI_ROLE_LABEL;
+
+ case GTK_ACCESSIBLE_ROLE_LANDMARK:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_LEGEND:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_LINK:
+ return ATSPI_ROLE_LINK;
+
+ case GTK_ACCESSIBLE_ROLE_LIST:
+ return ATSPI_ROLE_LIST;
+
+ case GTK_ACCESSIBLE_ROLE_LIST_BOX:
+ return ATSPI_ROLE_LIST_BOX;
+
+ case GTK_ACCESSIBLE_ROLE_LIST_ITEM:
+ return ATSPI_ROLE_LIST_ITEM;
+
+ case GTK_ACCESSIBLE_ROLE_LOG:
+ return ATSPI_ROLE_LOG;
+
+ case GTK_ACCESSIBLE_ROLE_MAIN:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_MARQUEE:
+ return ATSPI_ROLE_MARQUEE;
+
+ case GTK_ACCESSIBLE_ROLE_MATH:
+ return ATSPI_ROLE_MATH;;
+
+ case GTK_ACCESSIBLE_ROLE_METER:
+ return ATSPI_ROLE_LEVEL_BAR;
+
+ case GTK_ACCESSIBLE_ROLE_MENU:
+ return ATSPI_ROLE_MENU;
+
+ case GTK_ACCESSIBLE_ROLE_MENU_BAR:
+ return ATSPI_ROLE_MENU_BAR;
+
+ case GTK_ACCESSIBLE_ROLE_MENU_ITEM:
+ return ATSPI_ROLE_MENU_ITEM;
+
+ case GTK_ACCESSIBLE_ROLE_MENU_ITEM_CHECKBOX:
+ return ATSPI_ROLE_CHECK_MENU_ITEM;
+
+ case GTK_ACCESSIBLE_ROLE_MENU_ITEM_RADIO:
+ return ATSPI_ROLE_RADIO_MENU_ITEM;
+
+ case GTK_ACCESSIBLE_ROLE_NAVIGATION:
+ return ATSPI_ROLE_FILLER;
+
+ case GTK_ACCESSIBLE_ROLE_NONE:
+ return ATSPI_ROLE_INVALID;
+
+ case GTK_ACCESSIBLE_ROLE_NOTE:
+ return ATSPI_ROLE_FOOTNOTE;
+
+ case GTK_ACCESSIBLE_ROLE_OPTION:
+ return ATSPI_ROLE_OPTION_PANE;
+
+ case GTK_ACCESSIBLE_ROLE_PRESENTATION:
+ return ATSPI_ROLE_DOCUMENT_PRESENTATION;
+
+ case GTK_ACCESSIBLE_ROLE_PROGRESS_BAR:
+ return ATSPI_ROLE_PROGRESS_BAR;
+
+ case GTK_ACCESSIBLE_ROLE_RADIO:
+ return ATSPI_ROLE_RADIO_BUTTON;
+
+ case GTK_ACCESSIBLE_ROLE_RADIO_GROUP:
+ return ATSPI_ROLE_GROUPING;
+
+ case GTK_ACCESSIBLE_ROLE_RANGE:
+ break;
+
+ case GTK_ACCESSIBLE_ROLE_REGION:
+ return ATSPI_ROLE_FILLER;
+
+ case GTK_ACCESSIBLE_ROLE_ROW:
+ return ATSPI_ROLE_TABLE_ROW;
+
+ case GTK_ACCESSIBLE_ROLE_ROW_GROUP:
+ return ATSPI_ROLE_GROUPING;
+
+ case GTK_ACCESSIBLE_ROLE_ROW_HEADER:
+ return ATSPI_ROLE_ROW_HEADER;
+
+ case GTK_ACCESSIBLE_ROLE_SCROLLBAR:
+ return ATSPI_ROLE_SCROLL_BAR;
+
+ case GTK_ACCESSIBLE_ROLE_SEARCH:
+ return ATSPI_ROLE_FORM;
+
+ case GTK_ACCESSIBLE_ROLE_SEARCH_BOX:
+ return ATSPI_ROLE_ENTRY;
+
+ case GTK_ACCESSIBLE_ROLE_SECTION:
+ return ATSPI_ROLE_FILLER;
+
+ case GTK_ACCESSIBLE_ROLE_SECTION_HEAD:
+ return ATSPI_ROLE_FILLER;
+
+ case GTK_ACCESSIBLE_ROLE_SELECT:
+ return ATSPI_ROLE_FILLER;
+
+ case GTK_ACCESSIBLE_ROLE_SEPARATOR:
+ return ATSPI_ROLE_SEPARATOR;
+
+ case GTK_ACCESSIBLE_ROLE_SLIDER:
+ return ATSPI_ROLE_SLIDER;
+
+ case GTK_ACCESSIBLE_ROLE_SPIN_BUTTON:
+ return ATSPI_ROLE_SPIN_BUTTON;
+
+ case GTK_ACCESSIBLE_ROLE_STATUS:
+ return ATSPI_ROLE_STATUS_BAR;
+
+ case GTK_ACCESSIBLE_ROLE_STRUCTURE:
+ return ATSPI_ROLE_FILLER;
+
+ case GTK_ACCESSIBLE_ROLE_SWITCH:
+ return ATSPI_ROLE_CHECK_BOX;
+
+ case GTK_ACCESSIBLE_ROLE_TAB:
+ return ATSPI_ROLE_PAGE_TAB;
+
+ case GTK_ACCESSIBLE_ROLE_TABLE:
+ return ATSPI_ROLE_TABLE;
+
+ case GTK_ACCESSIBLE_ROLE_TAB_LIST:
+ return ATSPI_ROLE_PAGE_TAB_LIST;
+
+ case GTK_ACCESSIBLE_ROLE_TAB_PANEL:
+ return ATSPI_ROLE_PANEL;
+
+ case GTK_ACCESSIBLE_ROLE_TEXT_BOX:
+ return ATSPI_ROLE_TEXT;
+
+ case GTK_ACCESSIBLE_ROLE_TIME:
+ return ATSPI_ROLE_TEXT;
+
+ case GTK_ACCESSIBLE_ROLE_TIMER:
+ return ATSPI_ROLE_TIMER;
+
+ case GTK_ACCESSIBLE_ROLE_TOOLBAR:
+ return ATSPI_ROLE_TOOL_BAR;
+
+ case GTK_ACCESSIBLE_ROLE_TOOLTIP:
+ return ATSPI_ROLE_TOOL_TIP;
+
+ case GTK_ACCESSIBLE_ROLE_TREE:
+ return ATSPI_ROLE_TREE;
+
+ case GTK_ACCESSIBLE_ROLE_TREE_GRID:
+ return ATSPI_ROLE_TREE_TABLE;
+
+ case GTK_ACCESSIBLE_ROLE_TREE_ITEM:
+ return ATSPI_ROLE_TREE_ITEM;
+
+ case GTK_ACCESSIBLE_ROLE_WIDGET:
+ return ATSPI_ROLE_FILLER;
+
+ case GTK_ACCESSIBLE_ROLE_WINDOW:
+ return ATSPI_ROLE_WINDOW;
+
+ default:
+ break;
+ }
+
+ return ATSPI_ROLE_FILLER;
+}
+
+/*<private>
+ * gtk_atspi_role_for_context:
+ * @context: a #GtkATContext
+ *
+ * Returns a suitable ATSPI role for a context, taking into account
+ * both the #GtkAccessibleRole set on the context and the type
+ * of accessible.
+ *
+ * Returns: an #AtspiRole
+ */
+AtspiRole
+gtk_atspi_role_for_context (GtkATContext *context)
+{
+ GtkAccessible *accessible = gtk_at_context_get_accessible (context);
+ GtkAccessibleRole role = gtk_at_context_get_accessible_role (context);
+
+ if (GTK_IS_PASSWORD_ENTRY (accessible))
+ return ATSPI_ROLE_PASSWORD_TEXT;
+
+ return gtk_accessible_role_to_atspi_role (role);
+}
+
+GVariant *
+gtk_at_spi_null_ref (void)
+{
+ return g_variant_new ("(so)", "", "/org/a11y/atspi/null");
+}
diff --git a/gtk/a11y/gtkatspiutilsprivate.h b/gtk/a11y/gtkatspiutilsprivate.h
new file mode 100644
index 0000000000..20b0b09305
--- /dev/null
+++ b/gtk/a11y/gtkatspiutilsprivate.h
@@ -0,0 +1,34 @@
+/* gtkatspiutilsprivate.h: Shared utilities for AT-SPI
+ *
+ * Copyright 2020 GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "gtkatspiprivate.h"
+#include "gtkatcontextprivate.h"
+
+G_BEGIN_DECLS
+
+AtspiRole
+gtk_atspi_role_for_context (GtkATContext *context);
+
+GVariant *
+gtk_at_spi_null_ref (void);
+
+G_END_DECLS
diff --git a/gtk/a11y/gtkatspivalue.c b/gtk/a11y/gtkatspivalue.c
new file mode 100644
index 0000000000..269d27e61a
--- /dev/null
+++ b/gtk/a11y/gtkatspivalue.c
@@ -0,0 +1,129 @@
+/* gtkatspivalue.c: AT-SPI Value implementation
+ *
+ * Copyright 2020 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkatspivalueprivate.h"
+
+#include "a11y/atspi/atspi-value.h"
+
+#include "gtkatcontextprivate.h"
+#include "gtkdebug.h"
+#include "gtklevelbar.h"
+#include "gtkpaned.h"
+#include "gtkprogressbar.h"
+#include "gtkrange.h"
+#include "gtkscalebutton.h"
+#include "gtkspinbutton.h"
+
+#include <gio/gio.h>
+
+static GVariant *
+handle_value_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GtkATContext *ctx = GTK_AT_CONTEXT (user_data);
+ struct {
+ const char *name;
+ GtkAccessibleProperty property;
+ } properties[] = {
+ { "MinimumValue", GTK_ACCESSIBLE_PROPERTY_VALUE_MIN },
+ { "MaximumValue", GTK_ACCESSIBLE_PROPERTY_VALUE_MAX },
+ { "CurrentValue", GTK_ACCESSIBLE_PROPERTY_VALUE_NOW },
+ };
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (properties); i++)
+ {
+ if (g_strcmp0 (property_name, properties[i].name) == 0)
+ {
+ if (gtk_at_context_has_accessible_property (ctx, properties[i].property))
+ {
+ GtkAccessibleValue *value;
+
+ value = gtk_at_context_get_accessible_property (ctx, properties[i].property);
+ return g_variant_new_double (gtk_number_accessible_value_get (value));
+ }
+ }
+ }
+
+ /* fall back for a) MinimumIncrement b) widgets that should have the
+ * properties but don't
+ */
+ return g_variant_new_double (0.0);
+}
+
+static gboolean
+handle_value_set_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GVariant *value,
+ GError **error,
+ gpointer user_data)
+{
+ GtkATContext *self = user_data;
+ GtkWidget *widget = GTK_WIDGET (gtk_at_context_get_accessible (self));
+
+ if (g_strcmp0 (property_name, "CurrentValue") == 0)
+ {
+ /* we only allow setting values if that is part of the user-exposed
+ * functionality of the widget.
+ */
+ if (GTK_IS_RANGE (widget))
+ gtk_range_set_value (GTK_RANGE (widget), g_variant_get_double (value));
+ else if (GTK_IS_PANED (widget))
+ gtk_paned_set_position (GTK_PANED (widget), (int)(g_variant_get_double (value) + 0.5));
+ else if (GTK_IS_SPIN_BUTTON (widget))
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), g_variant_get_double (value));
+ else if (GTK_IS_SCALE_BUTTON (widget))
+ gtk_scale_button_set_value (GTK_SCALE_BUTTON (widget), g_variant_get_double (value));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static const GDBusInterfaceVTable value_vtable = {
+ NULL,
+ handle_value_get_property,
+ handle_value_set_property,
+};
+
+const GDBusInterfaceVTable *
+gtk_atspi_get_value_vtable (GtkAccessible *accessible)
+{
+ if (GTK_IS_LEVEL_BAR (accessible) ||
+ GTK_IS_PANED (accessible) ||
+ GTK_IS_PROGRESS_BAR (accessible) ||
+ GTK_IS_RANGE (accessible) ||
+ GTK_IS_SCALE_BUTTON (accessible) ||
+ GTK_IS_SPIN_BUTTON (accessible))
+ return &value_vtable;
+
+ return NULL;
+}
+
diff --git a/gtk/a11y/gtkatspivalueprivate.h b/gtk/a11y/gtkatspivalueprivate.h
new file mode 100644
index 0000000000..de882f118e
--- /dev/null
+++ b/gtk/a11y/gtkatspivalueprivate.h
@@ -0,0 +1,30 @@
+/* gtkatspivalueprivate.h: AT-SPI Value implementation
+ *
+ * Copyright 2020 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+#include "gtkaccessible.h"
+
+G_BEGIN_DECLS
+
+const GDBusInterfaceVTable *gtk_atspi_get_value_vtable (GtkAccessible *accessible);
+
+G_END_DECLS
diff --git a/gtk/a11y/meson.build b/gtk/a11y/meson.build
new file mode 100644
index 0000000000..49359159ae
--- /dev/null
+++ b/gtk/a11y/meson.build
@@ -0,0 +1,23 @@
+gtk_a11y_src = []
+gtk_a11y_backends = []
+
+if os_unix
+ gtk_a11y_backends += 'atspi'
+endif
+
+if gtk_a11y_backends.contains('atspi')
+ subdir('atspi')
+
+ gtk_a11y_src += files([
+ 'gtkatspicache.c',
+ 'gtkatspicontext.c',
+ 'gtkatspiroot.c',
+ 'gtkatspiutils.c',
+ 'gtkatspipango.c',
+ 'gtkatspitextbuffer.c',
+ 'gtkatspitext.c',
+ 'gtkatspivalue.c',
+ 'gtkatspieditabletext.c',
+ 'gtkatspiselection.c',
+ ])
+endif