+#include "jni.h"
+
+int OrcaAnnounce(JNIEnv *env, jstring str, jint priority);
+jobject OrcaGetConf(JNIEnv *env);
+void OrcaSetSpeechConf(JNIEnv *env, SPDConnection *connection, jobject conf);
+void OrcaSetLanguage(JNIEnv *env, SPDConnection *connection, jobject conf);
+void OrcaSetOutputModule(JNIEnv *env, SPDConnection *connection, jobject conf);
+void OrcaSetPunctuation(JNIEnv *env, SPDConnection *connection, jobject conf);
+void OrcaSetSynthesisVoice(JNIEnv *env, SPDConnection *connection, jobject conf);
+void OrcaSetVoiceRate(JNIEnv *env, SPDConnection *connection, jobject conf);
+void OrcaSetVoicePitch(JNIEnv *env, SPDConnection *connection, jobject conf);
+void OrcaSetVolume(JNIEnv *env, SPDConnection *connection, jobject conf);
+int OrcaGetEnableSpeech(JNIEnv *env, jobject conf);
+
+#endif // #ifndef NO_A11Y_SPEECHD_ANNOUNCING
+
+#endif //ORCACONF_H
+
diff --git a/src/jdk.accessibility/linux/classes/module-info.java.extra b/src/jdk.accessibility/linux/classes/module-info.java.extra
new file mode 100644
index 000000000000..d4c2cabf24d2
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/module-info.java.extra
@@ -0,0 +1 @@
+provides javax.accessibility.AccessibilityProvider with org.GNOME.Accessibility.AtkProvider;
\ No newline at end of file
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkAction.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkAction.java
new file mode 100644
index 000000000000..ddc8dd226613
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkAction.java
@@ -0,0 +1,214 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.lang.ref.WeakReference;
+import java.awt.EventQueue;
+
+/**
+ * The ATK Action interface implementation for Java accessibility.
+ *
+ * This class provides a bridge between Java's AccessibleAction interface
+ * and the ATK (Accessibility Toolkit) action interface.
+ */
+public class AtkAction {
+
+ private final WeakReference accessibleContextWeakRef;
+ private final WeakReference accessibleActionWeakRef;
+
+ private final String[] actionDescriptions;
+ private final String[] actionLocalizedNames;
+ private final int actionCount; // the number of accessible actions available on the object
+
+ private final Object actionDescriptionsLock = new Object();
+ private final Object actionLocalizedNamesLock = new Object();
+
+ private AtkAction(AccessibleContext ac) {
+ assert EventQueue.isDispatchThread();
+
+ AccessibleAction accessibleAction = ac.getAccessibleAction();
+ if (accessibleAction == null) {
+ throw new IllegalArgumentException("AccessibleContext must have AccessibleAction");
+ }
+
+ this.accessibleContextWeakRef = new WeakReference(ac);
+ this.accessibleActionWeakRef = new WeakReference(accessibleAction);
+ this.actionCount = accessibleAction.getAccessibleActionCount();
+ this.actionDescriptions = new String[actionCount];
+ this.actionLocalizedNames = new String[actionCount];
+ }
+
+ /* JNI upcalls section */
+
+ /**
+ * Factory method to create an AtkAction instance from an AccessibleContext.
+ * Called from native code via JNI.
+ *
+ * @param ac the AccessibleContext to wrap
+ * @return a new AtkAction instance, or null if creation fails
+ */
+ private static AtkAction create_atk_action(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return new AtkAction(ac);
+ }, null);
+ }
+
+ /**
+ * Performs the specified action on the object.
+ * Called from native code via JNI.
+ *
+ * @param index the action index corresponding to the action to be performed
+ * @return true if the action was successfully performed, false otherwise
+ */
+ private boolean do_action(int index) {
+ if (index < 0 || index >= actionCount) {
+ return false;
+ }
+ AccessibleAction accessibleAction = accessibleActionWeakRef.get();
+ if (accessibleAction == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleAction.doAccessibleAction(index);
+ }, false);
+ }
+
+ /**
+ * Gets the number of accessible actions available on the object.
+ * Called from native code via JNI.
+ *
+ * @return the number of actions, or 0 if this object does not implement actions
+ */
+ private int get_n_actions() {
+ return this.actionCount;
+ }
+
+ /**
+ * Returns a description of the specified action of the object.
+ * Called from native code via JNI.
+ *
+ * @param index the action index corresponding to the action
+ * @return a description string, or null if the action does not exist
+ */
+ private String get_description(int index) {
+ if (index < 0 || index >= actionCount) {
+ return null;
+ }
+ AccessibleAction accessibleAction = accessibleActionWeakRef.get();
+ if (accessibleAction == null) {
+ return null;
+ }
+
+ String desc;
+
+ synchronized (actionDescriptionsLock) {
+ desc = actionDescriptions[index];
+ if (desc != null) {
+ return desc;
+ }
+ }
+
+ String computedActionDesc = AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleAction.getAccessibleActionDescription(index);
+ }, null);
+
+ synchronized (actionDescriptionsLock) {
+ desc = actionDescriptions[index];
+ if (desc == null) {
+ actionDescriptions[index] = computedActionDesc;
+ desc = computedActionDesc;
+ }
+ }
+
+ return desc;
+ }
+
+ /**
+ * Sets a description of the specified action of the object.
+ * Called from native code via JNI.
+ *
+ * @param index the action index corresponding to the action
+ * @param description the description to be assigned to this action
+ * @return true if the description was successfully set, false otherwise
+ */
+ private boolean set_description(int index, String description) {
+ if (index < 0 || index >= actionCount) {
+ return false;
+ }
+ synchronized (actionDescriptionsLock) {
+ actionDescriptions[index] = description;
+ }
+ return true;
+ }
+
+ /**
+ * Returns the localized name of the specified action of the object.
+ * Called from native code via JNI.
+ *
+ * @param index the action index corresponding to the action
+ * @return a localized name string, or null if the action does not exist
+ */
+ private String get_localized_name(int index) {
+ if (index < 0 || index >= actionCount) {
+ return null;
+ }
+ AccessibleContext accessibleContext = accessibleContextWeakRef.get();
+ if (accessibleContext == null) {
+ return null;
+ }
+ AccessibleAction accessibleAction = accessibleActionWeakRef.get();
+ if (accessibleAction == null) {
+ return null;
+ }
+
+ String localizedName;
+
+ synchronized (actionLocalizedNamesLock) {
+ localizedName = actionLocalizedNames[index];
+ if (localizedName != null) {
+ return localizedName;
+ }
+ }
+
+ String computedLocalizedName = AtkUtil.invokeInSwingAndWait(() -> {
+ String description = accessibleAction.getAccessibleActionDescription(index);
+ if (description != null) {
+ return description;
+ }
+ String name = accessibleContext.getAccessibleName();
+ if (name != null) {
+ return name;
+ }
+ return "";
+ }, null);
+
+ synchronized (actionLocalizedNamesLock) {
+ localizedName = actionLocalizedNames[index];
+ if (localizedName == null) {
+ actionLocalizedNames[index] = computedLocalizedName;
+ localizedName = computedLocalizedName;
+ }
+ }
+
+ return localizedName;
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkComponent.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkComponent.java
new file mode 100644
index 000000000000..9250d3a14cbc
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkComponent.java
@@ -0,0 +1,407 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems Inc.
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Dimension;
+import java.lang.ref.WeakReference;
+import java.awt.EventQueue;
+
+/**
+ * The ATK Component interface implementation for Java accessibility.
+ *
+ * This class provides a bridge between Java's AccessibleComponent interface
+ * and the ATK (Accessibility Toolkit) component interface.
+ */
+public class AtkComponent {
+
+ private final WeakReference accessibleContextWeakRef;
+ private final WeakReference accessibleComponentWeakRef;
+
+ private AtkComponent(AccessibleContext ac) {
+ assert EventQueue.isDispatchThread();
+
+ AccessibleComponent accessibleComponent = ac.getAccessibleComponent();
+ if (accessibleComponent == null) {
+ throw new IllegalArgumentException("AccessibleContext must have AccessibleComponent");
+ }
+
+ this.accessibleContextWeakRef = new WeakReference(ac);
+ this.accessibleComponentWeakRef = new WeakReference(accessibleComponent);
+ }
+
+ private static Point getWindowLocation(AccessibleContext ac) {
+ assert EventQueue.isDispatchThread();
+
+ while (ac != null) {
+ AccessibleRole accessibleRole = ac.getAccessibleRole();
+ if (accessibleRole == AccessibleRole.DIALOG ||
+ accessibleRole == AccessibleRole.FRAME ||
+ accessibleRole == AccessibleRole.WINDOW) {
+ AccessibleComponent accessibleComponent = ac.getAccessibleComponent();
+ if (accessibleComponent == null) {
+ return null;
+ }
+ return accessibleComponent.getLocationOnScreen();
+ }
+ Accessible accessibleParent = ac.getAccessibleParent();
+ if (accessibleParent == null) {
+ return null;
+ }
+ ac = accessibleParent.getAccessibleContext();
+ }
+ return null;
+ }
+
+ /**
+ * Return the position of the object relative to the coordinate type
+ * 0 - Coordinates are relative to the screen.
+ * 1 - Coordinates are relative to the component's toplevel window.
+ * 2 - Coordinates are relative to the component's immediate parent.
+ *
+ * @param ac
+ * @param coordType
+ * @return
+ */
+ public static Point getLocationByCoordinateType(AccessibleContext ac, int coordType) {
+ assert EventQueue.isDispatchThread();
+
+ AccessibleComponent accessibleComponent = ac.getAccessibleComponent();
+ if (accessibleComponent == null) {
+ return null;
+ }
+
+ if (coordType == AtkCoordType.SCREEN) {
+ // Returns the location of the object on the screen.
+ return accessibleComponent.getLocationOnScreen();
+ }
+
+ if (coordType == AtkCoordType.WINDOW) {
+ Point windowLocation = getWindowLocation(ac);
+ if (windowLocation == null) {
+ return null;
+ }
+ Point componentLocationOnScreen = accessibleComponent.getLocationOnScreen();
+ if (componentLocationOnScreen == null) {
+ return null;
+ }
+ componentLocationOnScreen.translate(-windowLocation.x, -windowLocation.y);
+ return componentLocationOnScreen;
+ }
+
+ if (coordType == AtkCoordType.PARENT) {
+ // Gets the location of the object relative to the parent
+ // in the form of a point specifying the object's top-left
+ // corner in the screen's coordinate space.
+ return accessibleComponent.getLocation();
+ }
+
+ return null;
+ }
+
+ /* JNI upcalls section */
+
+ /**
+ * Factory method to create an AtkComponent instance from an AccessibleContext.
+ * Called from native code via JNI.
+ *
+ * @param ac the AccessibleContext to wrap
+ * @return a new AtkComponent instance, or null if creation fails
+ */
+ private static AtkComponent create_atk_component(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return new AtkComponent(ac);
+ }, null);
+ }
+
+ /**
+ * Checks whether the specified point is within the extent of the component.
+ * Called from native code via JNI.
+ *
+ * @param x x coordinate
+ * @param y y coordinate
+ * @param coordType specifies whether the coordinates are relative to the screen,
+ * the component's toplevel window, or the component's parent
+ * @return true if the specified point is within the extent of the component
+ */
+ private boolean contains(int x, int y, int coordType) {
+ AccessibleContext accessibleContext = accessibleContextWeakRef.get();
+ if (accessibleContext == null) {
+ return false;
+ }
+ AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get();
+ if (accessibleComponent == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ if (accessibleComponent.isVisible()) {
+ Point componentLocation = getLocationByCoordinateType(accessibleContext, coordType);
+ if (componentLocation == null) {
+ return false;
+ }
+
+ return accessibleComponent.contains(new Point(x - componentLocation.x, y - componentLocation.y));
+ }
+ return false;
+ }, false);
+ }
+
+ /**
+ * Gets a reference to the accessible child, if one exists, at the coordinate point
+ * specified by x and y.
+ * Called from native code via JNI.
+ *
+ * @param x x coordinate
+ * @param y y coordinate
+ * @param coordType specifies whether the coordinates are relative to the screen,
+ * the component's toplevel window, or the component's parent
+ * @return the AccessibleContext of the child at the specified point, or null if none exists
+ */
+ private AccessibleContext get_accessible_at_point(int x, int y, int coordType) {
+ AccessibleContext accessibleContext = accessibleContextWeakRef.get();
+ if (accessibleContext == null) {
+ return null;
+ }
+ AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get();
+ if (accessibleComponent == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ if (accessibleComponent.isVisible()) {
+ Point componentLocation = getLocationByCoordinateType(accessibleContext, coordType);
+ if (componentLocation == null) {
+ return null;
+ }
+
+ Accessible accessibleAt = accessibleComponent.getAccessibleAt(new Point(x - componentLocation.x, y - componentLocation.y));
+ if (accessibleAt == null) {
+ return null;
+ }
+ AccessibleContext accessibleContextAt = accessibleAt.getAccessibleContext();
+ if (accessibleContextAt != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContextAt);
+ }
+ return accessibleContextAt;
+ }
+ return null;
+ }, null);
+ }
+
+ /**
+ * Grabs focus for this component.
+ * Called from native code via JNI.
+ *
+ * @return true if successful, false otherwise
+ */
+ private boolean grab_focus() {
+ AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get();
+ if (accessibleComponent == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ if (!accessibleComponent.isFocusTraversable()) {
+ return false;
+ }
+ accessibleComponent.requestFocus();
+ return true;
+ }, false);
+ }
+
+ /**
+ * Sets the extents of the component.
+ * Called from native code via JNI.
+ *
+ * @param newXByCoordType x coordinate
+ * @param newYByCoordType y coordinate
+ * @param width width to set for the component
+ * @param height height to set for the component
+ * @param coordType specifies whether the coordinates are relative to the screen,
+ * the component's toplevel window, or the component's parent
+ * @return true if the extents were set successfully, false otherwise
+ */
+ private boolean set_extents(int newXByCoordType, int newYByCoordType, int width, int height, int coordType) {
+ AccessibleContext accessibleContext = accessibleContextWeakRef.get();
+ if (accessibleContext == null) {
+ return false;
+ }
+ AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get();
+ if (accessibleComponent == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ if (accessibleComponent.isVisible()) {
+ Point locationByCoordType = getLocationByCoordinateType(accessibleContext, coordType);
+ if (locationByCoordType == null) {
+ return false;
+ }
+
+ Point locationByParent = accessibleComponent.getLocation();
+ if (locationByParent == null) {
+ return false;
+ }
+
+ accessibleComponent.setBounds(new Rectangle(locationByParent.x + (newXByCoordType - locationByCoordType.x), locationByParent.y + (newYByCoordType - locationByCoordType.y), width, height));
+ return true;
+ }
+ return false;
+ }, false);
+ }
+
+ /**
+ * Gets the rectangle which gives the extent of the component.
+ * Called from native code via JNI.
+ *
+ * @param coordType specifies whether the coordinates are relative to the screen,
+ * the component's toplevel window, or the component's parent
+ * @return the Rectangle representing the component's extent, or null if it cannot be obtained
+ */
+ private Rectangle get_extents(int coordType) {
+ AccessibleContext accessibleContext = accessibleContextWeakRef.get();
+ if (accessibleContext == null) {
+ return null;
+ }
+ AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get();
+ if (accessibleComponent == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ if (accessibleComponent.isVisible()) {
+ Dimension dimension = accessibleComponent.getSize();
+ if (dimension == null) {
+ return null;
+ }
+ Point componentLocation = getLocationByCoordinateType(accessibleContext, coordType);
+ if (componentLocation == null) {
+ return null;
+ }
+
+ return new Rectangle(componentLocation.x, componentLocation.y, dimension.width, dimension.height);
+ }
+ return null;
+ }, null);
+ }
+
+ /**
+ * Sets the position of the component.
+ * Called from native code via JNI.
+ *
+ * @param newXByCoordType x coordinate
+ * @param newYByCoordType y coordinate
+ * @param coordType specifies whether the coordinates are relative to the screen,
+ * the component's toplevel window, or the component's parent
+ * @return true if the extents were set successfully, false otherwise
+ */
+ private boolean set_position(int newXByCoordType, int newYByCoordType, int coordType) {
+ AccessibleContext accessibleContext = accessibleContextWeakRef.get();
+ if (accessibleContext == null) {
+ return false;
+ }
+ AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get();
+ if (accessibleComponent == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ if (accessibleComponent.isVisible()) {
+ Point locationByCoordType = getLocationByCoordinateType(accessibleContext, coordType);
+ if (locationByCoordType == null) {
+ return false;
+ }
+
+ Point locationByParent = accessibleComponent.getLocation();
+ if (locationByParent == null) {
+ return false;
+ }
+
+ accessibleComponent.setLocation(new Point(locationByParent.x + (newXByCoordType - locationByCoordType.x), locationByParent.y + (newYByCoordType - locationByCoordType.y)));
+ return true;
+ }
+ return false;
+ }, false);
+ }
+
+ /**
+ * Sets the size of the component.
+ * Called from native code via JNI.
+ *
+ * @param width width to set for the component
+ * @param height height to set for the component
+ * @return true if the size was set successfully, false otherwise
+ */
+ private boolean set_size(int width, int height) {
+ AccessibleComponent accessibleComponent = accessibleComponentWeakRef.get();
+ if (accessibleComponent == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ if (accessibleComponent.isVisible()) {
+ accessibleComponent.setSize(new Dimension(width, height));
+ return true;
+ }
+ return false;
+ }, false);
+ }
+
+ /**
+ * Gets the AtkLayer of the component based on AccessibleRole.
+ * Called from native code via JNI.
+ *
+ * @return an int representing the AtkLayer of the component, or AtkLayer.INVALID if an error occurs
+ */
+ private int get_layer() {
+ AccessibleContext accessibleContext = accessibleContextWeakRef.get();
+ if (accessibleContext == null) {
+ return AtkLayer.INVALID;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ AccessibleRole accessibleRole = accessibleContext.getAccessibleRole();
+ if (accessibleRole == AccessibleRole.MENU ||
+ accessibleRole == AccessibleRole.MENU_ITEM ||
+ accessibleRole == AccessibleRole.POPUP_MENU) {
+ return AtkLayer.POPUP;
+ }
+ if (accessibleRole == AccessibleRole.INTERNAL_FRAME) {
+ return AtkLayer.MDI;
+ }
+ if (accessibleRole == AccessibleRole.GLASS_PANE) {
+ return AtkLayer.OVERLAY;
+ }
+ if (accessibleRole == AccessibleRole.CANVAS ||
+ accessibleRole == AccessibleRole.ROOT_PANE ||
+ accessibleRole == AccessibleRole.LAYERED_PANE) {
+ return AtkLayer.CANVAS;
+ }
+ if (accessibleRole == AccessibleRole.WINDOW) {
+ return AtkLayer.WINDOW;
+ }
+ return AtkLayer.WIDGET;
+ }, AtkLayer.INVALID);
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkCoordType.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkCoordType.java
new file mode 100644
index 000000000000..d2d27837aad6
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkCoordType.java
@@ -0,0 +1,40 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+public final class AtkCoordType {
+ /**
+ * Coordinates are relative to the screen.
+ */
+ public static final int SCREEN = 0;
+
+ /**
+ * Coordinates are relative to the component's toplevel window.
+ */
+ public static final int WINDOW = 1;
+
+ /**
+ * Coordinates are relative to the component's immediate parent.
+ */
+ public static final int PARENT = 2;
+
+ private AtkCoordType() {
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkEditableText.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkEditableText.java
new file mode 100644
index 000000000000..d8a2d0964173
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkEditableText.java
@@ -0,0 +1,212 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.awt.Toolkit;
+import java.awt.datatransfer.StringSelection;
+import javax.swing.text.*;
+import java.lang.ref.WeakReference;
+import java.awt.EventQueue;
+
+/**
+ * The ATK EditableText interface implementation for Java accessibility.
+ *
+ * This class provides a bridge between Java's AccessibleEditableText interface
+ * and the ATK (Accessibility Toolkit) editable text interface.
+ */
+public class AtkEditableText extends AtkText {
+
+ private final WeakReference accessibleEditableTextWeakRef;
+
+ private AtkEditableText(AccessibleContext ac) {
+ super(ac);
+
+ assert EventQueue.isDispatchThread();
+
+ AccessibleEditableText accessibleEditableText = ac.getAccessibleEditableText();
+ if (accessibleEditableText == null) {
+ throw new IllegalArgumentException("AccessibleContext must have AccessibleEditableText");
+ }
+
+ accessibleEditableTextWeakRef = new WeakReference(accessibleEditableText);
+ }
+
+ /* JNI upcalls section */
+
+ /**
+ * Factory method to create an AtkEditableText instance from an AccessibleContext.
+ * Called from native code via JNI.
+ *
+ * @param ac the AccessibleContext to wrap
+ * @return a new AtkEditableText instance, or null if creation fails
+ */
+ private static AtkEditableText create_atk_editable_text(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return new AtkEditableText(ac);
+ }, null);
+ }
+
+ /**
+ * Sets the text contents to the specified string.
+ * Called from native code via JNI.
+ *
+ * @param textContent the string to set as the text contents
+ */
+ private void set_text_contents(String textContent) {
+ AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get();
+ if (accessibleEditableText == null) {
+ return;
+ }
+
+ AtkUtil.invokeInSwing(() -> {
+ accessibleEditableText.setTextContents(textContent);
+ });
+ }
+
+ /**
+ * Inserts text at the specified position.
+ * Called from native code via JNI.
+ *
+ * @param textToInsert the string to insert
+ * @param position the position at which to insert the text
+ */
+ private void insert_text(String textToInsert, int position) {
+ AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get();
+ if (accessibleEditableText == null) {
+ return;
+ }
+
+ if (position < 0) {
+ position = 0;
+ }
+
+ final int finalPosition = position;
+ AtkUtil.invokeInSwing(() -> {
+ accessibleEditableText.insertTextAtIndex(finalPosition, textToInsert);
+ });
+ }
+
+ /**
+ * Copies text from the specified start and end positions to the system clipboard.
+ * Called from native code via JNI.
+ *
+ * @param start the start position
+ * @param end the end position
+ */
+ private void copy_text(int start, int end) {
+ AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get();
+ if (accessibleEditableText == null) {
+ return;
+ }
+
+ int charCount = AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleEditableText.getCharCount();
+ }, -1);
+
+ if (charCount == -1) {
+ return;
+ }
+
+ final int rightStart = getRightStart(start);
+ final int rightEnd = getRightEnd(rightStart, end, charCount);
+ AtkUtil.invokeInSwing(() -> {
+ String textRange = accessibleEditableText.getTextRange(rightStart, rightEnd);
+ if (textRange != null) {
+ StringSelection stringSelection = new StringSelection(textRange);
+ Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, stringSelection);
+ }
+ });
+ }
+
+ /**
+ * Cuts text from the specified start and end positions.
+ * Called from native code via JNI.
+ *
+ * @param start the start position
+ * @param end the end position
+ */
+ private void cut_text(int start, int end) {
+ AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get();
+ if (accessibleEditableText == null) {
+ return;
+ }
+
+ AtkUtil.invokeInSwing(() -> {
+ accessibleEditableText.cut(start, end);
+ });
+ }
+
+ /**
+ * Deletes text from the specified start and end positions.
+ * Called from native code via JNI.
+ *
+ * @param start the start position
+ * @param end the end position
+ */
+ private void delete_text(int start, int end) {
+ AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get();
+ if (accessibleEditableText == null) {
+ return;
+ }
+
+ AtkUtil.invokeInSwing(() -> {
+ accessibleEditableText.delete(start, end);
+ });
+ }
+
+ /**
+ * Pastes text from the system clipboard at the specified position.
+ * Called from native code via JNI.
+ *
+ * @param position the position at which to paste the text
+ */
+ private void paste_text(int position) {
+ AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get();
+ if (accessibleEditableText == null) {
+ return;
+ }
+
+ AtkUtil.invokeInSwing(() -> {
+ accessibleEditableText.paste(position);
+ });
+ }
+
+ /**
+ * Sets attributes for the text between two indices.
+ * Called from native code via JNI.
+ *
+ * @param attributeSet the AttributeSet for the text
+ * @param start the start index of the text
+ * @param end the end index of the text
+ * @return true if attributes were successfully set, false otherwise
+ */
+ private boolean set_run_attributes(AttributeSet attributeSet, int start, int end) {
+ AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get();
+ if (accessibleEditableText == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ accessibleEditableText.setAttributes(start, end, attributeSet);
+ return true;
+ }, false);
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkHyperlink.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkHyperlink.java
new file mode 100644
index 000000000000..9590ee41880c
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkHyperlink.java
@@ -0,0 +1,171 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.awt.EventQueue;
+import java.lang.ref.WeakReference;
+
+/**
+ * The ATK Hyperlink implementation for Java accessibility.
+ *
+ * This class provides a bridge between Java's AccessibleHyperlink interface
+ * and the ATK (Accessibility Toolkit) hyperlink interface.
+ */
+public class AtkHyperlink {
+
+ private final WeakReference accessibleHyperlinkWeakRef;
+
+ private AtkHyperlink(AccessibleHyperlink accessibleHyperlink) {
+ assert EventQueue.isDispatchThread();
+ accessibleHyperlinkWeakRef = new WeakReference(accessibleHyperlink);
+ }
+
+ static AtkHyperlink createAtkHyperlink(AccessibleHyperlink accessibleHyperlink) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return new AtkHyperlink(accessibleHyperlink);
+ }, null);
+ }
+
+ /* JNI upcalls section */
+
+ /**
+ * Gets the URI associated with the anchor specified by the index.
+ * Called from native code via JNI.
+ *
+ * @param index the zero-based index specifying the desired anchor
+ * @return a string specifying the URI, or null if not available
+ */
+ private String get_uri(int index) {
+ AccessibleHyperlink accessibleHyperlink = accessibleHyperlinkWeakRef.get();
+ if (accessibleHyperlink == null || index < 0) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Object accessibleActionObject = accessibleHyperlink.getAccessibleActionObject(index);
+ if (accessibleActionObject != null) {
+ return accessibleActionObject.toString();
+ }
+ return null;
+ }, null);
+ }
+
+ /**
+ * Returns the accessible object associated with this hyperlink's nth anchor.
+ * Called from native code via JNI.
+ *
+ * @param index the zero-based index specifying the desired anchor
+ * @return the AccessibleContext associated with this hyperlink's index-th anchor,
+ * or null if not available
+ */
+ private AccessibleContext get_object(int index) {
+ AccessibleHyperlink accessibleHyperlink = accessibleHyperlinkWeakRef.get();
+ if (accessibleHyperlink == null || index < 0) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Object anchor = accessibleHyperlink.getAccessibleActionAnchor(index);
+ if (anchor instanceof Accessible accessible) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ }
+ return accessibleContext;
+ }
+ return null;
+ }, null);
+ }
+
+ /**
+ * Gets the index with the hypertext document at which this link ends.
+ * Called from native code via JNI.
+ *
+ * @return the index with the hypertext document at which this link ends,
+ * or 0 if an error happened.
+ */
+ private int get_end_index() {
+ AccessibleHyperlink accessibleHyperlink = accessibleHyperlinkWeakRef.get();
+ if (accessibleHyperlink == null) {
+ return 0;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleHyperlink.getEndIndex();
+ }, 0);
+ }
+
+ /**
+ * Gets the index with the hypertext document at which this link begins.
+ * Called from native code via JNI.
+ *
+ * @return the index with the hypertext document at which this link begins,
+ * or 0 if an error happened
+ */
+ private int get_start_index() {
+ AccessibleHyperlink accessibleHyperlink = accessibleHyperlinkWeakRef.get();
+ if (accessibleHyperlink == null) {
+ return 0;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleHyperlink.getStartIndex();
+ }, 0);
+ }
+
+ /**
+ * Determines whether this link is still valid.
+ * Called from native code via JNI.
+ *
+ * Since the document that a link is associated with may have changed,
+ * this method returns true if the link is still valid (with respect to
+ * the document it references) and false otherwise.
+ *
+ * @return true if the link is still valid, false otherwise
+ */
+ private boolean is_valid() {
+ AccessibleHyperlink accessibleHyperlink = accessibleHyperlinkWeakRef.get();
+ if (accessibleHyperlink == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleHyperlink.isValid();
+ }, false);
+ }
+
+ /**
+ * Gets the number of anchors associated with this hyperlink.
+ * Called from native code via JNI (jaw_hyperlink_get_n_anchors in jawhyperlink.c).
+ *
+ * @return the number of anchors associated with this hyperlink
+ */
+ private int get_n_anchors() {
+ AccessibleHyperlink accessibleHyperlink = accessibleHyperlinkWeakRef.get();
+ if (accessibleHyperlink == null) {
+ return 0;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleHyperlink.getAccessibleActionCount();
+ }, 0);
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkHypertext.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkHypertext.java
new file mode 100644
index 000000000000..9b99e60ca5e7
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkHypertext.java
@@ -0,0 +1,133 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.lang.ref.WeakReference;
+import java.awt.EventQueue;
+
+/**
+ * The ATK Hypertext interface implementation for Java accessibility.
+ *
+ * This class provides a bridge between Java's AccessibleHypertext interface
+ * and the ATK (Accessibility Toolkit) hypertext interface.
+ * Hypertext allows navigation through documents containing
+ * embedded links or hyperlinks.
+ */
+public class AtkHypertext extends AtkText {
+
+ private final WeakReference accessibleHypertextRef;
+
+ private AtkHypertext(AccessibleContext ac) {
+ super(ac);
+
+ assert EventQueue.isDispatchThread();
+
+ AccessibleText accessibleText = ac.getAccessibleText();
+ if (accessibleText instanceof AccessibleHypertext accessibleHypertext) {
+ accessibleHypertextRef = new WeakReference(accessibleHypertext);
+ } else {
+ throw new IllegalArgumentException("AccessibleContext must have AccessibleHypertext");
+ }
+ }
+
+ /* JNI upcalls section */
+
+ /**
+ * Factory method to create an AtkHypertext instance from an AccessibleContext.
+ * Called from native code via JNI.
+ *
+ * @param ac the AccessibleContext to wrap
+ * @return a new AtkHypertext instance, or null if creation fails
+ */
+ private static AtkHypertext create_atk_hypertext(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return new AtkHypertext(ac);
+ }, null);
+ }
+
+ /**
+ * Gets the link in this hypertext document at the specified index.
+ * Called from native code via JNI.
+ *
+ * @param linkIndex an integer specifying the desired link (zero-based)
+ * @return the AtkHyperlink at the specified index, or null if not available
+ */
+ private AtkHyperlink get_link(int linkIndex) {
+ if (accessibleHypertextRef == null) {
+ return null;
+ }
+ AccessibleHypertext accessibleHypertext = accessibleHypertextRef.get();
+ if (accessibleHypertext == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ AccessibleHyperlink link = accessibleHypertext.getLink(linkIndex);
+ if (link != null) {
+ return AtkHyperlink.createAtkHyperlink(link);
+ }
+ return null;
+ }, null);
+ }
+
+ /**
+ * Gets the number of links within this hypertext document.
+ * Called from native code via JNI.
+ *
+ * @return the number of links within this hypertext document
+ */
+ private int get_n_links() {
+ if (accessibleHypertextRef == null) {
+ return 0;
+ }
+ AccessibleHypertext accessibleHypertext = accessibleHypertextRef.get();
+ if (accessibleHypertext == null) {
+ return 0;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleHypertext.getLinkCount();
+ }, 0);
+ }
+
+ /**
+ * Gets the index into the array of hyperlinks that is associated with
+ * the character specified by charIndex.
+ * Called from native code via JNI.
+ *
+ * @param charIndex a character index
+ * @return an index into the array of hyperlinks in this hypertext,
+ * or -1 if there is no hyperlink associated with this character
+ */
+ private int get_link_index(int charIndex) {
+ if (accessibleHypertextRef == null) {
+ return -1;
+ }
+ AccessibleHypertext accessibleHypertext = accessibleHypertextRef.get();
+ if (accessibleHypertext == null) {
+ return -1;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleHypertext.getLinkIndex(charIndex);
+ }, -1);
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkImage.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkImage.java
new file mode 100644
index 000000000000..fdcc0754aa0b
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkImage.java
@@ -0,0 +1,149 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.awt.Point;
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.lang.ref.WeakReference;
+import java.awt.EventQueue;
+
+/**
+ * The ATK Image interface implementation for Java accessibility.
+ *
+ * This class provides a bridge between Java's AccessibleIcon interface
+ * and the ATK (Accessibility Toolkit) image interface.
+ * AtkImage should be implemented by components which display
+ * image/pixmap content on-screen, such as icons, buttons with icons,
+ * toolbar elements, and image viewing panes.
+ *
+ * This interface provides two types of information: coordinate information
+ * (useful for screen review mode and onscreen magnifiers) and descriptive
+ * information (for alternative text-only presentation).
+ */
+public class AtkImage {
+
+ private final WeakReference accessibleContextWeakRef;
+ private final WeakReference accessibleIcons;
+
+ private AtkImage(AccessibleContext ac) {
+ assert EventQueue.isDispatchThread();
+
+ this.accessibleContextWeakRef = new WeakReference(ac);
+ this.accessibleIcons = new WeakReference(ac.getAccessibleIcon());
+ }
+
+ // JNI upcalls section
+
+ /**
+ * Factory method to create an AtkImage instance from an AccessibleContext.
+ * Called from native code via JNI.
+ *
+ * @param ac the AccessibleContext to wrap
+ * @return a new AtkImage instance, or null if creation fails
+ */
+ private static AtkImage create_atk_image(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return new AtkImage(ac);
+ }, null);
+ }
+
+ /**
+ * Gets the position of the image in the form of a point specifying the
+ * image's top-left corner.
+ * Called from native code via JNI.
+ *
+ * @param coordType specifies whether the coordinates are relative to the screen
+ * or to the component's top level window
+ * @return a Point representing the image position (x, y coordinates), or null
+ * if the position cannot be obtained (e.g., missing support).
+ */
+ private Point get_image_position(int coordType) {
+ AccessibleContext ac = accessibleContextWeakRef.get();
+ if (ac == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return AtkComponent.getLocationByCoordinateType(ac, coordType);
+ }, null);
+ }
+
+ /**
+ * Gets a textual description of this image.
+ * Called from native code via JNI.
+ *
+ * @return a string representing the image description, or null if there is
+ * no description available or an error occurs
+ */
+ private String get_image_description() {
+ AccessibleIcon[] accessibleIcons = this.accessibleIcons.get();
+ if (accessibleIcons == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ String description = null;
+ if (accessibleIcons.length > 0) {
+ description = accessibleIcons[0].getAccessibleIconDescription();
+ }
+ return description;
+ }, null);
+ }
+
+ /**
+ * Gets the width and height in pixels for the specified image.
+ * Called from native code via JNI.
+ *
+ * @return a Dimension containing the width and height of the image.
+ * Returns a Dimension with width and height set to -1 if the values
+ * cannot be obtained (for instance, if the object is not onscreen).
+ * If AccessibleIcon information is available, uses that; otherwise,
+ * falls back to the component's bounds.
+ */
+ private Dimension get_image_size() {
+ Dimension dimension = new Dimension(-1, -1);
+
+ AccessibleContext ac = accessibleContextWeakRef.get();
+ if (ac == null) {
+ return dimension;
+ }
+
+ AccessibleIcon[] accessibleIcons = this.accessibleIcons.get();
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ if (accessibleIcons != null && accessibleIcons.length > 0) {
+ dimension.height = accessibleIcons[0].getAccessibleIconHeight();
+ dimension.width = accessibleIcons[0].getAccessibleIconWidth();
+ } else {
+ AccessibleComponent accessibleComponent = ac.getAccessibleComponent();
+ if (accessibleComponent != null) {
+ Rectangle rectangle = accessibleComponent.getBounds();
+ if (rectangle != null) {
+ dimension.height = rectangle.height;
+ dimension.width = rectangle.width;
+ }
+ }
+ }
+ return dimension;
+ }, dimension);
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkInterface.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkInterface.java
new file mode 100644
index 000000000000..93b49266b252
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkInterface.java
@@ -0,0 +1,98 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import java.lang.annotation.Native;
+
+public interface AtkInterface {
+ /**
+ * The ATK interface provided by UI components
+ * which the user can activate/interact with.
+ */
+ @Native
+ int INTERFACE_ACTION = 0x00000001;
+
+ /**
+ * The ATK interface provided by UI components
+ * which occupy a physical area on the screen.
+ * which the user can activate/interact with.
+ */
+ @Native
+ int INTERFACE_COMPONENT = 0x00000002;
+
+ /**
+ * The ATK interface which represents the toplevel container for document content.
+ */
+ @Native
+ int INTERFACE_DOCUMENT = 0x00000004;
+
+ /**
+ * The ATK interface implemented by components containing user-editable text content.
+ */
+ @Native
+ int INTERFACE_EDITABLE_TEXT = 0x00000008;
+
+ /**
+ * The ATK interface which provides standard mechanism for manipulating hyperlinks.
+ */
+ @Native
+ int INTERFACE_HYPERTEXT = 0x00000020;
+
+ /**
+ * The ATK Interface implemented by components
+ * which expose image or pixmap content on-screen.
+ */
+ @Native
+ int INTERFACE_IMAGE = 0x00000040;
+
+ /**
+ * The ATK interface implemented by container objects
+ * whose AtkObject children can be selected.
+ */
+ @Native
+ int INTERFACE_SELECTION = 0x00000080;
+
+ /**
+ * The ATK interface implemented for UI components
+ * which contain tabular or row/column information.
+ */
+ @Native
+ int INTERFACE_TABLE = 0x00000200;
+
+ /**
+ * The ATK interface implemented for a cell inside a two-dimentional AtkTable.
+ */
+ @Native
+ int INTERFACE_TABLE_CELL = 0x00000400;
+
+ /**
+ * The ATK interface implemented by components with text content.
+ */
+ @Native
+ int INTERFACE_TEXT = 0x00000800;
+
+ /**
+ * The ATK interface implemented by valuators and components
+ * which display or select a value from a bounded range of values.
+ */
+ @Native
+ int INTERFACE_VALUE = 0x00001000;
+}
+
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkKeyEvent.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkKeyEvent.java
new file mode 100644
index 000000000000..9358247d97bc
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkKeyEvent.java
@@ -0,0 +1,330 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import java.awt.event.*;
+import java.util.HashMap;
+
+import sun.awt.AWTAccessor;
+
+/**
+ * Repeats structure of Atk.KeyEventStruct encapsulating information about a key event.
+ * Using to create Atk.KeyEventStruct instance.
+ *
+ * struct AtkKeyEventStruct {
+ * gint type;
+ * guint state;
+ * guint keyval;
+ * gint length;
+ * gchar* string;
+ * guint16 keycode;
+ * guint32 timestamp;
+ * }
+ */
+public class AtkKeyEvent {
+
+ // Symbols mapped to X11 keysym names
+ private static final HashMap nonAlphaNumericMap;
+
+ private static final int ATK_KEY_EVENT_PRESSED = 0;
+ private static final int ATK_KEY_EVENT_RELEASED = 1;
+
+ static {
+ // Non-alphanumeric symbols that need to be mapped to X11 keysym names
+ nonAlphaNumericMap = new HashMap<>(40);
+ nonAlphaNumericMap.put("!", "exclam");
+ nonAlphaNumericMap.put("@", "at");
+ nonAlphaNumericMap.put("#", "numbersign");
+ nonAlphaNumericMap.put("$", "dollar");
+ nonAlphaNumericMap.put("%", "percent");
+ nonAlphaNumericMap.put("^", "asciicircum");
+ nonAlphaNumericMap.put("&", "ampersand");
+ nonAlphaNumericMap.put("*", "asterisk");
+ nonAlphaNumericMap.put("(", "parenleft");
+ nonAlphaNumericMap.put(")", "parenright");
+ nonAlphaNumericMap.put("-", "minus");
+ nonAlphaNumericMap.put("_", "underscore");
+ nonAlphaNumericMap.put("=", "equal");
+ nonAlphaNumericMap.put("+", "plus");
+ nonAlphaNumericMap.put("\\", "backslash");
+ nonAlphaNumericMap.put("|", "bar");
+ nonAlphaNumericMap.put("`", "grave");
+ nonAlphaNumericMap.put("~", "asciitilde");
+ nonAlphaNumericMap.put("[", "bracketleft");
+ nonAlphaNumericMap.put("{", "braceleft");
+ nonAlphaNumericMap.put("]", "bracketright");
+ nonAlphaNumericMap.put("}", "braceright");
+ nonAlphaNumericMap.put(";", "semicolon");
+ nonAlphaNumericMap.put(":", "colon");
+ nonAlphaNumericMap.put("'", "apostrophe");
+ nonAlphaNumericMap.put("\"", "quotedbl");
+ nonAlphaNumericMap.put(",", "comma");
+ nonAlphaNumericMap.put("<", "less");
+ nonAlphaNumericMap.put(".", "period");
+ nonAlphaNumericMap.put(">", "greater");
+ nonAlphaNumericMap.put("/", "slash");
+ nonAlphaNumericMap.put("?", "question");
+ }
+
+ private final int type;
+ private final boolean isShiftKeyDown;
+ private final boolean isCtrlKeyDown;
+ private final boolean isAltKeyDown;
+ private final boolean isMetaKeyDown;
+ private final boolean isAltGrKeyDown;
+ /* A keysym value corresponding to those used by GDK and X11. see /usr/X11/include/keysymdef.h.
+ * FIXME: in current implementation we get this value from GNOMEKeyInfo.gdkKeyCode that are defined manually GNOMEKeyMapping.initializeMap,
+ * doesn't look good.
+ */
+ private final int keyval;
+ /*
+ * Either a string approximating the text that would result
+ * from this keypress, or a symbolic name for this keypress.
+ */
+ private final String string;
+ // The raw hardware code that generated the key event.
+ private final int keycode;
+ // A timestamp in milliseconds indicating when the event occurred.
+ private final long timestamp;
+
+ public AtkKeyEvent(KeyEvent e) {
+ //type
+ switch (e.getID()) {
+ case KeyEvent.KEY_PRESSED:
+ case KeyEvent.KEY_TYPED:
+ type = ATK_KEY_EVENT_PRESSED; // 0
+ break;
+ case KeyEvent.KEY_RELEASED:
+ type = ATK_KEY_EVENT_RELEASED; // 1
+ break;
+ default:
+ type = ATK_KEY_EVENT_PRESSED; // 0
+ }
+
+ //modifiers
+ int modifierMask = e.getModifiersEx();
+ isShiftKeyDown = (modifierMask & InputEvent.SHIFT_DOWN_MASK) != 0;
+ isCtrlKeyDown = (modifierMask & InputEvent.CTRL_DOWN_MASK) != 0;
+ isAltKeyDown = (modifierMask & InputEvent.ALT_DOWN_MASK) != 0;
+ isMetaKeyDown = (modifierMask & InputEvent.META_DOWN_MASK) != 0;
+ isAltGrKeyDown = (modifierMask & InputEvent.ALT_GRAPH_DOWN_MASK) != 0;
+
+ GNOMEKeyMapping.GNOMEKeyInfo keyInfo = GNOMEKeyMapping.getKey(e);
+
+ int tempKeyval;
+ String tempString;
+ if (keyInfo != null) {
+ tempKeyval = keyInfo.gdkKeyCode();
+ tempString = keyInfo.gdkKeyString();
+ } else {
+ char keyChar = e.getKeyChar();
+ if (keyChar == KeyEvent.CHAR_UNDEFINED) {
+ tempKeyval = 0;
+ tempString = KeyEvent.getKeyText(e.getKeyCode());
+ if (tempString == null) tempString = "";
+ } else {
+ tempKeyval = keyChar;
+ tempString = String.valueOf(keyChar);
+ }
+ }
+
+ keycode = keyInfo != null ? keyInfo.gdkKeyCode() : e.getKeyCode();
+ timestamp = e.getWhen();
+
+ String nonAlphaNumericString = nonAlphaNumericMap.get(tempString);
+ if (nonAlphaNumericString != null) {
+ tempString = nonAlphaNumericString;
+ }
+
+ keyval = tempKeyval;
+ string = tempString;
+ }
+}
+
+class GNOMEKeyMapping {
+
+ private static HashMap keyMap = null;
+
+ /* Used to offset VK for NUMPAD keys that don't have a VK_KP_* equivalent.
+ * At present max VK_* value is 0x0000FFFF
+ * Also need to support Left/Right variations.
+ */
+ private static final int NUMPAD_OFFSET = 0xFEFE0000;
+ private static final int LEFT_OFFSET = 0xFEFD0000;
+ private static final int RIGHT_OFFSET = 0xFEFC0000;
+
+ static {
+ initializeMap();
+ }
+
+ private GNOMEKeyMapping() {
+ }
+
+ public static GNOMEKeyInfo getKey(KeyEvent e) {
+ GNOMEKeyInfo gdkKeyInfo;
+ int javaKeyCode = e.getKeyCode();
+ int javaKeyLocation = e.getKeyLocation();
+
+ if (javaKeyLocation == KeyEvent.KEY_LOCATION_NUMPAD)
+ javaKeyCode += NUMPAD_OFFSET;
+ else if (javaKeyLocation == KeyEvent.KEY_LOCATION_LEFT)
+ javaKeyCode += LEFT_OFFSET;
+ else if (javaKeyLocation == KeyEvent.KEY_LOCATION_RIGHT)
+ javaKeyCode += RIGHT_OFFSET;
+
+ if ((gdkKeyInfo = keyMap.get(javaKeyCode)) != null) {
+ return gdkKeyInfo;
+ } else {
+ return null;
+ }
+ }
+
+ private static void initializeMap() {
+ keyMap = new HashMap<>(146); // Currently only 110, so allocate 110 / 0.75
+
+ keyMap.put(KeyEvent.VK_COLON, new GNOMEKeyInfo(0x20a1, "ColonSign")); // GDK_ColonSign
+ keyMap.put(KeyEvent.VK_EURO_SIGN, new GNOMEKeyInfo(0x20ac, "EuroSign")); // GDK_EuroSign
+ keyMap.put(KeyEvent.VK_BACK_SPACE, new GNOMEKeyInfo(0xFF08, "BackSpace")); // GDK_BackSpace
+ keyMap.put(KeyEvent.VK_TAB, new GNOMEKeyInfo(0xFF09, "Tab")); // GDK_Tab
+ keyMap.put(KeyEvent.VK_CLEAR, new GNOMEKeyInfo(0xFF0B, "Clear")); // GDK_Clear
+ keyMap.put(KeyEvent.VK_ENTER, new GNOMEKeyInfo(0xFF0D, "Return")); // GDK_Return
+ keyMap.put(KeyEvent.VK_PAUSE, new GNOMEKeyInfo(0xFF13, "Pause")); // GDK_Pause
+ keyMap.put(KeyEvent.VK_SCROLL_LOCK, new GNOMEKeyInfo(0xFF14, "Scroll_Lock")); // GDK_Scroll_Lock
+ keyMap.put(KeyEvent.VK_ESCAPE, new GNOMEKeyInfo(0xFF1B, "Escape")); // GDK_Escape
+ keyMap.put(KeyEvent.VK_KANJI, new GNOMEKeyInfo(0xFF21, "Kanji")); // GDK_Kanji
+ keyMap.put(KeyEvent.VK_HIRAGANA, new GNOMEKeyInfo(0xFF25, "Hiragana")); // GDK_Hiragana
+ keyMap.put(KeyEvent.VK_KATAKANA, new GNOMEKeyInfo(0xFF26, "Katakana")); // GDK_Katakana
+ keyMap.put(KeyEvent.VK_KANA_LOCK, new GNOMEKeyInfo(0xFF2D, "Kana_Lock")); // GDK_Kana_Lock
+ keyMap.put(KeyEvent.VK_KANA, new GNOMEKeyInfo(0xFF2E, "Kana_Shift")); // GDK_Kana_Shift
+ keyMap.put(KeyEvent.VK_KANJI, new GNOMEKeyInfo(0xFF37, "Kanji_Bangou")); // GDK_Kanji_Bangou
+
+ keyMap.put(KeyEvent.VK_HOME, new GNOMEKeyInfo(0xFF50, "Home")); // GDK_Home
+ keyMap.put(KeyEvent.VK_LEFT, new GNOMEKeyInfo(0xFF51, "Left")); // GDK_Left
+ keyMap.put(KeyEvent.VK_UP, new GNOMEKeyInfo(0xFF52, "Up")); // GDK_Up
+ keyMap.put(KeyEvent.VK_RIGHT, new GNOMEKeyInfo(0xFF53, "Right")); // GDK_Right
+ keyMap.put(KeyEvent.VK_DOWN, new GNOMEKeyInfo(0xFF54, "Down")); // GDK_Down
+ keyMap.put(KeyEvent.VK_PAGE_UP, new GNOMEKeyInfo(0xFF55, "Page_Up")); // GDK_Page_Up
+ keyMap.put(KeyEvent.VK_PAGE_DOWN, new GNOMEKeyInfo(0xFF56, "Page_Down")); // GDK_Page_Down
+ keyMap.put(KeyEvent.VK_END, new GNOMEKeyInfo(0xFF57, "End")); // GDK_End
+ keyMap.put(KeyEvent.VK_PRINTSCREEN, new GNOMEKeyInfo(0xFF61, "Print")); // GDK_Print
+ keyMap.put(KeyEvent.VK_INSERT, new GNOMEKeyInfo(0xFF63, "Insert")); // GDK_Insert
+ keyMap.put(KeyEvent.VK_UNDO, new GNOMEKeyInfo(0xFF65, "Undo")); // GDK_Undo
+ keyMap.put(KeyEvent.VK_AGAIN, new GNOMEKeyInfo(0xFF66, "Redo")); // GDK_Redo
+ keyMap.put(KeyEvent.VK_FIND, new GNOMEKeyInfo(0xFF68, "Find")); // GDK_Find
+ keyMap.put(KeyEvent.VK_CANCEL, new GNOMEKeyInfo(0xFF69, "Cancel")); // GDK_Cancel
+ keyMap.put(KeyEvent.VK_HELP, new GNOMEKeyInfo(0xFF6A, "Help")); // GDK_Help
+ keyMap.put(KeyEvent.VK_ALT_GRAPH, new GNOMEKeyInfo(0xFF7E, "Mode_Switch")); // GDK_Mode_Switch
+ keyMap.put(KeyEvent.VK_NUM_LOCK, new GNOMEKeyInfo(0xFF7F, "Num_Lock")); // GDK_Num_Lock
+ keyMap.put(KeyEvent.VK_KP_LEFT, new GNOMEKeyInfo(0xFF96, "KP_Left")); // GDK_KP_Left
+ keyMap.put(KeyEvent.VK_KP_UP, new GNOMEKeyInfo(0xFF97, "KP_Up")); // GDK_KP_Up
+ keyMap.put(KeyEvent.VK_KP_RIGHT, new GNOMEKeyInfo(0xFF98, "KP_Right")); // GDK_KP_Right
+ keyMap.put(KeyEvent.VK_KP_DOWN, new GNOMEKeyInfo(0xFF99, "KP_Down")); // GDK_KP_Dow
+
+ // For Key's that are NUMPAD, but no VK_KP_* equivalent exists
+ // NOTE: Some syms do have VK_KP equivalents, but may or may not have
+ // KeyLocation() set to NUMPAD - so these are in twice with and
+ // without the offset..
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUM_LOCK, new GNOMEKeyInfo(0xFF7F, "Num_Lock")); // GDK_Num_Lock
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_ENTER, new GNOMEKeyInfo(0xFF8D, "KP_Enter")); // GDK_KP_Enter
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_HOME, new GNOMEKeyInfo(0xFF95, "KP_Home")); // GDK_KP_Home
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_KP_LEFT, new GNOMEKeyInfo(0xFF96, "KP_Left")); // GDK_KP_Left
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_KP_UP, new GNOMEKeyInfo(0xFF97, "KP_Up")); // GDK_KP_Up
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_KP_RIGHT, new GNOMEKeyInfo(0xFF98, "KP_Right")); // GDK_KP_Right
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_KP_DOWN, new GNOMEKeyInfo(0xFF99, "KP_Down")); // GDK_KP_Down
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_PAGE_UP, new GNOMEKeyInfo(0xFF9A, "KP_Page_Up")); // GDK_KP_Page_Up
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_PAGE_DOWN, new GNOMEKeyInfo(0xFF9B, "KP_Page_Down")); // GDK_KP_Page_Down
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_END, new GNOMEKeyInfo(0xFF9C, "KP_End")); // GDK_KP_End
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_BEGIN, new GNOMEKeyInfo(0xFF9D, "KP_Begin")); // GDK_KP_Begin
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_INSERT, new GNOMEKeyInfo(0xFF9E, "KP_Insert")); // GDK_KP_Insert
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_DELETE, new GNOMEKeyInfo(0xFF9F, "KP_Delete")); // GDK_KP_Delete
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_MULTIPLY, new GNOMEKeyInfo(0xFFAA, "KP_Multiply")); // GDK_KP_Multiply
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_ADD, new GNOMEKeyInfo(0xFFAB, "KP_Add")); // GDK_KP_Add
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_SEPARATOR, new GNOMEKeyInfo(0xFFAC, "KP_Separator")); // GDK_KP_Separator
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_SUBTRACT, new GNOMEKeyInfo(0xFFAD, "KP_Subtract")); // GDK_KP_Subtract
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_DECIMAL, new GNOMEKeyInfo(0xFFAE, "KP_Decimal")); // GDK_KP_Decimal
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_DIVIDE, new GNOMEKeyInfo(0xFFAF, "KP_Divide")); // GDK_KP_Divide
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD0, new GNOMEKeyInfo(0xFFB0, "KP_0")); // GDK_KP_0
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD1, new GNOMEKeyInfo(0xFFB1, "KP_1")); // GDK_KP_1
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD2, new GNOMEKeyInfo(0xFFB2, "KP_2")); // GDK_KP_2
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD3, new GNOMEKeyInfo(0xFFB3, "KP_3")); // GDK_KP_3
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD4, new GNOMEKeyInfo(0xFFB4, "KP_4")); // GDK_KP_4
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD5, new GNOMEKeyInfo(0xFFB5, "KP_5")); // GDK_KP_5
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD6, new GNOMEKeyInfo(0xFFB6, "KP_6")); // GDK_KP_6
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD7, new GNOMEKeyInfo(0xFFB7, "KP_7")); // GDK_KP_7
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD8, new GNOMEKeyInfo(0xFFB8, "KP_8")); // GDK_KP_8
+ keyMap.put(NUMPAD_OFFSET + KeyEvent.VK_NUMPAD9, new GNOMEKeyInfo(0xFFB9, "KP_9")); // GDK_KP_9
+
+ keyMap.put(KeyEvent.VK_NUMPAD0, new GNOMEKeyInfo(0xFFB0, "KP_0")); // GDK_KP_0
+ keyMap.put(KeyEvent.VK_NUMPAD1, new GNOMEKeyInfo(0xFFB1, "KP_1")); // GDK_KP_1
+ keyMap.put(KeyEvent.VK_NUMPAD2, new GNOMEKeyInfo(0xFFB2, "KP_2")); // GDK_KP_2
+ keyMap.put(KeyEvent.VK_NUMPAD3, new GNOMEKeyInfo(0xFFB3, "KP_3")); // GDK_KP_3
+ keyMap.put(KeyEvent.VK_NUMPAD4, new GNOMEKeyInfo(0xFFB4, "KP_4")); // GDK_KP_4
+ keyMap.put(KeyEvent.VK_NUMPAD5, new GNOMEKeyInfo(0xFFB5, "KP_5")); // GDK_KP_5
+ keyMap.put(KeyEvent.VK_NUMPAD6, new GNOMEKeyInfo(0xFFB6, "KP_6")); // GDK_KP_6
+ keyMap.put(KeyEvent.VK_NUMPAD7, new GNOMEKeyInfo(0xFFB7, "KP_7")); // GDK_KP_7
+ keyMap.put(KeyEvent.VK_NUMPAD8, new GNOMEKeyInfo(0xFFB8, "KP_8")); // GDK_KP_8
+ keyMap.put(KeyEvent.VK_NUMPAD9, new GNOMEKeyInfo(0xFFB9, "KP_9")); // GDK_KP_9
+ keyMap.put(KeyEvent.VK_F1, new GNOMEKeyInfo(0xFFBE, "F1")); // GDK_F1
+ keyMap.put(KeyEvent.VK_F2, new GNOMEKeyInfo(0xFFBF, "F2")); // GDK_F2
+ keyMap.put(KeyEvent.VK_F3, new GNOMEKeyInfo(0xFFC0, "F3")); // GDK_F3
+ keyMap.put(KeyEvent.VK_F4, new GNOMEKeyInfo(0xFFC1, "F4")); // GDK_F4
+ keyMap.put(KeyEvent.VK_F5, new GNOMEKeyInfo(0xFFC2, "F5")); // GDK_F5
+ keyMap.put(KeyEvent.VK_F6, new GNOMEKeyInfo(0xFFC3, "F6")); // GDK_F6
+ keyMap.put(KeyEvent.VK_F7, new GNOMEKeyInfo(0xFFC4, "F7")); // GDK_F7
+ keyMap.put(KeyEvent.VK_F8, new GNOMEKeyInfo(0xFFC5, "F8")); // GDK_F8
+ keyMap.put(KeyEvent.VK_F9, new GNOMEKeyInfo(0xFFC6, "F9")); // GDK_F9
+ keyMap.put(KeyEvent.VK_F10, new GNOMEKeyInfo(0xFFC7, "F10")); // GDK_F10
+ keyMap.put(KeyEvent.VK_F11, new GNOMEKeyInfo(0xFFC8, "F11")); // GDK_F11
+ keyMap.put(KeyEvent.VK_F12, new GNOMEKeyInfo(0xFFC9, "F12")); // GDK_F12
+ keyMap.put(KeyEvent.VK_F13, new GNOMEKeyInfo(0xFFCA, "F13")); // GDK_F13
+ keyMap.put(KeyEvent.VK_F14, new GNOMEKeyInfo(0xFFCB, "F14")); // GDK_F14
+ keyMap.put(KeyEvent.VK_F15, new GNOMEKeyInfo(0xFFCC, "F15")); // GDK_F15
+ keyMap.put(KeyEvent.VK_F16, new GNOMEKeyInfo(0xFFCD, "F16")); // GDK_F16
+ keyMap.put(KeyEvent.VK_F17, new GNOMEKeyInfo(0xFFCE, "F17")); // GDK_F17
+ keyMap.put(KeyEvent.VK_F18, new GNOMEKeyInfo(0xFFCF, "F18")); // GDK_F18
+ keyMap.put(KeyEvent.VK_F19, new GNOMEKeyInfo(0xFFD0, "F19")); // GDK_F19
+ keyMap.put(KeyEvent.VK_F20, new GNOMEKeyInfo(0xFFD1, "F20")); // GDK_F20
+ keyMap.put(KeyEvent.VK_F21, new GNOMEKeyInfo(0xFFD2, "F21")); // GDK_F21
+ keyMap.put(KeyEvent.VK_F22, new GNOMEKeyInfo(0xFFD3, "F22")); // GDK_F22
+ keyMap.put(KeyEvent.VK_F23, new GNOMEKeyInfo(0xFFD4, "F23")); // GDK_F23
+ keyMap.put(KeyEvent.VK_F24, new GNOMEKeyInfo(0xFFD5, "F24")); // GDK_F24
+
+ keyMap.put(KeyEvent.VK_SHIFT, new GNOMEKeyInfo(0xFFE2, "Shift_R")); // GDK_Shift_R
+ keyMap.put(KeyEvent.VK_CONTROL, new GNOMEKeyInfo(0xFFE4, "Control_R")); // GDK_Control_R
+ keyMap.put(KeyEvent.VK_CAPS_LOCK, new GNOMEKeyInfo(0xFFE5, "Caps_Lock")); // GDK_Caps_Lock
+ keyMap.put(KeyEvent.VK_META, new GNOMEKeyInfo(0xFFE8, "Meta_R")); // GDK_Meta_R
+ keyMap.put(KeyEvent.VK_ALT, new GNOMEKeyInfo(0xFFEA, "Alt_R")); // GDK_Alt_R
+ keyMap.put(KeyEvent.VK_DELETE, new GNOMEKeyInfo(0xFFFF, "Delete")); // GDK_Delete
+
+ // Left & Right Variations, default (set above) will be right...
+ keyMap.put(LEFT_OFFSET + KeyEvent.VK_SHIFT, new GNOMEKeyInfo(0xFFE1, "Shift_L")); // GDK_Shift_L
+ keyMap.put(RIGHT_OFFSET + KeyEvent.VK_SHIFT, new GNOMEKeyInfo(0xFFE2, "Shift_R")); // GDK_Shift_R
+ keyMap.put(LEFT_OFFSET + KeyEvent.VK_CONTROL, new GNOMEKeyInfo(0xFFE3, "Control_L")); // GDK_Control_L
+ keyMap.put(RIGHT_OFFSET + KeyEvent.VK_CONTROL, new GNOMEKeyInfo(0xFFE4, "Control_R")); // GDK_Control_R
+ keyMap.put(LEFT_OFFSET + KeyEvent.VK_META, new GNOMEKeyInfo(0xFFE7, "Meta_L")); // GDK_Meta_L
+ keyMap.put(RIGHT_OFFSET + KeyEvent.VK_META, new GNOMEKeyInfo(0xFFE8, "Meta_R")); // GDK_Meta_R
+ keyMap.put(LEFT_OFFSET + KeyEvent.VK_ALT, new GNOMEKeyInfo(0xFFE9, "Alt_L")); // GDK_Alt_L
+ keyMap.put(RIGHT_OFFSET + KeyEvent.VK_ALT, new GNOMEKeyInfo(0xFFEA, "Alt_R")); // GDK_Alt_R
+ }
+
+ record GNOMEKeyInfo(int gdkKeyCode, String gdkKeyString) {
+ }
+}
+
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkLayer.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkLayer.java
new file mode 100644
index 000000000000..2e95580b6f55
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkLayer.java
@@ -0,0 +1,34 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+public final class AtkLayer {
+ public static final int INVALID = 0;
+ public static final int BACKGROUND = 1;
+ public static final int CANVAS = 2;
+ public static final int WIDGET = 3;
+ public static final int MDI = 4;
+ public static final int POPUP = 5;
+ public static final int OVERLAY = 6;
+ public static final int WINDOW = 7;
+
+ private AtkLayer() {
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkObject.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkObject.java
new file mode 100644
index 000000000000..ce8cd4d4e34e
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkObject.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2018, Red Hat, Inc.
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.util.Locale;
+import javax.swing.JMenuItem;
+import javax.swing.KeyStroke;
+import java.awt.event.KeyEvent;
+import java.awt.event.InputEvent;
+import java.awt.EventQueue;
+
+/**
+ * AtkObject:
+ *
+ * Java-side utility class used by the GNOME Accessibility Bridge.
+ *
+ * That class is used to wrap AccessibleContext Java object
+ * to avoid the concurrency of AWT objects.
+ *
+ * @autor Giuseppe Capaldo
+ */
+public class AtkObject {
+
+ // need to fix [missing-explicit-ctor]
+ private AtkObject() {
+ }
+
+ /**
+ * Returns the JMenuItem accelerator. Similar implementation is used on
+ * macOS, see CAccessibility.getAcceleratorText(AccessibleContext) in OpenJDK, and
+ * on Windows, see AccessBridge.getAccelerator(AccessibleContext) in OpenJDK.
+ */
+ private static String getAcceleratorText(AccessibleContext ac) {
+ assert (EventQueue.isDispatchThread());
+
+ String acceleratorText = "";
+ Accessible parent = ac.getAccessibleParent();
+ if (parent != null) {
+ int indexInParent = ac.getAccessibleIndexInParent();
+ Accessible child = parent.getAccessibleContext()
+ .getAccessibleChild(indexInParent);
+ if (child instanceof JMenuItem menuItem) {
+ KeyStroke keyStroke = menuItem.getAccelerator();
+ if (keyStroke != null) {
+ int modifiers = keyStroke.getModifiers();
+ String modifiersText = modifiers > 0 ? InputEvent.getModifiersExText(modifiers) : "";
+
+ int keyCode = keyStroke.getKeyCode();
+ String keyCodeText = keyCode != 0 ? KeyEvent.getKeyText(keyCode) : String.valueOf(keyStroke.getKeyChar());
+
+ acceleratorText += modifiersText;
+ if (!modifiersText.isEmpty() && !keyCodeText.isEmpty()) {
+ acceleratorText += "+";
+ }
+ acceleratorText += keyCodeText;
+ }
+ }
+ }
+ return acceleratorText;
+ }
+
+ // JNI upcalls section
+
+ /**
+ * Gets the ATK interface flags for the given accessible object.
+ * Called from native code via JNI.
+ *
+ * @param o the accessible object (AccessibleContext or Accessible)
+ * @return bitwise OR of ATK interface flags from {@link AtkInterface}
+ */
+ private static int get_tflag_from_obj(Object o) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ int flags = 0;
+ AccessibleContext ac;
+
+ if (o instanceof AccessibleContext accessibleContext) {
+ ac = accessibleContext;
+ } else if (o instanceof Accessible accessible) {
+ ac = accessible.getAccessibleContext();
+ } else {
+ return flags;
+ }
+
+ if (ac.getAccessibleAction() != null) {
+ flags |= AtkInterface.INTERFACE_ACTION;
+ }
+ if (ac.getAccessibleComponent() != null) {
+ flags |= AtkInterface.INTERFACE_COMPONENT;
+ }
+ AccessibleText text = ac.getAccessibleText();
+ if (text != null) {
+ flags |= AtkInterface.INTERFACE_TEXT;
+ if (text instanceof AccessibleHypertext) {
+ flags |= AtkInterface.INTERFACE_HYPERTEXT;
+ }
+ if (ac.getAccessibleEditableText() != null) {
+ flags |= AtkInterface.INTERFACE_EDITABLE_TEXT;
+ }
+ }
+ if (ac.getAccessibleIcon() != null) {
+ flags |= AtkInterface.INTERFACE_IMAGE;
+ }
+ if (ac.getAccessibleSelection() != null) {
+ flags |= AtkInterface.INTERFACE_SELECTION;
+ }
+ AccessibleTable table = ac.getAccessibleTable();
+ if (table != null) {
+ flags |= AtkInterface.INTERFACE_TABLE;
+ }
+ Accessible parent = ac.getAccessibleParent();
+ if (parent != null) {
+ AccessibleContext parentAccessibleContext = parent.getAccessibleContext();
+ if (parentAccessibleContext != null) {
+ table = parentAccessibleContext.getAccessibleTable();
+ if (table != null && table instanceof AccessibleExtendedTable) {
+ flags |= AtkInterface.INTERFACE_TABLE_CELL;
+ }
+ }
+ }
+ if (ac.getAccessibleValue() != null) {
+ flags |= AtkInterface.INTERFACE_VALUE;
+ }
+ return flags;
+ }, 0);
+ }
+
+ /**
+ * Gets the parent AccessibleContext of the given accessible context.
+ * Called from native code via JNI.
+ *
+ * @param ac the accessible context
+ * @return the parent accessible context, or null if no parent exists
+ */
+ private static AccessibleContext get_accessible_parent(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Accessible accessibleParent = ac.getAccessibleParent();
+ if (accessibleParent == null) {
+ return null;
+ }
+ AccessibleContext accessibleContext = accessibleParent.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ }
+ return accessibleContext;
+ }, null);
+ }
+
+ /**
+ * Sets the parent of the given accessible context.
+ * Called from native code via JNI.
+ *
+ * @param ac the accessible context whose parent should be set
+ * @param parentAccessibleContext the new parent accessible context (must be Accessible)
+ */
+ private static void set_accessible_parent(AccessibleContext ac, AccessibleContext parentAccessibleContext) {
+ AtkUtil.invokeInSwing(() -> {
+ if (parentAccessibleContext instanceof Accessible parentAccessible) {
+ ac.setAccessibleParent(parentAccessible);
+ }
+ });
+ }
+
+ /**
+ * Gets the accessible name of the given accessible context.
+ * Called from native code via JNI.
+ *
+ * @param ac the accessible context
+ * @return the accessible name, with accelerator text appended, or null if no name is set
+ */
+ private static String get_accessible_name(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ String accessibleName = ac.getAccessibleName();
+ if (accessibleName == null) {
+ return null;
+ }
+ String acceleratorText = getAcceleratorText(ac);
+ if (!acceleratorText.isEmpty()) {
+ return accessibleName + " " + acceleratorText;
+ }
+ return accessibleName;
+ }, null);
+ }
+
+ /**
+ * Sets the accessible name of the given accessible context.
+ * Called from native code via JNI.
+ *
+ * @param ac the accessible context
+ * @param name the new accessible name
+ */
+ private static void set_accessible_name(AccessibleContext ac, String name) {
+ AtkUtil.invokeInSwing(() -> {
+ ac.setAccessibleName(name);
+ });
+ }
+
+ /**
+ * Gets the accessible description of the given accessible context.
+ * Called from native code via JNI.
+ *
+ * @param ac the accessible context
+ * @return the accessible description, or empty string if no description is set
+ */
+ private static String get_accessible_description(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return ac.getAccessibleDescription();
+ }, null);
+ }
+
+ /**
+ * Sets the accessible description of the given accessible context.
+ * Called from native code via JNI.
+ *
+ * @param ac the accessible context
+ * @param description the new accessible description
+ */
+ private static void set_accessible_description(AccessibleContext ac, String description) {
+ AtkUtil.invokeInSwing(() -> {
+ ac.setAccessibleDescription(description);
+ });
+ }
+
+ /**
+ * Gets the number of accessible children of the given accessible context.
+ * Called from native code via JNI.
+ *
+ * @param ac the accessible context
+ * @return the number of accessible children, or 0 if there are no children
+ */
+ private static int get_accessible_children_count(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return ac.getAccessibleChildrenCount();
+ }, 0);
+ }
+
+ /**
+ * Gets the index of this accessible context within its parent's children.
+ * Called from native code via JNI.
+ *
+ * @param ac the accessible context
+ * @return the zero-based index in parent, or -1 if no parent exists or index cannot be determined
+ */
+ private static int get_accessible_index_in_parent(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return ac.getAccessibleIndexInParent();
+ }, -1);
+ }
+
+ /**
+ * Gets the accessible role of the given accessible context.
+ * Called from native code via JNI.
+ *
+ * @param ac the accessible context
+ * @return the accessible role, or null if the role cannot be determined
+ */
+ private static AccessibleRole get_accessible_role(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return ac.getAccessibleRole();
+ }, null);
+ }
+
+ /**
+ * Gets an array of accessible states for the given accessible context.
+ * Called from native code via JNI.
+ *
+ * @param ac the accessible context
+ * @return an array of accessible states, or null if no state set exists
+ */
+ private static AccessibleState[] get_array_accessible_state(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ AccessibleStateSet stateSet = ac.getAccessibleStateSet();
+ if (stateSet == null) {
+ return null;
+ } else {
+ return stateSet.toArray();
+ }
+ }, null);
+ }
+
+ /**
+ * Gets the locale of the given accessible context.
+ * Called from native code via JNI.
+ *
+ * @param ac the accessible context
+ * @return the locale string in the format "language_country@script@variant", or null if locale cannot be determined
+ */
+ private static String get_locale(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Locale l = ac.getLocale();
+ String locale = l.getLanguage();
+ String country = l.getCountry();
+ String script = l.getScript();
+ String variant = l.getVariant();
+ if (!country.isEmpty()) {
+ locale += "_" + country;
+ }
+ if (!script.isEmpty()) {
+ locale += "@" + script;
+ }
+ if (!variant.isEmpty()) {
+ locale += "@" + variant;
+ }
+ return locale;
+ }, null);
+ }
+
+ /**
+ * Gets an array of accessible relations for the given accessible context.
+ * Called from native code via JNI.
+ *
+ * @param ac the accessible context
+ * @return an array of WrapKeyAndTarget records containing relation keys and targets,
+ * or an empty array if no relations exist
+ */
+ private static WrapKeyAndTarget[] get_array_accessible_relation(AccessibleContext ac) {
+ WrapKeyAndTarget[] d = new WrapKeyAndTarget[0];
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ AccessibleRelationSet relationSet = ac.getAccessibleRelationSet();
+ if (relationSet == null) {
+ return d;
+ } else {
+ AccessibleRelation[] array = relationSet.toArray();
+ WrapKeyAndTarget[] result = new WrapKeyAndTarget[array.length];
+ for (int i = 0; i < array.length; i++) {
+ String key = array[i].getKey();
+ Object[] objs = array[i].getTarget();
+ AccessibleContext[] contexts = new AccessibleContext[objs.length];
+ for (int j = 0; j < objs.length; j++) {
+ if (objs[j] instanceof Accessible accessible) {
+ contexts[j] = accessible.getAccessibleContext();
+ } else {
+ contexts[j] = null;
+ }
+ }
+ result[i] = new WrapKeyAndTarget(key, contexts);
+ }
+ return result;
+ }
+ }, d);
+ }
+
+ /**
+ * Gets the accessible child at the specified index.
+ * Called from native code via JNI.
+ *
+ * @param ac the parent accessible context
+ * @param index the zero-based index of the child
+ * @return the child accessible context at the given index, or null if no child exists at that index
+ */
+ private static AccessibleContext get_accessible_child(AccessibleContext ac, int index) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Accessible child = ac.getAccessibleChild(index);
+ if (child == null) {
+ return null;
+ }
+ AccessibleContext accessibleContext = child.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ }
+ return accessibleContext;
+ }, null);
+ }
+
+ /**
+ * A record that wraps an accessible relation key with its target accessible contexts.
+ * Used to pass relation information from Java to native code.
+ */
+ private record WrapKeyAndTarget(String key, AccessibleContext[] relations) {
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkProvider.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkProvider.java
new file mode 100644
index 000000000000..f5252f35d4b8
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkProvider.java
@@ -0,0 +1,45 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2017 Oracle and/or its affiliates.
+ * Copyright (C) 2017 Fridrich Strba
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.AccessibilityProvider;
+
+public final class AtkProvider extends AccessibilityProvider {
+
+ private static final String name = "org.GNOME.Accessibility.AtkWrapper";
+
+ public AtkProvider() {
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void activate() {
+ if (isEnabled()) {
+ new AtkWrapper();
+ }
+ }
+
+ private boolean isEnabled() {
+ return !java.lang.Boolean.parseBoolean(System.getProperty("linux.jdk.accessibility.atkwrapper.block", "false"));
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkSelection.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkSelection.java
new file mode 100644
index 000000000000..aeafcdcb946e
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkSelection.java
@@ -0,0 +1,216 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.lang.ref.WeakReference;
+import java.awt.EventQueue;
+
+/**
+ * The ATK Selection interface implementation for Java accessibility.
+ *
+ * This class provides a bridge between Java's AccessibleSelection interface
+ * and the ATK (Accessibility Toolkit) selection interface.
+ */
+public class AtkSelection {
+
+ private final WeakReference accessibleContextWeakRef;
+ private final WeakReference accessibleSelectionWeakRef;
+
+ private AtkSelection(AccessibleContext ac) {
+ assert EventQueue.isDispatchThread();
+
+ AccessibleSelection accessibleSelection = ac.getAccessibleSelection();
+ if (accessibleSelection == null) {
+ throw new IllegalArgumentException("AccessibleContext must have AccessibleSelection");
+ }
+
+ this.accessibleContextWeakRef = new WeakReference(ac);
+ this.accessibleSelectionWeakRef = new WeakReference(accessibleSelection);
+ }
+
+ /* JNI upcalls section */
+
+ /**
+ * Factory method to create an AtkSelection instance from an AccessibleContext.
+ * Called from native code via JNI.
+ *
+ * @param ac the AccessibleContext to wrap
+ * @return a new AtkSelection instance, or null if creation fails
+ */
+ private static AtkSelection create_atk_selection(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return new AtkSelection(ac);
+ }, null);
+ }
+
+ /**
+ * Adds the specified accessible child to the object's selection.
+ * Called from native code via JNI.
+ *
+ * @param index the index of the child in the object's list of children
+ * @return true if the child was successfully selected, false otherwise
+ */
+ private boolean add_selection(int index) {
+ AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get();
+ if (accessibleSelection == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ accessibleSelection.addAccessibleSelection(index);
+ return is_child_selected(index);
+ }, false);
+ }
+
+ /**
+ * Clears the selection in the object so that no children in the object are selected.
+ * Called from native code via JNI.
+ *
+ * @return true if the selection was successfully cleared, false otherwise
+ */
+ private boolean clear_selection() {
+ AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get();
+ if (accessibleSelection == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ accessibleSelection.clearAccessibleSelection();
+ return true;
+ }, false);
+ }
+
+ /**
+ * Gets a reference to the accessible object representing the specified selected child of the object.
+ * Called from native code via JNI.
+ *
+ * @param index the index of the selected child in the selection
+ * @return the AccessibleContext of the selected child, or null if none
+ */
+ private AccessibleContext ref_selection(int index) {
+ AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get();
+ if (accessibleSelection == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Accessible selectedChild = accessibleSelection.getAccessibleSelection(index);
+ if (selectedChild == null) {
+ return null;
+ }
+ AccessibleContext selectedChildAccessibleContext = selectedChild.getAccessibleContext();
+ if (selectedChildAccessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(selectedChildAccessibleContext);
+ }
+ return selectedChildAccessibleContext;
+ }, null);
+ }
+
+ /**
+ * Gets the number of accessible children currently selected.
+ * Called from native code via JNI.
+ *
+ * @return the number of currently selected children, or 0 if none are selected
+ */
+ private int get_selection_count() {
+ AccessibleContext ac = accessibleContextWeakRef.get();
+ if (ac == null) {
+ return 0;
+ }
+ AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get();
+ if (accessibleSelection == null) {
+ return 0;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ int count = 0;
+ for (int i = 0; i < ac.getAccessibleChildrenCount(); i++) {
+ if (accessibleSelection.isAccessibleChildSelected(i)) {
+ count++;
+ }
+ }
+ return count;
+ }, 0);
+ }
+
+ /**
+ * Determines if the specified child of the object is included in the object's selection.
+ * Called from native code via JNI.
+ *
+ * @param i the index of the child in the object's list of children
+ * @return true if the child is selected, false otherwise
+ */
+ private boolean is_child_selected(int i) {
+ AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get();
+ if (accessibleSelection == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleSelection.isAccessibleChildSelected(i);
+ }, false);
+ }
+
+ /**
+ * Removes the specified child of the object from the object's selection.
+ * Called from native code via JNI.
+ *
+ * @param i the index of the selected child in the selection
+ * @return true if the child was successfully removed from the selection, false otherwise
+ */
+ private boolean remove_selection(int i) {
+ AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get();
+ if (accessibleSelection == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ accessibleSelection.removeAccessibleSelection(i);
+ return !is_child_selected(i);
+ }, false);
+ }
+
+ /**
+ * Causes every child of the object to be selected if the object supports multiple selection.
+ * Called from native code via JNI.
+ *
+ * @return true if all children were successfully selected (object supports multiple selection), false otherwise
+ */
+ private boolean select_all_selection() {
+ AccessibleContext accessibleContext = accessibleContextWeakRef.get();
+ if (accessibleContext == null) {
+ return false;
+ }
+ AccessibleSelection accessibleSelection = accessibleSelectionWeakRef.get();
+ if (accessibleSelection == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ AccessibleStateSet stateSet = accessibleContext.getAccessibleStateSet();
+ if (stateSet.contains(AccessibleState.MULTISELECTABLE)) {
+ accessibleSelection.selectAllAccessibleSelection();
+ return true;
+ }
+ return false;
+ }, false);
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkSignal.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkSignal.java
new file mode 100644
index 000000000000..d41d43eb869b
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkSignal.java
@@ -0,0 +1,70 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import java.lang.annotation.Native;
+
+public interface AtkSignal {
+ @Native
+ int TEXT_CARET_MOVED = 0;
+ @Native
+ int TEXT_PROPERTY_CHANGED_INSERT = 1;
+ @Native
+ int TEXT_PROPERTY_CHANGED_DELETE = 2;
+ @Native
+ int TEXT_PROPERTY_CHANGED_REPLACE = 3;
+ @Native
+ int OBJECT_CHILDREN_CHANGED_ADD = 4;
+ @Native
+ int OBJECT_CHILDREN_CHANGED_REMOVE = 5;
+ @Native
+ int OBJECT_ACTIVE_DESCENDANT_CHANGED = 6;
+ @Native
+ int OBJECT_SELECTION_CHANGED = 7;
+ @Native
+ int OBJECT_VISIBLE_DATA_CHANGED = 8;
+ @Native
+ int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_ACTIONS = 9;
+ @Native
+ int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_VALUE = 10;
+ @Native
+ int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_DESCRIPTION = 11;
+ @Native
+ int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_NAME = 12;
+ @Native
+ int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_HYPERTEXT_OFFSET = 13;
+ @Native
+ int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_CAPTION = 14;
+ @Native
+ int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_SUMMARY = 15;
+ @Native
+ int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_HEADER = 16;
+ @Native
+ int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_DESCRIPTION = 17;
+ @Native
+ int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_HEADER = 18;
+ @Native
+ int OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_DESCRIPTION = 19;
+ @Native
+ int TABLE_MODEL_CHANGED = 20;
+ @Native
+ int TEXT_PROPERTY_CHANGED = 21;
+}
+
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTable.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTable.java
new file mode 100644
index 000000000000..c72b9cd09568
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTable.java
@@ -0,0 +1,572 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems Inc.
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.lang.ref.WeakReference;
+import java.awt.EventQueue;
+
+/**
+ * The ATK Table interface implementation for Java accessibility.
+ *
+ * This class provides a bridge between Java's AccessibleTable interface
+ * and the ATK (Accessibility Toolkit) table interface.
+ */
+public class AtkTable {
+
+ private final WeakReference accessibleContextWeakRef;
+ private final WeakReference accessibleTableWeakRef;
+
+ private AtkTable(AccessibleContext ac) {
+ assert EventQueue.isDispatchThread();
+
+ AccessibleTable accessibleTable = ac.getAccessibleTable();
+ if (accessibleTable == null) {
+ throw new IllegalArgumentException("AccessibleContext must have AccessibleTable");
+ }
+
+ this.accessibleContextWeakRef = new WeakReference(ac);
+ this.accessibleTableWeakRef = new WeakReference(ac.getAccessibleTable());
+ }
+
+ /* JNI upcalls section */
+
+ /**
+ * Factory method to create an AtkTable instance from an AccessibleContext.
+ * Called from native code via JNI.
+ *
+ * @param ac the AccessibleContext to wrap
+ * @return a new AtkTable instance, or null if creation fails
+ */
+ private static AtkTable create_atk_table(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return new AtkTable(ac);
+ }, null);
+ }
+
+ /**
+ * Gets an accessible context at the specified row and column in the table.
+ * Called from native code via JNI.
+ *
+ * @param row the row index
+ * @param column the column index
+ * @return the AccessibleContext of the cell at the specified position, or null if none
+ */
+ private AccessibleContext ref_at(int row, int column) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Accessible accessible = accessibleTable.getAccessibleAt(row, column);
+ if (accessible == null) {
+ return null;
+ }
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ }
+ return accessibleContext;
+ }, null);
+ }
+
+ /**
+ * Gets the index of the accessible child at the specified row and column.
+ * Called from native code via JNI.
+ *
+ * @param row the row index
+ * @param column the column index
+ * @return the child index, or -1 if no child exists at that position
+ */
+ private int get_index_at(int row, int column) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return -1;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ if (accessibleTable instanceof AccessibleExtendedTable accessibleExtendedTable) {
+ return accessibleExtendedTable.getAccessibleIndex(row, column);
+ }
+ Accessible child = accessibleTable.getAccessibleAt(row, column);
+ if (child == null) {
+ return -1;
+ }
+ AccessibleContext childAccessibleContext = child.getAccessibleContext();
+ if (childAccessibleContext == null) {
+ return -1;
+ }
+ return childAccessibleContext.getAccessibleIndexInParent();
+ }, -1);
+ }
+
+ /**
+ * Gets the column index at the specified child index.
+ * Called from native code via JNI.
+ *
+ * @param index the child index
+ * @return the column index, or -1 if not available
+ */
+ private int get_column_at_index(int index) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return -1;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ int column = -1;
+ if (accessibleTable instanceof AccessibleExtendedTable accessibleExtendedTable) {
+ column = accessibleExtendedTable.getAccessibleColumn(index);
+ }
+ return column;
+ }, -1);
+ }
+
+ /**
+ * Gets the row index at the specified child index.
+ * Called from native code via JNI.
+ *
+ * @param index the child index
+ * @return the row index, or -1 if not available
+ */
+ private int get_row_at_index(int index) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return -1;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ int row = -1;
+ if (accessibleTable instanceof AccessibleExtendedTable accessibleExtendedTable) {
+ row = accessibleExtendedTable.getAccessibleRow(index);
+ }
+ return row;
+ }, -1);
+ }
+
+ /**
+ * Gets the number of columns in the table.
+ * Called from native code via JNI.
+ *
+ * @return the number of columns, or 0 if the table is not available
+ */
+ private int get_n_columns() {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return 0;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleTable.getAccessibleColumnCount();
+ }, 0);
+ }
+
+ /**
+ * Gets the number of rows in the table.
+ * Called from native code via JNI.
+ *
+ * @return the number of rows, or 0 if the table is not available
+ */
+ private int get_n_rows() {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return 0;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleTable.getAccessibleRowCount();
+ }, 0);
+ }
+
+ /**
+ * Gets the number of columns occupied by the accessible object at the specified row and column.
+ * Called from native code via JNI.
+ *
+ * @param row the row index
+ * @param column the column index
+ * @return the column extent (colspan), or 0 if not available
+ */
+ private int get_column_extent_at(int row, int column) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return 0;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleTable.getAccessibleColumnExtentAt(row, column);
+ }, 0);
+ }
+
+ /**
+ * Gets the number of rows occupied by the accessible object at the specified row and column.
+ * Called from native code via JNI.
+ *
+ * @param row the row index
+ * @param column the column index
+ * @return the row extent (rowspan), or 0 if not available
+ */
+ private int get_row_extent_at(int row, int column) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return 0;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleTable.getAccessibleRowExtentAt(row, column);
+ }, 0);
+ }
+
+ /**
+ * Gets the caption for the table.
+ * Called from native code via JNI.
+ *
+ * @return the AccessibleContext of the table caption, or null if none
+ */
+ private AccessibleContext get_caption() {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Accessible accessible = accessibleTable.getAccessibleCaption();
+ if (accessible == null) {
+ return null;
+ }
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ }
+ return accessibleContext;
+ }, null);
+ }
+
+ /**
+ * Sets the caption for the table.
+ * Called from native code via JNI.
+ *
+ * @param a the Accessible to use as the table caption
+ */
+ private void set_caption(Accessible a) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return;
+ }
+
+ AtkUtil.invokeInSwing(() -> {
+ accessibleTable.setAccessibleCaption(a);
+ });
+ }
+
+ /**
+ * Gets the description text of the specified column in the table.
+ * Called from native code via JNI.
+ *
+ * @param column an int representing a column in the table
+ * @return a String representing the column description, or null if the table doesn't implement
+ * this interface or if no description is available for the specified column
+ */
+ private String get_column_description(int column) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Accessible accessible = accessibleTable.getAccessibleColumnDescription(column);
+ if (accessible != null) {
+ AccessibleContext ac = accessible.getAccessibleContext();
+ if (ac != null) {
+ return ac.getAccessibleDescription();
+ }
+ }
+ return null;
+ }, null);
+ }
+
+ /**
+ * Sets the description text of the specified column in the table.
+ * Called from native code via JNI.
+ *
+ * @param column an int representing a column in table
+ * @param description a String object representing the description text to set for the
+ * specified column of the table
+ */
+ private void set_column_description(int column, String description) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return;
+ }
+
+ AtkUtil.invokeInSwing(() -> {
+ Accessible accessible = accessibleTable.getAccessibleColumnDescription(column);
+ if (accessible != null) {
+ AccessibleContext ac = accessible.getAccessibleContext();
+ if (ac != null) {
+ ac.setAccessibleDescription(description);
+ }
+ }
+ });
+ }
+
+ /**
+ * Gets the description text of the specified row in the table.
+ * Called from native code via JNI.
+ *
+ * @param row an int representing a row in the table
+ * @return a String representing the row description, or null if the table doesn't implement
+ * this interface or if no description is available for the specified row
+ */
+ private String get_row_description(int row) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Accessible accessible = accessibleTable.getAccessibleRowDescription(row);
+ if (accessible != null) {
+ AccessibleContext ac = accessible.getAccessibleContext();
+ if (ac != null) {
+ return ac.getAccessibleDescription();
+ }
+ }
+ return null;
+ }, null);
+ }
+
+ /**
+ * Sets the description text of the specified row in the table.
+ * Called from native code via JNI.
+ *
+ * @param row an int representing a row in table
+ * @param description a String object representing the description text to set for the
+ * specified row of the table
+ */
+ private void set_row_description(int row, String description) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return;
+ }
+
+ AtkUtil.invokeInSwing(() -> {
+ Accessible accessible = accessibleTable.getAccessibleRowDescription(row);
+ if (accessible != null) {
+ AccessibleContext ac = accessible.getAccessibleContext();
+ if (ac != null) {
+ ac.setAccessibleDescription(description);
+ }
+ }
+ });
+ }
+
+ /**
+ * Gets the column header at the specified column index.
+ * Called from native code via JNI.
+ *
+ * @param column the column index
+ * @return the AccessibleContext of the column header, or null if none
+ */
+ private AccessibleContext get_column_header(int column) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ AccessibleTable accessibleColumnHeader = accessibleTable.getAccessibleColumnHeader();
+ if (accessibleColumnHeader != null) {
+ Accessible accessible = accessibleColumnHeader.getAccessibleAt(0, column);
+ if (accessible == null) {
+ return null;
+ }
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ }
+ return accessibleContext;
+ }
+ return null;
+ }, null);
+ }
+
+ /**
+ * Gets the row header at the specified row index.
+ * Called from native code via JNI.
+ *
+ * @param row the row index
+ * @return the AccessibleContext of the row header, or null if none
+ */
+ private AccessibleContext get_row_header(int row) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ AccessibleTable accessibleRowHeader = accessibleTable.getAccessibleRowHeader();
+ if (accessibleRowHeader != null) {
+ Accessible accessible = accessibleRowHeader.getAccessibleAt(row, 0);
+ if (accessible == null) {
+ return null;
+ }
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ }
+ return accessibleContext;
+ }
+ return null;
+ }, null);
+ }
+
+ /**
+ * Gets the summary description of the table.
+ * Called from native code via JNI.
+ *
+ * @return the AccessibleContext of the table summary, or null if none
+ */
+ private AccessibleContext get_summary() {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Accessible accessibleSummary = accessibleTable.getAccessibleSummary();
+ if (accessibleSummary == null) {
+ return null;
+ }
+ AccessibleContext accessibleContext = accessibleSummary.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ }
+ return accessibleContext;
+ }, null);
+ }
+
+ /**
+ * Sets the summary description of the table.
+ * Called from native code via JNI.
+ *
+ * @param a the Accessible to use as the table summary
+ */
+ private void set_summary(Accessible a) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return;
+ }
+
+ AtkUtil.invokeInSwing(() -> {
+ accessibleTable.setAccessibleSummary(a);
+ });
+ }
+
+ /**
+ * Gets the selected columns in the table.
+ * Called from native code via JNI.
+ *
+ * @return an array of column indices that are selected, or null if none are selected
+ */
+ private int[] get_selected_columns() {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleTable.getSelectedAccessibleColumns();
+ }, null);
+ }
+
+ /**
+ * Gets the selected rows in the table.
+ * Called from native code via JNI.
+ *
+ * @return an array of row indices that are selected, or null if none are selected
+ */
+ private int[] get_selected_rows() {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleTable.getSelectedAccessibleRows();
+ }, null);
+ }
+
+ /**
+ * Determines whether the specified column is selected.
+ * Called from native code via JNI.
+ *
+ * @param column the column index
+ * @return true if the column is selected, false otherwise
+ */
+ private boolean is_column_selected(int column) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleTable.isAccessibleColumnSelected(column);
+ }, false);
+ }
+
+ /**
+ * Determines whether the specified row is selected.
+ * Called from native code via JNI.
+ *
+ * @param row the row index
+ * @return true if the row is selected, false otherwise
+ */
+ private boolean is_row_selected(int row) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleTable.isAccessibleRowSelected(row);
+ }, false);
+ }
+
+ /**
+ * Determines whether the accessible object at the specified row and column is selected.
+ * Called from native code via JNI.
+ *
+ * @param row the row index
+ * @param column the column index
+ * @return true if the cell at the specified position is selected, false otherwise
+ */
+ private boolean is_selected(int row, int column) {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleTable.isAccessibleSelected(row, column);
+ }, false);
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTableCell.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTableCell.java
new file mode 100644
index 000000000000..3828377841e5
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTableCell.java
@@ -0,0 +1,171 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.lang.ref.WeakReference;
+import java.awt.EventQueue;
+
+/**
+ * The ATK TableCell interface implementation for Java accessibility.
+ *
+ * This class provides a bridge between Java's AccessibleTable interface
+ * and the ATK (Accessibility Toolkit) table cell interface, representing
+ * individual cells within an accessible table.
+ */
+public class AtkTableCell {
+ private final WeakReference accessibleTableWeakRef;
+ private final int row; // the row index of this table cell, used by native code
+ private final int rowSpan; // the number of rows occupied by this table cell, used by native code
+ private final int column; // the column index of this table cell, used by native code
+ private final int columnSpan; // the number of columns occupied by this table cell, used by native code
+
+ private AtkTableCell(AccessibleContext ac) {
+ assert EventQueue.isDispatchThread();
+
+ Accessible accessibleParent = ac.getAccessibleParent();
+ if (accessibleParent == null) {
+ throw new IllegalArgumentException("AccessibleContext must have accessibleParent");
+ }
+
+ AccessibleContext parentAccessibleContext = accessibleParent.getAccessibleContext();
+ if (parentAccessibleContext == null) {
+ throw new IllegalArgumentException("AccessibleContext must have accessibleParent with AccessibleContext");
+ }
+
+ AccessibleTable accessibleTable = parentAccessibleContext.getAccessibleTable();
+ if (accessibleTable == null) {
+ throw new IllegalArgumentException("AccessibleContext must have accessibleParent with AccessibleTable");
+ }
+ accessibleTableWeakRef = new WeakReference(accessibleTable);
+
+ if (accessibleTable instanceof AccessibleExtendedTable accessibleExtendedTable) {
+ int index = ac.getAccessibleIndexInParent();
+ row = accessibleExtendedTable.getAccessibleRow(index);
+ column = accessibleExtendedTable.getAccessibleColumn(index);
+ } else {
+ row = -1;
+ column = -1;
+ }
+
+ rowSpan = accessibleTable.getAccessibleRowExtentAt(row, column);
+ columnSpan = accessibleTable.getAccessibleColumnExtentAt(row, column);
+ }
+
+ /* JNI upcalls section */
+
+ /**
+ * Factory method to create an AtkTableCell instance from an AccessibleContext.
+ * Called from native code via JNI.
+ *
+ * @param ac the AccessibleContext representing a table cell
+ * @return a new AtkTableCell instance, or null if creation fails
+ */
+ private static AtkTableCell create_atk_table_cell(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return new AtkTableCell(ac);
+ }, null);
+ }
+
+ /**
+ * Gets the table containing this cell.
+ * Called from native code via JNI.
+ *
+ * @return the AccessibleTable containing this cell, or null if unavailable
+ */
+ private AccessibleTable get_table() {
+ if (accessibleTableWeakRef == null) {
+ return null;
+ }
+ return accessibleTableWeakRef.get();
+ }
+
+ /**
+ * Returns the column headers as an array of AccessibleContext objects.
+ * Called from native code via JNI.
+ *
+ * @return an array of AccessibleContext objects representing the column headers,
+ * or null if column headers are not available
+ */
+ private AccessibleContext[] get_accessible_column_header() {
+ if (accessibleTableWeakRef == null) {
+ return null;
+ }
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return null;
+ }
+ AccessibleTable iteration = accessibleTable.getAccessibleColumnHeader();
+ if (iteration != null) {
+ int length = iteration.getAccessibleColumnCount();
+ AccessibleContext[] result = new AccessibleContext[length];
+ for (int i = 0; i < length; i++) {
+ Accessible accessible = iteration.getAccessibleAt(0, i);
+ if (accessible != null) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ result[i] = accessibleContext;
+ }
+ }
+ }
+ return result;
+ }
+ return null;
+ }, null);
+ }
+
+ /**
+ * Returns the row headers as an array of AccessibleContext objects.
+ * Called from native code via JNI.
+ *
+ * @return an array of AccessibleContext objects representing the row headers,
+ * or null if row headers are not available
+ */
+ private AccessibleContext[] get_accessible_row_header() {
+ if (accessibleTableWeakRef == null) {
+ return null;
+ }
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ AccessibleTable accessibleTable = accessibleTableWeakRef.get();
+ if (accessibleTable == null) {
+ return null;
+ }
+ AccessibleTable iteration = accessibleTable.getAccessibleRowHeader();
+ if (iteration != null) {
+ int length = iteration.getAccessibleRowCount();
+ AccessibleContext[] result = new AccessibleContext[length];
+ for (int i = 0; i < length; i++) {
+ Accessible accessible = iteration.getAccessibleAt(0, i);
+ if (accessible != null) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ result[i] = accessibleContext;
+ }
+ }
+ }
+ return result;
+ }
+ return null;
+ }, null);
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkText.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkText.java
new file mode 100644
index 000000000000..f417719020ef
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkText.java
@@ -0,0 +1,1006 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.text.*;
+import java.awt.Rectangle;
+import java.awt.Point;
+import java.lang.ref.WeakReference;
+import java.awt.EventQueue;
+
+/**
+ * The ATK Text interface implementation for Java accessibility.
+ *
+ * This class provides a bridge between Java's AccessibleText interface
+ * and the ATK (Accessibility Toolkit) text interface used by assistive
+ * technologies.
+ */
+public class AtkText {
+
+ private final WeakReference accessibleContextWeakRef;
+ private final WeakReference accessibleTextWeakRef;
+ private final WeakReference accessibleEditableTextWeakRef;
+
+ protected AtkText(AccessibleContext ac) {
+ assert EventQueue.isDispatchThread();
+
+ this.accessibleContextWeakRef = new WeakReference(ac);
+
+ AccessibleText accessibleText = ac.getAccessibleText();
+ if (accessibleText == null) {
+ throw new IllegalArgumentException("AccessibleContext must have AccessibleText");
+ }
+ this.accessibleTextWeakRef = new WeakReference(accessibleText);
+
+ this.accessibleEditableTextWeakRef = new WeakReference(ac.getAccessibleEditableText());
+ }
+
+ public static int getRightStart(int start) {
+ return Math.max(start, 0);
+ }
+
+ /**
+ * Returns a valid end position based on start, end, and text length constraints.
+ *
+ * @param start The starting position in the text, which may be invalid.
+ * @param end The ending position, which may be undefined (-1) or invalid.
+ * @param charCountInText The total character count in the text.
+ * @return A corrected end position.
+ */
+ public static int getRightEnd(int start, int end, int charCountInText) {
+ int rightStart = getRightStart(start);
+
+ // unique case : the end is undefined or an error happened,
+ // let's define the right end as charCountInText
+ if (end == -1) {
+ return charCountInText;
+ }
+
+ if (end < 0) { // we processed end == -1 in another statement
+ return rightStart;
+ } else if (end < rightStart) {
+ return rightStart;
+ } else if (charCountInText < end) {
+ return charCountInText;
+ } else {
+ return end;
+ }
+ }
+
+ private int getCurrentWordStart(int offset, String text) {
+ if (text == null || text.isEmpty()) {
+ return BreakIterator.DONE;
+ }
+
+ int length = text.length();
+ if (offset < 0) offset = 0;
+ if (offset >= length) offset = length - 1;
+
+ if (!Character.isLetter(text.codePointAt(offset))) {
+ return BreakIterator.DONE;
+ }
+
+ BreakIterator words = BreakIterator.getWordInstance();
+ words.setText(text);
+
+ int wordEnd = words.following(offset);
+ if (wordEnd == BreakIterator.DONE) return BreakIterator.DONE;
+
+ int wordStart = words.previous();
+ return wordStart;
+ }
+
+ // Currently unused.
+ private int getNextWordStart(int offset, String text) {
+ if (text == null || text.isEmpty()) {
+ return BreakIterator.DONE;
+ }
+
+ int length = text.length();
+
+ if (offset < 0) {
+ offset = 0;
+ } else if (offset > length) {
+ offset = length;
+ }
+
+ BreakIterator words = BreakIterator.getWordInstance();
+ words.setText(text);
+ int segmentStart = words.following(offset);
+ int segmentEnd = words.next();
+
+ while (segmentEnd != BreakIterator.DONE) {
+ for (int i = segmentStart; i < segmentEnd; i++) {
+ if (Character.isLetter(text.codePointAt(i))) {
+ return segmentStart;
+ }
+ }
+ segmentStart = segmentEnd;
+ segmentEnd = words.next();
+ }
+
+ return BreakIterator.DONE;
+ }
+
+ private int getPreviousWordStart(int offset, String text) {
+ if (text == null || text.isEmpty()) return BreakIterator.DONE;
+
+ int length = text.length();
+ if (offset < 0) offset = 0;
+ if (offset > length) offset = length;
+
+ BreakIterator words = BreakIterator.getWordInstance();
+ words.setText(text);
+
+ int segmentEnd = words.preceding(offset);
+ if (segmentEnd == BreakIterator.DONE) return BreakIterator.DONE;
+
+ int segmentStart = words.previous();
+
+ while (segmentStart != BreakIterator.DONE) {
+ for (int i = segmentStart; i < segmentEnd; i++) {
+ if (Character.isLetter(text.codePointAt(i))) {
+ return segmentStart;
+ }
+ }
+ segmentEnd = segmentStart;
+ segmentStart = words.previous();
+ }
+
+ return BreakIterator.DONE;
+ }
+
+ private int getWordEndFromStart(int start, String text) {
+ if (start == BreakIterator.DONE || text == null || text.isEmpty()) {
+ return BreakIterator.DONE;
+ }
+
+ BreakIterator words = BreakIterator.getWordInstance();
+ words.setText(text);
+
+ int end = words.following(start);
+ return (end == BreakIterator.DONE) ? text.length() : end;
+ }
+
+ /**
+ * Gets the start position of the sentence that contains the given offset.
+ *
+ * @param offset The character offset within the text
+ * @param text The full text to search within
+ * @return The start position of the current sentence, or BreakIterator.DONE if not found
+ */
+ private int getCurrentSentenceStart(int offset, String text) {
+ if (text == null || text.isEmpty()) {
+ return BreakIterator.DONE;
+ }
+
+ int length = text.length();
+ if (offset < 0) offset = 0;
+ if (offset >= length) offset = length - 1;
+
+ BreakIterator sentences = BreakIterator.getSentenceInstance();
+ sentences.setText(text);
+
+ int sentenceEnd = sentences.following(offset);
+ if (sentenceEnd == BreakIterator.DONE) return BreakIterator.DONE;
+
+ int sentenceStart = sentences.previous();
+ return sentenceStart;
+ }
+
+ /**
+ * Gets the start position of the next sentence after the given offset.
+ *
+ * @param offset The character offset within the text
+ * @param text The full text to search within
+ * @return The start position of the next sentence, or BreakIterator.DONE if not found
+ */
+ private int getNextSentenceStart(int offset, String text) {
+ if (text == null || text.isEmpty()) {
+ return BreakIterator.DONE;
+ }
+
+ BreakIterator sentences = BreakIterator.getSentenceInstance();
+ sentences.setText(text);
+
+ return sentences.following(offset);
+ }
+
+ /**
+ * Gets the start position of the previous sentence before the given offset.
+ *
+ * @param offset The character offset within the text
+ * @param text The full text to search within
+ * @return The start position of the previous sentence, or BreakIterator.DONE if not found
+ */
+ private int getPreviousSentenceStart(int offset, String text) {
+ if (text == null || text.isEmpty()) {
+ return BreakIterator.DONE;
+ }
+
+ BreakIterator sentences = BreakIterator.getSentenceInstance();
+ sentences.setText(text);
+
+ return sentences.preceding(offset);
+ }
+
+ /**
+ * Gets the end position of a sentence given its start position.
+ *
+ * @param start The start position of the sentence
+ * @param text The full text to search within
+ * @return The end position of the sentence, or BreakIterator.DONE if not found
+ */
+ private int getSentenceEndFromStart(int start, String text) {
+ if (start == BreakIterator.DONE || text == null || text.isEmpty()) {
+ return BreakIterator.DONE;
+ }
+
+ BreakIterator sentences = BreakIterator.getSentenceInstance();
+ sentences.setText(text);
+
+ int end = sentences.following(start);
+ return (end == BreakIterator.DONE) ? text.length() : end;
+ }
+
+ /**
+ * Gets the start position of the line that contains the given offset.
+ *
+ * @param offset The character offset within the text
+ * @param text The full text to search within
+ * @return The start position of the current line
+ */
+ private int getCurrentLineStart(int offset, String text) {
+ if (text == null || text.isEmpty()) {
+ return 0;
+ }
+
+ int length = text.length();
+ if (offset < 0) offset = 0;
+ if (offset >= length) offset = length - 1;
+
+ int pos = offset;
+ while (pos > 0) {
+ if (text.charAt(pos - 1) == '\n') {
+ return pos;
+ }
+ pos--;
+ }
+ return 0;
+ }
+
+ private int getNextLineStart(int offset, String str) {
+ int max = str.length();
+ while (offset < max) {
+ if (str.charAt(offset) == '\n')
+ return offset + 1;
+ offset += 1;
+ }
+ return offset;
+ }
+
+ private int getPreviousLineStart(int offset, String str) {
+ offset -= 2;
+ while (offset >= 0) {
+ if (str.charAt(offset) == '\n')
+ return offset + 1;
+ offset -= 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Gets the end position of a line given its start position.
+ *
+ * @param start The start position of the line
+ * @param text The full text to search within
+ * @return The end position of the line (position of newline or end of text)
+ */
+ private int getLineEndFromStart(int start, String text) {
+ if (text == null || text.isEmpty()) {
+ return 0;
+ }
+
+ int length = text.length();
+ if (start < 0) start = 0;
+ if (start >= length) return length;
+
+ int pos = start;
+ while (pos < length) {
+ if (text.charAt(pos) == '\n') {
+ return pos;
+ }
+ pos++;
+ }
+ return length;
+ }
+
+ private int getNextLineEnd(int offset, String str) {
+ int max = str.length();
+ offset += 1;
+ while (offset < max) {
+ if (str.charAt(offset) == '\n')
+ return offset;
+ offset += 1;
+ }
+ return offset;
+ }
+
+ private int getPreviousLineEnd(int offset, String str) {
+ offset -= 1;
+ while (offset >= 0) {
+ if (str.charAt(offset) == '\n')
+ return offset;
+ offset -= 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Gets the start position of the paragraph that contains the given offset.
+ * Paragraphs are defined as text blocks separated by newlines.
+ *
+ * @param offset The character offset within the text
+ * @param text The full text to search within
+ * @return The start position of the current paragraph
+ */
+ private int getCurrentParagraphStart(int offset, String text) {
+ if (text == null || text.isEmpty()) {
+ return 0;
+ }
+
+ int length = text.length();
+ if (offset < 0) offset = 0;
+ if (offset >= length) offset = length - 1;
+
+ // Search backwards from offset to find the start of the current paragraph
+ int pos = offset;
+ while (pos > 0) {
+ if (text.charAt(pos - 1) == '\n') {
+ return pos;
+ }
+ pos--;
+ }
+ return 0;
+ }
+
+ private int getNextParagraphStart(int offset, String str) {
+ int max = str.length();
+ while (offset < max) {
+ if (offset < max - 1 && str.charAt(offset) == '\n' && str.charAt(offset + 1) == '\n') {
+ return offset + 2;
+ }
+ offset += 1;
+ }
+ return offset;
+ }
+
+ private int getPreviousParagraphStart(int offset, String str) {
+ offset -= 2;
+ while (offset >= 0) {
+ if (offset > 0 && str.charAt(offset) == '\n' && str.charAt(offset - 1) == '\n') {
+ return offset + 1;
+ }
+ offset -= 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Gets the end position of a paragraph given its start position.
+ * Paragraphs are defined as text blocks separated by newlines.
+ *
+ * @param start The start position of the paragraph
+ * @param text The full text to search within
+ * @return The end position of the paragraph (position of newline or end of text)
+ */
+ private int getParagraphEndFromStart(int start, String text) {
+ if (text == null || text.isEmpty()) {
+ return 0;
+ }
+
+ int length = text.length();
+ if (start < 0) start = 0;
+ if (start >= length) return length;
+
+ int pos = start;
+ while (pos < length) {
+ if (text.charAt(pos) == '\n') {
+ return pos;
+ }
+ pos++;
+ }
+ return length;
+ }
+
+ @Deprecated
+ private StringSequence getTextAtOffset(int offset,
+ int boundaryType) {
+ int characterCount = get_character_count();
+ if (offset < 0 || offset > characterCount) {
+ return null;
+ }
+
+ switch (boundaryType) {
+ case AtkTextBoundary.CHAR: {
+ if (offset == characterCount) {
+ return null;
+ }
+ String str = get_text(offset, offset + 1);
+ return new StringSequence(str, offset, offset + 1);
+ }
+ case AtkTextBoundary.WORD_START:
+ case AtkTextBoundary.WORD_END: {
+ // The returned string will contain the word at the offset if the offset is
+ // inside a word and will contain the word before the offset if the offset is not inside a word
+ if (offset == characterCount) {
+ return null;
+ }
+
+ String fullText = get_text(0, characterCount);
+
+ int start = getCurrentWordStart(offset, fullText);
+ int end = getWordEndFromStart(start, fullText);
+ if (start == BreakIterator.DONE || end == BreakIterator.DONE) {
+ start = getPreviousWordStart(offset, fullText);
+ end = getWordEndFromStart(start, fullText);
+ if (start == BreakIterator.DONE || end == BreakIterator.DONE) {
+ return null;
+ }
+ }
+ String str = get_text(start, end);
+ return new StringSequence(str, start, end);
+ }
+ case AtkTextBoundary.SENTENCE_START:
+ case AtkTextBoundary.SENTENCE_END: {
+ // The returned string will contain the sentence at the offset if the offset
+ // is inside a sentence and will contain the sentence before the offset if the offset is not inside a sentence.
+ if (offset == characterCount) {
+ return null;
+ }
+
+ String fullText = get_text(0, characterCount);
+
+ int start = getCurrentSentenceStart(offset, fullText);
+ int end = getSentenceEndFromStart(start, fullText);
+ if (start == BreakIterator.DONE || end == BreakIterator.DONE) {
+ start = getPreviousSentenceStart(offset, fullText);
+ end = getSentenceEndFromStart(start, fullText);
+ if (start == BreakIterator.DONE || end == BreakIterator.DONE) {
+ return null;
+ }
+ }
+ String str = get_text(start, end);
+ return new StringSequence(str, start, end);
+ }
+ case AtkTextBoundary.LINE_START:
+ case AtkTextBoundary.LINE_END: {
+ // Returned string is from the line start at or before the offset to the line start after the offset.
+ if (offset == characterCount) {
+ return null;
+ }
+
+ String fullText = get_text(0, characterCount);
+
+ int start = getCurrentLineStart(offset, fullText);
+ int end = getLineEndFromStart(start, fullText);
+
+ String str = get_text(start, end);
+ return new StringSequence(str, start, end);
+ }
+ default: {
+ return null;
+ }
+ }
+ }
+
+ // JNI upcalls section
+
+ /**
+ * Factory method to create an AtkText instance from an AccessibleContext.
+ * Called from native code via JNI.
+ *
+ * @param ac the AccessibleContext to wrap
+ * @return a new AtkText instance, or null if creation fails
+ */
+ private static AtkText create_atk_text(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return new AtkText(ac);
+ }, null);
+ }
+
+ /**
+ * Gets the specified text from start to end offset.
+ * Called from native code via JNI.
+ *
+ * @param start a starting character offset within the text
+ * @param end an ending character offset within the text, or -1 for the end of the string
+ * @return a string containing the text from start up to, but not including end, or null if retrieval fails
+ */
+ private String get_text(int start, int end) {
+ AccessibleText accessibleText = accessibleTextWeakRef.get();
+ if (accessibleText == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ int rightStart = getRightStart(start);
+ int rightEnd = getRightEnd(rightStart, end, accessibleText.getCharCount());
+
+ if (accessibleText instanceof AccessibleExtendedText accessibleExtendedText) {
+ return accessibleExtendedText.getTextRange(rightStart, rightEnd);
+ }
+ StringBuilder builder = new StringBuilder();
+ for (int i = rightStart; i <= rightEnd - 1; i++) {
+ String textAtIndex = accessibleText.getAtIndex(AccessibleText.CHARACTER, i);
+ builder.append(textAtIndex);
+ }
+ return builder.toString();
+ }, null);
+ }
+
+ /**
+ * Gets the character at the specified offset.
+ * Called from native code via JNI.
+ *
+ * @param offset a character offset within the text
+ * @return the character at the specified offset, or '\0' in case of failure
+ */
+ private char get_character_at_offset(int offset) {
+ AccessibleText accessibleText = accessibleTextWeakRef.get();
+ if (accessibleText == null) {
+ return '\0';
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ String textAtOffset = accessibleText.getAtIndex(AccessibleText.CHARACTER, offset);
+ if (textAtOffset == null || textAtOffset.isEmpty()) {
+ return '\0';
+ }
+ return textAtOffset.charAt(0);
+ }, '\0');
+ }
+
+ /**
+ * Gets the offset of the position of the caret (cursor).
+ * Called from native code via JNI.
+ *
+ * @return the character offset of the position of the caret, or -1 if the caret is not located
+ * inside the element or in the case of any other failure
+ */
+ private int get_caret_offset() {
+ AccessibleText accessibleText = accessibleTextWeakRef.get();
+ if (accessibleText == null) {
+ return -1;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleText.getCaretPosition();
+ }, -1);
+ }
+
+ /**
+ * Gets the bounding box containing the glyph representing the character at a particular text offset.
+ * Called from native code via JNI.
+ *
+ * @param offset the offset of the text character for which bounding information is required
+ * @param coordType specifies whether coordinates are relative to the screen or widget window
+ * @return a Rectangle containing the bounding box (x, y, width, height), or null if the extent
+ * cannot be obtained. Returns null if all coordinates are set to -1.
+ */
+ private Rectangle get_character_extents(int offset, int coordType) {
+ AccessibleContext ac = accessibleContextWeakRef.get();
+ if (ac == null) {
+ return null;
+ }
+
+ AccessibleText accessibleText = accessibleTextWeakRef.get();
+ if (accessibleText == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Rectangle characterBounds = accessibleText.getCharacterBounds(offset);
+ if (characterBounds == null) {
+ return null;
+ }
+ Point componentLocation = AtkComponent.getLocationByCoordinateType(ac, coordType);
+ if (componentLocation == null) {
+ return null;
+ }
+ characterBounds.x += componentLocation.x;
+ characterBounds.y += componentLocation.y;
+ return characterBounds;
+ }, null);
+ }
+
+ /**
+ * Gets the character count.
+ * Called from native code via JNI.
+ *
+ * @return the number of characters, or -1 in case of failure
+ */
+ private int get_character_count() {
+ AccessibleText accessibleText = accessibleTextWeakRef.get();
+ if (accessibleText == null) {
+ return 0;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleText.getCharCount();
+ }, 0);
+ }
+
+ /**
+ * Gets the offset of the character located at coordinates @x and @y. @x and @y
+ * are interpreted as being relative to the screen or this widget's window
+ * depending on @coords.
+ *
+ * @param x int screen x-position of character
+ * @param y int screen y-position of character
+ * @param coord_type int specify whether coordinates are relative to the screen or
+ * widget window
+ * @return the offset to the character which is located at the specified
+ * @x and @y coordinates or -1 in case of failure.
+ */
+ private int get_offset_at_point(int x, int y, int coord_type) {
+ AccessibleContext ac = accessibleContextWeakRef.get();
+ if (ac == null) {
+ return -1;
+ }
+ AccessibleText accessibleText = accessibleTextWeakRef.get();
+ if (accessibleText == null) {
+ return -1;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Point componentLocation = AtkComponent.getLocationByCoordinateType(ac, coord_type);
+ if (componentLocation == null) {
+ return -1;
+ }
+ return accessibleText.getIndexAtPoint(new Point(x - componentLocation.x, y - componentLocation.y));
+ }, -1);
+ }
+
+ /**
+ * Gets the bounding box for text within the specified range.
+ * Called from native code via JNI.
+ *
+ * @param start the offset of the first text character for which boundary information is required
+ * @param end the offset of the text character after the last character for which boundary information is required
+ * @param coordType specifies whether coordinates are relative to the screen or widget window
+ * @return a Rectangle filled in with the bounding box, or null if the extents cannot be obtained.
+ * Returns null if all rectangle fields are set to -1.
+ */
+ private Rectangle get_range_extents(int start, int end, int coordType) {
+ AccessibleContext ac = accessibleContextWeakRef.get();
+ if (ac == null) {
+ return null;
+ }
+ AccessibleText accessibleText = accessibleTextWeakRef.get();
+ if (accessibleText == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ if (accessibleText instanceof AccessibleExtendedText accessibleExtendedText) {
+ final int rightStart = getRightStart(start);
+ final int rightEnd = getRightEnd(rightStart, end, accessibleText.getCharCount());
+
+ Rectangle textBounds = accessibleExtendedText.getTextBounds(rightStart, rightEnd);
+ if (textBounds == null) {
+ return null;
+ }
+ Point componentLocation = AtkComponent.getLocationByCoordinateType(ac, coordType);
+ if (componentLocation == null) {
+ return null;
+ }
+ textBounds.x += componentLocation.x;
+ textBounds.y += componentLocation.y;
+ return textBounds;
+ }
+ return null;
+ }, null);
+ }
+
+ /**
+ * Gets the number of selected regions.
+ *
+ * @param text an #AtkText
+ * @return The number of selected regions, or -1 in the case of failure.
+ */
+ private int get_n_selections() {
+ AccessibleText accessibleText = accessibleTextWeakRef.get();
+ if (accessibleText == null) {
+ return -1;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ String selectedText = accessibleText.getSelectedText();
+ if (selectedText != null) {
+ return 1;
+ }
+ return 0;
+ }, -1);
+ }
+
+ /**
+ * Gets the text from the specified selection.
+ * Called from native code via JNI.
+ *
+ * @return a StringSequence containing the selected text and its start and end offsets,
+ * or null if there is no selection or retrieval fails
+ */
+ private StringSequence get_selection() {
+ AccessibleText accessibleText = accessibleTextWeakRef.get();
+ if (accessibleText == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ int start = accessibleText.getSelectionStart();
+ int end = accessibleText.getSelectionEnd();
+ String text = accessibleText.getSelectedText();
+ if (text == null) {
+ return null;
+ }
+ return new StringSequence(text, start, end);
+ }, null);
+ }
+
+ /**
+ * Adds a selection bounded by the specified offsets.
+ * Called from native code via JNI.
+ *
+ * @param start the starting character offset of the selected region
+ * @param end the offset of the first character after the selected region
+ * @return true if successful, false otherwise. Note that Java AccessibleText only supports
+ * a single selection, so this will return false if a selection already exists.
+ */
+ private boolean add_selection(int start, int end) {
+ AccessibleText accessibleText = accessibleTextWeakRef.get();
+ if (accessibleText == null) {
+ return false;
+ }
+ AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get();
+
+ // Java AccessibleText only supports a single selection, so reject if one already exists
+ if (accessibleEditableText == null || get_n_selections() > 0) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ final int rightStart = getRightStart(start);
+ final int rightEnd = getRightEnd(rightStart, end, accessibleText.getCharCount());
+
+ return set_selection(0, rightStart, rightEnd);
+ }, false);
+ }
+
+ /**
+ * Removes the specified selection.
+ * Called from native code via JNI.
+ *
+ * @param selectionNum the selection number. The selected regions are assigned numbers
+ * that correspond to how far the region is from the start of the text.
+ * Since Java only supports a single selection, only 0 is valid.
+ * @return true if successful, false otherwise
+ */
+ private boolean remove_selection(int selectionNum) {
+ AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get();
+ if (accessibleEditableText == null || selectionNum > 0) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ accessibleEditableText.selectText(0, 0);
+ return true;
+ }, false);
+ }
+
+ /**
+ * Changes the start and end offset of the specified selection.
+ * Called from native code via JNI.
+ *
+ * @param selectionNum the selection number. The selected regions are assigned numbers
+ * that correspond to how far the region is from the start of the text.
+ * Since Java only supports a single selection, only 0 is valid.
+ * @param start the new starting character offset of the selection
+ * @param end the new end position (offset immediately past) of the selection
+ * @return true if successful, false otherwise
+ */
+ private boolean set_selection(int selectionNum, int start, int end) {
+ AccessibleText accessibleText = accessibleTextWeakRef.get();
+ if (accessibleText == null) {
+ return false;
+ }
+ AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get();
+ if (accessibleEditableText == null || selectionNum > 0) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ final int rightStart = getRightStart(start);
+ final int rightEnd = getRightEnd(rightStart, end, accessibleText.getCharCount());
+
+ accessibleEditableText.selectText(rightStart, rightEnd);
+ return true;
+ }, false);
+ }
+
+ /**
+ * Sets the caret (cursor) position to the specified offset.
+ * Called from native code via JNI.
+ *
+ * @param offset the character offset of the new caret position
+ * @return true if successful, false otherwise
+ */
+ private boolean set_caret_offset(int offset) {
+ AccessibleText accessibleText = accessibleTextWeakRef.get();
+ if (accessibleText == null) {
+ return false;
+ }
+ AccessibleEditableText accessibleEditableText = accessibleEditableTextWeakRef.get();
+ if (accessibleEditableText == null) {
+ return false;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ int rightOffset = getRightEnd(0, offset, accessibleText.getCharCount());
+ accessibleEditableText.selectText(rightOffset, rightOffset);
+ return true;
+ }, false);
+ }
+
+ /**
+ * @param offset Position.
+ * @param boundaryType An AtkTextBoundary.
+ * @return A newly allocated string containing the text at offset bounded by the specified boundary_type.
+ * @deprecated Please use get_string_at_offset() instead.
+ *
+ * Returns a newly allocated string containing the text at offset bounded by the specified boundary_type.
+ */
+ @Deprecated
+ private StringSequence get_text_at_offset(int offset, int boundaryType) {
+ return getTextAtOffset(offset, boundaryType);
+ }
+
+ /**
+ * @param offset Position.
+ * @param boundaryType An AtkTextBoundary.
+ * @return A newly allocated string containing the text before offset bounded by the specified boundary_type
+ * @deprecated Please use get_string_at_offset() instead.
+ *
+ * Returns a newly allocated string containing the text before offset bounded by the specified boundary_type.
+ */
+ @Deprecated
+ private StringSequence get_text_before_offset(int offset, int boundaryType) {
+ // TODO: Implement if required.
+ return getTextAtOffset(offset, boundaryType);
+ }
+
+ /**
+ * @param offset Position.
+ * @param boundaryType An AtkTextBoundary.
+ * @return A newly allocated string containing the text after offset bounded by the specified boundary_type.
+ * @deprecated Please use get_string_at_offset() instead.
+ *
+ * Returns newly allocated string containing the text after offset bounded by the specified boundary_type.
+ */
+ @Deprecated
+ private StringSequence get_text_after_offset(int offset, int boundaryType) {
+ // TODO: Implement if required.
+ return getTextAtOffset(offset, boundaryType);
+ }
+
+ /**
+ * Gets a portion of the text exposed through an {@link AtkText} according to a given
+ * offset and a specific granularity, along with the start and end offsets defining the
+ * boundaries of such a portion of text.
+ *
+ * @param offset The position in the text where the extraction starts.
+ * @param granularity The granularity of the text to extract, which can be one of the following:
+ * - {@link AtkTextGranularity#ATK_TEXT_GRANULARITY_CHAR}: returns the character at the offset.
+ * - {@link AtkTextGranularity#ATK_TEXT_GRANULARITY_WORD}: returns the word that contains the offset.
+ * - {@link AtkTextGranularity#ATK_TEXT_GRANULARITY_SENTENCE}: returns the sentence that contains the offset.
+ * - {@link AtkTextGranularity#ATK_TEXT_GRANULARITY_LINE}: returns the line that contains the offset.
+ * - {@link AtkTextGranularity#ATK_TEXT_GRANULARITY_PARAGRAPH}: returns the paragraph that contains the offset.
+ * @param start_offset (out) The starting character offset of the returned string, or -1 if there is an error (e.g., invalid offset, not implemented).
+ * @param end_offset (out) The offset of the first character after the returned string, or -1 in the case of an error (e.g., invalid offset, not implemented).
+ * @return A newly allocated string containing the text at the specified offset, bounded by the specified granularity.
+ * The caller is responsible for freeing the returned string using {@code g_free()}.
+ * Returns {@code null} if the offset is invalid or no implementation is available.
+ * @since 2.10 (in atk)
+ */
+ private StringSequence get_string_at_offset(int offset, int granularity) {
+ int characterCount = get_character_count();
+ if (offset < 0 || offset >= characterCount) {
+ return null;
+ }
+
+ switch (granularity) {
+ case AtkTextGranularity.CHAR: {
+ String resultText = get_text(offset, offset + 1);
+ return new StringSequence(resultText, offset, offset + 1);
+ }
+ case AtkTextGranularity.WORD: {
+ // Granularity is defined by the boundaries of a word,
+ // starting at the beginning of the current word and finishing
+ // at the beginning of the following one, if present.
+ String fullText = get_text(0, characterCount);
+
+ int start = getCurrentWordStart(offset, fullText);
+ int end = getWordEndFromStart(start, fullText);
+ if (start == BreakIterator.DONE || end == BreakIterator.DONE) {
+ return null;
+ } else {
+ String resultText = get_text(start, end);
+ return new StringSequence(resultText, start, end);
+ }
+ }
+ case AtkTextGranularity.SENTENCE: {
+ // Granularity is defined by the boundaries of a sentence,
+ // starting at the beginning of the current sentence and finishing
+ // at the end of the current sentence.
+ String fullText = get_text(0, characterCount);
+
+ int start = getCurrentSentenceStart(offset, fullText);
+ int end = getSentenceEndFromStart(start, fullText);
+ if (start == BreakIterator.DONE || end == BreakIterator.DONE) {
+ return null;
+ } else {
+ String resultText = get_text(start, end);
+ return new StringSequence(resultText, start, end);
+ }
+ }
+ case AtkTextGranularity.LINE: {
+ // Granularity is defined by the boundaries of a line,
+ // starting at the beginning of the current line and finishing
+ // at the beginning of the following one, if present.
+ String fullText = get_text(0, characterCount);
+
+ int start = getCurrentLineStart(offset, fullText);
+ int end = getLineEndFromStart(start, fullText);
+
+ String resultText = get_text(start, end);
+ return new StringSequence(resultText, start, end);
+ }
+ case AtkTextGranularity.PARAGRAPH: {
+ // Granularity is defined by the boundaries of a paragraph,
+ // starting at the beginning of the current paragraph and finishing
+ // at the end of the current paragraph (newline or end of text).
+ String fullText = get_text(0, characterCount);
+
+ int start = getCurrentParagraphStart(offset, fullText);
+ int end = getParagraphEndFromStart(start, fullText);
+
+ String resultText = get_text(start, end);
+ return new StringSequence(resultText, start, end);
+ }
+ default: {
+ return null;
+ }
+ }
+ }
+
+ private record StringSequence(String str, int start_offset, int end_offset) {
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTextBoundary.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTextBoundary.java
new file mode 100644
index 000000000000..a99eb053e196
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTextBoundary.java
@@ -0,0 +1,34 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+public final class AtkTextBoundary {
+ public static final int CHAR = 0;
+ public static final int WORD_START = 1;
+ public static final int WORD_END = 2;
+ public static final int SENTENCE_START = 3;
+ public static final int SENTENCE_END = 4;
+ public static final int LINE_START = 5;
+ public static final int LINE_END = 6;
+
+ private AtkTextBoundary() {
+ }
+}
+
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTextGranularity.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTextGranularity.java
new file mode 100644
index 000000000000..256bafc608b1
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkTextGranularity.java
@@ -0,0 +1,31 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+public final class AtkTextGranularity {
+ public static final int CHAR = 0;
+ public static final int WORD = 1;
+ public static final int SENTENCE = 2;
+ public static final int LINE = 3;
+ public static final int PARAGRAPH = 4;
+
+ private AtkTextGranularity() {
+ }
+}
\ No newline at end of file
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkUtil.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkUtil.java
new file mode 100644
index 000000000000..f0e3e3def191
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkUtil.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2018, Red Hat, Inc.
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.swing.*;
+import java.util.concurrent.*;
+
+import sun.util.logging.PlatformLogger;
+
+/**
+ * Utility class for safely executing code on the event dispatching thread.
+ * Is used to wrap the callback on Java object to avoid the concurrency of AWT objects.
+ *
+ * @autor Giuseppe Capaldo
+ */
+public final class AtkUtil {
+ private static final PlatformLogger log = PlatformLogger.getLogger("org.GNOME.Accessibility.AtkUtil");
+
+ private AtkUtil() {
+ }
+
+ /**
+ * Executes a Callable on event dispatching thread and returns its result.
+ * If called from the EDT, it executes the function directly.
+ * Otherwise, it schedules asynchronous execution on the EDT and waits for the result.
+ *
+ * @param function The Callable task to execute.
+ * @param d A default value to return if an exception occurs.
+ * @param The return type of the Callable.
+ * @return The result of the Callable, or the default value in case of an exception.
+ */
+ public static T invokeInSwingAndWait(Callable function, T d) {
+ if (SwingUtilities.isEventDispatchThread()) {
+ // We are already running in the EDT, we can call it directly
+ try {
+ return function.call();
+ } catch (Exception ex) {
+ if (log.isLoggable(PlatformLogger.Level.WARNING)) {
+ log.severe("Error occurred while executing function. Returning default value.", ex);
+ }
+ return d;
+ }
+ }
+
+ RunnableFuture wf = new FutureTask<>(function);
+ SwingUtilities.invokeLater(wf);
+ try {
+ return wf.get();
+ } catch (InterruptedException | ExecutionException ex) {
+ if (log.isLoggable(PlatformLogger.Level.WARNING)) {
+ log.severe("Swing task execution interrupted or failed, returning default value.", ex);
+ }
+ return d;
+ }
+ }
+
+ /**
+ * Executes a Runnable on the event dispatching thread.
+ * If called from the EDT, it runs the function directly.
+ * Otherwise, it executed asynchronously on the AWT event dispatching thread.
+ *
+ * @param function The Runnable task to execute.
+ */
+ public static void invokeInSwing(Runnable function) {
+ if (SwingUtilities.isEventDispatchThread()) {
+ // We are already running in the EDT, we can call it directly
+ function.run();
+ return;
+ }
+
+ SwingUtilities.invokeLater(function);
+ }
+
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkValue.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkValue.java
new file mode 100644
index 000000000000..de1c68b5b4a1
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkValue.java
@@ -0,0 +1,152 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems Inc.
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.lang.ref.WeakReference;
+import java.awt.EventQueue;
+import java.lang.Double;
+
+/**
+ * The ATK Value interface implementation for Java accessibility.
+ *
+ * This class provides a bridge between Java's AccessibleValue interface
+ * and the ATK (Accessibility Toolkit) value interface, enabling access to
+ * numeric values and their ranges for accessible objects.
+ */
+public class AtkValue {
+ private final WeakReference accessibleValueWeakRef;
+
+ private AtkValue(AccessibleContext ac) {
+ assert EventQueue.isDispatchThread();
+
+ AccessibleValue accessibleValue = ac.getAccessibleValue();
+ if (accessibleValue == null) {
+ throw new IllegalArgumentException("AccessibleContext must have AccessibleValue");
+ }
+
+ this.accessibleValueWeakRef = new WeakReference(accessibleValue);
+ }
+
+ /* JNI upcalls section */
+
+ /**
+ * Factory method to create an AtkValue instance from an AccessibleContext.
+ * Called from native code via JNI.
+ *
+ * @param ac the AccessibleContext to wrap
+ * @return a new AtkValue instance, or null if creation fails
+ */
+ private static AtkValue create_atk_value(AccessibleContext ac) {
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return new AtkValue(ac);
+ }, null);
+ }
+
+ /**
+ * Gets the current value of this object.
+ * Called from native code via JNI.
+ *
+ * @return a Number representing the current accessible value, or null if the value
+ * is unavailable or the object doesn't implement this interface
+ */
+ private Number get_current_value() {
+ AccessibleValue accessibleValue = accessibleValueWeakRef.get();
+ if (accessibleValue == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ return accessibleValue.getCurrentAccessibleValue();
+ }, null);
+ }
+
+ /**
+ * Gets the maximum value of this object.
+ * Called from native code via JNI.
+ *
+ * @return a Double representing the maximum accessible value, or null if the value
+ * is unavailable or the object doesn't implement this interface
+ */
+ private Double get_maximum_value() {
+ AccessibleValue accessibleValue = accessibleValueWeakRef.get();
+ if (accessibleValue == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Number max = accessibleValue.getMaximumAccessibleValue();
+ if (max == null) {
+ return null;
+ }
+ return Double.valueOf(max.doubleValue());
+ }, null);
+ }
+
+ /**
+ * Gets the minimum value of this object.
+ * Called from native code via JNI.
+ *
+ * @return a Double representing the minimum accessible value, or null if the value
+ * is unavailable or the object doesn't implement this interface
+ */
+ private Double get_minimum_value() {
+ AccessibleValue accessibleValue = accessibleValueWeakRef.get();
+ if (accessibleValue == null) {
+ return null;
+ }
+
+ return AtkUtil.invokeInSwingAndWait(() -> {
+ Number min = accessibleValue.getMinimumAccessibleValue();
+ if (min == null) {
+ return null;
+ }
+ return Double.valueOf(min.doubleValue());
+ }, null);
+ }
+
+ /**
+ * Sets the current value of this object.
+ * Called from native code via JNI.
+ *
+ * @param n the Number value to set as the current accessible value
+ */
+ private void set_value(Number n) {
+ AccessibleValue accessibleValue = accessibleValueWeakRef.get();
+ if (accessibleValue == null) {
+ return;
+ }
+
+ AtkUtil.invokeInSwing(() -> {
+ accessibleValue.setCurrentAccessibleValue(n);
+ });
+ }
+
+ /**
+ * Gets the minimum increment by which the value may be changed.
+ * Called from native code via JNI.
+ *
+ * @return the minimum increment value, returns Double.MIN_VALUE
+ */
+ private double get_increment() {
+ return Double.MIN_VALUE;
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkWrapper.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkWrapper.java
new file mode 100644
index 000000000000..3e4dd483065a
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkWrapper.java
@@ -0,0 +1,710 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems Inc.
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.GNOME.Accessibility;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.*;
+import java.io.*;
+import javax.accessibility.*;
+import java.awt.Toolkit;
+import javax.swing.JComboBox;
+import java.util.*;
+
+import sun.util.logging.PlatformLogger;
+
+public class AtkWrapper {
+ private static final PlatformLogger log = PlatformLogger.getLogger("org.GNOME.Accessibility.AtkWrapper");
+ private static boolean accessibilityEnabled = false;
+ private static boolean nativeLibraryInited = false;
+ // Previously focused accessible context
+ private static AccessibleContext oldSourceContext = null;
+ // Last saved focused accessible context (excluding JRootPane)
+ private static AccessibleContext savedSourceContext = null;
+ // Previously focused JRootPane's AccessibleContext
+ private static AccessibleContext oldPaneContext = null;
+
+ private static final PropertyChangeListener propertyChangeListener = new PropertyChangeListener() {
+
+ public void propertyChange(PropertyChangeEvent e) {
+ Object o = e.getSource();
+ AccessibleContext ac;
+ if (o instanceof AccessibleContext accessibleContext) {
+ ac = accessibleContext;
+ } else if (o instanceof Accessible accessible) {
+ ac = accessible.getAccessibleContext();
+ } else {
+ return;
+ }
+
+ if (ac == null) {
+ return;
+ }
+
+ Object oldValue = e.getOldValue();
+ Object newValue = e.getNewValue();
+ String propertyName = e.getPropertyName();
+
+ if (propertyName.equals(AccessibleContext.ACCESSIBLE_CARET_PROPERTY)) {
+ Object[] args = new Object[1];
+ args[0] = newValue;
+
+ if (newValue != null && newValue instanceof AccessibleContext accessibleContext) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ } else if (newValue != null && newValue instanceof Accessible accessible) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ }
+ }
+
+ emitSignal(ac, AtkSignal.TEXT_CARET_MOVED, args);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TEXT_PROPERTY)) {
+ /**
+ * The documentation for AccessibleContext.ACCESSIBLE_TEXT_PROPERTY states that newValue
+ * is an instance of AccessibleTextSequence in an insertion event, and oldValue is an
+ * instance of AccessibleTextSequence in a deletion event. This allows us to use
+ * AtkSignal.TEXT_PROPERTY_CHANGED_INSERT and AtkSignal.TEXT_PROPERTY_CHANGED_DELETE.
+ * However, Swing components send a signal where oldValue is null and newValue is an
+ * instance of Integer for both insertion and deletion events. As a result, we continue
+ * using the deprecated in atk AtkSignal.TEXT_PROPERTY_CHANGED.
+ */
+ if (oldValue == null && newValue != null) {
+ if (newValue instanceof AccessibleTextSequence newSeq) { // insertion event according to the Swing documentation
+ Object[] args = new Object[3];
+ args[0] = Integer.valueOf(newSeq.startIndex); // the position (character offset) of the insertion
+ args[1] = Integer.valueOf(newSeq.endIndex - newSeq.startIndex); // the length (in characters) of text inserted
+ args[2] = newSeq.text; // the new text inserted
+
+ /*
+ * `text_insert` has 3 parameters:
+ * 1. arg1 - the position (character offset) of the insertion.
+ * 2. arg2 - the length (in characters) of text inserted.
+ * 3. arg3 - the new text inserted.
+ */
+ emitSignal(ac, AtkSignal.TEXT_PROPERTY_CHANGED_INSERT, args);
+ } else if (newValue instanceof Integer) { // real insertion or deletion event that Swing components send
+ Object[] args = new Object[1];
+ args[0] = newValue; // the position (character offset) of the insertion or deletion
+ /*
+ * `text-changed` has 2 parameters:
+ * 1. arg1 - the position (character offset) of the insertion or deletion.
+ * 2. arg2 - the length (in characters) of text inserted or deleted.
+ *
+ * In our case, arg2 is unknown.
+ */
+ emitSignal(ac, AtkSignal.TEXT_PROPERTY_CHANGED, args);
+ }
+ } else if (oldValue != null && newValue == null) {
+ if (oldValue instanceof AccessibleTextSequence oldSeq) { // deletion event according to the Swing documentation
+ Object[] args = new Object[3];
+ args[0] = Integer.valueOf(oldSeq.startIndex); // the position (character offset) of the removal
+ args[1] = Integer.valueOf(oldSeq.endIndex - oldSeq.startIndex); // the length (in characters) of text removed
+ args[2] = oldSeq.text; // the old text removed
+
+ /*
+ * `text_remove` has 3 parameters:
+ * 1. arg1 - the position (character offset) of the removal.
+ * 2. arg2 - the length (in characters) of text removed.
+ * 3. arg3 - the new text removed.
+ */
+ emitSignal(ac, AtkSignal.TEXT_PROPERTY_CHANGED_DELETE, args);
+ }
+ }
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_CHILD_PROPERTY)) {
+ if (oldValue == null && newValue != null) { //child added
+ AccessibleContext child_ac;
+ if (newValue instanceof Accessible accessible) {
+ child_ac = accessible.getAccessibleContext();
+ } else {
+ return;
+ }
+
+ if (child_ac == null) {
+ return;
+ }
+
+ Object[] args = new Object[2];
+ args[0] = Integer.valueOf(child_ac.getAccessibleIndexInParent());
+ args[1] = child_ac;
+
+ AtkWrapperDisposer.getInstance().addRecord(child_ac);
+
+ emitSignal(ac, AtkSignal.OBJECT_CHILDREN_CHANGED_ADD, args);
+ } else if (oldValue != null && newValue == null) { //child removed
+ AccessibleContext child_ac;
+ if (oldValue instanceof Accessible accessible) {
+ child_ac = accessible.getAccessibleContext();
+ } else {
+ return;
+ }
+
+ if (child_ac == null) {
+ return;
+ }
+
+ Object[] args = new Object[2];
+ args[0] = Integer.valueOf(child_ac.getAccessibleIndexInParent());
+ args[1] = child_ac;
+
+ AtkWrapperDisposer.getInstance().addRecord(child_ac);
+
+ emitSignal(ac, AtkSignal.OBJECT_CHILDREN_CHANGED_REMOVE, args);
+ }
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY)) {
+ AccessibleContext child_ac;
+ if (newValue instanceof Accessible accessible) {
+ child_ac = accessible.getAccessibleContext();
+ } else {
+ return;
+ }
+ if (child_ac == null) {
+ return;
+ }
+
+ /*
+ * JComboBox sends two signals: object:active-descendant-changed and object:selection-changed.
+ * Information about the component is announced when processing the object:selection-changed signal.
+ * The object:active-descendant-changed signal interrupts this announcement in earlier versions of GNOME.
+ * Skipping the emission of object:active-descendant-changed signal resolves this issue.
+ * See: https://gitlab.gnome.org/GNOME/java-atk-wrapper/-/issues/29
+ */
+ if (ac.getAccessibleRole() == AccessibleRole.COMBO_BOX) {
+ return;
+ }
+ Object[] args = new Object[1];
+ args[0] = child_ac;
+
+ AtkWrapperDisposer.getInstance().addRecord(child_ac);
+
+ emitSignal(ac, AtkSignal.OBJECT_ACTIVE_DESCENDANT_CHANGED, args);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY)) {
+ boolean isTextEvent = false;
+ AccessibleRole role = ac.getAccessibleRole();
+ if ((role == AccessibleRole.TEXT) ||
+ role.toDisplayString(java.util.Locale.US).equalsIgnoreCase("paragraph")) {
+ isTextEvent = true;
+ } else if (role == AccessibleRole.MENU_BAR) {
+ dispatchFocusEvent(o);
+ } else if (role == AccessibleRole.PAGE_TAB_LIST) {
+ AccessibleSelection selection = ac.getAccessibleSelection();
+ if (selection != null &&
+ selection.getAccessibleSelectionCount() > 0) {
+ java.lang.Object child = selection.getAccessibleSelection(0);
+ dispatchFocusEvent(child);
+ }
+ }
+
+ /*
+ * When navigating through nodes, JTree sends two signals: object:selection-changed and object:active-descendant-changed.
+ * The object:selection-changed signal is emitted before object:active-descendant-changed, leading to the following issues:
+ * 1. When focusing on the root of the tree, object:active-descendant-changed interrupts the announcement and does not
+ * provide any output because the FOCUS MANAGER doesn't update the focus - it is already set to the same object.
+ * 2. For other nodes, the correct behavior happens because of the bug in JTree:
+ * AccessibleJTree incorrectly reports the selected children and object:selection-changed sets focus on the tree.
+ *
+ * Removing the object:selection-changed signal ensures that the locus of focus is updated during object:active-descendant-changed,
+ * allowing elements to be announced correctly.
+ * See: https://gitlab.gnome.org/GNOME/orca/-/issues/552
+ */
+ if (ac.getAccessibleRole() == AccessibleRole.TREE) {
+ return;
+ }
+
+ if (!isTextEvent) {
+ emitSignal(ac, AtkSignal.OBJECT_SELECTION_CHANGED, null);
+ }
+
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY)) {
+ emitSignal(ac, AtkSignal.OBJECT_VISIBLE_DATA_CHANGED, null);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_ACTION_PROPERTY)) {
+ Object[] args = new Object[2];
+ args[0] = oldValue;
+ args[1] = newValue;
+ emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_ACTIONS, args);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_VALUE_PROPERTY)) {
+ if (oldValue instanceof Number oldValueNamber && newValue instanceof Number newValueNumber) {
+ Object[] args = new Object[2];
+ args[0] = Double.valueOf(oldValueNamber.doubleValue());
+ args[1] = Double.valueOf(newValueNumber.doubleValue());
+ emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_VALUE, args);
+ }
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY)) {
+ emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_DESCRIPTION, null);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_NAME_PROPERTY)) {
+ if (!Objects.equals(newValue, oldValue)) {
+ emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_NAME, null);
+ }
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_HYPERTEXT_OFFSET)) {
+ emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_HYPERTEXT_OFFSET, null);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_MODEL_CHANGED)) {
+ emitSignal(ac, AtkSignal.TABLE_MODEL_CHANGED, null);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_CAPTION_CHANGED)) {
+ emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_CAPTION, null);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_SUMMARY_CHANGED)) {
+ emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_SUMMARY, null);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_COLUMN_HEADER_CHANGED)) {
+ emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_HEADER, null);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_COLUMN_DESCRIPTION_CHANGED)) {
+ emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_DESCRIPTION, null);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_ROW_HEADER_CHANGED)) {
+ emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_HEADER, null);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_TABLE_ROW_DESCRIPTION_CHANGED)) {
+ emitSignal(ac, AtkSignal.OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_DESCRIPTION, null);
+ } else if (propertyName.equals(AccessibleContext.ACCESSIBLE_STATE_PROPERTY)) {
+ Accessible parent = ac.getAccessibleParent();
+ AccessibleRole role = ac.getAccessibleRole();
+ if (role != null) {
+ if (newValue == AccessibleState.FOCUSED ||
+ newValue == AccessibleState.SELECTED) {
+ dispatchFocusEvent(o);
+ }
+ }
+ AccessibleState state;
+ boolean value = false;
+ if (newValue != null) {
+ state = (AccessibleState) newValue;
+ value = true;
+ } else {
+ state = (AccessibleState) oldValue;
+ value = false;
+ }
+ if (state == AccessibleState.COLLAPSED) {
+ state = AccessibleState.EXPANDED;
+ value = false;
+ }
+ if (parent instanceof JComboBox && oldValue ==
+ AccessibleState.VISIBLE) {
+ objectStateChange(ac, AccessibleState.SHOWING, value);
+ }
+ objectStateChange(ac, state, value);
+ if (parent instanceof JComboBox && newValue ==
+ AccessibleState.VISIBLE && oldValue == null) {
+ objectStateChange(ac, AccessibleState.SHOWING, value);
+ }
+ }
+ }
+ };
+
+ static {
+ try {
+ String xpropPath = findXPropPath();
+ if (xpropPath == null) {
+ throw new RuntimeException("No xprop found");
+ }
+ System.loadLibrary("atk-wrapper");
+ ProcessBuilder builder = new ProcessBuilder(xpropPath, "-root");
+ Process p = builder.start();
+ BufferedReader b = new BufferedReader(new InputStreamReader(p.getInputStream()));
+ String result;
+ while ((result = b.readLine()) != null) {
+ if (result.indexOf("AT_SPI_IOR") >= 0 || result.indexOf("AT_SPI_BUS") >= 0) {
+ if (AtkWrapper.initNativeLibrary()) {
+ nativeLibraryInited = true;
+ }
+ break;
+ }
+ }
+
+ if (!nativeLibraryInited) {
+ builder = new ProcessBuilder("dbus-send", "--session", "--dest=org.a11y.Bus", "--print-reply", "/org/a11y/bus", "org.a11y.Bus.GetAddress");
+ p = builder.start();
+ var ignoredOutput = p.getInputStream();
+ while (ignoredOutput.skip(Long.MAX_VALUE) == Long.MAX_VALUE) ;
+ p.waitFor();
+ if (p.exitValue() == 0) {
+ if (AtkWrapper.initNativeLibrary()) {
+ nativeLibraryInited = true;
+ }
+ }
+ }
+
+ if (!nativeLibraryInited) {
+ throw new IllegalStateException("Accessibility is disabled due to an error in initNativeLibrary.");
+ }
+
+ if (!AtkWrapper.loadAtkBridge()) {
+ throw new IllegalStateException("Accessibility is disabled due to an error in loadAtkBridge.");
+ }
+
+ accessibilityEnabled = true;
+
+ } catch (Exception e) {
+ if (log.isLoggable(PlatformLogger.Level.SEVERE)) {
+ log.severe("Error during ATK accessibility initialization: ", e);
+ }
+ }
+ }
+
+ private final WindowAdapter windowAdapter = new WindowAdapter() {
+ public void windowActivated(WindowEvent e) {
+ Object o = e.getSource();
+ if (o instanceof Accessible accessible) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ AtkWrapper.windowActivate(accessibleContext);
+ }
+ }
+ }
+
+ public void windowDeactivated(WindowEvent e) {
+ Object o = e.getSource();
+ if (o instanceof Accessible accessible) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ AtkWrapper.windowDeactivate(accessibleContext);
+ }
+ }
+ }
+
+ public void windowStateChanged(WindowEvent e) {
+ Object o = e.getSource();
+ if (o instanceof Accessible accessible) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ AtkWrapper.windowStateChange(accessibleContext);
+ if ((e.getNewState() & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH) {
+ AtkWrapper.windowMaximize(accessibleContext);
+ }
+ }
+ }
+ }
+
+ public void windowDeiconified(WindowEvent e) {
+ Object o = e.getSource();
+ if (o instanceof Accessible accessible) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ AtkWrapper.windowRestore(accessibleContext);
+ }
+ }
+ }
+
+ public void windowIconified(WindowEvent e) {
+ Object o = e.getSource();
+ if (o instanceof Accessible accessible) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ AtkWrapper.windowMinimize(accessibleContext);
+ }
+ }
+ }
+
+ public void windowOpened(WindowEvent e) {
+ Object o = e.getSource();
+ if (o instanceof Accessible accessible) {
+ boolean isToplevel = isToplevel(o);
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ AtkWrapper.windowOpen(accessibleContext, isToplevel);
+ }
+ }
+ }
+
+ public void windowClosed(WindowEvent e) {
+ Object o = e.getSource();
+ if (o instanceof Accessible accessible) {
+ boolean isToplevel = isToplevel(o);
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ AtkWrapper.windowClose(accessibleContext, isToplevel);
+ }
+ }
+ }
+
+ public void windowClosing(WindowEvent e) {
+ Object o = e.getSource();
+ if (o instanceof Accessible accessible) {
+ boolean isToplevel = isToplevel(o);
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ AtkWrapper.windowClose(accessibleContext, isToplevel);
+ }
+ }
+ }
+
+ public void windowGainedFocus(WindowEvent e) {
+ }
+
+ public void windowLostFocus(WindowEvent e) {
+ }
+ };
+ private final ComponentAdapter componentAdapter = new ComponentAdapter() {
+ public void componentResized(ComponentEvent e) {
+ Object o = e.getSource();
+ if (o instanceof Accessible accessible) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ AtkWrapper.boundsChanged(accessibleContext);
+ }
+ }
+ }
+
+ public void componentMoved(ComponentEvent e) {
+ Object o = e.getSource();
+ if (o instanceof Accessible accessible) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ AtkWrapper.boundsChanged(accessibleContext);
+ }
+ }
+ }
+
+ public void componentShown(ComponentEvent e) {
+ Object o = e.getSource();
+ if (o instanceof Accessible accessible) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ AtkWrapper.componentAdded(accessibleContext);
+ }
+ }
+ }
+
+ public void componentHidden(ComponentEvent e) {
+ Object o = e.getSource();
+ if (o instanceof Accessible accessible) {
+ AccessibleContext accessibleContext = accessible.getAccessibleContext();
+ if (accessibleContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(accessibleContext);
+ AtkWrapper.componentRemoved(accessibleContext);
+ }
+ }
+ }
+ };
+ private final AWTEventListener globalListener = new AWTEventListener() {
+ private boolean firstEvent = true;
+
+ public void eventDispatched(AWTEvent e) {
+ if (e instanceof WindowEvent windowEvent) {
+ switch (e.getID()) {
+ case WindowEvent.WINDOW_OPENED:
+ Window win = windowEvent.getWindow();
+ win.addWindowListener(windowAdapter);
+ win.addWindowStateListener(windowAdapter);
+ win.addWindowFocusListener(windowAdapter);
+ break;
+ case WindowEvent.WINDOW_LOST_FOCUS:
+ AtkWrapper.dispatchFocusEvent(null);
+ break;
+ default:
+ break;
+ }
+ } else if (e instanceof ContainerEvent containerEvent) {
+ switch (e.getID()) {
+ case ContainerEvent.COMPONENT_ADDED: {
+ Component c = containerEvent.getChild();
+ c.addComponentListener(componentAdapter);
+ break;
+ }
+ case ContainerEvent.COMPONENT_REMOVED: {
+ Component c = containerEvent.getChild();
+ c.removeComponentListener(componentAdapter);
+ break;
+ }
+
+ default:
+ break;
+ }
+ } else if (e instanceof FocusEvent) {
+ switch (e.getID()) {
+ case FocusEvent.FOCUS_GAINED:
+ AtkWrapper.dispatchFocusEvent(e.getSource());
+ break;
+ default:
+ break;
+ }
+ } else if (e instanceof KeyEvent keyEvent) {
+ AtkWrapper.dispatchKeyEvent(new AtkKeyEvent(keyEvent));
+ }
+ }
+ };
+ private final Toolkit toolkit = Toolkit.getDefaultToolkit();
+
+ public AtkWrapper() {
+ if (!accessibilityEnabled) {
+ throw new IllegalStateException("AtkWrapper not initialized due to disabled accessibility.");
+ }
+
+ toolkit.addAWTEventListener(globalListener,
+ AWTEvent.WINDOW_EVENT_MASK |
+ AWTEvent.FOCUS_EVENT_MASK |
+ AWTEvent.CONTAINER_EVENT_MASK |
+ AWTEvent.KEY_EVENT_MASK);
+ }
+
+ public static void main(String args[]) {
+ new AtkWrapper();
+ }
+
+ private static String findXPropPath() {
+ String pathEnv = System.getenv().get("PATH");
+ if (pathEnv != null) {
+ String pathSeparator = File.pathSeparator;
+ java.util.List paths = Arrays.asList(pathEnv.split(File.pathSeparator));
+ for (String path : paths) {
+ File xpropFile = new File(path, "xprop");
+ if (xpropFile.exists()) {
+ return xpropFile.getAbsolutePath();
+ }
+ }
+ }
+ return null;
+ }
+
+ private static boolean isToplevel(Object o) {
+ boolean isToplevel = false;
+ if (o instanceof java.awt.Window ||
+ o instanceof java.awt.Frame ||
+ o instanceof java.awt.Dialog) {
+ isToplevel = true;
+ }
+ return isToplevel;
+ }
+
+ private static void dispatchFocusEvent(Object eventSource) {
+ if (eventSource == null) {
+ oldSourceContext = null;
+ return;
+ }
+
+ AccessibleContext eventSourceContext;
+
+ try {
+ if (eventSource instanceof AccessibleContext accessibleContext) {
+ eventSourceContext = accessibleContext;
+ } else if (eventSource instanceof Accessible accessible) {
+ eventSourceContext = accessible.getAccessibleContext();
+ } else {
+ return;
+ }
+
+ if (eventSourceContext == oldSourceContext) {
+ return;
+ }
+
+ if (oldSourceContext != null) {
+ AccessibleRole role = oldSourceContext.getAccessibleRole();
+ if (role == AccessibleRole.MENU || role == AccessibleRole.MENU_ITEM) {
+ String jrootpane = "javax.swing.JRootPane$AccessibleJRootPane";
+ String name = eventSourceContext.getClass().getName();
+
+ if (jrootpane.compareTo(name) == 0) {
+ oldPaneContext = eventSourceContext;
+ return;
+ }
+ }
+ savedSourceContext = eventSourceContext;
+ } else if (oldPaneContext == eventSourceContext) {
+ eventSourceContext = savedSourceContext;
+ } else {
+ savedSourceContext = eventSourceContext;
+ }
+
+ oldSourceContext = eventSourceContext;
+ AccessibleRole role = eventSourceContext.getAccessibleRole();
+ if (role == AccessibleRole.PAGE_TAB_LIST) {
+ AccessibleSelection accSelection = eventSourceContext.getAccessibleSelection();
+ if (accSelection != null && accSelection.getAccessibleSelectionCount() > 0) {
+ Object child = accSelection.getAccessibleSelection(0);
+ if (child instanceof AccessibleContext accessibleContext) {
+ eventSourceContext = accessibleContext;
+ } else if (child instanceof Accessible accessible) {
+ eventSourceContext = accessible.getAccessibleContext();
+ } else {
+ return;
+ }
+ }
+ }
+ if (eventSourceContext != null) {
+ AtkWrapperDisposer.getInstance().addRecord(eventSourceContext);
+ focusNotify(eventSourceContext);
+ }
+ } catch (Exception e) {
+ if (log.isLoggable(PlatformLogger.Level.SEVERE)) {
+ log.severe("Error in dispatchFocusEvent: ", e);
+ }
+ }
+ }
+
+
+ private native static boolean initNativeLibrary();
+
+ private native static boolean loadAtkBridge();
+
+ native static long createNativeResources(AccessibleContext ac);
+
+ native static void releaseNativeResources(long ref);
+
+ private native static void focusNotify(AccessibleContext ac);
+
+ private native static void windowOpen(AccessibleContext ac,
+ boolean isToplevel);
+
+ private native static void windowClose(AccessibleContext ac,
+ boolean isToplevel);
+
+ private native static void windowMinimize(AccessibleContext ac);
+
+ private native static void windowMaximize(AccessibleContext ac);
+
+ private native static void windowRestore(AccessibleContext ac);
+
+ private native static void windowActivate(AccessibleContext ac);
+
+ private native static void windowDeactivate(AccessibleContext ac);
+
+ private native static void windowStateChange(AccessibleContext ac);
+
+ private native static void emitSignal(AccessibleContext ac, int id, Object[] args);
+
+ private native static void objectStateChange(AccessibleContext ac,
+ Object state, boolean value);
+
+ private native static void componentAdded(AccessibleContext ac);
+
+ private native static void componentRemoved(AccessibleContext ac);
+
+ private native static void boundsChanged(AccessibleContext ac);
+
+ private native static void dispatchKeyEvent(AtkKeyEvent e);
+
+
+ // JNI upcalls section
+
+ private static void register_property_change_listener(AccessibleContext ac) {
+ if (ac != null) {
+ AtkUtil.invokeInSwing(() -> {
+ ac.addPropertyChangeListener(propertyChangeListener);
+ });
+ }
+ }
+}
diff --git a/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkWrapperDisposer.java b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkWrapperDisposer.java
new file mode 100644
index 000000000000..37f4082bb2c7
--- /dev/null
+++ b/src/jdk.accessibility/linux/classes/org/GNOME/Accessibility/AtkWrapperDisposer.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2025 JetBrains s.r.o.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.GNOME.Accessibility;
+
+import javax.accessibility.*;
+import java.util.*;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.PhantomReference;
+import java.security.PrivilegedAction;
+
+import jdk.internal.misc.InnocuousThread;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * Manages the association between an AccessibleContext
+ * and native resources. Can be used to create such
+ * associations and ensures that the native resource associated
+ * with the AccessibleContext is released when the AccessibleContext
+ * is garbage collected.
+ *
+ * Java classes are fully responsible for creating associations between
+ * an AccessibleContext and native resources. For example, when a JNI upcall method
+ * returns an AccessibleContext, native assumes that the corresponding native resource
+ * has already been created and the association between the AccessibleContext and the
+ * native resource exists.
+ */
+@SuppressWarnings("removal")
+public class AtkWrapperDisposer implements Runnable {
+ // Reference queue that holds objects ready for garbage collection
+ private static final ReferenceQueue queue = new ReferenceQueue<>();
+
+ // Maps PhantomReferences and their associated native resource pointer
+ private static final Map, Long> phantomMap = new HashMap<>();
+
+ // Maps AccessibleContext object with native resource pointer
+ private static final WeakHashMap weakHashMap = new WeakHashMap<>();
+
+ private static final Object lock = new Object();
+ private static final PlatformLogger log = PlatformLogger.getLogger("org.GNOME.Accessibility.AtkWrapperDisposer");
+ private static volatile AtkWrapperDisposer INSTANCE = null;
+
+ private AtkWrapperDisposer() {
+ }
+
+ private void init() {
+ Thread t = InnocuousThread.newThread("Atk Wrapper Disposer", INSTANCE, Thread.MAX_PRIORITY);
+ t.setContextClassLoader(null);
+ t.setDaemon(true);
+ t.start();
+ }
+
+ public static synchronized AtkWrapperDisposer getInstance() {
+ if (INSTANCE == null) {
+ synchronized (AtkWrapperDisposer.class) {
+ if (INSTANCE == null) {
+ INSTANCE = new AtkWrapperDisposer();
+ INSTANCE.init();
+ }
+ }
+ }
+ return INSTANCE;
+ }
+
+ /**
+ * Monitors the reference queue and releases native resources when an associated
+ * AccessibleContext is garbage collected. The native resource is released using
+ * {@link AtkWrapper#releaseNativeResources}.
+ */
+ public void run() {
+ while (true) {
+ try {
+ // When an AccessibleContext is freed, release the associated native resource
+ Reference extends AccessibleContext> obj = queue.remove();
+ long nativeReference;
+ synchronized (lock) {
+ nativeReference = phantomMap.remove(obj);
+ }
+ AtkWrapper.releaseNativeResources(nativeReference);
+ obj.clear();
+ obj = null;
+ } catch (Exception e) {
+ if (log.isLoggable(PlatformLogger.Level.SEVERE)) {
+ log.severe("Exception while removing reference: ", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Associates an AccessibleContext with a newly created native resource. If the AccessibleContext
+ * is not already registered, a new native resource pointer is created using
+ * {@link AtkWrapper#createNativeResources}
+ *
+ * @param ac The AccessibleContext to associate with a native resource.
+ */
+ public void addRecord(AccessibleContext ac) {
+ if (ac == null) {
+ return;
+ }
+ synchronized (lock) {
+ if (!weakHashMap.containsKey(ac)) {
+ long nativeReference = AtkWrapper.createNativeResources(ac);
+ if (nativeReference != -1) {
+ PhantomReference phantomReference = new PhantomReference<>(ac, queue);
+ phantomMap.put(phantomReference, nativeReference);
+ weakHashMap.put(ac, nativeReference);
+ }
+ }
+ }
+ }
+
+ private long getRecord(AccessibleContext ac) {
+ synchronized (lock) {
+ if (weakHashMap.containsKey(ac)) {
+ return weakHashMap.get(ac);
+ }
+ }
+ return -1;
+ }
+
+ // JNI upcalls section
+
+ /**
+ * Retrieves the native resource associated with the given AccessibleContext.
+ * If no record exists, returns -1.
+ *
+ * @param ac The AccessibleContext whose native resource is requested.
+ * @return The native resource pointer associated with the given AccessibleContext,
+ * or -1 if no record exists.
+ */
+ private static long get_resource(AccessibleContext ac) {
+ return AtkWrapperDisposer.getInstance().getRecord(ac);
+ }
+}
\ No newline at end of file
diff --git a/src/jdk.accessibility/linux/conf/accessibility.properties b/src/jdk.accessibility/linux/conf/accessibility.properties
new file mode 100644
index 000000000000..0bbed565f6ad
--- /dev/null
+++ b/src/jdk.accessibility/linux/conf/accessibility.properties
@@ -0,0 +1 @@
+assistive_technologies=org.GNOME.Accessibility.AtkWrapper
\ No newline at end of file
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/AtkInterface.h b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkInterface.h
new file mode 100644
index 000000000000..d31659b0b0a5
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkInterface.h
@@ -0,0 +1,35 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_GNOME_Accessibility_AtkInterface */
+
+#ifndef _Included_org_GNOME_Accessibility_AtkInterface
+#define _Included_org_GNOME_Accessibility_AtkInterface
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_ACTION
+#define org_GNOME_Accessibility_AtkInterface_INTERFACE_ACTION 1L
+#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_COMPONENT
+#define org_GNOME_Accessibility_AtkInterface_INTERFACE_COMPONENT 2L
+#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_DOCUMENT
+#define org_GNOME_Accessibility_AtkInterface_INTERFACE_DOCUMENT 4L
+#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_EDITABLE_TEXT
+#define org_GNOME_Accessibility_AtkInterface_INTERFACE_EDITABLE_TEXT 8L
+#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_HYPERTEXT
+#define org_GNOME_Accessibility_AtkInterface_INTERFACE_HYPERTEXT 32L
+#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_IMAGE
+#define org_GNOME_Accessibility_AtkInterface_INTERFACE_IMAGE 64L
+#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_SELECTION
+#define org_GNOME_Accessibility_AtkInterface_INTERFACE_SELECTION 128L
+#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE
+#define org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE 512L
+#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE_CELL
+#define org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE_CELL 1024L
+#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_TEXT
+#define org_GNOME_Accessibility_AtkInterface_INTERFACE_TEXT 2048L
+#undef org_GNOME_Accessibility_AtkInterface_INTERFACE_VALUE
+#define org_GNOME_Accessibility_AtkInterface_INTERFACE_VALUE 4096L
+#ifdef __cplusplus
+}
+#endif
+#endif
\ No newline at end of file
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/AtkSignal.h b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkSignal.h
new file mode 100644
index 000000000000..cfce36781006
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkSignal.h
@@ -0,0 +1,68 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_GNOME_Accessibility_AtkSignal */
+
+#ifndef _Included_org_GNOME_Accessibility_AtkSignal
+#define _Included_org_GNOME_Accessibility_AtkSignal
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef org_GNOME_Accessibility_AtkSignal_TEXT_CARET_MOVED
+#define org_GNOME_Accessibility_AtkSignal_TEXT_CARET_MOVED 0L
+#undef org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_INSERT
+#define org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_INSERT 1L
+#undef org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_DELETE
+#define org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_DELETE 2L
+#undef org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_REPLACE
+#define org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_REPLACE 3L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_ADD
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_ADD 4L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_REMOVE
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_REMOVE 5L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_ACTIVE_DESCENDANT_CHANGED
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_ACTIVE_DESCENDANT_CHANGED 6L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_SELECTION_CHANGED
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_SELECTION_CHANGED 7L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_VISIBLE_DATA_CHANGED
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_VISIBLE_DATA_CHANGED 8L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_ACTIONS
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_ACTIONS \
+ 9L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_VALUE
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_VALUE \
+ 10L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_DESCRIPTION
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_DESCRIPTION \
+ 11L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_NAME
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_NAME \
+ 12L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_HYPERTEXT_OFFSET
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_HYPERTEXT_OFFSET \
+ 13L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_CAPTION
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_CAPTION \
+ 14L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_SUMMARY
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_SUMMARY \
+ 15L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_HEADER
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_HEADER \
+ 16L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_DESCRIPTION
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_DESCRIPTION \
+ 17L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_HEADER
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_HEADER \
+ 18L
+#undef org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_DESCRIPTION
+#define org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_DESCRIPTION \
+ 19L
+#undef org_GNOME_Accessibility_AtkSignal_TABLE_MODEL_CHANGED
+#define org_GNOME_Accessibility_AtkSignal_TABLE_MODEL_CHANGED 20L
+#undef org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED
+#define org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED 21L
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/AtkWrapper.c b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkWrapper.c
new file mode 100644
index 000000000000..3e87b2f40da4
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkWrapper.c
@@ -0,0 +1,1784 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems Inc.
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "AtkWrapper.h"
+#include "AtkSignal.h"
+#include "jawcache.h"
+#include "jawimpl.h"
+#include "jawtoplevel.h"
+#include "jawutil.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+int jaw_debug = 0;
+FILE *jaw_log_file;
+time_t jaw_start_time;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define KEY_DISPATCH_NOT_DISPATCHED 0
+#define KEY_DISPATCH_CONSUMED 1
+#define KEY_DISPATCH_NOT_CONSUMED 2
+
+#define GDK_SHIFT_MASK (1 << 0)
+#define GDK_CONTROL_MASK (1 << 2)
+#define GDK_MOD1_MASK (1 << 3)
+#define GDK_MOD5_MASK (1 << 7)
+#define GDK_META_MASK (1 << 28)
+
+#define JAW_LOG_FILE "jaw_log.txt"
+#define JAW_LOG_FILE2 "/tmp/" JAW_LOG_FILE
+
+#define ATSPI_CHECK_VERSION(major, minor, micro) \
+ (((ATSPI_MAJOR_VERSION) > (major)) || \
+ ((ATSPI_MAJOR_VERSION) == (major) && (ATSPI_MINOR_VERSION) > (minor)) || \
+ ((ATSPI_MAJOR_VERSION) == (major) && (ATSPI_MINOR_VERSION) == (minor) && \
+ (ATSPI_MICRO_VERSION) >= (micro)))
+
+gboolean jaw_accessibility_init(void);
+void jaw_accessibility_shutdown(void);
+
+static GMainLoop *jni_main_loop;
+static GMainContext *jni_main_context;
+
+static gboolean jaw_initialized = FALSE;
+
+static jclass cachedWrapperIntegerClass = NULL;
+static jmethodID cachedWrapperIntValueMethod = NULL;
+static jclass cachedWrapperAtkKeyEventClass = NULL;
+static jfieldID cachedWrapperTypeFieldID = NULL;
+static jfieldID cachedWrapperTypePressedFieldID = NULL;
+static jfieldID cachedWrapperTypeReleasedFieldID = NULL;
+static jfieldID cachedWrapperShiftFieldID = NULL;
+static jfieldID cachedWrapperCtrlFieldID = NULL;
+static jfieldID cachedWrapperAltFieldID = NULL;
+static jfieldID cachedWrapperMetaFieldID = NULL;
+static jfieldID cachedWrapperAltGrFieldID = NULL;
+static jfieldID cachedWrapperKeyvalFieldID = NULL;
+static jfieldID cachedWrapperStringFieldID = NULL;
+static jfieldID cachedWrapperKeycodeFieldID = NULL;
+static jfieldID cachedWrapperTimestampFieldID = NULL;
+
+static GMutex wrapper_cache_mutex;
+static gboolean wrapper_cache_initialized = FALSE;
+
+static gboolean atk_wrapper_init_jni_cache(JNIEnv *jniEnv);
+
+/*
+ * OpenJDK seems to be sending flurries of visible data changed events, which
+ * overloads us. They are however usually just for the same object, so we can
+ * compact them: there is no need to queue another one if the previous hasn't
+ * even been sent!
+ */
+static pthread_mutex_t jaw_vdc_dup_mutex = PTHREAD_MUTEX_INITIALIZER;
+static jobject jaw_vdc_last_ac = NULL;
+
+static void jaw_vdc_clear_last_ac(JNIEnv *jniEnv) {
+ if (jaw_vdc_last_ac != NULL && jniEnv != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_vdc_last_ac);
+ }
+ jaw_vdc_last_ac = NULL;
+}
+
+gboolean jaw_accessibility_init(void) {
+ JAW_DEBUG("");
+ if (atk_bridge_adaptor_init(NULL, NULL) < 0) {
+ g_warning("%s: atk_bridge_adaptor_init failed", G_STRFUNC);
+ return FALSE;
+ }
+ g_debug("Atk Bridge Initialized");
+ return TRUE;
+}
+
+void jaw_accessibility_shutdown(void) {
+ JAW_DEBUG("");
+
+ pthread_mutex_lock(&jaw_vdc_dup_mutex);
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ jaw_vdc_clear_last_ac(jniEnv);
+ pthread_mutex_unlock(&jaw_vdc_dup_mutex);
+
+ atk_bridge_adaptor_cleanup();
+}
+
+static gpointer jni_loop_callback(void *data) {
+ JAW_DEBUG("%p", data);
+ if (!g_main_loop_is_running((GMainLoop *)data))
+ g_main_loop_run((GMainLoop *)data);
+ else {
+ g_debug("Running JNI already");
+ }
+ return 0;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_initNativeLibrary(void) {
+ JAW_DEBUG("");
+ const gchar *debug_env = g_getenv("JAW_DEBUG");
+ if (debug_env != NULL) {
+ int val_debug = atoi(debug_env);
+ if (val_debug > 4)
+ jaw_debug = 4;
+ else
+ jaw_debug = val_debug;
+ }
+ if (jaw_debug != 0) {
+ jaw_log_file = fopen(JAW_LOG_FILE, "w+");
+ if (!jaw_log_file) {
+ perror("Error opening log file " JAW_LOG_FILE
+ ", trying " JAW_LOG_FILE2);
+ jaw_log_file = fopen(JAW_LOG_FILE2, "w+");
+ }
+
+ if (!jaw_log_file) {
+ perror("Error opening log file " JAW_LOG_FILE2);
+ return JNI_FALSE;
+ }
+ jaw_start_time = time(NULL);
+ }
+
+ // Java app with GTK Look And Feel will load gail
+ // Set NO_GAIL to "1" to prevent gail from executing
+
+ g_setenv("NO_GAIL", "1", TRUE);
+
+ // Disable ATK Bridge temporarily to aoid the loading
+ // of ATK Bridge by GTK look and feel
+ g_setenv("NO_AT_BRIDGE", "1", TRUE);
+
+ g_type_class_unref(g_type_class_ref(JAW_TYPE_UTIL));
+ // Force to invoke base initialization function of each ATK interfaces
+ g_type_class_unref(g_type_class_ref(ATK_TYPE_NO_OP_OBJECT));
+
+ return JNI_TRUE;
+}
+
+static guint jni_main_idle_add(GSourceFunc function, gpointer data) {
+ JAW_DEBUG("%p, %p", function, data);
+ GSource *source;
+ guint id;
+
+ source = g_idle_source_new();
+ g_source_set_callback(source, function, data, NULL);
+ id = g_source_attach(source, jni_main_context);
+ g_source_unref(source);
+
+ return id;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_loadAtkBridge(void) {
+ JAW_DEBUG("");
+ // Enable ATK Bridge so we can load it now
+ g_unsetenv("NO_AT_BRIDGE");
+
+ GThread *thread;
+ GError *err;
+ const char *message;
+ message = "JavaAtkWrapper-MainLoop";
+ err = NULL;
+
+ jaw_initialized = jaw_accessibility_init();
+ g_debug("%s: Jaw Initialization STATUS = %d", G_STRFUNC, jaw_initialized);
+ if (!jaw_initialized) {
+ g_warning("%s: loadAtkBridge: jaw_initialized == NULL", G_STRFUNC);
+ return JNI_FALSE;
+ }
+
+#if ATSPI_CHECK_VERSION(2, 33, 1)
+ jni_main_context = g_main_context_new();
+ jni_main_loop =
+ g_main_loop_new(jni_main_context, FALSE); /*main loop NOT running*/
+ atk_bridge_set_event_context(jni_main_context);
+#else
+ jni_main_loop = g_main_loop_new(NULL, FALSE);
+#endif
+
+ thread = g_thread_try_new(message, jni_loop_callback, (void *)jni_main_loop,
+ &err);
+ if (thread == NULL) {
+ g_warning("%s: g_thread_try_new failed: %s", G_STRFUNC, err->message);
+ g_main_loop_unref(jni_main_loop);
+#if ATSPI_CHECK_VERSION(2, 33, 1)
+ atk_bridge_set_event_context(NULL); // set default context
+ g_main_context_unref(jni_main_context);
+#endif
+ g_error_free(err);
+ jaw_accessibility_shutdown();
+ return JNI_FALSE;
+ } else {
+ /* We won't join it */
+ g_thread_unref(thread);
+ }
+ return JNI_TRUE;
+}
+
+typedef struct _CallbackPara {
+ jobject global_ac;
+ JawImpl *jaw_impl;
+ JawImpl *child_impl;
+ gboolean is_toplevel;
+ gint signal_id;
+ jobjectArray args;
+ AtkStateType atk_state;
+ gboolean state_value;
+} CallbackPara;
+
+typedef struct _CallbackParaEvent {
+ jobject global_event;
+} CallbackParaEvent;
+
+JNIEXPORT jlong JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_createNativeResources(JNIEnv *jniEnv,
+ jclass jClass,
+ jobject ac) {
+ JawImpl *jaw_impl = jaw_impl_create_instance(jniEnv, ac);
+ JAW_DEBUG("%p", jaw_impl);
+ if (jaw_impl == NULL) {
+ g_warning("%s: jaw_impl_create_instance failed", G_STRFUNC);
+ return -1;
+ }
+ return (jlong)jaw_impl;
+}
+
+JNIEXPORT void JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_releaseNativeResources(
+ JNIEnv *jniEnv, jclass jClass, jlong reference) {
+ JawImpl *jaw_impl = (JawImpl *)reference;
+ JAW_DEBUG("%p", jaw_impl);
+ if (jaw_impl == NULL) {
+ g_warning("%s: jaw_impl is NULL", G_STRFUNC);
+ return;
+ }
+ g_object_unref(G_OBJECT(jaw_impl));
+}
+
+static CallbackPara *alloc_callback_para(JNIEnv *jniEnv, jobject ac) {
+ JAW_DEBUG("%p, %p", jniEnv, ac);
+ if (ac == NULL) {
+ g_warning("%s: ac is NULL", G_STRFUNC);
+ return NULL;
+ }
+ JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, ac);
+ if (jaw_impl == NULL) {
+ g_warning("%s: jaw_impl_find_instance failed", G_STRFUNC);
+ return NULL;
+ }
+ g_object_ref(G_OBJECT(jaw_impl));
+ CallbackPara *para = g_new(CallbackPara, 1);
+ para->global_ac = ac;
+ para->jaw_impl = jaw_impl;
+ para->child_impl = NULL;
+ para->args = NULL;
+
+ return para;
+}
+
+static CallbackParaEvent *alloc_callback_para_event(JNIEnv *jniEnv,
+ jobject event) {
+ JAW_DEBUG("%p, %p", jniEnv, event);
+ if (event == NULL) {
+ g_warning("%s: event is NULL", G_STRFUNC);
+ return NULL;
+ }
+ CallbackParaEvent *para = g_new(CallbackParaEvent, 1);
+ para->global_event = event;
+ return para;
+}
+
+static void free_callback_para(CallbackPara *para) {
+ JAW_DEBUG("%p", para);
+
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jaw_util_get_jni_env returned NULL", G_STRFUNC);
+ }
+
+ if (jniEnv != NULL) {
+ if (para->global_ac != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, para->global_ac);
+ } else {
+ g_debug("%s: para->global_ac == NULL", G_STRFUNC);
+ }
+ }
+
+ if (para->jaw_impl != NULL) {
+ g_object_unref(G_OBJECT(para->jaw_impl));
+ } else {
+ g_debug("%s: para->jaw_impl == NULL", G_STRFUNC);
+ }
+
+ if (para->child_impl != NULL) {
+ g_object_unref(G_OBJECT(para->child_impl));
+ }
+
+ if (jniEnv != NULL) {
+ if (para->args != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, para->args);
+ } else {
+ g_debug("%s: para->args == NULL", G_STRFUNC);
+ }
+ }
+
+ g_free(para);
+}
+
+static void free_callback_para_event(CallbackParaEvent *para) {
+ JAW_DEBUG("%p", para);
+
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jaw_util_get_jni_env returned NULL", G_STRFUNC);
+ }
+
+ if (jniEnv != NULL) {
+ if (para->global_event != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, para->global_event);
+ } else {
+ g_debug("para->global_event == NULL");
+ }
+ }
+
+ g_free(para);
+}
+
+/* List of callback params to be freed */
+static GSList *callback_para_frees;
+static GMutex callback_para_frees_mutex;
+static GSList *callback_para_event_frees;
+static GMutex callback_para_event_frees_mutex;
+
+/* Add a note that this callback param should be freed from the application */
+static void queue_free_callback_para(CallbackPara *para) {
+ JAW_DEBUG("%p", para);
+ g_mutex_lock(&callback_para_frees_mutex);
+ callback_para_frees = g_slist_prepend(callback_para_frees, para);
+ g_mutex_unlock(&callback_para_frees_mutex);
+}
+
+static void queue_free_callback_para_event(CallbackParaEvent *para) {
+ JAW_DEBUG("%p", para);
+ g_mutex_lock(&callback_para_event_frees_mutex);
+ callback_para_event_frees =
+ g_slist_prepend(callback_para_event_frees, para);
+ g_mutex_unlock(&callback_para_event_frees_mutex);
+}
+
+/* Process the unreference requests */
+static void callback_para_process_frees(void) {
+ JAW_DEBUG("");
+ GSList *list, *cur, *next;
+
+ g_mutex_lock(&callback_para_frees_mutex);
+ list = callback_para_frees;
+ callback_para_frees = NULL;
+ g_mutex_unlock(&callback_para_frees_mutex);
+
+ for (cur = list; cur != NULL; cur = next) {
+ free_callback_para(cur->data);
+ next = g_slist_next(cur);
+ g_slist_free_1(cur);
+ }
+}
+
+static void callback_para_event_process_frees(void) {
+ JAW_DEBUG("");
+ GSList *list, *cur, *next;
+
+ g_mutex_lock(&callback_para_event_frees_mutex);
+ list = callback_para_event_frees;
+ callback_para_event_frees = NULL;
+ g_mutex_unlock(&callback_para_event_frees_mutex);
+
+ for (cur = list; cur != NULL; cur = next) {
+ free_callback_para_event(cur->data);
+ next = g_slist_next(cur);
+ g_slist_free_1(cur);
+ }
+}
+
+static gboolean focus_notify_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+ if (!atk_obj) {
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ atk_object_notify_state_change(atk_obj, ATK_STATE_FOCUSED, 1);
+
+ queue_free_callback_para(para);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_focusNotify(
+ JNIEnv *jniEnv, jclass jClass, jobject jAccContext) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(
+ jniEnv,
+ jAccContext); // `free_callback_para` is responsible for deleting it
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ jni_main_idle_add(focus_notify_handler, para);
+}
+
+static gboolean window_open_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+ if (!atk_obj) {
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ gboolean is_toplevel = para->is_toplevel;
+
+ if (!g_strcmp0(atk_role_get_name(atk_object_get_role(atk_obj)),
+ "redundant object")) {
+ queue_free_callback_para(para);
+ return G_SOURCE_REMOVE;
+ }
+
+ if (atk_object_get_role(atk_obj) == ATK_ROLE_TOOL_TIP) {
+ queue_free_callback_para(para);
+ return G_SOURCE_REMOVE;
+ }
+
+ if (is_toplevel != FALSE) {
+ gint n = jaw_toplevel_add_window(JAW_TOPLEVEL(atk_get_root()), atk_obj);
+ if (n != -1) {
+ g_object_notify(G_OBJECT(atk_get_root()), "accessible-name");
+ g_signal_emit_by_name(ATK_OBJECT(atk_get_root()),
+ "children-changed::add", n, atk_obj);
+ g_signal_emit_by_name(atk_obj, "create");
+ }
+ }
+
+ queue_free_callback_para(para);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowOpen(
+ JNIEnv *jniEnv, jclass jClass, jobject jAccContext, jboolean jIsToplevel) {
+ JAW_DEBUG("%p, %p, %p, %d", jniEnv, jClass, jAccContext, jIsToplevel);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(
+ jniEnv,
+ jAccContext); // `free_callback_para` is responsible for deleting it
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ para->is_toplevel = jIsToplevel;
+ jni_main_idle_add(window_open_handler, para);
+}
+
+static gboolean window_close_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+ if (!atk_obj) {
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ gboolean is_toplevel = para->is_toplevel;
+
+ if (!g_strcmp0(atk_role_get_name(atk_object_get_role(atk_obj)),
+ "redundant object")) {
+ queue_free_callback_para(para);
+ return G_SOURCE_REMOVE;
+ }
+
+ if (atk_object_get_role(atk_obj) == ATK_ROLE_TOOL_TIP) {
+ queue_free_callback_para(para);
+ return G_SOURCE_REMOVE;
+ }
+
+ if (is_toplevel != FALSE) {
+ gint n =
+ jaw_toplevel_remove_window(JAW_TOPLEVEL(atk_get_root()), atk_obj);
+ if (n != -1) {
+ g_object_notify(G_OBJECT(atk_get_root()), "accessible-name");
+ g_signal_emit_by_name(ATK_OBJECT(atk_get_root()),
+ "children-changed::remove", n, atk_obj);
+ g_signal_emit_by_name(atk_obj, "destroy");
+ }
+ }
+
+ queue_free_callback_para(para);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowClose(
+ JNIEnv *jniEnv, jclass jClass, jobject jAccContext, jboolean jIsToplevel) {
+ JAW_DEBUG("%p, %p, %p, %d", jniEnv, jClass, jAccContext, jIsToplevel);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(
+ jniEnv,
+ jAccContext); // `free_callback_para` is responsible for deleting it
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ para->is_toplevel = jIsToplevel;
+ jni_main_idle_add(window_close_handler, para);
+}
+
+static gboolean window_minimize_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+ if (!atk_obj) {
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ g_signal_emit_by_name(atk_obj, "minimize");
+
+ queue_free_callback_para(para);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowMinimize(
+ JNIEnv *jniEnv, jclass jClass, jobject jAccContext) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(
+ jniEnv,
+ jAccContext); // `free_callback_para` is responsible for deleting it
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ jni_main_idle_add(window_minimize_handler, para);
+}
+
+static gboolean window_maximize_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+
+ if (!atk_obj) {
+ g_warning("%s: atk_obj is NULL", G_STRFUNC);
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ g_signal_emit_by_name(atk_obj, "maximize");
+
+ queue_free_callback_para(para);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowMaximize(
+ JNIEnv *jniEnv, jclass jClass, jobject jAccContext) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(
+ jniEnv,
+ jAccContext); // `free_callback_para` is responsible for deleting it
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ jni_main_idle_add(window_maximize_handler, para);
+}
+
+static gboolean window_restore_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+ if (!atk_obj) {
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ g_signal_emit_by_name(atk_obj, "restore");
+
+ queue_free_callback_para(para);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowRestore(
+ JNIEnv *jniEnv, jclass jClass, jobject jAccContext) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(
+ jniEnv,
+ jAccContext); // `free_callback_para` is responsible for deleting it
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ jni_main_idle_add(window_restore_handler, para);
+}
+
+static gboolean window_activate_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+ if (!atk_obj) {
+ g_warning("%s: atk_obj is NULL", G_STRFUNC);
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ g_signal_emit_by_name(atk_obj, "activate");
+
+ queue_free_callback_para(para);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowActivate(
+ JNIEnv *jniEnv, jclass jClass, jobject jAccContext) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(
+ jniEnv,
+ jAccContext); // `free_callback_para` is responsible for deleting it
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ jni_main_idle_add(window_activate_handler, para);
+}
+
+static gboolean window_deactivate_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+ if (!atk_obj) {
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ g_signal_emit_by_name(atk_obj, "deactivate");
+
+ queue_free_callback_para(para);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowDeactivate(
+ JNIEnv *jniEnv, jclass jClass, jobject jAccContext) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(
+ jniEnv,
+ jAccContext); // `free_callback_para` is responsible for deleting it
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ jni_main_idle_add(window_deactivate_handler, para);
+}
+
+static gboolean window_state_change_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+ if (!atk_obj) {
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ g_signal_emit_by_name(atk_obj, "state-change", 0, 0);
+
+ queue_free_callback_para(para);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_windowStateChange(JNIEnv *jniEnv,
+ jclass jClass,
+ jobject jAccContext) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(
+ jniEnv,
+ jAccContext); // `free_callback_para` is responsible for deleting it
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ jni_main_idle_add(window_state_change_handler, para);
+}
+
+static gint get_int_value(JNIEnv *jniEnv, jobject o) {
+ JAW_DEBUG("%p, %p", jniEnv, o);
+
+ if (!atk_wrapper_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize cache", G_STRFUNC);
+ return 0;
+ }
+
+ return (gint)(*jniEnv)->CallIntMethod(jniEnv, o,
+ cachedWrapperIntValueMethod);
+}
+
+static gchar *get_string_value(JNIEnv *jniEnv, jobject o) {
+ JAW_DEBUG("%p, %p", jniEnv, o);
+ if (o == NULL) {
+ g_warning("%s: o is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("Failed to create a new local reference frame");
+ return NULL;
+ }
+
+ jclass objClass = (*jniEnv)->GetObjectClass(jniEnv, o);
+ if (!objClass) {
+ g_warning("%s: GetObjectClass failed", G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ jmethodID jmid = (*jniEnv)->GetMethodID(jniEnv, objClass, "toString",
+ "()Ljava/lang/String;");
+ if (!jmid) {
+ g_warning("%s: GetMethodID for toString failed", G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ jstring jstr = (jstring)(*jniEnv)->CallObjectMethod(jniEnv, o, jmid);
+ if (!jstr) {
+ g_warning("%s: CallObjectMethod failed", G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ const char *nativeStr = (*jniEnv)->GetStringUTFChars(jniEnv, jstr, NULL);
+ if (!nativeStr) {
+ g_warning("%s: nativeStr is NULL", G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ gchar *result = g_strdup(nativeStr);
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, nativeStr);
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return result;
+}
+
+static gboolean signal_emit_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (!jniEnv) {
+ g_warning("%s: jaw_util_get_jni_env failed", G_STRFUNC);
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("Failed to create a new local reference frame");
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ jobjectArray args = para->args;
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+
+ if (para->signal_id ==
+ org_GNOME_Accessibility_AtkSignal_OBJECT_VISIBLE_DATA_CHANGED) {
+ pthread_mutex_lock(&jaw_vdc_dup_mutex);
+ if ((*jniEnv)->IsSameObject(jniEnv, jaw_vdc_last_ac, para->global_ac)) {
+ /* So we will be sending the visible data changed event. If any
+ * other comes, we will want to send it */
+ jaw_vdc_clear_last_ac(jniEnv);
+ }
+ pthread_mutex_unlock(&jaw_vdc_dup_mutex);
+ }
+
+ switch (para->signal_id) {
+ case org_GNOME_Accessibility_AtkSignal_TEXT_CARET_MOVED: {
+ jobject objectArrayElement =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0);
+ if (!objectArrayElement) {
+ break;
+ }
+ gint cursor_pos = get_int_value(jniEnv, objectArrayElement);
+
+ g_signal_emit_by_name(atk_obj, "text_caret_moved", cursor_pos);
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_INSERT: {
+ jobject objectArrayElement0 =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0);
+ if (!objectArrayElement0) {
+ break;
+ }
+ gint insert_position = get_int_value(jniEnv, objectArrayElement0);
+
+ jobject objectArrayElement1 =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, args, 1);
+ if (!objectArrayElement1) {
+ break;
+ }
+ gint insert_length = get_int_value(jniEnv, objectArrayElement1);
+
+ jobject objectArrayElement2 =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, args, 2);
+ if (!objectArrayElement2) {
+ break;
+ }
+ gchar *insert_text = get_string_value(jniEnv, objectArrayElement2);
+
+ g_signal_emit_by_name(atk_obj, "text_insert", insert_position,
+ insert_length, insert_text);
+
+ g_free(insert_text);
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_DELETE: {
+ jobject objectArrayElement0 =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0);
+ if (!objectArrayElement0) {
+ break;
+ }
+ gint delete_position = get_int_value(jniEnv, objectArrayElement0);
+
+ jobject objectArrayElement1 =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, args, 1);
+ if (!objectArrayElement1) {
+ break;
+ }
+ gint delete_length = get_int_value(jniEnv, objectArrayElement1);
+
+ jobject objectArrayElement2 =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, args, 2);
+ if (!objectArrayElement2) {
+ break;
+ }
+ gchar *delete_text = get_string_value(jniEnv, objectArrayElement2);
+
+ g_signal_emit_by_name(atk_obj, "text_remove", delete_position,
+ delete_length, delete_text);
+
+ g_free(delete_text);
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_ADD: {
+ jobject objectArrayElement0 =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0);
+ if (!objectArrayElement0) {
+ break;
+ }
+ gint child_index = get_int_value(jniEnv, objectArrayElement0);
+
+ g_signal_emit_by_name(atk_obj, "children_changed::add", child_index,
+ para->child_impl);
+
+ if (atk_obj != NULL) {
+ g_object_ref(G_OBJECT(atk_obj));
+ }
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_REMOVE: {
+ jobject objectArrayElement0 =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0);
+ if (!objectArrayElement0) {
+ break;
+ }
+ gint child_index = get_int_value(jniEnv, objectArrayElement0);
+
+ jobject child_ac = (*jniEnv)->GetObjectArrayElement(jniEnv, args, 1);
+ if (!child_ac) {
+ break;
+ }
+ JawImpl *child_impl = jaw_impl_find_instance(jniEnv, child_ac);
+ if (!child_impl) {
+ break;
+ }
+
+ g_signal_emit_by_name(atk_obj, "children_changed::remove", child_index,
+ child_impl);
+
+ if (atk_obj != NULL) {
+ g_object_unref(G_OBJECT(atk_obj));
+ }
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_ACTIVE_DESCENDANT_CHANGED: {
+ g_signal_emit_by_name(atk_obj, "active_descendant_changed",
+ para->child_impl);
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_SELECTION_CHANGED: {
+ g_signal_emit_by_name(atk_obj, "selection_changed");
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_VISIBLE_DATA_CHANGED: {
+ g_signal_emit_by_name(atk_obj, "visible_data_changed");
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_ACTIONS: {
+ jobject objectArrayElement0 =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0);
+ if (!objectArrayElement0) {
+ break;
+ }
+ gint oldValue = get_int_value(jniEnv, objectArrayElement0);
+ jobject objectArrayElement1 =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, args, 1);
+ if (!objectArrayElement1) {
+ break;
+ }
+ gint newValue = get_int_value(jniEnv, objectArrayElement1);
+
+ AtkPropertyValues values = {NULL};
+
+ // GValues must be initialized
+ g_assert(!G_VALUE_HOLDS_INT(&values.old_value));
+ g_value_init(&values.old_value, G_TYPE_INT);
+ g_assert(G_VALUE_HOLDS_INT(&values.old_value));
+ g_value_set_int(&values.old_value, oldValue);
+ if (jaw_debug)
+ printf("%d\n", g_value_get_int(&values.old_value));
+
+ g_assert(!G_VALUE_HOLDS_INT(&values.new_value));
+ g_value_init(&values.new_value, G_TYPE_INT);
+ g_assert(G_VALUE_HOLDS_INT(&values.new_value));
+ g_value_set_int(&values.new_value, newValue);
+ if (jaw_debug)
+ printf("%d\n", g_value_get_int(&values.new_value));
+
+ values.property_name = "accessible-actions";
+
+ g_signal_emit_by_name(atk_obj, "property_change::accessible-actions",
+ &values);
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_VALUE: {
+ g_object_notify(G_OBJECT(atk_obj), "accessible-value");
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_DESCRIPTION: {
+ g_object_notify(G_OBJECT(atk_obj), "accessible-description");
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_NAME: {
+ g_object_notify(G_OBJECT(atk_obj), "accessible-name");
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_HYPERTEXT_OFFSET: {
+ g_signal_emit_by_name(
+ atk_obj, "property_change::accessible-hypertext-offset", NULL);
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_CAPTION: {
+ g_signal_emit_by_name(
+ atk_obj, "property_change::accessible-table-caption", NULL);
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_SUMMARY: {
+ g_signal_emit_by_name(
+ atk_obj, "property_change::accessible-table-summary", NULL);
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_HEADER: {
+ g_signal_emit_by_name(
+ atk_obj, "property_change::accessible-table-column-header", NULL);
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_DESCRIPTION: {
+ g_signal_emit_by_name(
+ atk_obj, "property_change::accessible-table-column-description",
+ NULL);
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_HEADER: {
+ g_signal_emit_by_name(
+ atk_obj, "property_change::accessible-table-row-header", NULL);
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_DESCRIPTION: {
+ g_signal_emit_by_name(
+ atk_obj, "property_change::accessible-table-row-description", NULL);
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_TABLE_MODEL_CHANGED: {
+ g_signal_emit_by_name(atk_obj, "model_changed");
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED: {
+ JawObject *jaw_obj = JAW_OBJECT(atk_obj);
+
+ jobject objectArrayElement0 =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0);
+ if (!objectArrayElement0) {
+ break;
+ }
+ gint newValue = get_int_value(jniEnv, objectArrayElement0);
+
+ gint prevCount = GPOINTER_TO_INT(
+ g_hash_table_lookup(jaw_obj->storedData, "Previous_Count"));
+ gint curCount = atk_text_get_character_count(ATK_TEXT(jaw_obj));
+
+ g_hash_table_insert(jaw_obj->storedData, (gpointer) "Previous_Count",
+ GINT_TO_POINTER(curCount));
+
+ /*
+ * The "text_changed" signal was deprecated, but only for performance
+ * reasons:
+ * https://mail.gnome.org/archives/gnome-accessibility-devel/2010-December/msg00007.html.
+ *
+ * Since there is no information about the string in this case, we
+ * cannot use "AtkObject::text-insert" or "AtkObject::text-remove", so
+ * we continue using the "text_changed" signal.
+ */
+ if (curCount > prevCount) {
+ g_signal_emit_by_name(atk_obj, "text_changed::insert", newValue,
+ curCount - prevCount);
+ } else if (curCount < prevCount) {
+ g_signal_emit_by_name(atk_obj, "text_changed::delete", newValue,
+ prevCount - curCount);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ queue_free_callback_para(para);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_emitSignal(
+ JNIEnv *jniEnv, jclass jClass, jobject jAccContext, jint id,
+ jobjectArray args) {
+ JAW_DEBUG("%p, %p, %p, %d, %p", jniEnv, jClass, jAccContext, id, args);
+
+ pthread_mutex_lock(&jaw_vdc_dup_mutex);
+ if (id != org_GNOME_Accessibility_AtkSignal_OBJECT_VISIBLE_DATA_CHANGED) {
+ /* Something may have happened since the last visible data changed
+ * event, so we want to sent it again */
+ jaw_vdc_clear_last_ac(jniEnv);
+ } else {
+ if ((*jniEnv)->IsSameObject(jniEnv, jaw_vdc_last_ac, jAccContext)) {
+ /* We have already queued to send one and nothing happened in
+ * between, this one is really useless */
+ pthread_mutex_unlock(&jaw_vdc_dup_mutex);
+ return;
+ }
+
+ jaw_vdc_clear_last_ac(jniEnv);
+ jaw_vdc_last_ac = (*jniEnv)->NewGlobalRef(jniEnv, jAccContext);
+ }
+ pthread_mutex_unlock(&jaw_vdc_dup_mutex);
+
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+
+ jobject global_ac = (*jniEnv)->NewGlobalRef(
+ jniEnv,
+ jAccContext); // `free_callback_para` is responsible for deleting it
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ jobjectArray global_args =
+ (jobjectArray)(*jniEnv)->NewGlobalRef(jniEnv, args);
+ para->signal_id = (gint)id;
+ para->args = global_args;
+
+ switch (para->signal_id) {
+ case org_GNOME_Accessibility_AtkSignal_TEXT_CARET_MOVED:
+ case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_INSERT:
+ case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_DELETE:
+ case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED_REPLACE:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_REMOVE:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_SELECTION_CHANGED:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_VISIBLE_DATA_CHANGED:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_ACTIONS:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_VALUE:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_DESCRIPTION:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_NAME:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_HYPERTEXT_OFFSET:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_CAPTION:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_SUMMARY:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_HEADER:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_COLUMN_DESCRIPTION:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_HEADER:
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_PROPERTY_CHANGE_ACCESSIBLE_TABLE_ROW_DESCRIPTION:
+ case org_GNOME_Accessibility_AtkSignal_TABLE_MODEL_CHANGED:
+ case org_GNOME_Accessibility_AtkSignal_TEXT_PROPERTY_CHANGED:
+ default:
+ break;
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_CHILDREN_CHANGED_ADD: {
+ jobject child_ac = (*jniEnv)->GetObjectArrayElement(jniEnv, args, 1);
+ if (child_ac == NULL) {
+ g_warning("%s: GetObjectArrayElement failed for child_ac",
+ G_STRFUNC);
+ queue_free_callback_para(para);
+ return;
+ }
+ JawImpl *child_impl = jaw_impl_find_instance(jniEnv, child_ac);
+ (*jniEnv)->DeleteLocalRef(jniEnv, child_ac);
+ if (child_impl == NULL) {
+ g_warning("%s: child_impl == NULL, return NULL", G_STRFUNC);
+ queue_free_callback_para(para);
+ return;
+ }
+ g_object_ref(G_OBJECT(child_impl));
+ para->child_impl = child_impl;
+ break;
+ }
+ case org_GNOME_Accessibility_AtkSignal_OBJECT_ACTIVE_DESCENDANT_CHANGED: {
+ jobject child_ac = (*jniEnv)->GetObjectArrayElement(jniEnv, args, 0);
+ if (child_ac == NULL) {
+ g_warning("%s: child_ac == NULL, return NULL", G_STRFUNC);
+ queue_free_callback_para(para);
+ return;
+ }
+ JawImpl *child_impl = jaw_impl_find_instance(jniEnv, child_ac);
+ (*jniEnv)->DeleteLocalRef(jniEnv, child_ac);
+ if (child_impl == NULL) {
+ g_warning("%s: child_impl == NULL, return NULL", G_STRFUNC);
+ queue_free_callback_para(para);
+ return;
+ }
+ g_object_ref(G_OBJECT(child_impl));
+ para->child_impl = child_impl;
+ break;
+ }
+ }
+
+ jni_main_idle_add(signal_emit_handler,
+ para); // calls `queue_free_callback_para`
+}
+
+static gboolean object_state_change_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+ if (!atk_obj) {
+ g_warning("%s: atk_obj is NULL", G_STRFUNC);
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ atk_object_notify_state_change(atk_obj, para->atk_state, para->state_value);
+
+ queue_free_callback_para(para);
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_objectStateChange(JNIEnv *jniEnv,
+ jclass jClass,
+ jobject jAccContext,
+ jobject state,
+ jboolean value) {
+ JAW_DEBUG("%p, %p, %p, %p, %d", jniEnv, jClass, jAccContext, state, value);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(jniEnv, jAccContext);
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ AtkStateType state_type =
+ jaw_util_get_atk_state_type_from_java_state(jniEnv, state);
+ para->atk_state = state_type;
+ para->state_value = value;
+ jni_main_idle_add(object_state_change_handler, para);
+}
+
+static gboolean component_added_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+ if (!atk_obj) {
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ if (atk_object_get_role(atk_obj) == ATK_ROLE_TOOL_TIP) {
+ atk_object_notify_state_change(atk_obj, ATK_STATE_SHOWING, 1);
+ }
+
+ queue_free_callback_para(para);
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_componentAdded(
+ JNIEnv *jniEnv, jclass jClass, jobject jAccContext) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(jniEnv, jAccContext);
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ jni_main_idle_add(component_added_handler, para);
+}
+
+static gboolean component_removed_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+ if (!atk_obj) {
+ g_warning("%s: atk_obj is NULL", G_STRFUNC);
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ if (atk_object_get_role(atk_obj) == ATK_ROLE_TOOL_TIP)
+ atk_object_notify_state_change(atk_obj, ATK_STATE_SHOWING, FALSE);
+ queue_free_callback_para(para);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_componentRemoved(
+ JNIEnv *jniEnv, jclass jClass, jobject jAccContext) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(jniEnv, jAccContext);
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ jni_main_idle_add(component_removed_handler, para);
+}
+
+/**
+ * Signal is emitted when the position or size of the component changes.
+ */
+static gboolean bounds_changed_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackPara *para = (CallbackPara *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(para->jaw_impl);
+ if (!atk_obj) {
+ queue_free_callback_para(para);
+ return FALSE;
+ }
+
+ AtkRectangle rect;
+ rect.x = -1;
+ rect.y = -1;
+ rect.width = -1;
+ rect.height = -1;
+ g_signal_emit_by_name(atk_obj, "bounds_changed", &rect);
+ queue_free_callback_para(para);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_boundsChanged(
+ JNIEnv *jniEnv, jclass jClass, jobject jAccContext) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAccContext);
+ if (!jAccContext) {
+ g_warning("%s: jAccContext is NULL", G_STRFUNC);
+ return;
+ }
+ jobject global_ac = (*jniEnv)->NewGlobalRef(jniEnv, jAccContext);
+ callback_para_process_frees();
+ CallbackPara *para = alloc_callback_para(jniEnv, global_ac);
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_ac);
+ return;
+ }
+ jni_main_idle_add(bounds_changed_handler, para);
+}
+
+static gboolean key_dispatch_handler(gpointer p) {
+ JAW_DEBUG("%p", p);
+ CallbackParaEvent *para = (CallbackParaEvent *)p;
+ if (para == NULL) {
+ g_warning("%s: para is NULL", G_STRFUNC);
+ return FALSE;
+ }
+ jobject jAtkKeyEvent = para->global_event;
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jaw_util_get_jni_env == NULL", G_STRFUNC);
+ queue_free_callback_para_event(para);
+ return G_SOURCE_REMOVE;
+ }
+
+ AtkKeyEventStruct *event = g_new0(AtkKeyEventStruct, 1);
+
+ if (!atk_wrapper_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize cache", G_STRFUNC);
+ g_free(event);
+ queue_free_callback_para_event(para);
+ return G_SOURCE_REMOVE;
+ }
+
+ jint type =
+ (*jniEnv)->GetIntField(jniEnv, jAtkKeyEvent, cachedWrapperTypeFieldID);
+ if (type == -1) {
+ g_warning("%s: Unknown key event type (-1) received; cleaning up and "
+ "removing source",
+ G_STRFUNC);
+ g_free(event);
+ queue_free_callback_para_event(para);
+ return G_SOURCE_REMOVE;
+ }
+
+ jint type_pressed = (*jniEnv)->GetStaticIntField(
+ jniEnv, cachedWrapperAtkKeyEventClass, cachedWrapperTypePressedFieldID);
+
+ jint type_released =
+ (*jniEnv)->GetStaticIntField(jniEnv, cachedWrapperAtkKeyEventClass,
+ cachedWrapperTypeReleasedFieldID);
+
+ if (type == type_pressed) {
+ event->type = ATK_KEY_EVENT_PRESS;
+ } else if (type == type_released) {
+ event->type = ATK_KEY_EVENT_RELEASE;
+ } else {
+ g_warning("%s: Unknown key event type (%d) received; cleaning up and "
+ "removing source",
+ G_STRFUNC, type);
+ g_free(event);
+ queue_free_callback_para_event(para);
+ return G_SOURCE_REMOVE;
+ }
+
+ // state: shift
+ jboolean jShiftKeyDown = (*jniEnv)->GetBooleanField(
+ jniEnv, jAtkKeyEvent, cachedWrapperShiftFieldID);
+ if (jShiftKeyDown != FALSE) {
+ event->state |= GDK_SHIFT_MASK;
+ }
+
+ // state: ctrl
+ jboolean jCtrlKeyDown = (*jniEnv)->GetBooleanField(
+ jniEnv, jAtkKeyEvent, cachedWrapperCtrlFieldID);
+ if (jCtrlKeyDown != FALSE) {
+ event->state |= GDK_CONTROL_MASK;
+ }
+
+ // state: alt
+ jboolean jAltKeyDown = (*jniEnv)->GetBooleanField(jniEnv, jAtkKeyEvent,
+ cachedWrapperAltFieldID);
+ if (jAltKeyDown != FALSE) {
+ event->state |= GDK_MOD1_MASK;
+ }
+
+ // state: meta
+ jboolean jMetaKeyDown = (*jniEnv)->GetBooleanField(
+ jniEnv, jAtkKeyEvent, cachedWrapperMetaFieldID);
+ if (jMetaKeyDown != FALSE) {
+ event->state |= GDK_META_MASK;
+ }
+
+ // state: alt gr
+ jboolean jAltGrKeyDown = (*jniEnv)->GetBooleanField(
+ jniEnv, jAtkKeyEvent, cachedWrapperAltGrFieldID);
+ if (jAltGrKeyDown != FALSE) {
+ event->state |= GDK_MOD5_MASK;
+ }
+
+ // keyval
+ event->keyval = (*jniEnv)->GetIntField(jniEnv, jAtkKeyEvent,
+ cachedWrapperKeyvalFieldID);
+
+ // string
+ jstring jstr = (jstring)(*jniEnv)->GetObjectField(
+ jniEnv, jAtkKeyEvent, cachedWrapperStringFieldID);
+ if (jstr != NULL) {
+ event->length = (gint)(*jniEnv)->GetStringLength(jniEnv, jstr);
+
+ const gchar *tmp_string = (*jniEnv)->GetStringUTFChars(jniEnv, jstr, 0);
+ if (tmp_string != NULL) {
+ event->string = g_strdup(tmp_string);
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, tmp_string);
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jstr);
+ jstr = NULL;
+ }
+
+ // keycode
+ event->keycode = (gint)(*jniEnv)->GetIntField(jniEnv, jAtkKeyEvent,
+ cachedWrapperKeycodeFieldID);
+
+ // timestamp
+ event->timestamp = (gint64)(*jniEnv)->GetIntField(
+ jniEnv, jAtkKeyEvent, cachedWrapperTimestampFieldID);
+
+ jaw_util_dispatch_key_event(event);
+
+ // clean up
+ g_free(event->string);
+ g_free(event);
+ queue_free_callback_para_event(para);
+
+ return G_SOURCE_REMOVE;
+}
+
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_dispatchKeyEvent(
+ JNIEnv *jniEnv, jclass jClass, jobject jAtkKeyEvent) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jClass, jAtkKeyEvent);
+ jobject global_key_event = (*jniEnv)->NewGlobalRef(jniEnv, jAtkKeyEvent);
+ callback_para_event_process_frees();
+ CallbackParaEvent *para =
+ alloc_callback_para_event(jniEnv, global_key_event);
+ if (para == NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, global_key_event);
+ return;
+ }
+ jni_main_idle_add(key_dispatch_handler, para);
+}
+
+static gboolean atk_wrapper_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&wrapper_cache_mutex);
+
+ if (wrapper_cache_initialized) {
+ g_mutex_unlock(&wrapper_cache_mutex);
+ return TRUE;
+ }
+
+ jclass localIntegerClass =
+ (*jniEnv)->FindClass(jniEnv, "java/lang/Integer");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localIntegerClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find Integer class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedWrapperIntegerClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localIntegerClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localIntegerClass);
+
+ if (cachedWrapperIntegerClass == NULL) {
+ g_warning("%s: Failed to create global reference for Integer class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedWrapperIntValueMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedWrapperIntegerClass, "intValue", "()I");
+
+ jclass localAtkKeyEventClass =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkKeyEvent");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localAtkKeyEventClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkKeyEvent class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedWrapperAtkKeyEventClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localAtkKeyEventClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localAtkKeyEventClass);
+
+ if (cachedWrapperAtkKeyEventClass == NULL) {
+ g_warning("%s: Failed to create global reference for AtkKeyEvent class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedWrapperTypeFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedWrapperAtkKeyEventClass, "type", "I");
+ cachedWrapperTypePressedFieldID = (*jniEnv)->GetStaticFieldID(
+ jniEnv, cachedWrapperAtkKeyEventClass, "ATK_KEY_EVENT_PRESSED", "I");
+ cachedWrapperTypeReleasedFieldID = (*jniEnv)->GetStaticFieldID(
+ jniEnv, cachedWrapperAtkKeyEventClass, "ATK_KEY_EVENT_RELEASED", "I");
+ cachedWrapperShiftFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedWrapperAtkKeyEventClass, "isShiftKeyDown", "Z");
+ cachedWrapperCtrlFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedWrapperAtkKeyEventClass, "isCtrlKeyDown", "Z");
+ cachedWrapperAltFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedWrapperAtkKeyEventClass, "isAltKeyDown", "Z");
+ cachedWrapperMetaFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedWrapperAtkKeyEventClass, "isMetaKeyDown", "Z");
+ cachedWrapperAltGrFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedWrapperAtkKeyEventClass, "isAltGrKeyDown", "Z");
+ cachedWrapperKeyvalFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedWrapperAtkKeyEventClass, "keyval", "I");
+ cachedWrapperStringFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedWrapperAtkKeyEventClass, "string", "Ljava/lang/String;");
+ cachedWrapperKeycodeFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedWrapperAtkKeyEventClass, "keycode", "I");
+ cachedWrapperTimestampFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedWrapperAtkKeyEventClass, "timestamp", "J");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedWrapperIntValueMethod == NULL ||
+ cachedWrapperTypeFieldID == NULL ||
+ cachedWrapperTypePressedFieldID == NULL ||
+ cachedWrapperTypeReleasedFieldID == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to cache one or more method/field IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ wrapper_cache_initialized = TRUE;
+ g_mutex_unlock(&wrapper_cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedWrapperIntegerClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedWrapperIntegerClass);
+ cachedWrapperIntegerClass = NULL;
+ }
+ cachedWrapperIntValueMethod = NULL;
+
+ if (cachedWrapperAtkKeyEventClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedWrapperAtkKeyEventClass);
+ cachedWrapperAtkKeyEventClass = NULL;
+ }
+ cachedWrapperTypeFieldID = NULL;
+ cachedWrapperTypePressedFieldID = NULL;
+ cachedWrapperTypeReleasedFieldID = NULL;
+ cachedWrapperShiftFieldID = NULL;
+ cachedWrapperCtrlFieldID = NULL;
+ cachedWrapperAltFieldID = NULL;
+ cachedWrapperMetaFieldID = NULL;
+ cachedWrapperAltGrFieldID = NULL;
+ cachedWrapperKeyvalFieldID = NULL;
+ cachedWrapperStringFieldID = NULL;
+ cachedWrapperKeycodeFieldID = NULL;
+ cachedWrapperTimestampFieldID = NULL;
+
+ g_mutex_unlock(&wrapper_cache_mutex);
+ return FALSE;
+}
+
+void atk_wrapper_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&wrapper_cache_mutex);
+
+ if (cachedWrapperIntegerClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedWrapperIntegerClass);
+ cachedWrapperIntegerClass = NULL;
+ }
+ cachedWrapperIntValueMethod = NULL;
+
+ if (cachedWrapperAtkKeyEventClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedWrapperAtkKeyEventClass);
+ cachedWrapperAtkKeyEventClass = NULL;
+ }
+ cachedWrapperTypeFieldID = NULL;
+ cachedWrapperTypePressedFieldID = NULL;
+ cachedWrapperTypeReleasedFieldID = NULL;
+ cachedWrapperShiftFieldID = NULL;
+ cachedWrapperCtrlFieldID = NULL;
+ cachedWrapperAltFieldID = NULL;
+ cachedWrapperMetaFieldID = NULL;
+ cachedWrapperAltGrFieldID = NULL;
+ cachedWrapperKeyvalFieldID = NULL;
+ cachedWrapperStringFieldID = NULL;
+ cachedWrapperKeycodeFieldID = NULL;
+ cachedWrapperTimestampFieldID = NULL;
+ wrapper_cache_initialized = FALSE;
+
+ g_mutex_unlock(&wrapper_cache_mutex);
+}
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/AtkWrapper.h b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkWrapper.h
new file mode 100644
index 000000000000..f517e6cc7a38
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/AtkWrapper.h
@@ -0,0 +1,178 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_GNOME_Accessibility_AtkWrapper */
+
+#ifndef _Included_org_GNOME_Accessibility_AtkWrapper
+#define _Included_org_GNOME_Accessibility_AtkWrapper
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: initNativeLibrary
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_initNativeLibrary(void);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: loadAtkBridge
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_loadAtkBridge(void);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: createNativeResources
+ * Signature: (Ljavax/accessibility/AccessibleContext;)V
+ */
+JNIEXPORT jlong JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_createNativeResources(JNIEnv *, jclass,
+ jobject);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: releaseNativeResources
+ * Signature: (Ljavax/accessibility/AccessibleContext;)V
+ */
+JNIEXPORT void JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_releaseNativeResources(JNIEnv *, jclass,
+ jlong);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: focusNotify
+ * Signature: (Ljavax/accessibility/AccessibleContext;)V
+ */
+JNIEXPORT void JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_focusNotify(JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: windowOpen
+ * Signature: (Ljavax/accessibility/AccessibleContext;Z)V
+ */
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowOpen(
+ JNIEnv *, jclass, jobject, jboolean);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: windowClose
+ * Signature: (Ljavax/accessibility/AccessibleContext;Z)V
+ */
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowClose(
+ JNIEnv *, jclass, jobject, jboolean);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: windowMinimize
+ * Signature: (Ljavax/accessibility/AccessibleContext;)V
+ */
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowMinimize(
+ JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: windowMaximize
+ * Signature: (Ljavax/accessibility/AccessibleContext;)V
+ */
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowMaximize(
+ JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: windowRestore
+ * Signature: (Ljavax/accessibility/AccessibleContext;)V
+ */
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowRestore(
+ JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: windowActivate
+ * Signature: (Ljavax/accessibility/AccessibleContext;)V
+ */
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowActivate(
+ JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: windowDeactivate
+ * Signature: (Ljavax/accessibility/AccessibleContext;)V
+ */
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_windowDeactivate(
+ JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: windowStateChange
+ * Signature: (Ljavax/accessibility/AccessibleContext;)V
+ */
+JNIEXPORT void JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_windowStateChange(JNIEnv *, jclass,
+ jobject);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: emitSignal
+ * Signature: (Ljavax/accessibility/AccessibleContext;I[Ljava/lang/Object;)V
+ */
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_emitSignal(
+ JNIEnv *, jclass, jobject, jint, jobjectArray);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: objectStateChange
+ * Signature: (Ljavax/accessibility/AccessibleContext;Ljava/lang/Object;Z)V
+ */
+JNIEXPORT void JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_objectStateChange(JNIEnv *, jclass,
+ jobject, jobject,
+ jboolean);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: componentAdded
+ * Signature: (Ljavax/accessibility/AccessibleContext;)V
+ */
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_componentAdded(
+ JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: componentRemoved
+ * Signature: (Ljavax/accessibility/AccessibleContext;)V
+ */
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_componentRemoved(
+ JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: boundsChanged
+ * Signature: (Ljavax/accessibility/AccessibleContext;)V
+ */
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_boundsChanged(
+ JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: dispatchKeyEvent
+ * Signature: (Lorg/GNOME/Accessibility/AtkKeyEvent;)Z
+ */
+JNIEXPORT void JNICALL Java_org_GNOME_Accessibility_AtkWrapper_dispatchKeyEvent(
+ JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_GNOME_Accessibility_AtkWrapper
+ * Method: getInstance
+ * Signature: (Ljavax/accessibility/AccessibleContext;)J
+ */
+JNIEXPORT jlong JNICALL
+Java_org_GNOME_Accessibility_AtkWrapper_getInstance(JNIEnv *, jclass, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawaction.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawaction.c
new file mode 100644
index 000000000000..e4e5fe8eaf51
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawaction.c
@@ -0,0 +1,633 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawcache.h"
+#include "jawimpl.h"
+#include "jawutil.h"
+#include
+#include
+
+/**
+ * (From Atk documentation)
+ *
+ * AtkAction:
+ *
+ * The ATK interface provided by UI components
+ * which the user can activate/interact with.
+ *
+ * #AtkAction should be implemented by instances of #AtkObject classes
+ * with which the user can interact directly, i.e. buttons,
+ * checkboxes, scrollbars, e.g. components which are not "passive"
+ * providers of UI information.
+ *
+ * Exceptions: when the user interaction is already covered by another
+ * appropriate interface such as #AtkEditableText (insert/delete text,
+ * etc.) or #AtkValue (set value) then these actions should not be
+ * exposed by #AtkAction as well.
+ *
+ * Though most UI interactions on components should be invocable via
+ * keyboard as well as mouse, there will generally be a close mapping
+ * between "mouse actions" that are possible on a component and the
+ * AtkActions. Where mouse and keyboard actions are redundant in
+ * effect, #AtkAction should expose only one action rather than
+ * exposing redundant actions if possible. By convention we have been
+ * using "mouse centric" terminology for #AtkAction names.
+ *
+ */
+
+static gboolean jaw_action_do_action(AtkAction *action, gint i);
+static gint jaw_action_get_n_actions(AtkAction *action);
+static const gchar *jaw_action_get_description(AtkAction *action, gint i);
+static gboolean jaw_action_set_description(AtkAction *action, gint i,
+ const gchar *description);
+static const gchar *jaw_action_get_localized_name(AtkAction *action, gint i);
+
+static jclass cachedActionAtkActionClass = NULL;
+static jmethodID cachedActionCreateAtkActionMethod = NULL;
+static jmethodID cachedActionDoActionMethod = NULL;
+static jmethodID cachedActionGetNActionsMethod = NULL;
+static jmethodID cachedActionGetDescriptionMethod = NULL;
+static jmethodID cachedActionSetDescriptionMethod = NULL;
+static jmethodID cachedActionGetLocalizedNameMethod = NULL;
+
+static GMutex cache_mutex;
+static gboolean cache_initialized = FALSE;
+
+/**
+ * jaw_action_init_jni_cache:
+ * @jniEnv: JNI environment
+ *
+ * Initializes and caches JNI class and method references for performance.
+ * This avoids repeated expensive JNI lookups on every method call.
+ *
+ * Returns: %TRUE if initialization succeeded, %FALSE otherwise
+ **/
+static gboolean jaw_action_init_jni_cache(JNIEnv *jniEnv);
+
+typedef struct _ActionData {
+ jobject atk_action;
+ const gchar *localized_name;
+ jstring jstrLocalizedName;
+ const gchar *action_description;
+ jstring jstrActionDescription;
+ GMutex mutex;
+} ActionData;
+
+#define JAW_GET_ACTION(action, def_ret) \
+ JAW_GET_OBJ_IFACE(action, \
+ org_GNOME_Accessibility_AtkInterface_INTERFACE_ACTION, \
+ ActionData, atk_action, jniEnv, atk_action, def_ret)
+
+/**
+ * AtkActionIface:
+ * @do_action:
+ * @get_n_actions:
+ * @get_description:
+ * @get_name:
+ * @get_keybinding:
+ * @set_description:
+ * @get_localized_name:
+ **/
+void jaw_action_interface_init(AtkActionIface *iface, gpointer data) {
+ JAW_DEBUG("%p, %p", iface, data);
+
+ if (iface == NULL) {
+ g_warning("%s: Null argument iface passed to the function", G_STRFUNC);
+ return;
+ }
+
+ iface->do_action = jaw_action_do_action;
+ iface->get_n_actions = jaw_action_get_n_actions;
+ iface->get_description = jaw_action_get_description;
+ iface->get_name = jaw_action_get_description;
+ iface->get_keybinding =
+ NULL; // missing java support: There is no dependency between
+ // javax.accessibility.AccessibleAction and keybindings,
+ // so there is no way to return the correct keybinding based on
+ // AccessibleContext.
+ iface->set_description = jaw_action_set_description;
+ iface->get_localized_name = jaw_action_get_localized_name;
+}
+
+gpointer jaw_action_data_init(jobject ac) {
+ JAW_DEBUG("%p", ac);
+
+ if (ac == NULL) {
+ g_warning("%s: Null argument ac passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_action_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jatk_action = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedActionAtkActionClass, cachedActionCreateAtkActionMethod,
+ ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_action == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create AtkAction Java object via "
+ "create_atk_action()",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ ActionData *data = g_new0(ActionData, 1);
+ g_mutex_init(&data->mutex);
+ data->atk_action = (*jniEnv)->NewGlobalRef(jniEnv, jatk_action);
+ if (data->atk_action == NULL) {
+ g_warning("%s: Failed to create global ref for atk_action", G_STRFUNC);
+ g_mutex_clear(&data->mutex);
+ g_free(data);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data;
+}
+
+void jaw_action_data_finalize(gpointer p) {
+ JAW_DEBUG("%p", p);
+
+ if (p == NULL) {
+ g_debug("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ ActionData *data = (ActionData *)p;
+ if (data == NULL) {
+ g_warning("%s: data is null after cast", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&data->mutex);
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+
+ if (jniEnv == NULL) {
+ g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC);
+ } else {
+ if (data->jstrLocalizedName != NULL) {
+ if (data->localized_name != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(
+ jniEnv, data->jstrLocalizedName, data->localized_name);
+ data->localized_name = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrLocalizedName);
+ data->jstrLocalizedName = NULL;
+ }
+
+ if (data->jstrActionDescription != NULL) {
+ if (data->action_description != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv,
+ data->jstrActionDescription,
+ data->action_description);
+ data->action_description = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrActionDescription);
+ data->jstrActionDescription = NULL;
+ }
+
+ if (data->atk_action != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_action);
+ data->atk_action = NULL;
+ }
+ }
+
+ g_mutex_unlock(&data->mutex);
+ g_mutex_clear(&data->mutex);
+ g_free(data);
+}
+
+/**
+ * jaw_action_do_action:
+ * @action: a #GObject instance that implements AtkActionIface
+ * @i: the action index corresponding to the action to be performed
+ *
+ * Perform the specified action on the object.
+ *
+ * Returns: %TRUE if success, %FALSE otherwise
+ **/
+static gboolean jaw_action_do_action(AtkAction *action, gint i) {
+ JAW_DEBUG("%p, %d", action, i);
+
+ if (action == NULL) {
+ g_warning("%s: Null action passed (index=%d)", G_STRFUNC, i);
+ return FALSE;
+ }
+
+ JAW_GET_ACTION(action,
+ FALSE); // create local JNI reference `jobject atk_action`
+
+ jboolean jresult = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_action, cachedActionDoActionMethod, (jint)i);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+
+ return jresult;
+}
+
+/**
+ * jaw_action_get_n_actions:
+ * @action: a #GObject instance that implements AtkActionIface
+ *
+ * Gets the number of accessible actions available on the object.
+ * If there are more than one, the first one is considered the
+ * "default" action of the object.
+ *
+ * Returns: the number of actions, or 0 if @action does not
+ * implement this interface.
+ **/
+static gint jaw_action_get_n_actions(AtkAction *action) {
+ JAW_DEBUG("%p", action);
+
+ if (action == NULL) {
+ g_warning("%s: Null action passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_ACTION(action,
+ 0); // create local JNI reference `jobject atk_action`
+
+ gint ret = (gint)(*jniEnv)->CallIntMethod(jniEnv, atk_action,
+ cachedActionGetNActionsMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+
+ return ret;
+}
+
+/**
+ * jaw_action_get_description:
+ * @action: a #GObject instance that implements AtkActionIface
+ * @i: the action index corresponding to the action to be performed
+ *
+ * Returns a description of the specified action of the object.
+ *
+ * Returns: (nullable): a description string for action @i, or %NULL if
+ * @action does not implement this interface or if an error occurs.
+ **/
+static const gchar *jaw_action_get_description(AtkAction *action, gint i) {
+ JAW_DEBUG("%p, %d", action, i);
+
+ if (action == NULL) {
+ g_warning("%s: Null action passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_ACTION(action,
+ NULL); // create local JNI reference `jobject atk_action`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jstring jstr = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_action, cachedActionGetDescriptionMethod, (jint)i);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_debug("%s: No description available for action (index=%d, action=%p)",
+ G_STRFUNC, i, action);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ g_mutex_lock(&data->mutex);
+ if (data->jstrActionDescription != NULL) {
+ if (data->action_description != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(
+ jniEnv, data->jstrActionDescription, data->action_description);
+ data->action_description = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrActionDescription);
+ data->jstrActionDescription = NULL;
+ }
+
+ data->jstrActionDescription = (*jniEnv)->NewGlobalRef(jniEnv, jstr);
+ if (data->jstrActionDescription == NULL) {
+ g_mutex_unlock(&data->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ data->action_description =
+ (*jniEnv)->GetStringUTFChars(jniEnv, data->jstrActionDescription, NULL);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || data->action_description == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ // data->jstrActionDescription != NULL
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrActionDescription);
+ data->jstrActionDescription = NULL;
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ g_mutex_unlock(&data->mutex);
+ return NULL;
+ }
+
+ g_mutex_unlock(&data->mutex);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data->action_description;
+}
+
+/**
+ * jaw_action_set_description:
+ * @action: a #GObject instance that implements AtkActionIface
+ * @i: the action index corresponding to the action to be performed
+ * @description: the description to be assigned to this action
+ *
+ * Returns: %TRUE if the description was successfully set, %FALSE otherwise.
+ **/
+static gboolean jaw_action_set_description(AtkAction *action, gint i,
+ const gchar *description) {
+ JAW_DEBUG("%p, %d, %s", action, i, description);
+
+ if (action == NULL) {
+ g_warning("%s: Null action passed (index=%d)", G_STRFUNC, i);
+ return FALSE;
+ }
+ if (description == NULL) {
+ g_warning("%s: Null description passed (index=%d)", G_STRFUNC, i);
+ return FALSE;
+ }
+
+ JAW_GET_ACTION(action,
+ FALSE); // create local JNI reference `jobject atk_action`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return FALSE;
+ }
+
+ jstring jdescription = (*jniEnv)->NewStringUTF(jniEnv, description);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jdescription == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create Java string for description",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return FALSE;
+ }
+
+ jboolean jisset = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_action, cachedActionSetDescriptionMethod, (jint)i,
+ (jstring)jdescription);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return jisset;
+}
+
+/**
+ * jaw_action_get_localized_name:
+ * @action: a #GObject instance that implements AtkActionIface
+ * @i: the action index corresponding to the action to be performed
+ *
+ * Returns: (nullable): a localized name string for action @i, or %NULL
+ * if @action does not implement this interface or if an error occurs.
+ **/
+static const gchar *jaw_action_get_localized_name(AtkAction *action, gint i) {
+ JAW_DEBUG("%p, %d", action, i);
+
+ if (action == NULL) {
+ g_warning("%s: Null argument action passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_ACTION(action,
+ NULL); // create local JNI reference `jobject atk_action`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jstring jstr = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_action, cachedActionGetLocalizedNameMethod, (jint)i);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_debug(
+ "%s: No localized name available for action (index=%d, action=%p)",
+ G_STRFUNC, i, action);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ g_mutex_lock(&data->mutex);
+ if (data->jstrLocalizedName != NULL) {
+ if (data->localized_name != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, data->jstrLocalizedName,
+ data->localized_name);
+ data->localized_name = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrLocalizedName);
+ data->jstrLocalizedName = NULL;
+ }
+ data->jstrLocalizedName = (*jniEnv)->NewGlobalRef(jniEnv, jstr);
+ if (data->jstrLocalizedName == NULL) {
+ g_mutex_unlock(&data->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ data->localized_name =
+ (*jniEnv)->GetStringUTFChars(jniEnv, data->jstrLocalizedName, NULL);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || data->localized_name == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ // data->jstrLocalizedName != NULL
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrLocalizedName);
+ data->jstrLocalizedName = NULL;
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ g_mutex_unlock(&data->mutex);
+ return NULL;
+ }
+
+ g_mutex_unlock(&data->mutex);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_action);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data->localized_name;
+}
+
+static gboolean jaw_action_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localClass =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkAction");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkAction class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedActionAtkActionClass = (*jniEnv)->NewGlobalRef(jniEnv, localClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClass);
+
+ if (cachedActionAtkActionClass == NULL) {
+ g_warning("%s: Failed to create global reference for AtkAction class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedActionCreateAtkActionMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedActionAtkActionClass, "create_atk_action",
+ "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/"
+ "AtkAction;");
+
+ cachedActionDoActionMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedActionAtkActionClass, "do_action", "(I)Z");
+
+ cachedActionGetNActionsMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedActionAtkActionClass, "get_n_actions", "()I");
+
+ cachedActionGetDescriptionMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedActionAtkActionClass,
+ "get_description", "(I)Ljava/lang/String;");
+
+ cachedActionSetDescriptionMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedActionAtkActionClass,
+ "set_description", "(ILjava/lang/String;)Z");
+
+ cachedActionGetLocalizedNameMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedActionAtkActionClass,
+ "get_localized_name", "(I)Ljava/lang/String;");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedActionCreateAtkActionMethod == NULL ||
+ cachedActionDoActionMethod == NULL ||
+ cachedActionGetNActionsMethod == NULL ||
+ cachedActionGetDescriptionMethod == NULL ||
+ cachedActionSetDescriptionMethod == NULL ||
+ cachedActionGetLocalizedNameMethod == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache one or more AtkAction method IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedActionAtkActionClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedActionAtkActionClass);
+ cachedActionAtkActionClass = NULL;
+ }
+ cachedActionCreateAtkActionMethod = NULL;
+ cachedActionDoActionMethod = NULL;
+ cachedActionGetNActionsMethod = NULL;
+ cachedActionGetDescriptionMethod = NULL;
+ cachedActionSetDescriptionMethod = NULL;
+ cachedActionGetLocalizedNameMethod = NULL;
+
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+}
+
+void jaw_action_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedActionAtkActionClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedActionAtkActionClass);
+ cachedActionAtkActionClass = NULL;
+ }
+ cachedActionCreateAtkActionMethod = NULL;
+ cachedActionDoActionMethod = NULL;
+ cachedActionGetNActionsMethod = NULL;
+ cachedActionGetDescriptionMethod = NULL;
+ cachedActionSetDescriptionMethod = NULL;
+ cachedActionGetLocalizedNameMethod = NULL;
+ cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
\ No newline at end of file
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawcache.h b/src/jdk.accessibility/linux/native/libatk-wrapper/jawcache.h
new file mode 100644
index 000000000000..ab676066ccd6
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawcache.h
@@ -0,0 +1,74 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _JAW_CACHE_H_
+#define _JAW_CACHE_H_
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void atk_wrapper_cache_cleanup(JNIEnv *jniEnv);
+void jaw_action_cache_cleanup(JNIEnv *jniEnv);
+void jaw_component_cache_cleanup(JNIEnv *jniEnv);
+void jaw_editable_text_cache_cleanup(JNIEnv *jniEnv);
+void jaw_hyperlink_cache_cleanup(JNIEnv *jniEnv);
+void jaw_hypertext_cache_cleanup(JNIEnv *jniEnv);
+void jaw_image_cache_cleanup(JNIEnv *jniEnv);
+void jaw_impl_cache_cleanup(JNIEnv *jniEnv);
+void jaw_object_cache_cleanup(JNIEnv *jniEnv);
+void jaw_selection_cache_cleanup(JNIEnv *jniEnv);
+void jaw_table_cache_cleanup(JNIEnv *jniEnv);
+void jaw_tablecell_cache_cleanup(JNIEnv *jniEnv);
+void jaw_text_cache_cleanup(JNIEnv *jniEnv);
+void jaw_value_cache_cleanup(JNIEnv *jniEnv);
+void jaw_util_cache_cleanup(JNIEnv *jniEnv);
+
+// TODO: currently leaking
+static inline void jaw_cache_cleanup(JNIEnv *jniEnv) {
+ if (jniEnv == NULL) {
+ g_warning("%s: Null argument jniEnv passed to the function", G_STRFUNC);
+ return;
+ }
+
+ atk_wrapper_cache_cleanup(jniEnv);
+ jaw_action_cache_cleanup(jniEnv);
+ jaw_component_cache_cleanup(jniEnv);
+ jaw_editable_text_cache_cleanup(jniEnv);
+ jaw_hyperlink_cache_cleanup(jniEnv);
+ jaw_hypertext_cache_cleanup(jniEnv);
+ jaw_image_cache_cleanup(jniEnv);
+ jaw_impl_cache_cleanup(jniEnv);
+ jaw_object_cache_cleanup(jniEnv);
+ jaw_selection_cache_cleanup(jniEnv);
+ jaw_table_cache_cleanup(jniEnv);
+ jaw_tablecell_cache_cleanup(jniEnv);
+ jaw_text_cache_cleanup(jniEnv);
+ jaw_value_cache_cleanup(jniEnv);
+ jaw_util_cache_cleanup(jniEnv);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
\ No newline at end of file
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawcomponent.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawcomponent.c
new file mode 100644
index 000000000000..1a19e9027610
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawcomponent.c
@@ -0,0 +1,801 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems Inc.
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawcache.h"
+#include "jawimpl.h"
+#include "jawutil.h"
+#include
+#include
+#include
+
+/**
+ * (From Atk documentation)
+ *
+ * AtkComponent:
+ *
+ * The ATK interface provided by UI components
+ * which occupy a physical area on the screen.
+ * which the user can activate/interact with.
+ *
+ * #AtkComponent should be implemented by most if not all UI elements
+ * with an actual on-screen presence, i.e. components which can be
+ * said to have a screen-coordinate bounding box. Virtually all
+ * widgets will need to have #AtkComponent implementations provided
+ * for their corresponding #AtkObject class. In short, only UI
+ * elements which are *not* GUI elements will omit this ATK interface.
+ *
+ * A possible exception might be textual information with a
+ * transparent background, in which case text glyph bounding box
+ * information is provided by #AtkText.
+ */
+
+static jclass cachedComponentAtkComponentClass = NULL;
+static jmethodID cachedComponentCreateAtkComponentMethod = NULL;
+static jmethodID cachedComponentContainsMethod = NULL;
+static jmethodID cachedComponentGetAccessibleAtPointMethod = NULL;
+static jmethodID cachedComponentGetExtentsMethod = NULL;
+static jmethodID cachedComponentSetExtentsMethod = NULL;
+static jmethodID cachedComponentSetPositionMethod = NULL;
+static jmethodID cachedComponentSetSizeMethod = NULL;
+static jmethodID cachedComponentGrabFocusMethod = NULL;
+static jmethodID cachedComponentGetLayerMethod = NULL;
+static jclass cachedComponentRectangleClass = NULL;
+static jfieldID cachedComponentRectangleXField = NULL;
+static jfieldID cachedComponentRectangleYField = NULL;
+static jfieldID cachedComponentRectangleWidthField = NULL;
+static jfieldID cachedComponentRectangleHeightField = NULL;
+
+static GMutex cache_mutex;
+static gboolean cache_initialized = FALSE;
+
+static gboolean jaw_component_init_jni_cache(JNIEnv *jniEnv);
+
+static gboolean jaw_component_contains(AtkComponent *component, gint x, gint y,
+ AtkCoordType coord_type);
+
+static AtkObject *
+jaw_component_ref_accessible_at_point(AtkComponent *component, gint x, gint y,
+ AtkCoordType coord_type);
+
+static void jaw_component_get_extents(AtkComponent *component, gint *x, gint *y,
+ gint *width, gint *height,
+ AtkCoordType coord_type);
+
+static gboolean jaw_component_set_extents(AtkComponent *component, gint x,
+ gint y, gint width, gint height,
+ AtkCoordType coord_type);
+
+static gboolean jaw_component_set_size(AtkComponent *component, gint width,
+ gint height);
+
+static gboolean jaw_component_set_position(AtkComponent *component, gint x,
+ gint y, AtkCoordType coord_type);
+
+static gboolean jaw_component_grab_focus(AtkComponent *component);
+static AtkLayer jaw_component_get_layer(AtkComponent *component);
+
+typedef struct _ComponentData {
+ jobject atk_component;
+} ComponentData;
+
+#define JAW_GET_COMPONENT(component, def_ret) \
+ JAW_GET_OBJ_IFACE( \
+ component, org_GNOME_Accessibility_AtkInterface_INTERFACE_COMPONENT, \
+ ComponentData, atk_component, jniEnv, atk_component, def_ret)
+
+/*
+ * Atk.Component Methods
+ * contains (x, y, coord_type)
+ * get_alpha ()
+ * get_extents (coord_type)
+ * get_layer ()
+ * get_mdi_zorder ()
+ * get_position (coord_type)
+ * get_size ()
+ * grab_focus ()
+ * ref_accessible_at_point (x, y, coord_type)
+ * remove_focus_handler (handler_id)
+ * scroll_to (type)
+ * scroll_to_point (coords, x, y)
+ * set_extents (x, y, width, height, coord_type)
+ * set_position (x, y, coord_type)
+ * set_size (width, height)
+ */
+void jaw_component_interface_init(AtkComponentIface *iface, gpointer data) {
+ JAW_DEBUG("%p,%p", iface, data);
+
+ if (iface == NULL) {
+ g_warning("%s: Null argument passed to function", G_STRFUNC);
+ return;
+ }
+
+ iface->contains = jaw_component_contains;
+ iface->get_alpha = NULL; // missing java support for iface->get_alpha
+ iface->get_layer = jaw_component_get_layer;
+ iface->get_extents = jaw_component_get_extents;
+ iface->get_mdi_zorder = NULL; // TODO: jaw_component_get_mdi_zorder;
+ // done by atk: iface->get_position (atk_component_real_get_position)
+ // done by atk: iface->get_size (atk_component_real_get_size)
+ iface->grab_focus = jaw_component_grab_focus;
+ iface->ref_accessible_at_point = jaw_component_ref_accessible_at_point;
+ iface->remove_focus_handler =
+ NULL; // deprecated: iface->remove_focus_handler
+ iface->scroll_to = NULL; // missing java support for iface->scroll_to
+ iface->scroll_to_point =
+ NULL; // missing java support for iface->scroll_to_point
+ iface->set_extents = jaw_component_set_extents;
+ iface->set_position = jaw_component_set_position;
+ iface->set_size = jaw_component_set_size;
+}
+
+gpointer jaw_component_data_init(jobject ac) {
+ JAW_DEBUG("%p", ac);
+
+ if (ac == NULL) {
+ g_warning("%s: Null argument passed to function", G_STRFUNC);
+ return NULL;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_component_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jatk_component = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedComponentAtkComponentClass,
+ cachedComponentCreateAtkComponentMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_component == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jatk_component using "
+ "create_atk_component method",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ ComponentData *data = g_new0(ComponentData, 1);
+ data->atk_component = (*jniEnv)->NewGlobalRef(jniEnv, jatk_component);
+ if (data->atk_component == NULL) {
+ g_warning("%s: Failed to create global ref for atk_component",
+ G_STRFUNC);
+ g_free(data);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data;
+}
+
+void jaw_component_data_finalize(gpointer p) {
+ JAW_DEBUG("%p", p);
+
+ if (p == NULL) {
+ g_debug("%s: Null argument passed to function", G_STRFUNC);
+ return;
+ }
+
+ ComponentData *data = (ComponentData *)p;
+ if (data == NULL) {
+ g_warning("%s: data is null after cast", G_STRFUNC);
+ return;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+
+ if (jniEnv == NULL) {
+ g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC);
+ } else {
+ if (data->atk_component != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_component);
+ data->atk_component = NULL;
+ }
+ }
+
+ g_free(data);
+}
+
+/**
+ * jaw_component_contains:
+ * @component: the #AtkComponent
+ * @x: x coordinate
+ * @y: y coordinate
+ * @coord_type: specifies whether the coordinates are relative to the screen
+ * or to the components top level window
+ *
+ * Checks whether the specified point is within the extent of the @component.
+ *
+ * Toolkit implementor note: ATK provides a default implementation for
+ * this virtual method. In general there are little reason to
+ * re-implement it.
+ *
+ * Returns: %TRUE or %FALSE indicating whether the specified point is within
+ * the extent of the @component or not
+ **/
+static gboolean jaw_component_contains(AtkComponent *component, gint x, gint y,
+ AtkCoordType coord_type) {
+ JAW_DEBUG("%p, %d, %d, %d", component, x, y, coord_type);
+
+ if (component == NULL) {
+ g_warning("%s: Null argument passed to function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_COMPONENT(
+ component,
+ FALSE); // create local JNI reference `jobject atk_component`
+
+ jboolean jcontains = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_component, cachedComponentContainsMethod, (jint)x, (jint)y,
+ (jint)coord_type);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+
+ return jcontains;
+}
+
+/**
+ * jaw_component_ref_accessible_at_point:
+ * @component: the #AtkComponent
+ * @x: x coordinate
+ * @y: y coordinate
+ * @coord_type: specifies whether the coordinates are relative to the screen
+ * or to the components top level window
+ *
+ * Gets a reference to the accessible child, if one exists, at the
+ * coordinate point specified by @x and @y.
+ *
+ * Returns: (nullable) (transfer full): a reference to the accessible
+ * child, if one exists
+ **/
+static AtkObject *
+jaw_component_ref_accessible_at_point(AtkComponent *component, gint x, gint y,
+ AtkCoordType coord_type) {
+ JAW_DEBUG("%p, %d, %d, %d", component, x, y, coord_type);
+
+ if (component == NULL) {
+ g_warning("%s: Null argument passed to function ", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_COMPONENT(
+ component, NULL); // create local JNI reference `jobject atk_component`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject child_ac = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_component, cachedComponentGetAccessibleAtPointMethod,
+ (jint)x, (jint)y, (jint)coord_type);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || child_ac == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_accessible_at_point method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, child_ac);
+
+ // From the documentation of the `ref_accessible_at_point`:
+ // "The caller of the method takes ownership of the returned data, and is
+ // responsible for freeing it." (transfer full annotation), so
+ // we have to ref the `jaw_impl`
+ if (jaw_impl != NULL) {
+ g_object_ref(G_OBJECT(jaw_impl));
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return ATK_OBJECT(jaw_impl);
+}
+
+/**
+ * jaw_component_get_extents:
+ * @component: an #AtkComponent
+ * @x: (out) (optional): address of #gint to put x coordinate
+ * @y: (out) (optional): address of #gint to put y coordinate
+ * @width: (out) (optional): address of #gint to put width
+ * @height: (out) (optional): address of #gint to put height
+ * @coord_type: specifies whether the coordinates are relative to the screen
+ * or to the components top level window
+ *
+ * Gets the rectangle which gives the extent of the @component.
+ *
+ * If the extent can not be obtained (e.g. a non-embedded plug or missing
+ * support), all of x, y, width, height are set to -1.
+ *
+ **/
+static void jaw_component_get_extents(AtkComponent *component, gint *x, gint *y,
+ gint *width, gint *height,
+ AtkCoordType coord_type) {
+ JAW_DEBUG("%p, %p, %p, %p, %p, %d", component, x, y, width, height,
+ coord_type);
+
+ if (component == NULL) {
+ g_warning("%s: Null component passed to function", G_STRFUNC);
+ return;
+ }
+
+ if (x != NULL)
+ (*x) = -1;
+ if (y != NULL)
+ (*y) = -1;
+ if (width != NULL)
+ (*width) = -1;
+ if (height != NULL)
+ (*height) = -1;
+
+ JAW_GET_COMPONENT(
+ component, ); // create local JNI reference `jobject atk_component`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(
+ jniEnv,
+ atk_component); // deleting ref that was created in
+ // JAW_GET_COMPONENT
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ jobject jrectangle = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_component, cachedComponentGetExtentsMethod,
+ (jint)coord_type);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jrectangle == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jrectangle using get_extents method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+
+ if (x != NULL) {
+ (*x) = (gint)(*jniEnv)->GetIntField(jniEnv, jrectangle,
+ cachedComponentRectangleXField);
+ }
+ if (y != NULL) {
+ (*y) = (gint)(*jniEnv)->GetIntField(jniEnv, jrectangle,
+ cachedComponentRectangleYField);
+ }
+ if (width != NULL) {
+ (*width) = (gint)(*jniEnv)->GetIntField(
+ jniEnv, jrectangle, cachedComponentRectangleWidthField);
+ }
+ if (height != NULL) {
+ (*height) = (gint)(*jniEnv)->GetIntField(
+ jniEnv, jrectangle, cachedComponentRectangleHeightField);
+ }
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * jaw_component_set_extents:
+ * @component: an #AtkComponent
+ * @x: x coordinate
+ * @y: y coordinate
+ * @width: width to set for @component
+ * @height: height to set for @component
+ * @coord_type: specifies whether the coordinates are relative to the screen
+ * or to the components top level window
+ *
+ * Sets the extents of @component.
+ *
+ * Returns: %TRUE or %FALSE whether the extents were set or not
+ **/
+static gboolean jaw_component_set_extents(AtkComponent *component, gint x,
+ gint y, gint width, gint height,
+ AtkCoordType coord_type) {
+ JAW_DEBUG("%p, %d, %d, %d, %d, %d", component, x, y, width, height,
+ coord_type);
+
+ if (component == NULL) {
+ g_warning("%s: Null argument passed to function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_COMPONENT(
+ component,
+ FALSE); // create local JNI reference `jobject atk_component`
+
+ jboolean assigned = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_component, cachedComponentSetExtentsMethod, (jint)x,
+ (jint)y, (jint)width, (jint)height, (jint)coord_type);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+
+ return assigned;
+}
+
+/**
+ * jaw_component_set_position:
+ * @component: an #AtkComponent
+ * @x: x coordinate
+ * @y: y coordinate
+ * @coord_type: specifies whether the coordinates are relative to the screen
+ * or to the components top level window
+ *
+ * Sets the position of @component.
+ *
+ * Returns: %TRUE or %FALSE whether the position were set or not
+ **/
+static gboolean jaw_component_set_position(AtkComponent *component, gint x,
+ gint y, AtkCoordType coord_type) {
+ JAW_DEBUG("%p, %d, %d, %d", component, x, y, coord_type);
+
+ if (component == NULL) {
+ g_warning("%s: Null argument passed to function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_COMPONENT(
+ component,
+ FALSE); // create local JNI reference `jobject atk_component`
+
+ jboolean assigned = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_component, cachedComponentSetPositionMethod, (jint)x,
+ (jint)y, (jint)coord_type);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+
+ return assigned;
+}
+
+/**
+ * jaw_component_set_size:
+ * @component: an #AtkComponent
+ * @width: width to set for @component
+ * @height: height to set for @component
+ *
+ * Sets the size of @component.
+ *
+ * Returns: %TRUE or %FALSE whether the size were set or not
+ **/
+static gboolean jaw_component_set_size(AtkComponent *component, gint width,
+ gint height) {
+ JAW_DEBUG("%p, %d, %d", component, width, height);
+
+ if (component == NULL) {
+ g_warning("%s: Null argument passed to function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_COMPONENT(
+ component,
+ FALSE); // create local JNI reference `jobject atk_component`
+
+ jboolean assigned = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_component, cachedComponentSetSizeMethod, (jint)width,
+ (jint)height);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+
+ return assigned;
+}
+
+/**
+ * jaw_component_grab_focus:
+ * @component: an #AtkComponent
+ *
+ * Grabs focus for this @component.
+ *
+ * Returns: %TRUE if successful, %FALSE otherwise.
+ **/
+static gboolean jaw_component_grab_focus(AtkComponent *component) {
+ JAW_DEBUG("%p", component);
+
+ if (component == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_COMPONENT(
+ component,
+ FALSE); // create local JNI reference `jobject atk_component`
+
+ jboolean jresult = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_component, cachedComponentGrabFocusMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+
+ return jresult;
+}
+
+/**
+ * jaw_component_get_layer:
+ * @component: an #AtkComponent
+ *
+ * Gets the layer of the component.
+ *
+ * Returns: an #AtkLayer which is the layer of the component, 0 if an error
+ *happened.
+ **/
+static AtkLayer jaw_component_get_layer(AtkComponent *component) {
+ JAW_DEBUG("%p", component);
+
+ if (component == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_COMPONENT(component,
+ 0); // create local JNI reference `jobject atk_component`
+
+ jint jlayer = (*jniEnv)->CallIntMethod(jniEnv, atk_component,
+ cachedComponentGetLayerMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_component);
+
+ return (AtkLayer)jlayer;
+}
+
+static gboolean jaw_component_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localClass =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkComponent");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkComponent class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedComponentAtkComponentClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClass);
+
+ if (cachedComponentAtkComponentClass == NULL) {
+ g_warning(
+ "%s: Failed to create global reference for AtkComponent class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedComponentCreateAtkComponentMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedComponentAtkComponentClass, "create_atk_component",
+ "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/"
+ "AtkComponent;");
+
+ cachedComponentContainsMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedComponentAtkComponentClass, "contains", "(III)Z");
+
+ cachedComponentGetAccessibleAtPointMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedComponentAtkComponentClass, "get_accessible_at_point",
+ "(III)Ljavax/accessibility/AccessibleContext;");
+
+ cachedComponentGetExtentsMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedComponentAtkComponentClass,
+ "get_extents", "(I)Ljava/awt/Rectangle;");
+
+ cachedComponentSetExtentsMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedComponentAtkComponentClass, "set_extents", "(IIIII)Z");
+
+ cachedComponentSetPositionMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedComponentAtkComponentClass, "set_position", "(III)Z");
+
+ cachedComponentSetSizeMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedComponentAtkComponentClass, "set_size", "(II)Z");
+
+ cachedComponentGrabFocusMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedComponentAtkComponentClass, "grab_focus", "()Z");
+
+ cachedComponentGetLayerMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedComponentAtkComponentClass, "get_layer", "()I");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedComponentCreateAtkComponentMethod == NULL ||
+ cachedComponentContainsMethod == NULL ||
+ cachedComponentGetAccessibleAtPointMethod == NULL ||
+ cachedComponentGetExtentsMethod == NULL ||
+ cachedComponentSetExtentsMethod == NULL ||
+ cachedComponentSetPositionMethod == NULL ||
+ cachedComponentSetSizeMethod == NULL ||
+ cachedComponentGrabFocusMethod == NULL ||
+ cachedComponentGetLayerMethod == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache one or more AtkComponent method IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ jclass localRectangleClass =
+ (*jniEnv)->FindClass(jniEnv, "java/awt/Rectangle");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localRectangleClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to find Rectangle class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedComponentRectangleClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localRectangleClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localRectangleClass);
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedComponentRectangleClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to create global reference for Rectangle class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedComponentRectangleXField =
+ (*jniEnv)->GetFieldID(jniEnv, cachedComponentRectangleClass, "x", "I");
+ cachedComponentRectangleYField =
+ (*jniEnv)->GetFieldID(jniEnv, cachedComponentRectangleClass, "y", "I");
+ cachedComponentRectangleWidthField = (*jniEnv)->GetFieldID(
+ jniEnv, cachedComponentRectangleClass, "width", "I");
+ cachedComponentRectangleHeightField = (*jniEnv)->GetFieldID(
+ jniEnv, cachedComponentRectangleClass, "height", "I");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedComponentRectangleXField == NULL ||
+ cachedComponentRectangleYField == NULL ||
+ cachedComponentRectangleWidthField == NULL ||
+ cachedComponentRectangleHeightField == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache one or more Rectangle field IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedComponentAtkComponentClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedComponentAtkComponentClass);
+ cachedComponentAtkComponentClass = NULL;
+ }
+ if (cachedComponentRectangleClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedComponentRectangleClass);
+ cachedComponentRectangleClass = NULL;
+ }
+ cachedComponentCreateAtkComponentMethod = NULL;
+ cachedComponentContainsMethod = NULL;
+ cachedComponentGetAccessibleAtPointMethod = NULL;
+ cachedComponentGetExtentsMethod = NULL;
+ cachedComponentSetExtentsMethod = NULL;
+ cachedComponentSetPositionMethod = NULL;
+ cachedComponentSetSizeMethod = NULL;
+ cachedComponentGrabFocusMethod = NULL;
+ cachedComponentGetLayerMethod = NULL;
+ cachedComponentRectangleXField = NULL;
+ cachedComponentRectangleYField = NULL;
+ cachedComponentRectangleWidthField = NULL;
+ cachedComponentRectangleHeightField = NULL;
+
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+}
+
+void jaw_component_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedComponentAtkComponentClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedComponentAtkComponentClass);
+ cachedComponentAtkComponentClass = NULL;
+ }
+ if (cachedComponentRectangleClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedComponentRectangleClass);
+ cachedComponentRectangleClass = NULL;
+ }
+ cachedComponentCreateAtkComponentMethod = NULL;
+ cachedComponentContainsMethod = NULL;
+ cachedComponentGetAccessibleAtPointMethod = NULL;
+ cachedComponentGetExtentsMethod = NULL;
+ cachedComponentSetExtentsMethod = NULL;
+ cachedComponentSetPositionMethod = NULL;
+ cachedComponentSetSizeMethod = NULL;
+ cachedComponentGrabFocusMethod = NULL;
+ cachedComponentGetLayerMethod = NULL;
+ cachedComponentRectangleXField = NULL;
+ cachedComponentRectangleYField = NULL;
+ cachedComponentRectangleWidthField = NULL;
+ cachedComponentRectangleHeightField = NULL;
+ cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jaweditabletext.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jaweditabletext.c
new file mode 100644
index 000000000000..faed3ed28c9c
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jaweditabletext.c
@@ -0,0 +1,551 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawcache.h"
+#include "jawimpl.h"
+#include "jawobject.h"
+#include "jawutil.h"
+#include
+#include
+
+/**
+ * (From Atk documentation)
+ *
+ * AtkEditableText:
+ *
+ * The ATK interface implemented by components containing user-editable text
+ * content.
+ *
+ * #AtkEditableText should be implemented by UI components which
+ * contain text which the user can edit, via the #AtkObject
+ * corresponding to that component (see #AtkObject).
+ *
+ * #AtkEditableText is a subclass of #AtkText, and as such, an object
+ * which implements #AtkEditableText is by definition an #AtkText
+ * implementor as well.
+ *
+ * See [iface@AtkText]
+ */
+
+static jclass cachedEditableTextAtkEditableTextClass = NULL;
+static jmethodID cachedEditableTextCreateAtkEditableTextMethod = NULL;
+static jmethodID cachedEditableTextSetTextContentsMethod = NULL;
+static jmethodID cachedEditableTextInsertTextMethod = NULL;
+static jmethodID cachedEditableTextCopyTextMethod = NULL;
+static jmethodID cachedEditableTextCutTextMethod = NULL;
+static jmethodID cachedEditableTextDeleteTextMethod = NULL;
+static jmethodID cachedEditableTextPasteTextMethod = NULL;
+static jmethodID cachedEditableTextSetRunAttributesMethod = NULL;
+
+static GMutex cache_mutex;
+static gboolean cache_initialized = FALSE;
+
+static gboolean jaw_editable_text_init_jni_cache(JNIEnv *jniEnv);
+
+static void jaw_editable_text_set_text_contents(AtkEditableText *text,
+ const gchar *string);
+static void jaw_editable_text_insert_text(AtkEditableText *text,
+ const gchar *string, gint length,
+ gint *position);
+static void jaw_editable_text_copy_text(AtkEditableText *text, gint start_pos,
+ gint end_pos);
+static void jaw_editable_text_cut_text(AtkEditableText *text, gint start_pos,
+ gint end_pos);
+static void jaw_editable_text_delete_text(AtkEditableText *text, gint start_pos,
+ gint end_pos);
+static void jaw_editable_text_paste_text(AtkEditableText *text, gint position);
+
+static gboolean
+jaw_editable_text_set_run_attributes(AtkEditableText *text,
+ AtkAttributeSet *attrib_set,
+ gint start_offset, gint end_offset);
+
+typedef struct _EditableTextData {
+ jobject atk_editable_text;
+} EditableTextData;
+
+#define JAW_GET_EDITABLETEXT(text, def_ret) \
+ JAW_GET_OBJ_IFACE( \
+ text, org_GNOME_Accessibility_AtkInterface_INTERFACE_EDITABLE_TEXT, \
+ EditableTextData, atk_editable_text, jniEnv, atk_editable_text, \
+ def_ret)
+
+/**
+ * AtkEditableTextIface:
+ * @set_run_attributes:
+ * @set_text_contents:
+ * @copy_text:
+ * @cut_text:
+ * @delete_text:
+ * @paste_text:
+ **/
+void jaw_editable_text_interface_init(AtkEditableTextIface *iface,
+ gpointer data) {
+ JAW_DEBUG("%p,%p", iface, data);
+
+ if (iface == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ iface->set_run_attributes = jaw_editable_text_set_run_attributes;
+ iface->set_text_contents = jaw_editable_text_set_text_contents;
+ iface->insert_text = jaw_editable_text_insert_text;
+ iface->copy_text = jaw_editable_text_copy_text;
+ iface->cut_text = jaw_editable_text_cut_text;
+ iface->delete_text = jaw_editable_text_delete_text;
+ iface->paste_text = jaw_editable_text_paste_text;
+}
+
+gpointer jaw_editable_text_data_init(jobject ac) {
+ JAW_DEBUG("%p", ac);
+
+ if (ac == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_editable_text_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jatk_editable_text = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedEditableTextAtkEditableTextClass,
+ cachedEditableTextCreateAtkEditableTextMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_editable_text == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jatk_editable_text using "
+ "create_atk_editable_text method",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ EditableTextData *data = g_new0(EditableTextData, 1);
+ data->atk_editable_text =
+ (*jniEnv)->NewGlobalRef(jniEnv, jatk_editable_text);
+ if (data->atk_editable_text == NULL) {
+ g_warning("%s: Failed to create global ref for atk_editable_text",
+ G_STRFUNC);
+ g_free(data);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data;
+}
+
+void jaw_editable_text_data_finalize(gpointer p) {
+ JAW_DEBUG("%p", p);
+
+ if (p == NULL) {
+ g_debug("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ EditableTextData *data = (EditableTextData *)p;
+ if (data == NULL) {
+ g_debug("%s: data is null after cast", G_STRFUNC);
+ return;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+
+ if (jniEnv == NULL) {
+ g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC);
+ } else {
+ if (data->atk_editable_text != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_editable_text);
+ data->atk_editable_text = NULL;
+ }
+ }
+
+ g_free(data);
+}
+
+void jaw_editable_text_set_text_contents(AtkEditableText *text,
+ const gchar *string) {
+ JAW_DEBUG("%p, %s", text, string);
+
+ if (text == NULL || string == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_EDITABLETEXT(
+ text, ); // create local JNI reference `jobject atk_editable_text`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ jstring jstr = (*jniEnv)->NewStringUTF(jniEnv, string);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jstr using NewStringUTF", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->CallVoidMethod(jniEnv, atk_editable_text,
+ cachedEditableTextSetTextContentsMethod, jstr);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+void jaw_editable_text_insert_text(AtkEditableText *text, const gchar *string,
+ gint length, gint *position) {
+ JAW_DEBUG("%p, %s, %d, %p", text, string, length, position);
+
+ if (text == NULL || string == NULL || position == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_EDITABLETEXT(
+ text, ); // create local JNI reference `jobject atk_editable_text`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ jstring jstr = (*jniEnv)->NewStringUTF(jniEnv, string);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jstr using NewStringUTF", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+ (*jniEnv)->CallVoidMethod(jniEnv, atk_editable_text,
+ cachedEditableTextInsertTextMethod, jstr,
+ (jint)*position);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ *position = *position + length;
+ atk_text_set_caret_offset(ATK_TEXT(text), *position);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+void jaw_editable_text_copy_text(AtkEditableText *text, gint start_pos,
+ gint end_pos) {
+ JAW_DEBUG("%p, %d, %d", text, start_pos, end_pos);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_EDITABLETEXT(
+ text, ); // create local JNI reference `jobject atk_editable_text`
+
+ (*jniEnv)->CallVoidMethod(jniEnv, atk_editable_text,
+ cachedEditableTextCopyTextMethod, (jint)start_pos,
+ (jint)end_pos);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ return;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+}
+
+void jaw_editable_text_cut_text(AtkEditableText *text, gint start_pos,
+ gint end_pos) {
+ JAW_DEBUG("%p, %d, %d", text, start_pos, end_pos);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_EDITABLETEXT(
+ text, ); // create local JNI reference `jobject atk_editable_text`
+
+ (*jniEnv)->CallVoidMethod(jniEnv, atk_editable_text,
+ cachedEditableTextCutTextMethod, (jint)start_pos,
+ (jint)end_pos);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ return;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+}
+
+void jaw_editable_text_delete_text(AtkEditableText *text, gint start_pos,
+ gint end_pos) {
+ JAW_DEBUG("%p, %d, %d", text, start_pos, end_pos);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_EDITABLETEXT(
+ text, ); // create local JNI reference `jobject atk_editable_text`
+
+ (*jniEnv)->CallVoidMethod(jniEnv, atk_editable_text,
+ cachedEditableTextDeleteTextMethod,
+ (jint)start_pos, (jint)end_pos);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ return;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+}
+
+void jaw_editable_text_paste_text(AtkEditableText *text, gint position) {
+ JAW_DEBUG("%p, %d", text, position);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_EDITABLETEXT(
+ text, ); // create local JNI reference `jobject atk_editable_text`
+
+ (*jniEnv)->CallVoidMethod(jniEnv, atk_editable_text,
+ cachedEditableTextPasteTextMethod,
+ (jint)position);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ return;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+}
+
+/**
+ *jaw_editable_text_set_run_attributes:
+ *@text: an #AtkEditableText
+ *@attrib_set: an #AtkAttributeSet
+ *@start_offset: start of range in which to set attributes
+ *@end_offset: end of range in which to set attributes
+ *
+ *Returns: %TRUE if attributes successfully set for the specified
+ *range, otherwise %FALSE
+ **/
+static gboolean
+jaw_editable_text_set_run_attributes(AtkEditableText *text,
+ AtkAttributeSet *attrib_set,
+ gint start_offset, gint end_offset) {
+ JAW_DEBUG("%p, %p, %d, %d", text, attrib_set, start_offset, end_offset);
+
+ if (text == NULL || attrib_set == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_EDITABLETEXT(
+ text, FALSE); // create local JNI reference `jobject atk_editable_text`
+
+ // TODO: make a proper conversion between attrib_set and swing AttributeSet,
+ // current implementation is incorrect
+ jboolean jresult = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_editable_text, cachedEditableTextSetRunAttributesMethod,
+ (jobject)attrib_set, (jint)start_offset, (jint)end_offset);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_editable_text);
+
+ return jresult;
+}
+
+static gboolean jaw_editable_text_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localClass =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkEditableText");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkEditableText class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedEditableTextAtkEditableTextClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClass);
+
+ if (cachedEditableTextAtkEditableTextClass == NULL) {
+ g_warning(
+ "%s: Failed to create global reference for AtkEditableText class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedEditableTextCreateAtkEditableTextMethod =
+ (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedEditableTextAtkEditableTextClass,
+ "create_atk_editable_text",
+ "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/"
+ "AtkEditableText;");
+
+ cachedEditableTextSetTextContentsMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedEditableTextAtkEditableTextClass,
+ "set_text_contents", "(Ljava/lang/String;)V");
+
+ cachedEditableTextInsertTextMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedEditableTextAtkEditableTextClass,
+ "insert_text", "(Ljava/lang/String;I)V");
+
+ cachedEditableTextCopyTextMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedEditableTextAtkEditableTextClass, "copy_text", "(II)V");
+
+ cachedEditableTextCutTextMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedEditableTextAtkEditableTextClass, "cut_text", "(II)V");
+
+ cachedEditableTextDeleteTextMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedEditableTextAtkEditableTextClass, "delete_text", "(II)V");
+
+ cachedEditableTextPasteTextMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedEditableTextAtkEditableTextClass, "paste_text", "(I)V");
+
+ cachedEditableTextSetRunAttributesMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedEditableTextAtkEditableTextClass, "set_run_attributes",
+ "(Ljavax/swing/text/AttributeSet;II)Z");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedEditableTextCreateAtkEditableTextMethod == NULL ||
+ cachedEditableTextSetTextContentsMethod == NULL ||
+ cachedEditableTextInsertTextMethod == NULL ||
+ cachedEditableTextCopyTextMethod == NULL ||
+ cachedEditableTextCutTextMethod == NULL ||
+ cachedEditableTextDeleteTextMethod == NULL ||
+ cachedEditableTextPasteTextMethod == NULL ||
+ cachedEditableTextSetRunAttributesMethod == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache one or more AtkEditableText method IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedEditableTextAtkEditableTextClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv,
+ cachedEditableTextAtkEditableTextClass);
+ cachedEditableTextAtkEditableTextClass = NULL;
+ }
+ cachedEditableTextCreateAtkEditableTextMethod = NULL;
+ cachedEditableTextSetTextContentsMethod = NULL;
+ cachedEditableTextInsertTextMethod = NULL;
+ cachedEditableTextCopyTextMethod = NULL;
+ cachedEditableTextCutTextMethod = NULL;
+ cachedEditableTextDeleteTextMethod = NULL;
+ cachedEditableTextPasteTextMethod = NULL;
+ cachedEditableTextSetRunAttributesMethod = NULL;
+
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+}
+
+void jaw_editable_text_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedEditableTextAtkEditableTextClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv,
+ cachedEditableTextAtkEditableTextClass);
+ cachedEditableTextAtkEditableTextClass = NULL;
+ }
+ cachedEditableTextCreateAtkEditableTextMethod = NULL;
+ cachedEditableTextSetTextContentsMethod = NULL;
+ cachedEditableTextInsertTextMethod = NULL;
+ cachedEditableTextCopyTextMethod = NULL;
+ cachedEditableTextCutTextMethod = NULL;
+ cachedEditableTextDeleteTextMethod = NULL;
+ cachedEditableTextPasteTextMethod = NULL;
+ cachedEditableTextSetRunAttributesMethod = NULL;
+ cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawhyperlink.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawhyperlink.c
new file mode 100644
index 000000000000..32340cffbace
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawhyperlink.c
@@ -0,0 +1,667 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawhyperlink.h"
+#include "jawcache.h"
+#include "jawimpl.h"
+#include "jawutil.h"
+#include
+
+/**
+ * (From Atk documentation)
+ *
+ * AtkHyperlink:
+ *
+ * An ATK object which encapsulates a link or set of links in a hypertext
+ * document.
+ *
+ * An ATK object which encapsulates a link or set of links (for
+ * instance in the case of client-side image maps) in a hypertext
+ * document. It may implement the AtkAction interface. AtkHyperlink
+ * may also be used to refer to inline embedded content, since it
+ * allows specification of a start and end offset within the host
+ * AtkHypertext object.
+ */
+
+static jclass cachedHyperlinkAtkHyperlinkClass = NULL;
+static jmethodID cachedHyperlinkGetUriMethod = NULL;
+static jmethodID cachedHyperlinkGetObjectMethod = NULL;
+static jmethodID cachedHyperlinkGetEndIndexMethod = NULL;
+static jmethodID cachedHyperlinkGetStartIndexMethod = NULL;
+static jmethodID cachedHyperlinkIsValidMethod = NULL;
+static jmethodID cachedHyperlinkGetNAnchorsMethod = NULL;
+
+static GMutex cache_mutex;
+static gboolean cache_initialized = FALSE;
+
+static gboolean jaw_hyperlink_init_jni_cache(JNIEnv *jniEnv);
+
+static void jaw_hyperlink_dispose(GObject *gobject);
+static void jaw_hyperlink_finalize(GObject *gobject);
+
+static gchar *jaw_hyperlink_get_uri(AtkHyperlink *atk_hyperlink, gint i);
+static AtkObject *jaw_hyperlink_get_object(AtkHyperlink *atk_hyperlink, gint i);
+static gint jaw_hyperlink_get_end_index(AtkHyperlink *atk_hyperlink);
+static gint jaw_hyperlink_get_start_index(AtkHyperlink *atk_hyperlink);
+static gboolean jaw_hyperlink_is_valid(AtkHyperlink *atk_hyperlink);
+static gint jaw_hyperlink_get_n_anchors(AtkHyperlink *atk_hyperlink);
+
+G_DEFINE_TYPE(JawHyperlink, jaw_hyperlink, ATK_TYPE_HYPERLINK)
+
+#define JAW_GET_HYPERLINK(atk_hyperlink, def_ret) \
+ JAW_GET_OBJ(atk_hyperlink, JAW_HYPERLINK, JawHyperlink, jaw_hyperlink, \
+ jhyperlink, jniEnv, jhyperlink, def_ret)
+
+JawHyperlink *jaw_hyperlink_new(jobject jhyperlink) {
+ JAW_DEBUG("%p", jhyperlink);
+
+ if (jhyperlink == NULL) {
+ g_warning("%s: NULL jhyperlink parameter", G_STRFUNC);
+ return NULL;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: Failed to get JNI environment", G_STRFUNC);
+ return NULL;
+ }
+
+ JawHyperlink *jaw_hyperlink = g_object_new(JAW_TYPE_HYPERLINK, NULL);
+ if (jaw_hyperlink == NULL) {
+ g_warning("%s: Failed to create JawHyperlink object", G_STRFUNC);
+ return NULL;
+ }
+
+ jaw_hyperlink->jhyperlink = (*jniEnv)->NewGlobalRef(jniEnv, jhyperlink);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ jaw_hyperlink->jhyperlink == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create global reference", G_STRFUNC);
+ g_object_unref(jaw_hyperlink);
+ return NULL;
+ }
+
+ return jaw_hyperlink;
+}
+
+/**
+ * _AtkHyperlinkClass:
+ * @get_uri:
+ * @get_object:
+ * @get_end_index:
+ * @get_start_index:
+ * @is_valid:
+ * @get_n_anchors:
+ * @link_state:
+ * @is_selected_link:
+ * @link_activated -- The signal link-activated is emitted when a link is
+ *activated.
+ **/
+static void jaw_hyperlink_class_init(JawHyperlinkClass *klass) {
+ JAW_DEBUG("%p", klass);
+
+ if (klass == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+ gobject_class->dispose = jaw_hyperlink_dispose;
+ gobject_class->finalize = jaw_hyperlink_finalize;
+
+ AtkHyperlinkClass *atk_hyperlink_class = ATK_HYPERLINK_CLASS(klass);
+ atk_hyperlink_class->get_uri = jaw_hyperlink_get_uri;
+ atk_hyperlink_class->get_object = jaw_hyperlink_get_object;
+ atk_hyperlink_class->get_end_index = jaw_hyperlink_get_end_index;
+ atk_hyperlink_class->get_start_index = jaw_hyperlink_get_start_index;
+ atk_hyperlink_class->is_valid = jaw_hyperlink_is_valid;
+ atk_hyperlink_class->get_n_anchors = jaw_hyperlink_get_n_anchors;
+ atk_hyperlink_class->link_state =
+ NULL; // missing java support for atk_hyperlink_class->link_state
+ atk_hyperlink_class->is_selected_link =
+ NULL; // missing java support for
+ // atk_hyperlink_class->is_selected_link
+}
+
+static void jaw_hyperlink_init(JawHyperlink *link) {
+ JAW_DEBUG("%p", link);
+ g_mutex_init(&link->mutex);
+}
+
+static void jaw_hyperlink_dispose(GObject *gobject) {
+ JAW_DEBUG("%p", gobject);
+
+ if (gobject == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ /* Chain up to parent's dispose */
+ G_OBJECT_CLASS(jaw_hyperlink_parent_class)->dispose(gobject);
+}
+
+static void jaw_hyperlink_finalize(GObject *gobject) {
+ JAW_DEBUG("%p", gobject);
+
+ if (gobject == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JawHyperlink *jaw_hyperlink = JAW_HYPERLINK(gobject);
+ if (jaw_hyperlink == NULL) {
+ g_debug("%s: jaw_hyperlink is NULL", G_STRFUNC);
+ G_OBJECT_CLASS(jaw_hyperlink_parent_class)->finalize(gobject);
+ return;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_debug("%s: jniEnv is NULL", G_STRFUNC);
+ g_mutex_clear(&jaw_hyperlink->mutex);
+ G_OBJECT_CLASS(jaw_hyperlink_parent_class)->finalize(gobject);
+ return;
+ }
+
+ g_mutex_lock(&jaw_hyperlink->mutex);
+
+ if (jaw_hyperlink->jstrUri != NULL) {
+ if (jaw_hyperlink->uri != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_hyperlink->jstrUri,
+ jaw_hyperlink->uri);
+ jaw_hyperlink->uri = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_hyperlink->jstrUri);
+ jaw_hyperlink->jstrUri = NULL;
+ }
+
+ if (jaw_hyperlink->jhyperlink != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_hyperlink->jhyperlink);
+ jaw_hyperlink->jhyperlink = NULL;
+ }
+
+ g_mutex_unlock(&jaw_hyperlink->mutex);
+ g_mutex_clear(&jaw_hyperlink->mutex);
+
+ /* Chain up to parent's finalize */
+ G_OBJECT_CLASS(jaw_hyperlink_parent_class)->finalize(gobject);
+}
+
+/**
+ * jaw_hyperlink_get_uri:
+ * @link_: an #AtkHyperlink
+ * @i: a (zero-index) integer specifying the desired anchor
+ *
+ * Get a the URI associated with the anchor specified
+ * by @i of @link_.
+ *
+ * Multiple anchors are primarily used by client-side image maps.
+ *
+ * Returns: a string specifying the URI
+ **/
+static gchar *jaw_hyperlink_get_uri(AtkHyperlink *atk_hyperlink, gint i) {
+ JAW_DEBUG("%p, %d", atk_hyperlink, i);
+
+ if (atk_hyperlink == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_HYPERLINK(atk_hyperlink,
+ NULL); // create local JNI reference `jobject jhyperlink`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_hyperlink_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ jstring jstr = (*jniEnv)->CallObjectMethod(
+ jniEnv, jhyperlink, cachedHyperlinkGetUriMethod, (jint)i);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ g_mutex_lock(&jaw_hyperlink->mutex);
+
+ if (jaw_hyperlink->jstrUri != NULL) {
+ if (jaw_hyperlink->uri != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_hyperlink->jstrUri,
+ jaw_hyperlink->uri);
+ jaw_hyperlink->uri = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_hyperlink->jstrUri);
+ jaw_hyperlink->jstrUri = NULL;
+ }
+
+ jaw_hyperlink->jstrUri = (*jniEnv)->NewGlobalRef(jniEnv, jstr);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jaw_hyperlink->jstrUri == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_mutex_unlock(&jaw_hyperlink->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ jaw_hyperlink->uri = (gchar *)(*jniEnv)->GetStringUTFChars(
+ jniEnv, jaw_hyperlink->jstrUri, NULL);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jaw_hyperlink->uri == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ // jaw_hyperlink->jstrUri != NULL
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_hyperlink->jstrUri);
+ jaw_hyperlink->jstrUri = NULL;
+
+ g_mutex_unlock(&jaw_hyperlink->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ gchar *result = jaw_hyperlink->uri;
+ g_mutex_unlock(&jaw_hyperlink->mutex);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return result;
+}
+
+/**
+ * jaw_hyperlink_get_object:
+ * @atk_hyperlink: an #AtkHyperlink
+ * @i: a (zero-index) integer specifying the desired anchor
+ *
+ * Returns the item associated with this hyperlinks nth anchor.
+ *
+ * Returns: (transfer none): an #AtkObject associated with this hyperlinks
+ * i-th anchor
+ **/
+static AtkObject *jaw_hyperlink_get_object(AtkHyperlink *atk_hyperlink,
+ gint i) {
+ JAW_DEBUG("%p, %d", atk_hyperlink, i);
+
+ if (atk_hyperlink == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_HYPERLINK(atk_hyperlink, NULL);
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_hyperlink_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ jobject ac = (*jniEnv)->CallObjectMethod(
+ jniEnv, jhyperlink, cachedHyperlinkGetObjectMethod, (jint)i);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || ac == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ AtkObject *obj = (AtkObject *)jaw_impl_find_instance(jniEnv, ac);
+ if (obj == NULL) {
+ g_warning("%s: No AtkObject found for AccessibleContext", G_STRFUNC);
+ }
+
+ // From documentation of the `atk_hyperlink_get_object`:
+ // The returned data is owned by the instance (transfer none annotation), so
+ // we don't ref the obj before returning it
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return obj;
+}
+
+/**
+ * atk_hyperlink_get_end_index:
+ * @link_: an #AtkHyperlink
+ *
+ * Gets the index with the hypertext document at which this link ends.
+ *
+ * Returns: the index with the hypertext document at which this link ends, 0 if
+ *an error happened.
+ **/
+static gint jaw_hyperlink_get_end_index(AtkHyperlink *atk_hyperlink) {
+ JAW_DEBUG("%p", atk_hyperlink);
+
+ if (atk_hyperlink == NULL) {
+ g_warning("%s: Null argument atk_hyperlink passed to the function",
+ G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_HYPERLINK(atk_hyperlink, 0);
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return 0;
+ }
+
+ if (!jaw_hyperlink_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ jint jindex = (*jniEnv)->CallIntMethod(jniEnv, jhyperlink,
+ cachedHyperlinkGetEndIndexMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return jindex;
+}
+
+/**
+ * jaw_hyperlink_get_start_index:
+ * @link_: an #AtkHyperlink
+ *
+ * Gets the index with the hypertext document at which this link begins.
+ *
+ * Returns: the index with the hypertext document at which this link begins, 0
+ * if an error happened
+ **/
+static gint jaw_hyperlink_get_start_index(AtkHyperlink *atk_hyperlink) {
+ JAW_DEBUG("%p", atk_hyperlink);
+
+ if (atk_hyperlink == NULL) {
+ g_warning("%s: Null argument atk_hyperlink passed to the function",
+ G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_HYPERLINK(atk_hyperlink, 0);
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return 0;
+ }
+
+ if (!jaw_hyperlink_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ jint jindex = (*jniEnv)->CallIntMethod(jniEnv, jhyperlink,
+ cachedHyperlinkGetStartIndexMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return jindex;
+}
+
+/**
+ * jaw_hyperlink_is_valid:
+ * @link_: an #AtkHyperlink
+ *
+ * Since the document that a link is associated with may have changed
+ * this method returns %TRUE if the link is still valid (with
+ * respect to the document it references) and %FALSE otherwise.
+ *
+ * Returns: whether or not this link is still valid
+ **/
+static gboolean jaw_hyperlink_is_valid(AtkHyperlink *atk_hyperlink) {
+ JAW_DEBUG("%p", atk_hyperlink);
+
+ if (atk_hyperlink == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_HYPERLINK(atk_hyperlink, FALSE);
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return FALSE;
+ }
+
+ if (!jaw_hyperlink_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return FALSE;
+ }
+
+ jboolean jvalid = (*jniEnv)->CallBooleanMethod(
+ jniEnv, jhyperlink, cachedHyperlinkIsValidMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return jvalid;
+}
+
+/**
+ * jaw_hyperlink_get_n_anchors:
+ * @link_: an #AtkHyperlink
+ *
+ * Gets the number of anchors associated with this hyperlink.
+ *
+ * Returns: the number of anchors associated with this hyperlink
+ **/
+static gint jaw_hyperlink_get_n_anchors(AtkHyperlink *atk_hyperlink) {
+ JAW_DEBUG("%p", atk_hyperlink);
+
+ if (atk_hyperlink == NULL) {
+ g_warning("%s: Null argument atk_hyperlink passed to the function",
+ G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_HYPERLINK(atk_hyperlink, 0);
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return 0;
+ }
+
+ if (!jaw_hyperlink_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ jint janchors = (*jniEnv)->CallIntMethod(jniEnv, jhyperlink,
+ cachedHyperlinkGetNAnchorsMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jhyperlink);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return janchors;
+}
+
+static gboolean jaw_hyperlink_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localClass =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkHyperlink");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkHyperlink class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedHyperlinkAtkHyperlinkClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClass);
+
+ if (cachedHyperlinkAtkHyperlinkClass == NULL) {
+ g_warning(
+ "%s: Failed to create global reference for AtkHyperlink class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedHyperlinkGetUriMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedHyperlinkAtkHyperlinkClass,
+ "get_uri", "(I)Ljava/lang/String;");
+
+ cachedHyperlinkGetObjectMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedHyperlinkAtkHyperlinkClass, "get_object",
+ "(I)Ljavax/accessibility/AccessibleContext;");
+
+ cachedHyperlinkGetEndIndexMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedHyperlinkAtkHyperlinkClass, "get_end_index", "()I");
+
+ cachedHyperlinkGetStartIndexMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedHyperlinkAtkHyperlinkClass, "get_start_index", "()I");
+
+ cachedHyperlinkIsValidMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedHyperlinkAtkHyperlinkClass, "is_valid", "()Z");
+
+ cachedHyperlinkGetNAnchorsMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedHyperlinkAtkHyperlinkClass, "get_n_anchors", "()I");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedHyperlinkGetUriMethod == NULL ||
+ cachedHyperlinkGetObjectMethod == NULL ||
+ cachedHyperlinkGetEndIndexMethod == NULL ||
+ cachedHyperlinkGetStartIndexMethod == NULL ||
+ cachedHyperlinkIsValidMethod == NULL ||
+ cachedHyperlinkGetNAnchorsMethod == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache one or more AtkHyperlink method IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedHyperlinkAtkHyperlinkClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedHyperlinkAtkHyperlinkClass);
+ cachedHyperlinkAtkHyperlinkClass = NULL;
+ }
+ cachedHyperlinkGetUriMethod = NULL;
+ cachedHyperlinkGetObjectMethod = NULL;
+ cachedHyperlinkGetEndIndexMethod = NULL;
+ cachedHyperlinkGetStartIndexMethod = NULL;
+ cachedHyperlinkIsValidMethod = NULL;
+ cachedHyperlinkGetNAnchorsMethod = NULL;
+
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+}
+
+void jaw_hyperlink_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedHyperlinkAtkHyperlinkClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedHyperlinkAtkHyperlinkClass);
+ cachedHyperlinkAtkHyperlinkClass = NULL;
+ }
+ cachedHyperlinkGetUriMethod = NULL;
+ cachedHyperlinkGetObjectMethod = NULL;
+ cachedHyperlinkGetEndIndexMethod = NULL;
+ cachedHyperlinkGetStartIndexMethod = NULL;
+ cachedHyperlinkIsValidMethod = NULL;
+ cachedHyperlinkGetNAnchorsMethod = NULL;
+ cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawhyperlink.h b/src/jdk.accessibility/linux/native/libatk-wrapper/jawhyperlink.h
new file mode 100644
index 000000000000..2f9fe119ea48
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawhyperlink.h
@@ -0,0 +1,62 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _JAW_HYPERLINK_H_
+#define _JAW_HYPERLINK_H_
+
+#include
+#include
+
+G_BEGIN_DECLS
+
+#define JAW_TYPE_HYPERLINK (jaw_hyperlink_get_type())
+#define JAW_HYPERLINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), JAW_TYPE_HYPERLINK, JawHyperlink))
+#define JAW_HYPERLINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), JAW_TYPE_HYPERLINK, JawHyperlinkClass))
+#define JAW_IS_HYPERLINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), JAW_TYPE_HYPERLINK))
+#define JAW_IS_HYPERLINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), JAW_TYPE_HYPERLINK))
+#define JAW_HYPERLINK_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), JAW_TYPE_HYPERLINK, JawHyperlinkClass))
+
+typedef struct _JawHyperlink JawHyperlink;
+typedef struct _JawHyperlinkClass JawHyperlinkClass;
+
+struct _JawHyperlink {
+ AtkHyperlink parent;
+ jobject jhyperlink;
+
+ jstring jstrUri;
+ gchar *uri;
+ GMutex mutex;
+};
+
+GType jaw_hyperlink_get_type(void);
+
+struct _JawHyperlinkClass {
+ AtkHyperlinkClass parent_class;
+};
+
+JawHyperlink *jaw_hyperlink_new(jobject jhyperlink);
+
+G_END_DECLS
+
+#endif
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawhypertext.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawhypertext.c
new file mode 100644
index 000000000000..08114adc0a61
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawhypertext.c
@@ -0,0 +1,397 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawcache.h"
+#include "jawhyperlink.h"
+#include "jawimpl.h"
+#include "jawutil.h"
+#include
+#include
+
+/**
+ * (From Atk documentation)
+ *
+ * AtkHypertext:
+ *
+ * The ATK interface which provides standard mechanism for manipulating
+ * hyperlinks.
+ *
+ * An interface used for objects which implement linking between
+ * multiple resource or content locations, or multiple 'markers'
+ * within a single document. A Hypertext instance is associated with
+ * one or more Hyperlinks, which are associated with particular
+ * offsets within the Hypertext's included content. While this
+ * interface is derived from Text, there is no requirement that
+ * Hypertext instances have textual content; they may implement Image
+ * as well, and Hyperlinks need not have non-zero text offsets.
+ */
+
+static jclass cachedHypertextAtkHypertextClass = NULL;
+static jmethodID cachedHypertextCreateAtkHypertextMethod = NULL;
+static jmethodID cachedHypertextGetLinkMethod = NULL;
+static jmethodID cachedHypertextGetNLinksMethod = NULL;
+static jmethodID cachedHypertextGetLinkIndexMethod = NULL;
+
+static GMutex cache_mutex;
+static gboolean cache_initialized = FALSE;
+
+static gboolean jaw_hypertext_init_jni_cache(JNIEnv *jniEnv);
+
+static AtkHyperlink *jaw_hypertext_get_link(AtkHypertext *hypertext,
+ gint link_index);
+static gint jaw_hypertext_get_n_links(AtkHypertext *hypertext);
+static gint jaw_hypertext_get_link_index(AtkHypertext *hypertext,
+ gint char_index);
+
+typedef struct _HypertextData {
+ jobject atk_hypertext;
+} HypertextData;
+
+#define JAW_GET_HYPERTEXT(hypertext, def_ret) \
+ JAW_GET_OBJ_IFACE( \
+ hypertext, org_GNOME_Accessibility_AtkInterface_INTERFACE_HYPERTEXT, \
+ HypertextData, atk_hypertext, jniEnv, atk_hypertext, def_ret)
+
+/**
+ * AtkHypertextIface:
+ * @get_link:
+ * @get_n_links:
+ * @get_link_index:
+ **/
+void jaw_hypertext_interface_init(AtkHypertextIface *iface, gpointer data) {
+ if (iface == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ iface->get_link = jaw_hypertext_get_link;
+ iface->get_n_links = jaw_hypertext_get_n_links;
+ iface->get_link_index = jaw_hypertext_get_link_index;
+}
+
+gpointer jaw_hypertext_data_init(jobject ac) {
+ JAW_DEBUG("%p", ac);
+
+ if (ac == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_hypertext_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jatk_hypertext = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedHypertextAtkHypertextClass,
+ cachedHypertextCreateAtkHypertextMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_hypertext == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jatk_hypertext using "
+ "create_atk_hypertext method",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ HypertextData *data = g_new0(HypertextData, 1);
+ data->atk_hypertext = (*jniEnv)->NewGlobalRef(jniEnv, jatk_hypertext);
+ if (data->atk_hypertext == NULL) {
+ g_warning("%s: Failed to create global ref for atk_hypertext",
+ G_STRFUNC);
+ g_free(data);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data;
+}
+
+void jaw_hypertext_data_finalize(gpointer p) {
+ JAW_DEBUG("%p", p);
+
+ if (p == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ HypertextData *data = (HypertextData *)p;
+ if (data == NULL) {
+ g_warning("%s: data is null after cast", G_STRFUNC);
+ return;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+
+ if (jniEnv == NULL) {
+ g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC);
+ } else {
+ if (data->atk_hypertext != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_hypertext);
+ data->atk_hypertext = NULL;
+ }
+ }
+
+ g_free(data);
+}
+
+/**
+ * jaw_hypertext_get_link:
+ * @hypertext: an #AtkHypertext
+ * @link_index: an integer specifying the desired link
+ *
+ * Gets the link in this hypertext document at index
+ * @link_index
+ *
+ * Returns: (transfer none): the link in this hypertext document at
+ * index @link_index
+ **/
+static AtkHyperlink *jaw_hypertext_get_link(AtkHypertext *hypertext,
+ gint link_index) {
+ JAW_DEBUG("%p, %d", hypertext, link_index);
+
+ if (hypertext == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_HYPERTEXT(
+ hypertext, NULL); // create local JNI reference `jobject atk_hypertext`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jhyperlink = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_hypertext, cachedHypertextGetLinkMethod, (jint)link_index);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jhyperlink == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jhyperlink using get_link method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ JawHyperlink *jaw_hyperlink = jaw_hyperlink_new(jhyperlink);
+ if (jaw_hyperlink == NULL) {
+ g_warning("%s: Failed to create JawHyperlink object for link_index %d",
+ G_STRFUNC, link_index);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return ATK_HYPERLINK(jaw_hyperlink);
+}
+
+/**
+ * jaw_hypertext_get_n_links:
+ * @hypertext: an #AtkHypertext
+ *
+ * Gets the number of links within this hypertext document.
+ *
+ * Returns: the number of links within this hypertext document
+ **/
+static gint jaw_hypertext_get_n_links(AtkHypertext *hypertext) {
+ JAW_DEBUG("%p", hypertext);
+
+ if (hypertext == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_HYPERTEXT(hypertext,
+ 0); // create local JNI reference `jobject atk_hypertext`
+
+ gint ret = (gint)(*jniEnv)->CallIntMethod(jniEnv, atk_hypertext,
+ cachedHypertextGetNLinksMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext);
+
+ return ret;
+}
+
+/**
+ * jaw_hypertext_get_link_index:
+ * @hypertext: an #AtkHypertext
+ * @char_index: a character index
+ *
+ * Gets the index into the array of hyperlinks that is associated with
+ * the character specified by @char_index.
+ *
+ * Returns: an index into the array of hyperlinks in @hypertext,
+ * or -1 if there is no hyperlink associated with this character.
+ **/
+static gint jaw_hypertext_get_link_index(AtkHypertext *hypertext,
+ gint char_index) {
+ JAW_DEBUG("%p, %d", hypertext, char_index);
+
+ if (hypertext == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return -1;
+ }
+
+ JAW_GET_HYPERTEXT(hypertext,
+ -1); // create local JNI reference `jobject atk_hypertext`
+
+ gint ret = (gint)(*jniEnv)->CallIntMethod(jniEnv, atk_hypertext,
+ cachedHypertextGetLinkIndexMethod,
+ (jint)char_index);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext);
+ return -1;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_hypertext);
+
+ return ret;
+}
+
+static gboolean jaw_hypertext_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localClass =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkHypertext");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkHypertext class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedHypertextAtkHypertextClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClass);
+
+ if (cachedHypertextAtkHypertextClass == NULL) {
+ g_warning(
+ "%s: Failed to create global reference for AtkHypertext class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedHypertextCreateAtkHypertextMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedHypertextAtkHypertextClass, "create_atk_hypertext",
+ "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/"
+ "AtkHypertext;");
+
+ cachedHypertextGetLinkMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedHypertextAtkHypertextClass, "get_link",
+ "(I)Lorg/GNOME/Accessibility/AtkHyperlink;");
+
+ cachedHypertextGetNLinksMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedHypertextAtkHypertextClass, "get_n_links", "()I");
+
+ cachedHypertextGetLinkIndexMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedHypertextAtkHypertextClass, "get_link_index", "(I)I");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedHypertextCreateAtkHypertextMethod == NULL ||
+ cachedHypertextGetLinkMethod == NULL ||
+ cachedHypertextGetNLinksMethod == NULL ||
+ cachedHypertextGetLinkIndexMethod == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache one or more AtkHypertext method IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedHypertextAtkHypertextClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedHypertextAtkHypertextClass);
+ cachedHypertextAtkHypertextClass = NULL;
+ }
+ cachedHypertextCreateAtkHypertextMethod = NULL;
+ cachedHypertextGetLinkMethod = NULL;
+ cachedHypertextGetNLinksMethod = NULL;
+ cachedHypertextGetLinkIndexMethod = NULL;
+
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+}
+
+void jaw_hypertext_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedHypertextAtkHypertextClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedHypertextAtkHypertextClass);
+ cachedHypertextAtkHypertextClass = NULL;
+ }
+ cachedHypertextCreateAtkHypertextMethod = NULL;
+ cachedHypertextGetLinkMethod = NULL;
+ cachedHypertextGetNLinksMethod = NULL;
+ cachedHypertextGetLinkIndexMethod = NULL;
+ cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawimage.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawimage.c
new file mode 100644
index 000000000000..ad0875f49a73
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawimage.c
@@ -0,0 +1,577 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawcache.h"
+#include "jawimpl.h"
+#include "jawutil.h"
+#include
+#include
+
+/**
+ * (From Atk documentation)
+ *
+ * AtkImage:
+ *
+ * The ATK Interface implemented by components
+ * which expose image or pixmap content on-screen.
+ *
+ * #AtkImage should be implemented by #AtkObject subtypes on behalf of
+ * components which display image/pixmap information onscreen, and
+ * which provide information (other than just widget borders, etc.)
+ * via that image content. For instance, icons, buttons with icons,
+ * toolbar elements, and image viewing panes typically should
+ * implement #AtkImage.
+ *
+ * #AtkImage primarily provides two types of information: coordinate
+ * information (useful for screen review mode of screenreaders, and
+ * for use by onscreen magnifiers), and descriptive information. The
+ * descriptive information is provided for alternative, text-only
+ * presentation of the most significant information present in the
+ * image.
+ */
+
+static jclass cachedImageAtkImageClass = NULL;
+static jmethodID cachedImageCreateAtkImageMethod = NULL;
+static jmethodID cachedImageGetImagePositionMethod = NULL;
+static jmethodID cachedImageGetImageDescriptionMethod = NULL;
+static jmethodID cachedImageGetImageSizeMethod = NULL;
+static jclass cachedImagePointClass = NULL;
+static jfieldID cachedImagePointXFieldID = NULL;
+static jfieldID cachedImagePointYFieldID = NULL;
+static jclass cachedImageDimensionClass = NULL;
+static jfieldID cachedImageDimensionWidthFieldID = NULL;
+static jfieldID cachedImageDimensionHeightFieldID = NULL;
+
+static GMutex cache_mutex;
+static gboolean cache_initialized = FALSE;
+
+static gboolean jaw_image_init_jni_cache(JNIEnv *jniEnv);
+
+static void jaw_image_get_image_position(AtkImage *image, gint *x, gint *y,
+ AtkCoordType coord_type);
+static const gchar *jaw_image_get_image_description(AtkImage *image);
+static void jaw_image_get_image_size(AtkImage *image, gint *width,
+ gint *height);
+
+typedef struct _ImageData {
+ jobject atk_image;
+ const gchar *image_description;
+ jstring jstrImageDescription;
+ GMutex mutex;
+} ImageData;
+
+#define JAW_GET_IMAGE(image, def_ret) \
+ JAW_GET_OBJ_IFACE(image, \
+ org_GNOME_Accessibility_AtkInterface_INTERFACE_IMAGE, \
+ ImageData, atk_image, jniEnv, atk_image, def_ret)
+
+/**
+ * AtkImageIface:
+ * @get_image_position:
+ * @get_image_description:
+ * @get_image_size
+ * @set_image_description:
+ * @get_image_locale:
+ **/
+void jaw_image_interface_init(AtkImageIface *iface, gpointer data) {
+ JAW_DEBUG("%p, %p", iface, data);
+
+ if (iface == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ iface->get_image_position = jaw_image_get_image_position;
+ iface->get_image_description = jaw_image_get_image_description;
+ iface->get_image_size = jaw_image_get_image_size;
+ iface->set_image_description = NULL; // TODO: iface->set_image_description
+ iface->get_image_locale = NULL; // TODO: iface->get_image_locale from
+ // AccessibleContext.getLocale()
+}
+
+gpointer jaw_image_data_init(jobject ac) {
+ JAW_DEBUG("%p", ac);
+
+ if (ac == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_image_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ jobject jatk_image = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedImageAtkImageClass, cachedImageCreateAtkImageMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_image == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning(
+ "%s: Failed to create jatk_image using create_atk_image method",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ ImageData *data = g_new0(ImageData, 1);
+ g_mutex_init(&data->mutex);
+ data->atk_image = (*jniEnv)->NewGlobalRef(jniEnv, jatk_image);
+ if (data->atk_image == NULL) {
+ g_warning("%s: Failed to create global ref for atk_image", G_STRFUNC);
+ g_mutex_clear(&data->mutex);
+ g_free(data);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data;
+}
+
+void jaw_image_data_finalize(gpointer p) {
+ JAW_DEBUG("%p", p);
+
+ if (p == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ ImageData *data = (ImageData *)p;
+ if (data == NULL) {
+ g_warning("%s: data is null after cast", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&data->mutex);
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+
+ if (jniEnv == NULL) {
+ g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC);
+ } else {
+ if (data->jstrImageDescription != NULL) {
+ if (data->image_description != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv,
+ data->jstrImageDescription,
+ data->image_description);
+ data->image_description = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrImageDescription);
+ data->jstrImageDescription = NULL;
+ }
+
+ if (data->atk_image != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_image);
+ data->atk_image = NULL;
+ }
+ }
+
+ g_mutex_unlock(&data->mutex);
+ g_mutex_clear(&data->mutex);
+ g_free(data);
+}
+
+/**
+ * jaw_image_get_image_position:
+ * @image: a #GObject instance that implements AtkImageIface
+ * @x: (out) (optional): address of #gint to put x coordinate position;
+ *otherwise, -1 if value cannot be obtained.
+ * @y: (out) (optional): address of #gint to put y coordinate position;
+ *otherwise, -1 if value cannot be obtained.
+ * @coord_type: specifies whether the coordinates are relative to the screen
+ * or to the components top level window
+ *
+ * Gets the position of the image in the form of a point specifying the
+ * images top-left corner.
+ *
+ * If the position can not be obtained (e.g. missing support), x and y are set
+ * to -1.
+ **/
+static void jaw_image_get_image_position(AtkImage *image, gint *x, gint *y,
+ AtkCoordType coord_type) {
+ JAW_DEBUG("%p, %p, %p, %d", image, x, y, coord_type);
+
+ if (image == NULL || x == NULL || y == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_IMAGE(image, ); // create local JNI reference `jobject atk_image`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_image);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ (*x) = -1;
+ (*y) = -1;
+
+ jobject jpoint = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_image, cachedImageGetImagePositionMethod, (jint)coord_type);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jpoint == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jpoint using get_image_position method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_image);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*x) =
+ (gint)(*jniEnv)->GetIntField(jniEnv, jpoint, cachedImagePointXFieldID);
+ (*y) =
+ (gint)(*jniEnv)->GetIntField(jniEnv, jpoint, cachedImagePointYFieldID);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_image);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * jaw_image_get_image_description:
+ * @image: a #GObject instance that implements AtkImageIface
+ *
+ * Get a textual description of this image.
+ *
+ * Returns: a string representing the image description or NULL
+ **/
+static const gchar *jaw_image_get_image_description(AtkImage *image) {
+ JAW_DEBUG("%p", image);
+
+ if (image == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_IMAGE(image,
+ NULL); // create local JNI reference `jobject atk_image`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_image);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jstring jstr = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_image, cachedImageGetImageDescriptionMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning(
+ "%s: Failed to create jstr using get_image_description method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_image);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ g_mutex_lock(&data->mutex);
+ if (data->jstrImageDescription != NULL) {
+ if (data->image_description != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, data->jstrImageDescription,
+ data->image_description);
+ data->image_description = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrImageDescription);
+ data->jstrImageDescription = NULL;
+ }
+
+ data->jstrImageDescription = (*jniEnv)->NewGlobalRef(jniEnv, jstr);
+ if (data->jstrImageDescription == NULL) {
+ g_mutex_unlock(&data->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_image);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ data->image_description =
+ (*jniEnv)->GetStringUTFChars(jniEnv, data->jstrImageDescription, NULL);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || data->image_description == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ // data->jstrImageDescription != NULL
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrImageDescription);
+ data->jstrImageDescription = NULL;
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_image);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ g_mutex_unlock(&data->mutex);
+ return NULL;
+ }
+
+ g_mutex_unlock(&data->mutex);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_image);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data->image_description;
+}
+
+/**
+ * jaw_image_get_image_size:
+ * @image: a #GObject instance that implements AtkImageIface
+ * @width: (out) (optional): filled with the image width, or -1 if the value
+ *cannot be obtained.
+ * @height: (out) (optional): filled with the image height, or -1 if the value
+ *cannot be obtained.
+ *
+ * Get the width and height in pixels for the specified image.
+ * The values of @width and @height are returned as -1 if the
+ * values cannot be obtained (for instance, if the object is not onscreen).
+ *
+ * If the size can not be obtained (e.g. missing support), x and y are set
+ * to -1.
+ **/
+
+static void jaw_image_get_image_size(AtkImage *image, gint *width,
+ gint *height) {
+ JAW_DEBUG("%p, %p, %p", image, width, height);
+
+ if (image == NULL || width == NULL || height == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_IMAGE(image, ); // create local JNI reference `jobject atk_image`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_image);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ (*width) = -1;
+ (*height) = -1;
+
+ jobject jdimension = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_image, cachedImageGetImageSizeMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jdimension == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jdimension using get_image_size method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_image);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_image);
+
+ (*width) = (gint)(*jniEnv)->GetIntField(jniEnv, jdimension,
+ cachedImageDimensionWidthFieldID);
+ (*height) = (gint)(*jniEnv)->GetIntField(jniEnv, jdimension,
+ cachedImageDimensionHeightFieldID);
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+static gboolean jaw_image_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localClass =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkImage");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkImage class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedImageAtkImageClass = (*jniEnv)->NewGlobalRef(jniEnv, localClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClass);
+
+ if (cachedImageAtkImageClass == NULL) {
+ g_warning("%s: Failed to create global reference for AtkImage class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedImageCreateAtkImageMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedImageAtkImageClass, "create_atk_image",
+ "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/"
+ "AtkImage;");
+
+ cachedImageGetImagePositionMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedImageAtkImageClass,
+ "get_image_position", "(I)Ljava/awt/Point;");
+
+ cachedImageGetImageDescriptionMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedImageAtkImageClass,
+ "get_image_description", "()Ljava/lang/String;");
+
+ cachedImageGetImageSizeMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedImageAtkImageClass,
+ "get_image_size", "()Ljava/awt/Dimension;");
+
+ jclass localPointClass = (*jniEnv)->FindClass(jniEnv, "java/awt/Point");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localPointClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find Point class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedImagePointClass = (*jniEnv)->NewGlobalRef(jniEnv, localPointClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localPointClass);
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || cachedImagePointClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create global reference for Point class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedImagePointXFieldID =
+ (*jniEnv)->GetFieldID(jniEnv, cachedImagePointClass, "x", "I");
+ cachedImagePointYFieldID =
+ (*jniEnv)->GetFieldID(jniEnv, cachedImagePointClass, "y", "I");
+
+ jclass localDimensionClass =
+ (*jniEnv)->FindClass(jniEnv, "java/awt/Dimension");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localDimensionClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find Dimension class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedImageDimensionClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localDimensionClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localDimensionClass);
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedImageDimensionClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create global reference for Dimension class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedImageDimensionWidthFieldID =
+ (*jniEnv)->GetFieldID(jniEnv, cachedImageDimensionClass, "width", "I");
+ cachedImageDimensionHeightFieldID =
+ (*jniEnv)->GetFieldID(jniEnv, cachedImageDimensionClass, "height", "I");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedImageCreateAtkImageMethod == NULL ||
+ cachedImageGetImagePositionMethod == NULL ||
+ cachedImageGetImageDescriptionMethod == NULL ||
+ cachedImageGetImageSizeMethod == NULL ||
+ cachedImagePointXFieldID == NULL || cachedImagePointYFieldID == NULL ||
+ cachedImageDimensionWidthFieldID == NULL ||
+ cachedImageDimensionHeightFieldID == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning(
+ "%s: Failed to cache one or more AtkImage method IDs or field IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedImageAtkImageClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImageAtkImageClass);
+ cachedImageAtkImageClass = NULL;
+ }
+ if (cachedImagePointClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImagePointClass);
+ cachedImagePointClass = NULL;
+ }
+ if (cachedImageDimensionClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImageDimensionClass);
+ cachedImageDimensionClass = NULL;
+ }
+ cachedImageCreateAtkImageMethod = NULL;
+ cachedImageGetImagePositionMethod = NULL;
+ cachedImageGetImageDescriptionMethod = NULL;
+ cachedImageGetImageSizeMethod = NULL;
+ cachedImagePointXFieldID = NULL;
+ cachedImagePointYFieldID = NULL;
+ cachedImageDimensionWidthFieldID = NULL;
+ cachedImageDimensionHeightFieldID = NULL;
+
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return FALSE;
+}
+
+void jaw_image_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedImageAtkImageClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImageAtkImageClass);
+ cachedImageAtkImageClass = NULL;
+ }
+ if (cachedImagePointClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImagePointClass);
+ cachedImagePointClass = NULL;
+ }
+ if (cachedImageDimensionClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImageDimensionClass);
+ cachedImageDimensionClass = NULL;
+ }
+ cachedImageCreateAtkImageMethod = NULL;
+ cachedImageGetImagePositionMethod = NULL;
+ cachedImageGetImageDescriptionMethod = NULL;
+ cachedImageGetImageSizeMethod = NULL;
+ cachedImagePointXFieldID = NULL;
+ cachedImagePointYFieldID = NULL;
+ cachedImageDimensionWidthFieldID = NULL;
+ cachedImageDimensionHeightFieldID = NULL;
+ cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawimpl.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawimpl.c
new file mode 100644
index 000000000000..fb4da258425f
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawimpl.c
@@ -0,0 +1,891 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems Inc.
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawimpl.h"
+#include "jawobject.h"
+#include "jawtoplevel.h"
+#include "jawutil.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static void jaw_impl_class_init(JawImplClass *klass);
+static void jaw_impl_dispose(GObject *gobject);
+static void jaw_impl_finalize(GObject *gobject);
+static gpointer jaw_impl_get_interface_data(JawObject *jaw_obj, guint iface);
+static void jaw_impl_initialize(AtkObject *atk_obj, gpointer data);
+
+typedef struct _JawInterfaceInfo {
+ void (*finalize)(gpointer);
+ gpointer data;
+} JawInterfaceInfo;
+
+static gpointer jaw_impl_parent_class = NULL;
+
+static GMutex typeTableMutex;
+static GHashTable *typeTable = NULL;
+
+static jclass cachedImplAtkWrapperDisposerClass = NULL;
+static jmethodID cachedImplGetResourceMethod = NULL;
+static jclass cachedImplAtkWrapperClass = NULL;
+static jmethodID cachedImplRegisterPropertyChangeListenerMethod = NULL;
+static jclass cachedImplAccessibleRelationClass = NULL;
+static jfieldID cachedImplChildNodeOfFieldID = NULL;
+static jfieldID cachedImplControlledByFieldID = NULL;
+static jfieldID cachedImplControllerForFieldID = NULL;
+static jfieldID cachedImplEmbeddedByFieldID = NULL;
+static jfieldID cachedImplEmbedsFieldID = NULL;
+static jfieldID cachedImplFlowsFromFieldID = NULL;
+static jfieldID cachedImplFlowsToFieldID = NULL;
+static jfieldID cachedImplLabelForFieldID = NULL;
+static jfieldID cachedImplLabeledByFieldID = NULL;
+static jfieldID cachedImplMemberOfFieldID = NULL;
+static jfieldID cachedImplParentWindowOfFieldID = NULL;
+static jfieldID cachedImplSubwindowOfFieldID = NULL;
+
+static GMutex cache_mutex;
+static gboolean impl_cache_initialized = FALSE;
+
+static gboolean jaw_impl_init_jni_cache(JNIEnv *jniEnv);
+
+static void aggregate_interface(JNIEnv *jniEnv, JawObject *jaw_obj,
+ guint tflag) {
+ JAW_DEBUG("%p, %p, %u", jniEnv, jaw_obj, tflag);
+
+ if (!jniEnv || !jaw_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JawImpl *jaw_impl = JAW_IMPL(tflag, jaw_obj);
+ if (jaw_impl == NULL) {
+ g_warning("%s: jaw_impl is NULL", G_STRFUNC);
+ return;
+ }
+
+ jaw_impl->tflag = tflag;
+
+ jobject ac = (*jniEnv)->NewGlobalRef(jniEnv, jaw_obj->acc_context);
+ if (ac == NULL) {
+ g_warning("%s: ac is NULL", G_STRFUNC);
+ return;
+ }
+
+ jaw_impl->ifaceTable = g_hash_table_new(NULL, NULL);
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_ACTION) {
+ JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1);
+ info->data = jaw_action_data_init(ac);
+ info->finalize = jaw_action_data_finalize;
+ g_hash_table_insert(
+ jaw_impl->ifaceTable,
+ (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_ACTION,
+ (gpointer)info);
+ }
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_COMPONENT) {
+ JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1);
+ info->data = jaw_component_data_init(ac);
+ info->finalize = jaw_component_data_finalize;
+ g_hash_table_insert(
+ jaw_impl->ifaceTable,
+ (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_COMPONENT,
+ (gpointer)info);
+ }
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_TEXT) {
+ JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1);
+ info->data = jaw_text_data_init(ac);
+ info->finalize = jaw_text_data_finalize;
+ g_hash_table_insert(
+ jaw_impl->ifaceTable,
+ (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_TEXT,
+ (gpointer)info);
+ }
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_EDITABLE_TEXT) {
+ JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1);
+ info->data = jaw_editable_text_data_init(ac);
+ info->finalize = jaw_editable_text_data_finalize;
+ g_hash_table_insert(
+ jaw_impl->ifaceTable,
+ (gpointer)
+ org_GNOME_Accessibility_AtkInterface_INTERFACE_EDITABLE_TEXT,
+ (gpointer)info);
+ }
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_HYPERTEXT) {
+ JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1);
+ info->data = jaw_hypertext_data_init(ac);
+ info->finalize = jaw_hypertext_data_finalize;
+ g_hash_table_insert(
+ jaw_impl->ifaceTable,
+ (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_HYPERTEXT,
+ (gpointer)info);
+ }
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_IMAGE) {
+ JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1);
+ info->data = jaw_image_data_init(ac);
+ info->finalize = jaw_image_data_finalize;
+ g_hash_table_insert(
+ jaw_impl->ifaceTable,
+ (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_IMAGE,
+ (gpointer)info);
+ }
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_SELECTION) {
+ JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1);
+ info->data = jaw_selection_data_init(ac);
+ info->finalize = jaw_selection_data_finalize;
+ g_hash_table_insert(
+ jaw_impl->ifaceTable,
+ (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_SELECTION,
+ (gpointer)info);
+ }
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_VALUE) {
+ JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1);
+ info->data = jaw_value_data_init(ac);
+ info->finalize = jaw_value_data_finalize;
+ g_hash_table_insert(
+ jaw_impl->ifaceTable,
+ (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_VALUE,
+ (gpointer)info);
+ }
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE) {
+ JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1);
+ info->data = jaw_table_data_init(ac);
+ info->finalize = jaw_table_data_finalize;
+ g_hash_table_insert(
+ jaw_impl->ifaceTable,
+ (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE,
+ (gpointer)info);
+ }
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE_CELL) {
+ JawInterfaceInfo *info = g_new(JawInterfaceInfo, 1);
+ info->data = jaw_table_cell_data_init(ac);
+ info->finalize = jaw_table_cell_data_finalize;
+ g_hash_table_insert(
+ jaw_impl->ifaceTable,
+ (gpointer)org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE_CELL,
+ (gpointer)info);
+ }
+
+ (*jniEnv)->DeleteGlobalRef(jniEnv, ac);
+}
+
+JawImpl *jaw_impl_create_instance(JNIEnv *jniEnv, jobject ac) {
+ JAW_DEBUG("%p, %p", jniEnv, ac);
+
+ if (!ac || !jniEnv) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JawImpl *jaw_impl;
+
+ guint tflag = jaw_util_get_tflag_from_jobj(jniEnv, ac);
+
+ jaw_impl = (JawImpl *)g_object_new(JAW_TYPE_IMPL(tflag), NULL);
+ if (jaw_impl == NULL) {
+ g_warning("%s: Failed to create new JawImpl object", G_STRFUNC);
+ (*jniEnv)->DeleteGlobalRef(jniEnv, ac);
+ return NULL;
+ }
+
+ JawObject *jaw_obj = JAW_OBJECT(jaw_impl);
+ if (jaw_obj == NULL) {
+ g_warning("%s: Failed to create JawObject", G_STRFUNC);
+ g_object_unref(G_OBJECT(jaw_impl));
+ return NULL;
+ }
+
+ jobject weak_ref = (*jniEnv)->NewWeakGlobalRef(jniEnv, ac);
+ jaw_obj->acc_context = weak_ref;
+ jaw_obj->storedData = g_hash_table_new(g_str_hash, g_str_equal);
+ aggregate_interface(jniEnv, jaw_obj, tflag);
+ atk_object_initialize(ATK_OBJECT(jaw_impl), NULL);
+
+ return jaw_impl;
+}
+
+JawImpl *jaw_impl_find_instance(JNIEnv *jniEnv, jobject ac) {
+ JAW_DEBUG("%p, %p", jniEnv, ac);
+
+ if (!ac || !jniEnv) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_impl_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ // Check if there is JawImpl associated with the accessible context
+ jlong reference = (*jniEnv)->CallStaticLongMethod(
+ jniEnv, cachedImplAtkWrapperDisposerClass, cachedImplGetResourceMethod,
+ ac);
+
+ // If a valid reference exists, return the existing JawImpl instance
+ if (reference != -1) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return (JawImpl *)reference;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+}
+
+static void jaw_impl_class_intern_init(gpointer klass, gpointer data) {
+ JAW_DEBUG("%p, %p", klass, data);
+
+ if (!klass) {
+ g_warning(
+ "Null argument passed to function jaw_impl_class_intern_init");
+ return;
+ }
+
+ if (jaw_impl_parent_class == NULL) {
+ jaw_impl_parent_class = g_type_class_peek_parent(klass);
+ }
+
+ jaw_impl_class_init((JawImplClass *)klass);
+}
+
+GType jaw_impl_get_type(guint tflag) {
+ JAW_DEBUG("%u", tflag);
+ GType type;
+
+ static const GInterfaceInfo atk_action_info = {
+ (GInterfaceInitFunc)jaw_action_interface_init,
+ (GInterfaceFinalizeFunc)NULL, NULL};
+
+ static const GInterfaceInfo atk_component_info = {
+ (GInterfaceInitFunc)jaw_component_interface_init,
+ (GInterfaceFinalizeFunc)NULL, NULL};
+
+ static const GInterfaceInfo atk_text_info = {
+ (GInterfaceInitFunc)jaw_text_interface_init,
+ (GInterfaceFinalizeFunc)NULL, NULL};
+
+ static const GInterfaceInfo atk_editable_text_info = {
+ (GInterfaceInitFunc)jaw_editable_text_interface_init,
+ (GInterfaceFinalizeFunc)NULL, NULL};
+
+ static const GInterfaceInfo atk_hypertext_info = {
+ (GInterfaceInitFunc)jaw_hypertext_interface_init,
+ (GInterfaceFinalizeFunc)NULL, NULL};
+
+ static const GInterfaceInfo atk_image_info = {
+ (GInterfaceInitFunc)jaw_image_interface_init,
+ (GInterfaceFinalizeFunc)NULL, NULL};
+
+ static const GInterfaceInfo atk_selection_info = {
+ (GInterfaceInitFunc)jaw_selection_interface_init,
+ (GInterfaceFinalizeFunc)NULL, NULL};
+
+ static const GInterfaceInfo atk_value_info = {
+ (GInterfaceInitFunc)jaw_value_interface_init,
+ (GInterfaceFinalizeFunc)NULL, NULL};
+
+ static const GInterfaceInfo atk_table_info = {
+ (GInterfaceInitFunc)jaw_table_interface_init,
+ (GInterfaceFinalizeFunc)NULL, NULL};
+
+ static const GInterfaceInfo atk_table_cell_info = {
+ (GInterfaceInitFunc)jaw_table_cell_interface_init,
+ (GInterfaceFinalizeFunc)NULL, NULL};
+
+ g_mutex_lock(&typeTableMutex);
+ if (typeTable == NULL) {
+ typeTable = g_hash_table_new(NULL, NULL);
+ }
+
+ type = GPOINTER_TO_GTYPE(
+ g_hash_table_lookup(typeTable, GUINT_TO_POINTER(tflag)));
+ g_mutex_unlock(&typeTableMutex);
+ if (type == 0) {
+ GTypeInfo tinfo = {
+ sizeof(JawImplClass),
+ (GBaseInitFunc)NULL, /* base init */
+ (GBaseFinalizeFunc)NULL, /* base finalize */
+ (GClassInitFunc)jaw_impl_class_intern_init, /*class init */
+ (GClassFinalizeFunc)NULL, /* class finalize */
+ NULL, /* class data */
+ sizeof(JawImpl), /* instance size */
+ 0, /* nb preallocs */
+ (GInstanceInitFunc)NULL, /* instance init */
+ NULL /* value table */
+ };
+
+ gchar className[20];
+ g_sprintf(className, "JawImpl_%d", tflag);
+
+ type = g_type_register_static(JAW_TYPE_OBJECT, className, &tinfo, 0);
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_ACTION)
+ g_type_add_interface_static(type, ATK_TYPE_ACTION,
+ &atk_action_info);
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_COMPONENT)
+ g_type_add_interface_static(type, ATK_TYPE_COMPONENT,
+ &atk_component_info);
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_TEXT)
+ g_type_add_interface_static(type, ATK_TYPE_TEXT, &atk_text_info);
+
+ if (tflag &
+ org_GNOME_Accessibility_AtkInterface_INTERFACE_EDITABLE_TEXT)
+ g_type_add_interface_static(type, ATK_TYPE_EDITABLE_TEXT,
+ &atk_editable_text_info);
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_HYPERTEXT)
+ g_type_add_interface_static(type, ATK_TYPE_HYPERTEXT,
+ &atk_hypertext_info);
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_IMAGE)
+ g_type_add_interface_static(type, ATK_TYPE_IMAGE, &atk_image_info);
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_SELECTION)
+ g_type_add_interface_static(type, ATK_TYPE_SELECTION,
+ &atk_selection_info);
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_VALUE)
+ g_type_add_interface_static(type, ATK_TYPE_VALUE, &atk_value_info);
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE)
+ g_type_add_interface_static(type, ATK_TYPE_TABLE, &atk_table_info);
+
+ if (tflag & org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE_CELL)
+ g_type_add_interface_static(type, ATK_TYPE_TABLE_CELL,
+ &atk_table_cell_info);
+
+ g_mutex_lock(&typeTableMutex);
+ g_hash_table_insert(typeTable, GINT_TO_POINTER(tflag),
+ JAW_TYPE_TO_POINTER(type));
+ g_mutex_unlock(&typeTableMutex);
+ }
+
+ return type;
+}
+
+static void jaw_impl_class_init(JawImplClass *klass) {
+ JAW_DEBUG("%p", klass);
+
+ if (!klass) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+ if (gobject_class == NULL) {
+ g_warning("%s: gobject_class is NULL", G_STRFUNC);
+ return;
+ }
+ gobject_class->dispose = jaw_impl_dispose;
+ gobject_class->finalize = jaw_impl_finalize;
+
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS(klass);
+ if (atk_class == NULL) {
+ g_warning("%s: atk_class is NULL", G_STRFUNC);
+ return;
+ }
+ atk_class->initialize = jaw_impl_initialize;
+
+ JawObjectClass *jaw_class = JAW_OBJECT_CLASS(klass);
+ if (jaw_class == NULL) {
+ g_warning("%s: jaw_class is NULL", G_STRFUNC);
+ return;
+ }
+ jaw_class->get_interface_data = jaw_impl_get_interface_data;
+}
+
+static void jaw_impl_dispose(GObject *gobject) {
+ JAW_DEBUG("%p", gobject);
+ /* Chain up to parent's dispose */
+ G_OBJECT_CLASS(jaw_impl_parent_class)->dispose(gobject);
+}
+
+static void jaw_impl_finalize(GObject *gobject) {
+ JAW_DEBUG("%p", gobject);
+
+ if (!gobject) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JawObject *jaw_obj = JAW_OBJECT(gobject);
+ if (jaw_obj == NULL) {
+ g_warning("%s: jaw_obj is NULL", G_STRFUNC);
+ return;
+ }
+ JawImpl *jaw_impl = (JawImpl *)jaw_obj;
+ if (jaw_impl == NULL) {
+ g_warning("%s: jaw_impl is NULL", G_STRFUNC);
+ return;
+ }
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return;
+ }
+
+ (*jniEnv)->DeleteWeakGlobalRef(jniEnv, jaw_obj->acc_context);
+ jaw_obj->acc_context = NULL;
+
+ /* Interface finalize */
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init(&iter, jaw_impl->ifaceTable);
+ while (g_hash_table_iter_next(&iter, NULL, &value)) {
+ JawInterfaceInfo *info = (JawInterfaceInfo *)value;
+ if (info != NULL) {
+ info->finalize(info->data);
+ g_free(info);
+ }
+
+ g_hash_table_iter_remove(&iter);
+ }
+ if (jaw_impl->ifaceTable != NULL) {
+ g_hash_table_unref(jaw_impl->ifaceTable);
+ }
+ if (jaw_obj->storedData != NULL) {
+ g_hash_table_destroy(jaw_obj->storedData);
+ }
+
+ /* Chain up to parent's finalize */
+ G_OBJECT_CLASS(jaw_impl_parent_class)->finalize(gobject);
+}
+
+static gpointer jaw_impl_get_interface_data(JawObject *jaw_obj, guint iface) {
+ JAW_DEBUG("%p, %u", jaw_obj, iface);
+
+ if (!jaw_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JawImpl *jaw_impl = (JawImpl *)jaw_obj;
+ if (jaw_obj == NULL) {
+ g_warning("%s: jaw_obj is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if (jaw_impl == NULL || jaw_impl->ifaceTable == NULL) {
+ return NULL;
+ }
+
+ JawInterfaceInfo *info =
+ g_hash_table_lookup(jaw_impl->ifaceTable, GUINT_TO_POINTER(iface));
+
+ if (info != NULL) {
+ return info->data;
+ }
+
+ return NULL;
+}
+
+static void jaw_impl_initialize(AtkObject *atk_obj, gpointer data) {
+ JAW_DEBUG("%p, %p", atk_obj, data);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ ATK_OBJECT_CLASS(jaw_impl_parent_class)->initialize(atk_obj, data);
+
+ JawObject *jaw_obj = JAW_OBJECT(atk_obj);
+ if (jaw_obj == NULL) {
+ g_warning("%s: jaw_obj is NULL", G_STRFUNC);
+ return;
+ }
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return;
+ }
+
+ if (!jaw_impl_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ jobject ac = (*jniEnv)->NewGlobalRef(jniEnv, jaw_obj->acc_context);
+ if (ac == NULL) {
+ g_warning("%s: Failed to create global reference to acc_context",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->CallStaticVoidMethod(
+ jniEnv, cachedImplAtkWrapperClass,
+ cachedImplRegisterPropertyChangeListenerMethod, ac);
+
+ (*jniEnv)->DeleteGlobalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * Checks if the given jKey (name of the relation) matches the value of static
+ * field in AccessibleRelation identified by fieldID.
+ *
+ * @param jniEnv JNI environment pointer
+ * @param jKey key of AccessibleRelation, the name of the relation
+ * @param fieldID cached field ID for the relation field
+ * @return TRUE if jKey equals the corresponding static field, FALSE
+ * otherwise
+ */
+static gboolean is_java_relation_key(JNIEnv *jniEnv, jstring jKey,
+ jfieldID fieldID) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jKey, fieldID);
+
+ if (!jniEnv || !fieldID) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ if (!jaw_impl_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return FALSE;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return FALSE;
+ }
+
+ jstring jConstKey = (*jniEnv)->GetStaticObjectField(
+ jniEnv, cachedImplAccessibleRelationClass, fieldID);
+
+ // jKey and jConstKey may be null
+ jboolean result = (*jniEnv)->IsSameObject(jniEnv, jKey, jConstKey);
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return result;
+}
+
+/**
+ * Compares the given key of Java AccessibleRelation (jrel_key) with some of
+ * AccessibleRelation fields. If a match is found, returns the
+ * corresponding AtkRelationType; otherwise, returns ATK_RELATION_NULL.
+ *
+ * @param jniEnv JNI environment pointer
+ * @param jrel_key key of AccessibleRelation, the name of the relation
+ * @return Corresponding AtkRelationType or ATK_RELATION_NULL
+ */
+AtkRelationType jaw_impl_get_atk_relation_type(JNIEnv *jniEnv,
+ jstring jrel_key) {
+ JAW_DEBUG("%p, %p", jniEnv, jrel_key);
+
+ if (!jaw_impl_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return FALSE;
+ }
+
+ if (is_java_relation_key(jniEnv, jrel_key, cachedImplChildNodeOfFieldID))
+ return ATK_RELATION_NODE_CHILD_OF;
+ if (is_java_relation_key(jniEnv, jrel_key, cachedImplControlledByFieldID))
+ return ATK_RELATION_CONTROLLED_BY;
+ if (is_java_relation_key(jniEnv, jrel_key, cachedImplControllerForFieldID))
+ return ATK_RELATION_CONTROLLER_FOR;
+ if (is_java_relation_key(jniEnv, jrel_key, cachedImplEmbeddedByFieldID))
+ return ATK_RELATION_EMBEDDED_BY;
+ if (is_java_relation_key(jniEnv, jrel_key, cachedImplEmbedsFieldID))
+ return ATK_RELATION_EMBEDS;
+ if (is_java_relation_key(jniEnv, jrel_key, cachedImplFlowsFromFieldID))
+ return ATK_RELATION_FLOWS_FROM;
+ if (is_java_relation_key(jniEnv, jrel_key, cachedImplFlowsToFieldID))
+ return ATK_RELATION_FLOWS_TO;
+ if (is_java_relation_key(jniEnv, jrel_key, cachedImplLabelForFieldID))
+ return ATK_RELATION_LABEL_FOR;
+ if (is_java_relation_key(jniEnv, jrel_key, cachedImplLabeledByFieldID))
+ return ATK_RELATION_LABELLED_BY;
+ if (is_java_relation_key(jniEnv, jrel_key, cachedImplMemberOfFieldID))
+ return ATK_RELATION_MEMBER_OF;
+ if (is_java_relation_key(jniEnv, jrel_key, cachedImplParentWindowOfFieldID))
+ return ATK_RELATION_PARENT_WINDOW_OF;
+ if (is_java_relation_key(jniEnv, jrel_key, cachedImplSubwindowOfFieldID))
+ return ATK_RELATION_SUBWINDOW_OF;
+ return ATK_RELATION_NULL;
+}
+
+static gboolean jaw_impl_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (impl_cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localAtkWrapperDisposer = (*jniEnv)->FindClass(
+ jniEnv, "org/GNOME/Accessibility/AtkWrapperDisposer");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localAtkWrapperDisposer == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkWrapperDisposer class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedImplAtkWrapperDisposerClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localAtkWrapperDisposer);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localAtkWrapperDisposer);
+
+ if (cachedImplAtkWrapperDisposerClass == NULL) {
+ g_warning("%s: Failed to create global reference for "
+ "AtkWrapperDisposer class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedImplGetResourceMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedImplAtkWrapperDisposerClass, "get_resource",
+ "(Ljavax/accessibility/AccessibleContext;)J");
+
+ jclass localAtkWrapper =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkWrapper");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localAtkWrapper == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkWrapper class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedImplAtkWrapperClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localAtkWrapper);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localAtkWrapper);
+
+ if (cachedImplAtkWrapperClass == NULL) {
+ g_warning("%s: Failed to create global reference for AtkWrapper class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedImplRegisterPropertyChangeListenerMethod =
+ (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedImplAtkWrapperClass,
+ "register_property_change_listener",
+ "(Ljavax/accessibility/AccessibleContext;)V");
+
+ jclass localAccessibleRelation =
+ (*jniEnv)->FindClass(jniEnv, "javax/accessibility/AccessibleRelation");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localAccessibleRelation == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AccessibleRelation class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedImplAccessibleRelationClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localAccessibleRelation);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localAccessibleRelation);
+
+ if (cachedImplAccessibleRelationClass == NULL) {
+ g_warning("%s: Failed to create global reference for "
+ "AccessibleRelation class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedImplChildNodeOfFieldID =
+ (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass,
+ "CHILD_NODE_OF", "Ljava/lang/String;");
+
+ cachedImplControlledByFieldID =
+ (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass,
+ "CONTROLLED_BY", "Ljava/lang/String;");
+
+ cachedImplControllerForFieldID =
+ (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass,
+ "CONTROLLER_FOR", "Ljava/lang/String;");
+
+ cachedImplEmbeddedByFieldID =
+ (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass,
+ "EMBEDDED_BY", "Ljava/lang/String;");
+
+ cachedImplEmbedsFieldID =
+ (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass,
+ "EMBEDS", "Ljava/lang/String;");
+
+ cachedImplFlowsFromFieldID =
+ (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass,
+ "FLOWS_FROM", "Ljava/lang/String;");
+
+ cachedImplFlowsToFieldID =
+ (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass,
+ "FLOWS_TO", "Ljava/lang/String;");
+
+ cachedImplLabelForFieldID =
+ (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass,
+ "LABEL_FOR", "Ljava/lang/String;");
+
+ cachedImplLabeledByFieldID =
+ (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass,
+ "LABELED_BY", "Ljava/lang/String;");
+
+ cachedImplMemberOfFieldID =
+ (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass,
+ "MEMBER_OF", "Ljava/lang/String;");
+
+ cachedImplParentWindowOfFieldID =
+ (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass,
+ "PARENT_WINDOW_OF", "Ljava/lang/String;");
+
+ cachedImplSubwindowOfFieldID =
+ (*jniEnv)->GetStaticFieldID(jniEnv, cachedImplAccessibleRelationClass,
+ "SUBWINDOW_OF", "Ljava/lang/String;");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedImplGetResourceMethod == NULL ||
+ cachedImplRegisterPropertyChangeListenerMethod == NULL ||
+ cachedImplChildNodeOfFieldID == NULL ||
+ cachedImplControlledByFieldID == NULL ||
+ cachedImplControllerForFieldID == NULL ||
+ cachedImplEmbeddedByFieldID == NULL ||
+ cachedImplEmbedsFieldID == NULL || cachedImplFlowsFromFieldID == NULL ||
+ cachedImplFlowsToFieldID == NULL || cachedImplLabelForFieldID == NULL ||
+ cachedImplLabeledByFieldID == NULL ||
+ cachedImplMemberOfFieldID == NULL ||
+ cachedImplParentWindowOfFieldID == NULL ||
+ cachedImplSubwindowOfFieldID == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache one or more JawImpl classes or "
+ "method/field IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ impl_cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedImplAtkWrapperDisposerClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImplAtkWrapperDisposerClass);
+ cachedImplAtkWrapperDisposerClass = NULL;
+ }
+ cachedImplGetResourceMethod = NULL;
+ if (cachedImplAtkWrapperClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImplAtkWrapperClass);
+ cachedImplAtkWrapperClass = NULL;
+ }
+ cachedImplRegisterPropertyChangeListenerMethod = NULL;
+ if (cachedImplAccessibleRelationClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImplAccessibleRelationClass);
+ cachedImplAccessibleRelationClass = NULL;
+ }
+ cachedImplChildNodeOfFieldID = NULL;
+ cachedImplControlledByFieldID = NULL;
+ cachedImplControllerForFieldID = NULL;
+ cachedImplEmbeddedByFieldID = NULL;
+ cachedImplEmbedsFieldID = NULL;
+ cachedImplFlowsFromFieldID = NULL;
+ cachedImplFlowsToFieldID = NULL;
+ cachedImplLabelForFieldID = NULL;
+ cachedImplLabeledByFieldID = NULL;
+ cachedImplMemberOfFieldID = NULL;
+ cachedImplParentWindowOfFieldID = NULL;
+ cachedImplSubwindowOfFieldID = NULL;
+
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+}
+
+void jaw_impl_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedImplAtkWrapperDisposerClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImplAtkWrapperDisposerClass);
+ cachedImplAtkWrapperDisposerClass = NULL;
+ }
+ cachedImplGetResourceMethod = NULL;
+ if (cachedImplAtkWrapperClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImplAtkWrapperClass);
+ cachedImplAtkWrapperClass = NULL;
+ }
+ cachedImplRegisterPropertyChangeListenerMethod = NULL;
+ if (cachedImplAccessibleRelationClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedImplAccessibleRelationClass);
+ cachedImplAccessibleRelationClass = NULL;
+ }
+ cachedImplChildNodeOfFieldID = NULL;
+ cachedImplControlledByFieldID = NULL;
+ cachedImplControllerForFieldID = NULL;
+ cachedImplEmbeddedByFieldID = NULL;
+ cachedImplEmbedsFieldID = NULL;
+ cachedImplFlowsFromFieldID = NULL;
+ cachedImplFlowsToFieldID = NULL;
+ cachedImplLabelForFieldID = NULL;
+ cachedImplLabeledByFieldID = NULL;
+ cachedImplMemberOfFieldID = NULL;
+ cachedImplParentWindowOfFieldID = NULL;
+ cachedImplSubwindowOfFieldID = NULL;
+ impl_cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawimpl.h b/src/jdk.accessibility/linux/native/libatk-wrapper/jawimpl.h
new file mode 100644
index 000000000000..f843d0f6a628
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawimpl.h
@@ -0,0 +1,115 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _JAW_IMPL_H_
+#define _JAW_IMPL_H_
+
+#include "jawobject.h"
+#include
+
+G_BEGIN_DECLS
+
+#define JAW_TYPE_IMPL(tf) (jaw_impl_get_type(tf))
+#define JAW_IMPL(tf, obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), JAW_TYPE_IMPL(tf), JawImpl))
+#define JAW_IMPL_CLASS(tf, klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), JAW_TYPE_IMPL(tf), JawImplClass))
+#define JAW_IS_IMPL(tf, obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), JAW_TYPE_IMPL(tf)))
+#define JAW_IS_IMPL_CLASS(tf, klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), JAW_TYPE_IMPL(tf)))
+#define JAW_IMPL_GET_CLASS(tf, obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), JAW_TYPE_IMPL(tf), JawImplClass))
+
+#ifdef GPOINTER_TO_SIZE
+#define GPOINTER_TO_GTYPE(gpointer) (GPOINTER_TO_SIZE(gpointer))
+#endif
+#ifdef GSIZE_TO_POINTER
+#define JAW_TYPE_TO_POINTER(gtype) (GSIZE_TO_POINTER(gtype))
+#endif
+
+typedef struct _JawImpl JawImpl;
+typedef struct _JawImplClass JawImplClass;
+
+/**
+ * JawImpl:
+ * A subclass of JawObject that represents an implementation
+ * of an accessibility object.
+ **/
+struct _JawImpl {
+ JawObject parent;
+
+ GHashTable *ifaceTable;
+ unsigned tflag;
+};
+
+JawImpl *jaw_impl_create_instance(JNIEnv *, jobject);
+JawImpl *jaw_impl_find_instance(JNIEnv *, jobject);
+
+GType jaw_impl_get_type(guint);
+AtkRelationType jaw_impl_get_atk_relation_type(JNIEnv *jniEnv,
+ jstring jrel_key);
+
+struct _JawImplClass {
+ JawObjectClass parent_class;
+};
+
+extern void jaw_action_interface_init(AtkActionIface *, gpointer);
+extern gpointer jaw_action_data_init(jobject);
+extern void jaw_action_data_finalize(gpointer);
+
+extern void jaw_component_interface_init(AtkComponentIface *, gpointer);
+extern gpointer jaw_component_data_init(jobject);
+extern void jaw_component_data_finalize(gpointer);
+
+extern void jaw_editable_text_interface_init(AtkEditableTextIface *, gpointer);
+extern gpointer jaw_editable_text_data_init(jobject);
+extern void jaw_editable_text_data_finalize(gpointer);
+
+extern void jaw_hypertext_interface_init(AtkHypertextIface *, gpointer);
+extern gpointer jaw_hypertext_data_init(jobject);
+extern void jaw_hypertext_data_finalize(gpointer);
+
+extern void jaw_image_interface_init(AtkImageIface *, gpointer);
+extern gpointer jaw_image_data_init(jobject);
+extern void jaw_image_data_finalize(gpointer);
+
+extern void jaw_selection_interface_init(AtkSelectionIface *, gpointer);
+extern gpointer jaw_selection_data_init(jobject);
+extern void jaw_selection_data_finalize(gpointer);
+
+extern void jaw_table_interface_init(AtkTableIface *, gpointer);
+extern gpointer jaw_table_data_init(jobject);
+extern void jaw_table_data_finalize(gpointer);
+
+extern void jaw_table_cell_interface_init(AtkTableCellIface *, gpointer);
+extern gpointer jaw_table_cell_data_init(jobject ac);
+extern void jaw_table_cell_data_finalize(gpointer);
+
+extern void jaw_text_interface_init(AtkTextIface *, gpointer);
+extern gpointer jaw_text_data_init(jobject);
+extern void jaw_text_data_finalize(gpointer);
+
+extern void jaw_value_interface_init(AtkValueIface *, gpointer);
+extern gpointer jaw_value_data_init(jobject);
+extern void jaw_value_data_finalize(gpointer);
+
+G_END_DECLS
+
+#endif
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawobject.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawobject.c
new file mode 100644
index 000000000000..3e61320be836
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawobject.c
@@ -0,0 +1,1497 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems Inc.
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawobject.h"
+#include "jawcache.h"
+#include "jawimpl.h"
+#include "jawtoplevel.h"
+#include "jawutil.h"
+#include
+#include
+
+/**
+ * (From Atk documentation)
+ *
+ * AtkObject:
+ *
+ * The base object class for the Accessibility Toolkit API.
+ *
+ * This class is the primary class for accessibility support via the
+ * Accessibility ToolKit (ATK). Objects which are instances of
+ * #AtkObject (or instances of AtkObject-derived types) are queried
+ * for properties which relate basic (and generic) properties of a UI
+ * component such as name and description. Instances of #AtkObject
+ * may also be queried as to whether they implement other ATK
+ * interfaces (e.g. #AtkAction, #AtkComponent, etc.), as appropriate
+ * to the role which a given UI component plays in a user interface.
+ *
+ * All UI components in an application which provide useful
+ * information or services to the user must provide corresponding
+ * #AtkObject instances on request (in GTK+, for instance, usually on
+ * a call to #gtk_widget_get_accessible ()), either via ATK support
+ * built into the toolkit for the widget class or ancestor class, or
+ * in the case of custom widgets, if the inherited #AtkObject
+ * implementation is insufficient, via instances of a new #AtkObject
+ * subclass.
+ *
+ * See [class@AtkObjectFactory], [class@AtkRegistry]. (GTK+ users see also
+ * #GtkAccessible).
+ *
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static jclass cachedObjectAtkObjectClass = NULL;
+static jmethodID cachedObjectGetAccessibleParentMethod = NULL;
+static jmethodID cachedObjectSetAccessibleParentMethod = NULL;
+static jmethodID cachedObjectGetAccessibleNameMethod = NULL;
+static jmethodID cachedObjectSetAccessibleNameMethod = NULL;
+static jmethodID cachedObjectGetAccessibleDescriptionMethod = NULL;
+static jmethodID cachedObjectSetAccessibleDescriptionMethod = NULL;
+static jmethodID cachedObjectGetAccessibleChildrenCountMethod = NULL;
+static jmethodID cachedObjectGetAccessibleIndexInParentMethod = NULL;
+static jmethodID cachedObjectGetArrayAccessibleStateMethod = NULL;
+static jmethodID cachedObjectGetLocaleMethod = NULL;
+static jmethodID cachedObjectGetArrayAccessibleRelationMethod = NULL;
+static jmethodID cachedObjectGetAccessibleChildMethod = NULL;
+static jclass cachedObjectAccessibleStateClass = NULL;
+static jfieldID cachedObjectCollapsedFieldID = NULL;
+static jclass cachedObjectWrapKeyAndTargetClass = NULL;
+static jfieldID cachedObjectRelationsFieldID = NULL;
+static jfieldID cachedObjectKeyFieldID = NULL;
+
+static GMutex cache_mutex;
+static gboolean cache_initialized = FALSE;
+
+static gboolean jaw_object_init_jni_cache(JNIEnv *jniEnv);
+
+static void jaw_object_initialize(AtkObject *jaw_obj, gpointer data);
+static void jaw_object_dispose(GObject *gobject);
+static void jaw_object_finalize(GObject *gobject);
+
+static const gchar *jaw_object_get_name(AtkObject *atk_obj);
+static const gchar *jaw_object_get_description(AtkObject *atk_obj);
+static gint jaw_object_get_n_children(AtkObject *atk_obj);
+static gint jaw_object_get_index_in_parent(AtkObject *atk_obj);
+static AtkRole jaw_object_get_role(AtkObject *atk_obj);
+static AtkStateSet *jaw_object_ref_state_set(AtkObject *atk_obj);
+static AtkObject *jaw_object_get_parent(AtkObject *obj);
+static void jaw_object_set_name(AtkObject *atk_obj, const gchar *name);
+static void jaw_object_set_description(AtkObject *atk_obj,
+ const gchar *description);
+static void jaw_object_set_parent(AtkObject *atk_obj, AtkObject *parent);
+static void jaw_object_set_role(AtkObject *atk_obj, AtkRole role);
+static const gchar *jaw_object_get_object_locale(AtkObject *atk_obj);
+static AtkRelationSet *jaw_object_ref_relation_set(AtkObject *atk_obj);
+static AtkObject *jaw_object_ref_child(AtkObject *atk_obj, gint i);
+
+static gpointer parent_class = NULL;
+
+enum {
+ ACTIVATE,
+ CREATE,
+ DEACTIVATE,
+ DESTROY,
+ MAXIMIZE,
+ MINIMIZE,
+ MOVE,
+ RESIZE,
+ RESTORE,
+ LAST_SIGNAL
+};
+
+static guint jaw_window_signals[LAST_SIGNAL] = {
+ 0,
+};
+
+G_DEFINE_TYPE(JawObject, jaw_object, ATK_TYPE_OBJECT);
+
+#define JAW_GET_OBJECT(atk_obj, def_ret) \
+ JAW_GET_OBJ(atk_obj, JAW_OBJECT, JawObject, jaw_obj, acc_context, jniEnv, \
+ ac, def_ret)
+
+static guint jaw_window_add_signal(const gchar *name, JawObjectClass *klass) {
+ JAW_DEBUG("%s, %p", name, klass);
+ return g_signal_new(name, G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0,
+ (GSignalAccumulator)NULL, NULL,
+ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+}
+
+static void jaw_object_class_init(JawObjectClass *klass) {
+ JAW_DEBUG("%p", klass);
+
+ if (klass == NULL) {
+ g_warning("%s: Null argument klass passed to the function", G_STRFUNC);
+ return;
+ }
+
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+ gobject_class->dispose = jaw_object_dispose;
+ gobject_class->finalize = jaw_object_finalize;
+
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS(klass);
+ parent_class = g_type_class_peek_parent(klass);
+
+ atk_class->get_name = jaw_object_get_name;
+ atk_class->get_description = jaw_object_get_description;
+ atk_class->get_parent = jaw_object_get_parent;
+ atk_class->get_n_children = jaw_object_get_n_children;
+ atk_class->ref_child = jaw_object_ref_child;
+ atk_class->get_index_in_parent = jaw_object_get_index_in_parent;
+ atk_class->ref_relation_set = jaw_object_ref_relation_set;
+ atk_class->get_role = jaw_object_get_role;
+ // atk_class->get_layer is done by atk
+ atk_class->get_mdi_zorder =
+ NULL; // missing java support for atk_class->get_mdi_zorder
+ atk_class->ref_state_set = jaw_object_ref_state_set;
+ atk_class->set_name = jaw_object_set_name;
+ atk_class->set_description = jaw_object_set_description;
+ atk_class->set_parent = jaw_object_set_parent;
+ atk_class->set_role = jaw_object_set_role;
+ atk_class->initialize = jaw_object_initialize;
+ atk_class->get_attributes = NULL; // TODO: atk_class->get_attributes
+ atk_class->get_object_locale = jaw_object_get_object_locale;
+
+ jaw_window_signals[ACTIVATE] = jaw_window_add_signal("activate", klass);
+ jaw_window_signals[CREATE] = jaw_window_add_signal("create", klass);
+ jaw_window_signals[DEACTIVATE] = jaw_window_add_signal("deactivate", klass);
+ jaw_window_signals[DESTROY] = jaw_window_add_signal("destroy", klass);
+ jaw_window_signals[MAXIMIZE] = jaw_window_add_signal("maximize", klass);
+ jaw_window_signals[MINIMIZE] = jaw_window_add_signal("minimize", klass);
+ jaw_window_signals[MOVE] = jaw_window_add_signal("move", klass);
+ jaw_window_signals[RESIZE] = jaw_window_add_signal("resize", klass);
+ jaw_window_signals[RESTORE] = jaw_window_add_signal("restore", klass);
+
+ klass->get_interface_data = NULL;
+}
+
+static void jaw_object_initialize(AtkObject *atk_obj, gpointer data) {
+ JAW_DEBUG("%p, %p", atk_obj, data);
+
+ if (atk_obj == NULL) {
+ g_warning("%s: Null argument atk_obj passed to the function",
+ G_STRFUNC);
+ return;
+ }
+
+ ATK_OBJECT_CLASS(jaw_object_parent_class)->initialize(atk_obj, data);
+}
+
+gpointer jaw_object_get_interface_data(JawObject *jaw_obj, guint iface) {
+ JAW_DEBUG("%p, %u", jaw_obj, iface);
+
+ if (jaw_obj == NULL) {
+ g_warning("%s: Null argument jaw_obj passed to the function",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ JawObjectClass *klass = JAW_OBJECT_GET_CLASS(jaw_obj);
+ if (klass == NULL) {
+ g_warning("%s: klass is NULL", G_STRFUNC);
+ return NULL;
+ }
+ if (klass->get_interface_data)
+ return klass->get_interface_data(jaw_obj, iface);
+
+ return NULL;
+}
+
+static void jaw_object_init(JawObject *object) {
+ JAW_DEBUG("%p", object);
+
+ if (object == NULL) {
+ g_warning("%s: Null argument object passed to the function", G_STRFUNC);
+ return;
+ }
+
+ AtkObject *atk_obj = ATK_OBJECT(object);
+ if (atk_obj == NULL) {
+ g_warning("%s: atk_obj is NULL", G_STRFUNC);
+ return;
+ }
+ atk_obj->description = NULL;
+
+ g_mutex_init(&object->mutex);
+ object->state_set = atk_state_set_new();
+}
+
+static void jaw_object_dispose(GObject *gobject) {
+ JAW_DEBUG("%p", gobject);
+
+ if (gobject == NULL) {
+ g_warning("%s: Null argument gobject passed to the function",
+ G_STRFUNC);
+ G_OBJECT_CLASS(jaw_object_parent_class)->dispose(gobject);
+ return;
+ }
+
+ G_OBJECT_CLASS(jaw_object_parent_class)->dispose(gobject);
+}
+
+static void jaw_object_finalize(GObject *gobject) {
+ JAW_DEBUG("%p", gobject);
+
+ if (gobject == NULL) {
+ g_warning("%s: Null argument gobject passed to the function",
+ G_STRFUNC);
+ return;
+ }
+
+ /* Customized finalize code */
+ JawObject *jaw_obj = JAW_OBJECT(gobject);
+ if (jaw_obj == NULL) {
+ g_debug("%s: jaw_obj is NULL", G_STRFUNC);
+ G_OBJECT_CLASS(jaw_object_parent_class)->finalize(gobject);
+ return;
+ }
+ AtkObject *atk_obj = ATK_OBJECT(gobject);
+ if (atk_obj == NULL) {
+ g_debug("%s: atk_obj is NULL", G_STRFUNC);
+ G_OBJECT_CLASS(jaw_object_parent_class)->finalize(gobject);
+ return;
+ }
+ g_mutex_lock(&jaw_obj->mutex);
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_debug("%s: jniEnv is NULL", G_STRFUNC);
+ g_mutex_unlock(&jaw_obj->mutex);
+ g_mutex_clear(&jaw_obj->mutex);
+ G_OBJECT_CLASS(jaw_object_parent_class)->finalize(gobject);
+ return;
+ }
+
+ if (jaw_obj->jstrName != NULL) {
+ if (atk_obj->name != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_obj->jstrName,
+ atk_obj->name);
+ atk_obj->name = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrName);
+ jaw_obj->jstrName = NULL;
+ }
+
+ if (jaw_obj->jstrDescription != NULL) {
+ if (atk_obj->description != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_obj->jstrDescription,
+ atk_obj->description);
+ atk_obj->description = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrDescription);
+ jaw_obj->jstrDescription = NULL;
+ }
+
+ if (jaw_obj->jstrLocale != NULL) {
+ if (jaw_obj->locale != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_obj->jstrLocale,
+ jaw_obj->locale);
+ jaw_obj->locale = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrLocale);
+ jaw_obj->jstrLocale = NULL;
+ }
+
+ if (jaw_obj->state_set != NULL) {
+ g_object_unref(G_OBJECT(jaw_obj->state_set));
+ }
+
+ g_mutex_unlock(&jaw_obj->mutex);
+ g_mutex_clear(&jaw_obj->mutex);
+
+ /* Chain up to parent's finalize method */
+ G_OBJECT_CLASS(jaw_object_parent_class)->finalize(gobject);
+}
+
+/**
+ * jaw_object_get_parent:
+ * @accessible: an #AtkObject
+ *
+ * Gets the accessible parent of the accessible.
+ *
+ * Returns: (transfer none): an #AtkObject representing the accessible
+ * parent of the accessible
+ **/
+static AtkObject *jaw_object_get_parent(AtkObject *atk_obj) {
+ JAW_DEBUG("%p", atk_obj);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ AtkObject *root = atk_get_root();
+ int toplevel_child_index =
+ jaw_toplevel_get_child_index(JAW_TOPLEVEL(root), atk_obj);
+ if (toplevel_child_index != -1) {
+ return ATK_OBJECT(root);
+ }
+
+ JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac`
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jparent = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedObjectAtkObjectClass,
+ cachedObjectGetAccessibleParentMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jparent == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ AtkObject *parent_obj =
+ (AtkObject *)jaw_impl_find_instance(jniEnv, jparent);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ if (parent_obj != NULL) {
+ return parent_obj;
+ }
+
+ g_warning(
+ "jaw_object_get_parent: didn't find jaw for parent, returning null");
+ return NULL;
+}
+
+/**
+ * jaw_object_set_parent:
+ * @accessible: an #AtkObject
+ *
+ * Gets the accessible parent of the accessible.
+ *
+ * Returns: (transfer none): an #AtkObject representing the accessible
+ * parent of the accessible
+ **/
+static void jaw_object_set_parent(AtkObject *atk_obj, AtkObject *parent) {
+ JAW_DEBUG("%p, %p", atk_obj, parent);
+
+ if (!atk_obj || !parent) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_OBJECT(atk_obj, ); // create local JNI reference `jobject ac`
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ return;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ JawObject *jaw_par = JAW_OBJECT(parent);
+ if (jaw_par == NULL) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+ jobject pa = (*jniEnv)->NewLocalRef(jniEnv, jaw_par->acc_context);
+ if (pa == NULL) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->CallStaticVoidMethod(jniEnv, cachedObjectAtkObjectClass,
+ cachedObjectSetAccessibleParentMethod, ac,
+ pa);
+ jaw_jni_clear_exception(jniEnv);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->DeleteLocalRef(jniEnv, pa);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * jaw_object_get_name:
+ * @role: The #AtkRole whose name is required
+ *
+ * Gets the description string describing the #AtkRole @role.
+ *
+ * Returns: the string describing the AtkRole
+ */
+static const gchar *jaw_object_get_name(AtkObject *atk_obj) {
+ JAW_DEBUG("%p", atk_obj);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ atk_obj->name = (gchar *)ATK_OBJECT_CLASS(parent_class)->get_name(atk_obj);
+
+ if (atk_object_get_role(atk_obj) == ATK_ROLE_COMBO_BOX &&
+ atk_object_get_n_accessible_children(atk_obj) == 1) {
+ AtkSelection *selection = ATK_SELECTION(atk_obj);
+ if (selection != NULL) {
+ // The caller of the method takes ownership of the returned data,
+ // and is responsible for freeing it.
+ AtkObject *child = atk_selection_ref_selection(selection, 0);
+ if (child != NULL) {
+ const gchar *name = atk_object_get_name(child);
+ g_object_unref(G_OBJECT(child));
+ if (name)
+ JAW_DEBUG("-> %s", name);
+ return name;
+ }
+ }
+ }
+
+ JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac`
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jstring jstr = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedObjectAtkObjectClass, cachedObjectGetAccessibleNameMethod,
+ ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ g_mutex_lock(&jaw_obj->mutex);
+ if (jaw_obj->jstrName != NULL) {
+ if (atk_obj->name != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_obj->jstrName,
+ atk_obj->name);
+ atk_obj->name = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrName);
+ jaw_obj->jstrName = NULL;
+ }
+
+ if (jstr != NULL) {
+ jaw_obj->jstrName = (*jniEnv)->NewGlobalRef(jniEnv, jstr);
+ if (jaw_obj->jstrName == NULL) {
+ g_mutex_unlock(&jaw_obj->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ atk_obj->name = (gchar *)(*jniEnv)->GetStringUTFChars(
+ jniEnv, jaw_obj->jstrName, NULL);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || atk_obj->name == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ // jaw_obj->jstrName != NULL
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrName);
+ jaw_obj->jstrName = NULL;
+
+ g_mutex_unlock(&jaw_obj->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ }
+ g_mutex_unlock(&jaw_obj->mutex);
+
+ if (atk_obj->name != NULL) {
+ JAW_DEBUG("-> %s", atk_obj->name);
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return atk_obj->name;
+}
+
+/**
+ * jaw_object_set_name:
+ * @accessible: an #AtkObject
+ * @name: a character string to be set as the accessible name
+ *
+ * Sets the accessible name of the accessible. You can't set the name
+ * to NULL. This is reserved for the initial value. In this aspect
+ * NULL is similar to ATK_ROLE_UNKNOWN. If you want to set the name to
+ * a empty value you can use "".
+ **/
+static void jaw_object_set_name(AtkObject *atk_obj, const gchar *name) {
+ JAW_DEBUG("%p, %s", atk_obj, name);
+
+ if (!atk_obj || !name) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_OBJECT(atk_obj, ); // create local JNI reference `jobject ac`
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ return;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ jstring jstr = NULL;
+ if (name != NULL) {
+ jstr = (*jniEnv)->NewStringUTF(jniEnv, name);
+ }
+
+ (*jniEnv)->CallStaticVoidMethod(jniEnv, cachedObjectAtkObjectClass,
+ cachedObjectSetAccessibleNameMethod, ac,
+ jstr);
+ jaw_jni_clear_exception(jniEnv);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * jaw_object_get_description:
+ * @accessible: an #AtkObject
+ *
+ * Gets the accessible description of the accessible.
+ *
+ * Returns: a character string representing the accessible description
+ * of the accessible.
+ *
+ **/
+static const gchar *jaw_object_get_description(AtkObject *atk_obj) {
+ JAW_DEBUG("%p", atk_obj);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac`
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jstring jstr = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedObjectAtkObjectClass,
+ cachedObjectGetAccessibleDescriptionMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ g_mutex_lock(&jaw_obj->mutex);
+ if (jaw_obj->jstrDescription != NULL) {
+ if (atk_obj->description != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_obj->jstrDescription,
+ atk_obj->description);
+ atk_obj->description = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrDescription);
+ jaw_obj->jstrDescription = NULL;
+ }
+
+ if (jstr != NULL) {
+ jaw_obj->jstrDescription = (*jniEnv)->NewGlobalRef(jniEnv, jstr);
+ if (jaw_obj->jstrDescription == NULL) {
+ g_mutex_unlock(&jaw_obj->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ atk_obj->description = (gchar *)(*jniEnv)->GetStringUTFChars(
+ jniEnv, jaw_obj->jstrDescription, NULL);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || atk_obj->description == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ // jaw_obj->jstrDescription != NULL
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrDescription);
+ jaw_obj->jstrDescription = NULL;
+
+ g_mutex_unlock(&jaw_obj->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ }
+ g_mutex_unlock(&jaw_obj->mutex);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return atk_obj->description;
+}
+
+/**
+ * jaw_object_set_description:
+ * @accessible: an #AtkObject
+ * @description: a character string to be set as the accessible description
+ *
+ * Sets the accessible description of the accessible. You can't set
+ * the description to NULL. This is reserved for the initial value. In
+ * this aspect NULL is similar to ATK_ROLE_UNKNOWN. If you want to set
+ * the name to a empty value you can use "".
+ **/
+static void jaw_object_set_description(AtkObject *atk_obj,
+ const gchar *description) {
+ JAW_DEBUG("%p, %s", atk_obj, description);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_OBJECT(atk_obj, ); // create local JNI reference `jobject ac`
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ return;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ jstring jstr = NULL;
+ if (description != NULL) {
+ jstr = (*jniEnv)->NewStringUTF(jniEnv, description);
+ }
+
+ (*jniEnv)->CallStaticVoidMethod(jniEnv, cachedObjectAtkObjectClass,
+ cachedObjectSetAccessibleDescriptionMethod,
+ ac, jstr);
+ jaw_jni_clear_exception(jniEnv);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * jaw_object_get_n_children:
+ * @accessible: an #AtkObject
+ *
+ * Gets the number of accessible children of the accessible.
+ *
+ * Returns: an integer representing the number of accessible children
+ * of the accessible.
+ **/
+static gint jaw_object_get_n_children(AtkObject *atk_obj) {
+ JAW_DEBUG("%p", atk_obj);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_OBJECT(atk_obj, 0); // create local JNI reference `jobject ac`
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ return 0;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return 0;
+ }
+
+ jint count = (*jniEnv)->CallStaticIntMethod(
+ jniEnv, cachedObjectAtkObjectClass,
+ cachedObjectGetAccessibleChildrenCountMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return (gint)count;
+}
+
+/**
+ * jaw_object_get_index_in_parent:
+ * @accessible: an #AtkObject
+ *
+ * Gets the 0-based index of this accessible in its parent; returns -1 if the
+ * accessible does not have an accessible parent.
+ *
+ * Returns: an integer which is the index of the accessible in its parent
+ **/
+static gint jaw_object_get_index_in_parent(AtkObject *atk_obj) {
+ JAW_DEBUG("%p", atk_obj);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return -1;
+ }
+
+ int toplevel_child_index =
+ jaw_toplevel_get_child_index(JAW_TOPLEVEL(atk_get_root()), atk_obj);
+ if (toplevel_child_index != -1) {
+ return toplevel_child_index;
+ }
+
+ JAW_GET_OBJECT(atk_obj, -1); // create local JNI reference `jobject ac`
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ return -1;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return -1;
+ }
+
+ jint index = (*jniEnv)->CallStaticIntMethod(
+ jniEnv, cachedObjectAtkObjectClass,
+ cachedObjectGetAccessibleIndexInParentMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return -1;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return (gint)index;
+}
+
+/**
+ * jaw_object_get_role:
+ * @accessible: an #AtkObject
+ *
+ * Gets the role of the accessible.
+ *
+ * Returns: an #AtkRole which is the role of the accessible
+ **/
+static AtkRole jaw_object_get_role(AtkObject *atk_obj) {
+ JAW_DEBUG("%p", atk_obj);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return ATK_ROLE_INVALID;
+ }
+
+ if (atk_obj->role != ATK_ROLE_INVALID &&
+ atk_obj->role != ATK_ROLE_UNKNOWN) {
+ return atk_obj->role;
+ }
+
+ JAW_GET_OBJECT(atk_obj,
+ ATK_ROLE_INVALID); // create local JNI reference `jobject ac`
+ AtkRole role = jaw_util_get_atk_role_from_AccessibleContext(ac);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+
+ return role;
+}
+
+/**
+ * jaw_object_set_role:
+ * @accessible: an #AtkObject
+ * @role: an #AtkRole to be set as the role
+ *
+ * Sets the role of the accessible.
+ **/
+static void jaw_object_set_role(AtkObject *atk_obj, AtkRole role) {
+ JAW_DEBUG("%p, %d", atk_obj, role);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ atk_obj->role = role;
+}
+
+#if !ATK_CHECK_VERSION(2, 38, 0)
+static gboolean is_collapsed_java_state(JNIEnv *jniEnv, jobject jobj) {
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return FALSE;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return FALSE;
+ }
+
+ jobject jstate = (*jniEnv)->GetStaticObjectField(
+ jniEnv, cachedObjectAccessibleStateClass, cachedObjectCollapsedFieldID);
+
+ // jobj and jstate may be null
+ if ((*jniEnv)->IsSameObject(jniEnv, jobj, jstate)) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return TRUE;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return FALSE;
+}
+#endif
+
+/**
+ * atk_object_ref_state_set:
+ * @accessible: an #AtkObject
+ *
+ * Gets a reference to the state set of the accessible; the caller must
+ * unreference it when it is no longer needed.
+ *
+ * Returns: (transfer full): a reference to an #AtkStateSet which is the state
+ * set of the accessible
+ **/
+static AtkStateSet *jaw_object_ref_state_set(AtkObject *atk_obj) {
+ JAW_DEBUG("%p", atk_obj);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac`
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ AtkStateSet *state_set = jaw_obj->state_set;
+ if (state_set == NULL) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ atk_state_set_clear_states(state_set);
+
+ jobject jstate_arr = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedObjectAtkObjectClass,
+ cachedObjectGetArrayAccessibleStateMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstate_arr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ jsize jarr_size = (*jniEnv)->GetArrayLength(jniEnv, jstate_arr);
+ jsize i;
+ for (i = 0; i < jarr_size; i++) {
+ jobject jstate =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, jstate_arr, i);
+#if !ATK_CHECK_VERSION(2, 38, 0)
+ if (jstate && is_collapsed_java_state(jniEnv, jstate)) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jstate);
+ continue;
+ }
+#endif
+ AtkStateType state_type =
+ jaw_util_get_atk_state_type_from_java_state(jniEnv, jstate);
+ atk_state_set_add_state(state_set, state_type);
+ if (state_type == ATK_STATE_ENABLED) {
+ atk_state_set_add_state(state_set, ATK_STATE_SENSITIVE);
+ }
+ (*jniEnv)->DeleteLocalRef(jniEnv, jstate);
+ }
+
+ g_object_ref(G_OBJECT(state_set)); // because transfer full
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return state_set;
+}
+
+/**
+ * jaw_object_get_object_locale:
+ * @accessible: an #AtkObject
+ *
+ * Gets a UTF-8 string indicating the POSIX-style LC_MESSAGES locale
+ * of @accessible.
+ *
+ * Returns: a UTF-8 string indicating the POSIX-style LC_MESSAGES
+ * locale of @accessible.
+ **/
+static const gchar *jaw_object_get_object_locale(AtkObject *atk_obj) {
+ JAW_DEBUG("%p", atk_obj);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac`
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jstr = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedObjectAtkObjectClass, cachedObjectGetLocaleMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ g_mutex_lock(&jaw_obj->mutex);
+ if (jaw_obj->jstrLocale != NULL) {
+ if (jaw_obj->locale != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, jaw_obj->jstrLocale,
+ jaw_obj->locale);
+ jaw_obj->locale = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrLocale);
+ jaw_obj->jstrLocale = NULL;
+ }
+
+ if (jstr != NULL) {
+ jaw_obj->jstrLocale = (*jniEnv)->NewGlobalRef(jniEnv, jstr);
+ if (jaw_obj->jstrLocale == NULL) {
+ g_mutex_unlock(&jaw_obj->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ jaw_obj->locale =
+ (*jniEnv)->GetStringUTFChars(jniEnv, jaw_obj->jstrLocale, NULL);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jaw_obj->locale == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ // jaw_obj->jstrLocale != NULL
+ (*jniEnv)->DeleteGlobalRef(jniEnv, jaw_obj->jstrLocale);
+ jaw_obj->jstrLocale = NULL;
+
+ g_mutex_unlock(&jaw_obj->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ }
+ g_mutex_unlock(&jaw_obj->mutex);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return jaw_obj->locale;
+}
+
+/**
+ * jaw_object_ref_relation_set:
+ * @accessible: an #AtkObject
+ *
+ * Gets the #AtkRelationSet associated with the object.
+ *
+ * Returns: (transfer full) : an #AtkRelationSet representing the relation set
+ * of the object.
+ **/
+static AtkRelationSet *jaw_object_ref_relation_set(AtkObject *atk_obj) {
+ JAW_DEBUG("%p)", atk_obj);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac`
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ if (atk_obj->relation_set != NULL) {
+ g_object_unref(G_OBJECT(atk_obj->relation_set));
+ }
+ atk_obj->relation_set = atk_relation_set_new();
+
+ jobject jwrap_key_target_arr = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedObjectAtkObjectClass,
+ cachedObjectGetArrayAccessibleRelationMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jwrap_key_target_arr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+
+ jsize jarr_size = (*jniEnv)->GetArrayLength(jniEnv, jwrap_key_target_arr);
+ if (!jarr_size) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ jsize i;
+ for (i = 0; i < jarr_size; i++) {
+ jobject jwrap_key_target =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, jwrap_key_target_arr, i);
+ if (!jwrap_key_target) {
+ continue;
+ }
+ jstring jrel_key = (*jniEnv)->GetObjectField(jniEnv, jwrap_key_target,
+ cachedObjectKeyFieldID);
+ if (!jrel_key) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jwrap_key_target);
+ continue;
+ }
+ AtkRelationType rel_type =
+ jaw_impl_get_atk_relation_type(jniEnv, jrel_key);
+ if (!rel_type) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jwrap_key_target);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jrel_key);
+ continue;
+ }
+ jobjectArray jtarget_arr = (*jniEnv)->GetObjectField(
+ jniEnv, jwrap_key_target, cachedObjectRelationsFieldID);
+ if (!jtarget_arr) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jwrap_key_target);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jrel_key);
+ continue;
+ }
+ jsize jtarget_size = (*jniEnv)->GetArrayLength(jniEnv, jtarget_arr);
+ if (!jtarget_size) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jwrap_key_target);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jrel_key);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jtarget_arr);
+ continue;
+ }
+
+ jsize j;
+ for (j = 0; j < jtarget_size; j++) {
+ jobject jtarget =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, jtarget_arr, j);
+ if (!jtarget) {
+ continue;
+ }
+ JawImpl *target_obj = jaw_impl_find_instance(jniEnv, jtarget);
+ if (target_obj == NULL) {
+ g_warning(
+ "jaw_object_ref_relation_set: target_obj == NULL occurs\n");
+ } else {
+ atk_object_add_relationship(atk_obj, rel_type,
+ ATK_OBJECT(target_obj));
+ }
+ (*jniEnv)->DeleteLocalRef(jniEnv, jtarget);
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jwrap_key_target);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jrel_key);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jtarget_arr);
+ }
+
+ if (atk_obj->relation_set == NULL) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ if (atk_obj->relation_set != NULL) { // because transfer full
+ g_object_ref(G_OBJECT(atk_obj->relation_set));
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return atk_obj->relation_set;
+}
+
+/**
+ * jaw_object_ref_child:
+ * @accessible: an #AtkObject
+ * @i: a gint representing the position of the child, starting from 0
+ *
+ * Gets a reference to the specified accessible child of the object.
+ *
+ * Returns: an #AtkObject representing the specified
+ * accessible child of the accessible.
+ **/
+static AtkObject *jaw_object_ref_child(AtkObject *atk_obj, gint i) {
+ JAW_DEBUG("%p, %d", atk_obj, i);
+
+ if (!atk_obj) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_OBJECT(atk_obj, NULL); // create local JNI reference `jobject ac`
+
+ if (!jaw_object_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject child_ac = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedObjectAtkObjectClass,
+ cachedObjectGetAccessibleChildMethod, ac, i);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || child_ac == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ AtkObject *obj = (AtkObject *)jaw_impl_find_instance(jniEnv, child_ac);
+ // From the documentation of `ref_child` in AtkObject
+ // (https://docs.gtk.org/atk/vfunc.Object.ref_child.html): The returned data
+ // is owned by the instance, so the object is not referenced before being
+ // returned. Documentation in the repository states nothing about it. In
+ // fact, `ref_child` is used in `atk_object_ref_accessible_child`, where the
+ // caller takes ownership of the returned data and is responsible for
+ // freeing it. `atk_object_ref_accessible_child` does not reference the
+ // object. Therefore, I assume that `ref_child` should reference the object.
+ if (obj != NULL) {
+ g_object_ref(G_OBJECT(obj));
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, ac);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return obj;
+}
+
+static gboolean jaw_object_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localClass =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkObject");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkObject class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedObjectAtkObjectClass = (*jniEnv)->NewGlobalRef(jniEnv, localClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClass);
+
+ if (cachedObjectAtkObjectClass == NULL) {
+ g_warning("%s: Failed to create global reference for AtkObject class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedObjectGetAccessibleParentMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedObjectAtkObjectClass, "get_accessible_parent",
+ "(Ljavax/accessibility/AccessibleContext;)Ljavax/accessibility/"
+ "AccessibleContext;");
+
+ cachedObjectSetAccessibleParentMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedObjectAtkObjectClass, "set_accessible_parent",
+ "(Ljavax/accessibility/AccessibleContext;Ljavax/accessibility/"
+ "AccessibleContext;)V");
+
+ cachedObjectGetAccessibleNameMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedObjectAtkObjectClass, "get_accessible_name",
+ "(Ljavax/accessibility/AccessibleContext;)Ljava/lang/String;");
+
+ cachedObjectSetAccessibleNameMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedObjectAtkObjectClass, "set_accessible_name",
+ "(Ljavax/accessibility/AccessibleContext;Ljava/lang/String;)V");
+
+ cachedObjectGetAccessibleDescriptionMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedObjectAtkObjectClass, "get_accessible_description",
+ "(Ljavax/accessibility/AccessibleContext;)Ljava/lang/String;");
+
+ cachedObjectSetAccessibleDescriptionMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedObjectAtkObjectClass, "set_accessible_description",
+ "(Ljavax/accessibility/AccessibleContext;Ljava/lang/String;)V");
+
+ cachedObjectGetAccessibleChildrenCountMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedObjectAtkObjectClass, "get_accessible_children_count",
+ "(Ljavax/accessibility/AccessibleContext;)I");
+
+ cachedObjectGetAccessibleIndexInParentMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedObjectAtkObjectClass, "get_accessible_index_in_parent",
+ "(Ljavax/accessibility/AccessibleContext;)I");
+
+ cachedObjectGetArrayAccessibleStateMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedObjectAtkObjectClass, "get_array_accessible_state",
+ "(Ljavax/accessibility/AccessibleContext;)[Ljavax/accessibility/"
+ "AccessibleState;");
+
+ cachedObjectGetLocaleMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedObjectAtkObjectClass, "get_locale",
+ "(Ljavax/accessibility/AccessibleContext;)Ljava/lang/String;");
+
+ cachedObjectGetArrayAccessibleRelationMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedObjectAtkObjectClass, "get_array_accessible_relation",
+ "(Ljavax/accessibility/AccessibleContext;)[Lorg/GNOME/Accessibility/"
+ "AtkObject$WrapKeyAndTarget;");
+
+ cachedObjectGetAccessibleChildMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedObjectAtkObjectClass, "get_accessible_child",
+ "(Ljavax/accessibility/AccessibleContext;I)Ljavax/accessibility/"
+ "AccessibleContext;");
+
+ jclass localAccessibleState =
+ (*jniEnv)->FindClass(jniEnv, "javax/accessibility/AccessibleState");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localAccessibleState == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AccessibleState class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedObjectAccessibleStateClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localAccessibleState);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localAccessibleState);
+
+ if (cachedObjectAccessibleStateClass == NULL) {
+ g_warning(
+ "%s: Failed to create global reference for AccessibleState class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedObjectCollapsedFieldID = (*jniEnv)->GetStaticFieldID(
+ jniEnv, cachedObjectAccessibleStateClass, "COLLAPSED",
+ "Ljavax/accessibility/AccessibleState;");
+
+ jclass localWrapKeyAndTarget = (*jniEnv)->FindClass(
+ jniEnv, "org/GNOME/Accessibility/AtkObject$WrapKeyAndTarget");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localWrapKeyAndTarget == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find WrapKeyAndTarget class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedObjectWrapKeyAndTargetClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localWrapKeyAndTarget);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localWrapKeyAndTarget);
+
+ if (cachedObjectWrapKeyAndTargetClass == NULL) {
+ g_warning("%s: Failed to create global reference for WrapKeyAndTarget "
+ "class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedObjectRelationsFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedObjectWrapKeyAndTargetClass, "relations",
+ "[Ljavax/accessibility/AccessibleContext;");
+
+ cachedObjectKeyFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedObjectWrapKeyAndTargetClass, "key", "Ljava/lang/String;");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedObjectGetAccessibleParentMethod == NULL ||
+ cachedObjectSetAccessibleParentMethod == NULL ||
+ cachedObjectGetAccessibleNameMethod == NULL ||
+ cachedObjectSetAccessibleNameMethod == NULL ||
+ cachedObjectGetAccessibleDescriptionMethod == NULL ||
+ cachedObjectSetAccessibleDescriptionMethod == NULL ||
+ cachedObjectGetAccessibleChildrenCountMethod == NULL ||
+ cachedObjectGetAccessibleIndexInParentMethod == NULL ||
+ cachedObjectGetArrayAccessibleStateMethod == NULL ||
+ cachedObjectGetLocaleMethod == NULL ||
+ cachedObjectGetArrayAccessibleRelationMethod == NULL ||
+ cachedObjectGetAccessibleChildMethod == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache one or more AtkObject method IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedObjectAtkObjectClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedObjectAtkObjectClass);
+ cachedObjectAtkObjectClass = NULL;
+ }
+ cachedObjectGetAccessibleParentMethod = NULL;
+ cachedObjectSetAccessibleParentMethod = NULL;
+ cachedObjectGetAccessibleNameMethod = NULL;
+ cachedObjectSetAccessibleNameMethod = NULL;
+ cachedObjectGetAccessibleDescriptionMethod = NULL;
+ cachedObjectSetAccessibleDescriptionMethod = NULL;
+ cachedObjectGetAccessibleChildrenCountMethod = NULL;
+ cachedObjectGetAccessibleIndexInParentMethod = NULL;
+ cachedObjectGetArrayAccessibleStateMethod = NULL;
+ cachedObjectGetLocaleMethod = NULL;
+ cachedObjectGetArrayAccessibleRelationMethod = NULL;
+ cachedObjectGetAccessibleChildMethod = NULL;
+ if (cachedObjectAccessibleStateClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedObjectAccessibleStateClass);
+ cachedObjectAccessibleStateClass = NULL;
+ }
+ cachedObjectCollapsedFieldID = NULL;
+ if (cachedObjectWrapKeyAndTargetClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedObjectWrapKeyAndTargetClass);
+ cachedObjectWrapKeyAndTargetClass = NULL;
+ }
+ cachedObjectRelationsFieldID = NULL;
+ cachedObjectKeyFieldID = NULL;
+
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+}
+
+void jaw_object_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedObjectAtkObjectClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedObjectAtkObjectClass);
+ cachedObjectAtkObjectClass = NULL;
+ }
+ cachedObjectGetAccessibleParentMethod = NULL;
+ cachedObjectSetAccessibleParentMethod = NULL;
+ cachedObjectGetAccessibleNameMethod = NULL;
+ cachedObjectSetAccessibleNameMethod = NULL;
+ cachedObjectGetAccessibleDescriptionMethod = NULL;
+ cachedObjectSetAccessibleDescriptionMethod = NULL;
+ cachedObjectGetAccessibleChildrenCountMethod = NULL;
+ cachedObjectGetAccessibleIndexInParentMethod = NULL;
+ cachedObjectGetArrayAccessibleStateMethod = NULL;
+ cachedObjectGetLocaleMethod = NULL;
+ cachedObjectGetArrayAccessibleRelationMethod = NULL;
+ cachedObjectGetAccessibleChildMethod = NULL;
+ if (cachedObjectAccessibleStateClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedObjectAccessibleStateClass);
+ cachedObjectAccessibleStateClass = NULL;
+ }
+ cachedObjectCollapsedFieldID = NULL;
+ if (cachedObjectWrapKeyAndTargetClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedObjectWrapKeyAndTargetClass);
+ cachedObjectWrapKeyAndTargetClass = NULL;
+ }
+ cachedObjectRelationsFieldID = NULL;
+ cachedObjectKeyFieldID = NULL;
+ cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawobject.h b/src/jdk.accessibility/linux/native/libatk-wrapper/jawobject.h
new file mode 100644
index 000000000000..70934bf146bb
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawobject.h
@@ -0,0 +1,72 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _JAW_OBJECT_H_
+#define _JAW_OBJECT_H_
+
+#include
+#include
+
+G_BEGIN_DECLS
+
+#define JAW_TYPE_OBJECT (jaw_object_get_type())
+#define JAW_OBJECT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), JAW_TYPE_OBJECT, JawObject))
+#define JAW_OBJECT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), JAW_TYPE_OBJECT, JawObjectClass))
+#define JAW_IS_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), JAW_TYPE_OBJECT))
+#define JAW_IS_OBJECT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), JAW_TYPE_OBJECT))
+#define JAW_OBJECT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), JAW_TYPE_OBJECT, JawObjectClass))
+
+typedef struct _JawObject JawObject;
+typedef struct _JawObjectClass JawObjectClass;
+
+/**
+ * JawObject:
+ * A base structure wrapping an AtkObject.
+ **/
+struct _JawObject {
+ AtkObject parent;
+
+ jobject acc_context;
+ jstring jstrName;
+ jstring jstrDescription;
+ jstring jstrLocale;
+ const gchar *locale;
+ AtkStateSet *state_set;
+
+ GHashTable *storedData;
+ GMutex mutex;
+};
+
+GType jaw_object_get_type(void);
+
+struct _JawObjectClass {
+ AtkObjectClass parent_class;
+
+ gpointer (*get_interface_data)(JawObject *, guint);
+};
+
+gpointer jaw_object_get_interface_data(JawObject *, guint);
+
+G_END_DECLS
+
+#endif
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawselection.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawselection.c
new file mode 100644
index 000000000000..b78a9d3b53a9
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawselection.c
@@ -0,0 +1,583 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawcache.h"
+#include "jawimpl.h"
+#include "jawutil.h"
+#include
+#include
+
+/**
+ * (From Atk documentation)
+ *
+ * AtkSelection:
+ *
+ * The ATK interface implemented by container objects whose #AtkObject children
+ * can be selected.
+ *
+ * #AtkSelection should be implemented by UI components with children
+ * which are exposed by #atk_object_ref_child and
+ * #atk_object_get_n_children, if the use of the parent UI component
+ * ordinarily involves selection of one or more of the objects
+ * corresponding to those #AtkObject children - for example,
+ * selectable lists.
+ *
+ * Note that other types of "selection" (for instance text selection)
+ * are accomplished a other ATK interfaces - #AtkSelection is limited
+ * to the selection/deselection of children.
+ */
+
+static jclass cachedSelectionAtkSelectionClass = NULL;
+static jmethodID cachedSelectionCreateAtkSelectionMethod = NULL;
+static jmethodID cachedSelectionAddSelectionMethod = NULL;
+static jmethodID cachedSelectionClearSelectionMethod = NULL;
+static jmethodID cachedSelectionRefSelectionMethod = NULL;
+static jmethodID cachedSelectionGetSelectionCountMethod = NULL;
+static jmethodID cachedSelectionIsChildSelectedMethod = NULL;
+static jmethodID cachedSelectionRemoveSelectionMethod = NULL;
+static jmethodID cachedSelectionSelectAllSelectionMethod = NULL;
+
+static GMutex cache_mutex;
+static gboolean cache_initialized = FALSE;
+
+static gboolean jaw_selection_init_jni_cache(JNIEnv *jniEnv);
+
+static gboolean jaw_selection_add_selection(AtkSelection *selection, gint i);
+static gboolean jaw_selection_clear_selection(AtkSelection *selection);
+static AtkObject *jaw_selection_ref_selection(AtkSelection *selection, gint i);
+static gint jaw_selection_get_selection_count(AtkSelection *selection);
+static gboolean jaw_selection_is_child_selected(AtkSelection *selection,
+ gint i);
+static gboolean jaw_selection_remove_selection(AtkSelection *selection, gint i);
+static gboolean jaw_selection_select_all_selection(AtkSelection *selection);
+
+typedef struct _SelectionData {
+ jobject atk_selection;
+} SelectionData;
+
+#define JAW_GET_SELECTION(selection, def_ret) \
+ JAW_GET_OBJ_IFACE( \
+ selection, org_GNOME_Accessibility_AtkInterface_INTERFACE_SELECTION, \
+ SelectionData, atk_selection, jniEnv, atk_selection, def_ret)
+
+/**
+ * AtkSelectionIface:
+ * @add_selection:
+ * @clear_selection:
+ * @ref_selection:
+ * @get_selection_count:
+ * @is_child_selected:
+ * @remove_selection:
+ * @select_all_selection:
+ **/
+void jaw_selection_interface_init(AtkSelectionIface *iface, gpointer data) {
+ JAW_DEBUG("%p, %p", iface, data);
+
+ if (iface == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ iface->add_selection = jaw_selection_add_selection;
+ iface->clear_selection = jaw_selection_clear_selection;
+ iface->ref_selection = jaw_selection_ref_selection;
+ iface->get_selection_count = jaw_selection_get_selection_count;
+ iface->is_child_selected = jaw_selection_is_child_selected;
+ iface->remove_selection = jaw_selection_remove_selection;
+ iface->select_all_selection = jaw_selection_select_all_selection;
+}
+
+gpointer jaw_selection_data_init(jobject ac) {
+ JAW_DEBUG("%p", ac);
+
+ if (ac == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_selection_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jatk_selection = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedSelectionAtkSelectionClass,
+ cachedSelectionCreateAtkSelectionMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_selection == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jatk_selection using "
+ "create_atk_selection method",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ SelectionData *data = g_new0(SelectionData, 1);
+ data->atk_selection = (*jniEnv)->NewGlobalRef(jniEnv, jatk_selection);
+ if (data->atk_selection == NULL) {
+ g_warning("%s: Failed to create global ref for atk_selection",
+ G_STRFUNC);
+ g_free(data);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data;
+}
+
+void jaw_selection_data_finalize(gpointer p) {
+ JAW_DEBUG("%p", p);
+
+ if (p == NULL) {
+ g_warning(
+ "Null argument passed to function jaw_selection_data_finalize");
+ return;
+ }
+
+ SelectionData *data = (SelectionData *)p;
+ if (data == NULL) {
+ return;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+
+ if (jniEnv == NULL) {
+ g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC);
+ } else {
+ if (data->atk_selection != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_selection);
+ data->atk_selection = NULL;
+ }
+ }
+
+ g_free(data);
+}
+
+/**
+ * jaw_selection_add_selection:
+ * @selection: a #GObject instance that implements AtkSelectionIface
+ * @i: a #gint specifying the child index.
+ *
+ * Adds the specified accessible child of the object to the
+ * object's selection.
+ *
+ * Returns: TRUE if success, FALSE otherwise.
+ **/
+static gboolean jaw_selection_add_selection(AtkSelection *selection, gint i) {
+ JAW_DEBUG("%p, %d", selection, i);
+
+ if (selection == NULL) {
+ g_warning(
+ "Null argument passed to function jaw_selection_add_selection");
+ return FALSE;
+ }
+
+ JAW_GET_SELECTION(
+ selection,
+ FALSE); // create local JNI reference `jobject atk_selection`
+
+ jboolean jbool = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_selection, cachedSelectionAddSelectionMethod, (jint)i);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+
+ return jbool;
+}
+
+/**
+ * jaw_selection_clear_selection:
+ * @selection: a #GObject instance that implements AtkSelectionIface
+ *
+ * Clears the selection in the object so that no children in the object
+ * are selected.
+ *
+ * Returns: TRUE if success, FALSE otherwise.
+ **/
+static gboolean jaw_selection_clear_selection(AtkSelection *selection) {
+ JAW_DEBUG("%p", selection);
+
+ if (selection == NULL) {
+ g_warning(
+ "Null argument passed to function jaw_selection_clear_selection");
+ return FALSE;
+ }
+
+ JAW_GET_SELECTION(
+ selection,
+ FALSE); // create local JNI reference `jobject atk_selection`
+
+ jboolean jbool = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_selection, cachedSelectionClearSelectionMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+
+ return jbool;
+}
+
+/**
+ * jaw_selection_ref_selection:
+ * @selection: a #GObject instance that implements AtkSelectionIface
+ * @i: a #gint specifying the index in the selection set. (e.g. the
+ * ith selection as opposed to the ith child).
+ *
+ * Gets a reference to the accessible object representing the specified
+ * selected child of the object.
+ *
+ * Returns: (nullable) (transfer full): an #AtkObject representing the
+ * selected accessible, or %NULL if @selection does not implement this
+ * interface.
+ **/
+static AtkObject *jaw_selection_ref_selection(AtkSelection *selection, gint i) {
+ JAW_DEBUG("%p, %d", selection, i);
+
+ if (selection == NULL) {
+ g_warning(
+ "Null argument passed to function jaw_selection_ref_selection");
+ return NULL;
+ }
+
+ JAW_GET_SELECTION(
+ selection, NULL); // create local JNI reference `jobject atk_selection`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject child_ac = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_selection, cachedSelectionRefSelectionMethod, (jint)i);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || child_ac == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ AtkObject *obj = (AtkObject *)jaw_impl_find_instance(jniEnv, child_ac);
+
+ // From the documentation of the `ref_selection`:
+ // "The caller of the method takes ownership of the returned data, and is
+ // responsible for freeing it." (transfer full)
+ if (obj != NULL) {
+ g_object_ref(G_OBJECT(obj));
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return obj;
+}
+
+/**
+ * jaw_selection_get_selection_count:
+ * @selection: a #GObject instance that implements AtkSelectionIface
+ *
+ * Gets the number of accessible children currently selected.
+ *
+ * Returns: a gint representing the number of items selected, or 0
+ * if @selection does not implement this interface.
+ **/
+static gint jaw_selection_get_selection_count(AtkSelection *selection) {
+ JAW_DEBUG("%p", selection);
+
+ if (selection == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_SELECTION(selection,
+ 0); // create local JNI reference `jobject atk_selection`
+
+ jint jcount = (*jniEnv)->CallIntMethod(
+ jniEnv, atk_selection, cachedSelectionGetSelectionCountMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+
+ return (gint)jcount;
+}
+
+/**
+ * jaw_selection_is_child_selected:
+ * @selection: a #GObject instance that implements AtkSelectionIface
+ * @i: a #gint specifying the child index.
+ *
+ * Determines if the current child of this object is selected
+ *
+ * Returns: a gboolean representing the specified child is selected, or 0
+ * if @selection does not implement this interface.
+ **/
+static gboolean jaw_selection_is_child_selected(AtkSelection *selection,
+ gint i) {
+ JAW_DEBUG("%p, %d", selection, i);
+
+ if (selection == NULL) {
+ g_warning(
+ "Null argument passed to function jaw_selection_is_child_selected");
+ return FALSE;
+ }
+
+ JAW_GET_SELECTION(
+ selection,
+ FALSE); // create local JNI reference `jobject atk_selection`
+
+ jboolean jbool = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_selection, cachedSelectionIsChildSelectedMethod, (jint)i);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+
+ return jbool;
+}
+
+/**
+ * jaw_selection_remove_selection:
+ * @selection: a #GObject instance that implements AtkSelectionIface
+ * @i: a #gint specifying the index in the selection set. (e.g. the
+ * ith selection as opposed to the ith child).
+ *
+ * Removes the specified child of the object from the object's selection.
+ *
+ * Returns: TRUE if success, FALSE otherwise.
+ **/
+static gboolean jaw_selection_remove_selection(AtkSelection *selection,
+ gint i) {
+ JAW_DEBUG("%p, %d", selection, i);
+
+ if (selection == NULL) {
+ g_warning(
+ "Null argument passed to function jaw_selection_remove_selection");
+ return FALSE;
+ }
+
+ JAW_GET_SELECTION(
+ selection,
+ FALSE); // create local JNI reference `jobject atk_selection`
+
+ jboolean jbool = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_selection, cachedSelectionRemoveSelectionMethod, (jint)i);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+
+ return jbool;
+}
+
+/**
+ * jaw_selection_select_all_selection:
+ * @selection: a #GObject instance that implements AtkSelectionIface
+ *
+ * Causes every child of the object to be selected if the object
+ * supports multiple selections.
+ *
+ * Returns: TRUE if success, FALSE otherwise.
+ **/
+static gboolean jaw_selection_select_all_selection(AtkSelection *selection) {
+ JAW_DEBUG("%p", selection);
+
+ if (selection == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_SELECTION(
+ selection,
+ FALSE); // create local JNI reference `jobject atk_selection`
+
+ jboolean jbool = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_selection, cachedSelectionSelectAllSelectionMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_selection);
+
+ return jbool;
+}
+
+static gboolean jaw_selection_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localClass =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkSelection");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkSelection class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedSelectionAtkSelectionClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClass);
+
+ if (cachedSelectionAtkSelectionClass == NULL) {
+ g_warning(
+ "%s: Failed to create global reference for AtkSelection class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedSelectionCreateAtkSelectionMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedSelectionAtkSelectionClass, "create_atk_selection",
+ "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/"
+ "AtkSelection;");
+
+ cachedSelectionAddSelectionMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedSelectionAtkSelectionClass, "add_selection", "(I)Z");
+
+ cachedSelectionClearSelectionMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedSelectionAtkSelectionClass, "clear_selection", "()Z");
+
+ cachedSelectionRefSelectionMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedSelectionAtkSelectionClass, "ref_selection",
+ "(I)Ljavax/accessibility/AccessibleContext;");
+
+ cachedSelectionGetSelectionCountMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedSelectionAtkSelectionClass, "get_selection_count", "()I");
+
+ cachedSelectionIsChildSelectedMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedSelectionAtkSelectionClass, "is_child_selected", "(I)Z");
+
+ cachedSelectionRemoveSelectionMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedSelectionAtkSelectionClass, "remove_selection", "(I)Z");
+
+ cachedSelectionSelectAllSelectionMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedSelectionAtkSelectionClass,
+ "select_all_selection", "()Z");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedSelectionCreateAtkSelectionMethod == NULL ||
+ cachedSelectionAddSelectionMethod == NULL ||
+ cachedSelectionClearSelectionMethod == NULL ||
+ cachedSelectionRefSelectionMethod == NULL ||
+ cachedSelectionGetSelectionCountMethod == NULL ||
+ cachedSelectionIsChildSelectedMethod == NULL ||
+ cachedSelectionRemoveSelectionMethod == NULL ||
+ cachedSelectionSelectAllSelectionMethod == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache one or more AtkSelection method IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedSelectionAtkSelectionClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedSelectionAtkSelectionClass);
+ cachedSelectionAtkSelectionClass = NULL;
+ }
+ cachedSelectionCreateAtkSelectionMethod = NULL;
+ cachedSelectionAddSelectionMethod = NULL;
+ cachedSelectionClearSelectionMethod = NULL;
+ cachedSelectionRefSelectionMethod = NULL;
+ cachedSelectionGetSelectionCountMethod = NULL;
+ cachedSelectionIsChildSelectedMethod = NULL;
+ cachedSelectionRemoveSelectionMethod = NULL;
+ cachedSelectionSelectAllSelectionMethod = NULL;
+
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+}
+
+void jaw_selection_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedSelectionAtkSelectionClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedSelectionAtkSelectionClass);
+ cachedSelectionAtkSelectionClass = NULL;
+ }
+ cachedSelectionCreateAtkSelectionMethod = NULL;
+ cachedSelectionAddSelectionMethod = NULL;
+ cachedSelectionClearSelectionMethod = NULL;
+ cachedSelectionRefSelectionMethod = NULL;
+ cachedSelectionGetSelectionCountMethod = NULL;
+ cachedSelectionIsChildSelectedMethod = NULL;
+ cachedSelectionRemoveSelectionMethod = NULL;
+ cachedSelectionSelectAllSelectionMethod = NULL;
+ cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawtable.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtable.c
new file mode 100644
index 000000000000..16258d045795
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtable.c
@@ -0,0 +1,1692 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems Inc.
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawcache.h"
+#include "jawimpl.h"
+#include "jawutil.h"
+#include
+#include
+
+/**
+ * (From Atk documentation)
+ *
+ * AtkTable:
+ *
+ * The ATK interface implemented for UI components which contain tabular or
+ * row/column information.
+ *
+ * #AtkTable should be implemented by components which present
+ * elements ordered via rows and columns. It may also be used to
+ * present tree-structured information if the nodes of the trees can
+ * be said to contain multiple "columns". Individual elements of an
+ * #AtkTable are typically referred to as "cells". Those cells should
+ * implement the interface #AtkTableCell, but #Atk doesn't require
+ * them to be direct children of the current #AtkTable. They can be
+ * grand-children, grand-grand-children etc. #AtkTable provides the
+ * API needed to get a individual cell based on the row and column
+ * numbers.
+ *
+ * Children of #AtkTable are frequently "lightweight" objects, that
+ * is, they may not have backing widgets in the host UI toolkit. They
+ * are therefore often transient.
+ *
+ * Since tables are often very complex, #AtkTable includes provision
+ * for offering simplified summary information, as well as row and
+ * column headers and captions. Headers and captions are #AtkObjects
+ * which may implement other interfaces (#AtkText, #AtkImage, etc.) as
+ * appropriate. #AtkTable summaries may themselves be (simplified)
+ * #AtkTables, etc.
+ *
+ * Note for implementors: in the past, #AtkTable required that all the
+ * cells should be direct children of #AtkTable, and provided some
+ * index based methods to request the cells. The practice showed that
+ * that forcing made #AtkTable implementation complex, and hard to
+ * expose other kind of children, like rows or captions. Right now,
+ * index-based methods are deprecated.
+ */
+
+static jclass cachedTableAtkTableClass = NULL;
+static jmethodID cachedTableCreateAtkTableMethod = NULL;
+static jmethodID cachedTableRefAtMethod = NULL;
+static jmethodID cachedTableGetIndexAtMethod = NULL;
+static jmethodID cachedTableGetColumnAtIndexMethod = NULL;
+static jmethodID cachedTableGetRowAtIndexMethod = NULL;
+static jmethodID cachedTableGetNColumnsMethod = NULL;
+static jmethodID cachedTableGetNRowsMethod = NULL;
+static jmethodID cachedTableGetColumnExtentAtMethod = NULL;
+static jmethodID cachedTableGetRowExtentAtMethod = NULL;
+static jmethodID cachedTableGetCaptionMethod = NULL;
+static jmethodID cachedTableGetColumnDescriptionMethod = NULL;
+static jmethodID cachedTableGetRowDescriptionMethod = NULL;
+static jmethodID cachedTableGetColumnHeaderMethod = NULL;
+static jmethodID cachedTableGetRowHeaderMethod = NULL;
+static jmethodID cachedTableGetSummaryMethod = NULL;
+static jmethodID cachedTableGetSelectedColumnsMethod = NULL;
+static jmethodID cachedTableGetSelectedRowsMethod = NULL;
+static jmethodID cachedTableIsColumnSelectedMethod = NULL;
+static jmethodID cachedTableIsRowSelectedMethod = NULL;
+static jmethodID cachedTableIsSelectedMethod = NULL;
+static jmethodID cachedTableSetRowDescriptionMethod = NULL;
+static jmethodID cachedTableSetColumnDescriptionMethod = NULL;
+static jmethodID cachedTableSetCaptionMethod = NULL;
+static jmethodID cachedTableSetSummaryMethod = NULL;
+
+static GMutex cache_mutex;
+static gboolean cache_initialized = FALSE;
+
+static gboolean jaw_table_init_jni_cache(JNIEnv *jniEnv);
+
+static AtkObject *jaw_table_ref_at(AtkTable *table, gint row, gint column);
+static gint jaw_table_get_index_at(AtkTable *table, gint row, gint column);
+static gint jaw_table_get_column_at_index(AtkTable *table, gint index);
+static gint jaw_table_get_row_at_index(AtkTable *table, gint index);
+static gint jaw_table_get_n_columns(AtkTable *table);
+static gint jaw_table_get_n_rows(AtkTable *table);
+static gint jaw_table_get_column_extent_at(AtkTable *table, gint row,
+ gint column);
+static gint jaw_table_get_row_extent_at(AtkTable *table, gint row, gint column);
+static AtkObject *jaw_table_get_caption(AtkTable *table);
+static const gchar *jaw_table_get_column_description(AtkTable *table,
+ gint column);
+static const gchar *jaw_table_get_row_description(AtkTable *table, gint row);
+static AtkObject *jaw_table_get_column_header(AtkTable *table, gint column);
+static AtkObject *jaw_table_get_row_header(AtkTable *table, gint row);
+static AtkObject *jaw_table_get_summary(AtkTable *table);
+static gint jaw_table_get_selected_columns(AtkTable *table, gint **selected);
+static gint jaw_table_get_selected_rows(AtkTable *table, gint **selected);
+static gboolean jaw_table_is_column_selected(AtkTable *table, gint column);
+static gboolean jaw_table_is_row_selected(AtkTable *table, gint row);
+static gboolean jaw_table_is_selected(AtkTable *table, gint row, gint column);
+
+static void jaw_table_set_row_description(AtkTable *table, gint row,
+ const gchar *description);
+static void jaw_table_set_column_description(AtkTable *table, gint column,
+ const gchar *description);
+
+static void jaw_table_set_caption(AtkTable *table, AtkObject *caption);
+static void jaw_table_set_summary(AtkTable *table, AtkObject *summary);
+
+typedef struct _TableData {
+ jobject atk_table;
+ const gchar *row_or_column_description;
+ jstring jstrRowOrColumnDescription;
+ GMutex mutex;
+} TableData;
+
+#define JAW_GET_TABLE(table, def_ret) \
+ JAW_GET_OBJ_IFACE(table, \
+ org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE, \
+ TableData, atk_table, jniEnv, atk_table, def_ret)
+
+void jaw_table_interface_init(AtkTableIface *iface, gpointer data) {
+ JAW_DEBUG("%p, %p", iface, data);
+
+ if (iface == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ iface->ref_at = jaw_table_ref_at;
+ iface->get_index_at = jaw_table_get_index_at;
+ iface->get_column_at_index = jaw_table_get_column_at_index;
+ iface->get_row_at_index = jaw_table_get_row_at_index;
+ iface->get_n_columns = jaw_table_get_n_columns;
+ iface->get_n_rows = jaw_table_get_n_rows;
+ iface->get_column_extent_at = jaw_table_get_column_extent_at;
+ iface->get_row_extent_at = jaw_table_get_row_extent_at;
+ iface->get_caption = jaw_table_get_caption;
+ iface->get_column_description = jaw_table_get_column_description;
+ iface->get_column_header = jaw_table_get_column_header;
+ iface->get_row_description = jaw_table_get_row_description;
+ iface->get_row_header = jaw_table_get_row_header;
+ iface->get_summary = jaw_table_get_summary;
+ iface->set_caption = jaw_table_set_caption;
+ iface->set_column_description = jaw_table_set_column_description;
+ iface->set_column_header =
+ NULL; // impossible to do a on AccessibleTable Java Object
+ iface->set_row_description = jaw_table_set_row_description;
+ iface->set_row_header =
+ NULL; // impossible to do a on AccessibleTable Java Object
+ iface->set_summary = jaw_table_set_summary;
+ iface->get_selected_columns = jaw_table_get_selected_columns;
+ iface->get_selected_rows = jaw_table_get_selected_rows;
+ iface->is_column_selected = jaw_table_is_column_selected;
+ iface->is_row_selected = jaw_table_is_row_selected;
+ iface->is_selected = jaw_table_is_selected;
+ iface->add_row_selection = NULL;
+ iface->remove_row_selection = NULL;
+ iface->add_column_selection =
+ NULL; // impossible to do a on AccessibleTable Java Object
+ iface->remove_column_selection =
+ NULL; // impossible to do a on AccessibleTable Java Object
+}
+
+gpointer jaw_table_data_init(jobject ac) {
+ JAW_DEBUG("%p", ac);
+
+ if (ac == NULL) {
+ g_warning("%s: Null argument ac passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_table_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jatk_table = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedTableAtkTableClass, cachedTableCreateAtkTableMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_table == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning(
+ "%s: Failed to create jatk_table using create_atk_table method",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ TableData *data = g_new0(TableData, 1);
+ g_mutex_init(&data->mutex);
+ data->atk_table = (*jniEnv)->NewGlobalRef(jniEnv, jatk_table);
+ if (data->atk_table == NULL) {
+ g_warning("%s: Failed to create global ref for atk_table", G_STRFUNC);
+ g_mutex_clear(&data->mutex);
+ g_free(data);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data;
+}
+
+void jaw_table_data_finalize(gpointer p) {
+ JAW_DEBUG("%p", p);
+
+ if (p == NULL) {
+ g_warning("%s: Null argument p passed to the function", G_STRFUNC);
+ return;
+ }
+
+ TableData *data = (TableData *)p;
+ if (data == NULL) {
+ g_warning("%s: data is NULL in finalize", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&data->mutex);
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+
+ if (jniEnv == NULL) {
+ g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC);
+ } else {
+ if (data->jstrRowOrColumnDescription != NULL) {
+ if (data->row_or_column_description != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(
+ jniEnv, data->jstrRowOrColumnDescription,
+ data->row_or_column_description);
+ data->row_or_column_description = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrRowOrColumnDescription);
+ data->jstrRowOrColumnDescription = NULL;
+ }
+ if (data->atk_table != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_table);
+ data->atk_table = NULL;
+ }
+ }
+
+ g_mutex_unlock(&data->mutex);
+ g_mutex_clear(&data->mutex);
+ g_free(data);
+}
+
+/**
+ * jaw_table_ref_at:
+ * @table: a GObject instance that implements AtkTableIface
+ * @row: a #gint representing a row in @table
+ * @column: a #gint representing a column in @table
+ *
+ * Get a reference to the table cell at @row, @column. This cell
+ * should implement the interface #AtkTableCell
+ *
+ * Returns: (transfer full): an #AtkObject representing the referred
+ * to accessible
+ **/
+static AtkObject *jaw_table_ref_at(AtkTable *table, gint row, gint column) {
+ JAW_DEBUG("%p, %d, %d", table, row, column);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JawObject *jaw_obj = JAW_OBJECT(table);
+ if (jaw_obj == NULL) {
+ g_warning("%s: jaw_obj is NULL", G_STRFUNC);
+ return NULL;
+ }
+ TableData *data = jaw_object_get_interface_data(
+ jaw_obj, org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE);
+ if (data == NULL) {
+ g_warning("%s: data is NULL", G_STRFUNC);
+ return NULL;
+ }
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return NULL;
+ }
+ jobject atk_table = (*jniEnv)->NewGlobalRef(jniEnv, data->atk_table);
+ if (atk_table == NULL) {
+ g_warning("%s: atk_table is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jac = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_table, cachedTableRefAtMethod, (jint)row, (jint)column);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jac == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jac using ref_at method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac);
+
+ // From the documentation of the `ref_at`:
+ // "The caller of the method takes ownership of the returned data, and is
+ // responsible for freeing it." (transfer full)
+ if (jaw_impl != NULL) {
+ g_object_ref(G_OBJECT(jaw_impl));
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return ATK_OBJECT(jaw_impl);
+}
+
+/**
+ * jaw_table_get_index_at:
+ * @table: a GObject instance that implements AtkTableIface
+ * @row: a #gint representing a row in @table
+ * @column: a #gint representing a column in @table
+ *
+ * Gets a #gint representing the index at the specified @row and
+ * @column.
+ *
+ * Deprecated in atk: Since 2.12. Use atk_table_ref_at() in order to get the
+ * accessible that represents the cell at (@row, @column)
+ *
+ * Returns: a #gint representing the index at specified position.
+ * The value -1 is returned if the object at row,column is not a child
+ * of table or table does not implement this interface.
+ **/
+static gint jaw_table_get_index_at(AtkTable *table, gint row, gint column) {
+ JAW_DEBUG("%p, %d, %d", table, row, column);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return -1;
+ }
+
+ JAW_GET_TABLE(table, -1); // create local JNI reference `jobject atk_table`
+
+ jint jindex =
+ (*jniEnv)->CallIntMethod(jniEnv, atk_table, cachedTableGetIndexAtMethod,
+ (jint)row, (jint)column);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_index_at method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ return -1;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+
+ return jindex;
+}
+
+/**
+ * jaw_table_get_column_at_index:
+ * @table: a GObject instance that implements AtkTableInterface
+ * @index_: a #gint representing an index in @table
+ *
+ * Gets a #gint representing the column at the specified @index_.
+ *
+ * Deprecated in atk: Since 2.12.
+ *
+ * Returns: a gint representing the column at the specified index,
+ * or -1 if the table does not implement this method.
+ **/
+static gint jaw_table_get_column_at_index(AtkTable *table, gint index) {
+ JAW_DEBUG("%p, %d", table, index);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return -1;
+ }
+
+ JAW_GET_TABLE(table, 0); // create local JNI reference `jobject atk_table`
+
+ jint jcolumn = (*jniEnv)->CallIntMethod(
+ jniEnv, atk_table, cachedTableGetColumnAtIndexMethod, (jint)index);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_column_at_index method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ return -1;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+
+ return jcolumn;
+}
+
+/**
+ * atk_table_get_row_at_index:
+ * @table: a GObject instance that implements AtkTableInterface
+ * @index_: a #gint representing an index in @table
+ *
+ * Gets a #gint representing the row at the specified @index_.
+ *
+ * Deprecated in atk: since 2.12.
+ *
+ * Returns: a gint representing the row at the specified index,
+ * or -1 if the table does not implement this method.
+ **/
+static gint jaw_table_get_row_at_index(AtkTable *table, gint index) {
+ JAW_DEBUG("%p, %d", table, index);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return -1;
+ }
+
+ JAW_GET_TABLE(table, -1); // create local JNI reference `jobject atk_table`
+
+ jint jrow = (*jniEnv)->CallIntMethod(
+ jniEnv, atk_table, cachedTableGetRowAtIndexMethod, (jint)index);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_row_at_index method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ return -1;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+
+ return jrow;
+}
+
+/**
+ * atk_table_get_n_columns:
+ * @table: a GObject instance that implements AtkTableIface
+ *
+ * Gets the number of columns in the table.
+ *
+ * Returns: a gint representing the number of columns, or 0
+ * if value does not implement this interface.
+ **/
+static gint jaw_table_get_n_columns(AtkTable *table) {
+ JAW_DEBUG("%p", table);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_TABLE(table, 0); // create local JNI reference `jobject atk_table`
+
+ jint jcolumns = (*jniEnv)->CallIntMethod(jniEnv, atk_table,
+ cachedTableGetNColumnsMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_n_columns method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+
+ return jcolumns;
+}
+
+/**
+ * jaw_table_get_n_rows:
+ * @table: a GObject instance that implements AtkTableIface
+ *
+ * Gets the number of rows in the table.
+ *
+ * Returns: a gint representing the number of rows, or 0
+ * if value does not implement this interface.
+ **/
+static gint jaw_table_get_n_rows(AtkTable *table) {
+ JAW_DEBUG("%p", table);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_TABLE(table, 0); // create local JNI reference `jobject atk_table`
+
+ jint jrows =
+ (*jniEnv)->CallIntMethod(jniEnv, atk_table, cachedTableGetNRowsMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_n_rows method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+
+ return jrows;
+}
+
+/**
+ * jaw_table_get_column_extent_at:
+ * @table: a GObject instance that implements AtkTableIface
+ * @row: a #gint representing a row in @table
+ * @column: a #gint representing a column in @table
+ *
+ * Gets the number of columns occupied by the accessible object
+ * at the specified @row and @column in the @table.
+ *
+ * Returns: a gint representing the column extent at specified position, or 0
+ * if value does not implement this interface.
+ **/
+static gint jaw_table_get_column_extent_at(AtkTable *table, gint row,
+ gint column) {
+ JAW_DEBUG("%p, %d, %d", table, row, column);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_TABLE(table, 0); // create local JNI reference `jobject atk_table`
+
+ jint jextent = (*jniEnv)->CallIntMethod(jniEnv, atk_table,
+ cachedTableGetColumnExtentAtMethod,
+ (jint)row, (jint)column);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_column_extent_at method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+
+ return jextent;
+}
+
+/**
+ * jaw_table_get_row_extent_at:
+ * @table: a GObject instance that implements AtkTableIface
+ * @column: a #gint representing a column in @table
+ *
+ * Gets the description text of the specified @column in the table
+ *
+ * Returns: a gchar* representing the column description, or %NULL
+ * if value does not implement this interface.
+ **/
+static gint jaw_table_get_row_extent_at(AtkTable *table, gint row,
+ gint column) {
+ JAW_DEBUG("%p, %d, %d", table, row, column);
+
+ if (table == NULL) {
+ g_warning("Null argument table passed to function "
+ "jaw_table_get_row_extent_at");
+ return 0;
+ }
+
+ JAW_GET_TABLE(table, 0); // create local JNI reference `jobject atk_table`
+
+ jint jextent = (*jniEnv)->CallIntMethod(jniEnv, atk_table,
+ cachedTableGetRowExtentAtMethod,
+ (jint)row, (jint)column);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_row_extent_at method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+
+ return jextent;
+}
+
+/**
+ * jaw_table_get_caption:
+ * @table: a GObject instance that implements AtkTableInterface
+ *
+ * Gets the caption for the @table.
+ *
+ * Returns: (nullable) (transfer none): a AtkObject* representing the
+ * table caption, or %NULL if value does not implement this interface.
+ **/
+static AtkObject *jaw_table_get_caption(AtkTable *table) {
+ JAW_DEBUG("%p", table);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_TABLE(table,
+ NULL); // create local JNI reference `jobject atk_table`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jac = (*jniEnv)->CallObjectMethod(jniEnv, atk_table,
+ cachedTableGetCaptionMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jac == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_caption method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac);
+
+ // From documentation of the `get_caption` in AtkObject:
+ // The returned data is owned by the instance (transfer none), so we don't
+ // ref the obj before returning it.
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return ATK_OBJECT(jaw_impl);
+}
+
+/**
+ * jaw_table_get_column_description:
+ * @table: a GObject instance that implements AtkTableIface
+ * @column: a #gint representing a column in @table
+ * @description: a #gchar representing the description text
+ * to set for the specified @column of the @table
+ *
+ * Sets the description text for the specified @column of the @table.
+ **/
+static const gchar *jaw_table_get_column_description(AtkTable *table,
+ gint column) {
+ JAW_DEBUG("%p, %d", table, column);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_TABLE(table,
+ NULL); // create local JNI reference `jobject atk_table`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jstring jstr = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_table, cachedTableGetColumnDescriptionMethod, (jint)column);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_column_description method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ g_mutex_lock(&data->mutex);
+ if (data->jstrRowOrColumnDescription != NULL) {
+ if (data->row_or_column_description != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(
+ jniEnv, data->jstrRowOrColumnDescription,
+ data->row_or_column_description);
+ data->row_or_column_description = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrRowOrColumnDescription);
+ data->jstrRowOrColumnDescription = NULL;
+ }
+
+ data->jstrRowOrColumnDescription = (*jniEnv)->NewGlobalRef(jniEnv, jstr);
+ if (data->jstrRowOrColumnDescription == NULL) {
+ g_mutex_unlock(&data->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ data->row_or_column_description = (*jniEnv)->GetStringUTFChars(
+ jniEnv, data->jstrRowOrColumnDescription, NULL);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ data->row_or_column_description == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ // data->jstrRowOrColumnDescription != NULL
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrRowOrColumnDescription);
+ data->jstrRowOrColumnDescription = NULL;
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ g_mutex_unlock(&data->mutex);
+ return NULL;
+ }
+
+ g_mutex_unlock(&data->mutex);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data->row_or_column_description;
+}
+
+/**
+ * jaw_table_get_row_description:
+ * @table: a GObject instance that implements AtkTableIface
+ * @row: a #gint representing a row in @table
+ *
+ * Gets the description text of the specified row in the table
+ *
+ * Returns: (nullable): a gchar* representing the row description, or
+ * %NULL if value does not implement this interface.
+ **/
+static const gchar *jaw_table_get_row_description(AtkTable *table, gint row) {
+ JAW_DEBUG("%p, %d", table, row);
+
+ if (table == NULL) {
+ g_warning("Null argument passed table to function "
+ "jaw_table_get_row_description");
+ return NULL;
+ }
+
+ JAW_GET_TABLE(table,
+ NULL); // create local JNI reference `jobject atk_table`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jstring jstr = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_table, cachedTableGetRowDescriptionMethod, (jint)row);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_row_description method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ g_mutex_lock(&data->mutex);
+ if (data->jstrRowOrColumnDescription != NULL) {
+ if (data->row_or_column_description != NULL) {
+ (*jniEnv)->ReleaseStringUTFChars(
+ jniEnv, data->jstrRowOrColumnDescription,
+ data->row_or_column_description);
+ data->row_or_column_description = NULL;
+ }
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrRowOrColumnDescription);
+ data->jstrRowOrColumnDescription = NULL;
+ }
+
+ data->jstrRowOrColumnDescription = (*jniEnv)->NewGlobalRef(jniEnv, jstr);
+ if (data->jstrRowOrColumnDescription == NULL) {
+ g_mutex_unlock(&data->mutex);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ data->row_or_column_description = (*jniEnv)->GetStringUTFChars(
+ jniEnv, data->jstrRowOrColumnDescription, NULL);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ data->row_or_column_description == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+
+ // data->jstrRowOrColumnDescription != NULL
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->jstrRowOrColumnDescription);
+ data->jstrRowOrColumnDescription = NULL;
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ g_mutex_unlock(&data->mutex);
+ return NULL;
+ }
+
+ g_mutex_unlock(&data->mutex);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data->row_or_column_description;
+}
+
+/**
+ * jaw_table_get_column_header:
+ * @table: a GObject instance that implements AtkTableIface
+ * @column: a #gint representing a column in the table
+ *
+ * Gets the column header of a specified column in an accessible table.
+ *
+ * Returns: (nullable) (transfer none): a AtkObject* representing the
+ * specified column header, or %NULL if value does not implement this
+ * interface.
+ **/
+static AtkObject *jaw_table_get_column_header(AtkTable *table, gint column) {
+ JAW_DEBUG("%p, %d", table, column);
+
+ if (table == NULL) {
+ g_warning("Null argument table passed to function "
+ "jaw_table_get_column_header");
+ return NULL;
+ }
+
+ JAW_GET_TABLE(table,
+ NULL); // create local JNI reference `jobject atk_table`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jac = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_table, cachedTableGetColumnHeaderMethod, (jint)column);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jac == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_column_header method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac);
+ // From documentation of the `atk_table_get_column_header` in AtkObject:
+ // The returned data is owned by the instance (transfer none), so we don't
+ // ref the obj before returning it.
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return ATK_OBJECT(jaw_impl);
+}
+
+/**
+ * jaw_table_get_row_header:
+ * @table: a GObject instance that implements AtkTableIface
+ * @row: a #gint representing a row in the table
+ *
+ * Gets the row header of a specified row in an accessible table.
+ *
+ * Returns: (nullable) (transfer none): a AtkObject* representing the
+ * specified row header, or %NULL if value does not implement this
+ * interface.
+ **/
+static AtkObject *jaw_table_get_row_header(AtkTable *table, gint row) {
+ JAW_DEBUG("%p, %d", table, row);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_TABLE(table,
+ NULL); // create local JNI reference `jobject atk_table`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jac = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_table, cachedTableGetRowHeaderMethod, (jint)row);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jac == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_row_header method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac);
+ // From documentation of the `atk_table_get_row_header` in AtkObject:
+ // The returned data is owned by the instance (transfer none), so we don't
+ // ref the jaw_impl before returning it.
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return ATK_OBJECT(jaw_impl);
+}
+
+/**
+ * jaw_table_get_summary:
+ * @table: a GObject instance that implements AtkTableIface
+ *
+ * Gets the summary description of the table.
+ *
+ * Returns: (transfer full): a AtkObject* representing a summary description
+ * of the table, or zero if value does not implement this interface.
+ **/
+static AtkObject *jaw_table_get_summary(AtkTable *table) {
+ JAW_DEBUG("%p", table);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_TABLE(table,
+ NULL); // create local JNI reference `jobject atk_table`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jac = (*jniEnv)->CallObjectMethod(jniEnv, atk_table,
+ cachedTableGetSummaryMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jac == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_summary method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac);
+ // From the documentation of the `get_summary`:
+ // "The caller of the method takes ownership of the returned data, and is
+ // responsible for freeing it." (transfer full)
+ if (jaw_impl != NULL) {
+ g_object_ref(G_OBJECT(jaw_impl));
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return ATK_OBJECT(jaw_impl);
+}
+
+/**
+ * jaw_table_get_selected_columns:
+ * @table: a GObject instance that implements AtkTableIface
+ * @selected: a #gint** that is to contain the selected columns numbers
+ *
+ * Gets the selected columns of the table by initializing **selected with
+ * the selected column numbers. This array should be freed by the caller.
+ *
+ * Returns: a gint representing the number of selected columns,
+ * or %0 if value does not implement this interface.
+ **/
+static gint jaw_table_get_selected_columns(AtkTable *table, gint **selected) {
+ JAW_DEBUG("%p, %p", table, selected);
+
+ if (selected == NULL) {
+ g_warning("%s: Null argument passed selected to function", G_STRFUNC);
+ return 0;
+ }
+ *selected = NULL;
+
+ if (table == NULL) {
+ g_warning("%s: Null argument passed table to function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_TABLE(table, 0);
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return 0;
+ }
+
+ jintArray jcolumnArray = (jintArray)((*jniEnv)->CallObjectMethod(
+ jniEnv, atk_table, cachedTableGetSelectedColumnsMethod));
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jcolumnArray == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_selected_columns method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ jsize length = (*jniEnv)->GetArrayLength(jniEnv, jcolumnArray);
+ if (length <= 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ jint *tmp = (*jniEnv)->GetIntArrayElements(jniEnv, jcolumnArray, NULL);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || tmp == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to read selected columns array", G_STRFUNC);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ *selected = g_new(gint, (gsize)length);
+
+ for (jsize i = 0; i < length; i++) {
+ (*selected)[i] = (gint)tmp[i];
+ }
+
+ (*jniEnv)->ReleaseIntArrayElements(jniEnv, jcolumnArray, tmp, JNI_ABORT);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return (gint)length;
+}
+
+/**
+ * jaw_table_get_selected_rows:
+ * @table: a GObject instance that implements AtkTableIface
+ * @selected: a #gint** that is to contain the selected row numbers
+ *
+ * Gets the selected rows of the table by initializing **selected with
+ * the selected row numbers. This array should be freed by the caller.
+ *
+ * Returns: a gint representing the number of selected rows,
+ * or zero if value does not implement this interface.
+ **/
+static gint jaw_table_get_selected_rows(AtkTable *table, gint **selected) {
+ JAW_DEBUG("%p, %p", table, selected);
+
+ if (selected == NULL) {
+ g_warning("%s: Null argument passed selected to function", G_STRFUNC);
+ return 0;
+ }
+ *selected = NULL;
+
+ if (table == NULL) {
+ g_warning("%s: Null argument passed table to function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_TABLE(table, 0);
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return 0;
+ }
+
+ jintArray jrowArray = (jintArray)((*jniEnv)->CallObjectMethod(
+ jniEnv, atk_table, cachedTableGetSelectedRowsMethod));
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jrowArray == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_selected_rows method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ jsize length = (*jniEnv)->GetArrayLength(jniEnv, jrowArray);
+ if (length <= 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ jint *tmp = (*jniEnv)->GetIntArrayElements(jniEnv, jrowArray, NULL);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || tmp == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to read selected rows array", G_STRFUNC);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ *selected = g_new(gint, (gsize)length);
+ for (jsize i = 0; i < length; i++) {
+ (*selected)[i] = (gint)tmp[i];
+ }
+
+ (*jniEnv)->ReleaseIntArrayElements(jniEnv, jrowArray, tmp, JNI_ABORT);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return (gint)length;
+}
+
+/**
+ * jaw_table_is_column_selected:
+ * @table: a GObject instance that implements AtkTableIface
+ * @column: a #gint representing a column in @table
+ *
+ * Gets a boolean value indicating whether the specified @column
+ * is selected
+ *
+ * Returns: a gboolean representing if the column is selected, or 0
+ * if value does not implement this interface.
+ **/
+static gboolean jaw_table_is_column_selected(AtkTable *table, gint column) {
+ JAW_DEBUG("%p, %d", table, column);
+
+ if (table == NULL) {
+ g_warning("Null argument table passed to function "
+ "jaw_table_is_column_selected");
+ return FALSE;
+ }
+
+ JAW_GET_TABLE(table, FALSE);
+
+ jboolean jselected = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_table, cachedTableIsColumnSelectedMethod, (jint)column);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call is_column_selected method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+
+ return jselected;
+}
+
+/**
+ * jaw_table_is_row_selected:
+ * @table: a GObject instance that implements AtkTableIface
+ * @row: a #gint representing a row in @table
+ *
+ * Gets a boolean value indicating whether the specified @row
+ * is selected
+ *
+ * Returns: a gboolean representing if the row is selected, or 0
+ * if value does not implement this interface.
+ **/
+static gboolean jaw_table_is_row_selected(AtkTable *table, gint row) {
+ JAW_DEBUG("%p, %d", table, row);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_TABLE(table, FALSE);
+
+ jboolean jselected = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_table, cachedTableIsRowSelectedMethod, (jint)row);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call is_row_selected method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+
+ return jselected;
+}
+
+/**
+ * jaw_table_is_selected:
+ * @table: a GObject instance that implements AtkTableIface
+ * @row: a #gint representing a row in @table
+ * @column: a #gint representing a column in @table
+ *
+ * Gets a boolean value indicating whether the accessible object
+ * at the specified @row and @column is selected
+ *
+ * Returns: a gboolean representing if the cell is selected, or 0
+ * if value does not implement this interface.
+ **/
+static gboolean jaw_table_is_selected(AtkTable *table, gint row, gint column) {
+ JAW_DEBUG("%p, %d, %d", table, row, column);
+
+ if (table == NULL) {
+ g_warning("%s: Null argument table passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_TABLE(table, FALSE);
+
+ jboolean jselected = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_table, cachedTableIsSelectedMethod, (jint)row,
+ (jint)column);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call is_selected method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+
+ return jselected;
+}
+
+/**
+ * jaw_table_set_row_description:
+ * @table: a GObject instance that implements AtkTableIface
+ * @row: a #gint representing a row in @table
+ * @description: a #gchar representing the description text
+ * to set for the specified @row of @table
+ *
+ * Sets the description text for the specified @row of @table.
+ **/
+static void jaw_table_set_row_description(AtkTable *table, gint row,
+ const gchar *description) {
+ JAW_DEBUG("%p, %d, %s", table, row, description);
+
+ if (!table || !description) {
+ g_warning(
+ "Null argument passed to function jaw_table_set_row_description");
+ return;
+ }
+
+ JAW_GET_TABLE(table, );
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ jstring jstr = (*jniEnv)->NewStringUTF(jniEnv, description);
+ if (jstr == NULL) {
+ g_warning("%s: Failed to create jstr from description", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->CallVoidMethod(
+ jniEnv, atk_table, cachedTableSetRowDescriptionMethod, (jint)row, jstr);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call set_row_description method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * jaw_table_set_column_description:
+ * @table: a GObject instance that implements AtkTableIface
+ * @column: a #gint representing a column in @table
+ * @description: a #gchar representing the description text
+ * to set for the specified @column of the @table
+ *
+ * Sets the description text for the specified @column of the @table.
+ **/
+static void jaw_table_set_column_description(AtkTable *table, gint column,
+ const gchar *description) {
+ JAW_DEBUG("%p, %d, %s", table, column, description);
+
+ if (!table || !description) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_TABLE(table, );
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ jstring jstr = (*jniEnv)->NewStringUTF(jniEnv, description);
+ if (jstr == NULL) {
+ g_warning("%s: Failed to create jstr from description", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->CallVoidMethod(jniEnv, atk_table,
+ cachedTableSetColumnDescriptionMethod,
+ (jint)column, jstr);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call set_column_description method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * jaw_table_set_caption:
+ * @table: a GObject instance that implements AtkTableIface
+ * @caption: a #AtkObject representing the caption to set for @table
+ *
+ * Sets the caption for the table.
+ **/
+static void jaw_table_set_caption(AtkTable *table, AtkObject *caption) {
+ JAW_DEBUG("%p, %p", table, caption);
+
+ if (!table || !caption) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_TABLE(table, );
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ JawObject *jcaption = JAW_OBJECT(caption);
+ if (jcaption == NULL) {
+ g_warning("%s: jcaption == NULL", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+ jclass accessible =
+ (*jniEnv)->FindClass(jniEnv, "javax/accessibility/Accessible");
+ if (accessible == NULL) {
+ g_warning("%s: Failed to find Accessible class", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+ if (!(*jniEnv)->IsInstanceOf(jniEnv, jcaption->acc_context, accessible)) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+ jobject obj = (*jniEnv)->NewLocalRef(jniEnv, jcaption->acc_context);
+ if (obj == NULL) {
+ g_warning("%s: jcaption obj == NULL", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->CallVoidMethod(jniEnv, atk_table, cachedTableSetCaptionMethod,
+ obj);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call set_caption method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * jaw_table_set_summary:
+ * @table: a GObject instance that implements AtkTableIface
+ * @accessible: an #AtkObject representing the summary description
+ * to set for @table
+ *
+ * Sets the summary description of the table.
+ **/
+static void jaw_table_set_summary(AtkTable *table, AtkObject *summary) {
+ JAW_DEBUG("%p, %p", table, summary);
+
+ if (!table || !summary) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_TABLE(table, );
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ JawObject *jsummary = JAW_OBJECT(summary);
+ if (jsummary == NULL) {
+ g_warning("%s: jsummary == NULL", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+ jclass accessible =
+ (*jniEnv)->FindClass(jniEnv, "javax/accessibility/Accessible");
+ if (accessible == NULL) {
+ g_warning("%s: Failed to find Accessible class", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+ if (!(*jniEnv)->IsInstanceOf(jniEnv, jsummary->acc_context, accessible)) {
+ g_warning("%s: jsummary->acc_context is not instance of accessible",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+ jobject obj = (*jniEnv)->NewLocalRef(jniEnv, jsummary->acc_context);
+ if (obj == NULL) {
+ g_warning("%s: jsummary obj == NULL", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->CallVoidMethod(jniEnv, atk_table, cachedTableSetSummaryMethod,
+ obj);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call set_summary method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_table);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+static gboolean jaw_table_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localClass =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkTable");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkTable class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedTableAtkTableClass = (*jniEnv)->NewGlobalRef(jniEnv, localClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClass);
+
+ if (cachedTableAtkTableClass == NULL) {
+ g_warning("%s: Failed to create global reference for AtkTable class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedTableCreateAtkTableMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedTableAtkTableClass, "create_atk_table",
+ "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/"
+ "AtkTable;");
+
+ cachedTableRefAtMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass, "ref_at",
+ "(II)Ljavax/accessibility/AccessibleContext;");
+
+ cachedTableGetIndexAtMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "get_index_at", "(II)I");
+
+ cachedTableGetColumnAtIndexMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "get_column_at_index", "(I)I");
+
+ cachedTableGetRowAtIndexMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "get_row_at_index", "(I)I");
+
+ cachedTableGetNColumnsMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "get_n_columns", "()I");
+
+ cachedTableGetNRowsMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "get_n_rows", "()I");
+
+ cachedTableGetColumnExtentAtMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "get_column_extent_at", "(II)I");
+
+ cachedTableGetRowExtentAtMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "get_row_extent_at", "(II)I");
+
+ cachedTableGetCaptionMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass, "get_caption",
+ "()Ljavax/accessibility/AccessibleContext;");
+
+ cachedTableGetColumnDescriptionMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "get_column_description",
+ "(I)Ljava/lang/String;");
+
+ cachedTableGetRowDescriptionMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass,
+ "get_row_description", "(I)Ljava/lang/String;");
+
+ cachedTableGetColumnHeaderMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "get_column_header",
+ "(I)Ljavax/accessibility/AccessibleContext;");
+
+ cachedTableGetRowHeaderMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "get_row_header",
+ "(I)Ljavax/accessibility/AccessibleContext;");
+
+ cachedTableGetSummaryMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass, "get_summary",
+ "()Ljavax/accessibility/AccessibleContext;");
+
+ cachedTableGetSelectedColumnsMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "get_selected_columns", "()[I");
+
+ cachedTableGetSelectedRowsMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "get_selected_rows", "()[I");
+
+ cachedTableIsColumnSelectedMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "is_column_selected", "(I)Z");
+
+ cachedTableIsRowSelectedMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "is_row_selected", "(I)Z");
+
+ cachedTableIsSelectedMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "is_selected", "(II)Z");
+
+ cachedTableSetRowDescriptionMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass,
+ "set_row_description", "(ILjava/lang/String;)V");
+
+ cachedTableSetColumnDescriptionMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableAtkTableClass, "set_column_description",
+ "(ILjava/lang/String;)V");
+
+ cachedTableSetCaptionMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass, "set_caption",
+ "(Ljavax/accessibility/Accessible;)V");
+
+ cachedTableSetSummaryMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedTableAtkTableClass, "set_summary",
+ "(Ljavax/accessibility/Accessible;)V");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedTableCreateAtkTableMethod == NULL ||
+ cachedTableRefAtMethod == NULL || cachedTableGetIndexAtMethod == NULL ||
+ cachedTableGetColumnAtIndexMethod == NULL ||
+ cachedTableGetRowAtIndexMethod == NULL ||
+ cachedTableGetNColumnsMethod == NULL ||
+ cachedTableGetNRowsMethod == NULL ||
+ cachedTableGetColumnExtentAtMethod == NULL ||
+ cachedTableGetRowExtentAtMethod == NULL ||
+ cachedTableGetCaptionMethod == NULL ||
+ cachedTableGetColumnDescriptionMethod == NULL ||
+ cachedTableGetRowDescriptionMethod == NULL ||
+ cachedTableGetColumnHeaderMethod == NULL ||
+ cachedTableGetRowHeaderMethod == NULL ||
+ cachedTableGetSummaryMethod == NULL ||
+ cachedTableGetSelectedColumnsMethod == NULL ||
+ cachedTableGetSelectedRowsMethod == NULL ||
+ cachedTableIsColumnSelectedMethod == NULL ||
+ cachedTableIsRowSelectedMethod == NULL ||
+ cachedTableIsSelectedMethod == NULL ||
+ cachedTableSetRowDescriptionMethod == NULL ||
+ cachedTableSetColumnDescriptionMethod == NULL ||
+ cachedTableSetCaptionMethod == NULL ||
+ cachedTableSetSummaryMethod == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache one or more AtkTable method IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedTableAtkTableClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTableAtkTableClass);
+ cachedTableAtkTableClass = NULL;
+ }
+ cachedTableCreateAtkTableMethod = NULL;
+ cachedTableRefAtMethod = NULL;
+ cachedTableGetIndexAtMethod = NULL;
+ cachedTableGetColumnAtIndexMethod = NULL;
+ cachedTableGetRowAtIndexMethod = NULL;
+ cachedTableGetNColumnsMethod = NULL;
+ cachedTableGetNRowsMethod = NULL;
+ cachedTableGetColumnExtentAtMethod = NULL;
+ cachedTableGetRowExtentAtMethod = NULL;
+ cachedTableGetCaptionMethod = NULL;
+ cachedTableGetColumnDescriptionMethod = NULL;
+ cachedTableGetRowDescriptionMethod = NULL;
+ cachedTableGetColumnHeaderMethod = NULL;
+ cachedTableGetRowHeaderMethod = NULL;
+ cachedTableGetSummaryMethod = NULL;
+ cachedTableGetSelectedColumnsMethod = NULL;
+ cachedTableGetSelectedRowsMethod = NULL;
+ cachedTableIsColumnSelectedMethod = NULL;
+ cachedTableIsRowSelectedMethod = NULL;
+ cachedTableIsSelectedMethod = NULL;
+ cachedTableSetRowDescriptionMethod = NULL;
+ cachedTableSetColumnDescriptionMethod = NULL;
+ cachedTableSetCaptionMethod = NULL;
+ cachedTableSetSummaryMethod = NULL;
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+}
+
+void jaw_table_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedTableAtkTableClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTableAtkTableClass);
+ cachedTableAtkTableClass = NULL;
+ }
+ cachedTableCreateAtkTableMethod = NULL;
+ cachedTableRefAtMethod = NULL;
+ cachedTableGetIndexAtMethod = NULL;
+ cachedTableGetColumnAtIndexMethod = NULL;
+ cachedTableGetRowAtIndexMethod = NULL;
+ cachedTableGetNColumnsMethod = NULL;
+ cachedTableGetNRowsMethod = NULL;
+ cachedTableGetColumnExtentAtMethod = NULL;
+ cachedTableGetRowExtentAtMethod = NULL;
+ cachedTableGetCaptionMethod = NULL;
+ cachedTableGetColumnDescriptionMethod = NULL;
+ cachedTableGetRowDescriptionMethod = NULL;
+ cachedTableGetColumnHeaderMethod = NULL;
+ cachedTableGetRowHeaderMethod = NULL;
+ cachedTableGetSummaryMethod = NULL;
+ cachedTableGetSelectedColumnsMethod = NULL;
+ cachedTableGetSelectedRowsMethod = NULL;
+ cachedTableIsColumnSelectedMethod = NULL;
+ cachedTableIsRowSelectedMethod = NULL;
+ cachedTableIsSelectedMethod = NULL;
+ cachedTableSetRowDescriptionMethod = NULL;
+ cachedTableSetColumnDescriptionMethod = NULL;
+ cachedTableSetCaptionMethod = NULL;
+ cachedTableSetSummaryMethod = NULL;
+ cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawtablecell.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtablecell.c
new file mode 100644
index 000000000000..02d09ecafc95
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtablecell.c
@@ -0,0 +1,775 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 021101301 USA
+ */
+
+#include "jawcache.h"
+#include "jawimpl.h"
+#include "jawutil.h"
+#include
+#include
+
+/**
+ * (From Atk documentation)
+ *
+ * AtkTableCell:
+ *
+ * The ATK interface implemented for a cell inside a two-dimentional #AtkTable
+ *
+ * Being #AtkTable a component which present elements ordered via rows
+ * and columns, an #AtkTableCell is the interface which each of those
+ * elements, so "cells" should implement.
+ *
+ * See [iface@AtkTable]
+ */
+
+static jclass cachedTableCellAtkTableCellClass = NULL;
+static jmethodID cachedTableCellCreateAtkTableCellMethod = NULL;
+static jmethodID cachedTableCellGetTableMethod = NULL;
+static jmethodID cachedTableCellGetAccessibleColumnHeaderMethod = NULL;
+static jmethodID cachedTableCellGetAccessibleRowHeaderMethod = NULL;
+static jfieldID cachedTableCellRowFieldID = NULL;
+static jfieldID cachedTableCellColumnFieldID = NULL;
+static jfieldID cachedTableCellRowSpanFieldID = NULL;
+static jfieldID cachedTableCellColumnSpanFieldID = NULL;
+
+static GMutex cache_mutex;
+static gboolean cache_initialized = FALSE;
+
+static gboolean jaw_table_cell_init_jni_cache(JNIEnv *jniEnv);
+
+static AtkObject *jaw_table_cell_get_table(AtkTableCell *cell);
+static GPtrArray *jaw_table_cell_get_column_header_cells(AtkTableCell *cell);
+static gboolean jaw_table_cell_get_position(AtkTableCell *cell, gint *row,
+ gint *column);
+static gboolean jaw_table_cell_get_row_column_span(AtkTableCell *cell,
+ gint *row, gint *column,
+ gint *row_span,
+ gint *column_span);
+static gint jaw_table_cell_get_row_span(AtkTableCell *cell);
+static GPtrArray *jaw_table_cell_get_row_header_cells(AtkTableCell *cell);
+static gint jaw_table_cell_get_column_span(AtkTableCell *cell);
+
+typedef struct _TableCellData {
+ jobject atk_table_cell;
+} TableCellData;
+
+#define JAW_GET_TABLECELL(cell, def_ret) \
+ JAW_GET_OBJ_IFACE( \
+ cell, org_GNOME_Accessibility_AtkInterface_INTERFACE_TABLE_CELL, \
+ TableCellData, atk_table_cell, jniEnv, jatk_table_cell, def_ret)
+
+/**
+ * AtkTableCellIface:
+ * @get_column_span: virtual function that returns the number of
+ * columns occupied by this cell accessible
+ * @get_column_header_cells: virtual function that returns the column
+ * headers as an array of cell accessibles
+ * @get_position: virtual function that retrieves the tabular position
+ * of this cell
+ * @get_row_span: virtual function that returns the number of rows
+ * occupied by this cell
+ * @get_row_header_cells: virtual function that returns the row
+ * headers as an array of cell accessibles
+ * @get_row_column_span: virtual function that get the row an column
+ * indexes and span of this cell
+ * @get_table: virtual function that returns a reference to the
+ * accessible of the containing table
+ *
+ * AtkTableCell is an interface for cells inside an #AtkTable.
+ *
+ * Since: 2.12
+ */
+void jaw_table_cell_interface_init(AtkTableCellIface *iface, gpointer data) {
+ JAW_DEBUG("%p, %p", iface, data);
+
+ if (iface == NULL) {
+ g_warning("%s: Null argument iface passed to the function", G_STRFUNC);
+ return;
+ }
+
+ iface->get_column_span = jaw_table_cell_get_column_span;
+ iface->get_column_header_cells = jaw_table_cell_get_column_header_cells;
+ iface->get_position = jaw_table_cell_get_position;
+ iface->get_row_span = jaw_table_cell_get_row_span;
+ iface->get_row_header_cells = jaw_table_cell_get_row_header_cells;
+ iface->get_row_column_span = jaw_table_cell_get_row_column_span;
+ iface->get_table = jaw_table_cell_get_table;
+}
+
+gpointer jaw_table_cell_data_init(jobject ac) {
+ JAW_DEBUG("%p", ac);
+
+ if (ac == NULL) {
+ g_warning("%s: Null argument ac passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_table_cell_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jatk_table_cell = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedTableCellAtkTableCellClass,
+ cachedTableCellCreateAtkTableCellMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_table_cell == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jatk_table_cell using "
+ "create_atk_table_cell method",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ TableCellData *data = g_new0(TableCellData, 1);
+ data->atk_table_cell = (*jniEnv)->NewGlobalRef(jniEnv, jatk_table_cell);
+ if (data->atk_table_cell == NULL) {
+ g_warning("%s: Failed to create global ref for atk_table_cell",
+ G_STRFUNC);
+ g_free(data);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data;
+}
+
+void jaw_table_cell_data_finalize(gpointer p) {
+ JAW_DEBUG("%p", p);
+
+ if (p == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ TableCellData *data = (TableCellData *)p;
+ if (data == NULL) {
+ g_warning("%s: TableCellData is NULL after cast", G_STRFUNC);
+ return;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+
+ if (jniEnv == NULL) {
+ g_warning("%s: JNIEnv is NULL in finalize, leaking JNI resources",
+ G_STRFUNC);
+ } else {
+ if (data->atk_table_cell != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_table_cell);
+ data->atk_table_cell = NULL;
+ }
+ }
+
+ g_free(data);
+}
+
+/**
+ * jaw_table_cell_get_table:
+ * @cell: a GObject instance that implements AtkTableCellIface
+ *
+ * Returns a reference to the accessible of the containing table.
+ *
+ * Returns: (transfer full): the atk object for the containing table.
+ */
+static AtkObject *jaw_table_cell_get_table(AtkTableCell *cell) {
+ JAW_DEBUG("%p", cell);
+
+ if (cell == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_TABLECELL(
+ cell, NULL); // create local JNI reference `jobject jatk_table_cell`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jac = (*jniEnv)->CallObjectMethod(jniEnv, jatk_table_cell,
+ cachedTableCellGetTableMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jac == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_table method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac);
+ // From the documentation of the `cell_get_table`:
+ // "The caller of the method takes ownership of the returned data, and is
+ // responsible for freeing it." (transfer full)
+ if (jaw_impl != NULL) {
+ g_object_ref(G_OBJECT(jaw_impl));
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return ATK_OBJECT(jaw_impl);
+}
+
+/**
+ * getPosition:
+ * @jniEnv: JNI environment pointer.
+ * @jatk_table_cell: a Java object implementing the AtkTableCell interface.
+ * @classAtkTableCell: the Java class of @jatk_table_cell.
+ * @row: (out): return location for the zero-based row index of the cell.
+ * @column: (out): return location for the zero-based column index of the cell.
+ *
+ * Retrieves the row and column index of the cell.
+ * If the operation succeeds, the values of @row and @column are updated.
+ * If it fails, the output arguments are left unchanged.
+ *
+ * Returns: %TRUE if successful; %FALSE otherwise.
+ */
+static gboolean getPosition(JNIEnv *jniEnv, jobject jatk_table_cell, gint *row,
+ gint *column) {
+ JAW_DEBUG("%p, %p, %p, %p", jniEnv, jatk_table_cell, row, column);
+
+ if (jniEnv == NULL || row == NULL || column == NULL) {
+ g_warning("%s: Null argument. jniEnv=%p, row=%p, column=%p", G_STRFUNC,
+ (void *)jniEnv, (void *)row, (void *)column);
+ return FALSE;
+ }
+
+ jint jrow = (*jniEnv)->GetIntField(jniEnv, jatk_table_cell,
+ cachedTableCellRowFieldID);
+ if (jrow < 0) {
+ g_warning("%s: Invalid row value (%d) retrieved", G_STRFUNC, jrow);
+ return FALSE;
+ }
+
+ jint jcolumn = (*jniEnv)->GetIntField(jniEnv, jatk_table_cell,
+ cachedTableCellColumnFieldID);
+ if (jcolumn < 0) {
+ g_warning("%s: Invalid column value (%d) retrieved", G_STRFUNC,
+ jcolumn);
+ return FALSE;
+ }
+
+ (*row) = (gint)jrow;
+ (*column) = (gint)jcolumn;
+ return TRUE;
+}
+
+/**
+ * jaw_table_cell_get_position:
+ * @cell: an #AtkTableCell.
+ * @row: (out): the row of the given cell.
+ * @column: (out): the column of the given cell.
+ *
+ * Retrieves the tabular position of this cell.
+ *
+ * Returns: %TRUE if successful; %FALSE otherwise.
+ *
+ * Since: 2.12
+ */
+static gboolean jaw_table_cell_get_position(AtkTableCell *cell, gint *row,
+ gint *column) {
+ JAW_DEBUG("%p, %p, %p", cell, row, column);
+
+ if (cell == NULL || row == NULL || column == NULL) {
+ g_warning("%s: Null argument. cell=%p, row=%p, column=%p", G_STRFUNC,
+ (void *)cell, (void *)row, (void *)column);
+ return FALSE;
+ }
+
+ JAW_GET_TABLECELL(cell, FALSE);
+
+ if (!getPosition(jniEnv, jatk_table_cell, row, column)) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+
+ return TRUE;
+}
+
+/**
+ * getRowSpan:
+ * @jniEnv: JNI environment pointer.
+ * @jatk_table_cell: a Java object implementing the AtkTableCell interface.
+ * @classAtkTableCell: the Java class of @jatk_table_cell.
+ * @row_span: (out): return location for the row-span value.
+ *
+ * Retrieves the `rowSpan` field from a Java ATK table cell object.
+ * On success, the value is stored in @row_span.
+ * On failure, @row_span is left unchanged.
+ *
+ * Returns: %TRUE if the value was successfully retrieved; %FALSE otherwise.
+ */
+static gboolean getRowSpan(JNIEnv *jniEnv, jobject jatk_table_cell,
+ gint *row_span) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jatk_table_cell, row_span);
+
+ if (jniEnv == NULL || row_span == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ jint jrow_span = (*jniEnv)->GetIntField(jniEnv, jatk_table_cell,
+ cachedTableCellRowSpanFieldID);
+ if (jrow_span < 0) {
+ g_warning("%s: Invalid row span value (%d) retrieved", G_STRFUNC,
+ jrow_span);
+ return FALSE;
+ }
+
+ (*row_span) = (gint)jrow_span;
+ return TRUE;
+}
+
+/**
+ * getColumnSpan:
+ * @jniEnv: a valid JNI environment pointer.
+ * @jatk_table_cell: a Java object implementing the AtkTableCell interface.
+ * @classAtkTableCell: the Java class of @jatk_table_cell.
+ * @column_span: (out): return location for the column-span value.
+ *
+ * Retrieves the `columnSpan` field from a Java ATK table cell object.
+ * On success, the value is stored in @column_span.
+ * On failure, @column_span is left unchanged.
+ *
+ * Returns: %TRUE if the column span value was successfully retrieved and
+ * stored in @column_span; %FALSE on error.
+ */
+static gboolean getColumnSpan(JNIEnv *jniEnv, jobject jatk_table_cell,
+ gint *column_span) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jatk_table_cell, column_span);
+
+ if (jniEnv == NULL || column_span == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ jint jcolumn_span = (*jniEnv)->GetIntField(
+ jniEnv, jatk_table_cell, cachedTableCellColumnSpanFieldID);
+ if (jcolumn_span < 0) {
+ g_warning("%s: Invalid column span value (%d) retrieved", G_STRFUNC,
+ jcolumn_span);
+ return FALSE;
+ }
+
+ (*column_span) = (gint)jcolumn_span;
+ return TRUE;
+}
+
+/**
+ * jaw_table_cell_get_row_column_span:
+ * @cell: an #AtkTableCell.
+ * @row: (out): the row index of the given cell.
+ * @column: (out): the column index of the given cell.
+ * @row_span: (out): the number of rows occupied by this cell.
+ * @column_span: (out): the number of columns occupied by this cell.
+ *
+ * Gets the row and column indexes and span of this cell accessible.
+ *
+ * Note: Even if the function returns %FALSE, some of the output arguments
+ * may have been partially updated before the failure occurred.
+ *
+ * Returns: %TRUE if successful; %FALSE otherwise.
+ *
+ * Since: 2.12
+ */
+static gboolean jaw_table_cell_get_row_column_span(AtkTableCell *cell,
+ gint *row, gint *column,
+ gint *row_span,
+ gint *column_span) {
+ JAW_DEBUG("%p, %p, %p, %p, %p", cell, row, column, row_span, column_span);
+
+ if (cell == NULL || row == NULL || column == NULL || row_span == NULL ||
+ column_span == NULL) {
+ g_warning("%s: Null argument. cell=%p, row=%p, column=%p, row_span=%p, "
+ "column_span=%p",
+ G_STRFUNC, (void *)cell, (void *)row, (void *)column,
+ (void *)row_span, (void *)column_span);
+ return FALSE;
+ }
+
+ JAW_GET_TABLECELL(cell, FALSE);
+
+ if (!getPosition(jniEnv, jatk_table_cell, row, column)) {
+ g_warning("%s: getPosition failed", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ return FALSE;
+ }
+
+ if (!getRowSpan(jniEnv, jatk_table_cell, row_span)) {
+ g_warning("%s: getRowSpan failed", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ return FALSE;
+ }
+
+ if (!getColumnSpan(jniEnv, jatk_table_cell, column_span)) {
+ g_warning("%s: getColumnSpan failed", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+
+ return TRUE;
+}
+
+/**
+ * jaw_table_cell_get_row_span:
+ * @cell: (nullable): an #AtkTableCell instance
+ *
+ * Returns the number of rows occupied by this cell accessible.
+ *
+ * Returns: (type gint):
+ * A gint representing the number of rows occupied by this cell, or 0 if the
+ * cell does not implement this method.
+ *
+ * Since: 2.12
+ */
+static gint jaw_table_cell_get_row_span(AtkTableCell *cell) {
+ JAW_DEBUG("%p", cell);
+
+ if (cell == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_TABLECELL(cell, 0);
+
+ gint row_span = 0;
+ if (!getRowSpan(jniEnv, jatk_table_cell, &row_span)) {
+ g_warning("%s: getRowSpan failed", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ return row_span;
+}
+
+/**
+ * jaw_table_cell_get_column_span:
+ * @cell: (nullable): an #AtkTableCell instance
+ *
+ * Returns the number of columns occupied by this table cell.
+ *
+ * Returns: (type gint):
+ * A gint representing the number of columns occupied by this cell,
+ * or 0 if the cell does not implement this method.
+ *
+ * Since: 2.12
+ */
+static gint jaw_table_cell_get_column_span(AtkTableCell *cell) {
+ JAW_DEBUG("%p", cell);
+
+ if (cell == NULL) {
+ g_warning("%s: Null argument cell passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_TABLECELL(cell, 0);
+
+ gint column_span = 0;
+ if (!getColumnSpan(jniEnv, jatk_table_cell, &column_span)) {
+ g_warning("%s: getColumnSpan failed", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+
+ return column_span;
+}
+
+/**
+ * jaw_table_cell_get_column_header_cells:
+ * @cell: a GObject instance that implements AtkTableCellIface
+ *
+ * Returns the column headers as an array of cell accessibles.
+ *
+ * Returns: (element-type AtkObject) (transfer full): a GPtrArray of AtkObjects
+ * representing the column header cells.
+ */
+static GPtrArray *jaw_table_cell_get_column_header_cells(AtkTableCell *cell) {
+ JAW_DEBUG("%p", cell);
+
+ if (cell == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_TABLECELL(
+ cell, NULL); // create local JNI reference `jobject jatk_table_cell`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobjectArray columnHeaders = (jobjectArray)(*jniEnv)->CallObjectMethod(
+ jniEnv, jatk_table_cell,
+ cachedTableCellGetAccessibleColumnHeaderMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || columnHeaders == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_accessible_column_header method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ jsize length = (*jniEnv)->GetArrayLength(jniEnv, columnHeaders);
+ GPtrArray *result = g_ptr_array_sized_new((guint)length);
+ if (result == NULL) {
+ g_warning("%s: Failed to allocate GPtrArray", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ for (jsize i = 0; i < length; i++) {
+ jobject jac =
+ (*jniEnv)->GetObjectArrayElement(jniEnv, columnHeaders, i);
+ JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac);
+ if (jaw_impl != NULL) {
+ g_ptr_array_add(result, g_object_ref(G_OBJECT(jaw_impl)));
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jac);
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return result;
+}
+
+/**
+ * jaw_table_cell_get_row_header_cells:
+ * @cell: a GObject instance that implements AtkTableCellIface
+ *
+ * Returns the row headers as an array of cell accessibles.
+ *
+ * Returns: (element-type AtkObject) (transfer full): a GPtrArray of AtkObjects
+ * representing the row header cells.
+ */
+static GPtrArray *jaw_table_cell_get_row_header_cells(AtkTableCell *cell) {
+ JAW_DEBUG("%p", cell);
+
+ if (cell == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_TABLECELL(
+ cell, NULL); // create local JNI reference `jobject jatk_table_cell`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobjectArray rowHeaders = (jobjectArray)(*jniEnv)->CallObjectMethod(
+ jniEnv, jatk_table_cell, cachedTableCellGetAccessibleRowHeaderMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || rowHeaders == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to call get_accessible_row_header method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ jsize length = (*jniEnv)->GetArrayLength(jniEnv, rowHeaders);
+ GPtrArray *result = g_ptr_array_sized_new((guint)length);
+ if (result == NULL) {
+ g_warning("%s: Failed to allocate GPtrArray", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ for (jsize i = 0; i < length; i++) {
+ jobject jac = (*jniEnv)->GetObjectArrayElement(jniEnv, rowHeaders, i);
+ JawImpl *jaw_impl = jaw_impl_find_instance(jniEnv, jac);
+ if (jaw_impl != NULL) {
+ g_ptr_array_add(result, g_object_ref(G_OBJECT(jaw_impl)));
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jac);
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, jatk_table_cell);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return result;
+}
+
+static gboolean jaw_table_cell_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localClass =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkTableCell");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkTableCell class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedTableCellAtkTableCellClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localClass);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClass);
+
+ if (cachedTableCellAtkTableCellClass == NULL) {
+ g_warning(
+ "%s: Failed to create global reference for AtkTableCell class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedTableCellCreateAtkTableCellMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedTableCellAtkTableCellClass, "create_atk_table_cell",
+ "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/"
+ "AtkTableCell;");
+
+ cachedTableCellGetTableMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableCellAtkTableCellClass, "get_table",
+ "()Ljavax/accessibility/AccessibleTable;");
+
+ cachedTableCellGetAccessibleColumnHeaderMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedTableCellAtkTableCellClass,
+ "get_accessible_column_header",
+ "()[Ljavax/accessibility/AccessibleContext;");
+
+ cachedTableCellGetAccessibleRowHeaderMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTableCellAtkTableCellClass, "get_accessible_row_header",
+ "()[Ljavax/accessibility/AccessibleContext;");
+
+ cachedTableCellRowFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedTableCellAtkTableCellClass, "row", "I");
+
+ cachedTableCellColumnFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedTableCellAtkTableCellClass, "column", "I");
+
+ cachedTableCellRowSpanFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedTableCellAtkTableCellClass, "rowSpan", "I");
+
+ cachedTableCellColumnSpanFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedTableCellAtkTableCellClass, "columnSpan", "I");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedTableCellCreateAtkTableCellMethod == NULL ||
+ cachedTableCellGetTableMethod == NULL ||
+ cachedTableCellGetAccessibleColumnHeaderMethod == NULL ||
+ cachedTableCellGetAccessibleRowHeaderMethod == NULL ||
+ cachedTableCellRowFieldID == NULL ||
+ cachedTableCellColumnFieldID == NULL ||
+ cachedTableCellRowSpanFieldID == NULL ||
+ cachedTableCellColumnSpanFieldID == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning(
+ "%s: Failed to cache one or more AtkTableCell method/field IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedTableCellAtkTableCellClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTableCellAtkTableCellClass);
+ cachedTableCellAtkTableCellClass = NULL;
+ }
+ cachedTableCellCreateAtkTableCellMethod = NULL;
+ cachedTableCellGetTableMethod = NULL;
+ cachedTableCellGetAccessibleColumnHeaderMethod = NULL;
+ cachedTableCellGetAccessibleRowHeaderMethod = NULL;
+ cachedTableCellRowFieldID = NULL;
+ cachedTableCellColumnFieldID = NULL;
+ cachedTableCellRowSpanFieldID = NULL;
+ cachedTableCellColumnSpanFieldID = NULL;
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+}
+
+void jaw_tablecell_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedTableCellAtkTableCellClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTableCellAtkTableCellClass);
+ cachedTableCellAtkTableCellClass = NULL;
+ }
+ cachedTableCellCreateAtkTableCellMethod = NULL;
+ cachedTableCellGetTableMethod = NULL;
+ cachedTableCellGetAccessibleColumnHeaderMethod = NULL;
+ cachedTableCellGetAccessibleRowHeaderMethod = NULL;
+ cachedTableCellRowFieldID = NULL;
+ cachedTableCellColumnFieldID = NULL;
+ cachedTableCellRowSpanFieldID = NULL;
+ cachedTableCellColumnSpanFieldID = NULL;
+ cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawtext.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtext.c
new file mode 100644
index 000000000000..7d361f9baa76
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtext.c
@@ -0,0 +1,1450 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawcache.h"
+#include "jawimpl.h"
+#include "jawutil.h"
+#include
+#include
+
+/**
+ * (From Atk documentation)
+ *
+ * AtkText:
+ *
+ * The ATK interface implemented by components with text content.
+ *
+ * #AtkText should be implemented by #AtkObjects on behalf of widgets
+ * that have text content which is either attributed or otherwise
+ * non-trivial. #AtkObjects whose text content is simple,
+ * unattributed, and very brief may expose that content via
+ * #atk_object_get_name instead; however if the text is editable,
+ * multi-line, typically longer than three or four words, attributed,
+ * selectable, or if the object already uses the 'name' ATK property
+ * for other information, the #AtkText interface should be used to
+ * expose the text content. In the case of editable text content,
+ * #AtkEditableText (a subtype of the #AtkText interface) should be
+ * implemented instead.
+ *
+ * #AtkText provides not only traversal facilities and change
+ * notification for text content, but also caret tracking and glyph
+ * bounding box calculations. Note that the text strings are exposed
+ * as UTF-8, and are therefore potentially multi-byte, and
+ * caret-to-byte offset mapping makes no assumptions about the
+ * character length; also bounding box glyph-to-offset mapping may be
+ * complex for languages which use ligatures.
+ */
+
+static jclass cachedTextAtkTextClass = NULL;
+static jmethodID cachedTextCreateAtkTextMethod = NULL;
+static jmethodID cachedTextGetTextMethod = NULL;
+static jmethodID cachedTextGetCharacterAtOffsetMethod = NULL;
+static jmethodID cachedTextGetTextAfterOffsetMethod = NULL;
+static jmethodID cachedTextGetTextAtOffsetMethod = NULL;
+static jmethodID cachedTextGetTextBeforeOffsetMethod = NULL;
+static jmethodID cachedTextGetStringAtOffsetMethod = NULL;
+static jmethodID cachedTextGetCaretOffsetMethod = NULL;
+static jmethodID cachedTextGetCharacterExtentsMethod = NULL;
+static jmethodID cachedTextGetCharacterCountMethod = NULL;
+static jmethodID cachedTextGetOffsetAtPointMethod = NULL;
+static jmethodID cachedTextGetRangeExtentsMethod = NULL;
+static jmethodID cachedTextGetNSelectionsMethod = NULL;
+static jmethodID cachedTextGetSelectionMethod = NULL;
+static jmethodID cachedTextAddSelectionMethod = NULL;
+static jmethodID cachedTextRemoveSelectionMethod = NULL;
+static jmethodID cachedTextSetSelectionMethod = NULL;
+static jmethodID cachedTextSetCaretOffsetMethod = NULL;
+static jclass cachedTextStringSequenceClass = NULL;
+static jfieldID cachedTextStrFieldID = NULL;
+static jfieldID cachedTextStartOffsetFieldID = NULL;
+static jfieldID cachedTextEndOffsetFieldID = NULL;
+
+static GMutex cache_mutex;
+static gboolean cache_initialized = FALSE;
+
+static gboolean jaw_text_init_jni_cache(JNIEnv *jniEnv);
+
+static gchar *jaw_text_get_text(AtkText *text, gint start_offset,
+ gint end_offset);
+static gunichar jaw_text_get_character_at_offset(AtkText *text, gint offset);
+static gchar *jaw_text_get_text_at_offset(AtkText *text, gint offset,
+ AtkTextBoundary boundary_type,
+ gint *start_offset, gint *end_offset);
+static gchar *jaw_text_get_text_before_offset(AtkText *text, gint offset,
+ AtkTextBoundary boundary_type,
+ gint *start_offset,
+ gint *end_offset);
+static gchar *jaw_text_get_text_after_offset(AtkText *text, gint offset,
+ AtkTextBoundary boundary_type,
+ gint *start_offset,
+ gint *end_offset);
+static gchar *jaw_text_get_string_at_offset(AtkText *text, gint offset,
+ AtkTextGranularity granularity,
+ gint *start_offset,
+ gint *end_offset);
+static gint jaw_text_get_caret_offset(AtkText *text);
+static void jaw_text_get_character_extents(AtkText *text, gint offset, gint *x,
+ gint *y, gint *width, gint *height,
+ AtkCoordType coords);
+static gint jaw_text_get_character_count(AtkText *text);
+static gint jaw_text_get_offset_at_point(AtkText *text, gint x, gint y,
+ AtkCoordType coords);
+static void jaw_text_get_range_extents(AtkText *text, gint start_offset,
+ gint end_offset, AtkCoordType coord_type,
+ AtkTextRectangle *rect);
+static gint jaw_text_get_n_selections(AtkText *text);
+static gchar *jaw_text_get_selection(AtkText *text, gint selection_num,
+ gint *start_offset, gint *end_offset);
+static gboolean jaw_text_add_selection(AtkText *text, gint start_offset,
+ gint end_offset);
+static gboolean jaw_text_remove_selection(AtkText *text, gint selection_num);
+static gboolean jaw_text_set_selection(AtkText *text, gint selection_num,
+ gint start_offset, gint end_offset);
+static gboolean jaw_text_set_caret_offset(AtkText *text, gint offset);
+
+typedef struct _TextData {
+ jobject atk_text;
+} TextData;
+
+#define JAW_GET_TEXT(text, def_ret) \
+ JAW_GET_OBJ_IFACE(text, \
+ org_GNOME_Accessibility_AtkInterface_INTERFACE_TEXT, \
+ TextData, atk_text, jniEnv, atk_text, def_ret)
+
+void jaw_text_interface_init(AtkTextIface *iface, gpointer data) {
+ JAW_DEBUG("%p, %p", iface, data);
+
+ if (iface == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ iface->get_text = jaw_text_get_text;
+ iface->get_text_after_offset = jaw_text_get_text_after_offset;
+ iface->get_text_at_offset = jaw_text_get_text_at_offset;
+ iface->get_character_at_offset = jaw_text_get_character_at_offset;
+ iface->get_text_before_offset = jaw_text_get_text_before_offset;
+ iface->get_string_at_offset = jaw_text_get_string_at_offset;
+ iface->get_caret_offset = jaw_text_get_caret_offset;
+ iface->get_run_attributes =
+ NULL; // TODO: iface->get_run_attributes by iterating
+ // getCharacterAttribute or using getTextSequenceAt with
+ // ATTRIBUTE_RUN
+ iface->get_default_attributes = NULL; // TODO: iface->get_default_attributes
+ iface->get_character_extents = jaw_text_get_character_extents;
+ iface->get_character_count = jaw_text_get_character_count;
+ iface->get_offset_at_point = jaw_text_get_offset_at_point;
+ iface->get_n_selections = jaw_text_get_n_selections;
+ iface->get_selection = jaw_text_get_selection;
+ iface->add_selection = jaw_text_add_selection;
+ iface->remove_selection = jaw_text_remove_selection;
+ iface->set_selection = jaw_text_set_selection;
+ iface->set_caret_offset = jaw_text_set_caret_offset;
+
+ /*
+ * signal handlers
+ */
+ // iface->text_changed implemented by atk
+ // iface->text_caret_moved implemented by atk
+ // iface->text_selection_changed implemented by atk
+ // iface->text_attributes_changed implemented by atk
+ iface->get_range_extents = jaw_text_get_range_extents;
+ iface->get_bounded_ranges =
+ NULL; // TODO: iface->get_bounded_ranges from getTextBounds
+
+ /*
+ * Scrolls this text range so it becomes visible on the screen.
+ *
+ * scroll_substring_to lets the implementation compute an appropriate target
+ * position on the screen, with type used as a positioning hint.
+ *
+ * scroll_substring_to_point lets the client specify a precise target
+ * position on the screen for the top-left of the substring.
+ *
+ * Since ATK 2.32
+ */
+ iface->scroll_substring_to = NULL; // missing java support
+ iface->scroll_substring_to_point = NULL; // missing java support
+}
+
+gpointer jaw_text_data_init(jobject ac) {
+ JAW_DEBUG("%p", ac);
+
+ if (ac == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_text_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jatk_text = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedTextAtkTextClass, cachedTextCreateAtkTextMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_text == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jatk_text using create_atk_text method",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ TextData *data = g_new0(TextData, 1);
+ data->atk_text = (*jniEnv)->NewGlobalRef(jniEnv, jatk_text);
+ if (data->atk_text == NULL) {
+ g_warning("%s: Failed to create global ref for atk_text", G_STRFUNC);
+ g_free(data);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data;
+}
+
+void jaw_text_data_finalize(gpointer p) {
+ JAW_DEBUG("%p", p);
+
+ if (p == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return;
+ }
+
+ TextData *data = (TextData *)p;
+ if (data == NULL) {
+ return;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+
+ if (jniEnv == NULL) {
+ g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC);
+ } else {
+ if (data->atk_text != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_text);
+ data->atk_text = NULL;
+ }
+ }
+
+ g_free(data);
+}
+
+static gchar *private_jaw_text_get_gtext_from_jstr(JNIEnv *jniEnv,
+ jstring jstr) {
+ JAW_DEBUG("%p, %p", jniEnv, jstr);
+
+ if (jniEnv == NULL || jstr == NULL) {
+ g_warning("%s: Null argument passed. jniEnv=%p, jstr=%p", G_STRFUNC,
+ (void *)jniEnv, (void *)jstr);
+ return NULL;
+ }
+
+ gchar *tmp_text = (gchar *)(*jniEnv)->GetStringUTFChars(jniEnv, jstr, NULL);
+ if (tmp_text == NULL) {
+ g_warning("%s: tmp_text is NULL", G_STRFUNC);
+ return NULL;
+ }
+ gchar *text = g_strdup(tmp_text);
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, tmp_text);
+
+ return text;
+}
+
+static gchar *private_jaw_text_get_gtext_from_string_seq(JNIEnv *jniEnv,
+ jobject jStrSeq,
+ gint *start_offset,
+ gint *end_offset) {
+ if (jniEnv == NULL || start_offset == NULL || end_offset == NULL) {
+ g_warning(
+ "%s: Null argument. jniEnv=%p, start_offset=%p, end_offset=%p",
+ G_STRFUNC, (void *)jniEnv, (void *)start_offset,
+ (void *)end_offset);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jstring jStr =
+ (*jniEnv)->GetObjectField(jniEnv, jStrSeq, cachedTextStrFieldID);
+ if (jStr == NULL) {
+ g_warning("%s: Failed to get jStr field", G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*start_offset) = (gint)(*jniEnv)->GetIntField(
+ jniEnv, jStrSeq, cachedTextStartOffsetFieldID);
+ (*end_offset) = (gint)(*jniEnv)->GetIntField(jniEnv, jStrSeq,
+ cachedTextEndOffsetFieldID);
+
+ gchar *result = private_jaw_text_get_gtext_from_jstr(jniEnv, jStr);
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return result;
+}
+
+/**
+ * atk_text_get_text:
+ * @text: an #AtkText
+ * @start_offset: a starting character offset within @text
+ * @end_offset: an ending character offset within @text, or -1 for the end of
+ *the string.
+ *
+ * Gets the specified text.
+ *
+ * Returns: a newly allocated string containing the text from @start_offset up
+ * to, but not including @end_offset. Use g_free() to free the returned
+ * string.
+ **/
+static gchar *jaw_text_get_text(AtkText *text, gint start_offset,
+ gint end_offset) {
+ JAW_DEBUG("%p, %d, %d", text, start_offset, end_offset);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument text passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_TEXT(text, NULL); // create local JNI reference `jobject atk_text`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jstring jstr =
+ (*jniEnv)->CallObjectMethod(jniEnv, atk_text, cachedTextGetTextMethod,
+ (jint)start_offset, (jint)end_offset);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jstr == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jstr using get_text method", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ gchar *result = private_jaw_text_get_gtext_from_jstr(jniEnv, jstr);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return result;
+}
+
+/**
+ * jaw_text_get_character_at_offset:
+ * @text: an #AtkText
+ * @offset: a character offset within @text
+ *
+ * Gets the specified text.
+ *
+ * Returns: the character at @offset or 0 in the case of failure.
+ **/
+static gunichar jaw_text_get_character_at_offset(AtkText *text, gint offset) {
+ JAW_DEBUG("%p, %d", text, offset);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument text passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_TEXT(text, 0);
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return 0;
+ }
+
+ jchar jcharacter = (*jniEnv)->CallCharMethod(
+ jniEnv, atk_text, cachedTextGetCharacterAtOffsetMethod, (jint)offset);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+ if (jcharacter == '\0') {
+ g_warning("%s: jcharacter is '\\0'", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return (gunichar)jcharacter;
+}
+
+/**
+ * jaw_text_get_text_after_offset:
+ * @text: an #AtkText
+ * @offset: position
+ * @boundary_type: An #AtkTextBoundary
+ * @start_offset: (out): the starting character offset of the returned string
+ * @end_offset: (out): the offset of the first character after the
+ * returned substring
+ *
+ * Gets the specified text.
+ *
+ * Deprecated: 2.9.3 in atk: Please use atk_text_get_string_at_offset() instead.
+ *
+ * Returns: a newly allocated string containing the text after @offset bounded
+ * by the specified @boundary_type. Use g_free() to free the returned
+ * string.
+ **/
+static gchar *jaw_text_get_text_after_offset(AtkText *text, gint offset,
+ AtkTextBoundary boundary_type,
+ gint *start_offset,
+ gint *end_offset) {
+ JAW_DEBUG("%p, %d, %d, %p, %p", text, offset, boundary_type, start_offset,
+ end_offset);
+
+ if (text == NULL || start_offset == NULL || end_offset == NULL) {
+ g_warning("%s: Null argument. text=%p, start_offset=%p, end_offset=%p",
+ G_STRFUNC, (void *)text, (void *)start_offset,
+ (void *)end_offset);
+ return NULL;
+ }
+
+ JAW_GET_TEXT(text, NULL); // create local JNI reference `jobject atk_text`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jStrSeq = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_text, cachedTextGetTextAfterOffsetMethod, (jint)offset,
+ (jint)boundary_type);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jStrSeq == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning(
+ "%s: Failed to create jStrSeq using get_text_after_offset method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ gchar *result = private_jaw_text_get_gtext_from_string_seq(
+ jniEnv, jStrSeq, start_offset, end_offset);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return result;
+}
+
+/**
+ * jaw_text_get_text_at_offset:
+ * @text: an #AtkText
+ * @offset: position
+ * @boundary_type: An #AtkTextBoundary
+ * @start_offset: (out): the starting character offset of the returned string
+ * @end_offset: (out): the offset of the first character after the
+ * returned substring
+ *
+ * Deprecated: This method is deprecated since ATK version
+ * 2.9.4. Please use atk_text_get_string_at_offset() instead.
+ *
+ * Returns: a newly allocated string containing the text at @offset bounded
+ * by the specified @boundary_type. Use g_free() to free the returned
+ * string.
+ **/
+static gchar *jaw_text_get_text_at_offset(AtkText *text, gint offset,
+ AtkTextBoundary boundary_type,
+ gint *start_offset,
+ gint *end_offset) {
+ JAW_DEBUG("%p, %d, %d, %p, %p", text, offset, boundary_type, start_offset,
+ end_offset);
+
+ if (text == NULL || start_offset == NULL || end_offset == NULL) {
+ g_warning("%s: Null argument. text=%p, start_offset=%p, end_offset=%p",
+ G_STRFUNC, (void *)text, (void *)start_offset,
+ (void *)end_offset);
+ return NULL;
+ }
+
+ JAW_GET_TEXT(text, NULL); // create local JNI reference `jobject atk_text`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jStrSeq = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_text, cachedTextGetTextAtOffsetMethod, (jint)offset,
+ (jint)boundary_type);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jStrSeq == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning(
+ "%s: Failed to create jStrSeq using get_text_at_offset method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ char *result = private_jaw_text_get_gtext_from_string_seq(
+ jniEnv, jStrSeq, start_offset, end_offset);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return result;
+}
+
+/**
+ * jaw_text_get_text_before_offset:
+ * @text: an #AtkText
+ * @offset: position
+ * @boundary_type: An #AtkTextBoundary
+ * @start_offset: (out): the starting character offset of the returned string
+ * @end_offset: (out): the offset of the first character after the
+ * returned substring
+ *
+ * Gets the specified text.
+ *
+ * Deprecated in atk: 2.9.3: Please use atk_text_get_string_at_offset() instead.
+ *
+ * Returns: a newly allocated string containing the text before @offset bounded
+ * by the specified @boundary_type. Use g_free() to free the returned
+ * string.
+ **/
+static gchar *jaw_text_get_text_before_offset(AtkText *text, gint offset,
+ AtkTextBoundary boundary_type,
+ gint *start_offset,
+ gint *end_offset) {
+ JAW_DEBUG("%p, %d, %d, %p, %p", text, offset, boundary_type, start_offset,
+ end_offset);
+
+ if (text == NULL || start_offset == NULL || end_offset == NULL) {
+ g_warning("%s: Null argument. text=%p, start_offset=%p, end_offset=%p",
+ G_STRFUNC, (void *)text, (void *)start_offset,
+ (void *)end_offset);
+ return NULL;
+ }
+
+ JAW_GET_TEXT(text, NULL); // create local JNI reference `jobject atk_text`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jStrSeq = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_text, cachedTextGetTextBeforeOffsetMethod, (jint)offset,
+ (jint)boundary_type);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jStrSeq == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning(
+ "%s: Failed to create jStrSeq using get_text_before_offset method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ char *result = private_jaw_text_get_gtext_from_string_seq(
+ jniEnv, jStrSeq, start_offset, end_offset);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return result;
+}
+
+/**
+ * jaw_text_get_string_at_offset:
+ * @text: an #AtkText
+ * @offset: position
+ * @granularity: An #AtkTextGranularity
+ * @start_offset: (out): the starting character offset of the returned string,
+ *or -1 in the case of error (e.g. invalid offset, not implemented)
+ * @end_offset: (out): the offset of the first character after the returned
+ *string, or -1 in the case of error (e.g. invalid offset, not implemented)
+ *
+ * Gets a portion of the text exposed through an #AtkText according to a given
+ *@offset and a specific @granularity, along with the start and end offsets
+ *defining the boundaries of such a portion of text.
+ *
+ * If @granularity is ATK_TEXT_GRANULARITY_CHAR the character at the
+ * offset is returned.
+ *
+ * If @granularity is ATK_TEXT_GRANULARITY_WORD the returned string
+ * is from the word start at or before the offset to the word start after
+ * the offset.
+ *
+ * The returned string will contain the word at the offset if the offset
+ * is inside a word and will contain the word before the offset if the
+ * offset is not inside a word.
+ *
+ * If @granularity is ATK_TEXT_GRANULARITY_SENTENCE the returned string
+ * is from the sentence start at or before the offset to the sentence
+ * start after the offset.
+ *
+ * The returned string will contain the sentence at the offset if the offset
+ * is inside a sentence and will contain the sentence before the offset
+ * if the offset is not inside a sentence.
+ *
+ * If @granularity is ATK_TEXT_GRANULARITY_LINE the returned string
+ * is from the line start at or before the offset to the line
+ * start after the offset.
+ *
+ * If @granularity is ATK_TEXT_GRANULARITY_PARAGRAPH the returned string
+ * is from the start of the paragraph at or before the offset to the start
+ * of the following paragraph after the offset.
+ *
+ * Since: 2.10 (in atk)
+ *
+ * Returns: (nullable): a newly allocated string containing the text at
+ * the @offset bounded by the specified @granularity. Use g_free()
+ * to free the returned string. Returns %NULL if the offset is invalid
+ * or no implementation is available.
+ **/
+static gchar *jaw_text_get_string_at_offset(AtkText *text, gint offset,
+ AtkTextGranularity granularity,
+ gint *start_offset,
+ gint *end_offset) {
+ JAW_DEBUG("%p, %d, %d, %p, %p", text, offset, granularity, start_offset,
+ end_offset);
+
+ if (text == NULL || start_offset == NULL || end_offset == NULL) {
+ g_warning("%s: Null argument. text=%p, start_offset=%p, end_offset=%p",
+ G_STRFUNC, (void *)text, (void *)start_offset,
+ (void *)end_offset);
+ return NULL;
+ }
+
+ JAW_GET_TEXT(text, NULL); // create local JNI reference `jobject atk_text`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jStrSeq = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_text, cachedTextGetStringAtOffsetMethod, (jint)offset,
+ (jint)granularity);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jStrSeq == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning(
+ "%s: Failed to create jStrSeq using get_string_at_offset method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ char *result = private_jaw_text_get_gtext_from_string_seq(
+ jniEnv, jStrSeq, start_offset, end_offset);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return result;
+}
+
+/**
+ * jaw_text_get_caret_offset:
+ * @text: an #AtkText
+ *
+ * Gets the offset of the position of the caret (cursor).
+ *
+ * Returns: the character offset of the position of the caret or -1 if
+ * the caret is not located inside the element or in the case of
+ * any other failure.
+ **/
+static gint jaw_text_get_caret_offset(AtkText *text) {
+ JAW_DEBUG("%p", text);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument text passed to the function", G_STRFUNC);
+ return -1;
+ }
+
+ JAW_GET_TEXT(text, -1);
+
+ jint joffset = (*jniEnv)->CallIntMethod(jniEnv, atk_text,
+ cachedTextGetCaretOffsetMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ return -1;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+
+ return (gint)joffset;
+}
+
+/**
+ * jaw_text_get_character_extents:
+ * @text: an #AtkText
+ * @offset: The offset of the text character for which bounding information is
+ *required.
+ * @x: (out) (optional): Pointer for the x coordinate of the bounding box
+ * @y: (out) (optional): Pointer for the y coordinate of the bounding box
+ * @width: (out) (optional): Pointer for the width of the bounding box
+ * @height: (out) (optional): Pointer for the height of the bounding box
+ * @coords: specify whether coordinates are relative to the screen or widget
+ *window
+ *
+ * If the extent can not be obtained (e.g. missing support), all of x, y, width,
+ * height are set to -1.
+ *
+ * Get the bounding box containing the glyph representing the character at
+ * a particular text offset.
+ **/
+static void jaw_text_get_character_extents(AtkText *text, gint offset, gint *x,
+ gint *y, gint *width, gint *height,
+ AtkCoordType coords) {
+ JAW_DEBUG("%p, %d, %p, %p, %p, %p, %d", text, offset, x, y, width, height,
+ coords);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument text passed to the function", G_STRFUNC);
+ return;
+ }
+
+ if (x != NULL)
+ *x = -1;
+ if (y != NULL)
+ *y = -1;
+ if (width != NULL)
+ *width = -1;
+ if (height != NULL)
+ *height = -1;
+
+ JAW_GET_TEXT(text, );
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ jobject jrect = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_text, cachedTextGetCharacterExtentsMethod, (jint)offset,
+ (jint)coords);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jrect == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning(
+ "%s: Failed to create jrect using get_character_extents method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ gint temp_x, temp_y, temp_width, temp_height;
+ jaw_util_get_rect_info(jniEnv, jrect, &temp_x, &temp_y, &temp_width,
+ &temp_height);
+
+ if (x != NULL)
+ *x = temp_x;
+ if (y != NULL)
+ *y = temp_y;
+ if (width != NULL)
+ *width = temp_width;
+ if (height != NULL)
+ *height = temp_height;
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * jaw_text_get_character_count:
+ * @text: an #AtkText
+ *
+ * Gets the character count.
+ *
+ * Returns: the number of characters or -1 in case of failure.
+ **/
+static gint jaw_text_get_character_count(AtkText *text) {
+ JAW_DEBUG("%p", text);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument text passed to the function", G_STRFUNC);
+ return -1;
+ }
+
+ JAW_GET_TEXT(text, -1);
+
+ jint jcount = (*jniEnv)->CallIntMethod(jniEnv, atk_text,
+ cachedTextGetCharacterCountMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ return -1;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+
+ return (gint)jcount;
+}
+
+/**
+ * jaw_text_get_offset_at_point:
+ * @text: an #AtkText
+ * @x: screen x-position of character
+ * @y: screen y-position of character
+ * @coords: specify whether coordinates are relative to the screen or
+ * widget window
+ *
+ * Gets the offset of the character located at coordinates @x and @y. @x and @y
+ * are interpreted as being relative to the screen or this widget's window
+ * depending on @coords.
+ *
+ * Returns: the offset to the character which is located at the specified
+ * @x and @y coordinates of -1 in case of failure.
+ **/
+static gint jaw_text_get_offset_at_point(AtkText *text, gint x, gint y,
+ AtkCoordType coords) {
+ JAW_DEBUG("%p, %d, %d, %d", text, x, y, coords);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument text passed to the function", G_STRFUNC);
+ return -1;
+ }
+
+ JAW_GET_TEXT(text, -1);
+
+ jint joffset = (*jniEnv)->CallIntMethod(jniEnv, atk_text,
+ cachedTextGetOffsetAtPointMethod,
+ (jint)x, (jint)y, (jint)coords);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ return -1;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+
+ return (gint)joffset;
+}
+
+/**
+ * jaw_text_get_range_extents:
+ * @text: an #AtkText
+ * @start_offset: The offset of the first text character for which boundary
+ * information is required.
+ * @end_offset: The offset of the text character after the last character
+ * for which boundary information is required.
+ * @coord_type: Specify whether coordinates are relative to the screen or widget
+ *window.
+ * @rect: (out): A pointer to a AtkTextRectangle which is filled in by this
+ *function.
+ *
+ * Get the bounding box for text within the specified range.
+ *
+ * If the extents can not be obtained (e.g. or missing support), the rectangle
+ * fields are set to -1.
+ *
+ * In Atk Since: 1.3
+ **/
+static void jaw_text_get_range_extents(AtkText *text, gint start_offset,
+ gint end_offset, AtkCoordType coord_type,
+ AtkTextRectangle *rect) {
+ JAW_DEBUG("%p, %d, %d, %d, %p", text, start_offset, end_offset, coord_type,
+ rect);
+
+ if (text == NULL || rect == NULL) {
+ g_warning("%s: Null argument. text=%p, rect=%p", G_STRFUNC,
+ (void *)text, (void *)rect);
+ return;
+ }
+
+ rect->x = -1;
+ rect->y = -1;
+ rect->width = -1;
+ rect->height = -1;
+
+ JAW_GET_TEXT(text, );
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ jobject jrect = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_text, cachedTextGetRangeExtentsMethod, (jint)start_offset,
+ (jint)end_offset, (jint)coord_type);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jrect == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jrect using get_range_extents method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ jaw_util_get_rect_info(jniEnv, jrect, &(rect->x), &(rect->y),
+ &(rect->width), &(rect->height));
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * jaw_text_get_n_selections:
+ * @text: an #AtkText
+ *
+ * Gets the number of selected regions.
+ *
+ * Returns: The number of selected regions, or -1 in the case of failure.
+ **/
+static gint jaw_text_get_n_selections(AtkText *text) {
+ JAW_DEBUG("%p", text);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument text passed to the function", G_STRFUNC);
+ return -1;
+ }
+
+ JAW_GET_TEXT(text, -1);
+
+ jint jselections = (*jniEnv)->CallIntMethod(jniEnv, atk_text,
+ cachedTextGetNSelectionsMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ return -1;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+
+ return (gint)jselections;
+}
+
+/**
+ * jaw_text_get_selection:
+ * @text: an #AtkText
+ * @selection_num: The selection number. The selected regions are
+ * assigned numbers that correspond to how far the region is from the
+ * start of the text. The selected region closest to the beginning
+ * of the text region is assigned the number 0, etc. Note that adding,
+ * moving or deleting a selected region can change the numbering.
+ * @start_offset: (out): passes back the starting character offset of the
+ *selected region
+ * @end_offset: (out): passes back the ending character offset (offset
+ *immediately past) of the selected region
+ *
+ * Gets the text from the specified selection.
+ *
+ * Returns: a newly allocated string containing the selected text. Use g_free()
+ * to free the returned string.
+ **/
+static gchar *jaw_text_get_selection(AtkText *text, gint selection_num,
+ gint *start_offset, gint *end_offset) {
+ JAW_DEBUG("%p, %d, %p, %p", text, selection_num, start_offset, end_offset);
+
+ if (text == NULL || start_offset == NULL || end_offset == NULL) {
+ g_warning("%s: Null argument. text=%p, start_offset=%p, end_offset=%p",
+ G_STRFUNC, (void *)text, (void *)start_offset,
+ (void *)end_offset);
+ return NULL;
+ }
+
+ JAW_GET_TEXT(text, NULL); // create local JNI reference `jobject atk_text`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ // Java AccessibleText only supports a single selection, so selection_num is
+ // not used.
+ jobject jStrSeq = (*jniEnv)->CallObjectMethod(jniEnv, atk_text,
+ cachedTextGetSelectionMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jStrSeq == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create jStrSeq using get_selection method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+
+ jstring jStr =
+ (*jniEnv)->GetObjectField(jniEnv, jStrSeq, cachedTextStrFieldID);
+ if (jStr == NULL) {
+ g_warning("%s: Failed to get jStr field", G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ *start_offset = (gint)(*jniEnv)->GetIntField(jniEnv, jStrSeq,
+ cachedTextStartOffsetFieldID);
+ *end_offset = (gint)(*jniEnv)->GetIntField(jniEnv, jStrSeq,
+ cachedTextEndOffsetFieldID);
+
+ gchar *result = private_jaw_text_get_gtext_from_jstr(jniEnv, jStr);
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return result;
+}
+
+/**
+ * jaw_text_add_selection:
+ * @text: an #AtkText
+ * @start_offset: the starting character offset of the selected region
+ * @end_offset: the offset of the first character after the selected region.
+ *
+ * Adds a selection bounded by the specified offsets.
+ *
+ * Returns: %TRUE if successful, %FALSE otherwise
+ **/
+static gboolean jaw_text_add_selection(AtkText *text, gint start_offset,
+ gint end_offset) {
+ JAW_DEBUG("%p, %d, %d", text, start_offset, end_offset);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument text passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_TEXT(text, FALSE);
+
+ jboolean jresult = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_text, cachedTextAddSelectionMethod, (jint)start_offset,
+ (jint)end_offset);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+
+ return jresult;
+}
+
+/**
+ * jaw_text_remove_selection:
+ * @text: an #AtkText
+ * @selection_num: The selection number. The selected regions are
+ * assigned numbers that correspond to how far the region is from the
+ * start of the text. The selected region closest to the beginning
+ * of the text region is assigned the number 0, etc. Note that adding,
+ * moving or deleting a selected region can change the numbering.
+ *
+ * Removes the specified selection.
+ *
+ * Returns: %TRUE if successful, %FALSE otherwise
+ **/
+static gboolean jaw_text_remove_selection(AtkText *text, gint selection_num) {
+ JAW_DEBUG("%p, %d", text, selection_num);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument text passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_TEXT(text, FALSE);
+
+ jboolean jresult = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_text, cachedTextRemoveSelectionMethod, (jint)selection_num);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+
+ return jresult;
+}
+
+/**
+ * jaw_text_set_selection:
+ * @text: an #AtkText
+ * @selection_num: The selection number. The selected regions are
+ * assigned numbers that correspond to how far the region is from the
+ * start of the text. The selected region closest to the beginning
+ * of the text region is assigned the number 0, etc. Note that adding,
+ * moving or deleting a selected region can change the numbering.
+ * @start_offset: the new starting character offset of the selection
+ * @end_offset: the new end position of (e.g. offset immediately past)
+ * the selection
+ *
+ * Changes the start and end offset of the specified selection.
+ *
+ * Returns: %TRUE if successful, %FALSE otherwise
+ **/
+static gboolean jaw_text_set_selection(AtkText *text, gint selection_num,
+ gint start_offset, gint end_offset) {
+ JAW_DEBUG("%p, %d, %d, %d", text, selection_num, start_offset, end_offset);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument text passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_TEXT(text, FALSE);
+
+ jboolean jresult = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_text, cachedTextSetSelectionMethod, (jint)selection_num,
+ (jint)start_offset, (jint)end_offset);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+
+ return jresult;
+}
+
+/**
+ * jaw_text_set_caret_offset:
+ * @text: an #AtkText
+ * @offset: the character offset of the new caret position
+ *
+ * Sets the caret (cursor) position to the specified @offset.
+ *
+ * In the case of rich-text content, this method should either grab focus
+ * or move the sequential focus navigation starting point (if the application
+ * supports this concept) as if the user had clicked on the new caret position.
+ * Typically, this means that the target of this operation is the node
+ *containing the new caret position or one of its ancestors. In other words,
+ *after this method is called, if the user advances focus, it should move to the
+ *first focusable node following the new caret position.
+ *
+ * Calling this method should also scroll the application viewport in a way
+ * that matches the behavior of the application's typical caret motion or tab
+ * navigation as closely as possible. This also means that if the application's
+ * caret motion or focus navigation does not trigger a scroll operation, this
+ * method should not trigger one either. If the application does not have a
+ *caret motion or focus navigation operation, this method should try to scroll
+ *the new caret position into view while minimizing unnecessary scroll motion.
+ *
+ * Returns: %TRUE if successful, %FALSE otherwise.
+ **/
+static gboolean jaw_text_set_caret_offset(AtkText *text, gint offset) {
+ JAW_DEBUG("%p, %d", text, offset);
+
+ if (text == NULL) {
+ g_warning("%s: Null argument text passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ JAW_GET_TEXT(text, FALSE);
+
+ jboolean jresult = (*jniEnv)->CallBooleanMethod(
+ jniEnv, atk_text, cachedTextSetCaretOffsetMethod, (jint)offset);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+ return FALSE;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_text);
+
+ return jresult;
+}
+
+static gboolean jaw_text_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localClassAtkText =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkText");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClassAtkText == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkText class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedTextAtkTextClass = (*jniEnv)->NewGlobalRef(jniEnv, localClassAtkText);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClassAtkText);
+
+ if (cachedTextAtkTextClass == NULL) {
+ g_warning("%s: Failed to create global reference for AtkText class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedTextCreateAtkTextMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedTextAtkTextClass, "create_atk_text",
+ "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/"
+ "AtkText;");
+
+ cachedTextGetTextMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_text", "(II)Ljava/lang/String;");
+
+ cachedTextGetCharacterAtOffsetMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_character_at_offset", "(I)C");
+
+ cachedTextGetTextAfterOffsetMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_text_after_offset",
+ "(II)Lorg/GNOME/Accessibility/AtkText$StringSequence;");
+
+ cachedTextGetTextAtOffsetMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_text_at_offset",
+ "(II)Lorg/GNOME/Accessibility/AtkText$StringSequence;");
+
+ cachedTextGetTextBeforeOffsetMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_text_before_offset",
+ "(II)Lorg/GNOME/Accessibility/AtkText$StringSequence;");
+
+ cachedTextGetStringAtOffsetMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_string_at_offset",
+ "(II)Lorg/GNOME/Accessibility/AtkText$StringSequence;");
+
+ cachedTextGetCaretOffsetMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_caret_offset", "()I");
+
+ cachedTextGetCharacterExtentsMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_character_extents",
+ "(II)Ljava/awt/Rectangle;");
+
+ cachedTextGetCharacterCountMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_character_count", "()I");
+
+ cachedTextGetOffsetAtPointMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_offset_at_point", "(III)I");
+
+ cachedTextGetRangeExtentsMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_range_extents",
+ "(III)Ljava/awt/Rectangle;");
+
+ cachedTextGetNSelectionsMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_n_selections", "()I");
+
+ cachedTextGetSelectionMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "get_selection",
+ "()Lorg/GNOME/Accessibility/AtkText$StringSequence;");
+
+ cachedTextAddSelectionMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "add_selection", "(II)Z");
+
+ cachedTextRemoveSelectionMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "remove_selection", "(I)Z");
+
+ cachedTextSetSelectionMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "set_selection", "(III)Z");
+
+ cachedTextSetCaretOffsetMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedTextAtkTextClass, "set_caret_offset", "(I)Z");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedTextCreateAtkTextMethod == NULL ||
+ cachedTextGetTextMethod == NULL ||
+ cachedTextGetCharacterAtOffsetMethod == NULL ||
+ cachedTextGetTextAfterOffsetMethod == NULL ||
+ cachedTextGetTextAtOffsetMethod == NULL ||
+ cachedTextGetTextBeforeOffsetMethod == NULL ||
+ cachedTextGetStringAtOffsetMethod == NULL ||
+ cachedTextGetCaretOffsetMethod == NULL ||
+ cachedTextGetCharacterExtentsMethod == NULL ||
+ cachedTextGetCharacterCountMethod == NULL ||
+ cachedTextGetOffsetAtPointMethod == NULL ||
+ cachedTextGetRangeExtentsMethod == NULL ||
+ cachedTextGetNSelectionsMethod == NULL ||
+ cachedTextGetSelectionMethod == NULL ||
+ cachedTextAddSelectionMethod == NULL ||
+ cachedTextRemoveSelectionMethod == NULL ||
+ cachedTextSetSelectionMethod == NULL ||
+ cachedTextSetCaretOffsetMethod == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache one or more AtkText method IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ jclass localClassStringSeq = (*jniEnv)->FindClass(
+ jniEnv, "org/GNOME/Accessibility/AtkText$StringSequence");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClassStringSeq == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkText$StringSequence class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedTextStringSequenceClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localClassStringSeq);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClassStringSeq);
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedTextStringSequenceClass == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create global reference for "
+ "AtkText$StringSequence class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedTextStrFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedTextStringSequenceClass, "str", "Ljava/lang/String;");
+
+ cachedTextStartOffsetFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedTextStringSequenceClass, "start_offset", "I");
+
+ cachedTextEndOffsetFieldID = (*jniEnv)->GetFieldID(
+ jniEnv, cachedTextStringSequenceClass, "end_offset", "I");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || cachedTextStrFieldID == NULL ||
+ cachedTextStartOffsetFieldID == NULL ||
+ cachedTextEndOffsetFieldID == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache StringSequence field IDs", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedTextAtkTextClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTextAtkTextClass);
+ cachedTextAtkTextClass = NULL;
+ }
+ if (cachedTextStringSequenceClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTextStringSequenceClass);
+ cachedTextStringSequenceClass = NULL;
+ }
+ cachedTextCreateAtkTextMethod = NULL;
+ cachedTextGetTextMethod = NULL;
+ cachedTextGetCharacterAtOffsetMethod = NULL;
+ cachedTextGetTextAfterOffsetMethod = NULL;
+ cachedTextGetTextAtOffsetMethod = NULL;
+ cachedTextGetTextBeforeOffsetMethod = NULL;
+ cachedTextGetStringAtOffsetMethod = NULL;
+ cachedTextGetCaretOffsetMethod = NULL;
+ cachedTextGetCharacterExtentsMethod = NULL;
+ cachedTextGetCharacterCountMethod = NULL;
+ cachedTextGetOffsetAtPointMethod = NULL;
+ cachedTextGetRangeExtentsMethod = NULL;
+ cachedTextGetNSelectionsMethod = NULL;
+ cachedTextGetSelectionMethod = NULL;
+ cachedTextAddSelectionMethod = NULL;
+ cachedTextRemoveSelectionMethod = NULL;
+ cachedTextSetSelectionMethod = NULL;
+ cachedTextSetCaretOffsetMethod = NULL;
+ cachedTextStrFieldID = NULL;
+ cachedTextStartOffsetFieldID = NULL;
+ cachedTextEndOffsetFieldID = NULL;
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+}
+
+void jaw_text_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedTextAtkTextClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTextAtkTextClass);
+ cachedTextAtkTextClass = NULL;
+ }
+ if (cachedTextStringSequenceClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedTextStringSequenceClass);
+ cachedTextStringSequenceClass = NULL;
+ }
+ cachedTextCreateAtkTextMethod = NULL;
+ cachedTextGetTextMethod = NULL;
+ cachedTextGetCharacterAtOffsetMethod = NULL;
+ cachedTextGetTextAfterOffsetMethod = NULL;
+ cachedTextGetTextAtOffsetMethod = NULL;
+ cachedTextGetTextBeforeOffsetMethod = NULL;
+ cachedTextGetStringAtOffsetMethod = NULL;
+ cachedTextGetCaretOffsetMethod = NULL;
+ cachedTextGetCharacterExtentsMethod = NULL;
+ cachedTextGetCharacterCountMethod = NULL;
+ cachedTextGetOffsetAtPointMethod = NULL;
+ cachedTextGetRangeExtentsMethod = NULL;
+ cachedTextGetNSelectionsMethod = NULL;
+ cachedTextGetSelectionMethod = NULL;
+ cachedTextAddSelectionMethod = NULL;
+ cachedTextRemoveSelectionMethod = NULL;
+ cachedTextSetSelectionMethod = NULL;
+ cachedTextSetCaretOffsetMethod = NULL;
+ cachedTextStrFieldID = NULL;
+ cachedTextStartOffsetFieldID = NULL;
+ cachedTextEndOffsetFieldID = NULL;
+ cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawtoplevel.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtoplevel.c
new file mode 100644
index 000000000000..7e8101e50529
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtoplevel.c
@@ -0,0 +1,383 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawtoplevel.h"
+#include "jawutil.h"
+#include
+#include
+#include
+#include
+
+static void jaw_toplevel_initialize(AtkObject *accessible, gpointer data);
+static void jaw_toplevel_object_finalize(GObject *obj);
+
+static const gchar *jaw_toplevel_get_name(AtkObject *obj);
+static const gchar *jaw_toplevel_get_description(AtkObject *obj);
+static gint jaw_toplevel_get_n_children(AtkObject *obj);
+static gint jaw_toplevel_get_index_in_parent(AtkObject *obj);
+static AtkRole jaw_toplevel_get_role(AtkObject *obj);
+static AtkObject *jaw_toplevel_ref_child(AtkObject *obj, gint i);
+static AtkObject *jaw_toplevel_get_parent(AtkObject *obj);
+
+G_DEFINE_TYPE(JawToplevel, jaw_toplevel, ATK_TYPE_OBJECT)
+
+static void jaw_toplevel_class_init(JawToplevelClass *klass) {
+ JAW_DEBUG("%p", klass);
+
+ if (klass == NULL) {
+ g_warning("%s: Null argument klass passed to the function", G_STRFUNC);
+ return;
+ }
+
+ AtkObjectClass *atk_object_class = ATK_OBJECT_CLASS(klass);
+ GObjectClass *g_object_class = G_OBJECT_CLASS(klass);
+
+ atk_object_class->initialize = jaw_toplevel_initialize;
+ atk_object_class->get_name = jaw_toplevel_get_name;
+ atk_object_class->get_description = jaw_toplevel_get_description;
+ atk_object_class->get_n_children = jaw_toplevel_get_n_children;
+ atk_object_class->get_index_in_parent = jaw_toplevel_get_index_in_parent;
+ atk_object_class->get_role = jaw_toplevel_get_role;
+ atk_object_class->ref_child = jaw_toplevel_ref_child;
+ atk_object_class->get_parent = jaw_toplevel_get_parent;
+
+ g_object_class->finalize = jaw_toplevel_object_finalize;
+}
+
+static void jaw_toplevel_init(JawToplevel *toplevel) {
+ JAW_DEBUG("%p", toplevel);
+
+ if (toplevel == NULL) {
+ g_warning("%s: Null argument toplevel passed to the function",
+ G_STRFUNC);
+ return;
+ }
+
+ g_mutex_init(&toplevel->mutex);
+ toplevel->windows = NULL;
+}
+
+static void jaw_toplevel_initialize(AtkObject *accessible, gpointer data) {
+ JAW_DEBUG("%p, %p", accessible, data);
+
+ if (accessible == NULL) {
+ g_warning("%s: Null argument accessible passed to the function",
+ G_STRFUNC);
+ return;
+ }
+
+ ATK_OBJECT_CLASS(jaw_toplevel_parent_class)->initialize(accessible, data);
+}
+
+static void jaw_toplevel_object_finalize(GObject *obj) {
+ JAW_DEBUG("%p", obj);
+
+ if (obj == NULL) {
+ g_warning("%s: Null argument obj passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JawToplevel *jaw_toplevel = JAW_TOPLEVEL(obj);
+
+ if (jaw_toplevel != NULL) {
+ g_mutex_lock(&jaw_toplevel->mutex);
+ if (jaw_toplevel->windows != NULL) {
+ g_list_free(jaw_toplevel->windows);
+ jaw_toplevel->windows = NULL;
+ }
+ g_mutex_unlock(&jaw_toplevel->mutex);
+ g_mutex_clear(&jaw_toplevel->mutex);
+ }
+
+ G_OBJECT_CLASS(jaw_toplevel_parent_class)->finalize(obj);
+}
+
+/**
+ * jaw_toplevel_get_name:
+ * @obj: an #AtkObject
+ *
+ * Gets the accessible name of the obj.
+ *
+ * Returns: a character string representing the accessible name of the object:
+ * the name of the first named child of the object, and if no such child exists,
+ * returns 'Java Application'.
+ **/
+static const gchar *jaw_toplevel_get_name(AtkObject *obj) {
+ JAW_DEBUG("%p", obj);
+
+ if (obj == NULL) {
+ g_warning("%s: Null argument obj passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ gint n_accessible_children = atk_object_get_n_accessible_children(obj);
+ for (gint i = 0; i < n_accessible_children; i++) {
+ // The caller of the method `atk_object_ref_accessible_child` takes
+ // ownership of the returned data, and is responsible for freeing it, so
+ // child must be freed.
+ AtkObject *child = atk_object_ref_accessible_child(obj, i);
+ if (child != NULL) {
+ const gchar *name = atk_object_get_name(child);
+ if (name && strlen(name) > 0) {
+ g_object_unref(G_OBJECT(child));
+ return name;
+ }
+ g_object_unref(G_OBJECT(child));
+ }
+ }
+
+ return "Java Application";
+}
+
+/**
+ * jaw_toplevel_get_description:
+ * @accessible: an #AtkObject
+ *
+ * Gets the accessible description of the accessible.
+ *
+ * Returns: a character string representing the accessible description
+ * of the accessible.
+ *
+ **/
+static const gchar *jaw_toplevel_get_description(AtkObject *obj) {
+ JAW_DEBUG("%p", obj);
+
+ if (obj == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ return "Accessible Java application";
+}
+
+/**
+ * jaw_toplevel_get_n_children:
+ * @obj: an #AtkObject
+ *
+ * Gets the number of accessible children of the obj.
+ *
+ * Returns: an integer representing the number of accessible children
+ * of the obj.
+ **/
+static gint jaw_toplevel_get_n_children(AtkObject *obj) {
+ JAW_DEBUG("%p", obj);
+
+ if (obj == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JawToplevel *jaw_toplevel = JAW_TOPLEVEL(obj);
+ if (jaw_toplevel == NULL) {
+ g_warning("%s: jaw_toplevel is NULL", G_STRFUNC);
+ return 0;
+ }
+
+ g_mutex_lock(&jaw_toplevel->mutex);
+ if (jaw_toplevel->windows == NULL) {
+ g_mutex_unlock(&jaw_toplevel->mutex);
+ return 0;
+ }
+ gint children_number = g_list_length(jaw_toplevel->windows);
+ g_mutex_unlock(&jaw_toplevel->mutex);
+
+ return children_number;
+}
+
+/**
+ * jaw_toplevel_get_index_in_parent:
+ * @obj: an #AtkObject
+ *
+ * Gets the 0-based index of this obj in its parent; returns -1 if the
+ * obj does not have an accessible parent.
+ *
+ * Returns: an integer which is the index of the obj in its parent
+ **/
+static gint jaw_toplevel_get_index_in_parent(AtkObject *obj) {
+ JAW_DEBUG("%p", obj);
+
+ if (obj == NULL) {
+ g_warning("%s: Null argument passed to the function", G_STRFUNC);
+ return -1;
+ }
+
+ // toplevel object does not have parent
+ return -1;
+}
+
+/**
+ * jaw_toplevel_get_role:
+ * @obj: an #AtkObject
+ *
+ * Gets the role of the accessible.
+ *
+ * Returns: an #AtkRole which is the role of the obj (`ATK_ROLE_APPLICATION` for
+ * toplevel object)
+ **/
+static AtkRole jaw_toplevel_get_role(AtkObject *obj) {
+ JAW_DEBUG("%p", obj);
+
+ if (obj == NULL) {
+ g_warning("%s: Null argument obj passed to the function", G_STRFUNC);
+ return ATK_ROLE_INVALID;
+ }
+
+ return ATK_ROLE_APPLICATION;
+}
+
+/**
+ * atk_object_ref_accessible_child:
+ * @obj: an #AtkObject
+ * @i: a gint representing the position of the child, starting from 0
+ *
+ * Gets a reference to the specified accessible child of the object.
+ * The accessible children are 0-based so the first accessible child is
+ * at index 0, the second at index 1 and so on.
+ *
+ * Returns: (transfer full): an #AtkObject representing the specified
+ * accessible child of the obj.
+ **/
+static AtkObject *jaw_toplevel_ref_child(AtkObject *obj, gint i) {
+ JAW_DEBUG("%p, %d", obj, i);
+
+ if (obj == NULL) {
+ g_warning("%s: Null argument obj passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JawToplevel *jaw_toplevel = JAW_TOPLEVEL(obj);
+ if (jaw_toplevel == NULL) {
+ g_warning("%s: jaw_toplevel is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ g_mutex_lock(&jaw_toplevel->mutex);
+ if (jaw_toplevel->windows == NULL) {
+ g_mutex_unlock(&jaw_toplevel->mutex);
+ return NULL;
+ }
+ AtkObject *child = (AtkObject *)g_list_nth_data(jaw_toplevel->windows, i);
+
+ // The caller of the `jaw_toplevel_ref_child` will be responsible for
+ // freeing the 'child' (because of transfer full annotation)
+ if (child != NULL) {
+ g_object_ref(G_OBJECT(child));
+ }
+ g_mutex_unlock(&jaw_toplevel->mutex);
+
+ return child;
+}
+
+/**
+ * atk_object_get_parent:
+ * @obj: an #AtkObject
+ *
+ * Gets the accessible parent of the accessible.
+ *
+ * Returns: (transfer none): an #AtkObject representing the accessible
+ * parent of the obj
+ **/
+static AtkObject *jaw_toplevel_get_parent(AtkObject *obj) {
+ JAW_DEBUG("%p", obj);
+
+ if (obj == NULL) {
+ g_warning("%s: Null argument obj passed to the function", G_STRFUNC);
+ }
+
+ return NULL;
+}
+
+// JawToplevel methods
+
+gint jaw_toplevel_add_window(JawToplevel *toplevel, AtkObject *child) {
+ JAW_DEBUG("%p, %p", toplevel, child);
+
+ if (toplevel == NULL || child == NULL) {
+ g_warning("%s: invalid argument(s) : toplevel=%p, child=%p", G_STRFUNC,
+ toplevel, child);
+ return -1;
+ }
+
+ g_mutex_lock(&toplevel->mutex);
+ if (toplevel->windows != NULL &&
+ g_list_index(toplevel->windows, child) != -1) {
+ g_mutex_unlock(&toplevel->mutex);
+ return -1;
+ }
+
+ toplevel->windows = g_list_append(toplevel->windows, child);
+ gint index = g_list_index(toplevel->windows, child);
+ g_mutex_unlock(&toplevel->mutex);
+
+ return index;
+}
+
+gint jaw_toplevel_remove_window(JawToplevel *toplevel, AtkObject *child) {
+ JAW_DEBUG("%p, %p", toplevel, child);
+
+ if (toplevel == NULL || child == NULL) {
+ g_warning("%s: invalid argument(s) : toplevel=%p, child=%p", G_STRFUNC,
+ toplevel, child);
+ return -1;
+ }
+
+ g_mutex_lock(&toplevel->mutex);
+ if (toplevel->windows == NULL) {
+ g_warning(
+ "%s: Cannot remove window %p: the toplevel window list is NULL "
+ "(not initialized or already cleared)",
+ G_STRFUNC, child);
+ g_mutex_unlock(&toplevel->mutex);
+ return -1;
+ }
+
+ gint index = (g_list_index(toplevel->windows, child));
+ if (index == -1) {
+ g_warning("%s: Cannot remove window %p: it is not present in the "
+ "toplevel window list",
+ G_STRFUNC, child);
+ g_mutex_unlock(&toplevel->mutex);
+ return -1;
+ }
+
+ toplevel->windows = g_list_remove(toplevel->windows, child);
+ g_mutex_unlock(&toplevel->mutex);
+
+ return index;
+}
+
+gint jaw_toplevel_get_child_index(JawToplevel *toplevel, AtkObject *child) {
+ JAW_DEBUG("%p, %p", toplevel, child);
+
+ if (toplevel == NULL || child == NULL) {
+ g_warning("%s: invalid argument(s) : toplevel=%p, child=%p", G_STRFUNC,
+ toplevel, child);
+ return -1;
+ }
+
+ g_mutex_lock(&toplevel->mutex);
+ if (toplevel->windows == NULL) {
+ g_mutex_unlock(&toplevel->mutex);
+ return -1;
+ }
+
+ gint index = g_list_index(toplevel->windows, child);
+ g_mutex_unlock(&toplevel->mutex);
+ return index;
+}
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawtoplevel.h b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtoplevel.h
new file mode 100644
index 000000000000..d457634ab632
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawtoplevel.h
@@ -0,0 +1,60 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _JAW_TOPLEVEL_H_
+#define _JAW_TOPLEVEL_H_
+
+#include
+
+G_BEGIN_DECLS
+
+#define JAW_TYPE_TOPLEVEL (jaw_toplevel_get_type())
+#define JAW_TOPLEVEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), JAW_TYPE_TOPLEVEL, JawToplevel))
+#define JAW_TOPLEVEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), JAW_TYPE_TOPLEVEL, JawToplevelClass))
+#define JAW_IS_TOPLEVEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), JAW_TYPE_TOPLEVEL))
+#define JAW_IS_TOPLEVEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), JAW_TYPE_TOPLEVEL))
+#define JAW_TOPLEVEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), JAW_TYPE_TOPLEVEL, JawToplevelClass))
+
+typedef struct _JawToplevel JawToplevel;
+typedef struct _JawToplevelClass JawToplevelClass;
+
+struct _JawToplevel {
+ AtkObject parent;
+ GList *windows;
+ GMutex mutex;
+};
+
+GType jaw_toplevel_get_type(void);
+
+struct _JawToplevelClass {
+ AtkObjectClass parent_class;
+};
+
+gint jaw_toplevel_add_window(JawToplevel *, AtkObject *);
+gint jaw_toplevel_remove_window(JawToplevel *, AtkObject *);
+gint jaw_toplevel_get_child_index(JawToplevel *, AtkObject *);
+
+G_END_DECLS
+
+#endif
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawutil.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawutil.c
new file mode 100644
index 000000000000..b0a012acaa0a
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawutil.c
@@ -0,0 +1,1213 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems Inc.
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawutil.h"
+#include "jawcache.h"
+#include "jawobject.h"
+#include "jawtoplevel.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static void jaw_util_class_init(JawUtilClass *klass, void *klass_data);
+static guint jaw_util_add_key_event_listener(AtkKeySnoopFunc listener,
+ gpointer data);
+static void jaw_util_remove_key_event_listener(guint remove_listener);
+static AtkObject *jaw_util_get_root(void);
+static const gchar *jaw_util_get_toolkit_name(void);
+static const gchar *jaw_util_get_toolkit_version(void);
+
+static JavaVM *cachedJVM = NULL;
+
+static jclass cachedUtilAtkObjectClass = NULL;
+static jclass cachedUtilAccessibleRoleClass = NULL;
+static jclass cachedUtilAccessibleStateClass = NULL;
+static jclass cachedUtilRectangleClass = NULL;
+static jmethodID cachedUtilGetTflagFromObjMethod = NULL;
+static jmethodID cachedUtilGetAccessibleRoleMethod = NULL;
+static jmethodID cachedUtilGetAccessibleParentMethod = NULL;
+static jfieldID cachedUtilRectangleXField = NULL;
+static jfieldID cachedUtilRectangleYField = NULL;
+static jfieldID cachedUtilRectangleWidthField = NULL;
+static jfieldID cachedUtilRectangleHeightField = NULL;
+
+static GMutex cache_mutex;
+static gboolean jawutil_cache_initialized = FALSE;
+
+static gboolean jawutil_init_jni_cache(JNIEnv *jniEnv);
+
+typedef struct _JawKeyListenerInfo {
+ AtkKeySnoopFunc listener;
+ gpointer data; // data that should be sent to the listener
+} JawKeyListenerInfo;
+
+// Maps unique keys to `JawKeyListenerInfo`
+static GHashTable *key_listener_list = NULL;
+
+GType jaw_util_get_type(void) {
+ JAW_DEBUG("");
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo tinfo = {
+ sizeof(JawUtilClass),
+ (GBaseInitFunc)NULL, /*base init*/
+ (GBaseFinalizeFunc)NULL, /*base finalize */
+ (GClassInitFunc)jaw_util_class_init, /* class init */
+ (GClassFinalizeFunc)NULL, /*class finalize */
+ NULL, /* class data */
+ sizeof(JawUtil), /* instance size */
+ 0, /* nb preallocs */
+ (GInstanceInitFunc)NULL, /* instance init */
+ NULL /* value table */
+ };
+
+ type = g_type_register_static(ATK_TYPE_UTIL, "JawUtil", &tinfo, 0);
+ }
+
+ return type;
+}
+
+static void jaw_util_class_init(JawUtilClass *kclass, void *klass_data) {
+ JAW_DEBUG("%p, %p", kclass, klass_data);
+
+ AtkUtilClass *atk_class;
+ gpointer data;
+
+ data = g_type_class_peek(ATK_TYPE_UTIL);
+ atk_class = ATK_UTIL_CLASS(data);
+
+ atk_class->add_key_event_listener = jaw_util_add_key_event_listener;
+ atk_class->remove_key_event_listener = jaw_util_remove_key_event_listener;
+ atk_class->get_root = jaw_util_get_root;
+ atk_class->get_toolkit_name = jaw_util_get_toolkit_name;
+ atk_class->get_toolkit_version = jaw_util_get_toolkit_version;
+}
+
+/**
+ * Notifies a key event to the registered key listener.
+ *
+ * @param key
+ * @param value Pointer to the `JawKeyListenerInfo`
+ * @param data Pointer to the `AtkKeyEventStruct`
+ *
+ * @return TRUE if the listener processes the event successfully,
+ * FALSE if any argument is null or the listener returns FALSE.
+ */
+static gboolean notify_hf(gpointer key, gpointer value, gpointer data) {
+ JAW_DEBUG("%p, %p, %p", key, value, data);
+
+ if (value == NULL || data == NULL) {
+ g_warning("%s: Null argument passed. value=%p, data=%p", G_STRFUNC,
+ (void *)value, (void *)data);
+ return FALSE;
+ }
+
+ JawKeyListenerInfo *info = (JawKeyListenerInfo *)value;
+ if (info == NULL) {
+ g_warning("%s: info is NULL", G_STRFUNC);
+ return FALSE;
+ }
+ AtkKeyEventStruct *key_event = (AtkKeyEventStruct *)data;
+ if (key_event == NULL) {
+ g_warning("%s: key_event is NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ AtkKeySnoopFunc func = info->listener;
+ gpointer func_data = info->data;
+
+ JAW_DEBUG("key event %d %x %x %d '%s' %u %u", key_event->type,
+ key_event->state, key_event->keyval, key_event->length,
+ key_event->string, (unsigned)key_event->keycode,
+ (unsigned)key_event->timestamp);
+
+ return (*func)(key_event, func_data) ? TRUE : FALSE;
+}
+
+/**
+ * Inserts key value pair into a hash table.
+ *
+ * @param key Pointer to the hash table's key
+ * @param value Pointer to the associated value
+ * @param data Pointer to the `GHashTable`
+ */
+static void insert_hf(gpointer key, gpointer value, gpointer data) {
+ JAW_DEBUG("%p, %p, %p", key, value, data);
+
+ if (key == NULL || value == NULL || data == NULL) {
+ g_warning("%s: Null argument passed. key=%p, value=%p, data=%p",
+ G_STRFUNC, (void *)key, (void *)value, (void *)data);
+ return;
+ }
+
+ GHashTable *hash_table = (GHashTable *)data;
+ if (hash_table == NULL) {
+ g_warning("%s: hash_table is NULL", G_STRFUNC);
+ return;
+ }
+ g_hash_table_insert(hash_table, key, value);
+}
+
+gboolean jaw_util_dispatch_key_event(AtkKeyEventStruct *event) {
+ JAW_DEBUG("%p", event);
+
+ if (event == NULL) {
+ g_warning("%s: Null argument event passed to the function", G_STRFUNC);
+ return FALSE;
+ }
+
+ gint consumed = 0;
+ if (key_listener_list != NULL) {
+ GHashTable *new_hash = g_hash_table_new(NULL, NULL);
+ if (new_hash != NULL) {
+ g_hash_table_foreach(key_listener_list, insert_hf, new_hash);
+ consumed = g_hash_table_foreach_steal(new_hash, notify_hf, event);
+ g_hash_table_destroy(new_hash);
+ }
+ }
+ g_debug("%s: consumed: %d", G_STRFUNC, consumed);
+
+ return (consumed > 0) ? TRUE : FALSE;
+}
+
+/**
+ * jaw_util_add_key_event_listener:
+ * @listener: the listener to notify
+ * @data: a #gpointer that points to a block of data that should be sent to the
+ *registered listeners, along with the event notification, when it occurs.
+ *
+ * Adds the specified function to the list of functions to be called
+ * when a key event occurs. The @data element will be passed to the
+ * #AtkKeySnoopFunc (@listener) as the @func_data param, on notification.
+ *
+ * Returns: added event listener id, or 0 on failure.
+ **/
+static guint jaw_util_add_key_event_listener(AtkKeySnoopFunc listener,
+ gpointer data) {
+ JAW_DEBUG("%p, %p", listener, data);
+
+ if (listener == NULL) {
+ g_warning("%s: Null argument listener passed to the function",
+ G_STRFUNC);
+ return 0;
+ }
+
+ static guint key = 0;
+
+ if (key_listener_list == NULL) {
+ key_listener_list = g_hash_table_new(NULL, NULL);
+ }
+
+ JawKeyListenerInfo *info = g_new0(JawKeyListenerInfo, 1);
+ info->listener = listener;
+ info->data = data;
+
+ key++;
+ g_hash_table_insert(key_listener_list, GUINT_TO_POINTER(key), info);
+
+ return key;
+}
+
+/**
+ * jaw_util_remove_key_event_listener:
+ * @listener_id: the id of the event listener to remove
+ *
+ * @listener_id is the value returned by #atk_add_key_event_listener
+ * when you registered that event listener.
+ *
+ * Removes the specified event listener.
+ **/
+static void jaw_util_remove_key_event_listener(guint remove_listener) {
+ JAW_DEBUG("%u", remove_listener);
+ gpointer *value = g_hash_table_lookup(key_listener_list,
+ GUINT_TO_POINTER(remove_listener));
+ if (value) {
+ g_free(value);
+ }
+
+ g_hash_table_remove(key_listener_list, GUINT_TO_POINTER(remove_listener));
+}
+
+/**
+ * jaw_util_get_root:
+ *
+ * Gets the root accessible container for the current application.
+ *
+ * Returns: (transfer none): the root accessible container for the current
+ * application
+ **/
+static AtkObject *jaw_util_get_root(void) {
+ JAW_DEBUG("");
+ static JawToplevel *root = NULL;
+
+ if (root == NULL) {
+ root = g_object_new(JAW_TYPE_TOPLEVEL, NULL);
+ atk_object_initialize(ATK_OBJECT(root), NULL);
+ }
+
+ return ATK_OBJECT(root);
+}
+
+/**
+ * jaw_util_get_toolkit_name:
+ *
+ * Gets name string for the GUI toolkit implementing ATK for this application.
+ *
+ * Returns: name string for the GUI toolkit implementing ATK for this
+ *application
+ **/
+static const gchar *jaw_util_get_toolkit_name(void) {
+ JAW_DEBUG("");
+ return "J2SE-access-bridge";
+}
+
+/**
+ * jaw_util_get_toolkit_version:
+ *
+ * Gets version string for the GUI toolkit implementing ATK for this
+ *application.
+ *
+ * Returns: version string for the GUI toolkit implementing ATK for this
+ *application
+ **/
+static const gchar *jaw_util_get_toolkit_version(void) {
+ JAW_DEBUG("");
+ return "1.0";
+}
+
+guint jaw_util_get_tflag_from_jobj(JNIEnv *jniEnv, jobject jObj) {
+ JAW_DEBUG("%p, %p", jniEnv, jObj);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: Null argument jniEnv passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ if (!jawutil_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return 0;
+ }
+
+ guint result = (guint)(*jniEnv)->CallStaticIntMethod(
+ jniEnv, cachedUtilAtkObjectClass, cachedUtilGetTflagFromObjMethod,
+ jObj);
+
+ return result;
+}
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserve) {
+ JAW_DEBUG("%p, %p", jvm, reserve);
+ if (jvm == NULL) {
+ g_error("JavaVM pointer was NULL when initializing library");
+ return JNI_ERR;
+ }
+ cachedJVM = jvm;
+ return JNI_VERSION_1_6;
+}
+
+JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *jvm, void *reserve) {
+ JAW_DEBUG("%p, %p", jvm, reserve);
+
+ if (jvm == NULL) {
+ g_error("JavaVM pointer was NULL when unloading library");
+ return;
+ }
+
+ g_warning("JNI_OnUnload() called but this is not supported yet\n");
+}
+
+JNIEnv *jaw_util_get_jni_env(void) {
+ if (cachedJVM == NULL) {
+ g_warning("%s: cachedJVM == NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ JNIEnv *env;
+ jint res = (*cachedJVM)->GetEnv(cachedJVM, (void **)&env, JNI_VERSION_1_6);
+ if (res == JNI_OK && env != NULL) {
+ return env;
+ }
+
+ switch (res) {
+ case JNI_EDETACHED:
+ JavaVMAttachArgs args;
+ args.version = JNI_VERSION_1_6;
+ args.name = "JavaAtkWrapper-JNI-Attached-Thread";
+ args.group = NULL;
+ res =
+ (*cachedJVM)
+ ->AttachCurrentThreadAsDaemon(cachedJVM, (void **)&env, &args);
+ if (res == JNI_OK && env != NULL) {
+ return env;
+ }
+ g_printerr("\n *** Attach failed. *** JNIEnv thread is detached.\n");
+ break;
+ case JNI_EVERSION:
+ g_printerr(" *** Version error *** \n");
+ break;
+ default:
+ g_printerr(" *** Unknown result %d *** \n", res);
+ break;
+ }
+
+ fflush(stderr);
+ return NULL;
+}
+
+/* Currently unused: our thread lives forever until application termination. */
+void jaw_util_detach(void) {
+ JAW_DEBUG("");
+ JavaVM *jvm;
+ jvm = cachedJVM;
+ (*jvm)->DetachCurrentThread(jvm);
+}
+
+static jobject jaw_util_get_java_acc_role(JNIEnv *jniEnv,
+ const gchar *roleName) {
+ JAW_DEBUG("%p, %s", jniEnv, roleName);
+
+ if (jniEnv == NULL || roleName == NULL) {
+ g_warning("%s: Null argument passed (jniEnv=%p, roleName_ptr=%p)",
+ G_STRFUNC, (void *)jniEnv, (void *)roleName);
+ return NULL;
+ }
+
+ if (!jawutil_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jfieldID jfid = (*jniEnv)->GetStaticFieldID(
+ jniEnv, cachedUtilAccessibleRoleClass, roleName,
+ "Ljavax/accessibility/AccessibleRole;");
+ if (jfid == NULL) {
+ g_warning("%s: Failed to find field %s in AccessibleRole class",
+ G_STRFUNC, roleName);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+ jobject jrole = (*jniEnv)->GetStaticObjectField(
+ jniEnv, cachedUtilAccessibleRoleClass, jfid);
+
+ return (*jniEnv)->PopLocalFrame(jniEnv, jrole);
+}
+
+static gboolean jaw_util_is_java_acc_role(JNIEnv *jniEnv, jobject acc_role,
+ const gchar *roleName) {
+ JAW_DEBUG("%p, %p, %s", jniEnv, acc_role, roleName);
+
+ if (jniEnv == NULL || roleName == NULL) {
+ g_warning("%s: Null argument passed (jniEnv=%p, roleName_ptr=%p)",
+ G_STRFUNC, (void *)jniEnv, (void *)roleName);
+ return FALSE;
+ }
+
+ jobject jrole = jaw_util_get_java_acc_role(jniEnv, roleName);
+
+ if ((*jniEnv)->IsSameObject(jniEnv, acc_role, jrole)) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+AtkRole
+jaw_util_get_atk_role_from_AccessibleContext(jobject jAccessibleContext) {
+ JAW_DEBUG("%p", jAccessibleContext);
+
+ if (jAccessibleContext == NULL) {
+ g_warning("%s: Null argument jAccessibleContext passed to the "
+ "function, return ATK_ROLE_UNKNOWN",
+ G_STRFUNC);
+ return ATK_ROLE_UNKNOWN;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is null, return ATK_ROLE_UNKNOWN", G_STRFUNC);
+ return ATK_ROLE_UNKNOWN;
+ }
+
+ if (!jawutil_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return ATK_ROLE_UNKNOWN;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame, return "
+ "ATK_ROLE_UNKNOWN",
+ G_STRFUNC);
+ return ATK_ROLE_UNKNOWN;
+ }
+
+ jobject ac_role = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedUtilAtkObjectClass, cachedUtilGetAccessibleRoleMethod,
+ jAccessibleContext);
+ if (ac_role == NULL) {
+ g_warning("%s: Failed to get accessible role from AccessibleContext",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_UNKNOWN;
+ }
+
+ if (!(*jniEnv)->IsInstanceOf(jniEnv, ac_role,
+ cachedUtilAccessibleRoleClass)) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_INVALID;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "ALERT")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_ALERT;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "AWT_COMPONENT")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_UNKNOWN;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "CANVAS")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_CANVAS;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "CHECK_BOX")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_CHECK_BOX;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "COLOR_CHOOSER")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_COLOR_CHOOSER;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "COLUMN_HEADER")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_COLUMN_HEADER;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "COMBO_BOX")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_COMBO_BOX;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "DATE_EDITOR")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_DATE_EDITOR;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "DESKTOP_ICON")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_DESKTOP_ICON;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "DESKTOP_PANE")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_DESKTOP_FRAME;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "DIALOG")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_DIALOG;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "DIRECTORY_PANE")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_DIRECTORY_PANE;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "EDITBAR")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_EDITBAR;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "FILE_CHOOSER")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_FILE_CHOOSER;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "FILLER")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_FILLER;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "FONT_CHOOSER")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_FONT_CHOOSER;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "FOOTER")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_FOOTER;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "FRAME")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_FRAME;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "GLASS_PANE")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_GLASS_PANE;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "GROUP_BOX")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_PANEL;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "HEADER")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_HEADER;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "HTML_CONTAINER")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_HTML_CONTAINER;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "HYPERLINK")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_LINK;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "ICON")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_ICON;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "INTERNAL_FRAME")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_INTERNAL_FRAME;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "LABEL")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_LABEL;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "LAYERED_PANE")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_LAYERED_PANE;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "LIST")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_LIST;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "LIST_ITEM")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_LIST_ITEM;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "MENU")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_MENU;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "MENU_BAR")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_MENU_BAR;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "MENU_ITEM")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_MENU_ITEM;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "OPTION_PANE")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_OPTION_PANE;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PAGE_TAB")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_PAGE_TAB;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PAGE_TAB_LIST")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_PAGE_TAB_LIST;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PANEL")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_PANEL;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PARAGRAPH")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_PARAGRAPH;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PASSWORD_TEXT")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_PASSWORD_TEXT;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "POPUP_MENU")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_POPUP_MENU;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PROGRESS_BAR")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_PROGRESS_BAR;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "PUSH_BUTTON")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_PUSH_BUTTON;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "RADIO_BUTTON")) {
+
+ jobject jparent = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedUtilAtkObjectClass,
+ cachedUtilGetAccessibleParentMethod, jAccessibleContext);
+ if (jparent == NULL) {
+ g_warning("%s: Failed to get accessible parent using "
+ "get_accessible_parent",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_RADIO_BUTTON;
+ }
+
+ jobject parent_role = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedUtilAtkObjectClass, cachedUtilGetAccessibleRoleMethod,
+ jparent);
+ if (parent_role == NULL) {
+ g_warning("%s: Failed to get parent role using get_accessible_role",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_RADIO_BUTTON;
+ }
+ if (jaw_util_is_java_acc_role(jniEnv, parent_role, "MENU")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_RADIO_MENU_ITEM;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_RADIO_BUTTON;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "ROOT_PANE")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_ROOT_PANE;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "ROW_HEADER")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_ROW_HEADER;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "RULER")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_RULER;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SCROLL_BAR")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_SCROLL_BAR;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SCROLL_PANE")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_SCROLL_PANE;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SEPARATOR")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_SEPARATOR;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SLIDER")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_SLIDER;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SPIN_BOX")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_SPIN_BUTTON;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SPLIT_PANE")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_SPLIT_PANE;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "STATUS_BAR")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_STATUSBAR;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "SWING_COMPONENT")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_UNKNOWN;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "TABLE")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_TABLE;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "TEXT")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_TEXT;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "TOGGLE_BUTTON")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_TOGGLE_BUTTON;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "TOOL_BAR")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_TOOL_BAR;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "TOOL_TIP")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_TOOL_TIP;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "TREE")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_TREE;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "UNKNOWN")) {
+ jobject jparent = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedUtilAtkObjectClass,
+ cachedUtilGetAccessibleParentMethod, jAccessibleContext);
+ if (jparent == NULL) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_APPLICATION;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return ATK_ROLE_UNKNOWN;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "VIEWPORT")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_VIEWPORT;
+ }
+
+ if (jaw_util_is_java_acc_role(jniEnv, ac_role, "WINDOW")) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return ATK_ROLE_WINDOW;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return ATK_ROLE_UNKNOWN; /* ROLE_EXTENDED */
+}
+
+static gboolean is_same_java_state(JNIEnv *jniEnv, jobject jobj,
+ const gchar *strState) {
+ if (jniEnv == NULL || strState == NULL) {
+ g_warning("%s: Null argument passed. jniEnv=%p, strState=%p", G_STRFUNC,
+ (void *)jniEnv, (void *)strState);
+ return FALSE;
+ }
+
+ if (!jawutil_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return FALSE;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return FALSE;
+ }
+
+ jfieldID jfid = (*jniEnv)->GetStaticFieldID(
+ jniEnv, cachedUtilAccessibleStateClass, strState,
+ "Ljavax/accessibility/AccessibleState;");
+ if (jfid == NULL) {
+ g_warning("%s: Failed to find field %s in AccessibleState class",
+ G_STRFUNC, strState);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return FALSE;
+ }
+ jobject jstate = (*jniEnv)->GetStaticObjectField(
+ jniEnv, cachedUtilAccessibleStateClass, jfid);
+ if (jstate == NULL) {
+ g_warning("%s: Failed to get static object field", G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return FALSE;
+ }
+ if ((*jniEnv)->IsSameObject(jniEnv, jobj, jstate)) {
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return TRUE;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return FALSE;
+}
+
+AtkStateType jaw_util_get_atk_state_type_from_java_state(JNIEnv *jniEnv,
+ jobject jobj) {
+ if (jniEnv == NULL) {
+ g_warning("%s: Null argument jniEnv passed to the function", G_STRFUNC);
+ return ATK_STATE_INVALID;
+ }
+
+ if (is_same_java_state(jniEnv, jobj, "ACTIVE"))
+ return ATK_STATE_ACTIVE;
+
+ if (is_same_java_state(jniEnv, jobj, "ARMED"))
+ return ATK_STATE_ARMED;
+
+ if (is_same_java_state(jniEnv, jobj, "BUSY"))
+ return ATK_STATE_BUSY;
+
+ if (is_same_java_state(jniEnv, jobj, "CHECKED"))
+ return ATK_STATE_CHECKED;
+
+ if (is_same_java_state(jniEnv, jobj, "COLLAPSED"))
+#if ATK_CHECK_VERSION(2, 38, 0)
+ return ATK_STATE_COLLAPSED;
+#else
+ return ATK_STATE_INVALID;
+#endif
+
+ if (is_same_java_state(jniEnv, jobj, "EDITABLE"))
+ return ATK_STATE_EDITABLE;
+
+ if (is_same_java_state(jniEnv, jobj, "ENABLED"))
+ return ATK_STATE_ENABLED;
+
+ if (is_same_java_state(jniEnv, jobj, "EXPANDABLE"))
+ return ATK_STATE_EXPANDABLE;
+
+ if (is_same_java_state(jniEnv, jobj, "EXPANDED"))
+ return ATK_STATE_EXPANDED;
+
+ if (is_same_java_state(jniEnv, jobj, "FOCUSABLE"))
+ return ATK_STATE_FOCUSABLE;
+
+ if (is_same_java_state(jniEnv, jobj, "FOCUSED"))
+ return ATK_STATE_FOCUSED;
+
+ if (is_same_java_state(jniEnv, jobj, "HORIZONTAL"))
+ return ATK_STATE_HORIZONTAL;
+
+ if (is_same_java_state(jniEnv, jobj, "ICONIFIED"))
+ return ATK_STATE_ICONIFIED;
+
+ if (is_same_java_state(jniEnv, jobj, "INDETERMINATE"))
+ return ATK_STATE_INDETERMINATE;
+
+ if (is_same_java_state(jniEnv, jobj, "MANAGES_DESCENDANTS"))
+ return ATK_STATE_MANAGES_DESCENDANTS;
+
+ if (is_same_java_state(jniEnv, jobj, "MODAL"))
+ return ATK_STATE_MODAL;
+
+ if (is_same_java_state(jniEnv, jobj, "MULTI_LINE"))
+ return ATK_STATE_MULTI_LINE;
+
+ if (is_same_java_state(jniEnv, jobj, "MULTISELECTABLE"))
+ return ATK_STATE_MULTISELECTABLE;
+
+ if (is_same_java_state(jniEnv, jobj, "OPAQUE"))
+ return ATK_STATE_OPAQUE;
+
+ if (is_same_java_state(jniEnv, jobj, "PRESSED"))
+ return ATK_STATE_PRESSED;
+
+ if (is_same_java_state(jniEnv, jobj, "RESIZABLE"))
+ return ATK_STATE_RESIZABLE;
+
+ if (is_same_java_state(jniEnv, jobj, "SELECTABLE"))
+ return ATK_STATE_SELECTABLE;
+
+ if (is_same_java_state(jniEnv, jobj, "SELECTED"))
+ return ATK_STATE_SELECTED;
+
+ if (is_same_java_state(jniEnv, jobj, "SHOWING"))
+ return ATK_STATE_SHOWING;
+
+ if (is_same_java_state(jniEnv, jobj, "SINGLE_LINE"))
+ return ATK_STATE_SINGLE_LINE;
+
+ if (is_same_java_state(jniEnv, jobj, "TRANSIENT"))
+ return ATK_STATE_TRANSIENT;
+
+ if (is_same_java_state(jniEnv, jobj, "TRUNCATED"))
+ return ATK_STATE_TRUNCATED;
+
+ if (is_same_java_state(jniEnv, jobj, "VERTICAL"))
+ return ATK_STATE_VERTICAL;
+
+ if (is_same_java_state(jniEnv, jobj, "VISIBLE"))
+ return ATK_STATE_VISIBLE;
+
+ return ATK_STATE_INVALID;
+}
+
+void jaw_util_get_rect_info(JNIEnv *jniEnv, jobject jrect, gint *x, gint *y,
+ gint *width, gint *height) {
+ JAW_DEBUG("%p, %p, %p, %p, %p, %p", jniEnv, jrect, x, y, width, height);
+
+ if (jniEnv == NULL || x == NULL || y == NULL || width == NULL ||
+ height == NULL || jrect == NULL) {
+ g_warning("%s: Null argument passed "
+ "(jniEnv=%p, x=%p, y=%p, width=%p, height=%p, jrect=%p)",
+ G_STRFUNC, (void *)jniEnv, (void *)x, (void *)y,
+ (void *)width, (void *)height, (void *)jrect);
+ return;
+ }
+
+ if (!jawutil_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return;
+ }
+
+ (*x) =
+ (gint)(*jniEnv)->GetIntField(jniEnv, jrect, cachedUtilRectangleXField);
+ (*y) =
+ (gint)(*jniEnv)->GetIntField(jniEnv, jrect, cachedUtilRectangleYField);
+ (*width) = (gint)(*jniEnv)->GetIntField(jniEnv, jrect,
+ cachedUtilRectangleWidthField);
+ (*height) = (gint)(*jniEnv)->GetIntField(jniEnv, jrect,
+ cachedUtilRectangleHeightField);
+}
+
+static gboolean jawutil_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (jawutil_cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localAtkObject =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkObject");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localAtkObject == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkObject class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedUtilAtkObjectClass = (*jniEnv)->NewGlobalRef(jniEnv, localAtkObject);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localAtkObject);
+
+ if (cachedUtilAtkObjectClass == NULL) {
+ g_warning("%s: Failed to create global reference for AtkObject class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ jclass localAccessibleRole =
+ (*jniEnv)->FindClass(jniEnv, "javax/accessibility/AccessibleRole");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localAccessibleRole == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AccessibleRole class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedUtilAccessibleRoleClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localAccessibleRole);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localAccessibleRole);
+
+ if (cachedUtilAccessibleRoleClass == NULL) {
+ g_warning(
+ "%s: Failed to create global reference for AccessibleRole class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ jclass localAccessibleState =
+ (*jniEnv)->FindClass(jniEnv, "javax/accessibility/AccessibleState");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localAccessibleState == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AccessibleState class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedUtilAccessibleStateClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localAccessibleState);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localAccessibleState);
+
+ if (cachedUtilAccessibleStateClass == NULL) {
+ g_warning(
+ "%s: Failed to create global reference for AccessibleState class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ jclass localRectangle = (*jniEnv)->FindClass(jniEnv, "java/awt/Rectangle");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localRectangle == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find Rectangle class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedUtilRectangleClass = (*jniEnv)->NewGlobalRef(jniEnv, localRectangle);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localRectangle);
+
+ if (cachedUtilRectangleClass == NULL) {
+ g_warning("%s: Failed to create global reference for Rectangle class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedUtilGetTflagFromObjMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedUtilAtkObjectClass, "get_tflag_from_obj",
+ "(Ljava/lang/Object;)I");
+
+ cachedUtilGetAccessibleRoleMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedUtilAtkObjectClass, "get_accessible_role",
+ "(Ljavax/accessibility/AccessibleContext;)Ljavax/accessibility/"
+ "AccessibleRole;");
+
+ cachedUtilGetAccessibleParentMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedUtilAtkObjectClass, "get_accessible_parent",
+ "(Ljavax/accessibility/AccessibleContext;)Ljavax/accessibility/"
+ "AccessibleContext;");
+
+ cachedUtilRectangleXField =
+ (*jniEnv)->GetFieldID(jniEnv, cachedUtilRectangleClass, "x", "I");
+ cachedUtilRectangleYField =
+ (*jniEnv)->GetFieldID(jniEnv, cachedUtilRectangleClass, "y", "I");
+ cachedUtilRectangleWidthField =
+ (*jniEnv)->GetFieldID(jniEnv, cachedUtilRectangleClass, "width", "I");
+ cachedUtilRectangleHeightField =
+ (*jniEnv)->GetFieldID(jniEnv, cachedUtilRectangleClass, "height", "I");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedUtilGetTflagFromObjMethod == NULL ||
+ cachedUtilGetAccessibleRoleMethod == NULL ||
+ cachedUtilGetAccessibleParentMethod == NULL ||
+ cachedUtilRectangleXField == NULL ||
+ cachedUtilRectangleYField == NULL ||
+ cachedUtilRectangleWidthField == NULL ||
+ cachedUtilRectangleHeightField == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to cache one or more method/field IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ jawutil_cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedUtilAtkObjectClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilAtkObjectClass);
+ cachedUtilAtkObjectClass = NULL;
+ }
+ if (cachedUtilAccessibleRoleClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilAccessibleRoleClass);
+ cachedUtilAccessibleRoleClass = NULL;
+ }
+ if (cachedUtilAccessibleStateClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilAccessibleStateClass);
+ cachedUtilAccessibleStateClass = NULL;
+ }
+ if (cachedUtilRectangleClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilRectangleClass);
+ cachedUtilRectangleClass = NULL;
+ }
+ cachedUtilGetTflagFromObjMethod = NULL;
+ cachedUtilGetAccessibleRoleMethod = NULL;
+ cachedUtilGetAccessibleParentMethod = NULL;
+ cachedUtilRectangleXField = NULL;
+ cachedUtilRectangleYField = NULL;
+ cachedUtilRectangleWidthField = NULL;
+ cachedUtilRectangleHeightField = NULL;
+
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+}
+
+void jaw_util_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedUtilAtkObjectClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilAtkObjectClass);
+ cachedUtilAtkObjectClass = NULL;
+ }
+ if (cachedUtilAccessibleRoleClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilAccessibleRoleClass);
+ cachedUtilAccessibleRoleClass = NULL;
+ }
+ if (cachedUtilAccessibleStateClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilAccessibleStateClass);
+ cachedUtilAccessibleStateClass = NULL;
+ }
+ if (cachedUtilRectangleClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedUtilRectangleClass);
+ cachedUtilRectangleClass = NULL;
+ }
+ cachedUtilGetTflagFromObjMethod = NULL;
+ cachedUtilGetAccessibleRoleMethod = NULL;
+ cachedUtilGetAccessibleParentMethod = NULL;
+ cachedUtilRectangleXField = NULL;
+ cachedUtilRectangleYField = NULL;
+ cachedUtilRectangleWidthField = NULL;
+ cachedUtilRectangleHeightField = NULL;
+ jawutil_cache_initialized = FALSE;
+ g_mutex_unlock(&cache_mutex);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawutil.h b/src/jdk.accessibility/linux/native/libatk-wrapper/jawutil.h
new file mode 100644
index 000000000000..6f114b13fe35
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawutil.h
@@ -0,0 +1,128 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _JAW_UTIL_H_
+#define _JAW_UTIL_H_
+
+#include
+#include
+#include
+#include
+#include
+
+extern int jaw_debug;
+extern FILE *jaw_log_file;
+extern time_t jaw_start_time;
+
+#define PRINT_AND_FLUSH(fmt, ...) \
+ do { \
+ fprintf(jaw_log_file, "[%lu] %s" fmt "\n", \
+ (unsigned long)(time(NULL) - jaw_start_time), __func__, \
+ ##__VA_ARGS__); \
+ fflush(jaw_log_file); \
+ } while (0)
+
+#define JAW_DEBUG(fmt, ...) \
+ do { \
+ if (jaw_debug) { \ \
+ PRINT_AND_FLUSH("(" fmt ")", ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+G_BEGIN_DECLS
+
+#define JAW_TYPE_UTIL (jaw_util_get_type())
+#define JAW_UTIL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), JAW_TYPE_UTIL, JawUtil))
+#define JAW_UTIL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), JAW_TYPE_UTIL, JawUtilClass))
+#define JAW_IS_UTIL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), JAW_TYPE_UTIL))
+#define JAW_IS_UTIL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), JAW_TYPE_UTIL))
+#define JAW_UTIL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), JAW_TYPE_UTIL, JawUtilClass))
+
+typedef struct _JawUtil JawUtil;
+typedef struct _JawUtilClass JawUtilClass;
+
+struct _JawUtil {
+ AtkUtil parent;
+};
+
+GType jaw_util_get_type(void);
+
+struct _JawUtilClass {
+ AtkUtilClass parent_class;
+};
+
+guint jaw_util_get_tflag_from_jobj(JNIEnv *jniEnv, jobject jObj);
+JNIEnv *jaw_util_get_jni_env(void);
+AtkRole jaw_util_get_atk_role_from_AccessibleContext(jobject jobj);
+AtkStateType jaw_util_get_atk_state_type_from_java_state(JNIEnv *jniEnv,
+ jobject jobj);
+void jaw_util_get_rect_info(JNIEnv *jniEnv, jobject jrect, gint *x, gint *y,
+ gint *width, gint *height);
+gboolean jaw_util_dispatch_key_event(AtkKeyEventStruct *event);
+
+void jaw_util_detach(void);
+
+#define JAW_DEFAULT_LOCAL_FRAME_SIZE 10
+
+#define JAW_GET_OBJ_IFACE(o, iface, Data, field, env, name, def_ret) \
+ JawObject *jaw_obj = JAW_OBJECT(o); \
+ if (!jaw_obj) { \
+ g_warning("%s: jaw_obj == NULL in JAW_GET_OBJ_IFACE", G_STRFUNC); \
+ return def_ret; \
+ } \
+ Data *data = jaw_object_get_interface_data(jaw_obj, iface); \
+ JNIEnv *env = jaw_util_get_jni_env(); \
+ if (!env) { \
+ g_warning(#env " == NULL"); \
+ return def_ret; \
+ } \
+ jobject name = (*env)->NewLocalRef(env, data->field); \
+ if (!name) { \
+ g_warning(#name " == NULL"); \
+ return def_ret; \
+ }
+
+#define JAW_GET_OBJ(o, CAST, JawObject, object_name, field, env, name, \
+ def_ret) \
+ JawObject *object_name = CAST(o); \
+ if (!object_name) { \
+ g_warning(#object_name " == NULL"); \
+ return def_ret; \
+ } \
+ JNIEnv *env = jaw_util_get_jni_env(); \
+ jobject name = (*env)->NewLocalRef(env, object_name->field); \
+ if (!name) { \
+ g_warning(#name " == NULL"); \
+ return def_ret; \
+ }
+
+static inline void jaw_jni_clear_exception(JNIEnv *env) {
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionDescribe(env);
+ (*env)->ExceptionClear(env);
+ }
+}
+
+G_END_DECLS
+
+#endif
diff --git a/src/jdk.accessibility/linux/native/libatk-wrapper/jawvalue.c b/src/jdk.accessibility/linux/native/libatk-wrapper/jawvalue.c
new file mode 100644
index 000000000000..7628e808bf03
--- /dev/null
+++ b/src/jdk.accessibility/linux/native/libatk-wrapper/jawvalue.c
@@ -0,0 +1,790 @@
+/*
+ * Java ATK Wrapper for GNOME
+ * Copyright (C) 2009 Sun Microsystems Inc.
+ * Copyright (C) 2015 Magdalen Berns
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jawcache.h"
+#include "jawimpl.h"
+#include "jawutil.h"
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static jclass cachedValueAtkValueClass = NULL;
+static jmethodID cachedValueCreateAtkValueMethod = NULL;
+static jmethodID cachedValueGetCurrentValueMethod = NULL;
+static jmethodID cachedValueSetValueMethod = NULL;
+static jmethodID cachedValueGetMinimumValueMethod = NULL;
+static jmethodID cachedValueGetMaximumValueMethod = NULL;
+static jmethodID cachedValueGetIncrementMethod = NULL;
+static jclass cachedValueByteClass = NULL;
+static jclass cachedValueDoubleClass = NULL;
+static jclass cachedValueFloatClass = NULL;
+static jclass cachedValueIntegerClass = NULL;
+static jclass cachedValueLongClass = NULL;
+static jclass cachedValueShortClass = NULL;
+static jmethodID cachedValueByteValueMethod = NULL;
+static jmethodID cachedValueDoubleValueMethod = NULL;
+static jmethodID cachedValueFloatValueMethod = NULL;
+static jmethodID cachedValueIntValueMethod = NULL;
+static jmethodID cachedValueLongValueMethod = NULL;
+static jmethodID cachedValueShortValueMethod = NULL;
+static jmethodID cachedValueDoubleConstructorMethod = NULL;
+
+static GMutex cache_mutex;
+static gboolean cache_initialized = FALSE;
+
+static gboolean jaw_value_init_jni_cache(JNIEnv *jniEnv);
+
+static void jaw_value_get_current_value(AtkValue *obj, GValue *value);
+static void jaw_value_set_value(AtkValue *obj, const gdouble value);
+static gdouble jaw_value_get_increment(AtkValue *obj);
+static AtkRange *jaw_value_get_range(AtkValue *obj);
+
+typedef struct _ValueData {
+ jobject atk_value;
+} ValueData;
+
+#define JAW_GET_VALUE(obj, def_ret) \
+ JAW_GET_OBJ_IFACE(obj, \
+ org_GNOME_Accessibility_AtkInterface_INTERFACE_VALUE, \
+ ValueData, atk_value, jniEnv, atk_value, def_ret)
+
+void jaw_value_interface_init(AtkValueIface *iface, gpointer data) {
+ JAW_DEBUG("%p, %p", iface, data);
+
+ if (iface == NULL) {
+ g_warning("%s: Null argument iface passed to the function", G_STRFUNC);
+ return;
+ }
+
+ iface->get_current_value = jaw_value_get_current_value;
+ iface->get_maximum_value = NULL; // deprecated: iface->get_maximum_value
+ iface->get_minimum_value = NULL; // deprecated: iface->get_minimum_value
+ iface->set_current_value = NULL; // deprecated: iface->set_current_value
+ iface->get_minimum_increment =
+ NULL; // deprecated: iface->get_minimum_increment
+ iface->get_value_and_text = NULL; // TODO: get_value_and_text
+ iface->get_range = jaw_value_get_range;
+ iface->get_increment = jaw_value_get_increment;
+ iface->get_sub_ranges =
+ NULL; // missing java support for iface->get_sub_ranges
+ iface->set_value = jaw_value_set_value;
+}
+
+gpointer jaw_value_data_init(jobject ac) {
+ JAW_DEBUG("%p", ac);
+
+ if (ac == NULL) {
+ g_warning("%s: Null argument ac passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv is NULL", G_STRFUNC);
+ return NULL;
+ }
+
+ if (!jaw_value_init_jni_cache(jniEnv)) {
+ g_warning("%s: Failed to initialize JNI cache", G_STRFUNC);
+ return NULL;
+ }
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jatk_value = (*jniEnv)->CallStaticObjectMethod(
+ jniEnv, cachedValueAtkValueClass, cachedValueCreateAtkValueMethod, ac);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jatk_value == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning(
+ "%s: Failed to create jatk_value using create_atk_value method",
+ G_STRFUNC);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ ValueData *data = g_new0(ValueData, 1);
+ data->atk_value = (*jniEnv)->NewGlobalRef(jniEnv, jatk_value);
+ if (data->atk_value == NULL) {
+ g_warning("%s: Failed to create global ref for atk_value", G_STRFUNC);
+ g_free(data);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ return data;
+}
+
+void jaw_value_data_finalize(gpointer p) {
+ JAW_DEBUG("%p", p);
+
+ if (p == NULL) {
+ g_warning("%s: Null argument p passed to the function", G_STRFUNC);
+ return;
+ }
+
+ ValueData *data = (ValueData *)p;
+ if (data == NULL) {
+ g_warning("%s: data is null", G_STRFUNC);
+ return;
+ }
+
+ JNIEnv *jniEnv = jaw_util_get_jni_env();
+
+ if (jniEnv == NULL) {
+ g_warning("%s: JNIEnv is NULL in finalize", G_STRFUNC);
+ } else {
+ if (data->atk_value != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, data->atk_value);
+ data->atk_value = NULL;
+ }
+ }
+
+ g_free(data);
+}
+
+static void private_get_g_value_from_java_number(JNIEnv *jniEnv,
+ jobject jnumber,
+ GValue *value) {
+ JAW_DEBUG("%p, %p, %p", jniEnv, jnumber, value);
+
+ if (jniEnv == NULL || value == NULL) {
+ g_warning(
+ "%s: Null argument passed to the function (jniEnv=%p, value=%p)",
+ G_STRFUNC, (void *)jniEnv, (void *)value);
+ return;
+ }
+
+ if ((*jniEnv)->IsInstanceOf(jniEnv, jnumber, cachedValueByteClass)) {
+ g_value_init(value, G_TYPE_CHAR);
+ g_value_set_schar(value,
+ (gchar)(*jniEnv)->CallByteMethod(
+ jniEnv, jnumber, cachedValueByteValueMethod));
+ return;
+ }
+
+ if ((*jniEnv)->IsInstanceOf(jniEnv, jnumber, cachedValueDoubleClass)) {
+ g_value_init(value, G_TYPE_DOUBLE);
+ g_value_set_double(value,
+ (gdouble)(*jniEnv)->CallDoubleMethod(
+ jniEnv, jnumber, cachedValueDoubleValueMethod));
+ return;
+ }
+
+ if ((*jniEnv)->IsInstanceOf(jniEnv, jnumber, cachedValueFloatClass)) {
+ g_value_init(value, G_TYPE_FLOAT);
+ g_value_set_float(value,
+ (gfloat)(*jniEnv)->CallFloatMethod(
+ jniEnv, jnumber, cachedValueFloatValueMethod));
+ return;
+ }
+
+ if ((*jniEnv)->IsInstanceOf(jniEnv, jnumber, cachedValueIntegerClass)) {
+ jint v = (*jniEnv)->CallIntMethod(jniEnv, jnumber,
+ cachedValueIntValueMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Exception in Integer.intValue()", G_STRFUNC);
+ return;
+ }
+
+ g_value_init(value, G_TYPE_INT);
+ g_value_set_int(value, (gint)v);
+ return;
+ }
+
+ if ((*jniEnv)->IsInstanceOf(jniEnv, jnumber, cachedValueShortClass)) {
+ jshort v = (*jniEnv)->CallShortMethod(jniEnv, jnumber,
+ cachedValueShortValueMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Exception in Short.shortValue()", G_STRFUNC);
+ return;
+ }
+
+ g_value_init(value, G_TYPE_INT);
+ g_value_set_int(value, (gint)v);
+ return;
+ }
+
+ if ((*jniEnv)->IsInstanceOf(jniEnv, jnumber, cachedValueLongClass)) {
+ g_value_init(value, G_TYPE_INT64);
+ g_value_set_int64(value,
+ (gint64)(*jniEnv)->CallLongMethod(
+ jniEnv, jnumber, cachedValueLongValueMethod));
+ return;
+ }
+}
+
+/**
+ * jaw_value_get_current_value:
+ * @obj: a GObject instance that implements AtkValueIface
+ * @value: (out): a #GValue representing the current accessible value
+ *
+ * Gets the value of this object.
+ *
+ * Deprecated in atk: Since 2.12. Use atk_value_get_value_and_text()
+ * instead.
+ **/
+static void jaw_value_get_current_value(AtkValue *obj, GValue *value) {
+ JAW_DEBUG("%p, %p", obj, value);
+
+ if (obj == NULL || value == NULL) {
+ g_warning("%s: Null argument passed to the function (obj=%p, value=%p)",
+ G_STRFUNC, (void *)obj, (void *)value);
+ return;
+ }
+
+ if (G_VALUE_TYPE(value) != G_TYPE_INVALID) {
+ g_value_unset(value);
+ }
+
+ JAW_GET_VALUE(obj, ); // create local JNI reference `jobject atk_value`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ jobject jnumber = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_value, cachedValueGetCurrentValueMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Exception occurred while calling get_current_value",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ if (jnumber == NULL) {
+ g_warning(
+ "%s: Failed to get jnumber by calling get_current_value method",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ private_get_g_value_from_java_number(jniEnv, jnumber, value);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * atk_value_set_value:
+ * @obj: a GObject instance that implements AtkValueIface
+ * @new_value: a double which is the desired new accessible value.
+ *
+ * Sets the value of this object.
+ *
+ * This method is intended to provide a way to change the value of the
+ * object. In any case, it is possible that the value can't be
+ * modified (ie: a read-only component). If the value changes due this
+ * call, it is possible that the text could change, and will trigger
+ * an #AtkValue::value-changed signal emission.
+ *
+ * Since: 2.12
+ **/
+static void jaw_value_set_value(AtkValue *obj, const gdouble value) {
+ JAW_DEBUG("%p, %lf", obj, value);
+
+ if (obj == NULL) {
+ g_warning("%s: Null argument obj passed to the function", G_STRFUNC);
+ return;
+ }
+
+ JAW_GET_VALUE(obj, ); // create local JNI reference `jobject atk_value`
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return;
+ }
+
+ jobject jdoubleValue = (*jniEnv)->NewObject(
+ jniEnv, cachedValueDoubleClass, cachedValueDoubleConstructorMethod,
+ (jdouble)value);
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || jdoubleValue == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to create Double object", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->CallVoidMethod(jniEnv, atk_value, cachedValueSetValueMethod,
+ jdoubleValue);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Exception occurred while calling set_value", G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+}
+
+/**
+ * jaw_value_convert_double_to_gdouble:
+ * @jniEnv: JNI environment
+ * @jdouble: a Java Double object (jobject)
+ * @result: (out): pointer to store the converted double value
+ *
+ * Converts a Java Double object to a gdouble primitive value.
+ *
+ * Returns: TRUE if conversion was successful, FALSE if jdouble is NULL
+ **/
+static gboolean jaw_value_convert_double_to_gdouble(JNIEnv *jniEnv,
+ jobject jdouble,
+ gdouble *result) {
+ if (jdouble == NULL) {
+ return FALSE;
+ }
+
+ *result = (gdouble)(*jniEnv)->CallDoubleMethod(
+ jniEnv, jdouble, cachedValueDoubleValueMethod);
+ return TRUE;
+}
+
+/**
+ * jaw_value_get_range:
+ * @obj: a GObject instance that implements AtkValueIface
+ *
+ * Gets the range of this object.
+ *
+ * Returns: (nullable) (transfer full): a newly allocated #AtkRange
+ * that represents the minimum, maximum and descriptor (if available)
+ * of @obj. NULL if that range is not defined.
+ *
+ * In Atk Since: 2.12
+ **/
+static AtkRange *jaw_value_get_range(AtkValue *obj) {
+ JAW_DEBUG("%p", obj);
+
+ if (obj == NULL) {
+ g_warning("%s: Null argument obj passed to the function", G_STRFUNC);
+ return NULL;
+ }
+
+ JAW_GET_VALUE(obj, NULL);
+
+ if ((*jniEnv)->PushLocalFrame(jniEnv, JAW_DEFAULT_LOCAL_FRAME_SIZE) < 0) {
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ g_warning("%s: Failed to create a new local reference frame",
+ G_STRFUNC);
+ return NULL;
+ }
+
+ jobject jmin = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_value, cachedValueGetMinimumValueMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Exception occurred while calling get_minimum_value",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ jobject jmax = (*jniEnv)->CallObjectMethod(
+ jniEnv, atk_value, cachedValueGetMaximumValueMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Exception occurred while calling get_maximum_value",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+ return NULL;
+ }
+
+ gdouble min_value, max_value;
+ gboolean has_min =
+ jaw_value_convert_double_to_gdouble(jniEnv, jmin, &min_value);
+ gboolean has_max =
+ jaw_value_convert_double_to_gdouble(jniEnv, jmax, &max_value);
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ (*jniEnv)->PopLocalFrame(jniEnv, NULL);
+
+ // If either min or max is NULL, we cannot construct a valid range
+ if (!has_min || !has_max) {
+ return NULL;
+ }
+
+ AtkRange *ret =
+ atk_range_new(min_value, max_value, NULL); // NULL description
+ return ret;
+}
+
+/**
+ * jaw_value_get_increment:
+ * @obj: a GObject instance that implements AtkValueIface
+ *
+ * Gets the minimum increment by which the value of this object may be
+ * changed. If zero, the minimum increment is undefined, which may
+ * mean that it is limited only by the floating point precision of the
+ * platform.
+ *
+ * Return value: the minimum increment by which the value of this
+ * object may be changed. zero if undefined.
+ *
+ * In atk Since: 2.12
+ **/
+static gdouble jaw_value_get_increment(AtkValue *obj) {
+ JAW_DEBUG("%p", obj);
+
+ if (obj == NULL) {
+ g_warning("%s: Null argument obj passed to the function", G_STRFUNC);
+ return 0;
+ }
+
+ JAW_GET_VALUE(obj, 0);
+
+ gdouble ret = (*jniEnv)->CallDoubleMethod(jniEnv, atk_value,
+ cachedValueGetIncrementMethod);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Exception occurred while calling get_increment",
+ G_STRFUNC);
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+ return 0;
+ }
+
+ (*jniEnv)->DeleteLocalRef(jniEnv, atk_value);
+
+ return ret;
+}
+
+static gboolean jaw_value_init_jni_cache(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return FALSE;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cache_initialized) {
+ g_mutex_unlock(&cache_mutex);
+ return TRUE;
+ }
+
+ jclass localClassAtkValue =
+ (*jniEnv)->FindClass(jniEnv, "org/GNOME/Accessibility/AtkValue");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localClassAtkValue == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find AtkValue class", G_STRFUNC);
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+ }
+
+ cachedValueAtkValueClass =
+ (*jniEnv)->NewGlobalRef(jniEnv, localClassAtkValue);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localClassAtkValue);
+
+ if (cachedValueAtkValueClass == NULL) {
+ g_warning("%s: Failed to create global reference for AtkValue class",
+ G_STRFUNC);
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+ }
+
+ cachedValueCreateAtkValueMethod = (*jniEnv)->GetStaticMethodID(
+ jniEnv, cachedValueAtkValueClass, "create_atk_value",
+ "(Ljavax/accessibility/AccessibleContext;)Lorg/GNOME/Accessibility/"
+ "AtkValue;");
+
+ cachedValueGetCurrentValueMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedValueAtkValueClass,
+ "get_current_value", "()Ljava/lang/Number;");
+
+ cachedValueSetValueMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedValueAtkValueClass, "set_value", "(Ljava/lang/Number;)V");
+
+ cachedValueGetMinimumValueMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedValueAtkValueClass,
+ "get_minimum_value", "()Ljava/lang/Double;");
+
+ cachedValueGetMaximumValueMethod =
+ (*jniEnv)->GetMethodID(jniEnv, cachedValueAtkValueClass,
+ "get_maximum_value", "()Ljava/lang/Double;");
+
+ cachedValueGetIncrementMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedValueAtkValueClass, "get_increment", "()D");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedValueCreateAtkValueMethod == NULL ||
+ cachedValueGetCurrentValueMethod == NULL ||
+ cachedValueSetValueMethod == NULL ||
+ cachedValueGetMinimumValueMethod == NULL ||
+ cachedValueGetMaximumValueMethod == NULL ||
+ cachedValueGetIncrementMethod == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache one or more AtkValue method IDs",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ jclass localByte = (*jniEnv)->FindClass(jniEnv, "java/lang/Byte");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localByte == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find Byte class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+ cachedValueByteClass = (*jniEnv)->NewGlobalRef(jniEnv, localByte);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localByte);
+ if (cachedValueByteClass == NULL) {
+ g_warning("%s: Failed to create global reference for Byte class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ jclass localDouble = (*jniEnv)->FindClass(jniEnv, "java/lang/Double");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localDouble == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find Double class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+ cachedValueDoubleClass = (*jniEnv)->NewGlobalRef(jniEnv, localDouble);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localDouble);
+ if (cachedValueDoubleClass == NULL) {
+ g_warning("%s: Failed to create global reference for Double class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ jclass localFloat = (*jniEnv)->FindClass(jniEnv, "java/lang/Float");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localFloat == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find Float class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+ cachedValueFloatClass = (*jniEnv)->NewGlobalRef(jniEnv, localFloat);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localFloat);
+ if (cachedValueFloatClass == NULL) {
+ g_warning("%s: Failed to create global reference for Float class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ jclass localInteger = (*jniEnv)->FindClass(jniEnv, "java/lang/Integer");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localInteger == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find Integer class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+ cachedValueIntegerClass = (*jniEnv)->NewGlobalRef(jniEnv, localInteger);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localInteger);
+ if (cachedValueIntegerClass == NULL) {
+ g_warning("%s: Failed to create global reference for Integer class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ jclass localLong = (*jniEnv)->FindClass(jniEnv, "java/lang/Long");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localLong == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find Long class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+ cachedValueLongClass = (*jniEnv)->NewGlobalRef(jniEnv, localLong);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localLong);
+ if (cachedValueLongClass == NULL) {
+ g_warning("%s: Failed to create global reference for Long class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ jclass localShort = (*jniEnv)->FindClass(jniEnv, "java/lang/Short");
+ if ((*jniEnv)->ExceptionCheck(jniEnv) || localShort == NULL) {
+ jaw_jni_clear_exception(jniEnv);
+ g_warning("%s: Failed to find Short class", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+ cachedValueShortClass = (*jniEnv)->NewGlobalRef(jniEnv, localShort);
+ (*jniEnv)->DeleteLocalRef(jniEnv, localShort);
+ if (cachedValueShortClass == NULL) {
+ g_warning("%s: Failed to create global reference for Short class",
+ G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cachedValueByteValueMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedValueByteClass, "byteValue", "()B");
+ cachedValueDoubleValueMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedValueDoubleClass, "doubleValue", "()D");
+ cachedValueFloatValueMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedValueFloatClass, "floatValue", "()F");
+ cachedValueIntValueMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedValueIntegerClass, "intValue", "()I");
+ cachedValueLongValueMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedValueLongClass, "longValue", "()J");
+ cachedValueShortValueMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedValueShortClass, "shortValue", "()S");
+ cachedValueDoubleConstructorMethod = (*jniEnv)->GetMethodID(
+ jniEnv, cachedValueDoubleClass, "", "(D)V");
+
+ if ((*jniEnv)->ExceptionCheck(jniEnv) ||
+ cachedValueByteValueMethod == NULL ||
+ cachedValueDoubleValueMethod == NULL ||
+ cachedValueFloatValueMethod == NULL ||
+ cachedValueIntValueMethod == NULL ||
+ cachedValueLongValueMethod == NULL ||
+ cachedValueShortValueMethod == NULL ||
+ cachedValueDoubleConstructorMethod == NULL) {
+
+ jaw_jni_clear_exception(jniEnv);
+
+ g_warning("%s: Failed to cache Number wrapper method IDs", G_STRFUNC);
+ goto cleanup_and_fail;
+ }
+
+ cache_initialized = TRUE;
+ g_mutex_unlock(&cache_mutex);
+
+ g_debug("%s: classes and methods cached successfully", G_STRFUNC);
+
+ return TRUE;
+
+cleanup_and_fail:
+ if (cachedValueAtkValueClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueAtkValueClass);
+ cachedValueAtkValueClass = NULL;
+ }
+ if (cachedValueByteClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueByteClass);
+ cachedValueByteClass = NULL;
+ }
+ if (cachedValueDoubleClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueDoubleClass);
+ cachedValueDoubleClass = NULL;
+ }
+ if (cachedValueFloatClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueFloatClass);
+ cachedValueFloatClass = NULL;
+ }
+ if (cachedValueIntegerClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueIntegerClass);
+ cachedValueIntegerClass = NULL;
+ }
+ if (cachedValueLongClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueLongClass);
+ cachedValueLongClass = NULL;
+ }
+ if (cachedValueShortClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueShortClass);
+ cachedValueShortClass = NULL;
+ }
+
+ cachedValueCreateAtkValueMethod = NULL;
+ cachedValueGetCurrentValueMethod = NULL;
+ cachedValueSetValueMethod = NULL;
+ cachedValueGetMinimumValueMethod = NULL;
+ cachedValueGetMaximumValueMethod = NULL;
+ cachedValueGetIncrementMethod = NULL;
+ cachedValueByteValueMethod = NULL;
+ cachedValueDoubleValueMethod = NULL;
+ cachedValueFloatValueMethod = NULL;
+ cachedValueIntValueMethod = NULL;
+ cachedValueLongValueMethod = NULL;
+ cachedValueShortValueMethod = NULL;
+ cachedValueDoubleConstructorMethod = NULL;
+
+ g_mutex_unlock(&cache_mutex);
+ return FALSE;
+}
+
+void jaw_value_cache_cleanup(JNIEnv *jniEnv) {
+ JAW_DEBUG("JNIEnv: %p", jniEnv);
+
+ if (jniEnv == NULL) {
+ g_warning("%s: jniEnv == NULL", G_STRFUNC);
+ return;
+ }
+
+ g_mutex_lock(&cache_mutex);
+
+ if (cachedValueAtkValueClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueAtkValueClass);
+ cachedValueAtkValueClass = NULL;
+ }
+ if (cachedValueByteClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueByteClass);
+ cachedValueByteClass = NULL;
+ }
+ if (cachedValueDoubleClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueDoubleClass);
+ cachedValueDoubleClass = NULL;
+ }
+ if (cachedValueFloatClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueFloatClass);
+ cachedValueFloatClass = NULL;
+ }
+ if (cachedValueIntegerClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueIntegerClass);
+ cachedValueIntegerClass = NULL;
+ }
+ if (cachedValueLongClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueLongClass);
+ cachedValueLongClass = NULL;
+ }
+ if (cachedValueShortClass != NULL) {
+ (*jniEnv)->DeleteGlobalRef(jniEnv, cachedValueShortClass);
+ cachedValueShortClass = NULL;
+ }
+ cachedValueCreateAtkValueMethod = NULL;
+ cachedValueGetCurrentValueMethod = NULL;
+ cachedValueSetValueMethod = NULL;
+ cachedValueGetMinimumValueMethod = NULL;
+ cachedValueGetMaximumValueMethod = NULL;
+ cachedValueGetIncrementMethod = NULL;
+ cachedValueByteValueMethod = NULL;
+ cachedValueDoubleValueMethod = NULL;
+ cachedValueFloatValueMethod = NULL;
+ cachedValueIntValueMethod = NULL;
+ cachedValueLongValueMethod = NULL;
+ cachedValueShortValueMethod = NULL;
+ cachedValueDoubleConstructorMethod = NULL;
+ cache_initialized = FALSE;
+
+ g_mutex_unlock(&cache_mutex);
+}
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file