summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2020-10-15 14:59:24 +0000
committerMatthias Clasen <mclasen@redhat.com>2020-10-15 14:59:24 +0000
commit9c28ecb0da9920e173cb99dc141a4321280c8ffb (patch)
tree788841b245e2dfd3f94134133f8e16e27be1bb38
parent2488d71a0abf223f52e27927c161a9840f48fd3a (diff)
parent8737692b2b30854f7d68387a74fd4f7551677dcc (diff)
downloadgtk+-9c28ecb0da9920e173cb99dc141a4321280c8ffb.tar.gz
Merge branch 'a11y/atspi' into 'master'
A11y/atspi See merge request GNOME/gtk!2698
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--docs/reference/gtk/section-accessibility.md10
-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
-rw-r--r--gtk/gtk-autocleanups.h1
-rw-r--r--gtk/gtkaccessible.c192
-rw-r--r--gtk/gtkaccessibleattributesetprivate.h2
-rw-r--r--gtk/gtkaccessibleprivate.h15
-rw-r--r--gtk/gtkatcontext.c187
-rw-r--r--gtk/gtkatcontext.h3
-rw-r--r--gtk/gtkatcontextprivate.h20
-rw-r--r--gtk/gtkcolumnview.c47
-rw-r--r--gtk/gtkcolumnviewcell.c1
-rw-r--r--gtk/gtkcolumnviewtitle.c1
-rw-r--r--gtk/gtkcombobox.c5
-rw-r--r--gtk/gtkdropdown.c5
-rw-r--r--gtk/gtkentry.c35
-rw-r--r--gtk/gtkenums.h22
-rw-r--r--gtk/gtkflowbox.c23
-rw-r--r--gtk/gtkgizmo.c21
-rw-r--r--gtk/gtkgizmoprivate.h10
-rw-r--r--gtk/gtkgridview.c7
-rw-r--r--gtk/gtklistbase.c1
-rw-r--r--gtk/gtklistbaseprivate.h1
-rw-r--r--gtk/gtklistbox.c36
-rw-r--r--gtk/gtklistitemmanager.c6
-rw-r--r--gtk/gtklistitemmanagerprivate.h2
-rw-r--r--gtk/gtklistitemwidget.c8
-rw-r--r--gtk/gtklistitemwidgetprivate.h3
-rw-r--r--gtk/gtklistview.c24
-rw-r--r--gtk/gtklistview.h2
-rw-r--r--gtk/gtklistviewprivate.h45
-rw-r--r--gtk/gtknotebook.c48
-rw-r--r--gtk/gtkpaned.c8
-rw-r--r--gtk/gtkpasswordentry.c28
-rw-r--r--gtk/gtkscalebutton.c17
-rw-r--r--gtk/gtksearchentry.c29
-rw-r--r--gtk/gtkspinbutton.c38
-rw-r--r--gtk/gtkspinbuttonprivate.h27
-rw-r--r--gtk/gtkstack.c58
-rw-r--r--gtk/gtkstackswitcher.c22
-rw-r--r--gtk/gtktestatcontext.c2
-rw-r--r--gtk/gtktext.c47
-rw-r--r--gtk/gtkwidget.c31
-rw-r--r--gtk/meson.build2
-rw-r--r--testsuite/a11y/accessible.c4
-rw-r--r--testsuite/css/api.test.in2
-rw-r--r--testsuite/css/change/change.test.in2
-rw-r--r--testsuite/css/change/meson.build13
-rw-r--r--testsuite/css/meson.build27
-rw-r--r--testsuite/css/nodes/meson.build13
-rw-r--r--testsuite/css/nodes/nodes.test.in2
-rw-r--r--testsuite/css/parser/parser.test.in2
-rw-r--r--testsuite/css/style/meson.build13
-rw-r--r--testsuite/css/style/style.test.in2
-rw-r--r--testsuite/gdk/gdk.test.in2
-rw-r--r--testsuite/gdk/meson.build33
-rw-r--r--testsuite/gsk/meson.build3
-rw-r--r--testsuite/gsk/render-nodes-cairo.test.in2
-rw-r--r--testsuite/gsk/render-nodes-vulkan.test.in2
-rw-r--r--testsuite/gtk/gtk.test.in2
-rw-r--r--testsuite/gtk/meson.build32
-rw-r--r--testsuite/reftests/meson.build17
-rw-r--r--testsuite/reftests/reftests-dark.test.in2
-rw-r--r--testsuite/reftests/reftests-hc.test.in2
-rw-r--r--testsuite/reftests/reftests-hci.test.in2
-rw-r--r--testsuite/reftests/reftests.test.in2
-rw-r--r--testsuite/tools/meson.build1
-rw-r--r--testsuite/tools/tools.test.in2
112 files changed, 10821 insertions, 161 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 166d77e47b..5607e2e1aa 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -99,6 +99,7 @@ installed-tests:
needs: []
variables:
EXTRA_MESON_FLAGS: "--prefix=/usr --libdir=/usr/lib64 -Dinstall-tests=true"
+ G_TEST_ACCESSIBLE: 1
script:
- meson subprojects update
- meson ${COMMON_MESON_FLAGS} ${EXTRA_MESON_FLAGS} ${BACKEND_FLAGS} ${FEATURE_FLAGS}
diff --git a/docs/reference/gtk/section-accessibility.md b/docs/reference/gtk/section-accessibility.md
index ade43a1bfa..ecd3dacd26 100644
--- a/docs/reference/gtk/section-accessibility.md
+++ b/docs/reference/gtk/section-accessibility.md
@@ -48,18 +48,28 @@ Each role name is part of the #GtkAccessibleRole enumeration.
| `BUTTON` | A control that performs an action when pressed | #GtkButton, #GtkLinkButton, #GtkExpander |
| `CHECKBOX` | A control that has three possible value: `true`, `false`, or `undefined` | #GtkCheckButton |
| `COMBOBOX` | A control that can be expanded to show a list of possible values to select | #GtkComboBox |
+| `COLUMN_HEADER` | A header in a columned list | #GtkColumnView |
| `DIALOG` | A dialog that prompts the user to enter information or require a response | #GtkDialog and subclasses |
+| `GRID` | A grid of items | #GtkFlowBox, #GtkGridView |
+| `GRID_CELL` | An item in a grid | #GtkFlowBoxChild, #GtkGridView, #GtkColumnView |
| `IMG` | An image | #GtkImage, #GtkPicture |
| `LABEL` | A visible name or caption for a user interface component | #GtkLabel |
+| `LIST` | A list of items | #GtkListBox |
+| `LIST_ITEM` | An item in a list | #GtkListBoxRow |
| `METER` | Represents a value within a known range | #GtkLevelBar |
| `PROGRESS_BAR` | An element that display progress | #GtkProgressBar |
| `RADIO` | A checkable input in a group of radio roles | #GtkCheckButton |
+| `ROW` | A row in a columned list | #GtkColumnView |
| `SCROLLBAR` | A graphical object controlling the scrolling of content | #GtkScrollbar |
| `SEARCH_BOX` | A text box for entering search criteria | #GtkSearchEntry |
| `SEPARATOR` | A divider that separates sections of content or groups of items | #GtkSeparator |
| `SPIN_BUTTON` | A range control that allows seelcting among discrete choices | #GtkSpinButton |
| `SWITCH` | A control that represents on/off values | #GtkSwitch |
+| `TAB` | A tab in a list of tabs for switching pages | #GtkStackSwitcher, #GtkNotebook |
+| `TAB_LIST` | A list of tabs for switching pages | #GtkStackSwitcher, #GtkNotebook |
+| `TAB_PANEL` | A page in a notebook or stack | #GtkStack |
| `TEXT_BOX` | A type of input that allows free-form text as its value. | #GtkEntry, #GtkPasswordEntry, #GtkTextView |
+| `TREE_GRID` | A treeview-like columned list | #GtkColumnView |
| `WINDOW` | An application window | #GtkWindow |
| `...` | … |
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
diff --git a/gtk/gtk-autocleanups.h b/gtk/gtk-autocleanups.h
index 77c6cd5a56..127a63c4a8 100644
--- a/gtk/gtk-autocleanups.h
+++ b/gtk/gtk-autocleanups.h
@@ -102,7 +102,6 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkInfoBar, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkLevelBar, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkLinkButton, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkListStore, g_object_unref)
-G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkListView, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkLockButton, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkMenuButton, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkMessageDialog, g_object_unref)
diff --git a/gtk/gtkaccessible.c b/gtk/gtkaccessible.c
index 0c9a399ef0..582cdf08fd 100644
--- a/gtk/gtkaccessible.c
+++ b/gtk/gtkaccessible.c
@@ -47,6 +47,9 @@
#include "gtkatcontextprivate.h"
#include "gtkenums.h"
#include "gtktypebuiltins.h"
+#include "gtkwidget.h"
+
+#include <glib/gi18n-lib.h>
#include <stdarg.h>
@@ -532,3 +535,192 @@ gtk_accessible_reset_relation (GtkAccessible *self,
gtk_at_context_set_accessible_relation (context, relation, NULL);
gtk_at_context_update (context);
}
+
+static const char *role_names[] = {
+ [GTK_ACCESSIBLE_ROLE_ALERT] = NC_("accessibility", "alert"),
+ [GTK_ACCESSIBLE_ROLE_ALERT_DIALOG] = NC_("accessibility", "alert dialog"),
+ [GTK_ACCESSIBLE_ROLE_BANNER] = NC_("accessibility", "banner"),
+ [GTK_ACCESSIBLE_ROLE_BUTTON] = NC_("accessibility", "button"),
+ [GTK_ACCESSIBLE_ROLE_CAPTION] = NC_("accessibility", "caption"),
+ [GTK_ACCESSIBLE_ROLE_CELL] = NC_("accessibility", "cell"),
+ [GTK_ACCESSIBLE_ROLE_CHECKBOX] = NC_("accessibility", "checkbox"),
+ [GTK_ACCESSIBLE_ROLE_COLUMN_HEADER] = NC_("accessibility", "column header"),
+ [GTK_ACCESSIBLE_ROLE_COMBO_BOX] = NC_("accessibility", "combo box"),
+ [GTK_ACCESSIBLE_ROLE_COMMAND] = NC_("accessibility", "command"),
+ [GTK_ACCESSIBLE_ROLE_COMPOSITE] = NC_("accessibility", "composite"),
+ [GTK_ACCESSIBLE_ROLE_DIALOG] = NC_("accessibility", "dialog"),
+ [GTK_ACCESSIBLE_ROLE_DOCUMENT] = NC_("accessibility", "document"),
+ [GTK_ACCESSIBLE_ROLE_FEED] = NC_("accessibility", "feed"),
+ [GTK_ACCESSIBLE_ROLE_FORM] = NC_("accessibility", "form"),
+ [GTK_ACCESSIBLE_ROLE_GENERIC] = NC_("accessibility", "generic"),
+ [GTK_ACCESSIBLE_ROLE_GRID] = NC_("accessibility", "grid"),
+ [GTK_ACCESSIBLE_ROLE_GRID_CELL] = NC_("accessibility", "grid cell"),
+ [GTK_ACCESSIBLE_ROLE_GROUP] = NC_("accessibility", "group"),
+ [GTK_ACCESSIBLE_ROLE_HEADING] = NC_("accessibility", "heading"),
+ [GTK_ACCESSIBLE_ROLE_IMG] = NC_("accessibility", "image"),
+ [GTK_ACCESSIBLE_ROLE_INPUT] = NC_("accessibility", "input"),
+ [GTK_ACCESSIBLE_ROLE_LABEL] = NC_("accessibility", "label"),
+ [GTK_ACCESSIBLE_ROLE_LANDMARK] = NC_("accessibility", "landmark"),
+ [GTK_ACCESSIBLE_ROLE_LEGEND] = NC_("accessibility", "legend"),
+ [GTK_ACCESSIBLE_ROLE_LINK] = NC_("accessibility", "link"),
+ [GTK_ACCESSIBLE_ROLE_LIST] = NC_("accessibility", "list"),
+ [GTK_ACCESSIBLE_ROLE_LIST_BOX] = NC_("accessibility", "list box"),
+ [GTK_ACCESSIBLE_ROLE_LIST_ITEM] = NC_("accessibility", "list item"),
+ [GTK_ACCESSIBLE_ROLE_LOG] = NC_("accessibility", "log"),
+ [GTK_ACCESSIBLE_ROLE_MAIN] = NC_("accessibility", "main"),
+ [GTK_ACCESSIBLE_ROLE_MARQUEE] = NC_("accessibility", "marquee"),
+ [GTK_ACCESSIBLE_ROLE_MATH] = NC_("accessibility", "math"),
+ [GTK_ACCESSIBLE_ROLE_METER] = NC_("accessibility", "meter"),
+ [GTK_ACCESSIBLE_ROLE_MENU] = NC_("accessibility", "menu"),
+ [GTK_ACCESSIBLE_ROLE_MENU_BAR] = NC_("accessibility", "menu bar"),
+ [GTK_ACCESSIBLE_ROLE_MENU_ITEM] = NC_("accessibility", "menu item"),
+ [GTK_ACCESSIBLE_ROLE_MENU_ITEM_CHECKBOX] = NC_("accessibility", "menu item checkbox"),
+ [GTK_ACCESSIBLE_ROLE_MENU_ITEM_RADIO] = NC_("accessibility", "menu item radio"),
+ [GTK_ACCESSIBLE_ROLE_NAVIGATION] = NC_("accessibility", "navigation"),
+ [GTK_ACCESSIBLE_ROLE_NONE] = NC_("accessibility", "none"),
+ [GTK_ACCESSIBLE_ROLE_NOTE] = NC_("accessibility", "note"),
+ [GTK_ACCESSIBLE_ROLE_OPTION] = NC_("accessibility", "option"),
+ [GTK_ACCESSIBLE_ROLE_PRESENTATION] = NC_("accessibility", "presentation"),
+ [GTK_ACCESSIBLE_ROLE_PROGRESS_BAR] = NC_("accessibility", "progress bar"),
+ [GTK_ACCESSIBLE_ROLE_RADIO] = NC_("accessibility", "radio"),
+ [GTK_ACCESSIBLE_ROLE_RADIO_GROUP] = NC_("accessibility", "radio group"),
+ [GTK_ACCESSIBLE_ROLE_RANGE] = NC_("accessibility", "range"),
+ [GTK_ACCESSIBLE_ROLE_REGION] = NC_("accessibility", "region"),
+ [GTK_ACCESSIBLE_ROLE_ROW] = NC_("accessibility", "row"),
+ [GTK_ACCESSIBLE_ROLE_ROW_GROUP] = NC_("accessibility", "row group"),
+ [GTK_ACCESSIBLE_ROLE_ROW_HEADER] = NC_("accessibility", "row header"),
+ [GTK_ACCESSIBLE_ROLE_SCROLLBAR] = NC_("accessibility", "scroll bar"),
+ [GTK_ACCESSIBLE_ROLE_SEARCH] = NC_("accessibility", "search"),
+ [GTK_ACCESSIBLE_ROLE_SEARCH_BOX] = NC_("accessibility", "search box"),
+ [GTK_ACCESSIBLE_ROLE_SECTION] = NC_("accessibility", "section"),
+ [GTK_ACCESSIBLE_ROLE_SECTION_HEAD] = NC_("accessibility", "section head"),
+ [GTK_ACCESSIBLE_ROLE_SELECT] = NC_("accessibility", "select"),
+ [GTK_ACCESSIBLE_ROLE_SEPARATOR] = NC_("accessibility", "separator"),
+ [GTK_ACCESSIBLE_ROLE_SLIDER] = NC_("accessibility", "slider"),
+ [GTK_ACCESSIBLE_ROLE_SPIN_BUTTON] = NC_("accessibility", "spin button"),
+ [GTK_ACCESSIBLE_ROLE_STATUS] = NC_("accessibility", "status"),
+ [GTK_ACCESSIBLE_ROLE_STRUCTURE] = NC_("accessibility", "structure"),
+ [GTK_ACCESSIBLE_ROLE_SWITCH] = NC_("accessibility", "switch"),
+ [GTK_ACCESSIBLE_ROLE_TAB] = NC_("accessibility", "tab"),
+ [GTK_ACCESSIBLE_ROLE_TABLE] = NC_("accessibility", "table"),
+ [GTK_ACCESSIBLE_ROLE_TAB_LIST] = NC_("accessibility", "tab list"),
+ [GTK_ACCESSIBLE_ROLE_TAB_PANEL] = NC_("accessibility", "tab panel"),
+ [GTK_ACCESSIBLE_ROLE_TEXT_BOX] = NC_("accessibility", "text box"),
+ [GTK_ACCESSIBLE_ROLE_TIME] = NC_("accessibility", "time"),
+ [GTK_ACCESSIBLE_ROLE_TIMER] = NC_("accessibility", "timer"),
+ [GTK_ACCESSIBLE_ROLE_TOOLBAR] = NC_("accessibility", "tool bar"),
+ [GTK_ACCESSIBLE_ROLE_TOOLTIP] = NC_("accessibility", "tool tip"),
+ [GTK_ACCESSIBLE_ROLE_TREE] = NC_("accessibility", "tree"),
+ [GTK_ACCESSIBLE_ROLE_TREE_GRID] = NC_("accessibility", "tree grid"),
+ [GTK_ACCESSIBLE_ROLE_TREE_ITEM] = NC_("accessibility", "tree item"),
+ [GTK_ACCESSIBLE_ROLE_WIDGET] = NC_("accessibility", "widget"),
+ [GTK_ACCESSIBLE_ROLE_WINDOW] = NC_("accessibility", "window"),
+};
+
+/*< private >
+ * gtk_accessible_role_to_name:
+ * @role: a #GtkAccessibleRole
+ * @domain: (nullable): the translation domain
+ *
+ * Converts a #GtkAccessibleRole value to the equivalent role name.
+ *
+ * If @domain is not %NULL, the returned string will be localized.
+ *
+ * Returns: (transfer none): the name of the role
+ */
+const char *
+gtk_accessible_role_to_name (GtkAccessibleRole role,
+ const char *domain)
+{
+ if (domain != NULL)
+ return g_dpgettext2 (domain, "accessibility", role_names[role]);
+
+ return role_names[role];
+}
+
+/*<private>
+ * gtk_accessible_platform_changed:
+ * @self: a #GtkAccessible
+ * @change: the platform state change to report
+ *
+ * ARIA discriminates between author-controlled states
+ * and 'platform' states, which are not. This function
+ * can be used by widgets to inform ATs that a platform
+ * state, such as focus, has changed.
+ *
+ * Note that the state itself is not included in this API.
+ * AT backends should use gtk_accessible_get_platform_state()
+ * to obtain the actual state.
+ */
+void
+gtk_accessible_platform_changed (GtkAccessible *self,
+ GtkAccessiblePlatformChange change)
+{
+ GtkATContext *context;
+
+ if (GTK_IS_WIDGET (self) &&
+ gtk_widget_get_root (GTK_WIDGET (self)) == NULL)
+ return;
+
+ context = gtk_accessible_get_at_context (self);
+
+ /* propagate changes up from ignored widgets */
+ if (gtk_accessible_get_accessible_role (self) == GTK_ACCESSIBLE_ROLE_NONE)
+ {
+ context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (gtk_widget_get_parent (GTK_WIDGET (self))));
+ }
+
+ if (context == NULL)
+ return;
+
+ gtk_at_context_platform_changed (context, change);
+ gtk_at_context_update (context);
+}
+
+/*<private>
+ * gtk_accessible_get_platform_state:
+ * @self: a #GtkAccessible
+ * @state: platform state to query
+ *
+ * Query a platform state, such as focus.
+ *
+ * See gtk_accessible_platform_changed().
+ *
+ * This functionality can be overridden by #GtkAccessible
+ * implementations, e.g. to get platform state from an ignored
+ * child widget, as is the case for #GtkText wrappers.
+ *
+ * Returns: the value of @state for the accessible
+ */
+gboolean
+gtk_accessible_get_platform_state (GtkAccessible *self,
+ GtkAccessiblePlatformState state)
+{
+ return GTK_ACCESSIBLE_GET_IFACE (self)->get_platform_state (self, state);
+}
+
+/*<private>
+ * gtk_accessible_should_present:
+ * @self: a #GtkAccessible
+ *
+ * Returns whether this accessible should be represented to ATs.
+ *
+ * By default, hidden widgets are are among these, but there can
+ * be other reasons to return %FALSE, e.g. for widgets that are
+ * purely presentations, or for widgets whose functionality is
+ * represented elsewhere, as is the case for #GtkText widgets.
+ *
+ * Returns: %TRUE if the widget should be represented
+ */
+gboolean
+gtk_accessible_should_present (GtkAccessible *self)
+{
+ if (GTK_IS_WIDGET (self) &&
+ !gtk_widget_get_visible (GTK_WIDGET (self)))
+ return FALSE;
+
+ if (gtk_accessible_get_accessible_role (self) == GTK_ACCESSIBLE_ROLE_NONE)
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/gtk/gtkaccessibleattributesetprivate.h b/gtk/gtkaccessibleattributesetprivate.h
index 83694c7ede..bce94d6876 100644
--- a/gtk/gtkaccessibleattributesetprivate.h
+++ b/gtk/gtkaccessibleattributesetprivate.h
@@ -20,7 +20,7 @@
#pragma once
-#include "gtkaccessibleprivate.h"
+#include "gtkaccessible.h"
#include "gtkaccessiblevalueprivate.h"
G_BEGIN_DECLS
diff --git a/gtk/gtkaccessibleprivate.h b/gtk/gtkaccessibleprivate.h
index aa5701cf50..4d1f458fb8 100644
--- a/gtk/gtkaccessibleprivate.h
+++ b/gtk/gtkaccessibleprivate.h
@@ -21,7 +21,7 @@
#pragma once
#include "gtkaccessible.h"
-#include "gtkatcontext.h"
+#include "gtkatcontextprivate.h"
G_BEGIN_DECLS
@@ -30,8 +30,21 @@ struct _GtkAccessibleInterface
GTypeInterface g_iface;
GtkATContext * (* get_at_context) (GtkAccessible *self);
+
+ gboolean (* get_platform_state) (GtkAccessible *self,
+ GtkAccessiblePlatformState state);
};
GtkATContext * gtk_accessible_get_at_context (GtkAccessible *self);
+const char * gtk_accessible_role_to_name (GtkAccessibleRole role,
+ const char *domain);
+
+void gtk_accessible_platform_changed (GtkAccessible *self,
+ GtkAccessiblePlatformChange change);
+gboolean gtk_accessible_get_platform_state (GtkAccessible *self,
+ GtkAccessiblePlatformState state);
+
+gboolean gtk_accessible_should_present (GtkAccessible *self);
+
G_END_DECLS
diff --git a/gtk/gtkatcontext.c b/gtk/gtkatcontext.c
index 6686e3f9a9..de9dc79275 100644
--- a/gtk/gtkatcontext.c
+++ b/gtk/gtkatcontext.c
@@ -36,15 +36,22 @@
#include "gtkatcontextprivate.h"
#include "gtkaccessiblevalueprivate.h"
+#include "gtkaccessibleprivate.h"
+#include "gtkdebug.h"
#include "gtktestatcontextprivate.h"
#include "gtktypebuiltins.h"
+#if defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND)
+#include "a11y/gtkatspicontextprivate.h"
+#endif
+
G_DEFINE_ABSTRACT_TYPE (GtkATContext, gtk_at_context, G_TYPE_OBJECT)
enum
{
PROP_ACCESSIBLE_ROLE = 1,
PROP_ACCESSIBLE,
+ PROP_DISPLAY,
N_PROPS
};
@@ -90,6 +97,10 @@ gtk_at_context_set_property (GObject *gobject,
self->accessible = g_value_get_object (value);
break;
+ case PROP_DISPLAY:
+ self->display = g_value_get_object (value);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
@@ -113,6 +124,10 @@ gtk_at_context_get_property (GObject *gobject,
g_value_set_object (value, self->accessible);
break;
+ case PROP_DISPLAY:
+ g_value_set_object (value, self->display);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
@@ -123,6 +138,7 @@ gtk_at_context_real_state_change (GtkATContext *self,
GtkAccessibleStateChange changed_states,
GtkAccessiblePropertyChange changed_properties,
GtkAccessibleRelationChange changed_relations,
+ GtkAccessiblePlatformChange changed_platform,
GtkAccessibleAttributeSet *states,
GtkAccessibleAttributeSet *properties,
GtkAccessibleAttributeSet *relations)
@@ -173,6 +189,20 @@ gtk_at_context_class_init (GtkATContextClass *klass)
G_PARAM_STATIC_STRINGS);
/**
+ * GtkATContext:display:
+ *
+ * The #GdkDisplay for the #GtkATContext.
+ */
+ obj_props[PROP_DISPLAY] =
+ g_param_spec_object ("display",
+ "Display",
+ "The display connection",
+ GDK_TYPE_DISPLAY,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ /**
* GtkATContext::state-change:
* @self: the #GtkATContext
*
@@ -354,13 +384,45 @@ gtk_at_context_get_accessible_role (GtkATContext *self)
return self->accessible_role;
}
+/*< private >
+ * gtk_at_context_get_display:
+ * @self: a #GtkATContext
+ *
+ * Retrieves the #GdkDisplay used to create the context.
+ *
+ * Returns: (transfer none): a #GdkDisplay
+ */
+GdkDisplay *
+gtk_at_context_get_display (GtkATContext *self)
+{
+ g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
+
+ return self->display;
+}
+
+static const struct {
+ const char *name;
+ GtkATContext * (* create_context) (GtkAccessibleRole accessible_role,
+ GtkAccessible *accessible,
+ GdkDisplay *display);
+} a11y_backends[] = {
+#if defined(GDK_WINDOWING_WAYLAND)
+ { "AT-SPI (Wayland)", gtk_at_spi_create_context },
+#endif
+#if defined(GDK_WINDOWING_X11)
+ { "AT-SPI (X11)", gtk_at_spi_create_context },
+#endif
+ { NULL, NULL },
+};
+
/**
* gtk_at_context_create: (constructor)
* @accessible_role: the accessible role used by the #GtkATContext
* @accessible: the #GtkAccessible implementation using the #GtkATContext
+ * @display: the #GdkDisplay used by the #GtkATContext
*
- * Creates a new #GtkATContext instance for the given accessible role and
- * accessible instance.
+ * Creates a new #GtkATContext instance for the given accessible role,
+ * accessible instance, and display connection.
*
* The #GtkATContext implementation being instantiated will depend on the
* platform.
@@ -369,7 +431,8 @@ gtk_at_context_get_accessible_role (GtkATContext *self)
*/
GtkATContext *
gtk_at_context_create (GtkAccessibleRole accessible_role,
- GtkAccessible *accessible)
+ GtkAccessible *accessible,
+ GdkDisplay *display)
{
static const char *gtk_test_accessible;
static const char *gtk_no_a11y;
@@ -401,8 +464,31 @@ gtk_at_context_create (GtkAccessibleRole accessible_role,
if (gtk_no_a11y[0] == '1')
return NULL;
+ GtkATContext *res = NULL;
+
+ for (guint i = 0; i < G_N_ELEMENTS (a11y_backends); i++)
+ {
+ if (a11y_backends[i].name == NULL)
+ break;
+
+ if (a11y_backends[i].create_context != NULL)
+ {
+ res = a11y_backends[i].create_context (accessible_role, accessible, display);
+ if (res != NULL)
+ break;
+ }
+ }
+
+ /* Fall back to the test context, so we can get debugging data */
+ if (res == NULL)
+ res = g_object_new (GTK_TYPE_TEST_AT_CONTEXT,
+ "accessible_role", accessible_role,
+ "accessible", accessible,
+ "display", display,
+ NULL);
+
/* FIXME: Add GIOExtension for AT contexts */
- return gtk_test_at_context_new (accessible_role, accessible);
+ return res;
}
/*< private >
@@ -420,7 +506,8 @@ gtk_at_context_update (GtkATContext *self)
/* There's no point in notifying of state changes if there weren't any */
if (self->updated_properties == 0 &&
self->updated_relations == 0 &&
- self->updated_states == 0)
+ self->updated_states == 0 &&
+ self->updated_platform == 0)
return;
GtkAccessibleStateChange changed_states =
@@ -432,12 +519,14 @@ gtk_at_context_update (GtkATContext *self)
GTK_AT_CONTEXT_GET_CLASS (self)->state_change (self,
changed_states, changed_properties, changed_relations,
+ self->updated_platform,
self->states, self->properties, self->relations);
g_signal_emit (self, obj_signals[STATE_CHANGE], 0);
self->updated_properties = 0;
self->updated_relations = 0;
self->updated_states = 0;
+ self->updated_platform = 0;
}
/*< private >
@@ -640,3 +729,91 @@ gtk_at_context_get_accessible_relation (GtkATContext *self,
return gtk_accessible_attribute_set_get_value (self->relations, relation);
}
+
+/*< private >
+ * gtk_at_context_get_label:
+ * @self: a #GtkATContext
+ *
+ * Retrieves the accessible label of the #GtkATContext.
+ *
+ * This is a convenience function meant to be used by #GtkATContext implementations.
+ *
+ * Returns: (transfer full): the label of the #GtkATContext
+ */
+char *
+gtk_at_context_get_label (GtkATContext *self)
+{
+ g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
+
+ GtkAccessibleValue *value = NULL;
+
+ if (gtk_accessible_attribute_set_contains (self->states, GTK_ACCESSIBLE_STATE_HIDDEN))
+ {
+ value = gtk_accessible_attribute_set_get_value (self->states, GTK_ACCESSIBLE_STATE_HIDDEN);
+
+ if (gtk_boolean_accessible_value_get (value))
+ return g_strdup ("");
+ }
+
+ if (gtk_accessible_attribute_set_contains (self->properties, GTK_ACCESSIBLE_PROPERTY_LABEL))
+ {
+ value = gtk_accessible_attribute_set_get_value (self->properties, GTK_ACCESSIBLE_PROPERTY_LABEL);
+
+ return g_strdup (gtk_string_accessible_value_get (value));
+ }
+
+ if (gtk_accessible_attribute_set_contains (self->relations, GTK_ACCESSIBLE_RELATION_LABELLED_BY))
+ {
+ value = gtk_accessible_attribute_set_get_value (self->relations, GTK_ACCESSIBLE_RELATION_LABELLED_BY);
+
+ GList *list = gtk_reference_list_accessible_value_get (value);
+ GtkAccessible *rel = GTK_ACCESSIBLE (list->data);
+ GtkATContext *rel_context = gtk_accessible_get_at_context (rel);
+
+ return gtk_at_context_get_label (rel_context);
+ }
+
+ GtkAccessibleRole role = gtk_at_context_get_accessible_role (self);
+
+ switch ((int) role)
+ {
+ case GTK_ACCESSIBLE_ROLE_RANGE:
+ {
+ int range_attrs[] = {
+ GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT,
+ GTK_ACCESSIBLE_PROPERTY_VALUE_NOW,
+ };
+
+ for (int i = 0; i < G_N_ELEMENTS (range_attrs); i++)
+ {
+ if (gtk_accessible_attribute_set_contains (self->properties, range_attrs[i]))
+ {
+ value = gtk_accessible_attribute_set_get_value (self->properties, range_attrs[i]);
+ break;
+ }
+ }
+
+ if (value != NULL)
+ return g_strdup (gtk_string_accessible_value_get (value));
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ GEnumClass *enum_class = g_type_class_peek (GTK_TYPE_ACCESSIBLE_ROLE);
+ GEnumValue *enum_value = g_enum_get_value (enum_class, role);
+
+ if (enum_value != NULL)
+ return g_strdup (enum_value->value_nick);
+
+ return g_strdup ("widget");
+}
+
+void
+gtk_at_context_platform_changed (GtkATContext *self,
+ GtkAccessiblePlatformChange change)
+{
+ self->updated_platform |= change;
+}
diff --git a/gtk/gtkatcontext.h b/gtk/gtkatcontext.h
index e6b742ffb0..2372f04b94 100644
--- a/gtk/gtkatcontext.h
+++ b/gtk/gtkatcontext.h
@@ -42,6 +42,7 @@ GtkAccessibleRole gtk_at_context_get_accessible_role (GtkATContext
GDK_AVAILABLE_IN_ALL
GtkATContext * gtk_at_context_create (GtkAccessibleRole accessible_role,
- GtkAccessible *accessible);
+ GtkAccessible *accessible,
+ GdkDisplay *display);
G_END_DECLS
diff --git a/gtk/gtkatcontextprivate.h b/gtk/gtkatcontextprivate.h
index 2b916187fd..4fec1444c8 100644
--- a/gtk/gtkatcontextprivate.h
+++ b/gtk/gtkatcontextprivate.h
@@ -80,12 +80,23 @@ typedef enum {
GTK_ACCESSIBLE_STATE_CHANGE_SELECTED = 1 << GTK_ACCESSIBLE_STATE_SELECTED
} GtkAccessibleStateChange;
+typedef enum {
+ GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE,
+ GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED,
+} GtkAccessiblePlatformState;
+
+typedef enum {
+ GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSABLE = 1 << GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE,
+ GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSED = 1 << GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED,
+} GtkAccessiblePlatformChange;
+
struct _GtkATContext
{
GObject parent_instance;
GtkAccessibleRole accessible_role;
GtkAccessible *accessible;
+ GdkDisplay *display;
GtkAccessibleAttributeSet *states;
GtkAccessibleAttributeSet *properties;
@@ -94,6 +105,7 @@ struct _GtkATContext
GtkAccessibleStateChange updated_states;
GtkAccessiblePropertyChange updated_properties;
GtkAccessibleRelationChange updated_relations;
+ GtkAccessiblePlatformChange updated_platform;
};
struct _GtkATContextClass
@@ -104,11 +116,14 @@ struct _GtkATContextClass
GtkAccessibleStateChange changed_states,
GtkAccessiblePropertyChange changed_properties,
GtkAccessibleRelationChange changed_relations,
+ GtkAccessiblePlatformChange changed_platform,
GtkAccessibleAttributeSet *states,
GtkAccessibleAttributeSet *properties,
GtkAccessibleAttributeSet *relations);
};
+GdkDisplay * gtk_at_context_get_display (GtkATContext *self);
+
void gtk_at_context_update (GtkATContext *self);
void gtk_at_context_set_accessible_state (GtkATContext *self,
@@ -133,6 +148,11 @@ gboolean gtk_at_context_has_accessible_relation (GtkATContext
GtkAccessibleValue * gtk_at_context_get_accessible_relation (GtkATContext *self,
GtkAccessibleRelation relation);
+char * gtk_at_context_get_label (GtkATContext *self);
+
+void gtk_at_context_platform_changed (GtkATContext *self,
+ GtkAccessiblePlatformChange change);
+
const char * gtk_accessible_property_get_attribute_name (GtkAccessibleProperty property);
const char * gtk_accessible_relation_get_attribute_name (GtkAccessibleRelation relation);
const char * gtk_accessible_state_get_attribute_name (GtkAccessibleState state);
diff --git a/gtk/gtkcolumnview.c b/gtk/gtkcolumnview.c
index 0f1ca8fd42..d2b73fe105 100644
--- a/gtk/gtkcolumnview.c
+++ b/gtk/gtkcolumnview.c
@@ -30,7 +30,7 @@
#include "gtkcssnodeprivate.h"
#include "gtkdropcontrollermotion.h"
#include "gtkintl.h"
-#include "gtklistview.h"
+#include "gtklistviewprivate.h"
#include "gtkmain.h"
#include "gtkprivate.h"
#include "gtkscrollable.h"
@@ -106,6 +106,44 @@
* .rich-list, .navigation-sidebar or .data-table.
*/
+/* We create a subclass of GtkListView for the sole purpose of overriding
+ * some parameters for item creation.
+ */
+
+#define GTK_TYPE_COLUMN_LIST_VIEW (gtk_column_list_view_get_type ())
+G_DECLARE_FINAL_TYPE (GtkColumnListView, gtk_column_list_view, GTK, COLUMN_LIST_VIEW, GtkListView)
+
+struct _GtkColumnListView
+{
+ GtkListView parent_instance;
+};
+
+struct _GtkColumnListViewClass
+{
+ GtkListViewClass parent_class;
+};
+
+G_DEFINE_TYPE (GtkColumnListView, gtk_column_list_view, GTK_TYPE_LIST_VIEW)
+
+static void
+gtk_column_list_view_init (GtkColumnListView *view)
+{
+}
+
+static void
+gtk_column_list_view_class_init (GtkColumnListViewClass *klass)
+{
+ GtkListBaseClass *list_base_class = GTK_LIST_BASE_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ list_base_class->list_item_name = "row";
+ list_base_class->list_item_role = GTK_ACCESSIBLE_ROLE_ROW;
+
+ gtk_widget_class_set_css_name (widget_class, I_("listview"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST);
+}
+
+
struct _GtkColumnView
{
GtkWidget parent_instance;
@@ -733,6 +771,7 @@ gtk_column_view_class_init (GtkColumnViewClass *klass)
g_cclosure_marshal_VOID__UINTv);
gtk_widget_class_set_css_name (widget_class, I_("columnview"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TREE_GRID);
}
static void update_column_resize (GtkColumnView *self,
@@ -1136,7 +1175,7 @@ gtk_column_view_init (GtkColumnView *self)
self->columns = g_list_store_new (GTK_TYPE_COLUMN_VIEW_COLUMN);
- self->header = gtk_list_item_widget_new (NULL, "header");
+ self->header = gtk_list_item_widget_new (NULL, "header", GTK_ACCESSIBLE_ROLE_ROW);
gtk_widget_set_can_focus (self->header, FALSE);
gtk_widget_set_layout_manager (self->header, gtk_column_view_layout_new (self));
gtk_widget_set_parent (self->header, GTK_WIDGET (self));
@@ -1164,8 +1203,8 @@ gtk_column_view_init (GtkColumnView *self)
self->sorter = GTK_SORTER (gtk_column_view_sorter_new ());
self->factory = gtk_column_list_item_factory_new (self);
- self->listview = GTK_LIST_VIEW (gtk_list_view_new (NULL,
- GTK_LIST_ITEM_FACTORY (g_object_ref (self->factory))));
+ self->listview = GTK_LIST_VIEW (g_object_new (GTK_TYPE_COLUMN_LIST_VIEW, NULL));
+ gtk_list_view_set_factory (self->listview, GTK_LIST_ITEM_FACTORY (self->factory));
gtk_widget_set_hexpand (GTK_WIDGET (self->listview), TRUE);
gtk_widget_set_vexpand (GTK_WIDGET (self->listview), TRUE);
g_signal_connect (self->listview, "activate", G_CALLBACK (gtk_column_view_activate_cb), self);
diff --git a/gtk/gtkcolumnviewcell.c b/gtk/gtkcolumnviewcell.c
index 0b94fe7bcd..f7db70e04f 100644
--- a/gtk/gtkcolumnviewcell.c
+++ b/gtk/gtkcolumnviewcell.c
@@ -204,6 +204,7 @@ gtk_column_view_cell_class_init (GtkColumnViewCellClass *klass)
gobject_class->dispose = gtk_column_view_cell_dispose;
gtk_widget_class_set_css_name (widget_class, I_("cell"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID_CELL);
}
static void
diff --git a/gtk/gtkcolumnviewtitle.c b/gtk/gtkcolumnviewtitle.c
index 9291bd01b6..f2cb4fca83 100644
--- a/gtk/gtkcolumnviewtitle.c
+++ b/gtk/gtkcolumnviewtitle.c
@@ -171,6 +171,7 @@ gtk_column_view_title_class_init (GtkColumnViewTitleClass *klass)
gobject_class->dispose = gtk_column_view_title_dispose;
gtk_widget_class_set_css_name (widget_class, I_("button"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_COLUMN_HEADER);
}
static void
diff --git a/gtk/gtkcombobox.c b/gtk/gtkcombobox.c
index 8b6cf921bc..a8c839b170 100644
--- a/gtk/gtkcombobox.c
+++ b/gtk/gtkcombobox.c
@@ -103,6 +103,10 @@
* contains a box with the .linked class. That box contains an entry and a
* button, both with the .combo class added.
* The button also contains another node with name arrow.
+ *
+ * # Accessibility
+ *
+ * GtkComboBox uses the #GTK_ACCESSIBLE_ROLE_COMBO_BOX role.
*/
@@ -797,6 +801,7 @@ gtk_combo_box_class_init (GtkComboBoxClass *klass)
gtk_widget_class_bind_template_callback (widget_class, gtk_combo_box_menu_hide);
gtk_widget_class_set_css_name (widget_class, I_("combobox"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_COMBO_BOX);
}
static void
diff --git a/gtk/gtkdropdown.c b/gtk/gtkdropdown.c
index 2dd07ade52..80dc7a1ae6 100644
--- a/gtk/gtkdropdown.c
+++ b/gtk/gtkdropdown.c
@@ -71,6 +71,10 @@
*
* GtkDropDown has a single CSS node with name dropdown,
* with the button and popover nodes as children.
+ *
+ * # Accessibility
+ *
+ * GtkDropDown uses the #GTK_ACCESSIBLE_ROLE_COMBO_BOX role.
*/
struct _GtkDropDown
@@ -525,6 +529,7 @@ gtk_drop_down_class_init (GtkDropDownClass *klass)
gtk_widget_class_bind_template_callback (widget_class, search_stop);
gtk_widget_class_set_css_name (widget_class, I_("dropdown"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_COMBO_BOX);
}
static void
diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c
index 3d058bad27..532d3896fe 100644
--- a/gtk/gtkentry.c
+++ b/gtk/gtkentry.c
@@ -30,6 +30,7 @@
#include "gtkentryprivate.h"
+#include "gtkaccessibleprivate.h"
#include "gtkadjustment.h"
#include "gtkbox.h"
#include "gtkbutton.h"
@@ -320,9 +321,12 @@ static void gtk_entry_measure (GtkWidget *widget,
static GtkBuildableIface *buildable_parent_iface = NULL;
static void gtk_entry_buildable_interface_init (GtkBuildableIface *iface);
+static void gtk_entry_accessible_interface_init (GtkAccessibleInterface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkEntry, gtk_entry, GTK_TYPE_WIDGET,
G_ADD_PRIVATE (GtkEntry)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE,
+ gtk_entry_accessible_interface_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
gtk_entry_buildable_interface_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
@@ -330,6 +334,37 @@ G_DEFINE_TYPE_WITH_CODE (GtkEntry, gtk_entry, GTK_TYPE_WIDGET,
G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE,
gtk_entry_cell_editable_init))
+/* Implement the GtkAccessible interface, in order to obtain focus
+ * state from the #GtkText widget that we are wrapping. The GtkText
+ * widget is ignored for accessibility purposes (it has role NONE),
+ * and any a11y text functionality is implemented for GtkEntry and
+ * similar wrappers (GtkPasswordEntry, GtkSpinButton, etc).
+ */
+static gboolean
+gtk_entry_accessible_get_platform_state (GtkAccessible *self,
+ GtkAccessiblePlatformState state)
+{
+ GtkEntry *entry = GTK_ENTRY (self);
+ GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry);
+
+ switch (state)
+ {
+ case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE:
+ return gtk_widget_get_focusable (GTK_WIDGET (priv->text));
+ case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED:
+ return gtk_widget_has_focus (GTK_WIDGET (priv->text));
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gtk_entry_accessible_interface_init (GtkAccessibleInterface *iface)
+{
+ GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (iface);
+ iface->get_at_context = parent_iface->get_at_context;
+ iface->get_platform_state = gtk_entry_accessible_get_platform_state;
+}
static const GtkBuildableParser pango_parser =
{
diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h
index d1ca41cb1a..561a273b59 100644
--- a/gtk/gtkenums.h
+++ b/gtk/gtkenums.h
@@ -1161,7 +1161,7 @@ typedef enum {
* @GTK_ACCESSIBLE_ROLE_CELL: Unused
* @GTK_ACCESSIBLE_ROLE_CHECKBOX: A checkable input element that has
* three possible values: `true`, `false`, or `mixed`
- * @GTK_ACCESSIBLE_ROLE_COLUMN_HEADER: Unused
+ * @GTK_ACCESSIBLE_ROLE_COLUMN_HEADER: A header in a columned list.
* @GTK_ACCESSIBLE_ROLE_COMBO_BOX: An input that controls another element,
* such as a list or a grid, that can dynamically pop up to help the user
* set the value of the input
@@ -1175,8 +1175,8 @@ typedef enum {
* @GTK_ACCESSIBLE_ROLE_FEED: Unused
* @GTK_ACCESSIBLE_ROLE_FORM: Unused
* @GTK_ACCESSIBLE_ROLE_GENERIC: Unused
- * @GTK_ACCESSIBLE_ROLE_GRID: Unused
- * @GTK_ACCESSIBLE_ROLE_GRID_CELL: Unused
+ * @GTK_ACCESSIBLE_ROLE_GRID: A grid of items.
+ * @GTK_ACCESSIBLE_ROLE_GRID_CELL: An item in a grid or tree grid.
* @GTK_ACCESSIBLE_ROLE_GROUP: Unused
* @GTK_ACCESSIBLE_ROLE_HEADING: Unused
* @GTK_ACCESSIBLE_ROLE_IMG: An image.
@@ -1185,9 +1185,9 @@ typedef enum {
* @GTK_ACCESSIBLE_ROLE_LANDMARK: Unused
* @GTK_ACCESSIBLE_ROLE_LEGEND: Unused
* @GTK_ACCESSIBLE_ROLE_LINK: Unused
- * @GTK_ACCESSIBLE_ROLE_LIST: Unused
- * @GTK_ACCESSIBLE_ROLE_LIST_BOX: Unused
- * @GTK_ACCESSIBLE_ROLE_LIST_ITEM: Unused
+ * @GTK_ACCESSIBLE_ROLE_LIST: A list of items.
+ * @GTK_ACCESSIBLE_ROLE_LIST_BOX: Unused.
+ * @GTK_ACCESSIBLE_ROLE_LIST_ITEM: An item in a list.
* @GTK_ACCESSIBLE_ROLE_LOG: Unused
* @GTK_ACCESSIBLE_ROLE_MAIN: Unused
* @GTK_ACCESSIBLE_ROLE_MARQUEE: Unused
@@ -1210,7 +1210,7 @@ typedef enum {
* @GTK_ACCESSIBLE_ROLE_RADIO_GROUP: Unused
* @GTK_ACCESSIBLE_ROLE_RANGE: Unused
* @GTK_ACCESSIBLE_ROLE_REGION: Unused
- * @GTK_ACCESSIBLE_ROLE_ROW: Unused
+ * @GTK_ACCESSIBLE_ROLE_ROW: A row in a columned list.
* @GTK_ACCESSIBLE_ROLE_ROW_GROUP: Unused
* @GTK_ACCESSIBLE_ROLE_ROW_HEADER: Unused
* @GTK_ACCESSIBLE_ROLE_SCROLLBAR: A graphical object that controls the scrolling
@@ -1232,10 +1232,10 @@ typedef enum {
* @GTK_ACCESSIBLE_ROLE_STRUCTURE: Unused
* @GTK_ACCESSIBLE_ROLE_SWITCH: A type of checkbox that represents on/off values,
* as opposed to checked/unchecked values.
- * @GTK_ACCESSIBLE_ROLE_TAB: Unused
+ * @GTK_ACCESSIBLE_ROLE_TAB: An item in a list of tab used for switching pages.
* @GTK_ACCESSIBLE_ROLE_TABLE: Unused
- * @GTK_ACCESSIBLE_ROLE_TAB_LIST: Unused
- * @GTK_ACCESSIBLE_ROLE_TAB_PANEL: Unused
+ * @GTK_ACCESSIBLE_ROLE_TAB_LIST: A list of tabs for switching pages.
+ * @GTK_ACCESSIBLE_ROLE_TAB_PANEL: A page in a notebook or stack.
* @GTK_ACCESSIBLE_ROLE_TEXT_BOX: A type of input that allows free-form text
* as its value.
* @GTK_ACCESSIBLE_ROLE_TIME: Unused
@@ -1243,7 +1243,7 @@ typedef enum {
* @GTK_ACCESSIBLE_ROLE_TOOLBAR: Unused
* @GTK_ACCESSIBLE_ROLE_TOOLTIP: Unused
* @GTK_ACCESSIBLE_ROLE_TREE: Unused
- * @GTK_ACCESSIBLE_ROLE_TREE_GRID: Unused
+ * @GTK_ACCESSIBLE_ROLE_TREE_GRID: A treeview-like, columned list.
* @GTK_ACCESSIBLE_ROLE_TREE_ITEM: Unused
* @GTK_ACCESSIBLE_ROLE_WIDGET: An interactive component of a graphical user
* interface. This is the role that GTK uses by default for widgets.
diff --git a/gtk/gtkflowbox.c b/gtk/gtkflowbox.c
index 7a306a8dc3..8d01400835 100644
--- a/gtk/gtkflowbox.c
+++ b/gtk/gtkflowbox.c
@@ -75,6 +75,7 @@
#include "gtkflowboxprivate.h"
+#include "gtkaccessible.h"
#include "gtkadjustment.h"
#include "gtkbinlayout.h"
#include "gtkbuildable.h"
@@ -483,6 +484,18 @@ gtk_flow_box_child_compute_expand (GtkWidget *widget,
}
}
+static void
+gtk_flow_box_child_root (GtkWidget *widget)
+{
+ GtkFlowBoxChild *child = GTK_FLOW_BOX_CHILD (widget);
+
+ GTK_WIDGET_CLASS (gtk_flow_box_child_parent_class)->root (widget);
+
+ gtk_accessible_update_state (GTK_ACCESSIBLE (child),
+ GTK_ACCESSIBLE_STATE_SELECTED, CHILD_PRIV (child)->selected,
+ -1);
+}
+
/* GObject implementation {{{2 */
static void
@@ -495,6 +508,7 @@ gtk_flow_box_child_class_init (GtkFlowBoxChildClass *class)
object_class->get_property = gtk_flow_box_child_get_property;
object_class->set_property = gtk_flow_box_child_set_property;
+ widget_class->root = gtk_flow_box_child_root;
widget_class->get_request_mode = gtk_flow_box_child_get_request_mode;
widget_class->compute_expand = gtk_flow_box_child_compute_expand;
widget_class->focus = gtk_flow_box_child_focus;
@@ -533,6 +547,7 @@ gtk_flow_box_child_class_init (GtkFlowBoxChildClass *class)
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
gtk_widget_class_set_css_name (widget_class, I_("flowboxchild"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID_CELL);
}
static void
@@ -905,6 +920,9 @@ gtk_flow_box_child_set_selected (GtkFlowBoxChild *child,
gtk_widget_unset_state_flags (GTK_WIDGET (child),
GTK_STATE_FLAG_SELECTED);
+ gtk_accessible_update_state (GTK_ACCESSIBLE (child),
+ GTK_ACCESSIBLE_STATE_SELECTED, selected,
+ -1);
return TRUE;
}
@@ -3870,6 +3888,7 @@ gtk_flow_box_class_init (GtkFlowBoxClass *class)
NULL);
gtk_widget_class_set_css_name (widget_class, I_("flowbox"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID);
}
static void
@@ -4724,6 +4743,10 @@ gtk_flow_box_set_selection_mode (GtkFlowBox *box,
BOX_PRIV (box)->selection_mode = mode;
+ gtk_accessible_update_property (GTK_ACCESSIBLE (box),
+ GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, mode == GTK_SELECTION_MULTIPLE,
+ -1);
+
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_SELECTION_MODE]);
if (dirty)
diff --git a/gtk/gtkgizmo.c b/gtk/gtkgizmo.c
index a929248a5c..00162645b3 100644
--- a/gtk/gtkgizmo.c
+++ b/gtk/gtkgizmo.c
@@ -131,8 +131,29 @@ gtk_gizmo_new (const char *css_name,
GtkGizmoFocusFunc focus_func,
GtkGizmoGrabFocusFunc grab_focus_func)
{
+ return gtk_gizmo_new_with_role (css_name,
+ GTK_ACCESSIBLE_ROLE_WIDGET,
+ measure_func,
+ allocate_func,
+ snapshot_func,
+ contains_func,
+ focus_func,
+ grab_focus_func);
+}
+
+GtkWidget *
+gtk_gizmo_new_with_role (const char *css_name,
+ GtkAccessibleRole role,
+ GtkGizmoMeasureFunc measure_func,
+ GtkGizmoAllocateFunc allocate_func,
+ GtkGizmoSnapshotFunc snapshot_func,
+ GtkGizmoContainsFunc contains_func,
+ GtkGizmoFocusFunc focus_func,
+ GtkGizmoGrabFocusFunc grab_focus_func)
+{
GtkGizmo *gizmo = GTK_GIZMO (g_object_new (GTK_TYPE_GIZMO,
"css-name", css_name,
+ "accessible-role", role,
NULL));
gizmo->measure_func = measure_func;
diff --git a/gtk/gtkgizmoprivate.h b/gtk/gtkgizmoprivate.h
index 478af19971..9257201a59 100644
--- a/gtk/gtkgizmoprivate.h
+++ b/gtk/gtkgizmoprivate.h
@@ -3,6 +3,7 @@
#define __GTK_GIZMO_H__
#include "gtkwidget.h"
+#include "gtkenums.h"
#define GTK_TYPE_GIZMO (gtk_gizmo_get_type ())
#define GTK_GIZMO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_GIZMO, GtkGizmo))
@@ -61,5 +62,14 @@ GtkWidget *gtk_gizmo_new (const char *css_name,
GtkGizmoFocusFunc focus_func,
GtkGizmoGrabFocusFunc grab_focus_func);
+GtkWidget *gtk_gizmo_new_with_role (const char *css_name,
+ GtkAccessibleRole role,
+ GtkGizmoMeasureFunc measure_func,
+ GtkGizmoAllocateFunc allocate_func,
+ GtkGizmoSnapshotFunc snapshot_func,
+ GtkGizmoContainsFunc contains_func,
+ GtkGizmoFocusFunc focus_func,
+ GtkGizmoGrabFocusFunc grab_focus_func);
+
#endif
diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c
index ba8ce422af..8c24afc9e1 100644
--- a/gtk/gtkgridview.c
+++ b/gtk/gtkgridview.c
@@ -30,6 +30,7 @@
#include "gtkprivate.h"
#include "gtksingleselection.h"
#include "gtkwidgetprivate.h"
+#include "gtkmultiselection.h"
/* Maximum number of list items created by the gridview.
* For debugging, you can set this to G_MAXUINT to ensure
@@ -1025,6 +1026,7 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
list_base_class->list_item_name = "child";
+ list_base_class->list_item_role = GTK_ACCESSIBLE_ROLE_GRID_CELL;
list_base_class->list_item_size = sizeof (Cell);
list_base_class->list_item_augment_size = sizeof (CellAugment);
list_base_class->list_item_augment_func = cell_augment;
@@ -1157,6 +1159,7 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
gtk_grid_view_activate_item);
gtk_widget_class_set_css_name (widget_class, I_("gridview"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID);
}
static void
@@ -1246,6 +1249,10 @@ gtk_grid_view_set_model (GtkGridView *self,
if (!gtk_list_base_set_model (GTK_LIST_BASE (self), model))
return;
+ gtk_accessible_update_property (GTK_ACCESSIBLE (self),
+ GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, GTK_IS_MULTI_SELECTION (model),
+ -1);
+
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c
index 201bcb0844..ed4f84ad2a 100644
--- a/gtk/gtklistbase.c
+++ b/gtk/gtklistbase.c
@@ -1794,6 +1794,7 @@ gtk_list_base_init_real (GtkListBase *self,
priv->item_manager = gtk_list_item_manager_new_for_size (GTK_WIDGET (self),
g_class->list_item_name,
+ g_class->list_item_role,
g_class->list_item_size,
g_class->list_item_augment_size,
g_class->list_item_augment_func);
diff --git a/gtk/gtklistbaseprivate.h b/gtk/gtklistbaseprivate.h
index fe640978b2..75d7466784 100644
--- a/gtk/gtklistbaseprivate.h
+++ b/gtk/gtklistbaseprivate.h
@@ -35,6 +35,7 @@ struct _GtkListBaseClass
GtkWidgetClass parent_class;
const char * list_item_name;
+ GtkAccessibleRole list_item_role;
gsize list_item_size;
gsize list_item_augment_size;
GtkRbTreeAugmentFunc list_item_augment_func;
diff --git a/gtk/gtklistbox.c b/gtk/gtklistbox.c
index c0028d83fd..189a68ea79 100644
--- a/gtk/gtklistbox.c
+++ b/gtk/gtklistbox.c
@@ -19,6 +19,7 @@
#include "gtklistbox.h"
+#include "gtkaccessible.h"
#include "gtkactionhelperprivate.h"
#include "gtkadjustmentprivate.h"
#include "gtkbinlayout.h"
@@ -678,6 +679,7 @@ gtk_list_box_class_init (GtkListBoxClass *klass)
NULL);
gtk_widget_class_set_css_name (widget_class, I_("list"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST);
}
static void
@@ -1119,6 +1121,10 @@ gtk_list_box_set_selection_mode (GtkListBox *box,
gtk_list_box_update_row_styles (box);
+ gtk_accessible_update_property (GTK_ACCESSIBLE (box),
+ GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, mode == GTK_SELECTION_MULTIPLE,
+ -1);
+
g_object_notify_by_pspec (G_OBJECT (box), properties[PROP_SELECTION_MODE]);
if (dirty)
@@ -1558,6 +1564,10 @@ gtk_list_box_row_set_selected (GtkListBoxRow *row,
gtk_widget_unset_state_flags (GTK_WIDGET (row),
GTK_STATE_FLAG_SELECTED);
+ gtk_accessible_update_state (GTK_ACCESSIBLE (row),
+ GTK_ACCESSIBLE_STATE_SELECTED, selected,
+ -1);
+
return TRUE;
}
@@ -3019,6 +3029,19 @@ gtk_list_box_row_hide (GtkWidget *widget)
gtk_list_box_row_visibility_changed (box, row);
}
+static void
+gtk_list_box_row_root (GtkWidget *widget)
+{
+ GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget);
+
+ GTK_WIDGET_CLASS (gtk_list_box_row_parent_class)->root (widget);
+
+ if (ROW_PRIV (row)->selectable)
+ gtk_accessible_update_state (GTK_ACCESSIBLE (row),
+ GTK_ACCESSIBLE_STATE_SELECTED, ROW_PRIV (row)->selected,
+ -1);
+}
+
/**
* gtk_list_box_row_changed:
* @row: a #GtkListBoxRow
@@ -3228,10 +3251,19 @@ gtk_list_box_row_set_selectable (GtkListBoxRow *row,
{
if (!selectable)
gtk_list_box_row_set_selected (row, FALSE);
-
+
ROW_PRIV (row)->selectable = selectable;
+ if (selectable)
+ gtk_accessible_update_state (GTK_ACCESSIBLE (row),
+ GTK_ACCESSIBLE_STATE_SELECTED, FALSE,
+ -1);
+ else
+ gtk_accessible_reset_state (GTK_ACCESSIBLE (row),
+ GTK_ACCESSIBLE_STATE_SELECTED);
+
gtk_list_box_update_row_style (gtk_list_box_row_get_box (row), row);
+
g_object_notify_by_pspec (G_OBJECT (row), row_properties[ROW_PROP_SELECTABLE]);
}
}
@@ -3413,6 +3445,7 @@ gtk_list_box_row_class_init (GtkListBoxRowClass *klass)
object_class->finalize = gtk_list_box_row_finalize;
object_class->dispose = gtk_list_box_row_dispose;
+ widget_class->root = gtk_list_box_row_root;
widget_class->show = gtk_list_box_row_show;
widget_class->hide = gtk_list_box_row_hide;
widget_class->focus = gtk_list_box_row_focus;
@@ -3478,6 +3511,7 @@ gtk_list_box_row_class_init (GtkListBoxRowClass *klass)
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
gtk_widget_class_set_css_name (widget_class, I_("row"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST_ITEM);
}
static void
diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c
index 7cad397ffa..c795653004 100644
--- a/gtk/gtklistitemmanager.c
+++ b/gtk/gtklistitemmanager.c
@@ -35,6 +35,7 @@ struct _GtkListItemManager
GtkListItemFactory *factory;
gboolean single_click_activate;
const char *item_css_name;
+ GtkAccessibleRole item_role;
GtkRbTree *items;
GSList *trackers;
@@ -111,6 +112,7 @@ gtk_list_item_manager_clear_node (gpointer _item)
GtkListItemManager *
gtk_list_item_manager_new_for_size (GtkWidget *widget,
const char *item_css_name,
+ GtkAccessibleRole item_role,
gsize element_size,
gsize augment_size,
GtkRbTreeAugmentFunc augment_func)
@@ -126,6 +128,7 @@ gtk_list_item_manager_new_for_size (GtkWidget *widget,
/* not taking a ref because the widget refs us */
self->widget = widget;
self->item_css_name = g_intern_string (item_css_name);
+ self->item_role = item_role;
self->items = gtk_rb_tree_new_for_size (element_size,
augment_size,
@@ -923,7 +926,8 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL);
result = gtk_list_item_widget_new (self->factory,
- self->item_css_name);
+ self->item_css_name,
+ self->item_role);
gtk_list_item_widget_set_single_click_activate (GTK_LIST_ITEM_WIDGET (result), self->single_click_activate);
diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h
index baf4c51442..d2b4bbc433 100644
--- a/gtk/gtklistitemmanagerprivate.h
+++ b/gtk/gtklistitemmanagerprivate.h
@@ -22,6 +22,7 @@
#define __GTK_LIST_ITEM_MANAGER_H__
#include "gtk/gtktypes.h"
+#include "gtk/gtkenums.h"
#include "gtk/gtklistitemfactory.h"
#include "gtk/gtkrbtreeprivate.h"
@@ -58,6 +59,7 @@ GType gtk_list_item_manager_get_type (void) G_GNUC_CO
GtkListItemManager * gtk_list_item_manager_new_for_size (GtkWidget *widget,
const char *item_css_name,
+ GtkAccessibleRole item_role,
gsize element_size,
gsize augment_size,
GtkRbTreeAugmentFunc augment_func);
diff --git a/gtk/gtklistitemwidget.c b/gtk/gtklistitemwidget.c
index ce4f505039..8f6a5c5633 100644
--- a/gtk/gtklistitemwidget.c
+++ b/gtk/gtklistitemwidget.c
@@ -453,12 +453,14 @@ gtk_list_item_widget_init (GtkListItemWidget *self)
GtkWidget *
gtk_list_item_widget_new (GtkListItemFactory *factory,
- const char *css_name)
+ const char *css_name,
+ GtkAccessibleRole role)
{
g_return_val_if_fail (css_name != NULL, NULL);
return g_object_new (GTK_TYPE_LIST_ITEM_WIDGET,
"css-name", css_name,
+ "accessible-role", role,
"factory", factory,
NULL);
}
@@ -480,6 +482,10 @@ gtk_list_item_widget_update (GtkListItemWidget *self,
gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE);
else
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED);
+
+ gtk_accessible_update_state (GTK_ACCESSIBLE (self),
+ GTK_ACCESSIBLE_STATE_SELECTED, selected,
+ -1);
}
void
diff --git a/gtk/gtklistitemwidgetprivate.h b/gtk/gtklistitemwidgetprivate.h
index 3042d08789..8f78b1a7a2 100644
--- a/gtk/gtklistitemwidgetprivate.h
+++ b/gtk/gtklistitemwidgetprivate.h
@@ -50,7 +50,8 @@ struct _GtkListItemWidgetClass
GType gtk_list_item_widget_get_type (void) G_GNUC_CONST;
GtkWidget * gtk_list_item_widget_new (GtkListItemFactory *factory,
- const char *css_name);
+ const char *css_name,
+ GtkAccessibleRole role);
void gtk_list_item_widget_update (GtkListItemWidget *self,
guint position,
diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
index 5382c7b1ad..ad8f8ea16c 100644
--- a/gtk/gtklistview.c
+++ b/gtk/gtklistview.c
@@ -19,7 +19,7 @@
#include "config.h"
-#include "gtklistview.h"
+#include "gtklistviewprivate.h"
#include "gtkbitset.h"
#include "gtkintl.h"
@@ -29,6 +29,7 @@
#include "gtkprivate.h"
#include "gtkrbtreeprivate.h"
#include "gtkwidgetprivate.h"
+#include "gtkmultiselection.h"
/* Maximum number of list items created by the listview.
* For debugging, you can set this to G_MAXUINT to ensure
@@ -139,21 +140,6 @@
typedef struct _ListRow ListRow;
typedef struct _ListRowAugment ListRowAugment;
-struct _GtkListView
-{
- GtkListBase parent_instance;
-
- GtkListItemManager *item_manager;
- gboolean show_separators;
-
- int list_width;
-};
-
-struct _GtkListViewClass
-{
- GtkListBaseClass parent_class;
-};
-
struct _ListRow
{
GtkListItemManagerItem parent;
@@ -799,6 +785,7 @@ gtk_list_view_class_init (GtkListViewClass *klass)
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
list_base_class->list_item_name = "row";
+ list_base_class->list_item_role = GTK_ACCESSIBLE_ROLE_LIST_ITEM;
list_base_class->list_item_size = sizeof (ListRow);
list_base_class->list_item_augment_size = sizeof (ListRowAugment);
list_base_class->list_item_augment_func = list_row_augment;
@@ -915,6 +902,7 @@ gtk_list_view_class_init (GtkListViewClass *klass)
gtk_list_view_activate_item);
gtk_widget_class_set_css_name (widget_class, I_("listview"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST);
}
static void
@@ -1000,6 +988,10 @@ gtk_list_view_set_model (GtkListView *self,
if (!gtk_list_base_set_model (GTK_LIST_BASE (self), model))
return;
+ gtk_accessible_update_property (GTK_ACCESSIBLE (self),
+ GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, GTK_IS_MULTI_SELECTION (model),
+ -1);
+
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
diff --git a/gtk/gtklistview.h b/gtk/gtklistview.h
index 9a258de1da..ee2f70c0c4 100644
--- a/gtk/gtklistview.h
+++ b/gtk/gtklistview.h
@@ -81,6 +81,8 @@ void gtk_list_view_set_enable_rubberband (GtkListView
GDK_AVAILABLE_IN_ALL
gboolean gtk_list_view_get_enable_rubberband (GtkListView *self);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkListView, g_object_unref)
+
G_END_DECLS
#endif /* __GTK_LIST_VIEW_H__ */
diff --git a/gtk/gtklistviewprivate.h b/gtk/gtklistviewprivate.h
new file mode 100644
index 0000000000..007e75661a
--- /dev/null
+++ b/gtk/gtklistviewprivate.h
@@ -0,0 +1,45 @@
+/*
+ * 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.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/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#ifndef __GTK_LIST_VIEW_PRIVATE_H__
+#define __GTK_LIST_VIEW_PRIVATE_H__
+
+#include <gtk/gtklistview.h>
+#include <gtk/gtklistbaseprivate.h>
+
+G_BEGIN_DECLS
+
+struct _GtkListView
+{
+ GtkListBase parent_instance;
+
+ GtkListItemManager *item_manager;
+ gboolean show_separators;
+
+ int list_width;
+};
+
+struct _GtkListViewClass
+{
+ GtkListBaseClass parent_class;
+};
+
+G_END_DECLS
+
+#endif /* __GTK_LIST_VIEW_H__ */
diff --git a/gtk/gtknotebook.c b/gtk/gtknotebook.c
index cdf873861f..81346060c1 100644
--- a/gtk/gtknotebook.c
+++ b/gtk/gtknotebook.c
@@ -144,6 +144,12 @@
* A tab node gets the .dnd style class while it is moved with drag-and-drop.
*
* The nodes are always arranged from left-to-right, regardless of text direction.
+ *
+ * # Accessibility
+ *
+ * GtkNotebook uses the #GTK_ACCESSIBLE_ROLE_TAB_LIST and
+ * #GTK_ACCESSIBLE_ROLE_TAB roles for its list of tabs and the
+ * #GTK_ACCESSIBLE_ROLE_TAB_PANEL for the pages.
*/
@@ -1404,13 +1410,14 @@ gtk_notebook_init (GtkNotebook *notebook)
gtk_widget_hide (notebook->header_widget);
gtk_widget_set_parent (notebook->header_widget, GTK_WIDGET (notebook));
- notebook->tabs_widget = gtk_gizmo_new ("tabs",
- gtk_notebook_measure_tabs,
- gtk_notebook_allocate_tabs,
- gtk_notebook_snapshot_tabs,
- NULL,
- (GtkGizmoFocusFunc)gtk_widget_focus_self,
- (GtkGizmoGrabFocusFunc)gtk_widget_grab_focus_self);
+ notebook->tabs_widget = gtk_gizmo_new_with_role ("tabs",
+ GTK_ACCESSIBLE_ROLE_TAB_LIST,
+ gtk_notebook_measure_tabs,
+ gtk_notebook_allocate_tabs,
+ gtk_notebook_snapshot_tabs,
+ NULL,
+ (GtkGizmoFocusFunc)gtk_widget_focus_self,
+ (GtkGizmoGrabFocusFunc)gtk_widget_grab_focus_self);
gtk_widget_set_hexpand (notebook->tabs_widget, TRUE);
gtk_box_append (GTK_BOX (notebook->header_widget), notebook->tabs_widget);
@@ -3915,6 +3922,7 @@ gtk_notebook_insert_notebook_page (GtkNotebook *notebook,
GList *list;
GtkWidget *sibling;
GtkEventController *controller;
+ GtkStackPage *stack_page;
nchildren = g_list_length (notebook->children);
if ((position < 0) || (position > nchildren))
@@ -3929,7 +3937,14 @@ gtk_notebook_insert_notebook_page (GtkNotebook *notebook,
else
sibling = notebook->arrow_widget[ARROW_RIGHT_AFTER];
- page->tab_widget = gtk_gizmo_new ("tab", measure_tab, allocate_tab, NULL, NULL, NULL, NULL);
+ page->tab_widget = gtk_gizmo_new_with_role ("tab",
+ GTK_ACCESSIBLE_ROLE_TAB,
+ measure_tab,
+ allocate_tab,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
g_object_set_data (G_OBJECT (page->tab_widget), "notebook", notebook);
gtk_widget_insert_before (page->tab_widget, notebook->tabs_widget, sibling);
controller = gtk_drop_controller_motion_new ();
@@ -3951,6 +3966,15 @@ gtk_notebook_insert_notebook_page (GtkNotebook *notebook,
g_object_set_data (G_OBJECT (page->tab_label), "notebook", notebook);
}
+ stack_page = gtk_stack_get_page (GTK_STACK (notebook->stack_widget), page->child);
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (page->tab_widget),
+ GTK_ACCESSIBLE_RELATION_CONTROLS, g_list_append (NULL, stack_page),
+ -1);
+
+ gtk_accessible_update_state (GTK_ACCESSIBLE (page->tab_widget),
+ GTK_ACCESSIBLE_STATE_SELECTED, FALSE,
+ -1);
+
gtk_notebook_update_labels (notebook);
if (!notebook->first_tab)
@@ -5303,12 +5327,20 @@ gtk_notebook_real_switch_page (GtkNotebook *notebook,
if (focus)
child_has_focus = gtk_widget_is_ancestor (focus, notebook->cur_page->child);
gtk_widget_unset_state_flags (notebook->cur_page->tab_widget, GTK_STATE_FLAG_CHECKED);
+
+ gtk_accessible_update_state (GTK_ACCESSIBLE (notebook->cur_page->tab_widget),
+ GTK_ACCESSIBLE_STATE_SELECTED, FALSE,
+ -1);
}
notebook->cur_page = page;
gtk_widget_set_state_flags (page->tab_widget, GTK_STATE_FLAG_CHECKED, FALSE);
gtk_widget_set_visible (notebook->header_widget, notebook->show_tabs);
+ gtk_accessible_update_state (GTK_ACCESSIBLE (notebook->cur_page->tab_widget),
+ GTK_ACCESSIBLE_STATE_SELECTED, TRUE,
+ -1);
+
if (!notebook->focus_tab ||
notebook->focus_tab->data != (gpointer) notebook->cur_page)
notebook->focus_tab =
diff --git a/gtk/gtkpaned.c b/gtk/gtkpaned.c
index 8ac244d483..929779a39a 100644
--- a/gtk/gtkpaned.c
+++ b/gtk/gtkpaned.c
@@ -1385,6 +1385,14 @@ gtk_paned_size_allocate (GtkWidget *widget,
gtk_widget_set_child_visible (paned->handle_widget, FALSE);
}
+
+ gtk_accessible_update_property (GTK_ACCESSIBLE (paned),
+ GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 0.0,
+ GTK_ACCESSIBLE_PROPERTY_VALUE_MAX,
+ (double) (paned->orientation == GTK_ORIENTATION_HORIZONTAL ? width : height),
+ GTK_ACCESSIBLE_PROPERTY_VALUE_NOW,
+ (double) paned->start_child_size,
+ -1);
}
diff --git a/gtk/gtkpasswordentry.c b/gtk/gtkpasswordentry.c
index d8da5b4e56..8d5de422b2 100644
--- a/gtk/gtkpasswordentry.c
+++ b/gtk/gtkpasswordentry.c
@@ -22,6 +22,7 @@
#include "gtkpasswordentryprivate.h"
+#include "gtkaccessibleprivate.h"
#include "gtktextprivate.h"
#include "gtkeditable.h"
#include "gtkeventcontrollerkey.h"
@@ -104,8 +105,10 @@ enum {
static GParamSpec *props[NUM_PROPERTIES] = { NULL, };
static void gtk_password_entry_editable_init (GtkEditableInterface *iface);
+static void gtk_password_entry_accessible_init (GtkAccessibleInterface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkPasswordEntry, gtk_password_entry, GTK_TYPE_WIDGET,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE, gtk_password_entry_accessible_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_password_entry_editable_init))
static void
@@ -486,6 +489,31 @@ gtk_password_entry_editable_init (GtkEditableInterface *iface)
iface->get_delegate = gtk_password_entry_get_delegate;
}
+static gboolean
+gtk_password_entry_accessible_get_platform_state (GtkAccessible *self,
+ GtkAccessiblePlatformState state)
+{
+ GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (self);
+
+ switch (state)
+ {
+ case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE:
+ return gtk_widget_get_focusable (GTK_WIDGET (entry->entry));
+ case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED:
+ return gtk_widget_has_focus (GTK_WIDGET (entry->entry));
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gtk_password_entry_accessible_init (GtkAccessibleInterface *iface)
+{
+ GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (iface);
+ iface->get_at_context = parent_iface->get_at_context;
+ iface->get_platform_state = gtk_password_entry_accessible_get_platform_state;
+}
+
GtkText *
gtk_password_entry_get_text_widget (GtkPasswordEntry *entry)
{
diff --git a/gtk/gtkscalebutton.c b/gtk/gtkscalebutton.c
index 60e28c52b1..a6d78cbdb5 100644
--- a/gtk/gtkscalebutton.c
+++ b/gtk/gtkscalebutton.c
@@ -414,6 +414,12 @@ gtk_scale_button_init (GtkScaleButton *button)
g_object_ref_sink (priv->adjustment);
gtk_range_set_adjustment (GTK_RANGE (priv->scale), priv->adjustment);
+ gtk_accessible_update_property (GTK_ACCESSIBLE (button),
+ GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (priv->adjustment),
+ GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (priv->adjustment),
+ GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (priv->adjustment),
+ -1);
+
gtk_widget_add_css_class (GTK_WIDGET (button), "scale");
controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
@@ -677,6 +683,13 @@ gtk_scale_button_set_adjustment (GtkScaleButton *button,
gtk_range_set_adjustment (GTK_RANGE (priv->scale), adjustment);
g_object_notify (G_OBJECT (button), "adjustment");
+
+ gtk_accessible_update_property (GTK_ACCESSIBLE (button),
+ GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (adjustment),
+ GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (adjustment),
+ GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment),
+ -1);
+
}
}
@@ -939,6 +952,10 @@ cb_scale_value_changed (GtkRange *range,
g_signal_emit (button, signals[VALUE_CHANGED], 0, value);
g_object_notify (G_OBJECT (button), "value");
+
+ gtk_accessible_update_property (GTK_ACCESSIBLE (button),
+ GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, value,
+ -1);
}
static void
diff --git a/gtk/gtksearchentry.c b/gtk/gtksearchentry.c
index 4960c98941..78268113c8 100644
--- a/gtk/gtksearchentry.c
+++ b/gtk/gtksearchentry.c
@@ -29,6 +29,7 @@
#include "gtksearchentryprivate.h"
+#include "gtkaccessibleprivate.h"
#include "gtkeditable.h"
#include "gtkboxlayout.h"
#include "gtkgestureclick.h"
@@ -135,8 +136,11 @@ struct _GtkSearchEntryClass
};
static void gtk_search_entry_editable_init (GtkEditableInterface *iface);
+static void gtk_search_entry_accessible_init (GtkAccessibleInterface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkSearchEntry, gtk_search_entry, GTK_TYPE_WIDGET,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE,
+ gtk_search_entry_accessible_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
gtk_search_entry_editable_init))
@@ -438,6 +442,31 @@ gtk_search_entry_editable_init (GtkEditableInterface *iface)
iface->get_delegate = gtk_search_entry_get_delegate;
}
+static gboolean
+gtk_search_entry_accessible_get_platform_state (GtkAccessible *self,
+ GtkAccessiblePlatformState state)
+{
+ GtkSearchEntry *entry = GTK_SEARCH_ENTRY (self);
+
+ switch (state)
+ {
+ case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE:
+ return gtk_widget_get_focusable (GTK_WIDGET (entry->entry));
+ case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED:
+ return gtk_widget_has_focus (GTK_WIDGET (entry->entry));
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gtk_search_entry_accessible_init (GtkAccessibleInterface *iface)
+{
+ GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (iface);
+ iface->get_at_context = parent_iface->get_at_context;
+ iface->get_platform_state = gtk_search_entry_accessible_get_platform_state;
+}
+
static void
gtk_search_entry_icon_release (GtkGestureClick *press,
int n_press,
diff --git a/gtk/gtkspinbutton.c b/gtk/gtkspinbutton.c
index b0cbfcf852..b0ff1672f0 100644
--- a/gtk/gtkspinbutton.c
+++ b/gtk/gtkspinbutton.c
@@ -29,6 +29,9 @@
#include "gtkspinbutton.h"
+#include "gtkspinbuttonprivate.h"
+
+#include "gtkaccessibleprivate.h"
#include "gtkadjustment.h"
#include "gtkbox.h"
#include "gtkbutton.h"
@@ -305,12 +308,15 @@ static void gtk_spin_button_default_output (GtkSpinButton *spin_button);
static void gtk_spin_button_update_width_chars (GtkSpinButton *spin_button);
+static void gtk_spin_button_accessible_init (GtkAccessibleInterface *iface);
static guint spinbutton_signals[LAST_SIGNAL] = {0};
static GParamSpec *spinbutton_props[NUM_SPINBUTTON_PROPS] = {NULL, };
G_DEFINE_TYPE_WITH_CODE (GtkSpinButton, gtk_spin_button, GTK_TYPE_WIDGET,
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE,
+ gtk_spin_button_accessible_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
gtk_spin_button_editable_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE,
@@ -572,6 +578,31 @@ gtk_spin_button_editable_init (GtkEditableInterface *iface)
iface->insert_text = gtk_spin_button_insert_text;
}
+static gboolean
+gtk_spin_button_accessible_get_platform_state (GtkAccessible *self,
+ GtkAccessiblePlatformState state)
+{
+ GtkSpinButton *spin_button = GTK_SPIN_BUTTON (self);
+
+ switch (state)
+ {
+ case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE:
+ return gtk_widget_get_focusable (spin_button->entry);
+ case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED:
+ return gtk_widget_has_focus (spin_button->entry);
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gtk_spin_button_accessible_init (GtkAccessibleInterface *iface)
+{
+ GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (iface);
+ iface->get_at_context = parent_iface->get_at_context;
+ iface->get_platform_state = gtk_spin_button_accessible_get_platform_state;
+}
+
static void
gtk_cell_editable_spin_button_activated (GtkText *text, GtkSpinButton *spin)
{
@@ -2310,3 +2341,10 @@ gtk_spin_button_update (GtkSpinButton *spin_button)
else
gtk_spin_button_set_value (spin_button, val);
}
+
+GtkText *
+gtk_spin_button_get_text_widget (GtkSpinButton *spin_button)
+{
+ return GTK_TEXT (spin_button->entry);
+}
+
diff --git a/gtk/gtkspinbuttonprivate.h b/gtk/gtkspinbuttonprivate.h
new file mode 100644
index 0000000000..2a83077faa
--- /dev/null
+++ b/gtk/gtkspinbuttonprivate.h
@@ -0,0 +1,27 @@
+/* GTK - The GIMP Toolkit
+ * 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/>.
+ */
+
+#pragma once
+
+#include <gtk/gtkspinbutton.h>
+#include <gtk/gtktext.h>
+
+G_BEGIN_DECLS
+
+GtkText *gtk_spin_button_get_text_widget (GtkSpinButton *spin_button);
+
+G_END_DECLS
diff --git a/gtk/gtkstack.c b/gtk/gtkstack.c
index 8fd95a39f1..a9a0902c23 100644
--- a/gtk/gtkstack.c
+++ b/gtk/gtkstack.c
@@ -23,6 +23,9 @@
#include <gtk/gtk.h>
#include "gtkstack.h"
+#include "gtkenums.h"
+#include "gtkaccessibleprivate.h"
+#include "gtkatcontextprivate.h"
#include "gtkprivate.h"
#include "gtkintl.h"
#include "gtkprogresstrackerprivate.h"
@@ -77,6 +80,11 @@
* # CSS nodes
*
* GtkStack has a single CSS node named stack.
+ *
+ * # Accessibility
+ *
+ * GtkStack uses the #GTK_ACCESSIBLE_ROLE_TAB_PANEL for the stack
+ * pages.
*/
/**
@@ -181,7 +189,9 @@ enum
CHILD_PROP_NEEDS_ATTENTION,
CHILD_PROP_VISIBLE,
CHILD_PROP_USE_UNDERLINE,
- LAST_CHILD_PROP
+ LAST_CHILD_PROP,
+
+ PROP_ACCESSIBLE_ROLE
};
struct _GtkStackPage
@@ -193,6 +203,9 @@ struct _GtkStackPage
char *title;
char *icon_name;
GtkWidget *last_focus;
+
+ GtkATContext *at_context;
+
guint needs_attention : 1;
guint visible : 1;
guint use_underline : 1;
@@ -207,7 +220,39 @@ struct _GtkStackPageClass
static GParamSpec *stack_props[LAST_PROP] = { NULL, };
static GParamSpec *stack_page_props[LAST_CHILD_PROP] = { NULL, };
-G_DEFINE_TYPE (GtkStackPage, gtk_stack_page, G_TYPE_OBJECT)
+static GtkATContext *
+gtk_stack_page_accessible_get_at_context (GtkAccessible *accessible)
+{
+ GtkStackPage *page = GTK_STACK_PAGE (accessible);
+
+ if (page->at_context == NULL)
+ {
+ GtkAccessibleRole role = GTK_ACCESSIBLE_ROLE_TAB_PANEL;
+ GdkDisplay *display = gtk_widget_get_display (page->widget);
+
+ page->at_context = gtk_at_context_create (role, accessible, display);
+ }
+
+ return page->at_context;
+}
+
+static gboolean
+gtk_stack_page_accessible_get_platform_state (GtkAccessible *self,
+ GtkAccessiblePlatformState state)
+{
+ return FALSE;
+}
+
+static void
+gtk_stack_page_accessible_init (GtkAccessibleInterface *iface)
+{
+ iface->get_at_context = gtk_stack_page_accessible_get_at_context;
+ iface->get_platform_state = gtk_stack_page_accessible_get_platform_state;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GtkStackPage, gtk_stack_page, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE,
+ gtk_stack_page_accessible_init))
static void
gtk_stack_page_init (GtkStackPage *page)
@@ -270,6 +315,10 @@ gtk_stack_page_get_property (GObject *object,
g_value_set_boolean (value, gtk_stack_page_get_use_underline (info));
break;
+ case PROP_ACCESSIBLE_ROLE:
+ g_value_set_enum (value, GTK_ACCESSIBLE_ROLE_TAB_PANEL);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -314,6 +363,9 @@ gtk_stack_page_set_property (GObject *object,
gtk_stack_page_set_use_underline (info, g_value_get_boolean (value));
break;
+ case PROP_ACCESSIBLE_ROLE:
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -386,6 +438,8 @@ gtk_stack_page_class_init (GtkStackPageClass *class)
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, LAST_CHILD_PROP, stack_page_props);
+
+ g_object_class_override_property (object_class, PROP_ACCESSIBLE_ROLE, "accessible-role");
}
#define GTK_TYPE_STACK_PAGES (gtk_stack_pages_get_type ())
diff --git a/gtk/gtkstackswitcher.c b/gtk/gtkstackswitcher.c
index 00b9ab3524..86fe74d418 100644
--- a/gtk/gtkstackswitcher.c
+++ b/gtk/gtkstackswitcher.c
@@ -58,6 +58,11 @@
* When circumstances require it, GtkStackSwitcher adds the
* .needs-attention style class to the widgets representing the
* stack pages.
+ *
+ * # Accessibility
+ *
+ * GtkStackSwitcher uses the #GTK_ACCESSIBLE_ROLE_TAB_LIST role
+ * and uses the #GTK_ACCESSIBLE_ROLE_TAB for its buttons.
*/
#define TIMEOUT_EXPAND 500
@@ -247,7 +252,9 @@ add_child (guint position,
GtkStackPage *page;
GtkEventController *controller;
- button = gtk_toggle_button_new ();
+ button = g_object_new (GTK_TYPE_TOGGLE_BUTTON,
+ "accessible-role", GTK_ACCESSIBLE_ROLE_TAB,
+ NULL);
gtk_widget_set_focus_on_click (button, FALSE);
controller = gtk_drop_controller_motion_new ();
@@ -264,6 +271,14 @@ add_child (guint position,
selected = gtk_selection_model_is_selected (self->pages, position);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), selected);
+ gtk_accessible_update_state (GTK_ACCESSIBLE (button),
+ GTK_ACCESSIBLE_STATE_SELECTED, selected,
+ -1);
+
+ gtk_accessible_update_relation (GTK_ACCESSIBLE (button),
+ GTK_ACCESSIBLE_RELATION_CONTROLS, g_list_append (NULL, page),
+ -1);
+
g_signal_connect (button, "notify::active", G_CALLBACK (on_button_toggled), self);
g_signal_connect (page, "notify", G_CALLBACK (on_page_updated), self);
@@ -328,6 +343,10 @@ selection_changed_cb (GtkSelectionModel *model,
{
selected = gtk_selection_model_is_selected (switcher->pages, i);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), selected);
+
+ gtk_accessible_update_state (GTK_ACCESSIBLE (button),
+ GTK_ACCESSIBLE_STATE_SELECTED, selected,
+ -1);
}
g_object_unref (page);
}
@@ -497,6 +516,7 @@ gtk_stack_switcher_class_init (GtkStackSwitcherClass *class)
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
gtk_widget_class_set_css_name (widget_class, I_("stackswitcher"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TAB_LIST);
}
/**
diff --git a/gtk/gtktestatcontext.c b/gtk/gtktestatcontext.c
index e1bf0f5d1d..6358c196ce 100644
--- a/gtk/gtktestatcontext.c
+++ b/gtk/gtktestatcontext.c
@@ -23,6 +23,7 @@
#include "gtktestatcontextprivate.h"
#include "gtkatcontextprivate.h"
+#include "gtkaccessibleprivate.h"
#include "gtkdebug.h"
#include "gtkenums.h"
#include "gtkprivate.h"
@@ -45,6 +46,7 @@ gtk_test_at_context_state_change (GtkATContext *self,
GtkAccessibleStateChange changed_states,
GtkAccessiblePropertyChange changed_properties,
GtkAccessibleRelationChange changed_relations,
+ GtkAccessiblePlatformChange changed_platform,
GtkAccessibleAttributeSet *states,
GtkAccessibleAttributeSet *properties,
GtkAccessibleAttributeSet *relations)
diff --git a/gtk/gtktext.c b/gtk/gtktext.c
index 6910aeff8b..26bf27eeb5 100644
--- a/gtk/gtktext.c
+++ b/gtk/gtktext.c
@@ -1482,6 +1482,7 @@ gtk_text_class_init (GtkTextClass *class)
"text.redo", NULL);
gtk_widget_class_set_css_name (widget_class, I_("text"));
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_NONE);
}
static void
@@ -3376,7 +3377,7 @@ gtk_text_delete_selection (GtkText *self)
int start_pos = MIN (priv->selection_bound, priv->current_pos);
int end_pos = MAX (priv->selection_bound, priv->current_pos);
- gtk_text_delete_text (self, start_pos, end_pos);
+ gtk_editable_delete_text (GTK_EDITABLE (self), start_pos, end_pos);
}
static void
@@ -3871,7 +3872,7 @@ gtk_text_insert_at_cursor (GtkText *self,
if (priv->editable)
{
gtk_text_reset_im_context (self);
- gtk_text_insert_text (self, str, -1, &pos);
+ gtk_editable_insert_text (GTK_EDITABLE (self), str, -1, &pos);
gtk_text_set_selection_bounds (self, pos, pos);
}
}
@@ -3904,7 +3905,7 @@ gtk_text_delete_from_cursor (GtkText *self,
{
case GTK_DELETE_CHARS:
end_pos = gtk_text_move_logically (self, priv->current_pos, count);
- gtk_text_delete_text (self, MIN (start_pos, end_pos), MAX (start_pos, end_pos));
+ gtk_editable_delete_text (GTK_EDITABLE (self), MIN (start_pos, end_pos), MAX (start_pos, end_pos));
break;
case GTK_DELETE_WORDS:
@@ -3934,21 +3935,21 @@ gtk_text_delete_from_cursor (GtkText *self,
count--;
}
- gtk_text_delete_text (self, start_pos, end_pos);
+ gtk_editable_delete_text (GTK_EDITABLE (self), start_pos, end_pos);
break;
case GTK_DELETE_DISPLAY_LINE_ENDS:
case GTK_DELETE_PARAGRAPH_ENDS:
if (count < 0)
- gtk_text_delete_text (self, 0, priv->current_pos);
+ gtk_editable_delete_text (GTK_EDITABLE (self), 0, priv->current_pos);
else
- gtk_text_delete_text (self, priv->current_pos, -1);
+ gtk_editable_delete_text (GTK_EDITABLE (self), priv->current_pos, -1);
break;
case GTK_DELETE_DISPLAY_LINES:
case GTK_DELETE_PARAGRAPHS:
- gtk_text_delete_text (self, 0, -1);
+ gtk_editable_delete_text (GTK_EDITABLE (self), 0, -1);
break;
case GTK_DELETE_WHITESPACE:
@@ -4008,12 +4009,12 @@ gtk_text_backspace (GtkText *self)
G_NORMALIZE_NFD);
len = g_utf8_strlen (normalized_text, -1);
- gtk_text_delete_text (self, prev_pos, priv->current_pos);
+ gtk_editable_delete_text (GTK_EDITABLE (self), prev_pos, priv->current_pos);
if (len > 1)
{
int pos = priv->current_pos;
- gtk_text_insert_text (self, normalized_text,
+ gtk_editable_insert_text (GTK_EDITABLE (self), normalized_text,
g_utf8_offset_to_pointer (normalized_text, len - 1) - normalized_text,
&pos);
gtk_text_set_selection_bounds (self, pos, pos);
@@ -4024,7 +4025,7 @@ gtk_text_backspace (GtkText *self)
}
else
{
- gtk_text_delete_text (self, prev_pos, priv->current_pos);
+ gtk_editable_delete_text (GTK_EDITABLE (self), prev_pos, priv->current_pos);
}
}
else
@@ -4238,9 +4239,9 @@ gtk_text_delete_surrounding_cb (GtkIMContext *context,
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
if (priv->editable)
- gtk_text_delete_text (self,
- priv->current_pos + offset,
- priv->current_pos + offset + n_chars);
+ gtk_editable_delete_text (GTK_EDITABLE (self),
+ priv->current_pos + offset,
+ priv->current_pos + offset + n_chars);
return TRUE;
}
@@ -4274,7 +4275,7 @@ gtk_text_enter_text (GtkText *self,
}
tmp_pos = priv->current_pos;
- gtk_text_insert_text (self, str, strlen (str), &tmp_pos);
+ gtk_editable_insert_text (GTK_EDITABLE (self), str, strlen (str), &tmp_pos);
gtk_text_set_selection_bounds (self, tmp_pos, tmp_pos);
priv->need_im_reset = old_need_im_reset;
@@ -5165,7 +5166,7 @@ gtk_text_delete_whitespace (GtkText *self)
end++;
if (start != end)
- gtk_text_delete_text (self, start, end);
+ gtk_editable_delete_text (GTK_EDITABLE (self), start, end);
}
@@ -5233,7 +5234,7 @@ paste_received (GObject *clipboard,
gtk_text_delete_selection (self);
pos = priv->current_pos;
- gtk_text_insert_text (self, text, length, &pos);
+ gtk_editable_insert_text (GTK_EDITABLE (self), text, length, &pos);
gtk_text_set_selection_bounds (self, pos, pos);
end_change (self);
@@ -5430,6 +5431,10 @@ gtk_text_set_editable (GtkText *self,
gtk_text_update_clipboard_actions (self);
gtk_text_update_emoji_action (self);
+ gtk_accessible_update_property (GTK_ACCESSIBLE (self),
+ GTK_ACCESSIBLE_PROPERTY_READ_ONLY, !priv->editable,
+ -1);
+
g_object_notify (G_OBJECT (self), "editable");
}
}
@@ -5454,9 +5459,9 @@ gtk_text_set_text (GtkText *self,
begin_change (self);
g_object_freeze_notify (G_OBJECT (self));
- gtk_text_delete_text (self, 0, -1);
+ gtk_editable_delete_text (GTK_EDITABLE (self), 0, -1);
tmp_pos = 0;
- gtk_text_insert_text (self, text, strlen (text), &tmp_pos);
+ gtk_editable_insert_text (GTK_EDITABLE (self), text, strlen (text), &tmp_pos);
g_object_thaw_notify (G_OBJECT (self));
end_change (self);
@@ -6240,7 +6245,7 @@ gtk_text_drag_drop (GtkDropTarget *dest,
drop_position < priv->selection_bound ||
drop_position > priv->current_pos)
{
- gtk_text_insert_text (self, str, length, &drop_position);
+ gtk_editable_insert_text (GTK_EDITABLE (self), str, length, &drop_position);
}
else
{
@@ -6249,7 +6254,7 @@ gtk_text_drag_drop (GtkDropTarget *dest,
begin_change (self);
gtk_text_delete_selection (self);
pos = MIN (priv->selection_bound, priv->current_pos);
- gtk_text_insert_text (self, str, length, &pos);
+ gtk_editable_insert_text (GTK_EDITABLE (self), str, length, &pos);
end_change (self);
}
@@ -6811,7 +6816,7 @@ emoji_picked (GtkEmojiChooser *chooser,
gtk_text_delete_selection (self);
pos = priv->current_pos;
- gtk_text_insert_text (self, text, -1, &pos);
+ gtk_editable_insert_text (GTK_EDITABLE (self), text, -1, &pos);
gtk_text_set_selection_bounds (self, pos, pos);
end_change (self);
}
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index c87f9304a7..a1c3a069b7 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -4948,6 +4948,9 @@ gtk_widget_set_focusable (GtkWidget *widget,
priv->focusable = focusable;
gtk_widget_queue_resize (widget);
+
+ gtk_accessible_platform_changed (GTK_ACCESSIBLE (widget), GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSABLE);
+
g_object_notify_by_pspec (G_OBJECT (widget), widget_props[PROP_FOCUSABLE]);
}
@@ -8092,10 +8095,14 @@ gtk_widget_accessible_get_at_context (GtkAccessible *accessible)
GtkWidget *self = GTK_WIDGET (accessible);
GtkWidgetPrivate *priv = gtk_widget_get_instance_private (self);
+ if (priv->in_destruction)
+ return NULL;
+
if (priv->at_context == NULL)
{
GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS (self);
GtkWidgetClassPrivate *class_priv = widget_class->priv;
+ GdkDisplay *display = _gtk_widget_get_display (self);
GtkAccessibleRole role;
/* Widgets have two options to set the accessible role: either they
@@ -8112,16 +8119,32 @@ gtk_widget_accessible_get_at_context (GtkAccessible *accessible)
role = class_priv->accessible_role;
priv->accessible_role = role;
- priv->at_context = gtk_at_context_create (role, accessible);
+ priv->at_context = gtk_at_context_create (role, accessible, display);
}
return priv->at_context;
}
+static gboolean
+gtk_widget_accessible_get_platform_state (GtkAccessible *self,
+ GtkAccessiblePlatformState state)
+{
+ switch (state)
+ {
+ case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE:
+ return gtk_widget_get_focusable (GTK_WIDGET (self));
+ case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED:
+ return gtk_widget_has_focus (GTK_WIDGET (self));
+ default:
+ g_assert_not_reached ();
+ }
+}
+
static void
gtk_widget_accessible_interface_init (GtkAccessibleInterface *iface)
{
iface->get_at_context = gtk_widget_accessible_get_at_context;
+ iface->get_platform_state = gtk_widget_accessible_get_platform_state;
}
/*
@@ -9751,6 +9774,8 @@ gtk_widget_set_has_focus (GtkWidget *widget,
priv->has_focus = has_focus;
+ gtk_accessible_platform_changed (GTK_ACCESSIBLE (widget), GTK_ACCESSIBLE_PLATFORM_CHANGE_FOCUSED);
+
g_object_notify_by_pspec (G_OBJECT (widget), widget_props[PROP_HAS_FOCUS]);
}
@@ -12138,6 +12163,10 @@ gtk_widget_update_orientation (GtkWidget *widget,
gtk_widget_add_css_class (widget, "vertical");
gtk_widget_remove_css_class (widget, "horizontal");
}
+
+ gtk_accessible_update_property (GTK_ACCESSIBLE (widget),
+ GTK_ACCESSIBLE_PROPERTY_ORIENTATION, orientation,
+ -1);
}
/**
diff --git a/gtk/meson.build b/gtk/meson.build
index 006ac8f31e..614b4079f8 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -1,3 +1,4 @@
+subdir('a11y')
subdir('deprecated')
subdir('inspector')
@@ -961,6 +962,7 @@ gtk_gen_headers = [
]
gtk_sources += [
+ gtk_a11y_src,
gtk_dbus_src,
gtk_deprecated_sources,
inspector_sources,
diff --git a/testsuite/a11y/accessible.c b/testsuite/a11y/accessible.c
index b8daf9e182..b353d9e94e 100644
--- a/testsuite/a11y/accessible.c
+++ b/testsuite/a11y/accessible.c
@@ -29,7 +29,9 @@ test_object_accessible_get_at_context (GtkAccessible *accessible)
TestObject *self = (TestObject*)accessible;
if (self->at_context == NULL)
- self->at_context = gtk_at_context_create (self->role, accessible);
+ self->at_context = gtk_at_context_create (self->role,
+ accessible,
+ gdk_display_get_default ());
return self->at_context;
}
diff --git a/testsuite/css/api.test.in b/testsuite/css/api.test.in
index ee5a0b38e4..d1d25a1bc1 100644
--- a/testsuite/css/api.test.in
+++ b/testsuite/css/api.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=@libexecdir@/installed-tests/gtk-4.0/css/api --tap -k
+Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @libexecdir@/installed-tests/gtk-4.0/css/api --tap -k"
Type=session
Output=TAP
diff --git a/testsuite/css/change/change.test.in b/testsuite/css/change/change.test.in
index 0996111779..9906e9f95b 100644
--- a/testsuite/css/change/change.test.in
+++ b/testsuite/css/change/change.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=@libexecdir@/installed-tests/gtk-4.0/css/change/test-css-change --tap -k
+Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @libexecdir@/installed-tests/gtk-4.0/css/change/test-css-change --tap -k"
Type=session
Output=TAP
diff --git a/testsuite/css/change/meson.build b/testsuite/css/change/meson.build
index 04204ed5ba..83e37ae2f6 100644
--- a/testsuite/css/change/meson.build
+++ b/testsuite/css/change/meson.build
@@ -9,14 +9,13 @@ test_change = executable(
install: get_option('install-tests'),
install_dir: testexecdir,
)
+
test('change', test_change,
- args: [ '--tap', '-k' ],
- protocol: 'tap',
- env: [
- 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
- 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
- ],
- suite: 'css')
+ args: [ '--tap', '-k' ],
+ protocol: 'tap',
+ env: csstest_env,
+ suite: 'css',
+)
test_data = [
'test1.css', 'test1.ui', 'test1.nodes',
diff --git a/testsuite/css/meson.build b/testsuite/css/meson.build
index bac0ebd074..931502bef7 100644
--- a/testsuite/css/meson.build
+++ b/testsuite/css/meson.build
@@ -1,3 +1,12 @@
+csstest_env = environment()
+csstest_env.set('GTK_TEST_ACCESSIBLE', '1')
+csstest_env.set('GSK_RENDERER', 'cairo')
+csstest_env.set('G_TEST_SRCDIR', meson.current_source_dir())
+csstest_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
+csstest_env.set('GIO_USE_VFS', 'local')
+csstest_env.set('GSETTINGS_BACKEND', 'memory')
+csstest_env.set('G_ENABLE_DIAGNOSTIC', '0')
+
subdir('parser')
subdir('nodes')
subdir('style')
@@ -14,10 +23,7 @@ test_api = executable('api', 'api.c',
test('api', test_api,
args: ['--tap', '-k' ],
protocol: 'tap',
- env: [
- 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
- 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
- ],
+ env: csstest_env,
suite: 'css')
test_data = executable('data', ['data.c', '../../gtk/css/gtkcssdataurl.c'],
@@ -29,10 +35,7 @@ test_data = executable('data', ['data.c', '../../gtk/css/gtkcssdataurl.c'],
test('data', test_data,
args: ['--tap', '-k' ],
protocol: 'tap',
- env: [
- 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
- 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
- ],
+ env: csstest_env,
suite: 'css')
if get_option('install-tests')
@@ -46,20 +49,24 @@ endif
if false and get_option ('profiler')
+ adwaita_env = csstest_env
+ adwaita_env.set('GTK_THEME', 'Adwaita')
test('performance-adwaita', test_performance,
args: [ '--mark', 'css validation',
'--name', 'performance-adwaita',
'--output', join_paths(meson.current_build_dir(), 'output'),
join_paths(meson.current_build_dir(), '../../demos/widget-factory/gtk4-widget-factory') ],
- env: [ 'GTK_THEME=Adwaita' ],
+ env: adwaita_env,
suite: [ 'css' ])
+ empty_env = csstest_env
+ empty_env.set('GTK_THEME', 'Empty')
test('performance-empty', test_performance,
args: [ '--mark', 'css validation',
'--name', 'performance-empty',
'--output', join_paths(meson.current_build_dir(), 'output'),
join_paths(meson.current_build_dir(), '../../demos/widget-factory/gtk4-widget-factory') ],
- env: [ 'GTK_THEME=Empty' ],
+ env: empty_env,
suite: [ 'css' ])
endif
diff --git a/testsuite/css/nodes/meson.build b/testsuite/css/nodes/meson.build
index 720ba46db8..11ed48b9e5 100644
--- a/testsuite/css/nodes/meson.build
+++ b/testsuite/css/nodes/meson.build
@@ -7,13 +7,11 @@ test_nodes = executable('test-css-nodes', 'test-css-nodes.c',
install_dir: testexecdir,
dependencies: libgtk_dep)
test('nodes', test_nodes,
- args: [ '--tap', '-k' ],
- protocol: 'tap',
- env: [
- 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
- 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
- ],
- suite: 'css')
+ args: [ '--tap', '-k' ],
+ protocol: 'tap',
+ env: csstest_env,
+ suite: 'css',
+)
test_data = [
'box.ltr.nodes',
@@ -77,5 +75,4 @@ if get_option('install-tests')
install_dir: testdatadir)
install_data(test_data, install_dir: testexecdir)
-
endif
diff --git a/testsuite/css/nodes/nodes.test.in b/testsuite/css/nodes/nodes.test.in
index bf5331999f..e662b746b6 100644
--- a/testsuite/css/nodes/nodes.test.in
+++ b/testsuite/css/nodes/nodes.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=@libexecdir@/installed-tests/gtk-4.0/css/nodes/test-css-nodes --tap -k
+Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @libexecdir@/installed-tests/gtk-4.0/css/nodes/test-css-nodes --tap -k"
Type=session
Output=TAP
diff --git a/testsuite/css/parser/parser.test.in b/testsuite/css/parser/parser.test.in
index 2312d7305d..91bb6d20f6 100644
--- a/testsuite/css/parser/parser.test.in
+++ b/testsuite/css/parser/parser.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=@libexecdir@/installed-tests/gtk-4.0/css/parser/test-css-parser --tap -k
+Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @libexecdir@/installed-tests/gtk-4.0/css/parser/test-css-parser --tap -k"
Type=session
Output=TAP
diff --git a/testsuite/css/style/meson.build b/testsuite/css/style/meson.build
index cadbf8dc16..b54adf7c68 100644
--- a/testsuite/css/style/meson.build
+++ b/testsuite/css/style/meson.build
@@ -17,13 +17,11 @@ test_style = executable(
install_dir: testexecdir,
)
test('style', test_style,
- args: [ '--tap', '-k' ],
- protocol: 'tap',
- env: [
- 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
- 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
- ],
- suite: 'css')
+ args: [ '--tap', '-k' ],
+ protocol: 'tap',
+ env: csstest_env,
+ suite: 'css',
+)
test_data = [
'adjacent-states.css',
@@ -65,5 +63,4 @@ if get_option('install-tests')
install_dir: testdatadir)
install_data(test_data, install_dir: testexecdir)
-
endif
diff --git a/testsuite/css/style/style.test.in b/testsuite/css/style/style.test.in
index 2990614b51..6cce32a0a0 100644
--- a/testsuite/css/style/style.test.in
+++ b/testsuite/css/style/style.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=@libexecdir@/installed-tests/gtk-4.0/css/style/test-css-style --tap -k
+Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @libexecdir@/installed-tests/gtk-4.0/css/style/test-css-style --tap -k"
Type=session
Output=TAP
diff --git a/testsuite/gdk/gdk.test.in b/testsuite/gdk/gdk.test.in
index 709cb6a6bd..ad90aa61ed 100644
--- a/testsuite/gdk/gdk.test.in
+++ b/testsuite/gdk/gdk.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=@testexecdir@/@test@ --tap -k --verbose
+Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @testexecdir@/@test@ --tap -k --verbose"
Type=session
Output=TAP
diff --git a/testsuite/gdk/meson.build b/testsuite/gdk/meson.build
index bd7744ee0e..aa93f8384d 100644
--- a/testsuite/gdk/meson.build
+++ b/testsuite/gdk/meson.build
@@ -16,28 +16,31 @@ tests = [
foreach t : tests
test_exe = executable(t, '@0@.c'.format(t),
- c_args: common_cflags,
- dependencies: libgtk_dep,
- install: get_option('install-tests'),
- install_dir: testexecdir)
+ c_args: common_cflags,
+ dependencies: libgtk_dep,
+ install: get_option('install-tests'),
+ install_dir: testexecdir,
+ )
test(t, test_exe,
- args: [ '--tap', '-k' ],
- protocol: 'tap',
- env: [
- 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
- 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
- ],
- suite: 'gdk')
+ args: [ '--tap', '-k' ],
+ protocol: 'tap',
+ env: [
+ 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
+ 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
+ ],
+ suite: 'gdk',
+ )
if get_option('install-tests')
test_cdata = configuration_data()
test_cdata.set('testexecdir', testexecdir)
test_cdata.set('test', t)
configure_file(input: 'gdk.test.in',
- output: '@0@.test'.format(t),
- configuration: test_cdata,
- install: true,
- install_dir: testdatadir)
+ output: '@0@.test'.format(t),
+ configuration: test_cdata,
+ install: true,
+ install_dir: testdatadir,
+ )
endif
endforeach
diff --git a/testsuite/gsk/meson.build b/testsuite/gsk/meson.build
index d9cb63f1ec..07b2d05955 100644
--- a/testsuite/gsk/meson.build
+++ b/testsuite/gsk/meson.build
@@ -101,6 +101,7 @@ foreach renderer : renderers
join_paths(meson.current_source_dir(), 'compare', test + '.png')],
env: [
'GSK_RENDERER=' + renderer[0],
+ 'GTK_TEST_ACCESSIBLE=1',
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
],
@@ -178,6 +179,7 @@ foreach test : node_parser_tests
],
env: [
'GSK_RENDERER=opengl',
+ 'GTK_TEST_ACCESSIBLE=1',
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
],
@@ -211,6 +213,7 @@ foreach t : tests
protocol: 'tap',
env: [
'GSK_RENDERER=cairo',
+ 'GTK_TEST_ACCESSIBLE=1',
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
],
diff --git a/testsuite/gsk/render-nodes-cairo.test.in b/testsuite/gsk/render-nodes-cairo.test.in
index 28a4f55d76..8976ff03d8 100644
--- a/testsuite/gsk/render-nodes-cairo.test.in
+++ b/testsuite/gsk/render-nodes-cairo.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=/bin/sh -c "env GSK_RENDERER=cairo @libexecdir@/installed-tests/gtk-4.0/gsk/test-render-nodes --tap -k --verbose"
+Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GSK_RENDERER=cairo GTK_TEST_ACCESSIBLE=1 @libexecdir@/installed-tests/gtk-4.0/gsk/test-render-nodes --tap -k --verbose"
Type=session
Output=TAP
diff --git a/testsuite/gsk/render-nodes-vulkan.test.in b/testsuite/gsk/render-nodes-vulkan.test.in
index d16ba906e4..4ad9523ed4 100644
--- a/testsuite/gsk/render-nodes-vulkan.test.in
+++ b/testsuite/gsk/render-nodes-vulkan.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=/bin/sh -c "env GSK_RENDERER=vulkan @libexecdir@/installed-tests/gtk-4.0/gsk/test-render-nodes --tap -k --verbose"
+Exec=/bin/sh -c "env GTK_TEST_ACCESSIBLE=1 GSK_RENDERER=vulkan @libexecdir@/installed-tests/gtk-4.0/gsk/test-render-nodes --tap -k --verbose"
Type=session
Output=TAP
diff --git a/testsuite/gtk/gtk.test.in b/testsuite/gtk/gtk.test.in
index 709cb6a6bd..ad90aa61ed 100644
--- a/testsuite/gtk/gtk.test.in
+++ b/testsuite/gtk/gtk.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=@testexecdir@/@test@ --tap -k --verbose
+Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 GTK_TEST_ACCESSIBLE=1 @testexecdir@/@test@ --tap -k --verbose"
Type=session
Output=TAP
diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build
index 11509fa5bc..6b973c1d9b 100644
--- a/testsuite/gtk/meson.build
+++ b/testsuite/gtk/meson.build
@@ -146,6 +146,15 @@ is_debug = get_option('buildtype').startswith('debug')
test_cargs = []
+test_env = environment()
+test_env.set('GTK_TEST_ACCESSIBLE', '1')
+test_env.set('GSK_RENDERER', 'cairo')
+test_env.set('G_TEST_SRCDIR', meson.current_source_dir())
+test_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
+test_env.set('GIO_USE_VFS', 'local')
+test_env.set('GSETTINGS_BACKEND', 'memory')
+test_env.set('G_ENABLE_DIAGNOSTIC', '0')
+
if os_unix
# tests += [['defaultvalue']] # disabled in Makefile.am as well
test_cargs += ['-DHAVE_UNIX_PRINT_WIDGETS']
@@ -182,11 +191,7 @@ foreach t : tests
args: [ '--tap', '-k' ],
protocol: 'tap',
timeout: test_timeout,
- env: [
- 'GSK_RENDERER=cairo',
- 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
- 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
- ],
+ env: test_env,
suite: ['gtk'] + test_extra_suites,
should_fail: expect_fail,
)
@@ -203,10 +208,7 @@ if add_languages('cpp', required: false)
test('c++ keywords', test_exe,
args: [ '--tap', '-k' ],
#protocol: 'tap',
- env: [
- 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
- 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
- ],
+ env: test_env,
suite: 'gtk')
if get_option('install-tests')
conf = configuration_data()
@@ -254,10 +256,7 @@ foreach test : focus_chain_tests
test(test[0] + ' ' + test[1], focus_chain,
args: [ join_paths(meson.current_source_dir(), 'focus-chain', test[0] + '.ui'),
join_paths(meson.current_source_dir(), 'focus-chain', test[0] + '.' + test[1]) ],
- env: [
- 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
- 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
- ],
+ env: test_env,
suite: [ 'gtk', 'focus' ])
endforeach
@@ -282,14 +281,17 @@ endif
if false and get_option ('profiler')
+ performance_env = test_env
+ performance_env.set('GTK_THEME', 'Empty')
+
test('performance-layout', test_performance,
args: [ '--mark', 'size allocation', join_paths(meson.current_build_dir(), '../../demos/widget-factory/gtk4-widget-factory') ],
- env: [ 'GTK_THEME=Empty' ],
+ env: performance_env,
suite: [ 'gtk' ])
test('performance-snapshot', test_performance,
args: [ '--mark', 'widget snapshot', join_paths(meson.current_build_dir(), '../../demos/widget-factory/gtk4-widget-factory') ],
- env: [ 'GTK_THEME=Empty' ],
+ env: performance_end,
suite: [ 'gtk' ])
endif
diff --git a/testsuite/reftests/meson.build b/testsuite/reftests/meson.build
index 0e8bdbc16e..da1edd67c8 100644
--- a/testsuite/reftests/meson.build
+++ b/testsuite/reftests/meson.build
@@ -450,6 +450,16 @@ xfails = [
'label-sizing.ui',
]
+reftest_env = environment()
+reftest_env.set('GTK_TEST_ACCESSIBLE', '1')
+reftest_env.set('GSK_RENDERER', 'opengl')
+reftest_env.set('G_TEST_SRCDIR', meson.current_source_dir())
+reftest_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
+reftest_env.set('GIO_USE_VFS', 'local')
+reftest_env.set('GSETTINGS_BACKEND', 'memory')
+reftest_env.set('G_ENABLE_DIAGNOSTIC', '0')
+reftest_env.set('REFTEST_MODULE_DIR', meson.current_build_dir())
+
foreach testname : testdata
if testname.endswith('.ui') and not testname.endswith('.ref.ui')
test('reftest ' + testname, gtk_reftest,
@@ -459,12 +469,7 @@ foreach testname : testdata
join_paths(meson.current_source_dir(), testname),
],
protocol: 'tap',
- env: [
- 'GSK_RENDERER=opengl',
- 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
- 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
- 'REFTEST_MODULE_DIR=@0@'.format(meson.current_build_dir())
- ],
+ env: reftest_env,
suite: 'reftest',
should_fail: xfails.contains(testname))
endif
diff --git a/testsuite/reftests/reftests-dark.test.in b/testsuite/reftests/reftests-dark.test.in
index ab0e2fb19f..d30a4f2099 100644
--- a/testsuite/reftests/reftests-dark.test.in
+++ b/testsuite/reftests/reftests-dark.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=/bin/sh -c "env GTK_THEME=Adwaita:dark G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests"
+Exec=/bin/sh -c "env GTK_THEME=Adwaita:dark GTK_TEST_ACCESSIBLE=1 G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests"
Type=session-exclusive
Output=TAP
diff --git a/testsuite/reftests/reftests-hc.test.in b/testsuite/reftests/reftests-hc.test.in
index 9d6cb5655d..88627b7ae4 100644
--- a/testsuite/reftests/reftests-hc.test.in
+++ b/testsuite/reftests/reftests-hc.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=/bin/sh -c "env GTK_THEME=HighContrast G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests"
+Exec=/bin/sh -c "env GTK_THEME=HighContrast GTK_TEST_ACCESSIBLE=1 G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests"
Type=session-exclusive
Output=TAP
diff --git a/testsuite/reftests/reftests-hci.test.in b/testsuite/reftests/reftests-hci.test.in
index 6ea0b1aa3a..24e785551c 100644
--- a/testsuite/reftests/reftests-hci.test.in
+++ b/testsuite/reftests/reftests-hci.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=/bin/sh -c "env GTK_THEME=HighContrastInverse G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests"
+Exec=/bin/sh -c "env GTK_THEME=HighContrastInverse GTK_TEST_ACCESSIBLE=1 G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests"
Type=session-exclusive
Output=TAP
diff --git a/testsuite/reftests/reftests.test.in b/testsuite/reftests/reftests.test.in
index 9eb1864bfa..53971961d6 100644
--- a/testsuite/reftests/reftests.test.in
+++ b/testsuite/reftests/reftests.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests"
+Exec=/bin/sh -c "env GTK_TEST_ACCESSIBLE=1 G_ENABLE_DIAGNOSTIC=0 @libexecdir@/installed-tests/gtk-4.0/gtk-reftest -k --verbose --tap --output $(pwd) --directory @libexecdir@/installed-tests/gtk-4.0/reftests"
Type=session-exclusive
Output=TAP
diff --git a/testsuite/tools/meson.build b/testsuite/tools/meson.build
index 85fc54aac4..1369ccfedc 100644
--- a/testsuite/tools/meson.build
+++ b/testsuite/tools/meson.build
@@ -23,6 +23,7 @@ if bash.found()
env: [
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
+ 'GTK_TEST_ACCESSIBLE=1',
'GTK_BUILDER_TOOL=@0@'.format(get_variable('gtk4_builder_tool').full_path()),
'GTK_QUERY_SETTINGS=@0@'.format(get_variable('gtk4_query_settings').full_path())
],
diff --git a/testsuite/tools/tools.test.in b/testsuite/tools/tools.test.in
index 24c47ce34f..d0f3810a44 100644
--- a/testsuite/tools/tools.test.in
+++ b/testsuite/tools/tools.test.in
@@ -1,4 +1,4 @@
[Test]
-Exec=/bin/sh -c "env G_ENABLE_DIAGNOSTIC=0 TEST_DATA_DIR=@testexecdir@/@test@-data @testexecdir@/@test@"
+Exec=/bin/sh -c "env GTK_TEST_ACCESSIBLE=1 G_ENABLE_DIAGNOSTIC=0 TEST_DATA_DIR=@testexecdir@/@test@-data @testexecdir@/@test@"
Type=session
Output=TAP