/*
* Java ATK Wrapper for GNOME
* Copyright (C) 2009 Sun Microsystems Inc.
* Copyright (C) 2015 Magdalen Berns <m.berns@thismagpie.com>
*
* 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;
public class AtkWrapper {
static boolean accessibilityEnabled = false;
static {
try {
Process p = Runtime.getRuntime().exec("@XPROP@ -root");
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) {
System.loadLibrary("atk-wrapper");
if (AtkWrapper.initNativeLibrary())
accessibilityEnabled = true;
break;
}
}
} catch (Exception e) {
e.printStackTrace();
e.getCause();
}
}
/**
* winAdapter
*
* <http://docs.oracle.com/javase/8/docs/api/java/awt/event/WindowAdapter.html>
* <http://docs.oracle.com/javase/8/docs/api/java/awt/event/WindowEvent.html>
*/
final WindowAdapter winAdapter = new WindowAdapter() {
/**
* windowActivated:
* Invoked when a Window becomes the active Window.
* Only a Frame or a Dialog can be the active Window. The
* native windowing system may denote the active Window
* or its children with special decorations, such as a
* highlighted title bar. The active Window is always either
* the focused Window, or the first Frame or Dialog that is
* an owner of the focused Window.
* @param e A WindowEvent object
*/
public void windowActivated(WindowEvent e) {
Object o = e.getSource();
if ( o instanceof javax.accessibility.Accessible ) {
AccessibleContext ac = ((javax.accessibility.Accessible)o).getAccessibleContext();
AtkWrapper.windowActivate(ac);
}
}
/**
* windowDeactivated:
* Invoked when a Window is no longer the active Window.
* Only a Frame or a Dialog can be the active Window. The
* native windowing system may denote the active Window
* or its children with special decorations, such as a
* highlighted title bar. The active Window is always either
* the focused Window, or the first Frame or Dialog that is
* an owner of the focused Window.
* @param e A WindowEvent object
*/
public void windowDeactivated(WindowEvent e) {
Object o = e.getSource();
if (o instanceof javax.accessibility.Accessible) {
AccessibleContext ac = ((javax.accessibility.Accessible)o).getAccessibleContext();
AtkWrapper.windowDeactivate(ac);
}
}
/**
* windowStateChanged:
* Invoked when a window state is changed.
*
* @param e A WindowEvent object
*/
public void windowStateChanged(WindowEvent e) {
Object o = e.getSource();
if (o instanceof javax.accessibility.Accessible) {
AccessibleContext ac = ((javax.accessibility.Accessible)o).getAccessibleContext();
AtkWrapper.windowStateChange(ac);
if( (e.getNewState() & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH ) {
AtkWrapper.windowMaximize(ac);
}
}
}
/**
* windowDeiconified:
* Invoked when a window is deiconified.
* @param e A WindowEvent instance
*/
public void windowDeiconified(WindowEvent e) {
Object o = e.getSource();
if (o instanceof javax.accessibility.Accessible) {
AccessibleContext ac = ((javax.accessibility.Accessible)o).getAccessibleContext();
AtkWrapper.windowRestore(ac);
}
}
/**
* windowIconified:
* Invoked when a window is iconified.
* @param e A WindowEvent instance
*/
public void windowIconified(WindowEvent e) {
Object o = e.getSource();
if (o instanceof javax.accessibility.Accessible) {
AccessibleContext ac = ((javax.accessibility.Accessible)o).getAccessibleContext();
AtkWrapper.windowMinimize(ac);
}
}
/**
* windowOpened
* @param e A WindowEvent object
*/
public void windowOpened(WindowEvent e) {
Object o = e.getSource();
if ( o instanceof javax.accessibility.Accessible ) {
boolean isToplevel = isToplevel(o);
AccessibleContext ac = ((javax.accessibility.Accessible)o).getAccessibleContext();
AtkWrapper.windowOpen(ac, isToplevel);
}
}
/**
* windowClosed:
* Invoked when a window has been closed.
* @param e A WindowEvent object
*/
public void windowClosed(WindowEvent e) {
Object o = e.getSource();
if (o instanceof javax.accessibility.Accessible) {
boolean isToplevel = isToplevel(o);
AccessibleContext ac = ((javax.accessibility.Accessible)o).getAccessibleContext();
AtkWrapper.windowClose(ac, isToplevel);
}
}
/**
* windowClosing:
* Invoked when a window is in the process of being closed.
* @param e A WindowEvent object
*/
public void windowClosing(WindowEvent e) {
Object o = e.getSource();
if (o instanceof javax.accessibility.Accessible) {
boolean isToplevel = isToplevel(o);
AccessibleContext ac = ((javax.accessibility.Accessible)o).getAccessibleContext();
AtkWrapper.windowClose(ac, isToplevel);
}
}
/**
* windowGainedFocus:
* Invoked when the Window is set to be the focused Window,
* which means that the Window, or one of its subcomponents,
* will receive keyboard events.
* @param e A WindowEvent object
*/
public void windowGainedFocus(WindowEvent e) {
}
public void windowLostFocus(WindowEvent e) {
}
}; // Close WindowAdapter brace
/**
* isToplevel
* @param o an instance
* @return true if instance is of a window, frame or dialogue object
* false otherwise.
*/
public 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;
}
final AWTEventListener globalListener = new AWTEventListener() {
private boolean firstEvent = true;
public void eventDispatched(AWTEvent e) {
if (firstEvent && accessibilityEnabled) {
firstEvent = false;
try {
AtkWrapper.loadAtkBridge();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if(e instanceof WindowEvent) {
switch( e.getID() ) {
case WindowEvent.WINDOW_OPENED:
Window win = ((WindowEvent)e).getWindow();
win.addWindowListener(winAdapter);
win.addWindowStateListener(winAdapter);
win.addWindowFocusListener(winAdapter);
break;
case WindowEvent.WINDOW_LOST_FOCUS:
AtkWrapper.dispatchFocusEvent(null);
break;
default:
break;
}
} else if(e instanceof ContainerEvent ) {
switch( e.getID() ) {
case ContainerEvent.COMPONENT_ADDED:
{
java.awt.Component c = ((ContainerEvent)e).getChild();
if (c instanceof javax.accessibility.Accessible) {
AccessibleContext ac = ((javax.accessibility.Accessible)c).getAccessibleContext();
AtkWrapper.componentAdded(ac);
}
break;
}
case ContainerEvent.COMPONENT_REMOVED:
{
java.awt.Component c = ((ContainerEvent)e).getChild();
if (c instanceof javax.accessibility.Accessible) {
AccessibleContext ac = ((javax.accessibility.Accessible)c).getAccessibleContext();
AtkWrapper.componentRemoved(ac);
}
break;
}
default:
break;
}
} else if(e instanceof FocusEvent) {
switch(e.getID()) {
case FocusEvent.FOCUS_GAINED:
AtkWrapper.dispatchFocusEvent(e.getSource());
break;
default:
break;
}
}
}
};
static AccessibleContext oldSourceContext = null;
static AccessibleContext savedSourceContext= null;
static AccessibleContext oldPaneContext = null;
/**
* dispatchFocusEvent
* @param eventSource An instance of the event source object.
*/
static void dispatchFocusEvent(Object eventSource) {
if (eventSource == null) {
oldSourceContext = null;
return;
}
AccessibleContext ctx;
try {
if (eventSource instanceof AccessibleContext) {
ctx = (AccessibleContext)eventSource;
} else if (eventSource instanceof javax.accessibility.Accessible) {
ctx = ((javax.accessibility.Accessible)eventSource).getAccessibleContext();
} else {
return;
}
if (ctx == 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 = ctx.getClass().getName();
if (jrootpane.compareTo(name) == 0) {
oldPaneContext = ctx;
return;
}
}
savedSourceContext = ctx;
} else if (oldPaneContext == ctx) {
ctx = savedSourceContext;
} else {
savedSourceContext = ctx;
}
oldSourceContext = ctx;
AccessibleRole role = ctx.getAccessibleRole();
if (role == AccessibleRole.PAGE_TAB_LIST) {
AccessibleSelection accSelection = ctx.getAccessibleSelection();
if (accSelection != null && accSelection.getAccessibleSelectionCount() > 0) {
Object child = accSelection.getAccessibleSelection(0);
if (child instanceof AccessibleContext) {
ctx = (AccessibleContext)child;
} else if (child instanceof javax.accessibility.Accessible) {
ctx = ((javax.accessibility.Accessible)child).getAccessibleContext();
} else {
return;
}
}
}
focusNotify(ctx);
} catch (Exception e) {
e.printStackTrace();
}
}
final Toolkit toolkit = Toolkit.getDefaultToolkit();
static PropertyChangeListener propertyChangeListener = new PropertyChangeListener() {
/**
* propertyChange:
* @param e An instance of the PropertyChangeEvent object.
*/
public void propertyChange(PropertyChangeEvent e) {
Object o = e.getSource();
AccessibleContext ac;
if (o instanceof AccessibleContext) {
ac = (AccessibleContext)o;
} else if (o instanceof javax.accessibility.Accessible) {
ac = ((javax.accessibility.Accessible)o).getAccessibleContext();
} else {
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;
emitSignal(ac, AtkSignal.TEXT_CARET_MOVED, args);
} else if( propertyName.equals(AccessibleContext.ACCESSIBLE_TEXT_PROPERTY) ) {
if (newValue == null) {
return;
}
if (newValue instanceof Integer) {
Object[] args = new Object[1];
args[0] = newValue;
emitSignal(ac, AtkSignal.TEXT_PROPERTY_CHANGED, args);
}
/*
if (oldValue == null && newValue != null) { //insertion event
if (!(newValue instanceof AccessibleTextSequence)) {
return;
}
AccessibleTextSequence newSeq = (AccessibleTextSequence)newValue;
Object[] args = new Object[2];
args[0] = new Integer(newSeq.startIndex);
args[1] = new Integer(newSeq.endIndex - newSeq.startIndex);
emitSignal(ac, AtkSignal.TEXT_PROPERTY_CHANGED_INSERT, args);
} else if (oldValue != null && newValue == null) { //deletion event
if (!(oldValue instanceof AccessibleTextSequence)) {
return;
}
AccessibleTextSequence oldSeq = (AccessibleTextSequence)oldValue;
Object[] args = new Object[2];
args[0] = new Integer(oldSeq.startIndex);
args[1] = new Integer(oldSeq.endIndex - oldSeq.startIndex);
emitSignal(ac, AtkSignal.TEXT_PROPERTY_CHANGED_DELETE, args);
} else if (oldValue != null && newValue != null) { //replacement event
//It seems ATK does not support "replace" currently
return;
}*/
} else if( propertyName.equals(AccessibleContext.ACCESSIBLE_CHILD_PROPERTY) ) {
if (oldValue == null && newValue != null) { //child added
AccessibleContext child_ac;
if (newValue instanceof javax.accessibility.Accessible) {
child_ac = ((javax.accessibility.Accessible)newValue).getAccessibleContext();
} else {
return;
}
Object[] args = new Object[2];
args[0] = new Integer(child_ac.getAccessibleIndexInParent());
args[1] = child_ac;
emitSignal(ac, AtkSignal.OBJECT_CHILDREN_CHANGED_ADD, args);
} else if (oldValue != null && newValue == null) { //child removed
AccessibleContext child_ac;
if (oldValue instanceof javax.accessibility.Accessible) {
child_ac = ((javax.accessibility.Accessible)oldValue).getAccessibleContext();
} else {
return;
}
Object[] args = new Object[2];
args[0] = new Integer(child_ac.getAccessibleIndexInParent());
args[1] = 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 javax.accessibility.Accessible) {
child_ac = ((javax.accessibility.Accessible)newValue).getAccessibleContext();
} else {
return;
}
Object[] args = new Object[1];
args[0] = 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);
}
}
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 && newValue instanceof Number) {
Object[] args = new Object[2];
args[0] = new Double(((Number)oldValue).doubleValue());
args[1] = new Double(((Number)newValue).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)) {
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)) {
javax.accessibility.Accessible parent = ac.getAccessibleParent();
javax.accessibility.AccessibleRole role = ac.getAccessibleRole();
javax.accessibility.AccessibleRole parent_role = null;
if (parent != null) {
parent_role = parent.getAccessibleContext().getAccessibleRole();
}
if (role != null) {
if (newValue == javax.accessibility.AccessibleState.FOCUSED ||
newValue == javax.accessibility.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 javax.swing.JComboBox && oldValue ==
javax.accessibility.AccessibleState.VISIBLE) {
objectStateChange(ac, AccessibleState.SHOWING, value);
}
objectStateChange(ac, state, value);
if (parent instanceof javax.swing.JComboBox && newValue ==
javax.accessibility.AccessibleState.VISIBLE && oldValue == null) {
objectStateChange(ac, AccessibleState.SHOWING, value);
}
}
}
};
public static void registerPropertyChangeListener(AccessibleContext ac) {
if (ac != null) {
try {
ac.addPropertyChangeListener(propertyChangeListener);
} catch (Exception e){
e.printStackTrace();
}
}
}
public native static boolean initNativeLibrary();
public native static void loadAtkBridge();
public native static void focusNotify(javax.accessibility.AccessibleContext ac);
public native static void windowOpen(javax.accessibility.AccessibleContext ac,
boolean isToplevel);
public native static void windowClose(javax.accessibility.AccessibleContext ac,
boolean isToplevel);
public native static void windowMinimize(javax.accessibility.AccessibleContext ac);
public native static void windowMaximize(javax.accessibility.AccessibleContext ac);
public native static void windowRestore(javax.accessibility.AccessibleContext ac);
public native static void windowActivate(javax.accessibility.AccessibleContext ac);
public native static void windowDeactivate(javax.accessibility.AccessibleContext ac);
public native static void windowStateChange(javax.accessibility.AccessibleContext ac);
public native static void emitSignal(javax.accessibility.AccessibleContext ac, int id, Object[] args);
public native static void objectStateChange(javax.accessibility.AccessibleContext ac,
java.lang.Object state, boolean value);
public native static void componentAdded(javax.accessibility.AccessibleContext ac);
public native static void componentRemoved(javax.accessibility.AccessibleContext ac);
public native static boolean dispatchKeyEvent(AtkKeyEvent e);
public static void printLog(String str) {
System.out.println(str);
}
public AtkWrapper() {
if (!accessibilityEnabled)
return;
toolkit.addAWTEventListener(globalListener,
AWTEvent.WINDOW_EVENT_MASK |
AWTEvent.FOCUS_EVENT_MASK |
AWTEvent.CONTAINER_EVENT_MASK);
toolkit.getSystemEventQueue().push(new EventQueue() {
boolean previousPressConsumed = false;
public void dispatchEvent(AWTEvent e) {
if (e instanceof KeyEvent) {
if (e.getID() == KeyEvent.KEY_PRESSED) {
boolean isConsumed = AtkWrapper.dispatchKeyEvent(new AtkKeyEvent((KeyEvent)e));
if (isConsumed) {
previousPressConsumed = true;
return;
}
} else if (e.getID() == KeyEvent.KEY_TYPED) {
if (previousPressConsumed) {
return;
}
} else if (e.getID() == KeyEvent.KEY_RELEASED) {
boolean isConsumed = AtkWrapper.dispatchKeyEvent(new AtkKeyEvent((KeyEvent)e));
previousPressConsumed = false;
if (isConsumed) {
return;
}
}
}
super.dispatchEvent(e);
}
});
}
public static void main(String args[]) {
new AtkWrapper();
}
}